index.ts 11 KB

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