浏览代码

Merge branch 'master' into KsavinN-breakpointLine

Jeremy Tuloup 5 年之前
父节点
当前提交
f2a8c4734d

文件差异内容过多而无法显示
+ 0 - 0
README.md


+ 9 - 0
binder/environment.yml

@@ -0,0 +1,9 @@
+name: jupyterlab-debugger
+channels:
+- conda-forge
+dependencies:
+- jupyterlab=1.1
+- nodejs
+- notebook=6
+- ptvsd
+- xeus-python=0.6

+ 1 - 0
binder/postBuild

@@ -0,0 +1 @@
+jlpm && jlpm build && jupyter labextension install .

+ 2 - 1
package.json

@@ -56,7 +56,8 @@
     "@phosphor/coreutils": "^1.3.1",
     "@phosphor/disposable": "^1.2.0",
     "@phosphor/widgets": "^1.8.0",
-    "vscode-debugprotocol": "1.35.0"
+    "vscode-debugprotocol": "1.35.0",
+    "react-inspector": "^2.0.0"
   },
   "devDependencies": {
     "@babel/core": "^7.5.5",

+ 4 - 36
src/debugger.ts

@@ -5,8 +5,6 @@ import { CodeEditor } from '@jupyterlab/codeeditor';
 
 import { DebugService } from './service';
 
-import { DebugSession } from './session';
-
 import { DebuggerEditors } from './editors';
 
 import { DebuggerSidebar } from './sidebar';
@@ -37,9 +35,11 @@ export class Debugger extends SplitPanel {
 
     this.model = new Debugger.Model(options);
 
-    this.sidebar = new DebuggerSidebar(this.model);
+    this.sidebar = new DebuggerSidebar();
     this.model.sidebar = this.sidebar;
 
+    this.service = new DebugService(this.model);
+
     const { editorFactory } = options;
     this.editors = new DebuggerEditors({ editorFactory });
     this.addWidget(this.editors);
@@ -50,6 +50,7 @@ export class Debugger extends SplitPanel {
   readonly editors: DebuggerEditors;
   readonly model: Debugger.Model;
   readonly sidebar: DebuggerSidebar;
+  readonly service: DebugService;
 
   dispose(): void {
     if (this.isDisposed) {
@@ -79,12 +80,6 @@ export namespace Debugger {
   export class Model implements IDisposable {
     constructor(options: Debugger.Model.IOptions) {
       this.connector = options.connector || null;
-      // Avoids setting session with invalid client
-      // session should be set only when a notebook or
-      // a console get the focus.
-      // TODO: also checks that the notebook or console
-      // runs a kernel with debugging ability
-      this.session = null;
       this.id = options.id;
       void this._populate();
     }
@@ -116,30 +111,6 @@ export namespace Debugger {
       return this._modeChanged;
     }
 
-    get session(): IDebugger.ISession {
-      return this._session;
-    }
-
-    set session(session: IDebugger.ISession | null) {
-      if (this._session === session) {
-        return;
-      }
-      if (this._session) {
-        this._session.dispose();
-      }
-      this._session = session;
-      this._service.session = session as DebugSession;
-      this._sessionChanged.emit(undefined);
-    }
-
-    get service(): DebugService {
-      return this._service;
-    }
-
-    get sessionChanged(): ISignal<this, void> {
-      return this._sessionChanged;
-    }
-
     get isDisposed(): boolean {
       return this._isDisposed;
     }
@@ -173,9 +144,6 @@ export namespace Debugger {
     private _isDisposed = false;
     private _mode: IDebugger.Mode;
     private _modeChanged = new Signal<this, IDebugger.Mode>(this);
-    private _session: IDebugger.ISession | null;
-    private _sessionChanged = new Signal<this, void>(this);
-    private _service = new DebugService(null, this);
     private _currentLineChanged = new Signal<this, number>(this);
   }
 

+ 7 - 4
src/handlers/cell.ts

@@ -10,7 +10,7 @@ import { Editor, Doc } from 'codemirror';
 import { Breakpoints, SessionTypes } from '../breakpoints';
 
 import { Debugger } from '../debugger';
-
+import { IDebugger } from '../tokens';
 import { IDisposable } from '@phosphor/disposable';
 
 import { Signal } from '@phosphor/signaling';
@@ -18,6 +18,7 @@ import { Signal } from '@phosphor/signaling';
 export class CellManager implements IDisposable {
   constructor(options: CellManager.IOptions) {
     this._debuggerModel = options.debuggerModel;
+    this._debuggerService = options.debuggerService;
     this.breakpointsModel = options.breakpointsModel;
     this.activeCell = options.activeCell;
     this._type = options.type;
@@ -38,6 +39,7 @@ export class CellManager implements IDisposable {
   private _previousCell: CodeCell;
   private previousLineCount: number;
   private _debuggerModel: Debugger.Model;
+  private _debuggerService: IDebugger.IService;
   private _type: SessionTypes;
   private breakpointsModel: Breakpoints.Model;
   private _activeCell: CodeCell;
@@ -115,8 +117,8 @@ export class CellManager implements IDisposable {
       this.activeCell &&
       this.activeCell.isAttached &&
       this.activeCell.editor &&
-      this._debuggerModel &&
-      this._debuggerModel.session
+      this._debuggerService &&
+      this._debuggerService.session
     ) {
       if (this.previousCell && !this.previousCell.isDisposed) {
         this.removeListener(this.previousCell);
@@ -169,7 +171,7 @@ export class CellManager implements IDisposable {
       this.breakpointsModel.removeBreakpoint(info as ILineInfo);
     } else {
       this.breakpointsModel.addBreakpoint(
-        this._debuggerModel.session.client.name,
+        this._debuggerService.session.client.name,
         this.getEditorId(),
         info as ILineInfo
       );
@@ -212,6 +214,7 @@ export class CellManager implements IDisposable {
 export namespace CellManager {
   export interface IOptions {
     debuggerModel: Debugger.Model;
+    debuggerService: IDebugger.IService;
     breakpointsModel: Breakpoints.Model;
     activeCell?: CodeCell;
     type: SessionTypes;

+ 8 - 2
src/handlers/console.ts

@@ -9,6 +9,8 @@ import { CodeCell } from '@jupyterlab/cells';
 
 import { Breakpoints } from '../breakpoints';
 
+import { IDebugger } from '../tokens';
+
 import { Debugger } from '../debugger';
 import { IDisposable } from '@phosphor/disposable';
 import { Signal } from '@phosphor/signaling';
@@ -16,12 +18,13 @@ import { Signal } from '@phosphor/signaling';
 export class DebuggerConsoleHandler implements IDisposable {
   constructor(options: DebuggerConsoleHandler.IOptions) {
     this.debuggerModel = options.debuggerModel;
-    this.consoleTracker = options.consoleTracker;
+    this.consoleTracker = options.tracker;
     this.breakpoints = this.debuggerModel.sidebar.breakpoints.model;
     this.cellManager = new CellManager({
       activeCell: this.consoleTracker.currentWidget.console.promptCell,
       breakpointsModel: this.breakpoints,
       debuggerModel: this.debuggerModel,
+      debuggerService: this.debuggerService,
       type: 'console'
     });
     this.consoleTracker.currentWidget.console.promptCellCreated.connect(
@@ -32,6 +35,7 @@ export class DebuggerConsoleHandler implements IDisposable {
 
   private consoleTracker: IConsoleTracker;
   private debuggerModel: Debugger.Model;
+  private debuggerService: IDebugger.IService;
   private breakpoints: Breakpoints.Model;
   private cellManager: CellManager;
   isDisposed: boolean;
@@ -54,6 +58,7 @@ export class DebuggerConsoleHandler implements IDisposable {
         activeCell: update,
         breakpointsModel: this.breakpoints,
         debuggerModel: this.debuggerModel,
+        debuggerService: this.debuggerService,
         type: 'console'
       });
     }
@@ -63,6 +68,7 @@ export class DebuggerConsoleHandler implements IDisposable {
 export namespace DebuggerConsoleHandler {
   export interface IOptions {
     debuggerModel: Debugger.Model;
-    consoleTracker: IConsoleTracker;
+    debuggerService: IDebugger.IService;
+    tracker: IConsoleTracker;
   }
 }

+ 9 - 2
src/handlers/notebook.ts

@@ -9,6 +9,8 @@ import { CellManager } from './cell';
 
 import { Debugger } from '../debugger';
 
+import { IDebugger } from '../tokens';
+
 import { Breakpoints } from '../breakpoints';
 
 import { IDisposable } from '@phosphor/disposable';
@@ -18,19 +20,22 @@ import { Signal } from '@phosphor/signaling';
 export class DebuggerNotebookHandler implements IDisposable {
   constructor(options: DebuggerNotebookHandler.IOptions) {
     this.debuggerModel = options.debuggerModel;
-    this.notebookTracker = options.notebookTracker;
+    this.debuggerService = options.debuggerService;
+    this.notebookTracker = options.tracker;
     this.breakpoints = this.debuggerModel.sidebar.breakpoints.model;
     this.notebookTracker.activeCellChanged.connect(this.onNewCell, this);
     this.cellManager = new CellManager({
       breakpointsModel: this.breakpoints,
       activeCell: this.notebookTracker.activeCell as CodeCell,
       debuggerModel: this.debuggerModel,
+      debuggerService: this.debuggerService,
       type: 'notebook'
     });
   }
 
   private notebookTracker: INotebookTracker;
   private debuggerModel: Debugger.Model;
+  private debuggerService: IDebugger.IService;
   private breakpoints: Breakpoints.Model;
   private cellManager: CellManager;
   isDisposed: boolean;
@@ -53,6 +58,7 @@ export class DebuggerNotebookHandler implements IDisposable {
         breakpointsModel: this.breakpoints,
         activeCell: codeCell,
         debuggerModel: this.debuggerModel,
+        debuggerService: this.debuggerService,
         type: 'notebook'
       });
     }
@@ -62,6 +68,7 @@ export class DebuggerNotebookHandler implements IDisposable {
 export namespace DebuggerNotebookHandler {
   export interface IOptions {
     debuggerModel: Debugger.Model;
-    notebookTracker: INotebookTracker;
+    debuggerService: IDebugger.IService;
+    tracker: INotebookTracker;
   }
 }

+ 75 - 83
src/index.ts

@@ -8,7 +8,7 @@ import {
   ILabShell
 } from '@jupyterlab/application';
 
-import { ICommandPalette } from '@jupyterlab/apputils';
+import { IClientSession, ICommandPalette } from '@jupyterlab/apputils';
 
 import { WidgetTracker, MainAreaWidget } from '@jupyterlab/apputils';
 
@@ -56,6 +56,51 @@ export namespace CommandIDs {
   export const changeMode = 'debugger:change-mode';
 }
 
+async function setDebugSession(
+  app: JupyterFrontEnd,
+  debug: IDebugger,
+  client: IClientSession
+) {
+  if (!debug.session) {
+    debug.session = new DebugSession({ client: client });
+  } else {
+    debug.session.client = client;
+  }
+  if (debug.session) {
+    await debug.session.restoreState();
+    app.commands.notifyCommandChanged();
+  }
+}
+
+class HandlerTracker<
+  H extends DebuggerConsoleHandler | DebuggerNotebookHandler
+> {
+  constructor(builder: new (option: any) => H) {
+    this.builder = builder;
+  }
+
+  update<
+    T extends IConsoleTracker | INotebookTracker,
+    W extends ConsolePanel | NotebookPanel
+  >(debug: IDebugger, tracker: T, widget: W): void {
+    if (debug.tracker.currentWidget && !this.handlers[widget.id]) {
+      const handler = new this.builder({
+        tracker: tracker,
+        debuggerModel: debug.tracker.currentWidget.content.model,
+        debuggerService: debug.tracker.currentWidget.content.service
+      });
+      this.handlers[widget.id] = handler;
+      widget.disposed.connect(() => {
+        delete this.handlers[widget.id];
+        handler.dispose();
+      });
+    }
+  }
+
+  private handlers: { [id: string]: H } = {};
+  private builder: new (option: any) => H;
+}
+
 /**
  * A plugin that provides visual debugging support for consoles.
  */
@@ -69,43 +114,17 @@ const consoles: JupyterFrontEndPlugin<void> = {
     tracker: IConsoleTracker,
     labShell: ILabShell
   ) => {
-    let oldhandler: {
-      id: string;
-      handler: DebuggerConsoleHandler;
-    };
+    const handlerTracker = new HandlerTracker<DebuggerConsoleHandler>(
+      DebuggerConsoleHandler
+    );
 
     labShell.currentChanged.connect(async (_, update) => {
       const widget = update.newValue;
-
       if (!(widget instanceof ConsolePanel)) {
         return;
       }
-
-      if (!debug.session) {
-        debug.session = new DebugSession({ client: widget.session });
-      } else {
-        debug.session.client = widget.session;
-      }
-      if (debug.session) {
-        await debug.session.restoreState();
-        app.commands.notifyCommandChanged();
-      }
-      if (debug.tracker.currentWidget) {
-        const handler = new DebuggerConsoleHandler({
-          consoleTracker: tracker,
-          debuggerModel: debug.tracker.currentWidget.content.model
-        });
-        if (!oldhandler) {
-          oldhandler = {
-            id: widget.id,
-            handler: handler
-          };
-        } else if (oldhandler.id !== widget.id) {
-          oldhandler.id = widget.id;
-          oldhandler.handler.dispose();
-          oldhandler.handler = handler;
-        }
-      }
+      await setDebugSession(app, debug, widget.session);
+      handlerTracker.update(debug, tracker, widget);
     });
   }
 };
@@ -172,43 +191,17 @@ const notebooks: JupyterFrontEndPlugin<void> = {
     tracker: INotebookTracker,
     labShell: ILabShell
   ) => {
-    let oldhandler: {
-      id: string;
-      handler: DebuggerNotebookHandler;
-    };
+    const handlerTracker = new HandlerTracker<DebuggerNotebookHandler>(
+      DebuggerNotebookHandler
+    );
 
     labShell.currentChanged.connect(async (_, update) => {
       const widget = update.newValue;
       if (!(widget instanceof NotebookPanel)) {
         return;
       }
-      if (!debug.session) {
-        debug.session = new DebugSession({ client: widget.session });
-      } else {
-        debug.session.client = widget.session;
-      }
-      if (debug.session) {
-        await debug.session.restoreState();
-        app.commands.notifyCommandChanged();
-      }
-      if (debug.tracker.currentWidget) {
-        if (!oldhandler) {
-          oldhandler = {
-            id: widget.id,
-            handler: new DebuggerNotebookHandler({
-              notebookTracker: tracker,
-              debuggerModel: debug.tracker.currentWidget.content.model
-            })
-          };
-        } else if (oldhandler.id !== widget.id) {
-          oldhandler.id = widget.id;
-          oldhandler.handler.dispose();
-          oldhandler.handler = new DebuggerNotebookHandler({
-            notebookTracker: tracker,
-            debuggerModel: debug.tracker.currentWidget.content.model
-          });
-        }
-      }
+      await setDebugSession(app, debug, widget.session);
+      handlerTracker.update(debug, tracker, widget);
     });
   }
 };
@@ -242,6 +235,12 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
       return tracker.currentWidget ? tracker.currentWidget.content.model : null;
     };
 
+    const getService = () => {
+      return tracker.currentWidget
+        ? tracker.currentWidget.content.service
+        : null;
+    };
+
     commands.addCommand(CommandIDs.mount, {
       execute: args => {
         if (!widget) {
@@ -283,14 +282,11 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
     commands.addCommand(CommandIDs.stop, {
       label: 'Stop',
       isEnabled: () => {
-        const debuggerModel = getModel();
-        return (debuggerModel &&
-          debuggerModel.session !== null &&
-          debuggerModel.session.isStarted) as boolean;
+        const service = getService();
+        return service && service.isStarted();
       },
       execute: async () => {
-        const debuggerModel = getModel();
-        await debuggerModel.session.stop();
+        await getService().session.stop();
         commands.notifyCommandChanged();
       }
     });
@@ -298,14 +294,11 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
     commands.addCommand(CommandIDs.start, {
       label: 'Start',
       isEnabled: () => {
-        const debuggerModel = getModel();
-        return (debuggerModel &&
-          debuggerModel.session !== null &&
-          !debuggerModel.session.isStarted) as boolean;
+        const service = getService();
+        return service && service.canStart();
       },
       execute: async () => {
-        const debuggerModel = getModel();
-        await debuggerModel.session.start();
+        await getService().session.start();
         commands.notifyCommandChanged();
       }
     });
@@ -313,14 +306,13 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
     commands.addCommand(CommandIDs.debugNotebook, {
       label: 'Launch',
       isEnabled: () => {
-        const debuggerModel = getModel();
-        return (debuggerModel &&
-          debuggerModel.session !== null &&
-          debuggerModel.session.isStarted) as boolean;
+        const service = getService();
+        return service && service.isStarted();
       },
       execute: async () => {
-        const debuggerModel = getModel();
-        await debuggerModel.service.launch(debuggerModel.codeValue.text);
+        await tracker.currentWidget.content.service.launch(
+          getModel().codeValue.text
+        );
       }
     });
 
@@ -410,11 +402,11 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
         },
         session: {
           get: (): IDebugger.ISession | null => {
-            return widget ? widget.content.model.session : null;
+            return widget ? widget.content.service.session : null;
           },
           set: (src: IDebugger.ISession | null) => {
             if (widget) {
-              widget.content.model.session = src;
+              widget.content.service.session = src;
             }
           }
         },

+ 41 - 18
src/service.ts

@@ -1,4 +1,4 @@
-import { DebugSession } from './session';
+import { ISignal, Signal } from '@phosphor/signaling';
 
 import { DebugProtocol } from 'vscode-debugprotocol';
 
@@ -10,37 +10,55 @@ import { Variables } from './variables';
 
 import { Callstack } from './callstack';
 
-export class DebugService {
-  constructor(session: DebugSession | null, debuggerModel: Debugger.Model) {
-    this.session = session;
+export class DebugService implements IDebugger.IService {
+  constructor(debuggerModel: Debugger.Model) {
+    // Avoids setting session with invalid client
+    // session should be set only when a notebook or
+    // a console get the focus.
+    // TODO: also checks that the notebook or console
+    // runs a kernel with debugging ability
+    this._session = null;
     this._model = debuggerModel;
   }
 
-  private _session: DebugSession;
-  private _model: Debugger.Model;
-  private frames: Frame[];
-
-  set session(session: DebugSession) {
+  set session(session: IDebugger.ISession) {
+    if (this._session === session) {
+      return;
+    }
+    if (this._session) {
+      this._session.dispose();
+    }
     this._session = session;
+    this._sessionChanged.emit(session);
   }
 
   get session() {
     return this._session;
   }
 
+  canStart(): boolean {
+    return this._session !== null && !this._session.isStarted;
+  }
+
+  isStarted(): boolean {
+    return this._session !== null && this._session.isStarted;
+  }
+
+  get sessionChanged(): ISignal<IDebugger.IService, IDebugger.ISession> {
+    return this._sessionChanged;
+  }
+
   // this will change for after execute cell
   async launch(code: string): Promise<void> {
     let threadId: number = 1;
     this.frames = [];
-    this.session.eventMessage.connect(
-      (sender: DebugSession, event: IDebugger.ISession.Event) => {
-        const eventName = event.event;
-        if (eventName === 'thread') {
-          const msg = event as DebugProtocol.ThreadEvent;
-          threadId = msg.body.threadId;
-        }
+    this.session.eventMessage.connect((_, event: IDebugger.ISession.Event) => {
+      const eventName = event.event;
+      if (eventName === 'thread') {
+        const msg = event as DebugProtocol.ThreadEvent;
+        threadId = msg.body.threadId;
       }
-    );
+    });
 
     const breakpoints: DebugProtocol.SourceBreakpoint[] = this.setBreakpoints();
     const reply = await this.session.sendRequest('dumpCell', {
@@ -135,11 +153,16 @@ export class DebugService {
       return {
         name: scope.name,
         variables: variables.map(variable => {
-          return { ...variable, description: '' };
+          return { ...variable };
         })
       };
     });
   };
+
+  private _session: IDebugger.ISession;
+  private _sessionChanged = new Signal<this, IDebugger.ISession>(this);
+  private _model: Debugger.Model;
+  private frames: Frame[];
 }
 
 export type Frame = {

+ 14 - 7
src/session.ts

@@ -116,7 +116,7 @@ export class DebugSession implements IDebugger.ISession {
 
       await this.sendRequest('attach', {});
     } catch (err) {
-      console.error('Error: ', err.message);
+      console.error('Error:', err.message);
     }
   }
 
@@ -131,7 +131,7 @@ export class DebugSession implements IDebugger.ISession {
       });
       this._isStarted = false;
     } catch (err) {
-      console.error('Error: ', err.message);
+      console.error('Error:', err.message);
     }
   }
 
@@ -139,6 +139,7 @@ export class DebugSession implements IDebugger.ISession {
    * Restore the state of a debug session.
    */
   async restoreState(): Promise<void> {
+    await this.client.ready;
     try {
       const message = await this.sendRequest('debugInfo', {});
       this._isStarted = message.body.isStarted;
@@ -168,7 +169,7 @@ export class DebugSession implements IDebugger.ISession {
   /**
    * Signal emitted for debug event messages.
    */
-  get eventMessage(): ISignal<DebugSession, IDebugger.ISession.Event> {
+  get eventMessage(): ISignal<IDebugger.ISession, IDebugger.ISession.Event> {
     return this._eventMessage;
   }
 
@@ -194,8 +195,13 @@ export class DebugSession implements IDebugger.ISession {
   private async _sendDebugMessage(
     msg: KernelMessage.IDebugRequestMsg['content']
   ): Promise<KernelMessage.IDebugReplyMsg> {
-    const reply = new PromiseDelegate<KernelMessage.IDebugReplyMsg>();
     const kernel = this.client.kernel;
+    if (!kernel) {
+      return Promise.reject(
+        new Error('A kernel is required to send debug messages.')
+      );
+    }
+    const reply = new PromiseDelegate<KernelMessage.IDebugReplyMsg>();
     const future = kernel.requestDebug(msg);
     future.onReply = (msg: KernelMessage.IDebugReplyMsg) => {
       return reply.resolve(msg);
@@ -207,9 +213,10 @@ export class DebugSession implements IDebugger.ISession {
   private _disposed = new Signal<this, void>(this);
   private _isDisposed: boolean = false;
   private _isStarted: boolean = false;
-  private _eventMessage = new Signal<DebugSession, IDebugger.ISession.Event>(
-    this
-  );
+  private _eventMessage = new Signal<
+    IDebugger.ISession,
+    IDebugger.ISession.Event
+  >(this);
   private _seq: number = 0;
 }
 

+ 1 - 19
src/sidebar.ts

@@ -5,16 +5,13 @@ import { SplitPanel } from '@phosphor/widgets';
 
 import { Breakpoints } from './breakpoints';
 
-import { Debugger } from './debugger';
-
 import { Callstack } from './callstack';
 
 import { Variables } from './variables';
 
 export class DebuggerSidebar extends SplitPanel {
-  constructor(model: Debugger.Model | null) {
+  constructor() {
     super();
-    this.model = model;
     this.orientation = 'vertical';
     this.addClass('jp-DebuggerSidebar');
 
@@ -28,21 +25,6 @@ export class DebuggerSidebar extends SplitPanel {
   }
 
   readonly variables: Variables;
-
   readonly callstack: Callstack;
-
   readonly breakpoints: Breakpoints;
-
-  get model(): Debugger.Model | null {
-    return this._model;
-  }
-  set model(model: Debugger.Model | null) {
-    if (this._model === model) {
-      return;
-    }
-    this._model = model;
-    this.update();
-  }
-
-  private _model: Debugger.Model | null = null;
 }

+ 38 - 1
src/tokens.ts

@@ -9,14 +9,17 @@ import {
 
 import { CodeEditor } from '@jupyterlab/codeeditor';
 
+import { Session } from '@jupyterlab/services';
+
 import { Token } from '@phosphor/coreutils';
 
 import { IObservableDisposable } from '@phosphor/disposable';
 
+import { ISignal } from '@phosphor/signaling';
+
 import { DebugProtocol } from 'vscode-debugprotocol';
 
 import { Debugger } from './debugger';
-import { Session } from '@jupyterlab/services';
 
 /**
  * An interface describing an application's visual debugger.
@@ -84,6 +87,40 @@ export namespace IDebugger {
      * Restore the state of a debug session.
      */
     restoreState(): Promise<void>;
+
+    /**
+     * Send a debug request to the kernel.
+     */
+    sendRequest<K extends keyof IDebugger.ISession.Request>(
+      command: K,
+      args: IDebugger.ISession.Request[K]
+    ): Promise<IDebugger.ISession.Response[K]>;
+
+    eventMessage: ISignal<IDebugger.ISession, IDebugger.ISession.Event>;
+  }
+
+  export interface IService {
+    /**
+     * The API debugger session to connect to a debugger
+     */
+    session: IDebugger.ISession;
+
+    /**
+     * Whether the debugger can start.
+     */
+    canStart(): boolean;
+
+    /**
+     * Whether the current debugger is started.
+     */
+    isStarted(): boolean;
+
+    /**
+     * For testing purpose only, to be removed.
+     */
+    launch(code: string): Promise<void>;
+
+    sessionChanged: ISignal<IDebugger.IService, IDebugger.ISession>;
   }
 
   export namespace ISession {

+ 0 - 62
src/variables/body/index.ts

@@ -1,62 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-
-import { Widget, SplitPanel, Panel } from '@phosphor/widgets';
-
-import { Variables } from '../index';
-
-import { Table } from './table';
-
-import { Search } from './search';
-import { Message } from '@phosphor/messaging';
-
-export class Body extends Panel {
-  constructor(model: Variables.IModel) {
-    super();
-    this.model = model;
-    this.addClass('jp-DebuggerVariables-body');
-
-    const searchParams = new Search(this.model);
-    const splitPanel = new SplitPanel();
-    const table = new Table(this.model);
-    const description = new Description(this.model);
-
-    splitPanel.orientation = 'vertical';
-    splitPanel.node.style.height = `100%`;
-    splitPanel.addWidget(table);
-    splitPanel.addWidget(description);
-
-    this.addWidget(searchParams);
-    this.addWidget(splitPanel);
-  }
-
-  model: Variables.IModel;
-}
-
-class Description extends Widget {
-  constructor(model: Variables.IModel) {
-    super();
-    this.addClass('jp-DebuggerVariables-description');
-    this.model = model;
-
-    this.model.currentChanged.connect(
-      (model: Variables.IModel, variable: Variables.IVariable) => {
-        this.currentVariable = variable;
-        this.update();
-      }
-    );
-  }
-
-  protected onUpdateRequest(msg: Message) {
-    if (!this.currentVariable) {
-      return;
-    }
-    this.node.innerHTML = `<b>name: ${this.currentVariable.name}</b>
-                                       <p>type: ${this.currentVariable.type} </p>
-                                       Description:
-                                       <p>${this.currentVariable.description}</p> `;
-  }
-
-  model: Variables.IModel;
-  currentVariable: Variables.IVariable;
-}

+ 124 - 0
src/variables/body/index.tsx

@@ -0,0 +1,124 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+
+import { Variables } from '../index';
+
+import { ObjectInspector, ObjectLabel, ITheme } from 'react-inspector';
+
+import { ReactWidget } from '@jupyterlab/apputils';
+
+import React, { useState, useEffect } from 'react';
+import { ArrayExt } from '@phosphor/algorithm';
+
+export class Body extends ReactWidget {
+  constructor(model: Variables.IModel) {
+    super();
+    this.model = model;
+    this.addClass('jp-DebuggerVariables-body');
+  }
+
+  model: Variables.IModel;
+
+  render() {
+    return <VariableComponent model={this.model} />;
+  }
+}
+
+const VariableComponent = ({ model }: { model: Variables.IModel }) => {
+  const [data, setData] = useState(model.scopes);
+
+  useEffect(() => {
+    const updateScopes = (_: Variables.IModel, update: Variables.IScope[]) => {
+      if (ArrayExt.shallowEqual(data, update)) {
+        return;
+      }
+      setData(update);
+    };
+
+    model.scopesChanged.connect(updateScopes);
+
+    return () => {
+      model.scopesChanged.disconnect(updateScopes);
+    };
+  });
+
+  const List = () => (
+    <>
+      {data.map(scopes => (
+        <ObjectInspector
+          key={scopes.name}
+          data={scopes.variables}
+          name={scopes.name}
+          nodeRenderer={defaultNodeRenderer}
+          theme={THEME}
+          expandLevel={1}
+        />
+      ))}
+    </>
+  );
+
+  return <>{List()}</>;
+};
+
+const defaultNodeRenderer = ({
+  depth,
+  name,
+  data,
+  isNonenumerable,
+  expanded,
+  theme
+}: {
+  depth: number;
+  name: string;
+  data: any;
+  isNonenumerable: boolean;
+  expanded: boolean;
+  theme?: string | Partial<ITheme>;
+}) => {
+  const label = data.name === '' || data.name == null ? name : data.name;
+  const value = data.value;
+
+  return depth === 0 ? (
+    <span>
+      <span>{label}</span>
+      <span>: </span>
+      <span>{data.length}</span>
+    </span>
+  ) : depth === 1 ? (
+    <span>
+      <span style={{ color: THEME.OBJECT_NAME_COLOR }}>{label}</span>
+      <span>: </span>
+      <span>{value}</span>
+    </span>
+  ) : (
+    <ObjectLabel name={label} data={data} isNonenumerable={isNonenumerable} />
+  );
+};
+
+const THEME = {
+  BASE_FONT_FAMILY: 'var(--jp-code-font-family)',
+  BASE_FONT_SIZE: 'var(--jp-code-font-size)',
+  BASE_LINE_HEIGHT: 'var(--jp-code-line-height)',
+
+  BASE_BACKGROUND_COLOR: 'var(--jp-layout-color1)',
+  BASE_COLOR: 'var(--jp-content-font-color1)',
+
+  OBJECT_NAME_COLOR: 'var(--jp-mirror-editor-attribute-color)',
+  OBJECT_VALUE_NULL_COLOR: 'var(--jp-mirror-editor-builtin-color)',
+  OBJECT_VALUE_UNDEFINED_COLOR: 'var(--jp-mirror-editor-builtin-color)',
+  OBJECT_VALUE_REGEXP_COLOR: 'var(--jp-mirror-editor-string-color)',
+  OBJECT_VALUE_STRING_COLOR: 'var(--jp-mirror-editor-string-color)',
+  OBJECT_VALUE_SYMBOL_COLOR: 'var(--jp-mirror-editor-operator-color)',
+  OBJECT_VALUE_NUMBER_COLOR: 'var(--jp-mirror-editor-number-color)',
+  OBJECT_VALUE_BOOLEAN_COLOR: 'var(--jp-mirror-editor-builtin-color))',
+  OBJECT_VALUE_FUNCTION_KEYWORD_COLOR: 'var(--jp-mirror-editor-def-color))',
+
+  ARROW_COLOR: 'var(--jp-content-font-color2)',
+  ARROW_MARGIN_RIGHT: 3,
+  ARROW_FONT_SIZE: 12,
+
+  TREENODE_FONT_FAMILY: 'var(--jp-code-font-family)',
+  TREENODE_FONT_SIZE: 'var(--jp-code-font-size)',
+  TREENODE_LINE_HEIGHT: 'var(--jp-code-line-height)',
+  TREENODE_PADDING_LEFT: 12
+};

+ 0 - 101
src/variables/body/search.tsx

@@ -1,101 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-
-import { ReactWidget } from '@jupyterlab/apputils';
-
-import { Widget, PanelLayout } from '@phosphor/widgets';
-
-import React, { useState, useRef, useEffect } from 'react';
-
-import { Variables } from '../index';
-
-export class Search extends Widget {
-  constructor(model: Variables.IModel) {
-    super();
-    this.addClass('jp-DebuggerVariables-search');
-
-    const layout = new PanelLayout();
-    this.layout = layout;
-    this.scope = new ScopeSearch(model);
-    this.search = new SearchInput(model);
-
-    layout.addWidget(this.scope);
-    layout.addWidget(this.search);
-  }
-
-  readonly scope: Widget;
-  readonly search: Widget;
-}
-
-const SearchComponent = ({ model }: any) => {
-  const [state, setState] = useState('');
-  model.filter = state;
-  return (
-    <div>
-      <span className="fa fa-search"></span>
-      <input
-        placeholder="Search..."
-        value={state}
-        onChange={e => {
-          setState(e.target.value);
-        }}
-      />
-    </div>
-  );
-};
-
-class SearchInput extends ReactWidget {
-  search: string;
-  model: Variables.IModel;
-  constructor(model: Variables.IModel) {
-    super();
-    this.model = model;
-    this.search = model.filter;
-    this.node.style;
-    this.addClass('jp-DebuggerVariables-input');
-  }
-
-  render() {
-    return <SearchComponent model={this.model} />;
-  }
-}
-
-class ScopeSearch extends ReactWidget {
-  constructor(model: Variables.IModel) {
-    super();
-    this.model = model;
-    this.node.style.overflow = 'visible';
-    this.node.style.width = '85px';
-    this.addClass('jp-DebuggerVariables-scope');
-  }
-
-  model: Variables.IModel;
-
-  render() {
-    return <ScopeMenuComponent model={this.model} />;
-  }
-}
-
-const ScopeMenuComponent = ({ model }: { model: Variables.IModel }) => {
-  const [scope, setScope] = useState(model.currentScope);
-  const wrapperRef = useRef(null);
-
-  useEffect(() => {
-    const updateScopes = (_: Variables.IModel, updates: Variables.IScope[]) => {
-      const scope = !!updates && updates.length > 0 ? updates[0] : null;
-      setScope(scope);
-    };
-    model.scopesChanged.connect(updateScopes);
-
-    return () => {
-      model.scopesChanged.disconnect(updateScopes);
-    };
-  });
-
-  return (
-    <div ref={wrapperRef}>
-      <span className="label">{scope ? scope.name : '-'}</span>
-      <span className="fa fa-caret-down"></span>
-    </div>
-  );
-};

+ 0 - 154
src/variables/body/table.tsx

@@ -1,154 +0,0 @@
-// Copyright (c) Jupyter Development Team.
-// Distributed under the terms of the Modified BSD License.
-
-import { ReactWidget } from '@jupyterlab/apputils';
-
-import { ArrayExt } from '@phosphor/algorithm';
-
-import { Widget, PanelLayout } from '@phosphor/widgets';
-
-import React, { useEffect, useState } from 'react';
-
-import { Variables } from '../index';
-
-export class Table extends ReactWidget {
-  constructor(model: Variables.IModel) {
-    super();
-    this.model = model;
-    this.addClass('jp-DebuggerVariables-table');
-    const layout = new PanelLayout();
-    this.layout = layout;
-  }
-
-  render() {
-    return <TableComponent model={this.model} />;
-  }
-
-  private model: Variables.IModel;
-
-  protected onResize(msg: Widget.ResizeMessage): void {
-    super.onResize(msg);
-    this.resizeBody(msg);
-  }
-
-  private getBody() {
-    if (this.node.childNodes) {
-      return (this.node.children[0].children[1] as HTMLElement) || null;
-    }
-  }
-
-  private getHead() {
-    if (this.node.childNodes) {
-      return (this.node.children[0].children[0] as HTMLElement) || null;
-    }
-  }
-
-  resizeBody(msg: Widget.ResizeMessage): void {
-    const head = this.getHead();
-    const body = this.getBody();
-    if (body && head) {
-      const totalHeight =
-        msg.height === -1 ? this.node.clientHeight : msg.height;
-      const headHeight = head.offsetHeight;
-      const bodyHeight = totalHeight - headHeight;
-      body.style.height = `${bodyHeight}px`;
-    }
-  }
-}
-
-const TableComponent = ({ model }: { model: Variables.IModel }) => {
-  const [variables, setVariables] = useState(model.variables);
-  const [longHeader, setLongHeader] = useState('value');
-  const [variable, TableBody] = useTbody(
-    variables,
-    model.currentVariable,
-    longHeader
-  );
-
-  useEffect(() => {
-    const updateVariables = (
-      _: Variables.IModel,
-      updates: Variables.IVariable[]
-    ) => {
-      if (ArrayExt.shallowEqual(variables, updates)) {
-        return;
-      }
-      setVariables(updates);
-    };
-    model.variablesChanged.connect(updateVariables);
-
-    return () => {
-      model.variablesChanged.disconnect(updateVariables);
-    };
-  });
-
-  const setWidth = (headerName: string): string => {
-    return headerName === longHeader ? '75%' : '25%';
-  };
-
-  model.currentVariable = variable;
-
-  return (
-    <div>
-      <table>
-        <thead>
-          <tr>
-            <th
-              onClick={() => setLongHeader('name')}
-              style={{ width: setWidth('name') }}
-            >
-              Name
-            </th>
-            <th
-              onClick={() => setLongHeader('value')}
-              style={{ width: setWidth('value') }}
-            >
-              Value
-            </th>
-          </tr>
-        </thead>
-      </table>
-      <TableBody />
-    </div>
-  );
-};
-
-const useTbody = (items: Array<any>, defaultState: any, header: any) => {
-  const [state, setState] = useState(defaultState);
-
-  const setClassIcon = (typeOf: string) => {
-    return typeOf === 'class' ? 'jp-ClassIcon' : 'jp-VariableIcon';
-  };
-
-  const setWidth = (headerName: string): string => {
-    return headerName === header ? '75%' : '25%';
-  };
-
-  const List = () => (
-    <div style={{ overflow: 'auto' }}>
-      <table>
-        <tbody>
-          {items.map(item => (
-            <tr
-              key={item.name}
-              onClick={e => setState(item)}
-              className={state === item ? ' selected' : ''}
-            >
-              <td style={{ paddingLeft: `${12}px`, width: setWidth('name') }}>
-                <span
-                  className={`jp-Icon jp-Icon-16 ${setClassIcon(item.type)}`}
-                ></span>
-                {item.name}
-              </td>
-              <td style={{ paddingLeft: `${12}px`, width: setWidth('value') }}>
-                {item.value}
-              </td>
-            </tr>
-          ))}
-        </tbody>
-      </table>
-    </div>
-  );
-
-  return [state, List, setState];
-};

+ 3 - 0
src/variables/body/typings.d.ts

@@ -0,0 +1,3 @@
+// Copyright (c) Jupyter Development Team.
+// Distributed under the terms of the Modified BSD License.
+/// <reference path="../../../typings/react-inspector/react-inspector.d.ts"/>

+ 20 - 54
src/variables/index.ts

@@ -13,20 +13,30 @@ export class Variables extends Panel {
   constructor(options: Variables.IOptions = {}) {
     super();
 
-    this.model = new Variables.IModel();
+    this.model = new Variables.IModel([]);
     this.addClass('jp-DebuggerVariables');
     this.title.label = 'Variables';
 
-    const header = new VariablesHeader(this.title.label);
+    this.header = new VariablesHeader(this.title.label);
     this.body = new Body(this.model);
 
-    this.addWidget(header);
+    this.addWidget(this.header);
     this.addWidget(this.body);
   }
 
   readonly model: Variables.IModel;
-
+  readonly header: VariablesHeader;
   readonly body: Widget;
+
+  protected onResize(msg: Widget.ResizeMessage): void {
+    super.onResize(msg);
+    this.resizeBody(msg);
+  }
+
+  private resizeBody(msg: Widget.ResizeMessage) {
+    const height = msg.height - this.header.node.offsetHeight;
+    this.body.node.style.height = `${height}px`;
+  }
 }
 
 class VariablesHeader extends Widget {
@@ -42,9 +52,7 @@ class VariablesHeader extends Widget {
 }
 
 export namespace Variables {
-  export interface IVariable extends DebugProtocol.Variable {
-    description: string;
-  }
+  export interface IVariable extends DebugProtocol.Variable {}
 
   export interface IScope {
     name: string;
@@ -54,26 +62,8 @@ export namespace Variables {
   export interface IModel {}
 
   export class IModel implements IModel {
-    constructor(model?: IScope[] | null) {
+    constructor(model: IScope[] = []) {
       this._state = model;
-      this._currentScope = !!this._state ? this._state[0] : null;
-    }
-
-    get currentScope(): IScope {
-      return this._currentScope;
-    }
-
-    set currentScope(value: IScope) {
-      if (this._currentScope === value) {
-        return;
-      }
-      this._currentScope = value;
-      const variables = !!value ? value.variables : [];
-      this._variablesChanged.emit(variables);
-    }
-
-    get currentChanged(): ISignal<this, IVariable> {
-      return this._currentChanged;
     }
 
     get scopesChanged(): ISignal<this, IScope[]> {
@@ -83,6 +73,7 @@ export namespace Variables {
     get currentVariable(): IVariable {
       return this._currentVariable;
     }
+
     set currentVariable(variable: IVariable) {
       if (this._currentVariable === variable) {
         return;
@@ -91,19 +82,6 @@ export namespace Variables {
       this._currentChanged.emit(variable);
     }
 
-    get filter() {
-      return this._filterState;
-    }
-    set filter(value) {
-      if (this._filterState === value) {
-        return;
-      }
-      this._filterState = value;
-      if (this._currentScope) {
-        this._variablesChanged.emit(this._filterVariables());
-      }
-    }
-
     get scopes(): IScope[] {
       return this._state;
     }
@@ -111,13 +89,9 @@ export namespace Variables {
     set scopes(scopes: IScope[]) {
       this._state = scopes;
       this._scopesChanged.emit(scopes);
-      this.currentScope = !!scopes ? scopes[0] : null;
     }
 
     get variables(): IVariable[] {
-      if (this._filterState) {
-        return this._filterVariables();
-      }
       return this._currentScope ? this._currentScope.variables : [];
     }
 
@@ -133,21 +107,13 @@ export namespace Variables {
       return this.variables;
     }
 
-    private _filterVariables(): IVariable[] {
-      return this._currentScope.variables.filter(
-        ele =>
-          ele.name
-            .toLocaleLowerCase()
-            .indexOf(this._filterState.toLocaleLowerCase()) !== -1
-      );
-    }
+    protected _state: IScope[];
 
     private _currentVariable: IVariable;
+    private _currentScope: IScope;
+
     private _currentChanged = new Signal<this, IVariable>(this);
     private _variablesChanged = new Signal<this, IVariable[]>(this);
-    private _filterState: string = '';
-    protected _state: IScope[];
-    private _currentScope: IScope;
     private _scopesChanged = new Signal<this, IScope[]>(this);
   }
 

+ 1 - 122
style/variables.css

@@ -4,128 +4,7 @@
 |----------------------------------------------------------------------------*/
 
 .jp-DebuggerVariables-body {
-  border-bottom: solid var(--jp-border-width) var(--jp-border-color2);
   min-height: 24px;
-  height: 99%;
-  display: flex;
-  flex-direction: column;
-}
-
-.jp-DebuggerVariables-table {
-  min-height: 50px;
-  flex-basis: 30%;
-}
-
-.jp-DebuggerVariables-table table {
-  width: 100%;
-  border-collapse: collapse;
-}
-
-.jp-DebuggerVariables-table table thead th {
-  margin-bottom: 4px;
-  border-top: var(--jp-border-width) solid var(--jp-border-color2);
-  border-bottom: var(--jp-border-width) solid var(--jp-border-color2);
-  min-height: 23px;
-  padding: 4px 12px;
-  font-weight: 500;
-  text-align: left;
-}
-
-.jp-DebuggerVariables-table table thead th:not(:first-child) {
-  border-left: var(--jp-border-width) solid var(--jp-border-color2);
-}
-
-.jp-DebuggerVariables-table table tbody tr td {
-  white-space: nowrap;
-  overflow: hidden;
-  text-overflow: ellipsis;
-  max-width: 0;
-  padding-top: 4px;
-  padding-bottom: 4px;
-}
-
-.jp-DebuggerVariables-table table tbody tr span {
-  padding-right: 3px;
-}
-
-.jp-DebuggerVariables-table table th:hover,
-.jp-DebuggerVariables-table table tbody tr:not(.selected):hover {
-  background: var(--jp-layout-color2);
-}
-
-.jp-DebuggerVariables-table table tr.selected {
-  color: white;
-  background: var(--jp-brand-color1);
-}
-
-.jp-DebuggerVariables-description {
-  border-top: var(--jp-border-width) solid var(--jp-border-color2);
-  min-height: 25px;
-  padding: 10px;
   overflow: auto;
-}
-
-.jp-DebuggerVariables-description p {
-  margin-top: 1rem;
-  margin-bottom: 1rem;
-}
-
-.jp-DebuggerVariables-search {
-  color: var(--jp-ui-font-color1);
-  overflow: visible;
-  flex: 0 0 auto;
-  display: flex;
-  flex-direction: row;
-  height: 24px;
-  align-items: center;
-}
-
-.jp-DebuggerVariables-search .jp-DebuggerVariables-input > div {
-  display: flex;
-  padding-left: 8px;
-  padding-right: 8px;
-  vertical-align: middle;
-  font-size: 14px;
-  line-height: 23px;
-  align-items: baseline;
-}
-
-.jp-DebuggerVariables-search > .jp-DebuggerVariables-input {
-  flex: 1 0 auto;
-}
-
-.jp-DebuggerVariables-search > .jp-DebuggerVariables-scope {
-  flex: 0 0 auto;
-}
-
-.jp-DebuggerVariables-search input {
-  background: var(--jp-layout-color1);
-  color: var(--jp-ui-font-color1);
-  min-height: 22px;
-  vertical-align: top;
-  width: 100%;
-  height: 100%;
-  border: none;
-  padding-left: 10px;
-}
-
-.jp-DebuggerVariables-search .jp-DebuggerVariables-scope span.label {
-  padding-left: 10px;
-  padding-right: 5px;
-}
-
-.jp-DebuggerVariables ul {
-  position: absolute;
-  list-style: none;
-  left: 0;
-  right: 0;
-  margin: 0;
-  top: 20px;
-  z-index: 10000;
-  padding: 10px 10px;
-  background: var(--jp-layout-color1);
-  color: var(--jp-ui-font-color1);
-  border: var(--jp-border-width) solid var(--jp-border-color1);
-  font-size: var(--jp-ui-font-size1);
-  box-shadow: 0px 1px 6px rgba(0, 0, 0, 0.2);
+  padding: 5px;
 }

+ 182 - 0
typings/react-inspector/react-inspector.d.ts

@@ -0,0 +1,182 @@
+// Type definitions for react-inspector
+// Project: https://github.com/xyc/react-inspector
+// Definitions by: vidartf <https://github.com/vidartf>
+// Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped
+// From  https://github.com/vidartf/jupyterlab-kernelspy
+
+declare module 'react-inspector' {
+  import * as React from 'react';
+
+  export class DOMInspector extends React.Component<DOMInspector.IProperties> {}
+
+  export namespace DOMInspector {
+    export interface IProperties {
+      data: HTMLElement;
+    }
+  }
+
+  export class ObjectInspector extends React.Component<
+    ObjectInspector.IProperties
+  > {}
+
+  export namespace ObjectInspector {
+    export interface INodeRendererArgs {
+      depth: number;
+      name: string;
+      data: any;
+      isNonenumerable: boolean;
+      expanded: boolean;
+    }
+
+    export interface IProperties {
+      data: any;
+
+      name?: string;
+
+      expandLevel?: number;
+
+      expandPaths?: string | string[];
+
+      showNonenumerable?: boolean;
+
+      sortObjectKeys?: boolean | ((a: string, b: string) => number);
+
+      nodeRenderer?: (args: INodeRendererArgs) => React.ReactElement<any>;
+
+      theme?: string | Partial<ITheme>;
+    }
+  }
+
+  export class TableInspector extends React.Component<
+    ObjectInspector.IProperties,
+    ObjectInspector.IState
+  > {
+    handleIndexTHClick(): void;
+
+    handleTHClick(col: string): void;
+  }
+
+  export namespace ObjectInspector {
+    export interface IProperties {
+      columns?: string[];
+      data: any;
+      theme?: string | Partial<ITheme>;
+    }
+
+    export interface IState {
+      sorted: boolean;
+      sortIndexColumn: boolean;
+      sortColumn: string | undefined;
+      sortAscending: boolean;
+    }
+  }
+
+  export function Inspector(
+    props: Partial<Inspector.IProperties>
+  ): ObjectInspector | TableInspector | DOMInspector;
+
+  export namespace Inspector {
+    export interface IProperties {
+      table: boolean;
+      data: any;
+      [key: string]: any;
+    }
+  }
+
+  export function ObjectLabel(
+    props: Partial<ObjectLabel.IProperties>
+  ): React.ReactElement<ObjectLabel.IProperties>;
+
+  export namespace ObjectLabel {
+    export interface IProperties {
+      name: string;
+      data: any;
+      isNonenumerable: boolean;
+    }
+  }
+
+  export function ObjectRootLabel(
+    props: Partial<ObjectRootLabel.IProperties>
+  ): React.ReactElement<ObjectRootLabel.IProperties>;
+
+  export namespace ObjectRootLabel {
+    export interface IProperties {
+      name: string;
+      data: any;
+    }
+  }
+
+  export function ObjectName(
+    props: Partial<ObjectName.IProperties>,
+    contexts: Partial<ObjectName.IContexts>
+  ): React.ReactElement<ObjectName.IProperties>;
+
+  export namespace ObjectName {
+    export interface IProperties {
+      name: string;
+      dimmed: boolean;
+      styles: React.CSSProperties;
+    }
+
+    export interface IContexts {
+      theme: ITheme;
+    }
+  }
+
+  export function ObjectValue(
+    props: Partial<ObjectValue.IProperties>,
+    contexts: Partial<ObjectValue.IContexts>
+  ): React.ReactElement<ObjectValue.IProperties>;
+
+  export namespace ObjectValue {
+    export interface IProperties {
+      object: any;
+      styles: React.CSSProperties;
+    }
+
+    export interface IContexts {
+      theme: ITheme;
+    }
+  }
+
+  export interface ITheme {
+    ARROW_COLOR: string;
+    ARROW_FONT_SIZE: number;
+    ARROW_MARGIN_RIGHT: number;
+    BASE_BACKGROUND_COLOR: string;
+    BASE_COLOR: string;
+    BASE_FONT_FAMILY: string;
+    BASE_FONT_SIZE: string;
+    BASE_LINE_HEIGHT: string;
+    HTML_ATTRIBUTE_NAME_COLOR: string;
+    HTML_ATTRIBUTE_VALUE_COLOR: string;
+    HTML_COMMENT_COLOR: string;
+    HTML_DOCTYPE_COLOR: string;
+    HTML_TAGNAME_COLOR: string;
+    HTML_TAGNAME_TEXT_TRANSFORM: string;
+    HTML_TAG_COLOR: string;
+    OBJECT_NAME_COLOR: string;
+    OBJECT_VALUE_BOOLEAN_COLOR: string;
+    OBJECT_VALUE_FUNCTION_KEYWORD_COLOR: string;
+    OBJECT_VALUE_NULL_COLOR: string;
+    OBJECT_VALUE_NUMBER_COLOR: string;
+    OBJECT_VALUE_REGEXP_COLOR: string;
+    OBJECT_VALUE_STRING_COLOR: string;
+    OBJECT_VALUE_SYMBOL_COLOR: string;
+    OBJECT_VALUE_UNDEFINED_COLOR: string;
+    TABLE_BORDER_COLOR: string;
+    TABLE_DATA_BACKGROUND_IMAGE: string;
+    TABLE_DATA_BACKGROUND_SIZE: string;
+    TABLE_SORT_ICON_COLOR: string;
+    TABLE_TH_BACKGROUND_COLOR: string;
+    TABLE_TH_HOVER_COLOR: string;
+    TREENODE_FONT_FAMILY: string;
+    TREENODE_FONT_SIZE: string;
+    TREENODE_LINE_HEIGHT: string;
+    TREENODE_PADDING_LEFT: number;
+  }
+
+  export const chromeDark: ITheme;
+
+  export const chromeLight: ITheme;
+}

部分文件因为文件数量过多而无法显示