Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jj/jk to esc #74

Closed
fominok opened this issue Mar 27, 2020 · 11 comments
Closed

jj/jk to esc #74

fominok opened this issue Mar 27, 2020 · 11 comments
Assignees
Labels
enhancement New feature or request

Comments

@fominok
Copy link

fominok commented Mar 27, 2020

Hi, thank you for this great extension!

Are there anyting for handling jj or jk to get out of insert mode? Or how hard it is to implement?

@71 71 self-assigned this Mar 28, 2020
@71 71 added the enhancement New feature or request label Mar 28, 2020
@71
Copy link
Owner

71 commented Mar 28, 2020

I don't think the commands jj and jk exist in Kakoune. It is also pretty hard to implement in Dance, since combinations of keys in insert mode aren't something I thought about until now. It's definitely doable, but if it ends up being implemented I'd like it to be configurable, which requires custom key combinations, which requires a lot of work and isn't in scope (yet).

If it really matters to you I'd be okay with adding a hack in the meantime.

@71 71 closed this as completed Mar 28, 2020
@fominok
Copy link
Author

fominok commented Mar 28, 2020

Right, they are not exist, but can be configured and there is a recipe in its wiki. For VSCodeVim there is such setting as it is not possible to do outside these modal-editing plugins, so I suggest to have it there too. It's pretty common and it will be nice to have it there sometime.

@dimus
Copy link

dimus commented Jan 2, 2021

I do use jk in kakoune, and it would be great to have it in Dance as well

@71
Copy link
Owner

71 commented Jan 2, 2021

I sort of thought about it and thought about several ways to add it to Dance, and then I realized that it's actually already possible.

Spoiler: install macro-commander (or whatever else you prefer with that kind of feature), and add the following macro:

"switchToNormal": [
  {
    "javascript": [
      "const editor = vscode.window.activeTextEditor;",
      "if (editor === undefined || editor.selection.active.character === 0) return;",
      "const lastCh = new vscode.Range(editor.selection.active.translate(0, -1), editor.selection.active);",
      "if (editor.document.getText(lastCh) !== 'j') return vscode.commands.executeCommand('default:type', { text: 'j' });",
      "else return editor.edit((b) => b.delete(lastCh)).then(() => vscode.commands.executeCommand('dance.set.normal'));"
    ],
  },
],

Then in the keybindings, add:

{
  "key": "j",
  "command": "macros.switchToNormal",
  "when": "editorTextFocus && dance.mode == 'insert'",
},

What it does is that whenever you press j:

  • If the previous character in the document is a j, it removes it and sets the mode to normal,
  • Otherwise, it inserts the j normally.

I might add support for macros to Dance to make things easier (and especially to no longer need to both define a macro and a keybinding -- a keybinding should be enough), but in the meantime this works.

I also considered the following ideas:

  • Add a new context so that keybindings could have "when": "... && dance.lastCommand == 'dance.cancel'".
    • Con: Doesn't work with non-Dance commands.
    • Con: We still need to forward the first j to the editor, which requires some code.
  • Add some timeout keys to Dance.
    • Con: No longer works like Kakoune, which has no timeout.
    • Con: Probably a mess / hacky to implement.
  • Full support for double keys.
    • Con: Probably a lot of work.
    • Con: Probably requires overriding the type command yet again.

@71
Copy link
Owner

71 commented Jan 2, 2021

With 0.4.0 now pushed, you can simply write in keybindings.json:

  {
    "key": "j",
    "command": "dance.run",
    "when": "editorTextFocus && dance.mode == 'insert'",
    "args": {
      "code": [
        "const editor = vscode.window.activeTextEditor;",
        "if (editor === undefined || editor.selection.active.character === 0) return;",
        "const lastCh = new vscode.Range(editor.selection.active.translate(0, -1), editor.selection.active);",
        "if (editor.document.getText(lastCh) !== 'j') return vscode.commands.executeCommand('default:type', { text: 'j' });",
        "else return editor.edit((b) => b.delete(lastCh)).then(() => vscode.commands.executeCommand('dance.set.normal'));",
      ],
    },
  },

@dimus
Copy link

dimus commented Jan 3, 2021

This solution for v0.4.0 seems to work quite well, thanks!

@losnappas
Copy link

The solution posted didn't work so well with multi selections, leaving behind j's, but this

  {
    "key": "j",
    "command": "dance.run",
    "when": "editorTextFocus && dance.mode == 'insert'",
    "args": {
      "code": [
        "const editor = vscode.window.activeTextEditor;",
        "if (editor === undefined) return;",
        "try {",
        // "if (editor.selection.active.character === 0) return editor.edit((b) => b.delete(lastCh)).then(() => vscode.commands.executeCommand('dance.set.normal'));",
        "const lastCh = new vscode.Range(editor.selection.active.translate(0, -1), editor.selection.active);",
        "if (editor.document.getText(lastCh) !== 'j') return vscode.commands.executeCommand('default:type', { text: 'j' });",
        "else return vscode.commands.executeCommand('deleteLeft').then(() => vscode.commands.executeCommand('multicommand.quitquit'));",
        "} catch (e) { return vscode.commands.executeCommand('default:type', { text: 'j' }); }"
      ]
    }
  },
  {
    "key": "k",
    "command": "dance.run",
    "when": "editorTextFocus && dance.mode == 'insert'",
    "args": {
      "code": [
        "const editor = vscode.window.activeTextEditor;",
        "if (editor === undefined) return;",
        "try {",
        "const lastCh = new vscode.Range(editor.selection.active.translate(0, -1), editor.selection.active);",
        "if (editor.document.getText(lastCh) !== 'j') return vscode.commands.executeCommand('default:type', { text: 'k' });",
        "else return vscode.commands.executeCommand('deleteLeft').then(() => vscode.commands.executeCommand('multicommand.quitquit'));",
        "} catch (e) { return vscode.commands.executeCommand('default:type', { text: 'k' }); }"
      ]
    }
  },

seems to do the trick. I wish I could pop out the undo stack for the typed j, though, but dunno about that.

@alker0
Copy link

alker0 commented Apr 14, 2021

In most situations a next character of j is any of a, e, i, o, p, q, r, s, and u, therefore I write:

{
    "key": "j j",
    "command": "dance.set.normal",
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j a",
    "command": "type",
    "args": {"text": "ja"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j e",
    "command": "type",
    "args": {"text": "je"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j i",
    "command": "type",
    "args": {"text": "ji"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j o",
    "command": "type",
    "args": {"text": "jo"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j p",
    "command": "type",
    "args": {"text": "jp"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j q",
    "command": "type",
    "args": {"text": "jq"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j r",
    "command": "type",
    "args": {"text": "jr"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j s",
    "command": "type",
    "args": {"text": "js"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },
  {
    "key": "j u",
    "command": "type",
    "args": {"text": "ju"},
    "when": "editorTextFocus && dance.mode == 'insert'"
  },

Not smart, but it is simple.

@71
Copy link
Owner

71 commented Apr 14, 2021

Since this seems like a popular feature, the next version of Dance will be able to do this natively. The dance.openMenu command now takes a prefix argument, which, if specified, instructs Dance to insert the prefix and the entered key if an item outside of the given menu is selected.

{
  "key": "j",
  "command": "dance.openMenu",
  "args": {
    "menu": {
      "items": {
        "j": {
          "text": "escape to Normal",
          "command": "dance.modes.set.normal",
        },
      },
    },
    "prefix": "j",
  },
  "when": "editorTextFocus && dance.mode == 'insert'"
}

@sransara
Copy link

I'm attempting to map jk to Escape and jj to type j with:

{
  "key": "j",
  "command": "dance.openMenu",
  "args": {
    "menu": {
      "items": {
        "k": {
          "text": "escape to Normal",
          "command": "dance.modes.set.normal"
        },
        "j": {
          "text": "insert j",
          "command": "default:type",
          "args": { "text": "j" }
        }
      }
    },
    "prefix": "j"
  },
  "when": "editorTextFocus && dance.mode == 'insert'"
},

But I'm getting an error (error executing command "dance.openMenu": Found non-callable @@iterator). I presume that is because type command is borrowed by the menu. Is there a way around it?

@71
Copy link
Owner

71 commented Jun 24, 2021

@sransara This issue is actually #177 (comment) (tl;dr: "args": { "text": "j" } should be "args": [{ "text": "j" }]). It's been fixed, but the fix hasn't been released on the marketplace yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

6 participants