123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- // Copyright (c) Jupyter Development Team.
- // Distributed under the terms of the Modified BSD License.
- import { CodeCell } from '@jupyterlab/cells';
- import { CodeMirrorEditor } from '@jupyterlab/codemirror';
- import { IDisposable } from '@phosphor/disposable';
- import { Signal } from '@phosphor/signaling';
- import { Doc, Editor } from 'codemirror';
- import { Breakpoints, SessionTypes } from '../breakpoints';
- import { Debugger } from '../debugger';
- import { IDebugger } from '../tokens';
- const LINE_HIGHLIGHT_CLASS = 'jp-breakpoint-line-highlight';
- export class CellManager implements IDisposable {
- constructor(options: CellManager.IOptions) {
- this._debuggerService = options.debuggerService;
- this.onModelChanged();
- this._debuggerService.modelChanged.connect(() => this.onModelChanged());
- this.activeCell = options.activeCell;
- this.onActiveCellChanged();
- }
- isDisposed: boolean;
- private onModelChanged() {
- this._debuggerModel = this._debuggerService.model;
- if (!this._debuggerModel) {
- return;
- }
- this.breakpointsModel = this._debuggerModel.breakpointsModel;
- this._debuggerModel.variablesModel.changed.connect(() => {
- this.cleanupHighlight();
- const firstFrame = this._debuggerModel.callstackModel.frames[0];
- if (!firstFrame) {
- return;
- }
- this.showCurrentLine(firstFrame.line);
- });
- this.breakpointsModel.changed.connect(async () => {
- if (!this.activeCell || this.activeCell.isDisposed) {
- return;
- }
- this.addBreakpointsToEditor(this.activeCell);
- });
- if (this.activeCell) {
- this._debuggerModel.codeValue = this.activeCell.model.value;
- }
- }
- private showCurrentLine(lineNumber: number) {
- if (!this.activeCell) {
- return;
- }
- const editor = this.activeCell.editor as CodeMirrorEditor;
- this.cleanupHighlight();
- editor.editor.addLineClass(lineNumber - 1, 'wrap', LINE_HIGHLIGHT_CLASS);
- }
- // TODO: call when the debugger stops
- private cleanupHighlight() {
- if (!this.activeCell) {
- return;
- }
- const editor = this.activeCell.editor as CodeMirrorEditor;
- editor.doc.eachLine(line => {
- editor.editor.removeLineClass(line, 'wrap', LINE_HIGHLIGHT_CLASS);
- });
- }
- dispose(): void {
- if (this.isDisposed) {
- return;
- }
- if (this.previousCell) {
- this.removeListener(this.previousCell);
- }
- this.removeListener(this.activeCell);
- this.cleanupHighlight();
- Signal.clearData(this);
- }
- set previousCell(cell: CodeCell) {
- this._previousCell = cell;
- }
- get previousCell() {
- return this._previousCell;
- }
- set activeCell(cell: CodeCell) {
- if (cell) {
- this._activeCell = cell;
- this._debuggerModel.codeValue = cell.model.value;
- this.onActiveCellChanged();
- }
- }
- get activeCell(): CodeCell {
- return this._activeCell;
- }
- protected clearGutter(cell: CodeCell) {
- const editor = cell.editor as CodeMirrorEditor;
- editor.doc.eachLine(line => {
- if ((line as ILineInfo).gutterMarkers) {
- editor.editor.setGutterMarker(line, 'breakpoints', null);
- }
- });
- }
- onActiveCellChanged() {
- if (
- this.activeCell &&
- this.activeCell.isAttached &&
- this.activeCell.editor &&
- this._debuggerService &&
- this._debuggerService.session
- ) {
- if (this.previousCell && !this.previousCell.isDisposed) {
- this.removeListener(this.previousCell);
- }
- this.previousCell = this.activeCell;
- this.setEditor(this.activeCell);
- }
- }
- protected setEditor(cell: CodeCell) {
- if (!cell || !cell.editor) {
- return;
- }
- const editor = cell.editor as CodeMirrorEditor;
- this.previousLineCount = editor.lineCount;
- const editorBreakpoints = this.getBreakpointsInfo(cell).map(lineInfo => {
- return Private.createBreakpoint(
- this._debuggerService.session.client.name,
- this.getEditorId(),
- lineInfo.line + 1
- );
- });
- void this._debuggerService.updateBreakpoints(editorBreakpoints);
- editor.setOption('lineNumbers', true);
- editor.editor.setOption('gutters', [
- 'CodeMirror-linenumbers',
- 'breakpoints'
- ]);
- editor.editor.on('gutterClick', this.onGutterClick);
- editor.editor.on('renderLine', this.onNewRenderLine);
- }
- protected removeListener(cell: CodeCell) {
- const editor = cell.editor as CodeMirrorEditor;
- editor.editor.off('gutterClick', this.onGutterClick);
- editor.editor.off('renderLine', this.onNewRenderLine);
- }
- protected getEditorId(): string {
- return this.activeCell.editor.uuid;
- }
- protected onGutterClick = (editor: Editor, lineNumber: number) => {
- const info = editor.lineInfo(lineNumber);
- if (!info) {
- return;
- }
- const isRemoveGutter = !!info.gutterMarkers;
- let breakpoints = this.breakpointsModel.breakpoints;
- if (isRemoveGutter) {
- breakpoints = breakpoints.filter(ele => ele.line !== info.line + 1);
- } else {
- breakpoints.push(
- Private.createBreakpoint(
- this._debuggerService.session.client.name,
- this.getEditorId(),
- info.line + 1
- )
- );
- }
- void this._debuggerService.updateBreakpoints(breakpoints);
- };
- protected onNewRenderLine = (editor: Editor, line: any) => {
- const lineInfo = editor.lineInfo(line);
- if (lineInfo.handle && lineInfo.handle.order === false) {
- return;
- }
- const doc: Doc = editor.getDoc();
- const linesNumber = doc.lineCount();
- if (this.previousLineCount !== linesNumber) {
- let lines: number[] = [];
- doc.eachLine(line => {
- if ((line as ILineInfo).gutterMarkers) {
- const lineInfo = editor.lineInfo(line);
- lines.push(lineInfo.line + 1);
- }
- });
- this.breakpointsModel.changeLines(lines);
- this.previousLineCount = linesNumber;
- }
- };
- private addBreakpointsToEditor(cell: CodeCell) {
- this.clearGutter(cell);
- const editor = cell.editor as CodeMirrorEditor;
- const breakpoints = this._debuggerModel.breakpointsModel.breakpoints;
- breakpoints.forEach(breakpoint => {
- editor.editor.setGutterMarker(
- breakpoint.line - 1,
- 'breakpoints',
- Private.createMarkerNode()
- );
- });
- }
- private getBreakpointsInfo(cell: CodeCell): ILineInfo[] {
- const editor = cell.editor as CodeMirrorEditor;
- let lines = [];
- for (let i = 0; i < editor.doc.lineCount(); i++) {
- const info = editor.editor.lineInfo(i);
- if (info.gutterMarkers) {
- lines.push(info);
- }
- }
- return lines;
- }
- private _previousCell: CodeCell;
- private previousLineCount: number;
- private _debuggerModel: Debugger.Model;
- private breakpointsModel: Breakpoints.Model;
- private _activeCell: CodeCell;
- private _debuggerService: IDebugger;
- }
- export namespace CellManager {
- export interface IOptions {
- debuggerModel: Debugger.Model;
- debuggerService: IDebugger;
- breakpointsModel: Breakpoints.Model;
- activeCell?: CodeCell;
- type: SessionTypes;
- }
- }
- export interface ILineInfo {
- line: any;
- handle: any;
- text: string;
- /** Object mapping gutter IDs to marker elements. */
- gutterMarkers: any;
- textClass: string;
- bgClass: string;
- wrapClass: string;
- /** Array of line widgets attached to this line. */
- widgets: any;
- }
- namespace Private {
- export function createMarkerNode() {
- let marker = document.createElement('div');
- marker.className = 'jp-breakpoint-marker';
- marker.innerHTML = '●';
- return marker;
- }
- export function createBreakpoint(
- session: string,
- type: string,
- line: number
- ) {
- return {
- line,
- active: true,
- verified: true,
- source: {
- name: session
- }
- };
- }
- }
|