Forráskód Böngészése

Merge pull request #29 from KsavinN/debugger-ui

Callstack and Breakpoint Development
KsavinN 5 éve
szülő
commit
7b52661e3b

+ 83 - 0
src/breakpoints/body.tsx

@@ -0,0 +1,83 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import React, { useState } from 'react';
+import { Breakpoints } from '.';
+import { ReactWidget } from '@jupyterlab/apputils';
+import { ArrayExt } from '@phosphor/algorithm';
+import { ISignal } from '@phosphor/signaling';
+
+export class Body extends ReactWidget {
+  constructor(model: Breakpoints.IModel) {
+    super();
+    this.model = model;
+    this.addClass('jp-DebuggerBreakpoints-body');
+  }
+
+  render() {
+    return <BreakpointsComponent model={this.model} />;
+  }
+
+  readonly model: Breakpoints.IModel;
+}
+
+const BreakpointsComponent = ({ model }: { model: Breakpoints.IModel }) => {
+  const [breakpoints, setBreakpoints] = useState(model.breakpoints);
+
+  model.breakpointsChanged.connect(
+    (_: Breakpoints.IModel, updates: Breakpoints.IBreakpoint[]) => {
+      if (ArrayExt.shallowEqual(breakpoints, updates)) {
+        return;
+      }
+      setBreakpoints(updates);
+    }
+  );
+
+  return (
+    <div>
+      {breakpoints.map((breakpoint: any) => (
+        <BreakpointComponent
+          key={breakpoint.id}
+          breakpoint={breakpoint}
+          breakpointChanged={model.breakpointChanged}
+        />
+      ))}
+    </div>
+  );
+};
+
+const BreakpointComponent = ({
+  breakpoint,
+  breakpointChanged
+}: {
+  breakpoint: Breakpoints.IBreakpoint;
+  breakpointChanged: ISignal<Breakpoints.IModel, Breakpoints.IBreakpoint>;
+}) => {
+  const [active, setActive] = useState(breakpoint.active);
+  breakpoint.active = active;
+
+  breakpointChanged.connect(
+    (_: Breakpoints.IModel, updates: Breakpoints.IBreakpoint) => {
+      setActive(updates.active);
+    }
+  );
+
+  const setBreakpointEnabled = (state: boolean) => {
+    setActive(state);
+  };
+
+  return (
+    <div className={`breakpoint`}>
+      <input
+        onChange={() => {
+          setBreakpointEnabled(!active);
+        }}
+        type="checkbox"
+        checked={active}
+      />
+      <span>
+        {breakpoint.source.name} : {breakpoint.line}
+      </span>
+    </div>
+  );
+};

+ 84 - 5
src/breakpoints/index.ts

@@ -4,16 +4,20 @@
 import { Toolbar, ToolbarButton } from '@jupyterlab/apputils';
 
 import { Widget, Panel, PanelLayout } from '@phosphor/widgets';
+import { DebugProtocol } from 'vscode-debugprotocol';
+import { Body } from './body';
+import { Signal, ISignal } from '@phosphor/signaling';
 
 export class Breakpoints extends Panel {
   constructor(options: Breakpoints.IOptions = {}) {
     super();
 
-    this.model = {};
+    this.model = new Breakpoints.IModel(MOCK_BREAKPOINTS);
     this.addClass('jp-DebuggerBreakpoints');
     this.title.label = 'Breakpoints';
 
     const header = new BreakpointsHeader(this.title.label);
+    this.body = new Body(this.model);
 
     this.addWidget(header);
     this.addWidget(this.body);
@@ -22,16 +26,31 @@ export class Breakpoints extends Panel {
       'deactivate',
       new ToolbarButton({
         iconClassName: 'jp-DebuggerDeactivateIcon',
+        tooltip: `${this.isAllActive ? 'Deactivate' : 'Activate'} Breakpoints`,
         onClick: () => {
-          console.log('`deactivate` was clicked');
+          this.isAllActive = !this.isAllActive;
+          this.model.breakpoints.map((breakpoint: Breakpoints.IBreakpoint) => {
+            breakpoint.active = this.isAllActive;
+            this.model.breakpoint = breakpoint;
+          });
+        }
+      })
+    );
+
+    header.toolbar.addItem(
+      'closeAll',
+      new ToolbarButton({
+        iconClassName: 'jp-CloseAllIcon',
+        onClick: () => {
+          this.model.breakpoints = [];
         },
-        tooltip: 'Deactivate Breakpoints'
+        tooltip: 'Remove All Breakpoints'
       })
     );
   }
 
-  readonly body = new Panel();
-
+  private isAllActive = true;
+  readonly body: Widget;
   readonly model: Breakpoints.IModel;
 }
 
@@ -52,13 +71,73 @@ class BreakpointsHeader extends Widget {
 }
 
 export namespace Breakpoints {
+  export interface IBreakpoint extends DebugProtocol.Breakpoint {
+    active: boolean;
+  }
+
   /**
    * The breakpoints UI model.
    */
   export interface IModel {}
 
+  export class IModel implements IModel {
+    constructor(model: IBreakpoint[]) {
+      this._state = model;
+    }
+
+    get breakpointsChanged(): ISignal<this, IBreakpoint[]> {
+      return this._breakpointsChanged;
+    }
+
+    get breakpoints(): IBreakpoint[] {
+      return this._state;
+    }
+
+    get breakpointChanged(): ISignal<this, IBreakpoint> {
+      return this._breakpointChanged;
+    }
+
+    set breakpoints(breakpoints: IBreakpoint[]) {
+      this._state = breakpoints;
+      this._breakpointsChanged.emit(this._state);
+    }
+
+    set breakpoint(breakpoint: IBreakpoint) {
+      const index = this._state.findIndex(ele => ele.id === breakpoint.id);
+      if (index !== -1) {
+        this._state[index] = breakpoint;
+        this._breakpointChanged.emit(breakpoint);
+      }
+    }
+
+    private _state: IBreakpoint[];
+    private _breakpointsChanged = new Signal<this, IBreakpoint[]>(this);
+    private _breakpointChanged = new Signal<this, IBreakpoint>(this);
+  }
+
   /**
    * 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
+  }
+];

+ 42 - 0
src/callstack/body.tsx

@@ -0,0 +1,42 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import React, { useState } from 'react';
+import { Callstack } from '.';
+import { ReactWidget } from '@jupyterlab/apputils';
+
+export class Body extends ReactWidget {
+  constructor(model: Callstack.IModel) {
+    super();
+    this.model = model;
+    this.addClass('jp-DebuggerCallstack-body');
+  }
+
+  render() {
+    return <FramesComponent model={this.model} />;
+  }
+
+  readonly model: Callstack.IModel;
+}
+
+const FramesComponent = ({ model }: { model: Callstack.IModel }) => {
+  const [frames] = useState(model.frames);
+  const [selected, setSelected] = useState();
+  const onSelected = (frame: any) => {
+    setSelected(frame);
+  };
+
+  return (
+    <ul>
+      {frames.map(ele => (
+        <li
+          key={ele.id}
+          onClick={() => onSelected(ele)}
+          className={selected === ele ? 'selected' : ''}
+        >
+          {ele.name} at {ele.source.name}:{ele.line}
+        </li>
+      ))}
+    </ul>
+  );
+};

+ 44 - 4
src/callstack/index.ts

@@ -4,19 +4,22 @@
 import { Toolbar, ToolbarButton } from '@jupyterlab/apputils';
 
 import { Widget, Panel, PanelLayout } from '@phosphor/widgets';
+import { Body } from './body';
+import { DebugProtocol } from 'vscode-debugprotocol';
 
 export class Callstack extends Panel {
   constructor(options: Callstack.IOptions = {}) {
     super();
 
-    this.model = {};
+    this.model = new Callstack.IModel(MOCK_FRAMES);
     this.addClass('jp-DebuggerCallstack');
     this.title.label = 'Callstack';
 
     const header = new CallstackHeader(this.title.label);
+    const body = new Body(this.model);
 
     this.addWidget(header);
-    this.addWidget(this.body);
+    this.addWidget(body);
 
     header.toolbar.addItem(
       'continue',
@@ -70,8 +73,6 @@ export class Callstack extends Panel {
     );
   }
 
-  readonly body = new Panel();
-
   readonly model: Callstack.IModel;
 }
 
@@ -92,7 +93,46 @@ class CallstackHeader extends Widget {
 }
 
 export namespace Callstack {
+  export interface IFrame extends DebugProtocol.StackFrame {}
+
   export interface IModel {}
 
+  export class IModel implements IModel {
+    constructor(model: IFrame[]) {
+      this.state = model;
+    }
+
+    set frames(newFrames: IFrame[]) {
+      this.state = newFrames;
+    }
+
+    get frames(): IFrame[] {
+      return this.state;
+    }
+
+    private state: IFrame[];
+  }
+
   export interface IOptions extends Panel.IOptions {}
 }
+
+const MOCK_FRAMES: Callstack.IFrame[] = [
+  {
+    id: 0,
+    name: 'test',
+    source: {
+      name: 'untitled.py'
+    },
+    line: 6,
+    column: 1
+  },
+  {
+    id: 1,
+    name: '<module>',
+    source: {
+      name: 'untitled.py'
+    },
+    line: 7,
+    column: 1
+  }
+];

+ 4 - 0
style/breakpoints.css

@@ -2,3 +2,7 @@
 | Copyright (c) Jupyter Development Team.
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/
+
+.jp-DebuggerBreakpoints-body {
+  padding: 10px;
+}

+ 19 - 0
style/callstack.css

@@ -2,3 +2,22 @@
 | Copyright (c) Jupyter Development Team.
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/
+
+.jp-DebuggerCallstack-body ul {
+  list-style: none;
+  margin: 0;
+  padding: 0;
+  background: var(--jp-layout-color1);
+  color: var(--jp-ui-font-color1);
+  font-size: var(--jp-ui-font-size1);
+}
+
+.jp-DebuggerCallstack-body ul li {
+  padding: 5px;
+  padding-left: 8px;
+}
+
+.jp-DebuggerCallstack-body ul li.selected {
+  color: white;
+  background: var(--jp-brand-color1);
+}

+ 4 - 0
style/icons.css

@@ -54,3 +54,7 @@
 .jp-VariableIcon {
   background-image: url('icons/variable.svg');
 }
+
+.jp-CloseAllIcon {
+  background-image: url('icons/close-all.svg');
+}

+ 4 - 0
style/icons/close-all.svg

@@ -0,0 +1,4 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path fill-rule="evenodd" clip-rule="evenodd" d="M8.62132 8.0858L7.91421 7.37869L6.5 8.7929L5.08579 7.37869L4.37868 8.0858L5.79289 9.50001L4.37868 10.9142L5.08579 11.6213L6.5 10.2071L7.91421 11.6213L8.62132 10.9142L7.20711 9.50001L8.62132 8.0858Z" fill="#424242"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M5 3L6 2H13L14 3V10L13 11H11V13L10 14H3L2 13V6L3 5H5V3ZM6 5H10L11 6V10H13V3H6V5ZM10 6H3V13H10V6Z" fill="#424242"/>
+</svg>