cell.ts 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { CodeCell } from '@jupyterlab/cells';
  4. import { CodeMirrorEditor } from '@jupyterlab/codemirror';
  5. import { Editor, Doc } from 'codemirror';
  6. import { Breakpoints, SessionTypes } from '../breakpoints';
  7. import { Debugger } from '../debugger';
  8. import { IDebugger } from '../tokens';
  9. import { IDisposable } from '@phosphor/disposable';
  10. import { Signal } from '@phosphor/signaling';
  11. const LINE_HIGHLIGHT_CLASS = 'jp-breakpoint-line-highlight';
  12. export class CellManager implements IDisposable {
  13. constructor(options: CellManager.IOptions) {
  14. this._debuggerModel = options.debuggerModel;
  15. this._debuggerService = options.debuggerService;
  16. this.breakpointsModel = options.breakpointsModel;
  17. this.activeCell = options.activeCell;
  18. this._type = options.type;
  19. this.onActiveCellChanged();
  20. this.breakpointsModel.clearedBreakpoints.connect((_, type) => {
  21. if (type !== this._type) {
  22. return;
  23. }
  24. this.clearGutter(this.activeCell);
  25. });
  26. this._debuggerModel.currentLineChanged.connect((_, lineNumber) => {
  27. this.showCurrentLine(lineNumber);
  28. });
  29. }
  30. private _previousCell: CodeCell;
  31. private previousLineCount: number;
  32. private _debuggerModel: Debugger.Model;
  33. private _debuggerService: IDebugger.IService;
  34. private _type: SessionTypes;
  35. private breakpointsModel: Breakpoints.Model;
  36. private _activeCell: CodeCell;
  37. isDisposed: boolean;
  38. private showCurrentLine(lineNumber: number) {
  39. if (!this.activeCell) {
  40. return;
  41. }
  42. const editor = this.activeCell.editor as CodeMirrorEditor;
  43. this.cleanupHighlight();
  44. editor.editor.addLineClass(lineNumber - 1, 'wrap', LINE_HIGHLIGHT_CLASS);
  45. }
  46. // TODO: call when the debugger stops
  47. private cleanupHighlight() {
  48. if (!this.activeCell) {
  49. return;
  50. }
  51. const editor = this.activeCell.editor as CodeMirrorEditor;
  52. editor.doc.eachLine(line => {
  53. editor.editor.removeLineClass(line, 'wrap', LINE_HIGHLIGHT_CLASS);
  54. });
  55. }
  56. dispose(): void {
  57. if (this.isDisposed) {
  58. return;
  59. }
  60. if (this.previousCell) {
  61. this.removeListener(this.previousCell);
  62. }
  63. this.removeListener(this.activeCell);
  64. this.cleanupHighlight();
  65. Signal.clearData(this);
  66. }
  67. set previousCell(cell: CodeCell) {
  68. this._previousCell = cell;
  69. }
  70. get previousCell() {
  71. return this._previousCell;
  72. }
  73. set activeCell(cell: CodeCell) {
  74. if (cell) {
  75. this._activeCell = cell;
  76. this._debuggerModel.codeValue = cell.model.value;
  77. this.onActiveCellChanged();
  78. }
  79. }
  80. get activeCell(): CodeCell {
  81. return this._activeCell;
  82. }
  83. protected clearGutter(cell: CodeCell) {
  84. const editor = cell.editor as CodeMirrorEditor;
  85. editor.doc.eachLine(line => {
  86. if ((line as ILineInfo).gutterMarkers) {
  87. editor.editor.setGutterMarker(line, 'breakpoints', null);
  88. }
  89. });
  90. }
  91. onActiveCellChanged() {
  92. if (
  93. this.activeCell &&
  94. this.activeCell.isAttached &&
  95. this.activeCell.editor &&
  96. this._debuggerService &&
  97. this._debuggerService.session
  98. ) {
  99. if (this.previousCell && !this.previousCell.isDisposed) {
  100. this.removeListener(this.previousCell);
  101. this.clearGutter(this.previousCell);
  102. this.breakpointsModel.breakpoints = [];
  103. }
  104. this.previousCell = this.activeCell;
  105. this.setEditor(this.activeCell);
  106. }
  107. }
  108. protected setEditor(cell: CodeCell) {
  109. if (!cell || !cell.editor) {
  110. return;
  111. }
  112. const editor = cell.editor as CodeMirrorEditor;
  113. this.previousLineCount = editor.lineCount;
  114. editor.setOption('lineNumbers', true);
  115. editor.editor.setOption('gutters', [
  116. 'CodeMirror-linenumbers',
  117. 'breakpoints'
  118. ]);
  119. editor.editor.on('gutterClick', this.onGutterClick);
  120. editor.editor.on('renderLine', this.onNewRenderLine);
  121. }
  122. protected removeListener(cell: CodeCell) {
  123. const editor = cell.editor as CodeMirrorEditor;
  124. editor.setOption('lineNumbers', false);
  125. editor.editor.off('gutterClick', this.onGutterClick);
  126. editor.editor.off('renderLine', this.onNewRenderLine);
  127. }
  128. protected getEditorId(): string {
  129. return this.activeCell.editor.uuid;
  130. }
  131. protected onGutterClick = (editor: Editor, lineNumber: number) => {
  132. const info = editor.lineInfo(lineNumber);
  133. if (!info) {
  134. return;
  135. }
  136. const isRemoveGutter = !!info.gutterMarkers;
  137. if (isRemoveGutter) {
  138. this.breakpointsModel.removeBreakpoint(info as ILineInfo);
  139. } else {
  140. this.breakpointsModel.addBreakpoint(
  141. this._debuggerService.session.client.name,
  142. this.getEditorId(),
  143. info as ILineInfo
  144. );
  145. }
  146. editor.setGutterMarker(
  147. lineNumber,
  148. 'breakpoints',
  149. isRemoveGutter ? null : this.createMarkerNode()
  150. );
  151. };
  152. protected onNewRenderLine = (editor: Editor, line: any) => {
  153. const lineInfo = editor.lineInfo(line);
  154. if (lineInfo.handle && lineInfo.handle.order === false) {
  155. return;
  156. }
  157. const doc: Doc = editor.getDoc();
  158. const linesNumber = doc.lineCount();
  159. if (this.previousLineCount !== linesNumber) {
  160. let lines: ILineInfo[] = [];
  161. doc.eachLine(line => {
  162. if ((line as ILineInfo).gutterMarkers) {
  163. lines.push(editor.lineInfo(line));
  164. }
  165. });
  166. this.breakpointsModel.changeLines(lines);
  167. this.previousLineCount = linesNumber;
  168. }
  169. };
  170. private createMarkerNode() {
  171. let marker = document.createElement('div');
  172. marker.className = 'jp-breakpoint-marker';
  173. marker.innerHTML = '●';
  174. return marker;
  175. }
  176. }
  177. export namespace CellManager {
  178. export interface IOptions {
  179. debuggerModel: Debugger.Model;
  180. debuggerService: IDebugger.IService;
  181. breakpointsModel: Breakpoints.Model;
  182. activeCell?: CodeCell;
  183. type: SessionTypes;
  184. }
  185. }
  186. export interface ILineInfo {
  187. line: any;
  188. handle: any;
  189. text: string;
  190. /** Object mapping gutter IDs to marker elements. */
  191. gutterMarkers: any;
  192. textClass: string;
  193. bgClass: string;
  194. wrapClass: string;
  195. /** Array of line widgets attached to this line. */
  196. widgets: any;
  197. }