-
-
Notifications
You must be signed in to change notification settings - Fork 43
Connect Breakpoints widget with NoteBook #36
Changes from 11 commits
c5b8d24
833b2cc
e60f900
e6bd0dc
b3e1fa7
5ad3971
484108a
ec77927
2b0ce1d
c37f31c
01aa541
8752e6f
061feff
2d676fa
7dd3752
47d0bb3
d303fcd
7da863f
b7551a1
743347f
25b1dc8
a3995cb
681a7d8
7f50def
951a239
c24b512
fe547a4
a0bdb64
fdd97c2
6fcd20f
6e63811
a053c95
c8c3a85
a2d10a6
e6c6fa9
933fade
0297d8d
2915521
85d59d9
f0f1186
28bdc70
9355956
1baa7f4
d7071cc
79cbf93
6cd90a5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,16 @@ import { Widget, Panel, PanelLayout } from '@phosphor/widgets'; | |
import { DebugProtocol } from 'vscode-debugprotocol'; | ||
import { Body } from './body'; | ||
import { Signal, ISignal } from '@phosphor/signaling'; | ||
import { INotebookTracker } from '@jupyterlab/notebook'; | ||
import { CodeMirrorEditor } from '@jupyterlab/codemirror'; | ||
import { Editor, Doc } from 'codemirror'; | ||
import { CodeCell } from '@jupyterlab/cells'; | ||
|
||
export class Breakpoints extends Panel { | ||
constructor(options: Breakpoints.IOptions = {}) { | ||
constructor(options: Breakpoints.IOptions) { | ||
super(); | ||
|
||
this.model = new Breakpoints.IModel(MOCK_BREAKPOINTS); | ||
this.model = new Breakpoints.IModel([]); | ||
this.addClass('jp-DebuggerBreakpoints'); | ||
this.title.label = 'Breakpoints'; | ||
|
||
|
@@ -43,17 +47,160 @@ export class Breakpoints extends Panel { | |
iconClassName: 'jp-CloseAllIcon', | ||
onClick: () => { | ||
this.model.breakpoints = []; | ||
this.cellsBreakpoints[this.getCell().id] = []; | ||
this.removeAllGutterBreakpoints(this.getCell()); | ||
}, | ||
tooltip: 'Remove All Breakpoints' | ||
}) | ||
); | ||
|
||
this.noteTracker = options.noteTracker; | ||
if (this.noteTracker) { | ||
this.noteTracker.activeCellChanged.connect( | ||
this.onActiveCellChanged, | ||
this | ||
); | ||
} | ||
} | ||
|
||
private isAllActive = true; | ||
readonly body: Widget; | ||
readonly model: Breakpoints.IModel; | ||
} | ||
noteTracker: INotebookTracker; | ||
previousCell: CodeCell; | ||
previousLineCount: number; | ||
cellsBreakpoints: { [id: string]: Breakpoints.IBreakpoint[] } = {}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we change the name of this to |
||
|
||
protected onActiveCellChanged() { | ||
const activeCell = this.getCell(); | ||
if (this.model && activeCell) { | ||
if (this.previousCell && !this.previousCell.isDisposed) { | ||
this.removeListner(this.previousCell); | ||
} | ||
this.previousCell = activeCell; | ||
const id: string = activeCell.model.id; | ||
if (id && !this.cellsBreakpoints[id]) { | ||
this.cellsBreakpoints[id] = []; | ||
} | ||
this.model.breakpoints = this.cellsBreakpoints[id]; | ||
this.setEditor(activeCell); | ||
} | ||
} | ||
|
||
protected getCell(): CodeCell { | ||
return this.noteTracker.activeCell as CodeCell; | ||
} | ||
|
||
protected removeAllGutterBreakpoints(cell: CodeCell) { | ||
const editor = cell.editor as CodeMirrorEditor; | ||
editor.editor.getDoc().eachLine(line => { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thinking out loud: I was wondering whether there was a way to avoid the |
||
editor.editor.setGutterMarker(line, 'breakpoints', null); | ||
}); | ||
} | ||
|
||
removeListner(cell: CodeCell) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. typo: |
||
const editor = cell.editor as CodeMirrorEditor; | ||
this.cellsBreakpoints[cell.model.id] = this.model.breakpoints; | ||
this.model.breakpoints = []; | ||
editor.setOption('lineNumbers', false); | ||
editor.editor.off('gutterClick', this.onGutterClick); | ||
editor.editor.off('renderLine', this.onNewRenderLine); | ||
} | ||
|
||
setEditor(cell: CodeCell) { | ||
if (!cell || !cell.editor) { | ||
return; | ||
} | ||
|
||
const editor = cell.editor as CodeMirrorEditor; | ||
editor.setOption('lineNumbers', true); | ||
editor.editor.setOption('gutters', [ | ||
'CodeMirror-linenumbers', | ||
'breakpoints' | ||
]); | ||
|
||
editor.editor.on('gutterClick', this.onGutterClick); | ||
editor.editor.on('renderLine', this.onNewRenderLine); | ||
} | ||
|
||
protected onNewRenderLine = (editor: Editor, line: any) => { | ||
const lineInfo = editor.lineInfo(line); | ||
if ( | ||
!this.model.breakpoints && | ||
this.model.breakpoints.length < 1 && | ||
lineInfo.handle && | ||
lineInfo.handle.order === false | ||
) { | ||
return; | ||
} | ||
|
||
const doc: Doc = editor.getDoc(); | ||
const linesNumber = doc.lineCount(); | ||
|
||
if (this.previousLineCount !== linesNumber) { | ||
if (this.previousLineCount > linesNumber) { | ||
this.model.changeLines(lineInfo.line, -1); | ||
} | ||
if (this.previousLineCount < linesNumber) { | ||
this.model.changeLines(lineInfo.line, +1); | ||
} | ||
this.previousLineCount = linesNumber; | ||
} | ||
// eage case for backspace line 2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what does this edge case look like? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If you set a breakpoint in line 1 and after that remove line 2 in editor gutter remove automatically breakpoint from line 1. |
||
if (lineInfo.line === 0) { | ||
const breakpoint: Breakpoints.IBreakpoint = this.model.getBreakpointByLineNumber( | ||
-1 | ||
); | ||
if (breakpoint) { | ||
this.model.removeBreakpoint(breakpoint); | ||
} | ||
} | ||
}; | ||
|
||
private addBreakpoint(line: number) { | ||
this.model.breakpoint = { | ||
id: this.model.breakpoints.length + 1, | ||
active: true, | ||
verified: true, | ||
source: { | ||
// TODO: need get filename | ||
name: 'untitled.py' | ||
}, | ||
line: line | ||
}; | ||
} | ||
|
||
protected onGutterClick = (editor: Editor, lineNumber: number) => { | ||
const info = editor.lineInfo(lineNumber); | ||
if (!info) { | ||
return; | ||
} | ||
const isRemoveGutter = !!info.gutterMarkers; | ||
|
||
const breakpoint: Breakpoints.IBreakpoint = this.model.getBreakpointByLineNumber( | ||
lineNumber | ||
); | ||
|
||
if (!breakpoint && !isRemoveGutter) { | ||
this.addBreakpoint(lineNumber); | ||
} else if (isRemoveGutter) { | ||
this.model.removeBreakpoint(breakpoint); | ||
} | ||
|
||
editor.setGutterMarker( | ||
lineNumber, | ||
'breakpoints', | ||
isRemoveGutter ? null : this.createMarkerNode() | ||
); | ||
}; | ||
|
||
createMarkerNode() { | ||
var marker = document.createElement('div'); | ||
marker.className = 'jp-breakpoint-marker'; | ||
marker.innerHTML = '●'; | ||
return marker; | ||
} | ||
} | ||
class BreakpointsHeader extends Widget { | ||
constructor(title: string) { | ||
super({ node: document.createElement('header') }); | ||
|
@@ -103,13 +250,34 @@ export namespace Breakpoints { | |
} | ||
|
||
set breakpoint(breakpoint: IBreakpoint) { | ||
const index = this._state.findIndex(ele => ele.id === breakpoint.id); | ||
const index = this._state.findIndex(ele => ele.line === breakpoint.line); | ||
if (index !== -1) { | ||
this._state[index] = breakpoint; | ||
this._breakpointChanged.emit(breakpoint); | ||
} else { | ||
this.breakpoints = [...this.breakpoints, breakpoint]; | ||
} | ||
} | ||
|
||
removeBreakpoint(breakpoint: IBreakpoint) { | ||
const breakpoints = this.breakpoints.filter( | ||
ele => ele.line !== breakpoint.line | ||
); | ||
this.breakpoints = breakpoints; | ||
} | ||
|
||
changeLines(lineEnter: number, howMany: number) { | ||
const breakpoints = this.breakpoints.map(ele => { | ||
ele.line = lineEnter <= ele.line ? ele.line + howMany : ele.line; | ||
return ele; | ||
}); | ||
this.breakpoints = breakpoints; | ||
} | ||
|
||
getBreakpointByLineNumber(lineNumber: number) { | ||
return this.breakpoints.find(ele => ele.line === lineNumber); | ||
} | ||
|
||
private _state: IBreakpoint[]; | ||
private _breakpointsChanged = new Signal<this, IBreakpoint[]>(this); | ||
private _breakpointChanged = new Signal<this, IBreakpoint>(this); | ||
|
@@ -118,26 +286,7 @@ export namespace Breakpoints { | |
/** | ||
* Instantiation options for `Breakpoints`; | ||
*/ | ||
export interface IOptions extends Panel.IOptions {} | ||
} | ||
|
||
const MOCK_BREAKPOINTS = [ | ||
{ | ||
id: 0, | ||
active: true, | ||
verified: true, | ||
source: { | ||
name: 'untitled.py' | ||
}, | ||
line: 6 | ||
}, | ||
{ | ||
id: 1, | ||
verified: true, | ||
active: false, | ||
source: { | ||
name: 'untitled.py' | ||
}, | ||
line: 7 | ||
export interface IOptions extends Panel.IOptions { | ||
noteTracker?: INotebookTracker; | ||
} | ||
]; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,21 +3,18 @@ | |
|
||
import { IDataConnector } from '@jupyterlab/coreutils'; | ||
|
||
import { BoxPanel, TabPanel } from '@phosphor/widgets'; | ||
import { BoxPanel } from '@phosphor/widgets'; | ||
|
||
import { ReadonlyJSONValue, UUID } from '@phosphor/coreutils'; | ||
|
||
import { IDisposable } from '@phosphor/disposable'; | ||
|
||
import { DebuggerSidebar } from './sidebar'; | ||
import { INotebookTracker } from '@jupyterlab/notebook'; | ||
import { CodeCell } from '@jupyterlab/cells'; | ||
import { IEditorTracker } from '@jupyterlab/fileeditor'; | ||
|
||
export class Debugger extends BoxPanel { | ||
readonly model: Debugger.Model; | ||
|
||
readonly tabs = new TabPanel(); | ||
|
||
readonly sidebar: DebuggerSidebar; | ||
|
||
constructor(options: Debugger.IOptions) { | ||
super({ direction: 'left-to-right' }); | ||
|
||
|
@@ -26,10 +23,15 @@ export class Debugger extends BoxPanel { | |
this.title.label = 'Debugger'; | ||
|
||
this.addClass('jp-Debugger'); | ||
this.addWidget(this.tabs); | ||
this.addWidget(this.sidebar); | ||
} | ||
|
||
readonly model: Debugger.Model; | ||
|
||
readonly sidebar: DebuggerSidebar; | ||
|
||
previousCell: CodeCell; | ||
|
||
dispose(): void { | ||
if (this.isDisposed) { | ||
return; | ||
|
@@ -45,14 +47,16 @@ export class Debugger extends BoxPanel { | |
export namespace Debugger { | ||
export interface IOptions { | ||
connector?: IDataConnector<ReadonlyJSONValue>; | ||
|
||
noteTracker?: INotebookTracker; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure I understand the reasoning here. Why does the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In actual branch, i working on a split this. I get your idea before, these changes I made after the suggestion of Jeremy. I guess tomorrow or on Wednesday I will merge changes to this branch. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ah okay, great; I'll take a look whenever you're ready for that! |
||
editorTracker?: IEditorTracker; | ||
id?: string; | ||
} | ||
|
||
export class Model implements IDisposable { | ||
constructor(options: Debugger.Model.IOptions) { | ||
this.connector = options.connector || null; | ||
this.id = options.id || UUID.uuid4(); | ||
this._notebook = options.noteTracker; | ||
void this._populate(); | ||
} | ||
|
||
|
@@ -64,6 +68,10 @@ export namespace Debugger { | |
return this._isDisposed; | ||
} | ||
|
||
get notebookTracker() { | ||
return this._notebook; | ||
} | ||
|
||
dispose(): void { | ||
this._isDisposed = true; | ||
} | ||
|
@@ -77,6 +85,7 @@ export namespace Debugger { | |
} | ||
|
||
private _isDisposed = false; | ||
private _notebook: INotebookTracker; | ||
} | ||
|
||
export namespace Model { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should go to
devDependencies