session.ts 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import { KernelMessage, Session } from '@jupyterlab/services';
  4. import { ITranslator, nullTranslator } from '@jupyterlab/translation';
  5. import { PromiseDelegate } from '@lumino/coreutils';
  6. import { ISignal, Signal } from '@lumino/signaling';
  7. import { DebugProtocol } from '@vscode/debugprotocol';
  8. import { IDebugger } from './tokens';
  9. /**
  10. * A concrete implementation of IDebugger.ISession.
  11. */
  12. export class DebuggerSession implements IDebugger.ISession {
  13. /**
  14. * Instantiate a new debug session
  15. *
  16. * @param options - The debug session instantiation options.
  17. */
  18. constructor(options: DebuggerSession.IOptions) {
  19. this.connection = options.connection;
  20. this.translator = options.translator || nullTranslator;
  21. }
  22. /**
  23. * Whether the debug session is disposed.
  24. */
  25. get isDisposed(): boolean {
  26. return this._isDisposed;
  27. }
  28. /**
  29. * Returns the initialize response .
  30. */
  31. get capabilities(): DebugProtocol.Capabilities | undefined {
  32. return this._capabilities;
  33. }
  34. /**
  35. * A signal emitted when the debug session is disposed.
  36. */
  37. get disposed(): ISignal<this, void> {
  38. return this._disposed;
  39. }
  40. /**
  41. * Returns the API session connection to connect to a debugger.
  42. */
  43. get connection(): Session.ISessionConnection | null {
  44. return this._connection;
  45. }
  46. /**
  47. * Sets the API session connection to connect to a debugger to
  48. * the given parameter.
  49. *
  50. * @param connection - The new API session connection.
  51. */
  52. set connection(connection: Session.ISessionConnection | null) {
  53. if (this._connection) {
  54. this._connection.iopubMessage.disconnect(this._handleEvent, this);
  55. }
  56. this._connection = connection;
  57. if (!this._connection) {
  58. this._isStarted = false;
  59. return;
  60. }
  61. this._connection.iopubMessage.connect(this._handleEvent, this);
  62. this._ready = new PromiseDelegate<void>();
  63. const future = this.connection?.kernel?.requestDebug({
  64. type: 'request',
  65. seq: 0,
  66. command: 'debugInfo'
  67. });
  68. if (future) {
  69. future.onReply = (msg: KernelMessage.IDebugReplyMsg): void => {
  70. this._ready.resolve();
  71. future.dispose();
  72. };
  73. }
  74. }
  75. /**
  76. * Whether the debug session is started.
  77. */
  78. get isStarted(): boolean {
  79. return this._isStarted;
  80. }
  81. /**
  82. * Whether to pause on exceptions
  83. */
  84. get pausingOnExceptions(): string[] {
  85. return this._pausingOnExceptions;
  86. }
  87. set pausingOnExceptions(updatedPausingOnExceptions: string[]) {
  88. this._pausingOnExceptions = updatedPausingOnExceptions;
  89. }
  90. /**
  91. * Exception paths defined by the debugger
  92. */
  93. get exceptionPaths(): string[] {
  94. return this._exceptionPaths;
  95. }
  96. /**
  97. * Exception breakpoint filters defined by the debugger
  98. */
  99. get exceptionBreakpointFilters():
  100. | DebugProtocol.ExceptionBreakpointsFilter[]
  101. | undefined {
  102. return this._exceptionBreakpointFilters;
  103. }
  104. /**
  105. * Signal emitted for debug event messages.
  106. */
  107. get eventMessage(): ISignal<IDebugger.ISession, IDebugger.ISession.Event> {
  108. return this._eventMessage;
  109. }
  110. /**
  111. * Dispose the debug session.
  112. */
  113. dispose(): void {
  114. if (this._isDisposed) {
  115. return;
  116. }
  117. this._isDisposed = true;
  118. this._disposed.emit();
  119. Signal.clearData(this);
  120. }
  121. /**
  122. * Start a new debug session
  123. */
  124. async start(): Promise<void> {
  125. const initializeResponse = await this.sendRequest('initialize', {
  126. clientID: 'jupyterlab',
  127. clientName: 'JupyterLab',
  128. adapterID: this.connection?.kernel?.name ?? '',
  129. pathFormat: 'path',
  130. linesStartAt1: true,
  131. columnsStartAt1: true,
  132. supportsVariableType: true,
  133. supportsVariablePaging: true,
  134. supportsRunInTerminalRequest: true,
  135. locale: document.documentElement.lang
  136. });
  137. if (!initializeResponse.success) {
  138. throw new Error(
  139. `Could not start the debugger: ${initializeResponse.message}`
  140. );
  141. }
  142. this._capabilities = initializeResponse.body;
  143. this._isStarted = true;
  144. this._exceptionBreakpointFilters =
  145. initializeResponse.body?.exceptionBreakpointFilters;
  146. await this.sendRequest('attach', {});
  147. }
  148. /**
  149. * Stop the running debug session.
  150. */
  151. async stop(): Promise<void> {
  152. await this.sendRequest('disconnect', {
  153. restart: false,
  154. terminateDebuggee: false
  155. });
  156. this._isStarted = false;
  157. }
  158. /**
  159. * Restore the state of a debug session.
  160. */
  161. async restoreState(): Promise<IDebugger.ISession.Response['debugInfo']> {
  162. const message = await this.sendRequest('debugInfo', {});
  163. this._isStarted = message.body.isStarted;
  164. this._exceptionPaths = message.body?.exceptionPaths;
  165. return message;
  166. }
  167. /**
  168. * Send a custom debug request to the kernel.
  169. *
  170. * @param command debug command.
  171. * @param args arguments for the debug command.
  172. */
  173. async sendRequest<K extends keyof IDebugger.ISession.Request>(
  174. command: K,
  175. args: IDebugger.ISession.Request[K]
  176. ): Promise<IDebugger.ISession.Response[K]> {
  177. await this._ready.promise;
  178. const message = await this._sendDebugMessage({
  179. type: 'request',
  180. seq: this._seq++,
  181. command,
  182. arguments: args
  183. });
  184. return message.content as IDebugger.ISession.Response[K];
  185. }
  186. /**
  187. * Handle debug events sent on the 'iopub' channel.
  188. *
  189. * @param sender - the emitter of the event.
  190. * @param message - the event message.
  191. */
  192. private _handleEvent(
  193. sender: Session.ISessionConnection,
  194. message: KernelMessage.IIOPubMessage
  195. ): void {
  196. const msgType = message.header.msg_type;
  197. if (msgType !== 'debug_event') {
  198. return;
  199. }
  200. const event = message.content as IDebugger.ISession.Event;
  201. this._eventMessage.emit(event);
  202. }
  203. /**
  204. * Send a debug request message to the kernel.
  205. *
  206. * @param msg debug request message to send to the kernel.
  207. */
  208. private async _sendDebugMessage(
  209. msg: KernelMessage.IDebugRequestMsg['content']
  210. ): Promise<KernelMessage.IDebugReplyMsg> {
  211. const kernel = this.connection?.kernel;
  212. if (!kernel) {
  213. return Promise.reject(
  214. new Error('A kernel is required to send debug messages.')
  215. );
  216. }
  217. const reply = new PromiseDelegate<KernelMessage.IDebugReplyMsg>();
  218. const future = kernel.requestDebug(msg);
  219. future.onReply = (msg: KernelMessage.IDebugReplyMsg): void => {
  220. reply.resolve(msg);
  221. };
  222. await future.done;
  223. return reply.promise;
  224. }
  225. protected translator: ITranslator;
  226. private _seq = 0;
  227. private _ready = new PromiseDelegate<void>();
  228. private _connection: Session.ISessionConnection | null;
  229. private _capabilities: DebugProtocol.Capabilities | undefined;
  230. private _isDisposed = false;
  231. private _isStarted = false;
  232. private _pausingOnExceptions: string[] = [];
  233. private _exceptionPaths: string[] = [];
  234. private _exceptionBreakpointFilters:
  235. | DebugProtocol.ExceptionBreakpointsFilter[]
  236. | undefined = [];
  237. private _disposed = new Signal<this, void>(this);
  238. private _eventMessage = new Signal<
  239. IDebugger.ISession,
  240. IDebugger.ISession.Event
  241. >(this);
  242. }
  243. /**
  244. * A namespace for `DebuggerSession` statics.
  245. */
  246. export namespace DebuggerSession {
  247. /**
  248. * Instantiation options for a `DebuggerSession`.
  249. */
  250. export interface IOptions {
  251. /**
  252. * The session connection used by the debug session.
  253. */
  254. connection: Session.ISessionConnection;
  255. /**
  256. * The application language translator.
  257. */
  258. translator?: ITranslator;
  259. }
  260. }