From e4d477fb4c544e99b37c0dc82955f64f58d08854 Mon Sep 17 00:00:00 2001 From: Laurent Cozic Date: Fri, 16 Feb 2018 22:53:53 +0000 Subject: [PATCH] CLI: Resolve #63: Allow customizing shortcuts --- CliClient/app/app-gui.js | 335 +++++++++++++++------------------- CliClient/app/app.js | 61 ++++++- CliClient/app/command-help.js | 16 +- CliClient/package-lock.json | 6 +- CliClient/package.json | 2 +- README.md | 3 +- README_terminal.md | 91 +++++++-- docs/index.html | 13 +- docs/terminal/index.html | 146 +++++++++++++-- 9 files changed, 423 insertions(+), 250 deletions(-) diff --git a/CliClient/app/app-gui.js b/CliClient/app/app-gui.js index 906efeb06..9fa16c99b 100644 --- a/CliClient/app/app-gui.js +++ b/CliClient/app/app-gui.js @@ -35,38 +35,55 @@ const ConsoleWidget = require('./gui/ConsoleWidget.js'); class AppGui { - constructor(app, store) { - this.app_ = app; - this.store_ = store; + constructor(app, store, keymap) { + try { + this.app_ = app; + this.store_ = store; - BaseWidget.setLogger(app.logger()); + BaseWidget.setLogger(app.logger()); - this.term_ = new TermWrapper(tk.terminal); + this.term_ = new TermWrapper(tk.terminal); - this.renderer_ = null; - this.logger_ = new Logger(); - this.buildUi(); + // Some keys are directly handled by the tkwidget framework + // so they need to be remapped in a different way. + this.tkWidgetKeys_ = { + 'focus_next': 'TAB', + 'focus_previous': 'SHIFT_TAB', + 'move_up': 'UP', + 'move_down': 'DOWN', + 'page_down': 'PAGE_DOWN', + 'page_up': 'PAGE_UP', + }; - this.renderer_ = new Renderer(this.term(), this.rootWidget_); + this.renderer_ = null; + this.logger_ = new Logger(); + this.buildUi(); - this.app_.on('modelAction', async (event) => { - await this.handleModelAction(event.action); - }); + this.renderer_ = new Renderer(this.term(), this.rootWidget_); - this.shortcuts_ = this.setupShortcuts(); + this.app_.on('modelAction', async (event) => { + await this.handleModelAction(event.action); + }); - this.inputMode_ = AppGui.INPUT_MODE_NORMAL; + this.keymap_ = this.setupKeymap(keymap); - this.commandCancelCalled_ = false; + this.inputMode_ = AppGui.INPUT_MODE_NORMAL; - this.currentShortcutKeys_ = []; - this.lastShortcutKeyTime_ = 0; + this.commandCancelCalled_ = false; - // Recurrent sync is setup only when the GUI is started. In - // a regular command it's not necessary since the process - // exits right away. - reg.setupRecurrentSync(); - DecryptionWorker.instance().scheduleStart(); + this.currentShortcutKeys_ = []; + this.lastShortcutKeyTime_ = 0; + + // Recurrent sync is setup only when the GUI is started. In + // a regular command it's not necessary since the process + // exits right away. + reg.setupRecurrentSync(); + DecryptionWorker.instance().scheduleStart(); + } catch (error) { + this.fullScreen(false); + console.error(error); + process.exit(1); + } } store() { @@ -105,6 +122,7 @@ class AppGui { buildUi() { this.rootWidget_ = new ReduxRootWidget(this.store_); this.rootWidget_.name = 'root'; + this.rootWidget_.autoShortcutsEnabled = false; const folderList = new FolderListWidget(); folderList.style = { @@ -272,152 +290,26 @@ class AppGui { this.stdout(chalk.cyan.bold('> ' + cmd)); } - setupShortcuts() { - const shortcuts = {}; + setupKeymap(keymap) { + const output = []; - shortcuts['TAB'] = { - friendlyName: 'Tab', - description: () => _('Give focus to next pane'), - isDocOnly: true, - } + for (let i = 0; i < keymap.length; i++) { + const item = Object.assign({}, keymap[i]); - shortcuts['SHIFT_TAB'] = { - friendlyName: 'Shift+Tab', - description: () => _('Give focus to previous pane'), - isDocOnly: true, - } + if (!item.command) throw new Error('Missing command for keymap item: ' + JSON.stringify(item)); - shortcuts[':'] = { - description: () => _('Enter command line mode'), - action: async () => { - const cmd = await this.widget('statusBar').prompt(); - if (!cmd) return; - this.addCommandToConsole(cmd); - await this.processCommand(cmd); - }, - }; + if (!('type' in item)) item.type = 'exec'; - shortcuts['ESC'] = { // Built into terminal-kit inputField - description: () => _('Exit command line mode'), - isDocOnly: true, - }; - - shortcuts['ENTER'] = { - description: () => _('Edit the selected note'), - action: () => { - const w = this.widget('mainWindow').focusedWidget; - if (w.name === 'folderList') { - this.widget('noteList').focus(); - } else if (w.name === 'noteList' || w.name === 'noteText') { - this.processCommand('edit $n'); - } - }, - } - - shortcuts['CTRL_C'] = { - description: () => _('Cancel the current command.'), - friendlyName: 'Ctrl+C', - isDocOnly: true, - } - - shortcuts['CTRL_D'] = { - description: () => _('Exit the application.'), - friendlyName: 'Ctrl+D', - isDocOnly: true, - } - - shortcuts['DELETE'] = { - description: () => _('Delete the currently selected note or notebook.'), - action: async () => { - if (this.widget('folderList').hasFocus) { - const item = this.widget('folderList').selectedJoplinItem; - - if (!item) return; - - if (item.type_ === BaseModel.TYPE_FOLDER) { - await this.processCommand('rmbook ' + item.id); - } else if (item.type_ === BaseModel.TYPE_TAG) { - this.stdout(_('To delete a tag, untag the associated notes.')); - } else if (item.type_ === BaseModel.TYPE_SEARCH) { - this.store().dispatch({ - type: 'SEARCH_DELETE', - id: item.id, - }); - } - } else if (this.widget('noteList').hasFocus) { - await this.processCommand('rmnote $n'); - } else { - this.stdout(_('Please select the note or notebook to be deleted first.')); - } + if (item.command in this.tkWidgetKeys_) { + item.type = 'tkwidgets'; } - }; - shortcuts['BACKSPACE'] = { - alias: 'DELETE', - }; + item.canRunAlongOtherCommands = item.type === 'function' && ['toggle_metadata', 'toggle_console'].indexOf(item.command) >= 0; - shortcuts[' '] = { - friendlyName: 'SPACE', - description: () => _('Set a to-do as completed / not completed'), - action: 'todo toggle $n', + output.push(item); } - shortcuts['tc'] = { - description: () => _('[t]oggle [c]onsole between maximized/minimized/hidden/visible.'), - action: () => { - if (!this.consoleIsShown()) { - this.showConsole(); - this.minimizeConsole(); - } else { - if (this.consoleIsMaximized()) { - this.hideConsole(); - } else { - this.maximizeConsole(); - } - } - }, - canRunAlongOtherCommands: true, - } - - shortcuts['/'] = { - description: () => _('Search'), - action: { type: 'prompt', initialText: 'search ""', cursorPosition: -2 }, - }; - - shortcuts['tm'] = { - description: () => _('[t]oggle note [m]etadata.'), - action: () => { - this.toggleNoteMetadata(); - }, - canRunAlongOtherCommands: true, - } - - shortcuts['mn'] = { - description: () => _('[M]ake a new [n]ote'), - action: { type: 'prompt', initialText: 'mknote ""', cursorPosition: -2 }, - } - - shortcuts['mt'] = { - description: () => _('[M]ake a new [t]odo'), - action: { type: 'prompt', initialText: 'mktodo ""', cursorPosition: -2 }, - } - - shortcuts['mb'] = { - description: () => _('[M]ake a new note[b]ook'), - action: { type: 'prompt', initialText: 'mkbook ""', cursorPosition: -2 }, - } - - shortcuts['yn'] = { - description: () => _('Copy ([Y]ank) the [n]ote to a notebook.'), - action: { type: 'prompt', initialText: 'cp $n ""', cursorPosition: -2 }, - } - - shortcuts['dn'] = { - description: () => _('Move the note to a notebook.'), - action: { type: 'prompt', initialText: 'mv $n ""', cursorPosition: -2 }, - } - - return shortcuts; + return output; } toggleConsole() { @@ -492,8 +384,16 @@ class AppGui { return this.logger_; } - shortcuts() { - return this.shortcuts_; + keymap() { + return this.keymap_; + } + + keymapItemByKey(key) { + for (let i = 0; i < this.keymap_.length; i++) { + const item = this.keymap_[i]; + if (item.keys.indexOf(key) >= 0) return item; + } + return null; } term() { @@ -524,18 +424,78 @@ class AppGui { } } - async processCommand(cmd) { + async processFunctionCommand(cmd) { + + if (cmd === 'activate') { + + const w = this.widget('mainWindow').focusedWidget; + if (w.name === 'folderList') { + this.widget('noteList').focus(); + } else if (w.name === 'noteList' || w.name === 'noteText') { + this.processPromptCommand('edit $n'); + } + + } else if (cmd === 'delete') { + + if (this.widget('folderList').hasFocus) { + const item = this.widget('folderList').selectedJoplinItem; + + if (!item) return; + + if (item.type_ === BaseModel.TYPE_FOLDER) { + await this.processPromptCommand('rmbook ' + item.id); + } else if (item.type_ === BaseModel.TYPE_TAG) { + this.stdout(_('To delete a tag, untag the associated notes.')); + } else if (item.type_ === BaseModel.TYPE_SEARCH) { + this.store().dispatch({ + type: 'SEARCH_DELETE', + id: item.id, + }); + } + } else if (this.widget('noteList').hasFocus) { + await this.processPromptCommand('rmnote $n'); + } else { + this.stdout(_('Please select the note or notebook to be deleted first.')); + } + + } else if (cmd === 'toggle_console') { + + if (!this.consoleIsShown()) { + this.showConsole(); + this.minimizeConsole(); + } else { + if (this.consoleIsMaximized()) { + this.hideConsole(); + } else { + this.maximizeConsole(); + } + } + + } else if (cmd === 'toggle_metadata') { + + this.toggleNoteMetadata(); + + } else if (cmd === 'enter_command_line_mode') { + + const cmd = await this.widget('statusBar').prompt(); + if (!cmd) return; + this.addCommandToConsole(cmd); + await this.processPromptCommand(cmd); + + } else { + + throw new Error('Unknown command: ' + cmd); + + } + } + + async processPromptCommand(cmd) { if (!cmd) return; cmd = cmd.trim(); if (!cmd.length) return; this.logger().info('Got command: ' + cmd); - if (cmd === 'q' || cmd === 'wq' || cmd === 'qa') { // Vim bonus - await this.app().exit(); - return; - } - try { let note = this.widget('noteList').currentItem; let folder = this.widget('folderList').currentItem; @@ -786,35 +746,34 @@ class AppGui { // ------------------------------------------------------------------------- const shortcutKey = this.currentShortcutKeys_.join(''); - let cmd = shortcutKey in this.shortcuts_ ? this.shortcuts_[shortcutKey] : null; + let keymapItem = this.keymapItemByKey(shortcutKey); // If this command is an alias to another command, resolve to the actual command - if (cmd && cmd.alias) cmd = this.shortcuts_[cmd.alias]; - let processShortcutKeys = !this.app().currentCommand() && cmd; - if (cmd && cmd.canRunAlongOtherCommands) processShortcutKeys = true; + let processShortcutKeys = !this.app().currentCommand() && keymapItem; + if (keymapItem && keymapItem.canRunAlongOtherCommands) processShortcutKeys = true; if (statusBar.promptActive) processShortcutKeys = false; - if (cmd && cmd.isDocOnly) processShortcutKeys = false; if (processShortcutKeys) { - this.logger().info('Shortcut:', shortcutKey, cmd.description()); + this.logger().info('Shortcut:', shortcutKey, keymapItem); this.currentShortcutKeys_ = []; - if (typeof cmd.action === 'function') { - await cmd.action(); - } else if (typeof cmd.action === 'object') { - if (cmd.action.type === 'prompt') { - let promptOptions = {}; - if ('cursorPosition' in cmd.action) promptOptions.cursorPosition = cmd.action.cursorPosition; - const commandString = await statusBar.prompt(cmd.action.initialText ? cmd.action.initialText : '', null, promptOptions); - this.addCommandToConsole(commandString); - await this.processCommand(commandString); - } else { - throw new Error('Unknown command: ' + JSON.stringify(cmd.action)); - } - } else { // String - this.stdout(cmd.action); - await this.processCommand(cmd.action); + + if (keymapItem.type === 'function') { + this.processFunctionCommand(keymapItem.command); + } else if (keymapItem.type === 'prompt') { + let promptOptions = {}; + if ('cursorPosition' in keymapItem) promptOptions.cursorPosition = keymapItem.cursorPosition; + const commandString = await statusBar.prompt(keymapItem.command ? keymapItem.command : '', null, promptOptions); + this.addCommandToConsole(commandString); + await this.processPromptCommand(commandString); + } else if (keymapItem.type === 'exec') { + this.stdout(keymapItem.command); + await this.processPromptCommand(keymapItem.command); + } else if (keymapItem.type === 'tkwidgets') { + this.widget('root').handleKey(this.tkWidgetKeys_[keymapItem.command]); + } else { + throw new Error('Unknown command type: ' + JSON.stringify(keymapItem)); } } diff --git a/CliClient/app/app.js b/CliClient/app/app.js index 158612deb..383147857 100644 --- a/CliClient/app/app.js +++ b/CliClient/app/app.js @@ -312,6 +312,63 @@ class Application extends BaseApplication { return this.activeCommand_; } + async loadKeymaps() { + const defaultKeyMap = [ + { "keys": [":"], "type": "function", "command": "enter_command_line_mode" }, + { "keys": ["TAB"], "type": "function", "command": "focus_next" }, + { "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" }, + { "keys": ["UP"], "type": "function", "command": "move_up" }, + { "keys": ["DOWN"], "type": "function", "command": "move_down" }, + { "keys": ["PAGE_UP"], "type": "function", "command": "page_up" }, + { "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" }, + { "keys": ["ENTER"], "type": "function", "command": "activate" }, + { "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" }, + { "keys": [" "], "command": "todo toggle $n" }, + { "keys": ["tc"], "type": "function", "command": "toggle_console" }, + { "keys": ["tm"], "type": "function", "command": "toggle_metadata" }, + { "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 }, + { "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 }, + { "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 }, + { "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 }, + { "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 }, + { "keys": ["dn"], "type": "prompt", "command": "mv $n \"\"", "cursorPosition": -2 } + ]; + + // Filter the keymap item by command so that items in keymap.json can override + // the default ones. + const itemsByCommand = {}; + + for (let i = 0; i < defaultKeyMap.length; i++) { + itemsByCommand[defaultKeyMap[i].command] = defaultKeyMap[i] + } + + const filePath = Setting.value('profileDir') + '/keymap.json'; + if (await fs.pathExists(filePath)) { + try { + let configString = await fs.readFile(filePath, 'utf-8'); + configString = configString.replace(/^\s*\/\/.*/, ''); // Strip off comments + const keymap = JSON.parse(configString); + for (let keymapIndex = 0; keymapIndex < keymap.length; keymapIndex++) { + const item = keymap[keymapIndex]; + itemsByCommand[item.command] = item; + } + } catch (error) { + let msg = error.message ? error.message : ''; + msg = 'Could not load keymap ' + filePath + '\n' + msg; + error.message = msg; + throw error; + } + } + + const output = []; + for (let n in itemsByCommand) { + if (!itemsByCommand.hasOwnProperty(n)) continue; + output.push(itemsByCommand[n]); + } + + return output; + } + async start(argv) { argv = await super.start(argv); @@ -338,8 +395,10 @@ class Application extends BaseApplication { } else { // Otherwise open the GUI this.initRedux(); + const keymap = await this.loadKeymaps(); + const AppGui = require('./app-gui.js'); - this.gui_ = new AppGui(this, this.store()); + this.gui_ = new AppGui(this, this.store(), keymap); this.gui_.setLogger(this.logger_); await this.gui_.start(); diff --git a/CliClient/app/command-help.js b/CliClient/app/command-help.js index a46e60dfa..38490b1a3 100644 --- a/CliClient/app/command-help.js +++ b/CliClient/app/command-help.js @@ -36,21 +36,19 @@ class Command extends BaseCommand { async action(args) { const stdoutWidth = app().commandStdoutMaxWidth(); - if (args.command === 'shortcuts') { + if (args.command === 'shortcuts' || args.command === 'keymap') { if (app().gui().isDummy()) { throw new Error(_('Shortcuts are not available in CLI mode.')); } - const shortcuts = app().gui().shortcuts(); + const keymap = app().gui().keymap(); let rows = []; - for (let n in shortcuts) { - if (!shortcuts.hasOwnProperty(n)) continue; - const shortcut = shortcuts[n]; - if (!shortcut.description) continue; - n = shortcut.friendlyName ? shortcut.friendlyName : n; - rows.push([n, shortcut.description()]); + for (let i = 0; i < keymap.length; i++) { + const item = keymap[i]; + const keys = item.keys.map((k) => k === ' ' ? '(SPACE)' : k); + rows.push([keys.join(', '), item.command]); } cliUtils.printArray(this.stdout.bind(this), rows); @@ -78,7 +76,7 @@ class Command extends BaseCommand { this.stdout(_('To maximise/minimise the console, press "TC".')); this.stdout(_('To enter command line mode, press ":"')); this.stdout(_('To exit command line mode, press ESCAPE')); - this.stdout(_('For the complete list of available keyboard shortcuts, type `help shortcuts`')); + this.stdout(_('For the complete list of available keyboard shortcuts, type `help keymap`')); } app().gui().showConsole(); diff --git a/CliClient/package-lock.json b/CliClient/package-lock.json index da879763b..1340d525b 100644 --- a/CliClient/package-lock.json +++ b/CliClient/package-lock.json @@ -2057,9 +2057,9 @@ "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" }, "tkwidgets": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/tkwidgets/-/tkwidgets-0.5.21.tgz", - "integrity": "sha512-gJfpYq3UM6AZ23ZM+D9BZ1PhsJLLHgjCOf487/lS9pO0uDdnkMcVXkkKEfRl00EyjPnGc88QZhEkVOvrtKsuPA==", + "version": "0.5.25", + "resolved": "https://registry.npmjs.org/tkwidgets/-/tkwidgets-0.5.25.tgz", + "integrity": "sha512-f+12QbxNCLg9Jou5JoPJxATGLmzpDAQeM7QRTXvuqdEB/QvPD9+UlPUL7eYJP1QJv2zzT6EIWWbdpDkXPEtzCQ==", "requires": { "chalk": "2.3.0", "emphasize": "1.5.0", diff --git a/CliClient/package.json b/CliClient/package.json index e821e1e8d..61a4159e9 100644 --- a/CliClient/package.json +++ b/CliClient/package.json @@ -58,7 +58,7 @@ "string-to-stream": "^1.1.0", "strip-ansi": "^4.0.0", "tcp-port-used": "^0.1.2", - "tkwidgets": "^0.5.21", + "tkwidgets": "^0.5.25", "url-parse": "^1.2.0", "uuid": "^3.0.1", "word-wrap": "^1.2.3", diff --git a/README.md b/README.md index 61bb80fc1..1ce63337f 100644 --- a/README.md +++ b/README.md @@ -217,13 +217,12 @@ Current translations: ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/br.png) | Português (Brasil) | [pt_BR](https://github.com/laurent22/joplin/blob/master/CliClient/locales/pt_BR.po) | | 73% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/ru.png) | Русский | [ru_RU](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ru_RU.po) | Artyom Karlov | 92% ![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/cn.png) | 中文 (简体) | [zh_CN](https://github.com/laurent22/joplin/blob/master/CliClient/locales/zh_CN.po) | RCJacH | 75% -![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/master/png/16/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 73% +![](https://raw.githubusercontent.com/stevenrskelton/flag-icon/masWter/png/16/country-4x3/jp.png) | 日本語 | [ja_JP](https://github.com/laurent22/joplin/blob/master/CliClient/locales/ja_JP.po) | | 73% # Known bugs - Non-alphabetical characters such as Chinese or Arabic might create glitches in the terminal on Windows. This is a limitation of the current Windows console. -- Auto-update is not working in the Linux desktop application. - While the mobile can sync and load tags, it is not currently possible to create new ones. The desktop and terminal apps can create, delete and edit tags. # License diff --git a/README_terminal.md b/README_terminal.md index 23be47990..a8e225af8 100644 --- a/README_terminal.md +++ b/README_terminal.md @@ -96,7 +96,7 @@ The complete usage information is available from command-line mode, by typing on Command | Description --------|------------------- `help` | General help information -`help shortcuts` | Lists the available shortcuts +`help keymap` | Lists the available shortcuts `help [command]` | Displays information about a particular command If the help is not fully visible, press `Tab` multiple times till the console is in focus and use the arrow keys or page up/down to scroll the text. @@ -177,25 +177,78 @@ Give a new title to the note: # Available shortcuts -There are two types of shortcuts: those that manipulate the user interface directly, such as `TAB` to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing `mn` ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with `mknote ""` from where the title of the note can be entered. See below for the full list of shortcuts: +There are two types of shortcuts: those that manipulate the user interface directly, such as `TAB` to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing `mn` ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with `mknote ""` from where the title of the note can be entered. See below for the full list of default shortcuts: - Tab Give focus to next pane - Shift+Tab Give focus to previous pane - : Enter command line mode - ESC Exit command line mode - ENTER Edit the selected note - Ctrl+C Cancel the current command. - Ctrl+D Exit the application. - DELETE Delete the currently selected note or notebook. - SPACE Set a to-do as completed / not completed - tc [t]oggle [c]onsole between maximized/minimized/hidden/visible. - / Search - tm [t]oggle note [m]etadata. - mn [M]ake a new [n]ote - mt [M]ake a new [t]odo - mb [M]ake a new note[b]ook - yn Copy ([Y]ank) the [n]ote to a notebook. - dn Move the note to a notebook. + : enter_command_line_mode + TAB focus_next + SHIFT_TAB focus_previous + UP move_up + DOWN move_down + PAGE_UP page_up + PAGE_DOWN page_down + ENTER activate + DELETE, BACKSPACE delete + (SPACE) todo toggle $n + tc toggle_console + tm toggle_metadata + / search "" + mn mknote "" + mt mktodo "" + mb mkbook "" + yn cp $n "" + dn mv $n "" + +Shortcut can be configured by adding a file `keymap.json` to the profile directory. The content of this file is a JSON array with each entry defining a command and the keys associated with it. + +Each entry can have the following properties: + +Name | Description +-----|------------ +`keys` | The array of keys that will trigger the action. Special keys such as page up, down arrow, etc. needs to be specified UPPERCASE. See the [list of available special keys](https://github.com/cronvel/terminal-kit/blob/3114206a9556f518cc63abbcb3d188fe1995100d/lib/termconfig/xterm.js#L531). For example, `['DELETE', 'BACKSPACE']` means the command will run if the user pressed either the delete or backspace key. Key combinations can also be provided - in that case specify them lowercase. For example "tc" means that the command will be executed when the user pressed "t" then "c". Special keys can also be used in this fashion - simply write them one after the other. For instance, `CTRL_WCTRL_W` means the action would be executed if the user pressed "ctrl-w ctrl-w". +`type` | The command type. It can have the value "exec", "function" or "prompt". **exec**: Simply execute the provided command. For example `edit $n` would edit the selected note. **function**: Run a special commands (see below for the list of function. **prompt**: A bit similar to "exec", except that the command is not going to be executed immediately - this allows the user to provide additional data. For example `mknote ""` would fill the command line with this command and allow the user to set the title. A prompt command can also take a `cursorPosition` parameter (see below) +`command` | The command that needs to be executed +`cusorPosition` | An integer. For prompt commands, tells where the cursor (caret) should start at. This is convenient for example to position the cursor between quotes. Use a negative value to set a position starting from the end. + +This is the list of available functions: + +Name | Description +-----|------------ +enter_command_line_mode | Enter command line mode +focus_next | Focus next pane (or widget) +focus_previous | Focus previous pane (or widget) +move_up | Move up (in a list for example) +move_down | Move down (in a list for example) +page_up | Page up +page_down | Page down +activate | Activates the selected item. If the item is a note for example it will be open in the editor +delete | Deletes the selected item +toggle_console | Toggle the console +toggle_metadata | Toggle note metadata + +As an example, this is the default keymap: + +```json +[ + { "keys": [":"], "type": "function", "command": "enter_command_line_mode" }, + { "keys": ["TAB"], "type": "function", "command": "focus_next" }, + { "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" }, + { "keys": ["UP"], "type": "function", "command": "move_up" }, + { "keys": ["DOWN"], "type": "function", "command": "move_down" }, + { "keys": ["PAGE_UP"], "type": "function", "command": "page_up" }, + { "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" }, + { "keys": ["ENTER"], "type": "function", "command": "activate" }, + { "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" }, + { "keys": [" "], "command": "todo toggle $n" }, + { "keys": ["tc"], "type": "function", "command": "toggle_console" }, + { "keys": ["tm"], "type": "function", "command": "toggle_metadata" }, + { "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 }, + { "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 }, + { "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 }, + { "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 }, + { "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 }, + { "keys": ["dn"], "type": "prompt", "command": "mv $n \"\"", "cursorPosition": -2 } +] +``` # Available commands diff --git a/docs/index.html b/docs/index.html index e69f29f67..bedbc2e3d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -392,14 +392,14 @@ $$ Croatian hr_HR -Hrvoje Mandić trbuhom@net.hr +Hrvoje Mandić trbuhom@net.hr 71% Deutsch de_DE -Tobias Strobel git@strobeltobias.de +Tobias Strobel git@strobeltobias.de 90% @@ -413,7 +413,7 @@ $$ Español es_ES -Fernando Martín f@mrtn.es +Fernando Martín f@mrtn.es 98% @@ -455,18 +455,18 @@ $$ Русский ru_RU -Artyom Karlov artyom.karlov@gmail.com +Artyom Karlov artyom.karlov@gmail.com 92% 中文 (简体) zh_CN -RCJacH RCJacH@outlook.com +RCJacH RCJacH@outlook.com 75% - + 日本語 ja_JP @@ -478,7 +478,6 @@ $$

Known bugs

  • Non-alphabetical characters such as Chinese or Arabic might create glitches in the terminal on Windows. This is a limitation of the current Windows console.
  • -
  • Auto-update is not working in the Linux desktop application.
  • While the mobile can sync and load tags, it is not currently possible to create new ones. The desktop and terminal apps can create, delete and edit tags.

License

diff --git a/docs/terminal/index.html b/docs/terminal/index.html index d3f114309..3c18f3a97 100644 --- a/docs/terminal/index.html +++ b/docs/terminal/index.html @@ -278,7 +278,7 @@ sudo ln -s ~/.joplin-bin/bin/joplin /usr/bin/joplin General help information -help shortcuts +help keymap Lists the available shortcuts @@ -332,25 +332,131 @@ fe889 07/12/2017 17:57 My note

Give a new title to the note:

$ joplin set fe889 title "New title"
 

Available shortcuts

-

There are two types of shortcuts: those that manipulate the user interface directly, such as TAB to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing mn ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with mknote "" from where the title of the note can be entered. See below for the full list of shortcuts:

-
Tab       Give focus to next pane
-Shift+Tab Give focus to previous pane
-:         Enter command line mode
-ESC       Exit command line mode
-ENTER     Edit the selected note
-Ctrl+C    Cancel the current command.
-Ctrl+D    Exit the application.
-DELETE    Delete the currently selected note or notebook.
-SPACE     Set a to-do as completed / not completed
-tc        [t]oggle [c]onsole between maximized/minimized/hidden/visible.
-/         Search
-tm        [t]oggle note [m]etadata.
-mn        [M]ake a new [n]ote
-mt        [M]ake a new [t]odo
-mb        [M]ake a new note[b]ook
-yn        Copy ([Y]ank) the [n]ote to a notebook.
-dn        Move the note to a notebook.
-

Available commands

+

There are two types of shortcuts: those that manipulate the user interface directly, such as TAB to move from one pane to another, and those that are simply shortcuts to actual commands. In a way similar to Vim, these shortcuts are generally a verb followed by an object. For example, typing mn ([m]ake [n]ote), is used to create a new note: it will switch the interface to command line mode and pre-fill it with mknote "" from where the title of the note can be entered. See below for the full list of default shortcuts:

+
:                 enter_command_line_mode
+TAB               focus_next
+SHIFT_TAB         focus_previous
+UP                move_up
+DOWN              move_down
+PAGE_UP           page_up
+PAGE_DOWN         page_down
+ENTER             activate
+DELETE, BACKSPACE delete
+(SPACE)           todo toggle $n
+tc                toggle_console
+tm                toggle_metadata
+/                 search ""
+mn                mknote ""
+mt                mktodo ""
+mb                mkbook ""
+yn                cp $n ""
+dn                mv $n ""
+

Shortcut can be configured by adding a file keymap.json to the profile directory. The content of this file is a JSON array with each entry defining a command and the keys associated with it.

+

Each entry can have the following properties:

+ + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
keysThe array of keys that will trigger the action. Special keys such as page up, down arrow, etc. needs to be specified UPPERCASE. See the list of available special keys. For example, ['DELETE', 'BACKSPACE'] means the command will run if the user pressed either the delete or backspace key. Key combinations can also be provided - in that case specify them lowercase. For example "tc" means that the command will be executed when the user pressed "t" then "c". Special keys can also be used in this fashion - simply write them one after the other. For instance, CTRL_WCTRL_W means the action would be executed if the user pressed "ctrl-w ctrl-w".
typeThe command type. It can have the value "exec", "function" or "prompt". exec: Simply execute the provided command. For example edit $n would edit the selected note. function: Run a special commands (see below for the list of function. prompt: A bit similar to "exec", except that the command is not going to be executed immediately - this allows the user to provide additional data. For example mknote "" would fill the command line with this command and allow the user to set the title. A prompt command can also take a cursorPosition parameter (see below)
commandThe command that needs to be executed
cusorPositionAn integer. For prompt commands, tells where the cursor (caret) should start at. This is convenient for example to position the cursor between quotes. Use a negative value to set a position starting from the end.
+

This is the list of available functions:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
NameDescription
enter_command_line_modeEnter command line mode
focus_nextFocus next pane (or widget)
focus_previousFocus previous pane (or widget)
move_upMove up (in a list for example)
move_downMove down (in a list for example)
page_upPage up
page_downPage down
activateActivates the selected item. If the item is a note for example it will be open in the editor
deleteDeletes the selected item
toggle_consoleToggle the console
toggle_metadataToggle note metadata
+

As an example, this is the default keymap:

+
[
+    { "keys": [":"], "type": "function", "command": "enter_command_line_mode" },
+    { "keys": ["TAB"], "type": "function", "command": "focus_next" },
+    { "keys": ["SHIFT_TAB"], "type": "function", "command": "focus_previous" },
+    { "keys": ["UP"], "type": "function", "command": "move_up" },
+    { "keys": ["DOWN"], "type": "function", "command": "move_down" },
+    { "keys": ["PAGE_UP"], "type": "function", "command": "page_up" },
+    { "keys": ["PAGE_DOWN"], "type": "function", "command": "page_down" },
+    { "keys": ["ENTER"], "type": "function", "command": "activate" },
+    { "keys": ["DELETE", "BACKSPACE"], "type": "function", "command": "delete" },
+    { "keys": [" "], "command": "todo toggle $n" },
+    { "keys": ["tc"], "type": "function", "command": "toggle_console" },
+    { "keys": ["tm"], "type": "function", "command": "toggle_metadata" },
+    { "keys": ["/"], "type": "prompt", "command": "search \"\"", "cursorPosition": -2 },
+    { "keys": ["mn"], "type": "prompt", "command": "mknote \"\"", "cursorPosition": -2 },
+    { "keys": ["mt"], "type": "prompt", "command": "mktodo \"\"", "cursorPosition": -2 },
+    { "keys": ["mb"], "type": "prompt", "command": "mkbook \"\"", "cursorPosition": -2 },
+    { "keys": ["yn"], "type": "prompt", "command": "cp $n \"\"", "cursorPosition": -2 },
+    { "keys": ["dn"], "type": "prompt", "command": "mv $n \"\"", "cursorPosition": -2 }
+]
+
+

Available commands

The following commands are available in command-line mode:

attach <note> <file>