plugin.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. Menu
  5. } from 'phosphor/lib/ui/menu';
  6. import {
  7. JupyterLab, JupyterLabPlugin
  8. } from '../application';
  9. import {
  10. IClipboard
  11. } from '../clipboard';
  12. import {
  13. ICommandPalette
  14. } from '../commandpalette';
  15. import {
  16. IMainMenu
  17. } from '../mainmenu';
  18. import {
  19. IDocumentRegistry,
  20. restartKernel, selectKernelForContext
  21. } from '../docregistry';
  22. import {
  23. IInspector
  24. } from '../inspector';
  25. import {
  26. IRenderMime
  27. } from '../rendermime';
  28. import {
  29. IServiceManager
  30. } from '../services';
  31. import {
  32. IStateDB
  33. } from '../statedb';
  34. import {
  35. INotebookTracker, NotebookModelFactory, NotebookPanel, NotebookTracker,
  36. NotebookWidgetFactory, NotebookActions
  37. } from './index';
  38. /**
  39. * The class name for all main area portrait tab icons.
  40. */
  41. const PORTRAIT_ICON_CLASS = 'jp-MainAreaPortraitIcon';
  42. /**
  43. * The class name for the notebook icon from the default theme.
  44. */
  45. const NOTEBOOK_ICON_CLASS = 'jp-ImageNotebook';
  46. /**
  47. * The notebook plugin state namespace.
  48. */
  49. const NAMESPACE = 'notebooks';
  50. /**
  51. * The notebook instance tracker.
  52. */
  53. const tracker = new NotebookTracker();
  54. /**
  55. * The map of command ids used by the notebook.
  56. */
  57. const cmdIds = {
  58. interrupt: 'notebook:interrupt-kernel',
  59. restart: 'notebook:restart-kernel',
  60. restartClear: 'notebook:restart-clear',
  61. restartRunAll: 'notebook:restart-runAll',
  62. switchKernel: 'notebook:switch-kernel',
  63. clearAllOutputs: 'notebook:clear-outputs',
  64. run: 'notebook-cells:run',
  65. runAndAdvance: 'notebook-cells:run-and-advance',
  66. runAndInsert: 'notebook-cells:run-and-insert',
  67. runAll: 'notebook:run-all',
  68. toCode: 'notebook-cells:to-code',
  69. toMarkdown: 'notebook-cells:to-markdown',
  70. toRaw: 'notebook-cells:to-raw',
  71. cut: 'notebook-cells:cut',
  72. copy: 'notebook-cells:copy',
  73. paste: 'notebook-cells:paste',
  74. moveUp: 'notebook-cells:move-up',
  75. moveDown: 'notebook-cells:move-down',
  76. clearOutputs: 'notebook-cells:clear-output',
  77. deleteCell: 'notebook-cells:delete',
  78. insertAbove: 'notebook-cells:insert-above',
  79. insertBelow: 'notebook-cells:insert-below',
  80. selectAbove: 'notebook-cells:select-above',
  81. selectBelow: 'notebook-cells:select-below',
  82. extendAbove: 'notebook-cells:extend-above',
  83. extendBelow: 'notebook-cells:extend-below',
  84. editMode: 'notebook:edit-mode',
  85. merge: 'notebook-cells:merge',
  86. split: 'notebook-cells:split',
  87. commandMode: 'notebook:command-mode',
  88. toggleLines: 'notebook-cells:toggle-line-numbers',
  89. toggleAllLines: 'notebook-cells:toggle-all-line-numbers',
  90. undo: 'notebook-cells:undo',
  91. redo: 'notebook-cells:redo',
  92. markdown1: 'notebook-cells:markdown-header1',
  93. markdown2: 'notebook-cells:markdown-header2',
  94. markdown3: 'notebook-cells:markdown-header3',
  95. markdown4: 'notebook-cells:markdown-header4',
  96. markdown5: 'notebook-cells:markdown-header5',
  97. markdown6: 'notebook-cells:markdown-header6',
  98. };
  99. /**
  100. * The notebook widget tracker provider.
  101. */
  102. export
  103. const notebookTrackerProvider: JupyterLabPlugin<INotebookTracker> = {
  104. id: 'jupyter.services.notebook-tracker',
  105. provides: INotebookTracker,
  106. requires: [
  107. IDocumentRegistry,
  108. IServiceManager,
  109. IRenderMime,
  110. IClipboard,
  111. IMainMenu,
  112. ICommandPalette,
  113. IInspector,
  114. NotebookPanel.IRenderer,
  115. IStateDB
  116. ],
  117. activate: activateNotebookHandler,
  118. autoStart: true
  119. };
  120. /**
  121. * Activate the notebook handler extension.
  122. */
  123. function activateNotebookHandler(app: JupyterLab, registry: IDocumentRegistry, services: IServiceManager, rendermime: IRenderMime, clipboard: IClipboard, mainMenu: IMainMenu, palette: ICommandPalette, inspector: IInspector, renderer: NotebookPanel.IRenderer, state: IStateDB): INotebookTracker {
  124. let widgetFactory = new NotebookWidgetFactory({
  125. name: 'Notebook',
  126. fileExtensions: ['.ipynb'],
  127. modelName: 'notebook',
  128. defaultFor: ['.ipynb'],
  129. preferKernel: true,
  130. canStartKernel: true,
  131. rendermime,
  132. clipboard,
  133. renderer
  134. });
  135. // Sync tracker and set the source of the code inspector.
  136. app.shell.currentChanged.connect((sender, args) => {
  137. let widget = tracker.sync(args.newValue);
  138. if (widget) {
  139. inspector.source = widget.content.inspectionHandler;
  140. }
  141. });
  142. registry.addModelFactory(new NotebookModelFactory());
  143. registry.addWidgetFactory(widgetFactory);
  144. registry.addFileType({
  145. name: 'Notebook',
  146. extension: '.ipynb',
  147. contentType: 'notebook',
  148. fileFormat: 'json'
  149. });
  150. registry.addCreator({
  151. name: 'Notebook',
  152. fileType: 'Notebook',
  153. widgetName: 'Notebook'
  154. });
  155. addCommands(app, services);
  156. populatePalette(palette);
  157. let id = 0; // The ID counter for notebook panels.
  158. widgetFactory.widgetCreated.connect((sender, widget) => {
  159. // If the notebook panel does not have an ID, assign it one.
  160. widget.id = widget.id || `notebook-${++id}`;
  161. widget.title.icon = `${PORTRAIT_ICON_CLASS} ${NOTEBOOK_ICON_CLASS}`;
  162. // Immediately set the inspector source to the current notebook.
  163. inspector.source = widget.content.inspectionHandler;
  164. // Add the notebook panel to the tracker.
  165. tracker.add(widget);
  166. // Add the notebook path to the state database.
  167. let key = `${NAMESPACE}:${widget.context.path}`;
  168. state.save(key, widget.context.path);
  169. // Remove the notebook path from the state database on disposal.
  170. widget.disposed.connect(() => { state.remove(key); });
  171. // Keep track of path changes in the state database.
  172. widget.context.pathChanged.connect((sender, path) => {
  173. state.remove(key);
  174. key = `${NAMESPACE}:${path}`;
  175. state.save(key, path);
  176. });
  177. });
  178. // Reload any notebooks whose state has been stored.
  179. Promise.all([state.fetchNamespace(NAMESPACE), app.started])
  180. .then(([paths]) => {
  181. let open = 'file-operations:open';
  182. paths.forEach(path => { app.commands.execute(open, { path }); });
  183. });
  184. // Add main menu notebook menu.
  185. mainMenu.addMenu(createMenu(app), { rank: 20 });
  186. return tracker;
  187. }
  188. /**
  189. * Add the notebook commands to the application's command registry.
  190. */
  191. function addCommands(app: JupyterLab, services: IServiceManager): void {
  192. let commands = app.commands;
  193. commands.addCommand(cmdIds.runAndAdvance, {
  194. label: 'Run Cell(s) and Advance',
  195. execute: () => {
  196. let current = tracker.currentWidget;
  197. if (current) {
  198. let content = current.content;
  199. NotebookActions.runAndAdvance(content, current.context.kernel);
  200. }
  201. }
  202. });
  203. commands.addCommand(cmdIds.run, {
  204. label: 'Run Cell(s)',
  205. execute: () => {
  206. let current = tracker.currentWidget;
  207. if (current) {
  208. NotebookActions.run(current.content, current.context.kernel);
  209. }
  210. }
  211. });
  212. commands.addCommand(cmdIds.runAndInsert, {
  213. label: 'Run Cell(s) and Insert',
  214. execute: () => {
  215. let current = tracker.currentWidget;
  216. if (current) {
  217. NotebookActions.runAndInsert(current.content, current.context.kernel);
  218. }
  219. }
  220. });
  221. commands.addCommand(cmdIds.runAll, {
  222. label: 'Run All Cells',
  223. execute: () => {
  224. let current = tracker.currentWidget;
  225. if (current) {
  226. NotebookActions.runAll(current.content, current.context.kernel);
  227. }
  228. }
  229. });
  230. commands.addCommand(cmdIds.restart, {
  231. label: 'Restart Kernel',
  232. execute: () => {
  233. let current = tracker.currentWidget;
  234. if (current) {
  235. restartKernel(current.kernel, current.node).then(() => {
  236. current.activate();
  237. });
  238. }
  239. }
  240. });
  241. commands.addCommand(cmdIds.restartClear, {
  242. label: 'Restart Kernel & Clear Outputs',
  243. execute: () => {
  244. let current = tracker.currentWidget;
  245. if (current) {
  246. let promise = restartKernel(current.kernel, current.node);
  247. promise.then(result => {
  248. current.activate();
  249. if (result) {
  250. NotebookActions.clearAllOutputs(current.content);
  251. }
  252. });
  253. }
  254. }
  255. });
  256. commands.addCommand(cmdIds.restartRunAll, {
  257. label: 'Restart Kernel & Run All',
  258. execute: () => {
  259. let current = tracker.currentWidget;
  260. if (current) {
  261. let promise = restartKernel(current.kernel, current.node);
  262. promise.then(result => {
  263. current.activate();
  264. NotebookActions.runAll(current.content, current.context.kernel);
  265. });
  266. }
  267. }
  268. });
  269. commands.addCommand(cmdIds.clearAllOutputs, {
  270. label: 'Clear All Outputs',
  271. execute: () => {
  272. let current = tracker.currentWidget;
  273. if (current) {
  274. NotebookActions.clearAllOutputs(current.content);
  275. }
  276. }
  277. });
  278. commands.addCommand(cmdIds.clearOutputs, {
  279. label: 'Clear Output(s)',
  280. execute: () => {
  281. let current = tracker.currentWidget;
  282. if (current) {
  283. NotebookActions.clearOutputs(current.content);
  284. }
  285. }
  286. });
  287. commands.addCommand(cmdIds.interrupt, {
  288. label: 'Interrupt Kernel',
  289. execute: () => {
  290. let current = tracker.currentWidget;
  291. if (current) {
  292. let kernel = current.context.kernel;
  293. if (kernel) {
  294. kernel.interrupt();
  295. }
  296. }
  297. }
  298. });
  299. commands.addCommand(cmdIds.toCode, {
  300. label: 'Convert to Code',
  301. execute: () => {
  302. let current = tracker.currentWidget;
  303. if (current) {
  304. NotebookActions.changeCellType(current.content, 'code');
  305. }
  306. }
  307. });
  308. commands.addCommand(cmdIds.toMarkdown, {
  309. label: 'Convert to Markdown',
  310. execute: () => {
  311. let current = tracker.currentWidget;
  312. if (current) {
  313. NotebookActions.changeCellType(current.content, 'markdown');
  314. }
  315. }
  316. });
  317. commands.addCommand(cmdIds.toRaw, {
  318. label: 'Convert to Raw',
  319. execute: () => {
  320. let current = tracker.currentWidget;
  321. if (current) {
  322. NotebookActions.changeCellType(current.content, 'raw');
  323. }
  324. }
  325. });
  326. commands.addCommand(cmdIds.cut, {
  327. label: 'Cut Cell(s)',
  328. execute: () => {
  329. let current = tracker.currentWidget;
  330. if (current) {
  331. NotebookActions.cut(current.content, current.clipboard);
  332. }
  333. }
  334. });
  335. commands.addCommand(cmdIds.copy, {
  336. label: 'Copy Cell(s)',
  337. execute: () => {
  338. let current = tracker.currentWidget;
  339. if (current) {
  340. NotebookActions.copy(current.content, current.clipboard);
  341. }
  342. }
  343. });
  344. commands.addCommand(cmdIds.paste, {
  345. label: 'Paste Cell(s)',
  346. execute: () => {
  347. let current = tracker.currentWidget;
  348. if (current) {
  349. NotebookActions.paste(current.content, current.clipboard);
  350. }
  351. }
  352. });
  353. commands.addCommand(cmdIds.deleteCell, {
  354. label: 'Delete Cell(s)',
  355. execute: () => {
  356. let current = tracker.currentWidget;
  357. if (current) {
  358. NotebookActions.deleteCells(current.content);
  359. }
  360. }
  361. });
  362. commands.addCommand(cmdIds.split, {
  363. label: 'Split Cell',
  364. execute: () => {
  365. let current = tracker.currentWidget;
  366. if (current) {
  367. NotebookActions.splitCell(current.content);
  368. }
  369. }
  370. });
  371. commands.addCommand(cmdIds.merge, {
  372. label: 'Merge Selected Cell(s)',
  373. execute: () => {
  374. let current = tracker.currentWidget;
  375. if (current) {
  376. NotebookActions.mergeCells(current.content);
  377. }
  378. }
  379. });
  380. commands.addCommand(cmdIds.insertAbove, {
  381. label: 'Insert Cell Above',
  382. execute: () => {
  383. let current = tracker.currentWidget;
  384. if (current) {
  385. NotebookActions.insertAbove(current.content);
  386. }
  387. }
  388. });
  389. commands.addCommand(cmdIds.insertBelow, {
  390. label: 'Insert Cell Below',
  391. execute: () => {
  392. let current = tracker.currentWidget;
  393. if (current) {
  394. NotebookActions.insertBelow(current.content);
  395. }
  396. }
  397. });
  398. commands.addCommand(cmdIds.selectAbove, {
  399. label: 'Select Cell Above',
  400. execute: () => {
  401. let current = tracker.currentWidget;
  402. if (current) {
  403. NotebookActions.selectAbove(current.content);
  404. }
  405. }
  406. });
  407. commands.addCommand(cmdIds.selectBelow, {
  408. label: 'Select Cell Below',
  409. execute: () => {
  410. let current = tracker.currentWidget;
  411. if (current) {
  412. NotebookActions.selectBelow(current.content);
  413. }
  414. }
  415. });
  416. commands.addCommand(cmdIds.extendAbove, {
  417. label: 'Extend Selection Above',
  418. execute: () => {
  419. let current = tracker.currentWidget;
  420. if (current) {
  421. NotebookActions.extendSelectionAbove(current.content);
  422. }
  423. }
  424. });
  425. commands.addCommand(cmdIds.extendBelow, {
  426. label: 'Extend Selection Below',
  427. execute: () => {
  428. let current = tracker.currentWidget;
  429. if (current) {
  430. NotebookActions.extendSelectionBelow(current.content);
  431. }
  432. }
  433. });
  434. commands.addCommand(cmdIds.moveUp, {
  435. label: 'Move Cell(s) Up',
  436. execute: () => {
  437. let current = tracker.currentWidget;
  438. if (current) {
  439. NotebookActions.moveUp(current.content);
  440. }
  441. }
  442. });
  443. commands.addCommand(cmdIds.moveDown, {
  444. label: 'Move Cell(s) Down',
  445. execute: () => {
  446. let current = tracker.currentWidget;
  447. if (current) {
  448. NotebookActions.moveDown(current.content);
  449. }
  450. }
  451. });
  452. commands.addCommand(cmdIds.toggleLines, {
  453. label: 'Toggle Line Numbers',
  454. execute: () => {
  455. let current = tracker.currentWidget;
  456. if (current) {
  457. NotebookActions.toggleLineNumbers(current.content);
  458. }
  459. }
  460. });
  461. commands.addCommand(cmdIds.toggleAllLines, {
  462. label: 'Toggle All Line Numbers',
  463. execute: () => {
  464. let current = tracker.currentWidget;
  465. if (current) {
  466. NotebookActions.toggleAllLineNumbers(current.content);
  467. }
  468. }
  469. });
  470. commands.addCommand(cmdIds.commandMode, {
  471. label: 'To Command Mode',
  472. execute: () => {
  473. let current = tracker.currentWidget;
  474. if (current) {
  475. current.content.mode = 'command';
  476. }
  477. }
  478. });
  479. commands.addCommand(cmdIds.editMode, {
  480. label: 'To Edit Mode',
  481. execute: () => {
  482. let current = tracker.currentWidget;
  483. if (current) {
  484. current.content.mode = 'edit';
  485. }
  486. }
  487. });
  488. commands.addCommand(cmdIds.undo, {
  489. label: 'Undo Cell Operation',
  490. execute: () => {
  491. let current = tracker.currentWidget;
  492. if (current) {
  493. NotebookActions.undo(current.content);
  494. }
  495. }
  496. });
  497. commands.addCommand(cmdIds.redo, {
  498. label: 'Redo Cell Operation',
  499. execute: () => {
  500. let current = tracker.currentWidget;
  501. if (current) {
  502. NotebookActions.redo(current.content);
  503. }
  504. }
  505. });
  506. commands.addCommand(cmdIds.switchKernel, {
  507. label: 'Switch Kernel',
  508. execute: () => {
  509. let current = tracker.currentWidget;
  510. if (current) {
  511. let context = current.context;
  512. let node = current.node;
  513. selectKernelForContext(context, services.sessions, node).then(() => {
  514. current.activate();
  515. });
  516. }
  517. }
  518. });
  519. commands.addCommand(cmdIds.markdown1, {
  520. label: 'Markdown Header 1',
  521. execute: () => {
  522. let current = tracker.currentWidget;
  523. if (current) {
  524. NotebookActions.setMarkdownHeader(current.content, 1);
  525. }
  526. }
  527. });
  528. commands.addCommand(cmdIds.markdown2, {
  529. label: 'Markdown Header 2',
  530. execute: () => {
  531. let current = tracker.currentWidget;
  532. if (current) {
  533. NotebookActions.setMarkdownHeader(current.content, 2);
  534. }
  535. }
  536. });
  537. commands.addCommand(cmdIds.markdown3, {
  538. label: 'Markdown Header 3',
  539. execute: () => {
  540. let current = tracker.currentWidget;
  541. if (current) {
  542. NotebookActions.setMarkdownHeader(current.content, 3);
  543. }
  544. }
  545. });
  546. commands.addCommand(cmdIds.markdown4, {
  547. label: 'Markdown Header 4',
  548. execute: () => {
  549. let current = tracker.currentWidget;
  550. if (current) {
  551. NotebookActions.setMarkdownHeader(current.content, 4);
  552. }
  553. }
  554. });
  555. commands.addCommand(cmdIds.markdown5, {
  556. label: 'Markdown Header 5',
  557. execute: () => {
  558. let current = tracker.currentWidget;
  559. if (current) {
  560. NotebookActions.setMarkdownHeader(current.content, 5);
  561. }
  562. }
  563. });
  564. commands.addCommand(cmdIds.markdown6, {
  565. label: 'Markdown Header 6',
  566. execute: () => {
  567. let current = tracker.currentWidget;
  568. if (current) {
  569. NotebookActions.setMarkdownHeader(current.content, 6);
  570. }
  571. }
  572. });
  573. }
  574. /**
  575. * Populate the application's command palette with notebook commands.
  576. */
  577. function populatePalette(palette: ICommandPalette): void {
  578. let category = 'Notebook Operations';
  579. [
  580. cmdIds.interrupt,
  581. cmdIds.restart,
  582. cmdIds.restartClear,
  583. cmdIds.restartRunAll,
  584. cmdIds.runAll,
  585. cmdIds.clearAllOutputs,
  586. cmdIds.toggleAllLines,
  587. cmdIds.editMode,
  588. cmdIds.commandMode,
  589. cmdIds.switchKernel
  590. ].forEach(command => { palette.addItem({ command, category }); });
  591. category = 'Notebook Cell Operations';
  592. [
  593. cmdIds.run,
  594. cmdIds.runAndAdvance,
  595. cmdIds.runAndInsert,
  596. cmdIds.clearOutputs,
  597. cmdIds.toCode,
  598. cmdIds.toMarkdown,
  599. cmdIds.toRaw,
  600. cmdIds.cut,
  601. cmdIds.copy,
  602. cmdIds.paste,
  603. cmdIds.deleteCell,
  604. cmdIds.split,
  605. cmdIds.merge,
  606. cmdIds.insertAbove,
  607. cmdIds.insertBelow,
  608. cmdIds.selectAbove,
  609. cmdIds.selectBelow,
  610. cmdIds.extendAbove,
  611. cmdIds.extendBelow,
  612. cmdIds.moveDown,
  613. cmdIds.moveUp,
  614. cmdIds.toggleLines,
  615. cmdIds.undo,
  616. cmdIds.redo,
  617. cmdIds.markdown1,
  618. cmdIds.markdown2,
  619. cmdIds.markdown3,
  620. cmdIds.markdown4,
  621. cmdIds.markdown5,
  622. cmdIds.markdown6
  623. ].forEach(command => { palette.addItem({ command, category }); });
  624. }
  625. /**
  626. * Creates a menu for the notebook.
  627. */
  628. function createMenu(app: JupyterLab): Menu {
  629. let { commands, keymap } = app;
  630. let menu = new Menu({ commands, keymap });
  631. let settings = new Menu({ commands, keymap });
  632. menu.title.label = 'Notebook';
  633. settings.title.label = 'Settings';
  634. settings.addItem({ command: cmdIds.toggleAllLines });
  635. menu.addItem({ command: cmdIds.undo });
  636. menu.addItem({ command: cmdIds.redo });
  637. menu.addItem({ command: cmdIds.split });
  638. menu.addItem({ command: cmdIds.deleteCell });
  639. menu.addItem({ command: cmdIds.clearAllOutputs });
  640. menu.addItem({ command: cmdIds.runAll });
  641. menu.addItem({ command: cmdIds.restart });
  642. menu.addItem({ command: cmdIds.switchKernel });
  643. menu.addItem({ type: 'separator' });
  644. menu.addItem({ type: 'submenu', menu: settings });
  645. return menu;
  646. }