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

Initial VIM support in cell editors #8706

Closed

Conversation

echarles
Copy link
Member

References

This PR addresses for Cell Editors Problem 1 - Separated Keymap from Text Editor VIM discussed in #8592

Code changes

code-mirror-extension now tracks also the notebook and update the cell editor options when the Keymap settings is changed.

User-facing changes

Cell editors benefit from VIM mode support. Instead of ESC, user has to use CTRL-[ to exit from insert mode.

ezgif-4-29be8604048f

Backwards-incompatible changes

None

@jupyterlab-dev-mode
Copy link

Thanks for making a pull request to JupyterLab!

To try out this branch on binder, follow this link: Binder

@ianhi
Copy link
Contributor

ianhi commented Jul 23, 2020

!!! very excited to see this moving towards core jupyterlab 😄

Cell editors benefit from VIM mode support. Instead of ESC, user has to use CTRL-[ to exit from insert mode.

Is this restriction on Esc in order to maintain the usage of Esc to enter jupyter command mode? If so I appreciate the desire to maintain jupyterlab shortcuts but think that this change will be extremely disruptive to both terminal vim users and users of the existing jupyterlab-vim extensions. I would instead recommend keeping Esc to exit insert mode and using shift-Esc to get to jupyter command mode.

More generally given the history and user base of https://github.com/jwkvam/jupyterlab-vim I think that any implementation in core should strive to maintain the extension's approach to adapting the vim keymap to jupyter. Not only are the extension's choices sensible, but many people are accustomed to them and there will be a fracturing of the user base if the core implementation does not match them. Personally I could live without some of the shortcuts in the existing jupyterlab-vim extension, but if Esc was is not a valid way to exit insert mode I would not use the core implementation. Instead I would either continue to use the extension or make an extension to bring back the current extensions behavior:

  1. Esc vim-insert -> vim-command
  2. Shift-Esc vim-[insert,command] -> jupyter command mode

I think it is worth taking most (or even all) of the keymaps for maneuvering the notebook from the existing extensions list here: https://github.com/axelfahy/jupyterlab-vim#key-bindings and defined here and here

I'm happy to help with this however would be most useful (feedback, adding coding, meeting to talk about it, other things?). Also CC two other people who may be interested in this: @axelfahy, @jwkvam

@ianhi
Copy link
Contributor

ianhi commented Jul 23, 2020

I also see that this is marked as "initial" so I totally get not having worked out all the keymap details, but I guess my contention is that a partial vim support would be worse than no support and people instead using the more complete extensions.

@jtpio
Copy link
Member

jtpio commented Jul 24, 2020

Thanks @echarles for this!

Just tried on the Binder and it looks great.

It looks like navigating between cells using k and j when the notebook is in command mode also works. But that seems to be the default in JupyterLab.

I think that any implementation in core should strive to maintain the extension's approach to adapting the vim keymap to jupyter

Indeed, other bindings such as z,z to recenter the scroll position would also be useful (currently it's the keybinding to delete cells). Or d,d and p to cut a cell and paste it somewhere else. But that goes beyond vim support in cell editors since they apply to the notebook command mode, and could be added in a separate change.

@blink1073
Copy link
Member

@echarles is this PR ready to review?

@echarles
Copy link
Member Author

@blink1073 Nope... (not sure how to put in back in draft) as we have received user feedback on various channels and need to make sure we address them, especially regarding key binding. I need to digest the comments and connect back to have a common agreement on the features before this can be reviewed.

@blink1073
Copy link
Member

image

Convert to draft option is there ^

@echarles echarles marked this pull request as draft July 26, 2020 11:40
@echarles
Copy link
Member Author

echarles commented Jul 27, 2020

Last commit adds shortcut to move up/down the cell.

I have tried to use ESC keystroke to exit from VIM edit mode. Therefor, the default Keybinding needs to be removed and I wonder if it is possible with the current approach where the VIM settings is applied for all editors and not on editor changes

  • With all editors, I would need to remove/disable the existing key binding but addKeyBinding(…) does not support disabled and shortcuts-extension is not exposed for consumption.
  • With action taken on editor change like in jupyterlab-vim extension, the shortcut remains and a commands allows to handle the key on the code editor

https://github.com/axelfahy/jupyterlab-vim/blob/c4e43f940ef4be4c961608b6192412d2f3a33d1f/src/index.ts#L307-L321

Comment on lines +50 to +59
{
"command": "notebook:move-cell-down",
"keys": ["Ctrl J"],
"selector": ".jp-Notebook:focus"
},
{
"command": "notebook:move-cell-up",
"keys": ["Ctrl K"],
"selector": ".jp-Notebook:focus"
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The suggested change below would bring these commands in line with what the extensions currently do. Although having a command to move cells up and down in jupyter command mode would also be awesome. Maybe we could add both?

Suggested change
{
"command": "notebook:move-cell-down",
"keys": ["Ctrl J"],
"selector": ".jp-Notebook:focus"
},
{
"command": "notebook:move-cell-up",
"keys": ["Ctrl K"],
"selector": ".jp-Notebook:focus"
},
{
"command": "notebook:move-cursor-down",
"keys": ["Ctrl J"],
"selector": ".jp-Notebook.jp-mod-editMode"
},
{
"command": "notebook:move-cursor-up",
"keys": ["Ctrl K"],
"selector": ".jp-Notebook.jp-mod-editMode"
},

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

having a command to move cells up and down in jupyter command mode would also be awesome.

this applies in jupyter command mode. Maybe you mean in VIM command mode?

Copy link
Contributor

@ianhi ianhi Jul 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, my bad for not being more precise with my terminology. Currently the extensions enable you to shift the cursor up or down to adjacent cells using ctrl-j/k in either vim-insert or vim-command modes. Though I think having these keybinds do something in jupyter-command mode is also a valuable addition. Like so:

shifting-cursor

@ianhi
Copy link
Contributor

ianhi commented Jul 27, 2020

With all editors, I would need to remove/disable the existing key binding but addKeyBinding(…) does not support disabled and shortcuts-extension is not exposed for consumption.

Huh that's tricky. Would just defining the keymap (using addKeyBinding) as a no-op or return false be equivalent to disabling?

@jasongrout
Copy link
Contributor

jasongrout commented Jul 27, 2020

The "disabled" attribute is part of the setting registry, whereas the addKeyBinding is at a lower level, at the application layer.

I think you can't add a keybinding identical to one that already exists (though you might try?)

@echarles
Copy link
Member Author

echarles commented Jul 27, 2020

The "disabled" attribute is part of the setting registry, whereas the addKeyBinding is at a lower level, at the application layer.

Yep, saw that, this why I looked at the shortcuts-extension and saw that it was not exposed to others. Would there be a way to disable a keybindings via the settings extension?

I think you can't add a keybinding identical to one that already exists (though you might try?)

Tried that, and various combination like e.g. what @ianhi suggests with Would just defining the keymap (using addKeyBinding) as a no-op or return false be equivalent to disabling? but it looks like the VIM keybindings has not the priority and is hidden by jupyterelab. I could invoke from the jlab command (therefor, I need to access the selected editor, and use a _onActiveCellChanged approach).

@echarles
Copy link
Member Author

The last commit introduces a listener on cell change which allows to override the ESC command to go out of insert mode.

@echarles echarles marked this pull request as ready for review July 30, 2020 09:52
@echarles
Copy link
Member Author

This branch is now aligned with master and takes implements what we have discussed:

  • VIM mode for cell editor (the same way as for file editor).
  • Esc to exits vim mode
  • Shift-Esc to go to jupyter command mode
  • ctrl-j/k to shift up or down a cell

It is ready for review.

Copy link
Contributor

@ianhi ianhi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is really great! I have one final issue to bring up but otherwise I am a big 👍 on merging this. I've left some comments on places you could optimize the code a bit if you wanted to, but I don't think any of the proposed changes are necessary.

remaining issue:
I just tried this out and found that moving between cells when in vim-command-mode was not possible using j or k and up still works but down no longer works:
moving-between-cells

The vim extensions work around this by adding a custom codemirror command to j and k in vim mode:
https://github.com/axelfahy/jupyterlab-vim/blob/c4e43f940ef4be4c961608b6192412d2f3a33d1f/src/index.ts#L164-L173

The expected behavior is that pressing j or k in the last or first line will move me to the next cell:
expected-between

Comment on lines +300 to +306
let activeCell: Cell | null = null;

commands.addCommand('codemirror:leave-vim-insert-mode', {
label: 'Leave VIM Insert Mode',
execute: args => {
if (activeCell) {
let editor = activeCell.editor as CodeMirrorEditor;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is implicitly depending on onActiveCellChanged having been called in order to have set activeCell.

Suggested change
let activeCell: Cell | null = null;
commands.addCommand('codemirror:leave-vim-insert-mode', {
label: 'Leave VIM Insert Mode',
execute: args => {
if (activeCell) {
let editor = activeCell.editor as CodeMirrorEditor;
commands.addCommand('codemirror:leave-vim-insert-mode', {
label: 'Leave VIM Insert Mode',
execute: args => {
const activeCell = notebookTracker.activeCell;
if (activeCell) {
let editor = activeCell.editor as CodeMirrorEditor;

Comment on lines +313 to +334
function onActiveCellChanged(): void {
if (keyMap === 'vim') {
activeCell = notebookTracker.activeCell;
if (activeCell) {
// From https://github.com/axelfahy/jupyterlab-vim/blob/c4e43f940ef4be4c961608b6192412d2f3a33d1f/src/index.ts
CodeMirror.prototype.save = () => {
void commands.execute('docmanager:save');
};
const lvim = (CodeMirror as any).Vim as any;
if (lvim) {
lvim.defineEx('quit', 'q', function(cm: any) {
void commands.execute('notebook:enter-command-mode');
});
lvim.defineAction('splitCell', (cm: any, actionArgs: any) => {
void commands.execute('notebook:split-cell-at-cursor');
});
lvim.mapCommand('-', 'action', 'splitCell', {}, { extra: 'normal' });
}
}
}
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think all of these act globally and thus only need to be run when the vim keymap is selected rather than everytime the active cell is changed. I'd suggest moving these definitions under the if( keymap == 'vim').

Comment on lines 169 to +172
if (keyMap === 'vim') {
// @ts-expect-error
await import('codemirror/keymap/vim.js');
commands.addKeyBinding({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it be easy to add a signal.emit here with the keymap in it? I could see that being helpful for extensions building around the keymaps. If not easy then it's no big deal.

@ianhi
Copy link
Contributor

ianhi commented Aug 9, 2020

Aghh sorry I found one more issue. If you create a new cell then that cell will not be in vim mode until you toggle the vim setting on and off.
not-on-cell-creation

I think this is why the extensions did all the work of setting the keymap onActiveCellChanged rather than with a tracker. It seems you can only use signals to track the creation of new NotebookPanels there is no tracker for the creation of new cells. Though maybe there ought to be?

@blink1073 blink1073 added this to the Future milestone Aug 26, 2020
@ianhi ianhi mentioned this pull request Sep 24, 2020
3 tasks
@ianhi
Copy link
Contributor

ianhi commented Sep 24, 2020

Rather than purely finding issues I had a go at solving them. #9068 is a rebase of this PR that fixes all of the issues I raised.

@echarles
Copy link
Member Author

Closing as this work is now done in #9068.

@echarles echarles closed this Sep 30, 2020
@github-actions github-actions bot added the status:resolved-locked Closed issues are locked after 30 days inactivity. Please open a new issue for related discussion. label Mar 30, 2021
@github-actions github-actions bot locked as resolved and limited conversation to collaborators Mar 30, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
pkg:codemirror pkg:notebook status:resolved-locked Closed issues are locked after 30 days inactivity. Please open a new issue for related discussion.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants