Keymap.json keybindings mess up custom keymaps

Bug report

Hi v5.6.0 messed up so much for me. can you allow me to download an earlier version via some link. I tried to find something online but couldn’t figure it out.
I use this tool for work every day and so I don’t really have the time to try and get this stuff to work properly.
In the past I spent a considerable amount of time hacking inkdrop with a lengthy custom init.js and a lot of keybindings.
now for starters the jj remap in vim doesn’t work anymore (it just doesn’t allow me to write j anymore in insert mode).
and there’s a ton of other issues with the switch from cson to json, which I see was automatically done in the last version without being announced.
I added custom remaps on a progammatic level via init.js

  const vimMoveUp = handleVimCommand({
    keys: 'k',
    type: 'motion',
    motion: 'moveByLines',
    motionArgs: { forward: false, linewise: true },
  });

  const vimMoveDown = handleVimCommand({
    keys: 'j',
    type: 'motion',
    motion: 'moveByLines',
    motionArgs: { forward: true, linewise: true },
  });

  /**
   * @description custom command definitions
   * @note if a command requires arguments, eg. core:open-note,
   * then we have to invoke it programmatically and assign it to a separate command instead
   * @example
   * inkdrop.commands.dispatch(document.body, 'core:open-note', {
   *   noteId: 'note:uugSvPq5r',
   * });
   */
  inkdrop.commands.add(document.body, 'vim:5-move-down', () => {
    for (let i = 0; i < 5; i++) {
      vimMoveDown();
    }
  });

  inkdrop.commands.add(document.body, 'vim:5-move-up', () => {
    for (let i = 0; i < 5; i++) {
      vimMoveUp();
    }
  });

but that also stopped working.
and some of my init.js made the new app crash, specifically this part that I needed to comment:

  // sample motion added to vim plugin
  // CodeMirror.Vim.defineMotion(
  //   'updated-moveByCharacters',
  //   function (_cm, head, motionArgs) {
  //     const cur = head;
  //     const repeat = motionArgs.repeat;
  //     const ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
  //     return Pos(cur.line, ch);
  //   }
  // );

  // // added command line commands
  // CodeMirror.Vim.defineEx('filter', 'f', (_cm, event) => {
  //   inkdrop.commands.dispatch(document.body, 'core:find');
  //   if (event.argString)
  //     inkdrop.commands.dispatch(document.body, 'core:filter-notes', {
  //       keyword: event.argString.trim(),
  //     });
  // });

  // CodeMirror.Vim.defineEx('find', 'fi', (_cm, event) => {
  //   inkdrop.commands.dispatch(document.body, 'core:find-global');
  //   if (event.argString)
  //     inkdrop.commands.dispatch(document.body, 'core:search-notes', {
  //       keyword: event.argString.trim(),
  //     });
  // });

here’s my whole init.js

// plugin command implementations: https://github.com/inkdropapp/inkdrop-vim/blob/5b1c63ecae1081cf33327e36731d8c3399a64af1/src/vim.js
// vim API implementation: https://github.com/inkdropapp/inkdrop-vim/blob/5b1c63ecae1081cf33327e36731d8c3399a64af1/src/keymap.js
// inkdrop add command documentation: https://docs.inkdrop.app/manual/the-init-file
// inkdrop defineEx documentation: https://github.com/inkdropapp/inkdrop-vim/blob/master/README.md#define-custom-ex-commands
// inkdrop API documentation: https://docs.inkdrop.app/manual/list-of-commands#coresidebar-focus

inkdrop.onEditorLoad((editor) => {
  const CodeMirror = require('codemirror');
  const { shell } = require('electron');

  const Pos = CodeMirror.Pos;
  const { cm } = editor;

  // sample motion added to vim plugin
  // CodeMirror.Vim.defineMotion(
  //   'updated-moveByCharacters',
  //   function (_cm, head, motionArgs) {
  //     const cur = head;
  //     const repeat = motionArgs.repeat;
  //     const ch = motionArgs.forward ? cur.ch + repeat : cur.ch - repeat;
  //     return Pos(cur.line, ch);
  //   }
  // );

  // // added command line commands
  // CodeMirror.Vim.defineEx('filter', 'f', (_cm, event) => {
  //   inkdrop.commands.dispatch(document.body, 'core:find');
  //   if (event.argString)
  //     inkdrop.commands.dispatch(document.body, 'core:filter-notes', {
  //       keyword: event.argString.trim(),
  //     });
  // });

  // CodeMirror.Vim.defineEx('find', 'fi', (_cm, event) => {
  //   inkdrop.commands.dispatch(document.body, 'core:find-global');
  //   if (event.argString)
  //     inkdrop.commands.dispatch(document.body, 'core:search-notes', {
  //       keyword: event.argString.trim(),
  //     });
  // });

  const handleVimCommand = (command) => {
    const _vim = CodeMirror.Vim;

    return () => {
      const vim = _vim.maybeInitVimState(cm);
      return cm.operation(() => {
        cm.curOp.isVimOp = true;
        try {
          _vim.commandDispatcher.processCommand(cm, vim, command);
        } catch (e) {
          // clear VIM state in case it's in a bad state.
          cm.state.vim = undefined;
          _vim.maybeInitVimState(cm);
          throw e;
        }
        return true;
      });
    };
  };

  const vimMoveUp = handleVimCommand({
    keys: 'k',
    type: 'motion',
    motion: 'moveByLines',
    motionArgs: { forward: false, linewise: true },
  });

  const vimMoveDown = handleVimCommand({
    keys: 'j',
    type: 'motion',
    motion: 'moveByLines',
    motionArgs: { forward: true, linewise: true },
  });

  /**
   * @description custom command definitions
   * @note if a command requires arguments, eg. core:open-note,
   * then we have to invoke it programmatically and assign it to a separate command instead
   * @example
   * inkdrop.commands.dispatch(document.body, 'core:open-note', {
   *   noteId: 'note:uugSvPq5r',
   * });
   */
  inkdrop.commands.add(document.body, 'vim:5-move-down', () => {
    for (let i = 0; i < 5; i++) {
      vimMoveDown();
    }
  });

  inkdrop.commands.add(document.body, 'vim:5-move-up', () => {
    for (let i = 0; i < 5; i++) {
      vimMoveUp();
    }
  });

  inkdrop.commands.add(document.body, 'focus-next-search', () => {
    inkdrop.commands.dispatch(document.body, 'core:focus-note-list-bar');
    inkdrop.commands.dispatch(document.body, 'core:open-next-note');
  });

  inkdrop.commands.add(document.body, 'unset', () => {});

  inkdrop.commands.add(document.body, 'logger', () => {
    // use for debugging
    const state = inkdrop.store.getState();
    console.log({ state, isSidebarVisible: state.mainLayout.sidebarVisible });
  });

  inkdrop.commands.add(document.body, 'focus-sidebar', () => {
    const state = inkdrop.store.getState();
    const isSidebarVisible = state.mainLayout.sidebarVisible;

    if (isSidebarVisible) {
      inkdrop.commands.dispatch(document.body, 'core:sidebar-focus');
    } else {
      inkdrop.commands.dispatch(document.body, 'view:toggle-sidebar');
      inkdrop.commands.dispatch(document.body, 'core:sidebar-focus');
    }
  });

  inkdrop.commands.add(document.body, 'toggle-sidebar-focus-editor', () => {
    inkdrop.commands.dispatch(document.body, 'view:toggle-sidebar');
    inkdrop.commands.dispatch(document.body, 'editor:focus');
  });

  inkdrop.commands.add(document.body, 'open-keymap-cson', () => {
    // Open a local file in the default app
    shell.openExternal('file://' + __dirname + '/keymap.cson');
  });

  inkdrop.commands.add(document.body, 'open-init-js', () => {
    // Open a local file in the default app
    shell.openExternal('file://' + __dirname + '/init.js');
  });

  // relative line setup
  function showRelativeLines(cm) {
    const lineNum = cm.getCursor().line + 1;
    if (cm.state.curLineNum === lineNum) {
      return;
    }
    cm.state.curLineNum = lineNum;
    cm.setOption('lineNumberFormatter', (l) =>
      l === lineNum ? lineNum : Math.abs(lineNum - l)
    );
  }

  cm.on('cursorActivity', showRelativeLines);

  /**
   * @description remove unwanted layouts
   * (can also be done via config.cson's hiddenComponents setting)
   * https://docs.inkdrop.app/manual/extending-the-ui
   * https://docs.inkdrop.app/reference/state-layouts
   */
  // const { layouts } = inkdrop.store.getState();

  // [...layouts['sidebar-workspace-menu']].forEach((element) => {
  //   inkdrop.layouts.removeComponentFromLayout(
  //     'sidebar-workspace-menu',
  //     element
  //   );
  // });
});

here’s my old keymap.cson that worked:

# useful example: https://gist.github.com/nuesslerm/ed19968c2326ea887052c6e5337e16fd
# public inkdrop API: https://docs.inkdrop.app/manual/list-of-commands
# vim keymap: https://github.com/inkdropapp/inkdrop-vim/blob/master/keymaps/vim.json
# medium article: https://blog.inkdrop.app/enabling-vim-keybindings-547f148b73b1
# github project: https://github.com/inkdropapp/inkdrop-vim

'.CodeMirror.vim-mode.insert-mode textarea':
  'j j': 'vim:exit-insert-mode'
  'j shift-J': 'vim:exit-insert-mode'
  'shift-J shift-J': 'vim:exit-insert-mode'
  'shift-J j': 'vim:exit-insert-mode'
  'j k': 'vim:exit-insert-mode'
  'j shift-K': 'vim:exit-insert-mode'
  'shift-J shift-K': 'vim:exit-insert-mode'
  'shift-J k': 'vim:exit-insert-mode'

'.CodeMirror.vim-mode.replace-mode textarea':
  'j j': 'vim:exit-insert-mode'
  'j shift-J': 'vim:exit-insert-mode'
  'shift-J shift-J': 'vim:exit-insert-mode'
  'shift-J j': 'vim:exit-insert-mode'
  'j k': 'vim:exit-insert-mode'
  'j shift-K': 'vim:exit-insert-mode'
  'shift-J shift-K': 'vim:exit-insert-mode'
  'shift-J k': 'vim:exit-insert-mode'

'.CodeMirror.vim-mode:not(.insert-mode):not(.key-buffering) textarea':
  'shift-J': 'vim:5-move-down'
  'shift-K': 'vim:5-move-up'
  ')': 'vim:move-to-next-paragraph'
  '(': 'vim:move-to-previous-paragraph'

'.CodeMirror.vim-mode.normal-mode:not(.key-buffering):not(.visual-mode) textarea':
  'u': 'vim:undo'
  'shift-U': 'core:redo'
  's k': 'editor:title:focus'
  'space k': 'editor:title:focus'
  'ctrl-a k': 'editor:title:focus'
  's h': 'core:focus-note-list-bar'
  'ctrl-a h': 'core:focus-note-list-bar'
  'space p': 'switch-notebook:toggle'
  'space f': 'switch-note:open'
  'space e': 'core:find'
  'space i': 'core:find-global'
  'space o': 'core:find'
  'ctrl-shift-N': 'toggle-sidebar-focus-editor'
  'ctrl-e': 'focus-sidebar'

'.editor-header-layout':
  'ctrl-a j': 'editor:focus'

# this is where the notebooks live
'.sidebar-navigation':
  's l': 'core:focus-note-list-bar'
  'ctrl-a l': 'core:focus-note-list-bar'
  'l': 'editor:focus'
  'j': 'core:sidebar-select-next-item'
  'k': 'core:sidebar-select-prev-item'
  'space p': 'switch-notebook:toggle'
  'space f': 'switch-note:open'
  'space e': 'core:find'
  'space i': 'core:find-global'
  'space o': 'core:find'
  'enter': 'logger' # doesn't work. why?

'.sidebar-book-filter-menu':
  's l': 'core:focus-note-list-bar'
  'l': 'core:focus-note-list-bar'

# this is the list of notes
'.note-list-bar':
  's l': 'editor:focus'
  's h': 'core:sidebar-focus'
  'ctrl-a l': 'editor:focus'
  'ctrl-a h': 'core:sidebar-focus'
  'l': 'editor:focus'
  'shift-L': 'editor:focus'
  'space p': 'switch-notebook:toggle'
  'space f': 'switch-note:open'
  'space e': 'core:find'
  'space i': 'core:find-global'
  'space o': 'core:find'
  'shift-K': 'core:open-prev-note'
  'shift-J': 'core:open-next-note'
  'space k': 'core:find'

'.note-list-search-bar':
  'shift-J': 'core:focus-note-list-bar'
  'enter': 'core:focus-note-list-bar'
  'tab': 'core:focus-note-list-bar'
  'down': 'logger' # doesn't work. why?
  'cmd-f': 'core:find-global'

'.mde-preview':
  's h': 'core:focus-note-list-bar'
  's k': 'editor:title:focus'
  'space k': 'editor:title:focus'
  'space p': 'switch-notebook:toggle'
  'space f': 'switch-note:open'
  'space e': 'core:find'
  'space i': 'core:find-global'
  'space o': 'core:find'
  '/': 'preview-finder:open'
  'n': 'preview-finder:next'
  'N': 'preview-finder:prev'

'body':
  'alt-1': 'logger'
  'ctrl-cmd-m': 'core:move-to-notebook'
  'alt-cmd-m': 'application:minimize'
  'ctrl-n': 'editor:focus'
  'ctrl-e': 'focus-sidebar'
  'cmd-2': 'core:focus-note-list-bar'
  'ctrl-alt-v': 'paste-url:paste-url'
  'ctrl-alt-p': 'paste-url:paste-url'
  'ctrl-shift-N': 'toggle-sidebar-focus-editor'
  'cmd-f': 'core:find'
  'ctrl-,': 'core:navigate-back'
  'ctrl-.': 'core:navigate-forward'
  'ctrl-a h': 'core:navigate-back'
  'ctrl-a l': 'core:navigate-forward'
  'ctrl-a r': 'window:reload'
  'ctrl-a ,': 'open-keymap-cson'
  'ctrl-a .': 'open-init-js'
  # use custom unset command to unset external plugin keymaps
  'cmd-k': 'unset'
  'ctrl-k': 'unset'
  'cmd-p': 'unset'

here’s my messed up keymap.json:

{
  ".CodeMirror.vim-mode.insert-mode textarea": {
    "j j": "vim:exit-insert-mode",
    "j shift-J": "vim:exit-insert-mode",
    "shift-J shift-J": "vim:exit-insert-mode",
    "shift-J j": "vim:exit-insert-mode",
    "j k": "vim:exit-insert-mode",
    "j shift-K": "vim:exit-insert-mode",
    "shift-J shift-K": "vim:exit-insert-mode",
    "shift-J k": "vim:exit-insert-mode"
  },
  ".CodeMirror.vim-mode.replace-mode textarea": {
    "j j": "vim:exit-insert-mode",
    "j shift-J": "vim:exit-insert-mode",
    "shift-J shift-J": "vim:exit-insert-mode",
    "shift-J j": "vim:exit-insert-mode",
    "j k": "vim:exit-insert-mode",
    "j shift-K": "vim:exit-insert-mode",
    "shift-J shift-K": "vim:exit-insert-mode",
    "shift-J k": "vim:exit-insert-mode"
  },
  ".CodeMirror.vim-mode:not(.insert-mode):not(.key-buffering) textarea": {
    "shift-J": "vim:5-move-down",
    "shift-K": "vim:5-move-up",
    ")": "vim:move-to-next-paragraph",
    "(": "vim:move-to-previous-paragraph"
  },
  ".CodeMirror.vim-mode.normal-mode:not(.key-buffering):not(.visual-mode) textarea": {
    "u": "vim:undo",
    "shift-U": "core:redo",
    "s k": "editor:title:focus",
    "space k": "editor:title:focus",
    "ctrl-a k": "editor:title:focus",
    "s h": "core:focus-note-list-bar",
    "ctrl-a h": "core:focus-note-list-bar",
    "space p": "switch-notebook:toggle",
    "space f": "switch-note:open",
    "space e": "core:find",
    "space i": "core:find-global",
    "space o": "core:find",
    "ctrl-shift-N": "toggle-sidebar-focus-editor",
    "ctrl-e": "focus-sidebar"
  },
  ".editor-header-layout": {
    "ctrl-a j": "editor:focus"
  },
  ".sidebar-navigation": {
    "s l": "core:focus-note-list-bar",
    "ctrl-a l": "core:focus-note-list-bar",
    "l": "editor:focus",
    "j": "core:sidebar-select-next-item",
    "k": "core:sidebar-select-prev-item",
    "space p": "switch-notebook:toggle",
    "space f": "switch-note:open",
    "space e": "core:find",
    "space i": "core:find-global",
    "space o": "core:find",
    "enter": "logger"
  },
  ".sidebar-book-filter-menu": {
    "s l": "core:focus-note-list-bar",
    "l": "core:focus-note-list-bar"
  },
  ".note-list-bar": {
    "s l": "editor:focus",
    "s h": "core:sidebar-focus",
    "ctrl-a l": "editor:focus",
    "ctrl-a h": "core:sidebar-focus",
    "l": "editor:focus",
    "shift-L": "editor:focus",
    "space p": "switch-notebook:toggle",
    "space f": "switch-note:open",
    "space e": "core:find",
    "space i": "core:find-global",
    "space o": "core:find",
    "shift-K": "core:open-prev-note",
    "shift-J": "core:open-next-note",
    "space k": "core:find"
  },
  ".note-list-search-bar": {
    "shift-J": "core:focus-note-list-bar",
    "enter": "core:focus-note-list-bar",
    "tab": "core:focus-note-list-bar",
    "down": "logger",
    "cmd-f": "core:find-global"
  },
  ".mde-preview": {
    "s h": "core:focus-note-list-bar",
    "s k": "editor:title:focus",
    "space k": "editor:title:focus",
    "space p": "switch-notebook:toggle",
    "space f": "switch-note:open",
    "space e": "core:find",
    "space i": "core:find-global",
    "space o": "core:find",
    "/": "preview-finder:open",
    "n": "preview-finder:next",
    "N": "preview-finder:prev"
  },
  "body": {
    "alt-1": "logger",
    "ctrl-cmd-m": "core:move-to-notebook",
    "alt-cmd-m": "application:minimize",
    "ctrl-n": "editor:focus",
    "ctrl-e": "focus-sidebar",
    "cmd-2": "core:focus-note-list-bar",
    "ctrl-alt-v": "paste-url:paste-url",
    "ctrl-alt-p": "paste-url:paste-url",
    "ctrl-shift-N": "toggle-sidebar-focus-editor",
    "cmd-f": "core:find",
    "ctrl-,": "core:navigate-back",
    "ctrl-.": "core:navigate-forward",
    "ctrl-a h": "core:navigate-back",
    "ctrl-a l": "core:navigate-forward",
    "ctrl-a r": "window:reload",
    "ctrl-a ,": "open-keymap-cson",
    "ctrl-a .": "open-init-js",
    "cmd-k": "unset",
    "ctrl-k": "unset",
    "cmd-p": "unset"
  }
}

Environment

  • Platform: macos
  • Platform version: venture 13.5
  • App Version: latest 5.6

How to reproduce

please post a link to an old version for now.

Hi Markus,

Thanks for the report.
Wow, you have a lot of customizations!
I tried your configuration and found an issue where canceling multi-stroke keymaps is not working. That’s why you can no longer input ‘j’ in insert mode. I’ll fix it.

some of my init.js made the new app crash

To improve the launch speed, plugins are now loaded asynchronously.
You have to wait for the vim plugin to load like so:

setTimeout(() => {
  // CodeMirror.Vim.defineMotion
}, 500)

The CSON to JSON transition is important to improve the launch speed since the CSON parser requires the entire CofeeScript library. They are just syntaxes.

Do you have any other issues?

1 Like

I built a patched binary wihch the multi-stroke issue is fixed.
Can you try it? https://inkdrop-dist.s3.ap-northeast-1.amazonaws.com/tmp/Inkdrop-5.6.0-arm64-Mac_patch1.zip

1 Like

Hi. first of all thanks for the quick response and for fixing up my post a bit.
second, sorry for sounding a bit annoyed in my post, I really do like inkdrop a lot, especially because it allowed all the things I did and my custom vim stuff. and also because of the great plugins.
I’ll try out the binary and let you know how it goes.

Hi @craftzdog. I just tried out your bin and it works amazing. Also what Im even more happy about is that simply wrapping my Vim init code with a setTimeout in fact made everything work like before.
Thanks so much and sorry again for being a bit cranky.
:pray:

Great to hear that it solved your issues!
I understand how frustrating it is to get such issues that were working fine in the previous versions.
Please note that the next major upgrade (v6.x) will replace the current editor with CodeMirror 6, so you will have to update your customizations. But it will be more versatile and flexible to customize!
Thanks again for the feedback. I’ll keep improving it.

Thanks for the heads up! I’m looking forward to it.

Fixed in v5.6.2-beta.0🙌 Please let me know if you find any issues.

Fixed in v5.6.2-beta.0🙌 Please let me know if you find any issues.

v5.6.2 has been released :partying_face: Thanks again for reporting.