Explorar el Código

Merge pull request #207 from jtpio/open-editors

Open files in the main area widget
Johan Mabille hace 5 años
padre
commit
30d0da7b57
Se han modificado 9 ficheros con 173 adiciones y 78 borrados
  1. 5 3
      src/debugger.ts
  2. 107 35
      src/editors/index.ts
  3. 12 14
      src/handlers/console.ts
  4. 9 10
      src/handlers/editor.ts
  5. 9 10
      src/handlers/notebook.ts
  6. 1 2
      src/index.ts
  7. 12 0
      src/service.ts
  8. 8 0
      src/tokens.ts
  9. 10 4
      tests/src/debugger.spec.ts

+ 5 - 3
src/debugger.ts

@@ -1,7 +1,7 @@
 // Copyright (c) Jupyter Development Team.
 // Distributed under the terms of the Modified BSD License.
 
-import { CodeEditor } from '@jupyterlab/codeeditor';
+import { IEditorServices } from '@jupyterlab/codeeditor';
 
 import { IDataConnector } from '@jupyterlab/coreutils';
 
@@ -49,7 +49,9 @@ export class Debugger extends SplitPanel {
     });
 
     this.editors = new DebuggerEditors({
-      editorFactory: options.editorFactory
+      model: this.model,
+      service: this.service,
+      editorServices: options.editorServices
     });
     this.addWidget(this.editors);
 
@@ -82,7 +84,7 @@ export class Debugger extends SplitPanel {
 export namespace Debugger {
   export interface IOptions {
     debugService: DebugService;
-    editorFactory: CodeEditor.Factory;
+    editorServices: IEditorServices;
     callstackCommands: Callstack.ICommands;
     connector?: IDataConnector<ReadonlyJSONValue>;
   }

+ 107 - 35
src/editors/index.ts

@@ -3,39 +3,49 @@
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/
 
-import { CodeEditor, CodeEditorWrapper } from '@jupyterlab/codeeditor';
+import {
+  CodeEditor,
+  CodeEditorWrapper,
+  IEditorMimeTypeService,
+  IEditorServices
+} from '@jupyterlab/codeeditor';
+
+import { find } from '@phosphor/algorithm';
 
 import { ISignal, Signal } from '@phosphor/signaling';
 
 import { TabPanel } from '@phosphor/widgets';
 
+import { Callstack } from '../callstack';
+
+import { Debugger } from '../debugger';
+
+import { IDebugger } from '../tokens';
+
 export class DebuggerEditors extends TabPanel {
   constructor(options: DebuggerEditors.IOptions) {
     super();
 
     this.tabsMovable = true;
+    this.tabBar.insertBehavior = 'select-tab';
 
     this.model = new DebuggerEditors.IModel();
+
+    this.debuggerModel = options.model;
+    this.debuggerService = options.service;
+    this.editorFactory = options.editorServices.factoryService.newInlineEditor;
+    this.mimeTypeService = options.editorServices.mimeTypeService;
+
+    this.debuggerModel.callstackModel.currentFrameChanged.connect(
+      this.onCurrentFrameChanged,
+      this
+    );
+
     this.model.editorAdded.connect((sender, data) => {
-      let editor = new CodeEditorWrapper({
-        model: new CodeEditor.Model({
-          value: data.code,
-          mimeType: data.mimeType
-        }),
-        factory: options.editorFactory,
-        config: {
-          readOnly: true,
-          lineNumbers: true
-        }
-      });
-
-      editor.title.label = data.title;
-      editor.title.closable = true;
-
-      this.addWidget(editor);
+      this.openEditor(data);
     });
 
-    MOCK_EDITORS.forEach(editor => this.model.addEditor(editor));
+    this.model.editors.forEach(editor => this.openEditor(editor));
 
     this.addClass('jp-DebuggerEditors');
   }
@@ -54,6 +64,71 @@ export class DebuggerEditors extends TabPanel {
     }
     Signal.clearData(this);
   }
+
+  private async onCurrentFrameChanged(
+    _: Callstack.Model,
+    frame: Callstack.IFrame
+  ) {
+    if (!frame) {
+      return;
+    }
+
+    const path = frame.source.path;
+    const source = await this.debuggerService.getSource({
+      sourceReference: 0,
+      path
+    });
+
+    if (!source.success) {
+      return;
+    }
+
+    const { content, mimeType } = source.body;
+    this.model.addEditor({
+      path,
+      code: content,
+      mimeType: mimeType || this.mimeTypeService.getMimeTypeByFilePath(path)
+    });
+  }
+
+  private openEditor(data: DebuggerEditors.IEditor) {
+    const { path, mimeType, code } = data;
+
+    const tab = find(this.tabBar.titles, title => title.label === path);
+    if (tab) {
+      this.tabBar.currentTitle = tab;
+      return;
+    }
+
+    let editor = new CodeEditorWrapper({
+      model: new CodeEditor.Model({
+        value: code,
+        mimeType: mimeType
+      }),
+      factory: this.editorFactory,
+      config: {
+        readOnly: true,
+        lineNumbers: true
+      }
+    });
+
+    editor.title.label = path;
+    editor.title.caption = path;
+    editor.title.closable = true;
+
+    this.tabBar.tabCloseRequested.connect((_, tab) => {
+      const widget = tab.title.owner;
+      widget.dispose();
+      this.model.removeEditor(tab.title.label);
+    });
+
+    this.addWidget(editor);
+  }
+
+  private debuggerModel: Debugger.Model;
+  private debuggerService: IDebugger;
+  private editorFactory: CodeEditor.Factory;
+  private mimeTypeService: IEditorMimeTypeService;
 }
 
 /**
@@ -64,14 +139,16 @@ export namespace DebuggerEditors {
    * The options used to create a DebuggerEditors.
    */
   export interface IOptions {
-    editorFactory: CodeEditor.Factory;
+    service: IDebugger;
+    model: Debugger.Model;
+    editorServices: IEditorServices;
   }
 
   /**
    * An interface for read only editors.
    */
   export interface IEditor {
-    title: string;
+    path: string;
     code: string;
     mimeType: string;
   }
@@ -101,24 +178,19 @@ export namespace DebuggerEditors {
      * @param editor The read-only editor info to add.
      */
     addEditor(editor: DebuggerEditors.IEditor) {
-      this._state.push(editor);
+      this._state.set(editor.path, editor);
       this._editorAdded.emit(editor);
     }
 
-    private _state: DebuggerEditors.IEditor[] = [];
+    /**
+     * Remove an editor from the TabPanel.
+     * @param path The path for the editor.
+     */
+    removeEditor(path: string) {
+      this._state.delete(path);
+    }
+
+    private _state = new Map<string, DebuggerEditors.IEditor>();
     private _editorAdded = new Signal<this, DebuggerEditors.IEditor>(this);
   }
 }
-
-const MOCK_EDITORS = [
-  {
-    title: 'untitled.py',
-    mimeType: 'text/x-ipython',
-    code: 'import math\nprint(math.pi)'
-  },
-  {
-    title: 'test.py',
-    mimeType: 'text/x-ipython',
-    code: 'import sys\nprint(sys.version)'
-  }
-];

+ 12 - 14
src/handlers/console.ts

@@ -11,7 +11,7 @@ import { Signal } from '@phosphor/signaling';
 
 import { Breakpoints } from '../breakpoints';
 
-import { CellManager } from '../handlers/cell';
+import { EditorHandler } from '../handlers/editor';
 
 import { IDebugger } from '../tokens';
 
@@ -23,12 +23,11 @@ export class ConsoleHandler implements IDisposable {
     this.debuggerService = options.debuggerService;
     this.consoleTracker = options.tracker;
     this.breakpoints = this.debuggerModel.breakpointsModel;
-    this.cellManager = new CellManager({
+    this.editorHandler = new EditorHandler({
       activeCell: this.consoleTracker.currentWidget.console.promptCell,
       breakpointsModel: this.breakpoints,
       debuggerModel: this.debuggerModel,
-      debuggerService: this.debuggerService,
-      type: 'console'
+      debuggerService: this.debuggerService
     });
     this.consoleTracker.currentWidget.console.promptCellCreated.connect(
       this.promptCellCreated,
@@ -36,11 +35,6 @@ export class ConsoleHandler implements IDisposable {
     );
   }
 
-  private consoleTracker: IConsoleTracker;
-  private debuggerModel: Debugger.Model;
-  private debuggerService: IDebugger;
-  private breakpoints: Breakpoints.Model;
-  private cellManager: CellManager;
   isDisposed: boolean;
 
   dispose(): void {
@@ -48,16 +42,20 @@ export class ConsoleHandler implements IDisposable {
       return;
     }
     this.isDisposed = true;
-    this.cellManager.dispose();
+    this.editorHandler.dispose();
     Signal.clearData(this);
   }
 
   protected promptCellCreated(sender: CodeConsole, update: CodeCell) {
-    if (this.cellManager) {
-      this.cellManager.previousCell = this.cellManager.activeCell;
-      this.cellManager.activeCell = update;
-    }
+    this.editorHandler.previousCell = this.editorHandler.activeCell;
+    this.editorHandler.activeCell = update;
   }
+
+  private consoleTracker: IConsoleTracker;
+  private debuggerModel: Debugger.Model;
+  private debuggerService: IDebugger;
+  private breakpoints: Breakpoints.Model;
+  private editorHandler: EditorHandler;
 }
 
 export namespace DebuggerConsoleHandler {

+ 9 - 10
src/handlers/cell.ts → src/handlers/editor.ts

@@ -15,7 +15,7 @@ import { Signal } from '@phosphor/signaling';
 
 import { Editor } from 'codemirror';
 
-import { Breakpoints, SessionTypes } from '../breakpoints';
+import { Breakpoints } from '../breakpoints';
 
 import { Callstack } from '../callstack';
 
@@ -27,8 +27,8 @@ const LINE_HIGHLIGHT_CLASS = 'jp-breakpoint-line-highlight';
 
 const CELL_CHANGED_TIMEOUT = 1000;
 
-export class CellManager implements IDisposable {
-  constructor(options: CellManager.IOptions) {
+export class EditorHandler implements IDisposable {
+  constructor(options: EditorHandler.IOptions) {
     // TODO: should we use the client name or a debug session id?
     this._id = options.debuggerService.session.client.name;
     this._debuggerService = options.debuggerService;
@@ -48,7 +48,7 @@ export class CellManager implements IDisposable {
 
     this._debuggerModel.callstackModel.currentFrameChanged.connect(
       (_, frame) => {
-        CellManager.clearHighlight(this.activeCell);
+        EditorHandler.clearHighlight(this.activeCell);
         if (!frame) {
           return;
         }
@@ -86,8 +86,8 @@ export class CellManager implements IDisposable {
       this._cellMonitor.dispose();
     }
     this.removeGutterClick(this.activeCell);
-    CellManager.clearHighlight(this.activeCell);
-    CellManager.clearGutter(this.activeCell);
+    EditorHandler.clearHighlight(this.activeCell);
+    EditorHandler.clearGutter(this.activeCell);
     Signal.clearData(this);
     this.isDisposed = true;
   }
@@ -201,7 +201,7 @@ export class CellManager implements IDisposable {
     }
 
     editor.focus();
-    CellManager.clearGutter(this.activeCell);
+    EditorHandler.clearGutter(this.activeCell);
 
     const isRemoveGutter = !!info.gutterMarkers;
     let breakpoints: Breakpoints.IBreakpoint[] = this.getBreakpoints(
@@ -232,7 +232,7 @@ export class CellManager implements IDisposable {
       breakpoints.length === 0 &&
       this._id === this._debuggerService.session.client.name
     ) {
-      CellManager.clearGutter(cell);
+      EditorHandler.clearGutter(cell);
     } else {
       breakpoints.forEach(breakpoint => {
         editor.editor.setGutterMarker(
@@ -274,13 +274,12 @@ export class CellManager implements IDisposable {
   > = null;
 }
 
-export namespace CellManager {
+export namespace EditorHandler {
   export interface IOptions {
     debuggerModel: Debugger.Model;
     debuggerService: IDebugger;
     breakpointsModel: Breakpoints.Model;
     activeCell?: CodeCell;
-    type: SessionTypes;
   }
 
   /**

+ 9 - 10
src/handlers/notebook.ts

@@ -21,7 +21,7 @@ import { IDebugger } from '../tokens';
 
 import { Callstack } from '../callstack';
 
-import { CellManager } from './cell';
+import { EditorHandler } from './editor';
 
 export class NotebookHandler implements IDisposable {
   constructor(options: NotebookHandler.IOptions) {
@@ -33,11 +33,10 @@ export class NotebookHandler implements IDisposable {
     this.id = options.id;
     this.breakpoints = this.debuggerModel.breakpointsModel;
 
-    this.cellManager = new CellManager({
+    this.editorHandler = new EditorHandler({
       breakpointsModel: this.breakpoints,
       debuggerModel: this.debuggerModel,
       debuggerService: this.debuggerService,
-      type: 'notebook',
       activeCell: this.notebookTracker.activeCell as CodeCell
     });
 
@@ -60,15 +59,15 @@ export class NotebookHandler implements IDisposable {
     }
     this.isDisposed = true;
     this.cleanAllCells();
-    this.cellManager.dispose();
+    this.editorHandler.dispose();
     Signal.clearData(this);
   }
 
   protected cleanAllCells() {
     const cells = this.notebookPanel.content.widgets;
     cells.forEach(cell => {
-      CellManager.clearHighlight(cell);
-      CellManager.clearGutter(cell);
+      EditorHandler.clearHighlight(cell);
+      EditorHandler.clearGutter(cell);
     });
   }
 
@@ -79,7 +78,7 @@ export class NotebookHandler implements IDisposable {
     if (notebookTracker.currentWidget.id !== this.id) {
       return;
     }
-    this.cellManager.activeCell = codeCell;
+    this.editorHandler.activeCell = codeCell;
   }
 
   private onCurrentFrameChanged(
@@ -92,7 +91,7 @@ export class NotebookHandler implements IDisposable {
     }
 
     const cells = notebook.content.widgets;
-    cells.forEach(cell => CellManager.clearHighlight(cell));
+    cells.forEach(cell => EditorHandler.clearHighlight(cell));
 
     if (!frame) {
       return;
@@ -106,7 +105,7 @@ export class NotebookHandler implements IDisposable {
         return;
       }
       notebook.content.activeCellIndex = i;
-      CellManager.showCurrentLine(cell, frame);
+      EditorHandler.showCurrentLine(cell, frame);
     });
   }
 
@@ -114,8 +113,8 @@ export class NotebookHandler implements IDisposable {
   private debuggerModel: Debugger.Model;
   private debuggerService: IDebugger;
   private breakpoints: Breakpoints.Model;
-  private cellManager: CellManager;
   private notebookPanel: NotebookPanel;
+  private editorHandler: EditorHandler;
   private id: string;
 }
 

+ 1 - 2
src/index.ts

@@ -242,7 +242,6 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
     palette: ICommandPalette | null
   ): IDebugger => {
     const { commands, shell } = app;
-    const editorFactory = editorServices.factoryService.newInlineEditor;
 
     const service = new DebugService();
 
@@ -417,7 +416,7 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
               debugService: service,
               connector: state,
               callstackCommands,
-              editorFactory
+              editorServices
             })
           });
           widget.id = id;

+ 12 - 0
src/service.ts

@@ -357,6 +357,18 @@ export class DebugService implements IDebugger {
     this._model.breakpointsModel.restoreBreakpoints(bpMap);
   }
 
+  /**
+   * Retrieve the content of a source file.
+   * @param source The source object containing the path to the file.
+   */
+  async getSource(source: DebugProtocol.Source) {
+    const reply = await this.session.sendRequest('source', {
+      source,
+      sourceReference: source.sourceReference
+    });
+    return reply;
+  }
+
   getAllFrames = async () => {
     this._model.callstackModel.currentFrameChanged.connect(this.onChangeFrame);
     this._model.variablesModel.variableExpanded.connect(this.getVariable);

+ 8 - 0
src/tokens.ts

@@ -135,6 +135,14 @@ export interface IDebugger extends IDisposable {
    * Removes all the breakpoints from the current notebook or console
    */
   clearBreakpoints(): Promise<void>;
+
+  /**
+   * Retrieve the content of a source file.
+   * @param source The source object containing the path to the file.
+   */
+  getSource(
+    source: DebugProtocol.Source
+  ): Promise<DebugProtocol.SourceResponse>;
 }
 
 /**

+ 10 - 4
tests/src/debugger.spec.ts

@@ -1,6 +1,9 @@
 import { expect } from 'chai';
 
-import { CodeMirrorEditorFactory } from '@jupyterlab/codemirror';
+import {
+  CodeMirrorEditorFactory,
+  CodeMirrorMimeTypeService
+} from '@jupyterlab/codemirror';
 
 import { CommandRegistry } from '@phosphor/commands';
 
@@ -13,8 +16,8 @@ class TestPanel extends Debugger {}
 describe('Debugger', () => {
   const service = new DebugService();
   const registry = new CommandRegistry();
-  const editorServices = new CodeMirrorEditorFactory();
-  const editorFactory = editorServices.newInlineEditor;
+  const factoryService = new CodeMirrorEditorFactory();
+  const mimeTypeService = new CodeMirrorMimeTypeService();
 
   let panel: TestPanel;
 
@@ -29,7 +32,10 @@ describe('Debugger', () => {
         stepIn: '',
         stepOut: ''
       },
-      editorFactory: editorFactory
+      editorServices: {
+        factoryService,
+        mimeTypeService
+      }
     });
   });