浏览代码

Merge pull request #54 from KsavinN/panel

Iterate on interfaces, tokens, and UI #2
Jeremy Tuloup 5 年之前
父节点
当前提交
c1f397c300
共有 7 个文件被更改,包括 358 次插入287 次删除
  1. 0 0
      schema/main.json
  2. 33 19
      src/debugger.ts
  3. 18 4
      src/handlers/cell.ts
  4. 21 71
      src/handlers/console.ts
  5. 25 58
      src/handlers/notebook.ts
  6. 232 117
      src/index.ts
  7. 29 18
      src/tokens.ts

+ 0 - 0
schema/debugger.json → schema/main.json


+ 33 - 19
src/debugger.ts

@@ -5,8 +5,6 @@ import { IDataConnector } from '@jupyterlab/coreutils';
 
 import { ReadonlyJSONValue } from '@phosphor/coreutils';
 
-import { INotebookTracker } from '@jupyterlab/notebook';
-
 import { IClientSession } from '@jupyterlab/apputils';
 
 import { IDisposable } from '@phosphor/disposable';
@@ -15,22 +13,27 @@ import { ISignal, Signal } from '@phosphor/signaling';
 
 import { BoxPanel } from '@phosphor/widgets';
 
+import { IDebugger } from './tokens';
+
 import { DebugSession } from './session';
 
-import { IDebuggerSidebar } from './tokens';
+import { DebuggerSidebar } from './sidebar';
 
 export class Debugger extends BoxPanel {
   constructor(options: Debugger.IOptions) {
     super({ direction: 'left-to-right' });
     this.model = new Debugger.Model(options);
-    // this.sidebar = new DebuggerSidebar(this.model);
-    this.title.label = 'Debugger-' + options.id;
+
+    this.sidebar = new DebuggerSidebar(this.model);
+
+    this.title.label = 'Debugger';
+    this.model.sidebar = this.sidebar;
 
     this.addClass('jp-Debugger');
-    // this.addWidget(this.sidebar);
   }
 
   readonly model: Debugger.Model;
+  readonly sidebar: DebuggerSidebar;
 
   dispose(): void {
     if (this.isDisposed) {
@@ -49,7 +52,6 @@ export namespace Debugger {
     connector?: IDataConnector<ReadonlyJSONValue>;
     id?: string;
     session?: IClientSession;
-    sidebar?: IDebuggerSidebar;
   }
 
   export class Model implements IDisposable {
@@ -61,22 +63,37 @@ export namespace Debugger {
     }
 
     readonly connector: IDataConnector<ReadonlyJSONValue> | null;
-
     readonly id: string;
 
+    get mode(): IDebugger.Mode {
+      return this._mode;
+    }
+
+    set mode(mode: IDebugger.Mode) {
+      if (this._mode === mode) {
+        return;
+      }
+      this._mode = mode;
+      this._modeChanged.emit(mode);
+    }
+
     get sidebar() {
       return this._sidebar;
     }
 
-    set sidebar(newSidebar: IDebuggerSidebar) {
-      this._sidebar = newSidebar;
+    set sidebar(sidebar: DebuggerSidebar) {
+      this._sidebar = sidebar;
     }
 
-    get session() {
+    get modeChanged(): ISignal<this, IDebugger.Mode> {
+      return this._modeChanged;
+    }
+
+    get session(): IDebugger.ISession {
       return this._session;
     }
 
-    set session(session: DebugSession | null) {
+    set session(session: IDebugger.ISession | null) {
       if (this._session === session) {
         return;
       }
@@ -95,10 +112,6 @@ export namespace Debugger {
       return this._isDisposed;
     }
 
-    get notebookTracker() {
-      return this._notebook;
-    }
-
     dispose(): void {
       this._isDisposed = true;
     }
@@ -111,11 +124,12 @@ export namespace Debugger {
       }
     }
 
+    private _sidebar: DebuggerSidebar;
     private _isDisposed = false;
-    private _notebook: INotebookTracker;
-    private _session: DebugSession | null;
+    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 _sidebar: IDebuggerSidebar;
   }
 
   export namespace Model {

+ 18 - 4
src/handlers/cell.ts

@@ -9,8 +9,10 @@ import { Editor, Doc } from 'codemirror';
 
 import { Breakpoints, SessionTypes } from '../breakpoints';
 import { Debugger } from '../debugger';
+import { IDisposable } from '@phosphor/disposable';
+import { Signal } from '@phosphor/signaling';
 
-export class CellManager {
+export class CellManager implements IDisposable {
   constructor(options: CellManager.IOptions) {
     this._debuggerModel = options.debuggerModel;
     this.breakpointsModel = options.breakpointsModel;
@@ -32,6 +34,18 @@ export class CellManager {
   private _type: SessionTypes;
   private breakpointsModel: Breakpoints.Model;
   private _activeCell: CodeCell;
+  isDisposed: boolean;
+
+  dispose(): void {
+    if (this.isDisposed) {
+      return;
+    }
+    if (this.previousCell) {
+      this.removeListener(this.previousCell);
+    }
+    this.removeListener(this.activeCell);
+    Signal.clearData(this);
+  }
 
   set previousCell(cell: CodeCell) {
     this._previousCell = cell;
@@ -68,7 +82,7 @@ export class CellManager {
       this._debuggerModel.session
     ) {
       if (this.previousCell && !this.previousCell.isDisposed) {
-        this.removeListner(this.previousCell);
+        this.removeListener(this.previousCell);
         this.clearGutter(this.previousCell);
         this.breakpointsModel.breakpoints = [];
       }
@@ -96,7 +110,7 @@ export class CellManager {
     editor.editor.on('renderLine', this.onNewRenderLine);
   }
 
-  protected removeListner(cell: CodeCell) {
+  protected removeListener(cell: CodeCell) {
     const editor = cell.editor as CodeMirrorEditor;
     editor.setOption('lineNumbers', false);
     editor.editor.off('gutterClick', this.onGutterClick);
@@ -118,7 +132,7 @@ export class CellManager {
       this.breakpointsModel.removeBreakpoint(info as ILineInfo);
     } else {
       this.breakpointsModel.addBreakpoint(
-        this._debuggerModel.session.id,
+        this._debuggerModel.session.client.name,
         this.getEditorId(),
         info as ILineInfo
       );

+ 21 - 71
src/handlers/console.ts

@@ -1,15 +1,7 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import {
-  IConsoleTracker,
-  ConsolePanel,
-  CodeConsole
-} from '@jupyterlab/console';
-
-import { DebugSession } from '../session';
-
-import { IClientSession, WidgetTracker } from '@jupyterlab/apputils';
+import { IConsoleTracker, CodeConsole } from '@jupyterlab/console';
 
 import { CellManager } from '../handlers/cell';
 
@@ -18,85 +10,43 @@ import { CodeCell } from '@jupyterlab/cells';
 import { Breakpoints } from '../breakpoints';
 
 import { Debugger } from '../debugger';
+import { IDisposable } from '@phosphor/disposable';
+import { Signal } from '@phosphor/signaling';
 
-export class DebuggerConsoleHandler {
+export class DebuggerConsoleHandler implements IDisposable {
   constructor(options: DebuggerNotebookHandler.IOptions) {
     this.debuggerModel = options.debuggerModel;
     this.consoleTracker = options.consoleTracker;
     this.breakpoints = this.debuggerModel.sidebar.breakpoints.model;
-    this.consoleTracker.currentChanged.connect(
-      (sender: WidgetTracker<ConsolePanel>, consolePanel: ConsolePanel) => {
-        this.newDebuggerSession(consolePanel);
-      }
-    );
-    this.debuggerModel.sessionChanged.connect(() => {
-      const consolePanel: ConsolePanel = this.consoleTracker.currentWidget;
-      if (
-        consolePanel &&
-        consolePanel.session.name === this.debuggerModel.session.id
-      ) {
-        if (!this.cellManager) {
-          consolePanel.console.promptCellCreated.connect(
-            this.promptCellCreated,
-            this
-          );
-          this.previousConsole = consolePanel;
-          this.cellManager = new CellManager({
-            breakpointsModel: this.breakpoints,
-            activeCell: consolePanel.console.promptCell as CodeCell,
-            debuggerModel: this.debuggerModel,
-            type: 'notebook'
-          });
-        }
-      }
+    this.cellManager = new CellManager({
+      activeCell: this.consoleTracker.currentWidget.console.promptCell,
+      breakpointsModel: this.breakpoints,
+      debuggerModel: this.debuggerModel,
+      type: 'console'
     });
+    this.consoleTracker.currentWidget.console.promptCellCreated.connect(
+      this.promptCellCreated,
+      this
+    );
   }
 
   private consoleTracker: IConsoleTracker;
   private debuggerModel: Debugger.Model;
   private breakpoints: Breakpoints.Model;
   private cellManager: CellManager;
-  private previousConsole: ConsolePanel;
-
-  protected newDebuggerSession(consolePanel: ConsolePanel) {
-    const session = consolePanel ? consolePanel.session : null;
-    if (this.debuggerModel.session && session) {
-      const newSession = new DebugSession({
-        client: session as IClientSession
-      });
-      this.debuggerModel.session = newSession;
-      if (this.cellManager && this.previousConsole) {
-        this.previousConsole.console.promptCellCreated.disconnect(
-          this.promptCellCreated,
-          this
-        );
-        if (consolePanel.console.promptCell) {
-          this.cellManager.previousCell = this.cellManager.activeCell;
-          this.cellManager.activeCell = consolePanel.console.promptCell;
-        }
-      }
-    } else if (session) {
-      const newSession = new DebugSession({
-        client: session as IClientSession
-      });
-      this.debuggerModel.session = newSession;
-    }
+  isDisposed: boolean;
 
-    if (session) {
-      consolePanel.console.promptCellCreated.connect(
-        this.promptCellCreated,
-        this
-      );
-      this.previousConsole = consolePanel;
+  dispose(): void {
+    if (this.isDisposed) {
+      return;
     }
+    this.isDisposed = true;
+    this.cellManager.dispose();
+    Signal.clearData(this);
   }
 
   protected promptCellCreated(sender: CodeConsole, update: CodeCell) {
-    if (
-      this.cellManager &&
-      this.debuggerModel.session.id ===
-        (this.consoleTracker.currentWidget.session as IClientSession).name
-    ) {
+    if (this.cellManager) {
       this.cellManager.previousCell = this.cellManager.activeCell;
       this.cellManager.activeCell = update;
     } else if (!this.cellManager) {

+ 25 - 58
src/handlers/notebook.ts

@@ -1,15 +1,7 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import {
-  INotebookTracker,
-  NotebookPanel,
-  NotebookTracker
-} from '@jupyterlab/notebook';
-
-import { DebugSession } from './../session';
-
-import { IClientSession } from '@jupyterlab/apputils';
+import { INotebookTracker, NotebookTracker } from '@jupyterlab/notebook';
 
 import { CodeCell } from '@jupyterlab/cells';
 
@@ -19,69 +11,44 @@ import { Debugger } from '../debugger';
 
 import { Breakpoints } from '../breakpoints';
 
-export class DebuggerNotebookHandler {
+import { IDisposable } from '@phosphor/disposable';
+
+import { Signal } from '@phosphor/signaling';
+
+export class DebuggerNotebookHandler implements IDisposable {
   constructor(options: DebuggerNotebookHandler.IOptions) {
     this.debuggerModel = options.debuggerModel;
     this.notebookTracker = options.notebookTracker;
     this.breakpoints = this.debuggerModel.sidebar.breakpoints.model;
-    this.notebookTracker.currentChanged.connect(
-      (sender, notebookPanel: NotebookPanel) => {
-        const session = notebookPanel ? notebookPanel.session : null;
-        this.newDebuggerSession(session, sender);
-      }
-    );
-    this.debuggerModel.sessionChanged.connect(() => {
-      const notebookPanel: NotebookPanel = this.notebookTracker.currentWidget;
-      if (
-        notebookPanel &&
-        notebookPanel.session.name === this.debuggerModel.session.id
-      ) {
-        this.notebookTracker.activeCellChanged.connect(this.onNewCell, this);
-        if (!this.cellManager) {
-          this.cellManager = new CellManager({
-            breakpointsModel: this.breakpoints,
-            activeCell: this.notebookTracker.activeCell as CodeCell,
-            debuggerModel: this.debuggerModel,
-            type: 'notebook'
-          });
-        }
-      }
-    });
+    this.notebookTracker.activeCellChanged.connect(this.onNewCell, this);
   }
 
   private notebookTracker: INotebookTracker;
   private debuggerModel: Debugger.Model;
   private breakpoints: Breakpoints.Model;
   private cellManager: CellManager;
+  isDisposed: boolean;
 
-  protected onNewCell(noteTracker: NotebookTracker, codeCell: CodeCell) {
-    setTimeout(() => {
-      if (this.cellManager) {
-        this.cellManager.activeCell = codeCell;
-        this.cellManager.onActiveCellChanged();
-      } else {
-        this.cellManager = new CellManager({
-          breakpointsModel: this.breakpoints,
-          activeCell: codeCell,
-          debuggerModel: this.debuggerModel,
-          type: 'notebook'
-        });
-      }
-    });
+  dispose(): void {
+    if (this.isDisposed) {
+      return;
+    }
+    this.isDisposed = true;
+    this.cellManager.dispose();
+    Signal.clearData(this);
   }
 
-  protected newDebuggerSession(
-    client: IClientSession | null,
-    note: INotebookTracker
-  ) {
-    if (this.debuggerModel.session) {
-      note.activeCellChanged.disconnect(this.onNewCell, this);
-    }
-    if (client) {
-      const newSession = new DebugSession({
-        client: client as IClientSession
+  protected onNewCell(noteTracker: NotebookTracker, codeCell: CodeCell) {
+    if (this.cellManager) {
+      this.cellManager.activeCell = codeCell;
+      this.cellManager.onActiveCellChanged();
+    } else {
+      this.cellManager = new CellManager({
+        breakpointsModel: this.breakpoints,
+        activeCell: codeCell,
+        debuggerModel: this.debuggerModel,
+        type: 'notebook'
       });
-      this.debuggerModel.session = newSession;
     }
   }
 }

+ 232 - 117
src/index.ts

@@ -5,7 +5,7 @@ import {
   ILayoutRestorer,
   JupyterFrontEnd,
   JupyterFrontEndPlugin,
-  LabShell
+  ILabShell
 } from '@jupyterlab/application';
 
 import { ICommandPalette } from '@jupyterlab/apputils';
@@ -14,9 +14,11 @@ import { WidgetTracker, MainAreaWidget } from '@jupyterlab/apputils';
 
 import { IConsoleTracker, ConsolePanel } from '@jupyterlab/console';
 
+import { IDebugger } from './tokens';
+
 import { IStateDB } from '@jupyterlab/coreutils';
 
-import { IEditorTracker } from '@jupyterlab/fileeditor';
+import { IEditorTracker, FileEditor } from '@jupyterlab/fileeditor';
 
 import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
 
@@ -24,18 +26,16 @@ import { UUID } from '@phosphor/coreutils';
 
 import { Debugger } from './debugger';
 
-import { IDebugger, IDebuggerSidebar } from './tokens';
+import { DebugSession } from './session';
 
 import { DebuggerNotebookHandler } from './handlers/notebook';
 
 import { DebuggerConsoleHandler } from './handlers/console';
 
-import { DebuggerSidebar } from './sidebar';
-
-import { SessionTypes } from './breakpoints';
-import { DebugSession } from './session';
 import { IDisposable } from '@phosphor/disposable';
 
+import { Kernel } from '@jupyterlab/services';
+
 /**
  * The command IDs used by the debugger plugin.
  */
@@ -51,6 +51,10 @@ export namespace CommandIDs {
   export const debugFile = 'debugger:debug-file';
 
   export const debugNotebook = 'debugger:debug-notebook';
+
+  export const mount = 'debugger:mount';
+
+  export const changeMode = 'debugger:change-mode';
 }
 
 /**
@@ -59,20 +63,45 @@ export namespace CommandIDs {
 const consoles: JupyterFrontEndPlugin<void> = {
   id: '@jupyterlab/debugger:consoles',
   autoStart: true,
-  requires: [IDebugger, IDebuggerSidebar, IConsoleTracker],
+  requires: [IDebugger, IConsoleTracker, ILabShell],
   activate: (
     _,
     debug: IDebugger,
-    sidebar: IDebuggerSidebar,
-    tracker: IConsoleTracker
+    tracker: IConsoleTracker,
+    labShell: ILabShell
   ) => {
-    debug.currentChanged.connect((_, update) => {
-      if (update) {
-        update.content.model.sidebar = sidebar;
-        new DebuggerConsoleHandler({
-          debuggerModel: update.content.model,
-          consoleTracker: tracker
+    let oldhandler: {
+      id: string;
+      handler: DebuggerConsoleHandler;
+    };
+
+    labShell.currentChanged.connect((_, 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.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;
+        }
       }
     });
   }
@@ -84,25 +113,29 @@ const consoles: JupyterFrontEndPlugin<void> = {
 const files: JupyterFrontEndPlugin<void> = {
   id: '@jupyterlab/debugger:files',
   autoStart: true,
-  requires: [IEditorTracker, IDebugger, INotebookTracker],
+  requires: [IDebugger, IEditorTracker, ILabShell],
   activate: (
     app: JupyterFrontEnd,
-    tracker: IEditorTracker | null,
     debug: IDebugger,
-    notebook: INotebookTracker
+    tracker: IEditorTracker,
+    labShell: ILabShell
   ) => {
-    const shell = app.shell;
-    (shell as LabShell).currentChanged.connect((sender, update) => {
-      const newWidget = update.newValue;
-      const session =
-        newWidget && (newWidget as NotebookPanel | ConsolePanel).session
-          ? (newWidget as NotebookPanel | ConsolePanel).session
-          : false;
-      if (session && debug.currentWidget) {
-        const debugModel: Debugger.Model = debug.currentWidget.content.model;
-        debugModel.session = new DebugSession({ client: session });
-        debugModel.sidebar.breakpoints.model.type = session.type as SessionTypes;
+    let _model: any;
+    labShell.currentChanged.connect((_, update) => {
+      const widget = update.newValue;
+      if (!(widget instanceof FileEditor)) {
+        return;
       }
+
+      //  Finding if the file is backed by a kernel or attach it to one.
+
+      const sessions = app.serviceManager.sessions;
+
+      void sessions.findByPath(widget.context.path).then(model => {
+        _model = model;
+        const session = sessions.connectTo(model);
+        debug.session.client = session;
+      });
     });
 
     app.commands.addCommand(CommandIDs.debugFile, {
@@ -111,9 +144,12 @@ const files: JupyterFrontEndPlugin<void> = {
           return;
         }
         if (tracker.currentWidget) {
-          // TODO: Find if the file is backed by a kernel or attach it to one.
-          // const widget = await app.commands.execute(CommandIDs.create);
-          // app.shell.add(widget, 'main');
+          const idKernel = debug.session.client.kernel.id;
+          void Kernel.findById(idKernel).catch(() => {
+            if (_model) {
+              Kernel.connectTo(_model);
+            }
+          });
         }
       }
     });
@@ -126,70 +162,54 @@ const files: JupyterFrontEndPlugin<void> = {
 const notebooks: JupyterFrontEndPlugin<void> = {
   id: '@jupyterlab/debugger:notebooks',
   autoStart: true,
-  requires: [IDebugger, IDebuggerSidebar],
-  optional: [INotebookTracker, ICommandPalette],
+  requires: [IDebugger, INotebookTracker, ILabShell],
   activate: (
-    app: JupyterFrontEnd,
+    _,
     debug: IDebugger,
-    sidebar: IDebuggerSidebar,
-    notebook: INotebookTracker,
-    palette: ICommandPalette
+    tracker: INotebookTracker,
+    labShell: ILabShell
   ) => {
-    // 1. Keep track of any new notebook that is created.
-    // 2. When the *active* notebook changes, hook it up to the debugger.
-    // 3. If a notebook is closed, dispose the debugger session.
-
-    debug.currentChanged.connect((_, update) => {
-      if (update) {
-        update.content.model.sidebar = sidebar;
-        new DebuggerNotebookHandler({
-          debuggerModel: update.content.model,
-          notebookTracker: notebook
+    let oldhandler: {
+      id: string;
+      handler: DebuggerNotebookHandler;
+    };
+
+    labShell.currentChanged.connect((_, 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.tracker.currentWidget) {
+        const handler = new DebuggerNotebookHandler({
+          notebookTracker: 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;
+        }
       }
     });
-
-    //   // Debugger model:
-    //   // LIST of editors that it currently cares about.
-    //   // Manages life cycle signal connections.
-    //   // Manages variables
-    // });
-  }
-};
-
-/**
- * A plugin providing a condensed sidebar UI for debugging.
- */
-const sidebar: JupyterFrontEndPlugin<IDebuggerSidebar> = {
-  id: '@jupyterlab/debugger:sidebar',
-  optional: [ILayoutRestorer],
-  provides: IDebuggerSidebar,
-  autoStart: true,
-  activate: (
-    app: JupyterFrontEnd,
-    restorer: ILayoutRestorer | null
-  ): DebuggerSidebar => {
-    const { shell } = app;
-    const label = 'Environment';
-    const namespace = 'jp-debugger-sidebar';
-    const sidebar = new DebuggerSidebar(null);
-    sidebar.id = namespace;
-    sidebar.title.label = label;
-    shell.add(sidebar, 'right', { activate: false });
-    if (restorer) {
-      restorer.add(sidebar, sidebar.id);
-    }
-
-    return sidebar;
   }
 };
 
 /**
  * A plugin providing a tracker code debuggers.
  */
-const tracker: JupyterFrontEndPlugin<IDebugger> = {
-  id: '@jupyterlab/debugger:tracker',
-  optional: [ILayoutRestorer, IDebuggerSidebar, ICommandPalette],
+const main: JupyterFrontEndPlugin<IDebugger> = {
+  id: '@jupyterlab/debugger:main',
+  optional: [ILayoutRestorer, ICommandPalette],
   requires: [IStateDB],
   provides: IDebugger,
   autoStart: true,
@@ -197,28 +217,64 @@ const tracker: JupyterFrontEndPlugin<IDebugger> = {
     app: JupyterFrontEnd,
     state: IStateDB,
     restorer: ILayoutRestorer | null,
-    sidebar: IDebuggerSidebar | null,
-    palette: ICommandPalette
+    palette: ICommandPalette | null
   ): IDebugger => {
     const tracker = new WidgetTracker<MainAreaWidget<Debugger>>({
       namespace: 'debugger'
     });
-
-    tracker.widgetUpdated.connect((_, update) => {
-      update;
-    });
-
+    const { commands, shell } = app;
+    let widget: MainAreaWidget<Debugger>;
     let commandStop: IDisposable;
 
     const getModel = () => {
       return tracker.currentWidget ? tracker.currentWidget.content.model : null;
     };
 
-    app.commands.addCommand(CommandIDs.stop, {
+    commands.addCommand(CommandIDs.mount, {
+      execute: args => {
+        if (!widget) {
+          return;
+        }
+
+        const mode = (args.mode as IDebugger.Mode) || 'expanded';
+
+        const { sidebar } = widget.content;
+        if (!mode) {
+          throw new Error(`Could not mount debugger in mode: "${mode}"`);
+        }
+        if (mode === 'expanded') {
+          if (widget.isAttached) {
+            return;
+          }
+
+          if (sidebar.isAttached) {
+            sidebar.parent = null;
+          }
+
+          //edge case when realod page after set condensed mode
+          widget.title.label = 'Debugger';
+          shell.add(widget, 'main');
+          return;
+        }
+
+        if (sidebar.isAttached) {
+          return;
+        }
+
+        if (widget.isAttached) {
+          widget.parent = null;
+        }
+
+        sidebar.id = 'jp-debugger-sidebar';
+        sidebar.title.label = 'Environment';
+        shell.add(sidebar, 'right', { activate: false });
+      }
+    });
+
+    commands.addCommand(CommandIDs.stop, {
       label: 'Stop',
       execute: async () => {
         const debuggerModel = getModel();
-
         if (debuggerModel) {
           await debuggerModel.session.stop();
           commandStop.dispose();
@@ -226,7 +282,7 @@ const tracker: JupyterFrontEndPlugin<IDebugger> = {
       }
     });
 
-    app.commands.addCommand(CommandIDs.start, {
+    commands.addCommand(CommandIDs.start, {
       label: 'Start',
       isEnabled: () => {
         const debuggerModel = getModel();
@@ -245,52 +301,112 @@ const tracker: JupyterFrontEndPlugin<IDebugger> = {
       }
     });
 
-    app.commands.addCommand(CommandIDs.create, {
+    commands.addCommand(CommandIDs.changeMode, {
+      label: 'Change Mode',
+      isEnabled: () => {
+        return !!tracker.currentWidget;
+      },
+      execute: () => {
+        const currentMode = tracker.currentWidget.content.model.mode;
+        tracker.currentWidget.content.model.mode =
+          currentMode === 'expanded' ? 'condensed' : 'expanded';
+        let mode = tracker.currentWidget.content.model.mode;
+
+        if (mode === 'condensed') {
+          void commands.execute(CommandIDs.mount, { mode });
+        } else if (mode === 'expanded') {
+          widget.content.sidebar.close();
+          void commands.execute(CommandIDs.mount, { mode });
+        }
+      }
+    });
+
+    commands.addCommand(CommandIDs.create, {
       label: 'Debugger',
-      execute: args => {
+      execute: async args => {
         const id = (args.id as string) || UUID.uuid4();
+        const savedMode = (await state.fetch('mode')) as IDebugger.Mode;
+        const mode = savedMode ? savedMode : 'expanded';
+
         if (id) {
           console.log('Debugger ID: ', id);
         }
 
-        const existedWidget = tracker.currentWidget;
+        if (tracker.currentWidget) {
+          widget = tracker.currentWidget;
+        } else {
+          widget = new MainAreaWidget({
+            content: new Debugger({
+              connector: state,
+              id: id
+            })
+          });
 
-        if (existedWidget) {
-          return;
-        }
+          void tracker.add(widget);
 
-        const widget = new MainAreaWidget({
-          content: new Debugger({
-            connector: state,
-            id: id
-          })
-        });
+          widget.content.model.mode = mode;
 
-        void tracker.add(widget);
-        app.shell.add(widget, 'main');
+          widget.content.model.modeChanged.connect((_, mode) => {
+            void state.save('mode', mode);
+          });
+        }
 
+        await commands.execute(CommandIDs.mount, { mode });
         return widget;
       }
     });
 
     if (palette) {
+      palette.addItem({ command: CommandIDs.changeMode, category: 'Debugger' });
       palette.addItem({ command: CommandIDs.create, category: 'Debugger' });
-      palette.addItem({
-        command: CommandIDs.start,
-        category: 'Debugger'
-      });
+      palette.addItem({ command: CommandIDs.start, category: 'Debugger' });
     }
 
     if (restorer) {
       // Handle state restoration.
       void restorer.restore(tracker, {
         command: CommandIDs.create,
-        args: widget => ({ id: widget.content.model.id }),
+        args: widget => ({
+          id: widget.content.model.id
+        }),
         name: widget => widget.content.model.id
       });
     }
 
-    return tracker;
+    // Create a proxy to pass the `session` and `mode` to the debugger.
+
+    const proxy: IDebugger = Object.defineProperties(
+      {},
+      {
+        mode: {
+          get: (): IDebugger.Mode => {
+            return widget.content.model.mode;
+          },
+          set: (mode: IDebugger.Mode) => {
+            if (widget) {
+              widget.content.model.mode = mode;
+            }
+          }
+        },
+        session: {
+          get: (): IDebugger.ISession | null => {
+            return null;
+          },
+          set: (src: IDebugger.ISession | null) => {
+            if (widget) {
+              widget.content.model.session = src;
+            }
+          }
+        },
+        tracker: {
+          get: (): WidgetTracker<MainAreaWidget<Debugger>> => {
+            return tracker;
+          }
+        }
+      }
+    );
+
+    return proxy;
   }
 };
 
@@ -302,8 +418,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
   consoles,
   files,
   notebooks,
-  sidebar,
-  tracker
+  main
 ];
 
 export default plugins;

+ 29 - 18
src/tokens.ts

@@ -3,8 +3,8 @@
 
 import {
   IClientSession,
-  IWidgetTracker,
-  MainAreaWidget
+  MainAreaWidget,
+  WidgetTracker
 } from '@jupyterlab/apputils';
 
 import { CodeEditor } from '@jupyterlab/codeeditor';
@@ -16,18 +16,41 @@ import { IObservableDisposable } from '@phosphor/disposable';
 import { DebugProtocol } from 'vscode-debugprotocol';
 
 import { Debugger } from './debugger';
-
-import { DebuggerSidebar } from './sidebar';
+import { Session } from '@jupyterlab/services';
 
 /**
  * An interface describing an application's visual debugger.
  */
-export interface IDebugger extends IWidgetTracker<MainAreaWidget<Debugger>> {}
+export interface IDebugger {
+  /**
+   * The mode of the debugger UI.
+   *
+   * #### Notes
+   * There is only ever one debugger instance. If it is `expanded`, it exists
+   * as a `MainAreaWidget`, otherwise it is a sidebar.
+   */
+  mode: IDebugger.Mode;
+
+  /**
+   * The current debugger session.
+   */
+  session: IDebugger.ISession;
+
+  /**
+   * tracker for get instance of debugger.
+   */
+  tracker: WidgetTracker<MainAreaWidget<Debugger>>;
+}
 
 /**
  * A namespace for visual debugger types.
  */
 export namespace IDebugger {
+  /**
+   * The mode of the debugger UI.
+   */
+  export type Mode = 'condensed' | 'expanded';
+
   /**
    * A visual debugger session.
    */
@@ -35,7 +58,7 @@ export namespace IDebugger {
     /**
      * The API client session to connect to a debugger.
      */
-    client: IClientSession;
+    client: IClientSession | Session.ISession;
 
     /**
      * The code editors in a debugger session.
@@ -167,15 +190,3 @@ export namespace IDebugger {
  * A token for a tracker for an application's visual debugger instances.
  */
 export const IDebugger = new Token<IDebugger>('@jupyterlab/debugger');
-
-/**
- * An interface describing an application's visual debugger.
- */
-export interface IDebuggerSidebar extends DebuggerSidebar {}
-
-/**
- * A token for a tracker for an application's visual debugger condensed sidebar.
- */
-export const IDebuggerSidebar = new Token<IDebuggerSidebar>(
-  '@jupyterlab/debugger-sidebar'
-);