index.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565
  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. ICommandPalette,
  11. IThemeManager,
  12. MainAreaWidget,
  13. WidgetTracker
  14. } from '@jupyterlab/apputils';
  15. import { IEditorServices } from '@jupyterlab/codeeditor';
  16. import { ConsolePanel, IConsoleTracker } from '@jupyterlab/console';
  17. import { DocumentWidget } from '@jupyterlab/docregistry';
  18. import { FileEditor, IEditorTracker } from '@jupyterlab/fileeditor';
  19. import { INotebookTracker, NotebookPanel } from '@jupyterlab/notebook';
  20. import { Session } from '@jupyterlab/services';
  21. import { ISettingRegistry } from '@jupyterlab/settingregistry';
  22. import {
  23. continueIcon,
  24. stepIntoIcon,
  25. stepOutIcon,
  26. stepOverIcon,
  27. terminateIcon,
  28. variableIcon
  29. } from './icons';
  30. import { Debugger } from './debugger';
  31. import { TrackerHandler } from './handlers/tracker';
  32. import { DebuggerHandler } from './handler';
  33. import { IDebugger, IDebuggerConfig, IDebuggerSources } from './tokens';
  34. import { VariablesBodyGrid } from './panels/variables/grid';
  35. /**
  36. * The command IDs used by the debugger plugin.
  37. */
  38. export namespace CommandIDs {
  39. export const debugContinue = 'debugger:continue';
  40. export const terminate = 'debugger:terminate';
  41. export const next = 'debugger:next';
  42. export const stepIn = 'debugger:stepIn';
  43. export const stepOut = 'debugger:stepOut';
  44. export const inspectVariable = 'debugger:inspect-variable';
  45. }
  46. /**
  47. * A plugin that provides visual debugging support for consoles.
  48. */
  49. const consoles: JupyterFrontEndPlugin<void> = {
  50. id: '@jupyterlab/debugger:consoles',
  51. autoStart: true,
  52. requires: [IDebugger, IDebuggerSources, IConsoleTracker],
  53. optional: [ILabShell],
  54. activate: (
  55. app: JupyterFrontEnd,
  56. debug: IDebugger,
  57. editorFinder: IDebugger.ISources,
  58. consoleTracker: IConsoleTracker,
  59. labShell: ILabShell
  60. ) => {
  61. const handler = new DebuggerHandler({
  62. type: 'console',
  63. shell: app.shell,
  64. service: debug
  65. });
  66. const updateHandlerAndCommands = async (
  67. widget: ConsolePanel
  68. ): Promise<void> => {
  69. const { sessionContext } = widget;
  70. await sessionContext.ready;
  71. await handler.updateContext(widget, sessionContext);
  72. app.commands.notifyCommandChanged();
  73. };
  74. if (labShell) {
  75. labShell.currentChanged.connect(async (_, update) => {
  76. const widget = update.newValue;
  77. if (!(widget instanceof ConsolePanel)) {
  78. return;
  79. }
  80. await updateHandlerAndCommands(widget);
  81. });
  82. return;
  83. }
  84. consoleTracker.currentChanged.connect(async (_, consolePanel) => {
  85. await updateHandlerAndCommands(consolePanel);
  86. });
  87. }
  88. };
  89. /**
  90. * A plugin that provides visual debugging support for file editors.
  91. */
  92. const files: JupyterFrontEndPlugin<void> = {
  93. id: '@jupyterlab/debugger:files',
  94. autoStart: true,
  95. requires: [IDebugger, IEditorTracker],
  96. optional: [ILabShell],
  97. activate: (
  98. app: JupyterFrontEnd,
  99. debug: IDebugger,
  100. editorTracker: IEditorTracker,
  101. labShell: ILabShell
  102. ) => {
  103. const handler = new DebuggerHandler({
  104. type: 'file',
  105. shell: app.shell,
  106. service: debug
  107. });
  108. const activeSessions: {
  109. [id: string]: Session.ISessionConnection;
  110. } = {};
  111. const updateHandlerAndCommands = async (
  112. widget: DocumentWidget
  113. ): Promise<void> => {
  114. const sessions = app.serviceManager.sessions;
  115. try {
  116. const model = await sessions.findByPath(widget.context.path);
  117. let session = activeSessions[model.id];
  118. if (!session) {
  119. // Use `connectTo` only if the session does not exist.
  120. // `connectTo` sends a kernel_info_request on the shell
  121. // channel, which blocks the debug session restore when waiting
  122. // for the kernel to be ready
  123. session = sessions.connectTo({ model });
  124. activeSessions[model.id] = session;
  125. }
  126. await handler.update(widget, session);
  127. app.commands.notifyCommandChanged();
  128. } catch {
  129. return;
  130. }
  131. };
  132. if (labShell) {
  133. labShell.currentChanged.connect(async (_, update) => {
  134. const widget = update.newValue;
  135. if (!(widget instanceof DocumentWidget)) {
  136. return;
  137. }
  138. const content = widget.content;
  139. if (!(content instanceof FileEditor)) {
  140. return;
  141. }
  142. await updateHandlerAndCommands(widget);
  143. });
  144. }
  145. editorTracker.currentChanged.connect(async (_, documentWidget) => {
  146. await updateHandlerAndCommands(
  147. (documentWidget as unknown) as DocumentWidget
  148. );
  149. });
  150. }
  151. };
  152. /**
  153. * A plugin that provides visual debugging support for notebooks.
  154. */
  155. const notebooks: JupyterFrontEndPlugin<void> = {
  156. id: '@jupyterlab/debugger:notebooks',
  157. autoStart: true,
  158. requires: [IDebugger, INotebookTracker],
  159. optional: [ILabShell],
  160. activate: (
  161. app: JupyterFrontEnd,
  162. service: IDebugger,
  163. notebookTracker: INotebookTracker,
  164. labShell: ILabShell
  165. ) => {
  166. const handler = new DebuggerHandler({
  167. type: 'notebook',
  168. shell: app.shell,
  169. service
  170. });
  171. const updateHandlerAndCommands = async (
  172. widget: NotebookPanel
  173. ): Promise<void> => {
  174. const { sessionContext } = widget;
  175. await sessionContext.ready;
  176. await handler.updateContext(widget, sessionContext);
  177. app.commands.notifyCommandChanged();
  178. };
  179. if (labShell) {
  180. labShell.currentChanged.connect(async (_, update) => {
  181. const widget = update.newValue;
  182. if (!(widget instanceof NotebookPanel)) {
  183. return;
  184. }
  185. await updateHandlerAndCommands(widget);
  186. });
  187. return;
  188. }
  189. notebookTracker.currentChanged.connect(
  190. async (_, notebookPanel: NotebookPanel) => {
  191. await updateHandlerAndCommands(notebookPanel);
  192. }
  193. );
  194. }
  195. };
  196. /**
  197. * A plugin that tracks notebook, console and file editors used for debugging.
  198. */
  199. const tracker: JupyterFrontEndPlugin<void> = {
  200. id: '@jupyterlab/debugger:tracker',
  201. autoStart: true,
  202. requires: [IDebugger, IEditorServices, IDebuggerSources],
  203. activate: (
  204. _,
  205. debug: IDebugger,
  206. editorServices: IEditorServices,
  207. editorFinder: IDebugger.ISources
  208. ) => {
  209. new TrackerHandler({
  210. editorServices,
  211. debuggerService: debug,
  212. editorFinder
  213. });
  214. }
  215. };
  216. /**
  217. * A plugin that provides a debugger service.
  218. */
  219. const service: JupyterFrontEndPlugin<IDebugger> = {
  220. id: '@jupyterlab/debugger:service',
  221. autoStart: true,
  222. provides: IDebugger,
  223. requires: [IDebuggerConfig, IDebuggerSources],
  224. activate: (
  225. app: JupyterFrontEnd,
  226. config: IDebugger.IConfig,
  227. editorFinder: IDebugger.ISources
  228. ) =>
  229. new Debugger.Service({
  230. config,
  231. editorFinder,
  232. specsManager: app.serviceManager.kernelspecs
  233. })
  234. };
  235. /**
  236. * A plugin that provides a configuration with hash method.
  237. */
  238. const configuration: JupyterFrontEndPlugin<IDebugger.IConfig> = {
  239. id: '@jupyterlab/debugger:config',
  240. provides: IDebuggerConfig,
  241. autoStart: true,
  242. activate: () => new Debugger.Config()
  243. };
  244. /**
  245. * A plugin that provides source/editor functionality for debugging.
  246. */
  247. const sources: JupyterFrontEndPlugin<IDebugger.ISources> = {
  248. id: '@jupyterlab/debugger:sources',
  249. autoStart: true,
  250. provides: IDebuggerSources,
  251. requires: [IDebuggerConfig, IEditorServices],
  252. optional: [INotebookTracker, IConsoleTracker, IEditorTracker],
  253. activate: (
  254. app: JupyterFrontEnd,
  255. config: IDebugger.IConfig,
  256. editorServices: IEditorServices,
  257. notebookTracker: INotebookTracker | null,
  258. consoleTracker: IConsoleTracker | null,
  259. editorTracker: IEditorTracker | null
  260. ): IDebugger.ISources => {
  261. return new Debugger.Sources({
  262. config,
  263. shell: app.shell,
  264. editorServices,
  265. notebookTracker,
  266. consoleTracker,
  267. editorTracker
  268. });
  269. }
  270. };
  271. /*
  272. * A plugin to open detailed views for variables.
  273. */
  274. const variables: JupyterFrontEndPlugin<void> = {
  275. id: '@jupyterlab/debugger:variables',
  276. autoStart: true,
  277. requires: [IDebugger],
  278. optional: [IThemeManager],
  279. activate: (
  280. app: JupyterFrontEnd,
  281. service: IDebugger,
  282. themeManager: IThemeManager
  283. ) => {
  284. const { commands, shell } = app;
  285. const tracker = new WidgetTracker<MainAreaWidget<VariablesBodyGrid>>({
  286. namespace: 'debugger/inspect-variable'
  287. });
  288. commands.addCommand(CommandIDs.inspectVariable, {
  289. label: 'Inspect Variable',
  290. caption: 'Inspect Variable',
  291. execute: async args => {
  292. const { variableReference } = args;
  293. if (!variableReference || variableReference === 0) {
  294. return;
  295. }
  296. const variables = await service.inspectVariable(
  297. variableReference as number
  298. );
  299. const title = args.title as string;
  300. const id = `jp-debugger-variable-${title}`;
  301. if (
  302. !variables ||
  303. variables.length === 0 ||
  304. tracker.find(widget => widget.id === id)
  305. ) {
  306. return;
  307. }
  308. const model = (service.model as Debugger.Model).variables;
  309. const widget = new MainAreaWidget<VariablesBodyGrid>({
  310. content: new VariablesBodyGrid({
  311. model,
  312. commands,
  313. scopes: [{ name: title, variables }]
  314. })
  315. });
  316. widget.addClass('jp-DebuggerVariables');
  317. widget.id = id;
  318. widget.title.icon = variableIcon;
  319. widget.title.label = `${service.session?.connection?.name} - ${title}`;
  320. void tracker.add(widget);
  321. model.changed.connect(() => widget.dispose());
  322. if (themeManager) {
  323. const updateStyle = (): void => {
  324. const isLight = themeManager?.theme
  325. ? themeManager.isLight(themeManager.theme)
  326. : true;
  327. widget.content.theme = isLight ? 'light' : 'dark';
  328. };
  329. themeManager.themeChanged.connect(updateStyle);
  330. widget.disposed.connect(() =>
  331. themeManager.themeChanged.disconnect(updateStyle)
  332. );
  333. updateStyle();
  334. }
  335. shell.add(widget, 'main', {
  336. mode: tracker.currentWidget ? 'split-right' : 'split-bottom'
  337. });
  338. }
  339. });
  340. }
  341. };
  342. /**
  343. * The main debugger UI plugin.
  344. */
  345. const main: JupyterFrontEndPlugin<void> = {
  346. id: '@jupyterlab/debugger:main',
  347. requires: [IDebugger, IEditorServices],
  348. optional: [
  349. ILabShell,
  350. ILayoutRestorer,
  351. ICommandPalette,
  352. ISettingRegistry,
  353. IThemeManager
  354. ],
  355. autoStart: true,
  356. activate: async (
  357. app: JupyterFrontEnd,
  358. service: IDebugger,
  359. editorServices: IEditorServices,
  360. labShell: ILabShell | null,
  361. restorer: ILayoutRestorer | null,
  362. palette: ICommandPalette | null,
  363. settingRegistry: ISettingRegistry | null,
  364. themeManager: IThemeManager | null
  365. ): Promise<void> => {
  366. const { commands, shell } = app;
  367. commands.addCommand(CommandIDs.debugContinue, {
  368. label: 'Continue',
  369. caption: 'Continue',
  370. icon: continueIcon,
  371. isEnabled: () => {
  372. return service.hasStoppedThreads();
  373. },
  374. execute: async () => {
  375. await service.continue();
  376. commands.notifyCommandChanged();
  377. }
  378. });
  379. commands.addCommand(CommandIDs.terminate, {
  380. label: 'Terminate',
  381. caption: 'Terminate',
  382. icon: terminateIcon,
  383. isEnabled: () => {
  384. return service.hasStoppedThreads();
  385. },
  386. execute: async () => {
  387. await service.restart();
  388. commands.notifyCommandChanged();
  389. }
  390. });
  391. commands.addCommand(CommandIDs.next, {
  392. label: 'Next',
  393. caption: 'Next',
  394. icon: stepOverIcon,
  395. isEnabled: () => {
  396. return service.hasStoppedThreads();
  397. },
  398. execute: async () => {
  399. await service.next();
  400. }
  401. });
  402. commands.addCommand(CommandIDs.stepIn, {
  403. label: 'StepIn',
  404. caption: 'Step In',
  405. icon: stepIntoIcon,
  406. isEnabled: () => {
  407. return service.hasStoppedThreads();
  408. },
  409. execute: async () => {
  410. await service.stepIn();
  411. }
  412. });
  413. commands.addCommand(CommandIDs.stepOut, {
  414. label: 'StepOut',
  415. caption: 'Step Out',
  416. icon: stepOutIcon,
  417. isEnabled: () => {
  418. return service.hasStoppedThreads();
  419. },
  420. execute: async () => {
  421. await service.stepOut();
  422. }
  423. });
  424. const callstackCommands = {
  425. registry: commands,
  426. continue: CommandIDs.debugContinue,
  427. terminate: CommandIDs.terminate,
  428. next: CommandIDs.next,
  429. stepIn: CommandIDs.stepIn,
  430. stepOut: CommandIDs.stepOut
  431. };
  432. const sidebar = new Debugger.Sidebar({
  433. service,
  434. callstackCommands,
  435. editorServices
  436. });
  437. if (settingRegistry) {
  438. const setting = await settingRegistry.load(main.id);
  439. const updateSettings = (): void => {
  440. const filters = setting.get('variableFilters').composite as {
  441. [key: string]: string[];
  442. };
  443. const list = filters[service.session?.connection?.kernel?.name];
  444. if (list) {
  445. sidebar.variables.filter = new Set<string>(list);
  446. }
  447. };
  448. updateSettings();
  449. setting.changed.connect(updateSettings);
  450. service.sessionChanged.connect(updateSettings);
  451. }
  452. if (themeManager) {
  453. const updateStyle = (): void => {
  454. const isLight = themeManager?.theme
  455. ? themeManager.isLight(themeManager.theme)
  456. : true;
  457. sidebar.variables.theme = isLight ? 'light' : 'dark';
  458. };
  459. themeManager.themeChanged.connect(updateStyle);
  460. updateStyle();
  461. }
  462. service.eventMessage.connect((_, event): void => {
  463. commands.notifyCommandChanged();
  464. if (labShell && event.event === 'initialized') {
  465. labShell.expandRight();
  466. }
  467. });
  468. service.sessionChanged.connect(_ => {
  469. commands.notifyCommandChanged();
  470. });
  471. if (restorer) {
  472. restorer.add(sidebar, 'debugger-sidebar');
  473. }
  474. shell.add(sidebar, 'right');
  475. if (palette) {
  476. const category = 'Debugger';
  477. [
  478. CommandIDs.debugContinue,
  479. CommandIDs.terminate,
  480. CommandIDs.next,
  481. CommandIDs.stepIn,
  482. CommandIDs.stepOut
  483. ].forEach(command => {
  484. palette.addItem({ command, category });
  485. });
  486. }
  487. }
  488. };
  489. /**
  490. * Export the plugins as default.
  491. */
  492. const plugins: JupyterFrontEndPlugin<any>[] = [
  493. service,
  494. consoles,
  495. files,
  496. notebooks,
  497. tracker,
  498. variables,
  499. main,
  500. sources,
  501. configuration
  502. ];
  503. export default plugins;