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

vim support in the notebook #9068

Closed
wants to merge 16 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions packages/codemirror-extension/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,18 @@
},
"dependencies": {
"@jupyterlab/application": "^3.0.0-rc.4",
"@jupyterlab/cells": "^3.0.0-rc.4",
"@jupyterlab/codeeditor": "^3.0.0-rc.4",
"@jupyterlab/codemirror": "^3.0.0-rc.4",
"@jupyterlab/docregistry": "^3.0.0-rc.4",
"@jupyterlab/fileeditor": "^3.0.0-rc.4",
"@jupyterlab/mainmenu": "^3.0.0-rc.4",
"@jupyterlab/notebook": "^3.0.0-rc.4",
"@jupyterlab/settingregistry": "^3.0.0-rc.4",
"@jupyterlab/statusbar": "^3.0.0-rc.4",
"@jupyterlab/translation": "^3.0.0-rc.4",
"@lumino/commands": "^1.11.3",
"@lumino/disposable": "^1.4.3",
"@lumino/widgets": "^1.14.0",
"codemirror": "~5.57.0"
},
Expand Down
18 changes: 18 additions & 0 deletions packages/codemirror-extension/schema/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@
"title": "Ctrl-C",
"description": "When enabled, which is the default, doing copy or cut when there is no selection will copy or cut the whole lines that have cursors on them.",
"default": true
},
"toggleComment": {
"type": "string",
"title": "Block Comment",
"description": "Key command for block comment",
"default": "Ctrl-/"
},
"toggleCommentMac": {
"type": "string",
"title": "Block Comment on MacOS keyboard",
"description": "Key command for block comment with MacOS",
"default": "Cmd-/"
Copy link
Contributor

Choose a reason for hiding this comment

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

Does JupyterLab support using Accel in this file to avoid having two different shortcuts (if that is a benefit)?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think so, but I just re-interpreted what jlab was already doing into being settings:

'Cmd-/': 'toggleComment',
'Ctrl-/': 'toggleComment',

Is accel = ctrl or alt?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok if this is what JupyterLab has elsewhere then just keep it as is I guess unless someone else with more knowledge chimes in. I believe Accel is set to Ctrl on Win/Linux and Cmd on macOS.

},
"vim:CtrlC-to-copy": {
"type": "boolean",
"title": "Ctrl-C Copies in Vim mode",
"description": "If true disable the default vim action for Ctrl-C to allow copying using the keyboard.",
"default": true
}
},
"type": "object"
Expand Down
146 changes: 101 additions & 45 deletions packages/codemirror-extension/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {

import { IEditMenu, IMainMenu } from '@jupyterlab/mainmenu';

import { setupKeymap } from './keymaps';

import { IEditorServices } from '@jupyterlab/codeeditor';

import {
Expand All @@ -23,10 +25,14 @@ import {
ICodeMirror
} from '@jupyterlab/codemirror';

import { IDisposable } from '@lumino/disposable';

import { IDocumentWidget } from '@jupyterlab/docregistry';

import { IEditorTracker, FileEditor } from '@jupyterlab/fileeditor';

import { INotebookTracker } from '@jupyterlab/notebook';

import { ISettingRegistry } from '@jupyterlab/settingregistry';

import { IStatusBar } from '@jupyterlab/statusbar';
Expand Down Expand Up @@ -69,7 +75,7 @@ const services: JupyterFrontEndPlugin<IEditorServices> = {
*/
const commands: JupyterFrontEndPlugin<void> = {
id: '@jupyterlab/codemirror-extension:commands',
requires: [IEditorTracker, ISettingRegistry, ITranslator],
requires: [IEditorTracker, INotebookTracker, ISettingRegistry, ITranslator],
optional: [IMainMenu],
activate: activateEditorCommands,
autoStart: true
Expand Down Expand Up @@ -165,7 +171,8 @@ function activateCodeMirror(app: JupyterFrontEnd): ICodeMirror {
*/
function activateEditorCommands(
app: JupyterFrontEnd,
tracker: IEditorTracker,
fileEditorTracker: IEditorTracker,
notebookTracker: INotebookTracker,
settingRegistry: ISettingRegistry,
translator: ITranslator,
mainMenu: IMainMenu | null
Expand All @@ -181,6 +188,12 @@ function activateEditorCommands(
selectionPointer,
lineWiseCopyCut
} = CodeMirrorEditor.defaultConfig;
commands;

let toggleComment = 'Ctrl-/';
let toggleCommentMac = 'Cmd-/';
Copy link
Contributor

Choose a reason for hiding this comment

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

Same comment about Accel here as above.

let vimDisableCtrlC = true;
let removeKeymaps: IDisposable[] = [];

/**
* Update the setting values.
Expand All @@ -189,15 +202,13 @@ function activateEditorCommands(
settings: ISettingRegistry.ISettings
): Promise<void> {
keyMap = (settings.get('keyMap').composite as string | null) || keyMap;

// Lazy loading of vim mode
if (keyMap === 'vim') {
// @ts-expect-error
await import('codemirror/keymap/vim.js');
}

removeKeymaps = await setupKeymap(
keyMap,
commands,
notebookTracker,
removeKeymaps
);
theme = (settings.get('theme').composite as string | null) || theme;

// Lazy loading of theme stylesheets
if (theme !== 'jupyter' && theme !== 'default') {
const filename =
Expand All @@ -223,64 +234,109 @@ function activateEditorCommands(
selectionPointer;
lineWiseCopyCut =
(settings.get('lineWiseCopyCut').composite as boolean) ?? lineWiseCopyCut;
toggleComment =
(settings.get('toggleComment').composite as string) ?? toggleComment;
toggleCommentMac =
(settings.get('toggleCommentMac').composite as string) ??
toggleCommentMac;
vimDisableCtrlC =
(settings.get('vim:CtrlC-to-copy').composite as boolean) ??
vimDisableCtrlC;
}

/**
* Set the settings of an editor instance
*/
function setEditorOptions(editor: CodeMirrorEditor) {
editor.setOption('keyMap', keyMap);
editor.setOption('theme', theme);
editor.setOption('lineWiseCopyCut', lineWiseCopyCut);
editor.setOption('selectionPointer', selectionPointer);
editor.setOption('styleActiveLine', styleActiveLine);
editor.setOption('styleSelectedText', styleSelectedText);
const extraKeys = editor.getOption('extraKeys') || {};
extraKeys[toggleComment] = 'toggleComment';
extraKeys[toggleCommentMac] = 'toggleComment';
if (vimDisableCtrlC && keyMap === 'vim') {
extraKeys['Ctrl-C'] = false;
}
editor.setOption('extraKeys', extraKeys);
}

/**
* Update the settings of the current tracker instances.
*/
function updateTracker(): void {
tracker.forEach(widget => {
function updateFileEditorTracker(): void {
fileEditorTracker.forEach(widget => {
if (widget.content.editor instanceof CodeMirrorEditor) {
const { editor } = widget.content;
editor.setOption('keyMap', keyMap);
editor.setOption('lineWiseCopyCut', lineWiseCopyCut);
const editor = widget.content.editor;
setEditorOptions(editor);
editor.setOption('scrollPastEnd', scrollPastEnd);
editor.setOption('selectionPointer', selectionPointer);
editor.setOption('styleActiveLine', styleActiveLine);
editor.setOption('styleSelectedText', styleSelectedText);
editor.setOption('theme', theme);
}
});
}

/**
* Handle the settings of FileEditors
*/
fileEditorTracker.widgetAdded.connect((sender, widget) => {
if (widget.content.editor instanceof CodeMirrorEditor) {
const editor = widget.content.editor;
setEditorOptions(editor);
editor.setOption('scrollPastEnd', scrollPastEnd);
}
});

/**
* Update the settings of the current Notebook instances
*/
function updateNotebookTracker(): void {
notebookTracker.forEach(widget => {
widget.content.widgets.forEach(cell => {
if (cell.inputArea.editor instanceof CodeMirrorEditor) {
setEditorOptions(cell.inputArea.editor);
}
});
});
}

// Fetch the initial state of the settings.
Promise.all([settingRegistry.load(id), restored])
.then(async ([settings]) => {
await updateSettings(settings);
updateTracker();
updateFileEditorTracker();
updateNotebookTracker();
settings.changed.connect(async () => {
await updateSettings(settings);
updateTracker();
updateFileEditorTracker();
updateNotebookTracker();
});
// connect to signal here to ensure vim keymap has
// been imported in case we need it
/**
* Handle settings of Notebook cells
*/
notebookTracker.newCellCreated.connect((sender, cell) => {
if (cell?.inputArea.editor instanceof CodeMirrorEditor) {
setEditorOptions(cell.inputArea.editor);
}
});
})
.catch((reason: Error) => {
console.error(reason.message);
updateTracker();
updateFileEditorTracker();
updateNotebookTracker();
});

/**
* Handle the settings of new widgets.
*/
tracker.widgetAdded.connect((sender, widget) => {
if (widget.content.editor instanceof CodeMirrorEditor) {
const { editor } = widget.content;
editor.setOption('keyMap', keyMap);
editor.setOption('lineWiseCopyCut', lineWiseCopyCut);
editor.setOption('selectionPointer', selectionPointer);
editor.setOption('scrollPastEnd', scrollPastEnd);
editor.setOption('styleActiveLine', styleActiveLine);
editor.setOption('styleSelectedText', styleSelectedText);
editor.setOption('theme', theme);
}
});

/**
* A test for whether the tracker has an active widget.
*/
function isEnabled(): boolean {
return (
tracker.currentWidget !== null &&
tracker.currentWidget === app.shell.currentWidget
(fileEditorTracker.currentWidget !== null &&
fileEditorTracker.currentWidget === app.shell.currentWidget) ||
(notebookTracker.currentWidget !== null &&
notebookTracker.currentWidget === app.shell.currentWidget)
);
}

Expand Down Expand Up @@ -334,7 +390,7 @@ function activateEditorCommands(
commands.addCommand(CommandIDs.find, {
label: trans.__('Find...'),
execute: () => {
const widget = tracker.currentWidget;
const widget = fileEditorTracker.currentWidget;
if (!widget) {
return;
}
Expand All @@ -347,7 +403,7 @@ function activateEditorCommands(
commands.addCommand(CommandIDs.goToLine, {
label: trans.__('Go to Line...'),
execute: () => {
const widget = tracker.currentWidget;
const widget = fileEditorTracker.currentWidget;
if (!widget) {
return;
}
Expand All @@ -361,7 +417,7 @@ function activateEditorCommands(
label: args => args['name'] as string,
execute: args => {
const name = args['name'] as string;
const widget = tracker.currentWidget;
const widget = fileEditorTracker.currentWidget;
if (name && widget) {
const spec = Mode.findByName(name);
if (spec) {
Expand All @@ -371,7 +427,7 @@ function activateEditorCommands(
},
isEnabled,
isToggled: args => {
const widget = tracker.currentWidget;
const widget = fileEditorTracker.currentWidget;
if (!widget) {
return false;
}
Expand Down Expand Up @@ -451,7 +507,7 @@ function activateEditorCommands(

// Add go to line capabilities to the edit menu.
mainMenu.editMenu.goToLiners.add({
tracker,
tracker: fileEditorTracker,
goToLine: (widget: IDocumentWidget<FileEditor>) => {
const editor = widget.content.editor as CodeMirrorEditor;
editor.execCommand('jumpToLine');
Expand Down