diff --git a/interface/debugger.js b/interface/debugger.js index a17d0cc..e76951e 100644 --- a/interface/debugger.js +++ b/interface/debugger.js @@ -54,9 +54,11 @@ declare module 'debugger' { } declare type Variable = { + id: string, name: string, value: string, - type: ?string + type: ?string, + has_children: boolean } declare class SessionEvent { @@ -79,10 +81,20 @@ declare module 'debugger' { constructor(type: TargetEventType, message: string): void; } + declare type VariableEventType = 'updated' | 'left-scope' | 'entered-scope' + + declare class VariableEvent { + type: VariableEventType; + variable: Variable; + + constructor(type: VariableEventType, variable: Variable): void; + } + declare interface EventDefs { BreakpointEvent: BreakpointEvent; SessionEvent: SessionEvent; TargetEvent: TargetEvent; + VariableEvent: VariableEvent; } declare interface DebuggerTarget { @@ -98,6 +110,8 @@ declare module 'debugger' { onTargetEvent(callback: ((event: TargetEvent) => void)): Disposable; + onVariableEvent(callback: ((event: VariableEvent) => void)): Disposable; + findBreakpoint(location: BreakpointLocation): ?Breakpoint; removeBreakpoint(breakpoint: Breakpoint): boolean; @@ -108,7 +122,7 @@ declare module 'debugger' { setSelectedFrame(level: number): void; - getVariableList(): Promise>; + getVariableChildren(variable: Variable): Promise>; } declare interface DebuggerRegistry { diff --git a/lib/debugger-scope-view.js b/lib/debugger-scope-view.js index ec18c39..85cb6d0 100644 --- a/lib/debugger-scope-view.js +++ b/lib/debugger-scope-view.js @@ -1,18 +1,172 @@ 'use babel' /* @flow */ + import DebuggerPanelSection from './debugger-panel-section' +import type { + DebuggerController, + DebuggerProxy, + SessionEvent, + Variable, + VariableEvent +} from 'debugger' + +const errorHandler = (error: mixed) => { + const message = (error && error.msg) ? error.msg : 'Unknown error occured' + atom.notifications.addError(message, { dismissable: true }) +} + +class DebuggerScopeViewPrivate { + element: DebuggerPanelSection; + variableListElement: HTMLElement; + + debuggerProxy: DebuggerProxy; + + createCallbacks(): void { + + const proxy = this.debuggerProxy + + proxy.onVariableEvent( (event: VariableEvent) => { + + if (event.type !== 'entered-scope') { return } + + this.createVariableView(event.variable, this.variableListElement) + }) + + proxy.onVariableEvent( (event: VariableEvent) => { + + if (event.type !== 'left-scope') { return } + + const element = document.querySelector(`[data-id='${event.variable.id}']`) + + if (!element) { throw new Error('Could not find corresponding element') } + + const parent = element.parentElement + + if (parent) { parent.removeChild(element) } + }) + } + + createScopeView(): void { + + const list = document.createElement('ul') + + list.classList.add('list-tree', 'has-collapsable-children') + + this.element.appendChild(list) + this.variableListElement = list + } + + createVariableView(variable: Variable, parent: HTMLElement): void { + + let variableView = document.createElement('li') + parent.appendChild(variableView) + + variableView.dataset.id = variable.id + + if (variable.has_children) { + variableView.classList.add('list-nested-item', 'collapsed') + let variableHeader = document.createElement('div') + variableView.appendChild(variableHeader) + + variableHeader.classList.add('list-item') + + const view = variableView + + variableHeader.addEventListener('click', event => { + + if (event.shiftKey || event.metaKey || event.ctrlKey) { return } + + if (view.classList.contains('collapsed')) { + view.classList.remove('collapsed') + + if (!view.querySelector('.list-tree')) { + const proxy = this.debuggerProxy + const childView = document.createElement('ul') + childView.classList.add('list-tree') + + view.appendChild(childView) + + proxy.getVariableChildren(variable) + .then( (children: Array) => { + for (let i=0; i { return p.element } + this.activate = (controller) => { p.activate(controller) } } + + activate: (controller: DebuggerController) => void; + + getElement: () => DebuggerPanelSection; } diff --git a/lib/debugger-view.js b/lib/debugger-view.js index a7aa22e..2f81eec 100644 --- a/lib/debugger-view.js +++ b/lib/debugger-view.js @@ -61,9 +61,9 @@ export default class DebuggerView { if (!event.executionLine || typeof event.executionLine !== 'object') { throw Error('SessionEvent.executionLine must be an object') } else if (typeof event.executionLine.filePath !== 'string') { - throw Error('SessionEvent.executionLine.filePath must be a string') + return } else if (typeof event.executionLine.bufferRow !== 'number') { - throw Error('SessionEvent.executionLine.bufferRow must be a string') + return } atom.workspace.open(event.executionLine.filePath).then((editor) => { diff --git a/styles/debugger-ui-default.scope-view.less b/styles/debugger-ui-default.scope-view.less new file mode 100644 index 0000000..06da871 --- /dev/null +++ b/styles/debugger-ui-default.scope-view.less @@ -0,0 +1,41 @@ +@import 'ui-variables'; + +debugger-panel-section.scope-view { + @panel-padding: @component-padding / 2; + + display: flex; + padding: @panel-padding; + + ul.list-tree { + flex-grow: 1; + } + + li.variable, div.variable { + align-items: baseline; + display: flex; + } + + .spacer { + flex-grow: 1; + } + + .type { + font-family: monospace; + margin-left: 0.5em; + margin-right: 0.5em; + font-size: 0.9em; + + &::before { + content: '('; + } + + &::after { + content: ')'; + } + } + + .value.unknown { + font-style: italic; + color: @text-color-subtle; + } +}