浏览代码

Merge pull request #97 from JohanMabille/continue

Continue, Next and StepIn commands
Jeremy Tuloup 5 年之前
父节点
当前提交
12c44ee889
共有 4 个文件被更改,包括 160 次插入4 次删除
  1. 1 0
      src/debugger.ts
  2. 51 1
      src/index.ts
  3. 75 1
      src/service.ts
  4. 33 2
      src/tokens.ts

+ 1 - 0
src/debugger.ts

@@ -57,6 +57,7 @@ export class Debugger extends SplitPanel {
       return;
     }
     this.model.dispose();
+    this.service.dispose();
     super.dispose();
   }
 

+ 51 - 1
src/index.ts

@@ -45,6 +45,12 @@ export namespace CommandIDs {
 
   export const stop = 'debugger:stop';
 
+  export const debugContinue = 'debugger:continue';
+
+  export const next = 'debugger:next';
+
+  export const stepIn = 'debugger:stepIn';
+
   export const debugConsole = 'debugger:debug-console';
 
   export const debugFile = 'debugger:debug-file';
@@ -303,6 +309,40 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
       }
     });
 
+    commands.addCommand(CommandIDs.debugContinue, {
+      label: 'Continue',
+      isEnabled: () => {
+        const service = getService();
+        return service && service.isThreadStopped();
+      },
+      execute: async () => {
+        await getService().continue();
+        commands.notifyCommandChanged();
+      }
+    });
+
+    commands.addCommand(CommandIDs.next, {
+      label: 'Next',
+      isEnabled: () => {
+        const service = getService();
+        return service && service.isThreadStopped();
+      },
+      execute: async () => {
+        await getService().next();
+      }
+    });
+
+    commands.addCommand(CommandIDs.stepIn, {
+      label: 'StepIn',
+      isEnabled: () => {
+        const service = getService();
+        return service && service.isThreadStopped();
+      },
+      execute: async () => {
+        await getService().stepIn();
+      }
+    });
+
     commands.addCommand(CommandIDs.debugNotebook, {
       label: 'Launch',
       isEnabled: () => {
@@ -360,6 +400,14 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
           });
         }
 
+        widget.content.service.eventMessage.connect(_ => {
+          commands.notifyCommandChanged();
+        });
+
+        widget.content.service.sessionChanged.connect(_ => {
+          commands.notifyCommandChanged();
+        });
+
         await commands.execute(CommandIDs.mount, { mode });
         return widget;
       }
@@ -371,6 +419,9 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
       palette.addItem({ command: CommandIDs.create, category });
       palette.addItem({ command: CommandIDs.start, category });
       palette.addItem({ command: CommandIDs.stop, category });
+      palette.addItem({ command: CommandIDs.debugContinue, category });
+      palette.addItem({ command: CommandIDs.next, category });
+      palette.addItem({ command: CommandIDs.stepIn, category });
       palette.addItem({ command: CommandIDs.debugNotebook, category });
     }
 
@@ -417,7 +468,6 @@ const main: JupyterFrontEndPlugin<IDebugger> = {
         }
       }
     );
-
     return proxy;
   }
 };

+ 75 - 1
src/service.ts

@@ -21,6 +21,18 @@ export class DebugService implements IDebugger.IService {
     this._model = debuggerModel;
   }
 
+  dispose(): void {
+    if (this.isDisposed) {
+      return;
+    }
+    this._isDisposed = true;
+    Signal.clearData(this);
+  }
+
+  get isDisposed(): boolean {
+    return this._isDisposed;
+  }
+
   set session(session: IDebugger.ISession) {
     if (this._session === session) {
       return;
@@ -29,6 +41,15 @@ export class DebugService implements IDebugger.IService {
       this._session.dispose();
     }
     this._session = session;
+
+    this._session.eventMessage.connect((_, event) => {
+      if (event.event === 'stopped') {
+        this._threadStopped.add(event.body.threadId);
+      } else if (event.event === 'continued') {
+        this._threadStopped.delete(event.body.threadId);
+      }
+      this._eventMessage.emit(event);
+    });
     this._sessionChanged.emit(session);
   }
 
@@ -44,10 +65,49 @@ export class DebugService implements IDebugger.IService {
     return this._session !== null && this._session.isStarted;
   }
 
+  isThreadStopped(): boolean {
+    return this._threadStopped.has(this.currentThread());
+  }
+
+  async continue(): Promise<void> {
+    try {
+      await this.session.sendRequest('continue', {
+        threadId: this.currentThread()
+      });
+      this._threadStopped.delete(this.currentThread());
+    } catch (err) {
+      console.error('Error:', err.message);
+    }
+  }
+
+  async next(): Promise<void> {
+    try {
+      await this.session.sendRequest('next', {
+        threadId: this.currentThread()
+      });
+    } catch (err) {
+      console.error('Error:', err.message);
+    }
+  }
+
+  async stepIn(): Promise<void> {
+    try {
+      await this.session.sendRequest('stepIn', {
+        threadId: this.currentThread()
+      });
+    } catch (err) {
+      console.error('Error:', err.message);
+    }
+  }
+
   get sessionChanged(): ISignal<IDebugger.IService, IDebugger.ISession> {
     return this._sessionChanged;
   }
 
+  get eventMessage(): ISignal<IDebugger.IService, IDebugger.ISession.Event> {
+    return this._eventMessage;
+  }
+
   // this will change for after execute cell
   async launch(code: string): Promise<void> {
     let threadId: number = 1;
@@ -159,10 +219,24 @@ export class DebugService implements IDebugger.IService {
     });
   };
 
+  private currentThread(): number {
+    // TODO: ask the model for the current thread ID
+    return 1;
+  }
+
+  private _isDisposed: boolean = false;
   private _session: IDebugger.ISession;
-  private _sessionChanged = new Signal<this, IDebugger.ISession>(this);
+  private _sessionChanged = new Signal<IDebugger.IService, IDebugger.ISession>(
+    this
+  );
+  private _eventMessage = new Signal<
+    IDebugger.IService,
+    IDebugger.ISession.Event
+  >(this);
   private _model: Debugger.Model;
   private frames: Frame[];
+  // TODO: move this in model
+  private _threadStopped = new Set();
 }
 
 export type Frame = {

+ 33 - 2
src/tokens.ts

@@ -13,7 +13,7 @@ import { Session } from '@jupyterlab/services';
 
 import { Token } from '@phosphor/coreutils';
 
-import { IObservableDisposable } from '@phosphor/disposable';
+import { IDisposable, IObservableDisposable } from '@phosphor/disposable';
 
 import { ISignal } from '@phosphor/signaling';
 
@@ -96,10 +96,13 @@ export namespace IDebugger {
       args: IDebugger.ISession.Request[K]
     ): Promise<IDebugger.ISession.Response[K]>;
 
+    /**
+     * Signal emitted for debug event messages.
+     */
     eventMessage: ISignal<IDebugger.ISession, IDebugger.ISession.Event>;
   }
 
-  export interface IService {
+  export interface IService extends IDisposable {
     /**
      * The API debugger session to connect to a debugger
      */
@@ -115,12 +118,40 @@ export namespace IDebugger {
      */
     isStarted(): boolean;
 
+    /**
+     * Whether the current thread is stopped.
+     */
+    isThreadStopped(): boolean;
+
+    /**
+     * Continues the execution of the current thread.
+     */
+    continue(): Promise<void>;
+
+    /**
+     * Makes the current thread run again for one step.
+     */
+    next(): Promise<void>;
+
+    /**
+     * Makes the current thread step in a function / method if possible.
+     */
+    stepIn(): Promise<void>;
+
     /**
      * For testing purpose only, to be removed.
      */
     launch(code: string): Promise<void>;
 
+    /**
+     * Signal emitted upon session changed.
+     */
     sessionChanged: ISignal<IDebugger.IService, IDebugger.ISession>;
+
+    /**
+     * Signal emitted for debug event messages.
+     */
+    eventMessage: ISignal<IDebugger.IService, IDebugger.ISession.Event>;
   }
 
   export namespace ISession {