UNPKG

@jupyterlab/debugger

Version:
340 lines 12 kB
// Copyright (c) Jupyter Development Team. // Distributed under the terms of the Modified BSD License. import { ActivityMonitor } from '@jupyterlab/coreutils'; import { Signal } from '@lumino/signaling'; import { Compartment, Prec, RangeSet, StateEffect, StateField } from '@codemirror/state'; import { Decoration, EditorView, gutter, GutterMarker } from '@codemirror/view'; /** * The class name added to the current line. */ const LINE_HIGHLIGHT_CLASS = 'jp-DebuggerEditor-highlight'; /** * The timeout for listening to editor content changes. */ const EDITOR_CHANGED_TIMEOUT = 1000; /** * A handler for a CodeEditor.IEditor. */ export class EditorHandler { /** * Instantiate a new EditorHandler. * * @param options The instantiation options for a EditorHandler. */ constructor(options) { var _a, _b, _c, _d; this._src = options.src; this._id = (_c = (_b = (_a = options.debuggerService.session) === null || _a === void 0 ? void 0 : _a.connection) === null || _b === void 0 ? void 0 : _b.id) !== null && _c !== void 0 ? _c : ''; this._path = (_d = options.path) !== null && _d !== void 0 ? _d : ''; this._debuggerService = options.debuggerService; this._editor = options.getEditor; this._editorMonitor = new ActivityMonitor({ signal: this._src.changed, timeout: EDITOR_CHANGED_TIMEOUT }); this._editorMonitor.activityStopped.connect(() => { this._sendEditorBreakpoints(); }, this); this._debuggerService.model.breakpoints.changed.connect(async () => { const editor = this.editor; if (!editor || editor.isDisposed) { return; } this._addBreakpointsToEditor(); }); this._debuggerService.model.breakpoints.restored.connect(async () => { const editor = this.editor; if (!editor || editor.isDisposed) { return; } this._addBreakpointsToEditor(); }); this._debuggerService.model.callstack.currentFrameChanged.connect(() => { const editor = this.editor; if (editor) { EditorHandler.clearHighlight(editor); } }); this._breakpointEffect = StateEffect.define({ map: (value, mapping) => ({ pos: value.pos.map(v => mapping.mapPos(v)) }) }); this._breakpointState = StateField.define({ create: () => { return RangeSet.empty; }, update: (breakpoints, transaction) => { breakpoints = breakpoints.map(transaction.changes); for (let ef of transaction.effects) { if (ef.is(this._breakpointEffect)) { let e = ef; if (e.value.pos.length) { breakpoints = breakpoints.update({ add: e.value.pos.map(v => Private.breakpointMarker.range(v)), sort: true }); } else { breakpoints = RangeSet.empty; } } } return breakpoints; } }); this._gutter = new Compartment(); this._highlightDeco = Decoration.line({ class: LINE_HIGHLIGHT_CLASS }); this._highlightState = StateField.define({ create: () => { return Decoration.none; }, update: (highlights, transaction) => { highlights = highlights.map(transaction.changes); for (let ef of transaction.effects) { if (ef.is(EditorHandler._highlightEffect)) { let e = ef; if (e.value.pos.length) { highlights = highlights.update({ add: e.value.pos.map(v => this._highlightDeco.range(v)) }); } else { highlights = Decoration.none; } } } return highlights; }, provide: f => EditorView.decorations.from(f) }); void options.editorReady().then(() => { this._setupEditor(); }); } /** * The editor */ get editor() { return this._editor(); } /** * Dispose the handler. */ dispose() { if (this.isDisposed) { return; } this._editorMonitor.dispose(); this._clearEditor(); this.isDisposed = true; Signal.clearData(this); } /** * Refresh the breakpoints display */ refreshBreakpoints() { this._addBreakpointsToEditor(); } /** * Setup the editor. */ _setupEditor() { const editor = this.editor; if (!editor || editor.isDisposed) { return; } editor.setOption('lineNumbers', true); const breakpointGutter = [ this._breakpointState, this._highlightState, Prec.highest(gutter({ class: 'cm-breakpoint-gutter', renderEmptyElements: true, markers: v => v.state.field(this._breakpointState), initialSpacer: () => Private.breakpointMarker, domEventHandlers: { mousedown: (view, line) => { this._onGutterClick(view, line.from); return true; } } })) ]; editor.injectExtension(this._gutter.of(breakpointGutter)); this._addBreakpointsToEditor(); } /** * Clear the editor by removing visual elements and handlers. */ _clearEditor() { const editor = this.editor; if (!editor || editor.isDisposed) { return; } EditorHandler.clearHighlight(editor); this._clearGutter(editor); editor.setOption('lineNumbers', false); editor.editor.dispatch({ effects: this._gutter.reconfigure([]) }); } /** * Send the breakpoints from the editor UI via the debug service. */ _sendEditorBreakpoints() { var _a; if ((_a = this.editor) === null || _a === void 0 ? void 0 : _a.isDisposed) { return; } const breakpoints = this._getBreakpointsFromEditor().map(lineNumber => { var _a, _b; return Private.createBreakpoint(((_b = (_a = this._debuggerService.session) === null || _a === void 0 ? void 0 : _a.connection) === null || _b === void 0 ? void 0 : _b.name) || '', lineNumber); }); void this._debuggerService.updateBreakpoints(this._src.getSource(), breakpoints, this._path); } /** * Handle a click on the gutter. * * @param editor The editor from where the click originated. * @param position The position corresponding to the click event. */ _onGutterClick(editor, position) { var _a, _b, _c; if (this._id !== ((_b = (_a = this._debuggerService.session) === null || _a === void 0 ? void 0 : _a.connection) === null || _b === void 0 ? void 0 : _b.id)) { return; } const lineNumber = editor.state.doc.lineAt(position).number; let stateBreakpoints = editor.state.field(this._breakpointState); let hasBreakpoint = false; stateBreakpoints.between(position, position, () => { hasBreakpoint = true; }); let breakpoints = this._getBreakpoints(); if (hasBreakpoint) { breakpoints = breakpoints.filter(ele => ele.line !== lineNumber); } else { breakpoints.push(Private.createBreakpoint((_c = this._path) !== null && _c !== void 0 ? _c : this._debuggerService.session.connection.name, lineNumber)); } breakpoints.sort((a, b) => { return a.line - b.line; }); void this._debuggerService.updateBreakpoints(this._src.getSource(), breakpoints, this._path); } /** * Add the breakpoints to the editor. */ _addBreakpointsToEditor() { var _a, _b; if (this._id !== ((_b = (_a = this._debuggerService.session) === null || _a === void 0 ? void 0 : _a.connection) === null || _b === void 0 ? void 0 : _b.id)) { return; } const editor = this.editor; const breakpoints = this._getBreakpoints(); this._clearGutter(editor); const breakpointPos = breakpoints.map(b => { return editor.state.doc.line(b.line).from; }); editor.editor.dispatch({ effects: this._breakpointEffect.of({ pos: breakpointPos }) }); } /** * Retrieve the breakpoints from the editor. */ _getBreakpointsFromEditor() { const editor = this.editor; const breakpoints = editor.editor.state.field(this._breakpointState); let lines = []; breakpoints.between(0, editor.doc.length, (from) => { lines.push(editor.doc.lineAt(from).number); }); return lines; } _clearGutter(editor) { if (!editor) { return; } const view = editor.editor; view.dispatch({ effects: this._breakpointEffect.of({ pos: [] }) }); } /** * Get the breakpoints for the editor using its content (code), * or its path (if it exists). */ _getBreakpoints() { const code = this._src.getSource(); return this._debuggerService.model.breakpoints.getBreakpoints(this._path || this._debuggerService.getCodeId(code)); } } /** * A namespace for EditorHandler `statics`. */ (function (EditorHandler) { EditorHandler._highlightEffect = StateEffect.define({ map: (value, mapping) => ({ pos: value.pos.map(v => mapping.mapPos(v)) }) }); /** * Highlight the current line of the frame in the given editor. * * @param editor The editor to highlight. * @param line The line number. */ function showCurrentLine(editor, line) { clearHighlight(editor); const cmEditor = editor; const linePos = cmEditor.doc.line(line).from; cmEditor.editor.dispatch({ effects: EditorHandler._highlightEffect.of({ pos: [linePos] }) }); } EditorHandler.showCurrentLine = showCurrentLine; /** * Remove all line highlighting indicators for the given editor. * * @param editor The editor to cleanup. */ function clearHighlight(editor) { if (!editor || editor.isDisposed) { return; } const cmEditor = editor; cmEditor.editor.dispatch({ effects: EditorHandler._highlightEffect.of({ pos: [] }) }); } EditorHandler.clearHighlight = clearHighlight; })(EditorHandler || (EditorHandler = {})); /** * A namespace for module private data. */ var Private; (function (Private) { /** * Create a marker DOM element for a breakpoint. */ Private.breakpointMarker = new (class extends GutterMarker { toDOM() { const marker = document.createTextNode('●'); return marker; } })(); /** * Create a new breakpoint. * * @param session The name of the session. * @param line The line number of the breakpoint. */ function createBreakpoint(session, line) { return { line, verified: true, source: { name: session } }; } Private.createBreakpoint = createBreakpoint; })(Private || (Private = {})); //# sourceMappingURL=editor.js.map