index.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ILabShell,
  5. ILayoutRestorer,
  6. JupyterFrontEnd,
  7. JupyterFrontEndPlugin
  8. } from '@jupyterlab/application';
  9. import {
  10. IClientSession,
  11. ICommandPalette,
  12. MainAreaWidget,
  13. WidgetTracker
  14. } from '@jupyterlab/apputils';
  15. import { IEditorServices } from '@jupyterlab/codeeditor';
  16. import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console';
  17. import { IStateDB } from '@jupyterlab/coreutils';
  18. import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
  19. import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
  20. import { Kernel } from '@jupyterlab/services';
  21. import { UUID } from '@phosphor/coreutils';
  22. import { Debugger } from './debugger';
  23. import { ConsoleHandler } from './handlers/console';
  24. import { NotebookHandler } from './handlers/notebook';
  25. import { DebugService } from './service';
  26. import { DebugSession } from './session';
  27. import { IDebugger } from './tokens';
  28. /**
  29. * The command IDs used by the debugger plugin.
  30. */
  31. export namespace CommandIDs {
  32. export const create = 'debugger:create';
  33. export const start = 'debugger:start';
  34. export const stop = 'debugger:stop';
  35. export const debugContinue = 'debugger:continue';
  36. export const terminate = 'debugger:terminate';
  37. export const next = 'debugger:next';
  38. export const stepIn = 'debugger:stepIn';
  39. export const stepOut = 'debugger:stepOut';
  40. export const debugConsole = 'debugger:debug-console';
  41. export const debugFile = 'debugger:debug-file';
  42. export const mount = 'debugger:mount';
  43. export const changeMode = 'debugger:change-mode';
  44. export const closeDebugger = 'debugger:close';
  45. }
  46. async function setDebugSession(
  47. app: JupyterFrontEnd,
  48. debug: IDebugger,
  49. client: IClientSession
  50. ) {
  51. if (!debug.session) {
  52. debug.session = new DebugSession({ client: client });
  53. } else {
  54. debug.session.client = client;
  55. }
  56. if (debug.isDebuggingEnabled) {
  57. await debug.restoreState(true);
  58. }
  59. app.commands.notifyCommandChanged();
  60. }
  61. class DebuggerHandler<H extends ConsoleHandler | NotebookHandler> {
  62. constructor(builder: new (option: any) => H) {
  63. this.builder = builder;
  64. }
  65. update<
  66. T extends IConsoleTracker | INotebookTracker,
  67. W extends ConsolePanel | NotebookPanel
  68. >(debug: IDebugger, tracker: T, widget: W): void {
  69. if (debug.model && !this.handlers[widget.id]) {
  70. const handler = new this.builder({
  71. tracker: tracker,
  72. debuggerService: debug,
  73. id: widget.id
  74. });
  75. this.handlers[widget.id] = handler;
  76. widget.disposed.connect(() => {
  77. handler.dispose();
  78. delete this.handlers[widget.id];
  79. });
  80. debug.model.disposed.connect(async () => {
  81. await debug.stop();
  82. Object.keys(this.handlers).forEach(id => {
  83. this.handlers[id].dispose();
  84. });
  85. this.handlers = {};
  86. });
  87. }
  88. }
  89. private handlers: { [id: string]: H } = {};
  90. private builder: new (option: any) => H;
  91. }
  92. /**
  93. * A plugin that provides visual debugging support for consoles.
  94. */
  95. const consoles: JupyterFrontEndPlugin<void> = {
  96. id: '@jupyterlab/debugger:consoles',
  97. autoStart: true,
  98. requires: [IDebugger, IConsoleTracker, ILabShell],
  99. activate: (
  100. app: JupyterFrontEnd,
  101. debug: IDebugger,
  102. tracker: IConsoleTracker,
  103. labShell: ILabShell
  104. ) => {
  105. const handler = new DebuggerHandler<ConsoleHandler>(ConsoleHandler);
  106. labShell.currentChanged.connect(async (_, update) => {
  107. const widget = update.newValue;
  108. if (!(widget instanceof ConsolePanel)) {
  109. return;
  110. }
  111. await setDebugSession(app, debug, widget.session);
  112. handler.update(debug, tracker, widget);
  113. });
  114. }
  115. };
  116. /**
  117. * A plugin that provides visual debugging support for file editors.
  118. */
  119. const files: JupyterFrontEndPlugin<void> = {
  120. id: '@jupyterlab/debugger:files',
  121. autoStart: true,
  122. requires: [IDebugger, IEditorTracker, ILabShell],
  123. activate: (
  124. app: JupyterFrontEnd,
  125. debug: IDebugger,
  126. tracker: IEditorTracker,
  127. labShell: ILabShell
  128. ) => {
  129. let _model: any;
  130. labShell.currentChanged.connect((_, update) => {
  131. const widget = update.newValue;
  132. if (!(widget instanceof FileEditor)) {
  133. return;
  134. }
  135. // Finding if the file is backed by a kernel or attach it to one.
  136. const sessions = app.serviceManager.sessions;
  137. void sessions.findByPath(widget.context.path).then(model => {
  138. _model = model;
  139. const session = sessions.connectTo(model);
  140. debug.session.client = session;
  141. });
  142. });
  143. app.commands.addCommand(CommandIDs.debugFile, {
  144. execute: async _ => {
  145. if (!tracker || !tracker.currentWidget) {
  146. return;
  147. }
  148. if (tracker.currentWidget) {
  149. const idKernel = debug.session.client.kernel.id;
  150. void Kernel.findById(idKernel).catch(() => {
  151. if (_model) {
  152. Kernel.connectTo(_model);
  153. }
  154. });
  155. }
  156. }
  157. });
  158. }
  159. };
  160. /**
  161. * A plugin that provides visual debugging support for notebooks.
  162. */
  163. const notebooks: JupyterFrontEndPlugin<void> = {
  164. id: '@jupyterlab/debugger:notebooks',
  165. autoStart: true,
  166. requires: [IDebugger, INotebookTracker, ILabShell],
  167. activate: (
  168. app: JupyterFrontEnd,
  169. debug: IDebugger,
  170. tracker: INotebookTracker,
  171. labShell: ILabShell
  172. ) => {
  173. const handler = new DebuggerHandler<NotebookHandler>(NotebookHandler);
  174. labShell.activeChanged.connect(async (_, update) => {
  175. const widget = update.newValue;
  176. if (!(widget instanceof NotebookPanel)) {
  177. return;
  178. }
  179. await setDebugSession(app, debug, widget.session);
  180. handler.update(debug, tracker, widget);
  181. });
  182. }
  183. };
  184. /**
  185. * A plugin providing a tracker code debuggers.
  186. */
  187. const main: JupyterFrontEndPlugin<IDebugger> = {
  188. id: '@jupyterlab/debugger:main',
  189. optional: [ILayoutRestorer, ICommandPalette],
  190. requires: [IStateDB, IEditorServices, ILabShell],
  191. provides: IDebugger,
  192. autoStart: true,
  193. activate: (
  194. app: JupyterFrontEnd,
  195. state: IStateDB,
  196. editorServices: IEditorServices,
  197. labShell: ILabShell,
  198. restorer: ILayoutRestorer | null,
  199. palette: ICommandPalette | null
  200. ): IDebugger => {
  201. const { commands, shell } = app;
  202. const service = new DebugService();
  203. const tracker = new WidgetTracker<MainAreaWidget<Debugger>>({
  204. namespace: 'debugger'
  205. });
  206. const defaultMode = 'condensed';
  207. let widget: MainAreaWidget<Debugger>;
  208. commands.addCommand(CommandIDs.closeDebugger, {
  209. label: 'Close Debugger',
  210. execute: args => {
  211. if (!widget) {
  212. return;
  213. }
  214. widget.content.sidebar.close();
  215. widget.dispose();
  216. }
  217. });
  218. app.contextMenu.addItem({
  219. command: CommandIDs.closeDebugger,
  220. selector: '.jp-DebuggerSidebar'
  221. });
  222. commands.addCommand(CommandIDs.mount, {
  223. execute: async args => {
  224. if (!widget) {
  225. return;
  226. }
  227. const mode = (args.mode as IDebugger.Mode) || defaultMode;
  228. const { sidebar } = widget.content;
  229. if (!mode) {
  230. throw new Error(`Could not mount debugger in mode: "${mode}"`);
  231. }
  232. if (mode === 'expanded') {
  233. if (widget.isAttached) {
  234. return;
  235. }
  236. if (sidebar.isAttached) {
  237. sidebar.parent = null;
  238. }
  239. // edge case when reload page after set condensed mode
  240. widget.title.label = 'Debugger';
  241. shell.add(widget, 'main');
  242. return;
  243. }
  244. // mode = 'condensed'
  245. if (widget.isAttached) {
  246. widget.parent = null;
  247. }
  248. sidebar.id = 'jp-debugger-sidebar';
  249. sidebar.title.label = 'Environment';
  250. shell.add(sidebar, 'right', { activate: false });
  251. if (labShell.currentWidget) {
  252. labShell.currentWidget.activate();
  253. }
  254. if (restorer) {
  255. restorer.add(sidebar, 'debugger-sidebar');
  256. }
  257. await service.restoreState(true);
  258. }
  259. });
  260. commands.addCommand(CommandIDs.debugContinue, {
  261. label: 'Continue',
  262. caption: 'Continue',
  263. iconClass: 'jp-MaterialIcon jp-RunIcon',
  264. isEnabled: () => {
  265. return service.isThreadStopped();
  266. },
  267. execute: async () => {
  268. await service.continue();
  269. commands.notifyCommandChanged();
  270. }
  271. });
  272. commands.addCommand(CommandIDs.terminate, {
  273. label: 'Terminate',
  274. caption: 'Terminate',
  275. iconClass: 'jp-MaterialIcon jp-StopIcon',
  276. isEnabled: () => {
  277. return service.isThreadStopped();
  278. },
  279. execute: async () => {
  280. await service.restart();
  281. commands.notifyCommandChanged();
  282. }
  283. });
  284. commands.addCommand(CommandIDs.next, {
  285. label: 'Next',
  286. caption: 'Next',
  287. iconClass: 'jp-MaterialIcon jp-StepOverIcon',
  288. isEnabled: () => {
  289. return service.isThreadStopped();
  290. },
  291. execute: async () => {
  292. await service.next();
  293. }
  294. });
  295. commands.addCommand(CommandIDs.stepIn, {
  296. label: 'StepIn',
  297. caption: 'Step In',
  298. iconClass: 'jp-MaterialIcon jp-StepInIcon',
  299. isEnabled: () => {
  300. return service.isThreadStopped();
  301. },
  302. execute: async () => {
  303. await service.stepIn();
  304. }
  305. });
  306. commands.addCommand(CommandIDs.stepOut, {
  307. label: 'StepOut',
  308. caption: 'Step Out',
  309. iconClass: 'jp-MaterialIcon jp-StepOutIcon',
  310. isEnabled: () => {
  311. return service.isThreadStopped();
  312. },
  313. execute: async () => {
  314. await service.stepOut();
  315. }
  316. });
  317. commands.addCommand(CommandIDs.changeMode, {
  318. label: 'Change Mode',
  319. isEnabled: () => {
  320. return !!tracker.currentWidget;
  321. },
  322. execute: () => {
  323. const currentMode = tracker.currentWidget.content.model.mode;
  324. const mode = currentMode === 'expanded' ? 'condensed' : 'expanded';
  325. tracker.currentWidget.content.model.mode = mode;
  326. void commands.execute(CommandIDs.mount, { mode });
  327. }
  328. });
  329. commands.addCommand(CommandIDs.create, {
  330. label: 'Debugger',
  331. execute: async args => {
  332. const id = (args.id as string) || UUID.uuid4();
  333. const savedMode = (await state.fetch('mode')) as IDebugger.Mode;
  334. const mode = savedMode ? savedMode : defaultMode;
  335. const callstackCommands = {
  336. registry: commands,
  337. continue: CommandIDs.debugContinue,
  338. terminate: CommandIDs.terminate,
  339. next: CommandIDs.next,
  340. stepIn: CommandIDs.stepIn,
  341. stepOut: CommandIDs.stepOut
  342. };
  343. if (tracker.currentWidget) {
  344. widget = tracker.currentWidget;
  345. } else {
  346. widget = new MainAreaWidget({
  347. content: new Debugger({
  348. debugService: service,
  349. connector: state,
  350. callstackCommands,
  351. editorServices
  352. })
  353. });
  354. widget.id = id;
  355. void tracker.add(widget);
  356. widget.content.model.mode = mode;
  357. widget.content.model.modeChanged.connect((_, mode) => {
  358. void state.save('mode', mode);
  359. });
  360. }
  361. console.log('Debugger ID: ', widget.id);
  362. widget.content.service.eventMessage.connect(_ => {
  363. commands.notifyCommandChanged();
  364. });
  365. widget.content.service.sessionChanged.connect(_ => {
  366. commands.notifyCommandChanged();
  367. });
  368. await commands.execute(CommandIDs.mount, { mode });
  369. return widget;
  370. }
  371. });
  372. if (palette) {
  373. const category = 'Debugger';
  374. [
  375. CommandIDs.changeMode,
  376. CommandIDs.create,
  377. CommandIDs.debugContinue,
  378. CommandIDs.terminate,
  379. CommandIDs.next,
  380. CommandIDs.stepIn,
  381. CommandIDs.stepOut
  382. ].forEach(command => {
  383. palette.addItem({ command, category });
  384. });
  385. }
  386. if (restorer) {
  387. // Handle state restoration.
  388. void restorer.restore(tracker, {
  389. command: CommandIDs.create,
  390. args: widget => ({
  391. id: widget.id
  392. }),
  393. name: widget => widget.id
  394. });
  395. }
  396. return service;
  397. }
  398. };
  399. /**
  400. * Export the plugins as default.
  401. */
  402. const plugins: JupyterFrontEndPlugin<any>[] = [
  403. consoles,
  404. files,
  405. notebooks,
  406. main
  407. ];
  408. export default plugins;