diff --git a/js/lab/src/plugin/codeEditor.ts b/js/lab/src/plugin/codeEditor.ts index cbb960e38c..3d088c3128 100644 --- a/js/lab/src/plugin/codeEditor.ts +++ b/js/lab/src/plugin/codeEditor.ts @@ -18,10 +18,13 @@ import { CodeMirrorEditor } from "@jupyterlab/codemirror"; import { Cell, CodeCell } from '@jupyterlab/cells'; import 'codemirror/mode/groovy/groovy'; +import {NotebookPanel} from "@jupyterlab/notebook"; const LINE_COMMENT_CHAR = '//'; +const LINE_MAGIC_MODE = 'line_magic'; +const CodeMirror = require("codemirror"); -export const registerCommentOutCmd = (panel) => { +export const registerCommentOutCmd = (panel: NotebookPanel) => { const cells = panel.notebook.widgets || []; cells @@ -50,3 +53,84 @@ const setCodeMirrorLineComment = (cell: Cell) => { mode.lineComment = LINE_COMMENT_CHAR; doc.mode = mode; }; + +export function extendHighlightModes(panel: NotebookPanel) { + const cells = panel.notebook.widgets || []; + + cells + .filter((cell) => (cell.editor instanceof CodeMirrorEditor)) + .forEach(setLineMagicForCell); + + CodeMirror.defineInitHook(addLineMagicsOverlay); +} + +function setLineMagicForCell(cell: Cell) { + if (!(cell instanceof CodeCell)) { + return; + } + + addLineMagicsOverlay((cell.editor).editor); +} + +const lineMagicOverlay = { + startState() { + return { firstMatched: false, inMagicLine: false }; + }, + + token(stream, state) { + if (!state.firstMatched) { + state.firstMatched = true; + + if (stream.match(/^%\w+/, false)) { + state.inMagicLine = true; + } + } + + if (state.inMagicLine) { + stream.eat(() => true); + + if (stream.eol()) { + state.inMagicLine = false; + } + + return LINE_MAGIC_MODE; + } + + stream.skipToEnd(); + + return null; + } +}; + +export function autoHighlightLineMagics(code_mirror) { + const current_mode = code_mirror.getOption('mode'); + const first_line = code_mirror.getLine(0); + const re = /^%\w+/; + + if (current_mode === LINE_MAGIC_MODE) { + return; + } + + if (first_line.match(re) !== null) { + // Add an overlay mode to recognize the first line as "line magic" instead + // of the mode used for the rest of the cell. + CodeMirror.defineMode(LINE_MAGIC_MODE, (config) => { + return CodeMirror.overlayMode(CodeMirror.getMode(config, current_mode), lineMagicOverlay); + }); + + code_mirror.setOption('mode', LINE_MAGIC_MODE); + } +} + +export function addLineMagicsOverlay(editor: any) { + autoHighlightLineMagics(editor); + + editor.off("focus", autoHighlightLineMagics); + editor.on("focus", autoHighlightLineMagics); + editor.off("change", autoHighlightLineMagics); + editor.on("change", autoHighlightLineMagics); + editor.off("blur", autoHighlightLineMagics); + editor.on("blur", autoHighlightLineMagics); + + editor.refresh(); +} diff --git a/js/lab/src/plugin/index.ts b/js/lab/src/plugin/index.ts index e59934fc91..f94a33803c 100644 --- a/js/lab/src/plugin/index.ts +++ b/js/lab/src/plugin/index.ts @@ -21,7 +21,7 @@ import { INotebookModel, NotebookPanel } from '@jupyterlab/notebook'; import { JupyterLab } from "@jupyterlab/application"; import { ISettingRegistry } from "@jupyterlab/coreutils"; import { registerCommTargets } from './comm'; -import { registerCommentOutCmd } from './codeEditor'; +import {extendHighlightModes, registerCommentOutCmd} from './codeEditor'; import { enableInitializationCellsFeature } from './initializationCells'; import UIOptionFeaturesHelper from "./UIOptionFeaturesHelper"; @@ -55,6 +55,7 @@ class BeakerxExtension implements DocumentRegistry.WidgetExtension { let settings = this.settings; Promise.all([panel.ready, panel.session.ready, context.ready]).then(function() { + extendHighlightModes(panel); enableInitializationCellsFeature(panel); registerCommentOutCmd(panel); registerCommTargets(panel, context); diff --git a/js/notebook/src/extension.js b/js/notebook/src/extension.js index 5b79bd74f0..646202854f 100644 --- a/js/notebook/src/extension.js +++ b/js/notebook/src/extension.js @@ -75,7 +75,7 @@ define([ var Autotranslation = require('./extension/autotranslation').Autotranslation; var BeakerXKernel = require('./extension/kernel').BeakerXKernel; var bkCoreManager = require('./shared/bkCoreManager').default; - var bxCodeMirror = require('./extension/codeMirror').default; + var bxCodeEditor = require('./extension/codeEditor').default; var inNotebook = !Jupyter.NotebookList; var mod_name = 'init_cell'; @@ -112,8 +112,8 @@ define([ if (inNotebook) { // setup things to run on loading config/notebook - bxCodeMirror.extendWithLineComment(Jupyter, CodeMirror); - bxCodeMirror.extendHighlightModes(Jupyter); + bxCodeEditor.extendWithLineComment(Jupyter, CodeMirror); + bxCodeEditor.extendHighlightModes(Jupyter); Jupyter.notebook.config.loaded .then(function update_options_from_config() { diff --git a/js/notebook/src/extension/codeMirror.ts b/js/notebook/src/extension/codeEditor.ts similarity index 100% rename from js/notebook/src/extension/codeMirror.ts rename to js/notebook/src/extension/codeEditor.ts