2020-03-14 01:46:14 +02:00
|
|
|
const { app } = require('./app.js');
|
2020-11-07 17:59:37 +02:00
|
|
|
const Note = require('@joplin/lib/models/Note.js');
|
|
|
|
const Folder = require('@joplin/lib/models/Folder.js');
|
|
|
|
const Tag = require('@joplin/lib/models/Tag.js');
|
2020-03-14 01:46:14 +02:00
|
|
|
const { cliUtils } = require('./cli-utils.js');
|
|
|
|
const yargParser = require('yargs-parser');
|
|
|
|
const fs = require('fs-extra');
|
2017-12-14 05:13:43 +02:00
|
|
|
|
|
|
|
async function handleAutocompletionPromise(line) {
|
2017-12-14 05:25:18 +02:00
|
|
|
// Auto-complete the command name
|
|
|
|
const names = await app().commandNames();
|
2020-03-14 01:46:14 +02:00
|
|
|
const words = getArguments(line);
|
2019-10-09 21:35:13 +02:00
|
|
|
// If there is only one word and it is not already a command name then you
|
2019-10-29 11:02:42 +02:00
|
|
|
// should look for commands it could be
|
2017-12-14 05:25:18 +02:00
|
|
|
if (words.length == 1) {
|
|
|
|
if (names.indexOf(words[0]) === -1) {
|
2020-05-21 10:14:33 +02:00
|
|
|
const x = names.filter(n => n.indexOf(words[0]) === 0);
|
2017-12-14 15:53:49 +02:00
|
|
|
if (x.length === 1) {
|
2019-09-19 23:51:18 +02:00
|
|
|
return `${x[0]} `;
|
2017-12-14 15:53:49 +02:00
|
|
|
}
|
2020-05-21 10:14:33 +02:00
|
|
|
return x.length > 0 ? x.map(a => `${a} `) : line;
|
2017-12-14 05:25:18 +02:00
|
|
|
} else {
|
|
|
|
return line;
|
|
|
|
}
|
|
|
|
}
|
2019-10-09 21:35:13 +02:00
|
|
|
// There is more than one word and it is a command
|
2017-12-14 05:25:18 +02:00
|
|
|
const metadata = (await app().commandMetadata())[words[0]];
|
2019-10-09 21:35:13 +02:00
|
|
|
// If for some reason this command does not have any associated metadata
|
|
|
|
// just don't autocomplete. However, this should not happen.
|
2017-12-14 05:25:18 +02:00
|
|
|
if (metadata === undefined) {
|
|
|
|
return line;
|
|
|
|
}
|
2019-10-30 11:43:47 +02:00
|
|
|
|
|
|
|
if (words[0] === 'tag' && words[1] === 'notetags') {
|
|
|
|
metadata.usage = 'tag <tag-command> <note>';
|
|
|
|
}
|
|
|
|
|
2019-10-09 21:35:13 +02:00
|
|
|
// complete an option
|
2020-03-14 01:46:14 +02:00
|
|
|
const next = words.length > 1 ? words[words.length - 1] : '';
|
|
|
|
const l = [];
|
2017-12-14 15:53:49 +02:00
|
|
|
if (next[0] === '-') {
|
2019-07-30 09:35:42 +02:00
|
|
|
for (let i = 0; i < metadata.options.length; i++) {
|
2017-12-14 15:53:49 +02:00
|
|
|
const options = metadata.options[i][0].split(' ');
|
2019-10-09 21:35:13 +02:00
|
|
|
// if there are multiple options then they will be separated by comma and
|
|
|
|
// space. The comma should be removed
|
2017-12-14 15:53:49 +02:00
|
|
|
if (options[0][options[0].length - 1] === ',') {
|
|
|
|
options[0] = options[0].slice(0, -1);
|
|
|
|
}
|
|
|
|
if (words.includes(options[0]) || words.includes(options[1])) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-10-09 21:35:13 +02:00
|
|
|
// First two elements are the flag and the third is the description
|
|
|
|
// Only autocomplete long
|
2017-12-14 15:53:49 +02:00
|
|
|
if (options.length > 1 && options[1].indexOf(next) === 0) {
|
2017-12-24 20:27:25 +02:00
|
|
|
l.push(options[1]);
|
2017-12-14 15:53:49 +02:00
|
|
|
} else if (options[0].indexOf(next) === 0) {
|
2018-01-18 22:29:13 +02:00
|
|
|
l.push(options[0]);
|
2017-12-23 03:48:56 +02:00
|
|
|
}
|
2017-12-14 05:25:18 +02:00
|
|
|
}
|
2017-12-24 20:27:25 +02:00
|
|
|
if (l.length === 0) {
|
|
|
|
return line;
|
|
|
|
}
|
2020-05-21 10:14:33 +02:00
|
|
|
const ret = l.map(a => toCommandLine(a));
|
2019-09-19 23:51:18 +02:00
|
|
|
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
2017-12-24 20:27:25 +02:00
|
|
|
return ret;
|
2017-12-14 05:25:18 +02:00
|
|
|
}
|
2019-10-09 21:35:13 +02:00
|
|
|
// Complete an argument
|
|
|
|
// Determine the number of positional arguments by counting the number of
|
|
|
|
// words that don't start with a - less one for the command name
|
2020-05-21 10:14:33 +02:00
|
|
|
const positionalArgs = words.filter(a => a.indexOf('-') !== 0).length - 1;
|
2017-12-14 15:53:49 +02:00
|
|
|
|
2020-03-14 01:46:14 +02:00
|
|
|
const cmdUsage = yargParser(metadata.usage)['_'];
|
2017-12-14 05:25:18 +02:00
|
|
|
cmdUsage.splice(0, 1);
|
2017-12-14 05:13:43 +02:00
|
|
|
|
2017-12-23 03:48:56 +02:00
|
|
|
if (cmdUsage.length >= positionalArgs) {
|
|
|
|
let argName = cmdUsage[positionalArgs - 1];
|
2017-12-14 15:53:49 +02:00
|
|
|
argName = cliUtils.parseCommandArg(argName).name;
|
2017-12-14 05:13:43 +02:00
|
|
|
|
2018-01-28 20:19:56 +02:00
|
|
|
const currentFolder = app().currentFolder();
|
|
|
|
|
|
|
|
if (argName == 'note' || argName == 'note-pattern') {
|
2019-09-19 23:51:18 +02:00
|
|
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: `${next}*` }) : [];
|
2020-05-21 10:14:33 +02:00
|
|
|
l.push(...notes.map(n => n.title));
|
2017-12-14 15:53:49 +02:00
|
|
|
}
|
2017-12-14 05:13:43 +02:00
|
|
|
|
2017-12-14 15:53:49 +02:00
|
|
|
if (argName == 'notebook') {
|
2019-09-19 23:51:18 +02:00
|
|
|
const folders = await Folder.search({ titlePattern: `${next}*` });
|
2020-05-21 10:14:33 +02:00
|
|
|
l.push(...folders.map(n => n.title));
|
2017-12-14 15:53:49 +02:00
|
|
|
}
|
2017-12-14 05:13:43 +02:00
|
|
|
|
2018-01-28 16:12:54 +02:00
|
|
|
if (argName == 'item') {
|
2019-09-19 23:51:18 +02:00
|
|
|
const notes = currentFolder ? await Note.previews(currentFolder.id, { titlePattern: `${next}*` }) : [];
|
|
|
|
const folders = await Folder.search({ titlePattern: `${next}*` });
|
2020-05-21 10:14:33 +02:00
|
|
|
l.push(...notes.map(n => n.title), folders.map(n => n.title));
|
2018-01-28 16:12:54 +02:00
|
|
|
}
|
|
|
|
|
2017-12-14 15:53:49 +02:00
|
|
|
if (argName == 'tag') {
|
2020-03-14 01:46:14 +02:00
|
|
|
const tags = await Tag.search({ titlePattern: `${next}*` });
|
2020-05-21 10:14:33 +02:00
|
|
|
l.push(...tags.map(n => n.title));
|
2018-01-28 16:12:54 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (argName == 'file') {
|
2020-03-14 01:46:14 +02:00
|
|
|
const files = await fs.readdir('.');
|
2018-01-28 16:12:54 +02:00
|
|
|
l.push(...files);
|
|
|
|
}
|
2017-12-14 05:13:43 +02:00
|
|
|
|
2017-12-14 15:53:49 +02:00
|
|
|
if (argName == 'tag-command') {
|
2020-03-14 01:46:14 +02:00
|
|
|
const c = filterList(['add', 'remove', 'list', 'notetags'], next);
|
2017-12-24 20:27:25 +02:00
|
|
|
l.push(...c);
|
2017-12-14 15:53:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if (argName == 'todo-command') {
|
2020-03-14 01:46:14 +02:00
|
|
|
const c = filterList(['toggle', 'clear'], next);
|
2017-12-24 20:27:25 +02:00
|
|
|
l.push(...c);
|
2017-12-14 15:53:49 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (l.length === 1) {
|
2017-12-24 20:27:25 +02:00
|
|
|
return toCommandLine([...words.slice(0, -1), l[0]]);
|
2017-12-14 15:53:49 +02:00
|
|
|
} else if (l.length > 1) {
|
2020-05-21 10:14:33 +02:00
|
|
|
const ret = l.map(a => toCommandLine(a));
|
2019-09-19 23:51:18 +02:00
|
|
|
ret.prefix = `${toCommandLine(words.slice(0, -1))} `;
|
2017-12-24 20:27:25 +02:00
|
|
|
return ret;
|
2017-12-14 05:25:18 +02:00
|
|
|
}
|
2017-12-14 15:53:49 +02:00
|
|
|
return line;
|
2017-12-14 05:13:43 +02:00
|
|
|
}
|
|
|
|
function handleAutocompletion(str, callback) {
|
2017-12-14 05:25:18 +02:00
|
|
|
handleAutocompletionPromise(str).then(function(res) {
|
|
|
|
callback(undefined, res);
|
|
|
|
});
|
2017-12-14 05:13:43 +02:00
|
|
|
}
|
2017-12-23 03:48:56 +02:00
|
|
|
function toCommandLine(args) {
|
2017-12-24 20:27:25 +02:00
|
|
|
if (Array.isArray(args)) {
|
2019-07-30 09:35:42 +02:00
|
|
|
return args
|
|
|
|
.map(function(a) {
|
|
|
|
if (a.indexOf('"') !== -1 || a.indexOf(' ') !== -1) {
|
2019-09-19 23:51:18 +02:00
|
|
|
return `'${a}'`;
|
2019-07-30 09:35:42 +02:00
|
|
|
} else if (a.indexOf('\'') !== -1) {
|
2019-09-19 23:51:18 +02:00
|
|
|
return `"${a}"`;
|
2019-07-30 09:35:42 +02:00
|
|
|
} else {
|
|
|
|
return a;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.join(' ');
|
2017-12-24 20:27:25 +02:00
|
|
|
} else {
|
2019-07-30 09:35:42 +02:00
|
|
|
if (args.indexOf('"') !== -1 || args.indexOf(' ') !== -1) {
|
2019-09-19 23:51:18 +02:00
|
|
|
return `'${args}' `;
|
2019-07-30 09:35:42 +02:00
|
|
|
} else if (args.indexOf('\'') !== -1) {
|
2019-09-19 23:51:18 +02:00
|
|
|
return `"${args}" `;
|
2017-12-23 03:48:56 +02:00
|
|
|
} else {
|
2019-09-19 23:51:18 +02:00
|
|
|
return `${args} `;
|
2017-12-23 03:48:56 +02:00
|
|
|
}
|
2017-12-24 20:27:25 +02:00
|
|
|
}
|
2017-12-23 03:48:56 +02:00
|
|
|
}
|
|
|
|
function getArguments(line) {
|
|
|
|
let inSingleQuotes = false;
|
|
|
|
let inDoubleQuotes = false;
|
|
|
|
let currentWord = '';
|
2020-03-14 01:46:14 +02:00
|
|
|
const parsed = [];
|
2019-07-30 09:35:42 +02:00
|
|
|
for (let i = 0; i < line.length; i++) {
|
|
|
|
if (line[i] === '"') {
|
|
|
|
if (inDoubleQuotes) {
|
2017-12-23 03:48:56 +02:00
|
|
|
inDoubleQuotes = false;
|
2019-10-09 21:35:13 +02:00
|
|
|
// maybe push word to parsed?
|
|
|
|
// currentWord += '"';
|
2017-12-23 03:48:56 +02:00
|
|
|
} else {
|
|
|
|
inDoubleQuotes = true;
|
2019-10-09 21:35:13 +02:00
|
|
|
// currentWord += '"';
|
2017-12-23 03:48:56 +02:00
|
|
|
}
|
2019-07-30 09:35:42 +02:00
|
|
|
} else if (line[i] === '\'') {
|
|
|
|
if (inSingleQuotes) {
|
2017-12-23 03:48:56 +02:00
|
|
|
inSingleQuotes = false;
|
2019-10-09 21:35:13 +02:00
|
|
|
// maybe push word to parsed?
|
|
|
|
// currentWord += "'";
|
2017-12-23 03:48:56 +02:00
|
|
|
} else {
|
|
|
|
inSingleQuotes = true;
|
2019-10-09 21:35:13 +02:00
|
|
|
// currentWord += "'";
|
2017-12-23 03:48:56 +02:00
|
|
|
}
|
2019-07-30 09:35:42 +02:00
|
|
|
} else if (/\s/.test(line[i]) && !(inDoubleQuotes || inSingleQuotes)) {
|
2017-12-23 03:48:56 +02:00
|
|
|
if (currentWord !== '') {
|
|
|
|
parsed.push(currentWord);
|
|
|
|
currentWord = '';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
currentWord += line[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!(inSingleQuotes || inDoubleQuotes) && /\s/.test(line[line.length - 1])) {
|
|
|
|
parsed.push('');
|
|
|
|
} else {
|
|
|
|
parsed.push(currentWord);
|
|
|
|
}
|
|
|
|
return parsed;
|
|
|
|
}
|
2017-12-14 05:13:43 +02:00
|
|
|
function filterList(list, next) {
|
2020-03-14 01:46:14 +02:00
|
|
|
const output = [];
|
2017-12-14 05:25:18 +02:00
|
|
|
for (let i = 0; i < list.length; i++) {
|
|
|
|
if (list[i].indexOf(next) !== 0) continue;
|
|
|
|
output.push(list[i]);
|
|
|
|
}
|
|
|
|
return output;
|
2017-12-14 05:13:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = { handleAutocompletion };
|