index.ts 37 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. ILayoutRestorer, JupyterLab, JupyterLabPlugin
  5. } from '@jupyterlab/application';
  6. import {
  7. Dialog, ICommandPalette, showDialog
  8. } from '@jupyterlab/apputils';
  9. import {
  10. IEditorServices
  11. } from '@jupyterlab/codeeditor';
  12. import {
  13. IStateDB, PageConfig, URLExt, uuid
  14. } from '@jupyterlab/coreutils';
  15. import {
  16. IFileBrowserFactory
  17. } from '@jupyterlab/filebrowser';
  18. import {
  19. ILauncher
  20. } from '@jupyterlab/launcher';
  21. import {
  22. IMainMenu, IEditMenu, IFileMenu, IKernelMenu, IRunMenu, IViewMenu
  23. } from '@jupyterlab/mainmenu';
  24. import {
  25. CellTools, ICellTools, INotebookTracker, NotebookActions,
  26. NotebookModelFactory, NotebookPanel, NotebookTracker, NotebookWidgetFactory
  27. } from '@jupyterlab/notebook';
  28. import {
  29. ServiceManager
  30. } from '@jupyterlab/services';
  31. import {
  32. ReadonlyJSONObject
  33. } from '@phosphor/coreutils';
  34. import {
  35. Message, MessageLoop
  36. } from '@phosphor/messaging';
  37. import {
  38. Menu, Panel, Widget
  39. } from '@phosphor/widgets';
  40. /**
  41. * The command IDs used by the notebook plugin.
  42. */
  43. namespace CommandIDs {
  44. export
  45. const createNew = 'notebook:create-new';
  46. export
  47. const interrupt = 'notebook:interrupt-kernel';
  48. export
  49. const restart = 'notebook:restart-kernel';
  50. export
  51. const restartClear = 'notebook:restart-clear-output';
  52. export
  53. const restartRunAll = 'notebook:restart-run-all';
  54. export
  55. const reconnectToKernel = 'notebook:reconnect-to-kernel';
  56. export
  57. const changeKernel = 'notebook:change-kernel';
  58. export
  59. const createConsole = 'notebook:create-console';
  60. export
  61. const createCellView = 'notebook:create-cell-view';
  62. export
  63. const clearAllOutputs = 'notebook:clear-all-cell-outputs';
  64. export
  65. const closeAndShutdown = 'notebook:close-and-shutdown';
  66. export
  67. const trust = 'notebook:trust';
  68. export
  69. const exportToFormat = 'notebook:export-to-format';
  70. export
  71. const run = 'notebook:run-cell';
  72. export
  73. const runAndAdvance = 'notebook:run-cell-and-select-next';
  74. export
  75. const runAndInsert = 'notebook:run-cell-and-insert-below';
  76. export
  77. const runAll = 'notebook:run-all-cells';
  78. export
  79. const toCode = 'notebook:change-cell-to-code';
  80. export
  81. const toMarkdown = 'notebook:change-cell-to-markdown';
  82. export
  83. const toRaw = 'notebook:change-cell-to-raw';
  84. export
  85. const cut = 'notebook:cut-cell';
  86. export
  87. const copy = 'notebook:copy-cell';
  88. export
  89. const paste = 'notebook:paste-cell';
  90. export
  91. const moveUp = 'notebook:move-cell-up';
  92. export
  93. const moveDown = 'notebook:move-cell-down';
  94. export
  95. const clearOutputs = 'notebook:clear-cell-output';
  96. export
  97. const deleteCell = 'notebook:delete-cell';
  98. export
  99. const insertAbove = 'notebook:insert-cell-above';
  100. export
  101. const insertBelow = 'notebook:insert-cell-below';
  102. export
  103. const selectAbove = 'notebook:move-cursor-up';
  104. export
  105. const selectBelow = 'notebook:move-cursor-down';
  106. export
  107. const extendAbove = 'notebook:extend-marked-cells-above';
  108. export
  109. const extendBelow = 'notebook:extend-marked-cells-below';
  110. export
  111. const editMode = 'notebook:enter-edit-mode';
  112. export
  113. const merge = 'notebook:merge-cells';
  114. export
  115. const split = 'notebook:split-cell-at-cursor';
  116. export
  117. const commandMode = 'notebook:enter-command-mode';
  118. export
  119. const toggleAllLines = 'notebook:toggle-all-cell-line-numbers';
  120. export
  121. const undoCellAction = 'notebook:undo-cell-action';
  122. export
  123. const redoCellAction = 'notebook:redo-cell-action';
  124. export
  125. const markdown1 = 'notebook:change-cell-to-heading-1';
  126. export
  127. const markdown2 = 'notebook:change-cell-to-heading-2';
  128. export
  129. const markdown3 = 'notebook:change-cell-to-heading-3';
  130. export
  131. const markdown4 = 'notebook:change-cell-to-heading-4';
  132. export
  133. const markdown5 = 'notebook:change-cell-to-heading-5';
  134. export
  135. const markdown6 = 'notebook:change-cell-to-heading-6';
  136. export
  137. const hideCode = 'notebook:hide-cell-code';
  138. export
  139. const showCode = 'notebook:show-cell-code';
  140. export
  141. const hideAllCode = 'notebook:hide-all-cell-code';
  142. export
  143. const showAllCode = 'notebook:show-all-cell-code';
  144. export
  145. const hideOutput = 'notebook:hide-cell-outputs';
  146. export
  147. const showOutput = 'notebook:show-cell-outputs';
  148. export
  149. const hideAllOutputs = 'notebook:hide-all-cell-outputs';
  150. export
  151. const showAllOutputs = 'notebook:show-all-cell-outputs';
  152. }
  153. /**
  154. * The class name for the notebook icon from the default theme.
  155. */
  156. const NOTEBOOK_ICON_CLASS = 'jp-NotebookRunningIcon';
  157. /**
  158. * The name of the factory that creates notebooks.
  159. */
  160. const FACTORY = 'Notebook';
  161. /**
  162. * The allowed Export To ... formats and their human readable labels.
  163. */
  164. const EXPORT_TO_FORMATS = [
  165. { 'format': 'html', 'label': 'HTML' },
  166. { 'format': 'latex', 'label': 'LaTeX' },
  167. { 'format': 'markdown', 'label': 'Markdown' },
  168. { 'format': 'pdf', 'label': 'PDF' },
  169. { 'format': 'rst', 'label': 'ReStructured Text' },
  170. { 'format': 'script', 'label': 'Executable Script' },
  171. { 'format': 'slides', 'label': 'Reveal JS' }
  172. ];
  173. /**
  174. * The notebook widget tracker provider.
  175. */
  176. const tracker: JupyterLabPlugin<INotebookTracker> = {
  177. id: '@jupyterlab/notebook-extension:tracker',
  178. provides: INotebookTracker,
  179. requires: [
  180. IMainMenu,
  181. ICommandPalette,
  182. NotebookPanel.IContentFactory,
  183. IEditorServices,
  184. ILayoutRestorer
  185. ],
  186. optional: [IFileBrowserFactory, ILauncher],
  187. activate: activateNotebookHandler,
  188. autoStart: true
  189. };
  190. /**
  191. * The notebook cell factory provider.
  192. */
  193. const factory: JupyterLabPlugin<NotebookPanel.IContentFactory> = {
  194. id: '@jupyterlab/notebook-extension:factory',
  195. provides: NotebookPanel.IContentFactory,
  196. requires: [IEditorServices],
  197. autoStart: true,
  198. activate: (app: JupyterLab, editorServices: IEditorServices) => {
  199. let editorFactory = editorServices.factoryService.newInlineEditor.bind(
  200. editorServices.factoryService);
  201. return new NotebookPanel.ContentFactory({ editorFactory });
  202. }
  203. };
  204. /**
  205. * The cell tools extension.
  206. */
  207. const tools: JupyterLabPlugin<ICellTools> = {
  208. activate: activateCellTools,
  209. provides: ICellTools,
  210. id: '@jupyterlab/notebook-extension:tools',
  211. autoStart: true,
  212. requires: [INotebookTracker, IEditorServices, IStateDB]
  213. };
  214. /**
  215. * Export the plugins as default.
  216. */
  217. const plugins: JupyterLabPlugin<any>[] = [factory, tracker, tools];
  218. export default plugins;
  219. /**
  220. * Activate the cell tools extension.
  221. */
  222. function activateCellTools(app: JupyterLab, tracker: INotebookTracker, editorServices: IEditorServices, state: IStateDB): Promise<ICellTools> {
  223. const id = 'cell-tools';
  224. const celltools = new CellTools({ tracker });
  225. const activeCellTool = new CellTools.ActiveCellTool();
  226. const slideShow = CellTools.createSlideShowSelector();
  227. const nbConvert = CellTools.createNBConvertSelector();
  228. const editorFactory = editorServices.factoryService.newInlineEditor
  229. .bind(editorServices.factoryService);
  230. const metadataEditor = new CellTools.MetadataEditorTool({ editorFactory });
  231. // Create message hook for triggers to save to the database.
  232. const hook = (sender: any, message: Message): boolean => {
  233. switch (message) {
  234. case Widget.Msg.ActivateRequest:
  235. state.save(id, { open: true });
  236. break;
  237. case Widget.Msg.AfterHide:
  238. case Widget.Msg.CloseRequest:
  239. state.remove(id);
  240. break;
  241. default:
  242. break;
  243. }
  244. return true;
  245. };
  246. celltools.title.label = 'Cell Tools';
  247. celltools.id = id;
  248. celltools.addItem({ tool: activeCellTool, rank: 1 });
  249. celltools.addItem({ tool: slideShow, rank: 2 });
  250. celltools.addItem({ tool: nbConvert, rank: 3 });
  251. celltools.addItem({ tool: metadataEditor, rank: 4 });
  252. MessageLoop.installMessageHook(celltools, hook);
  253. // Wait until the application has finished restoring before rendering.
  254. Promise.all([state.fetch(id), app.restored]).then(([args]) => {
  255. const open = !!(args && (args as ReadonlyJSONObject)['open'] as boolean);
  256. // After initial restoration, check if the cell tools should render.
  257. if (tracker.size) {
  258. app.shell.addToLeftArea(celltools);
  259. if (open) {
  260. app.shell.activateById(celltools.id);
  261. }
  262. }
  263. // For all subsequent widget changes, check if the cell tools should render.
  264. app.shell.currentChanged.connect((sender, args) => {
  265. // If there are any open notebooks, add cell tools to the side panel if
  266. // it is not already there.
  267. if (tracker.size) {
  268. if (!celltools.isAttached) {
  269. app.shell.addToLeftArea(celltools);
  270. }
  271. return;
  272. }
  273. // If there are no notebooks, close cell tools.
  274. celltools.close();
  275. });
  276. });
  277. return Promise.resolve(celltools);
  278. }
  279. /**
  280. * Activate the notebook handler extension.
  281. */
  282. function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette, contentFactory: NotebookPanel.IContentFactory, editorServices: IEditorServices, restorer: ILayoutRestorer, browserFactory: IFileBrowserFactory | null, launcher: ILauncher | null): INotebookTracker {
  283. const services = app.serviceManager;
  284. const factory = new NotebookWidgetFactory({
  285. name: FACTORY,
  286. fileTypes: ['notebook'],
  287. modelName: 'notebook',
  288. defaultFor: ['notebook'],
  289. preferKernel: true,
  290. canStartKernel: true,
  291. rendermime: app.rendermime,
  292. contentFactory,
  293. mimeTypeService: editorServices.mimeTypeService
  294. });
  295. const { commands } = app;
  296. const tracker = new NotebookTracker({ namespace: 'notebook' });
  297. // Handle state restoration.
  298. restorer.restore(tracker, {
  299. command: 'docmanager:open',
  300. args: panel => ({ path: panel.context.path, factory: FACTORY }),
  301. name: panel => panel.context.path,
  302. when: services.ready
  303. });
  304. let registry = app.docRegistry;
  305. registry.addModelFactory(new NotebookModelFactory({}));
  306. registry.addWidgetFactory(factory);
  307. addCommands(app, services, tracker);
  308. populatePalette(palette);
  309. let id = 0; // The ID counter for notebook panels.
  310. factory.widgetCreated.connect((sender, widget) => {
  311. // If the notebook panel does not have an ID, assign it one.
  312. widget.id = widget.id || `notebook-${++id}`;
  313. widget.title.icon = NOTEBOOK_ICON_CLASS;
  314. // Notify the instance tracker if restore data needs to update.
  315. widget.context.pathChanged.connect(() => { tracker.save(widget); });
  316. // Add the notebook panel to the tracker.
  317. tracker.add(widget);
  318. });
  319. // Add main menu notebook menu.
  320. populateMenus(app, mainMenu, tracker);
  321. // Utility function to create a new notebook.
  322. const createNew = (cwd: string, kernelName?: string) => {
  323. return commands.execute(
  324. 'docmanager:new-untitled', { path: cwd, type: 'notebook' }
  325. ).then(model => {
  326. return commands.execute('docmanager:open', {
  327. path: model.path, factory: FACTORY,
  328. kernel: { name: kernelName }
  329. });
  330. });
  331. };
  332. // Add a command for creating a new notebook in the File Menu.
  333. commands.addCommand(CommandIDs.createNew, {
  334. label: 'Notebook',
  335. caption: 'Create a new notebook',
  336. execute: () => {
  337. let cwd = browserFactory ?
  338. browserFactory.defaultBrowser.model.path : '';
  339. return createNew(cwd);
  340. }
  341. });
  342. // Add a launcher item if the launcher is available.
  343. if (launcher) {
  344. services.ready.then(() => {
  345. const specs = services.specs;
  346. const baseUrl = PageConfig.getBaseUrl();
  347. for (let name in specs.kernelspecs) {
  348. let displayName = specs.kernelspecs[name].display_name;
  349. let rank = name === specs.default ? 0 : Infinity;
  350. let kernelIconUrl = specs.kernelspecs[name].resources['logo-64x64'];
  351. if (kernelIconUrl) {
  352. let index = kernelIconUrl.indexOf('kernelspecs');
  353. kernelIconUrl = baseUrl + kernelIconUrl.slice(index);
  354. }
  355. launcher.add({
  356. displayName,
  357. category: 'Notebook',
  358. name,
  359. iconClass: 'jp-NotebookRunningIcon',
  360. callback: createNew,
  361. rank,
  362. kernelIconUrl
  363. });
  364. }
  365. });
  366. }
  367. app.contextMenu.addItem({
  368. command: CommandIDs.clearOutputs,
  369. selector: '.jp-Notebook .jp-Cell'
  370. });
  371. app.contextMenu.addItem({
  372. command: CommandIDs.split,
  373. selector: '.jp-Notebook .jp-Cell'
  374. });
  375. app.contextMenu.addItem({
  376. command: CommandIDs.createCellView,
  377. selector: '.jp-Notebook .jp-Cell'
  378. });
  379. app.contextMenu.addItem({
  380. type: 'separator',
  381. selector: '.jp-Notebook',
  382. rank: 0
  383. });
  384. app.contextMenu.addItem({
  385. command: CommandIDs.undoCellAction,
  386. selector: '.jp-Notebook',
  387. rank: 1
  388. });
  389. app.contextMenu.addItem({
  390. command: CommandIDs.redoCellAction,
  391. selector: '.jp-Notebook',
  392. rank: 2
  393. });
  394. app.contextMenu.addItem({
  395. type: 'separator',
  396. selector: '.jp-Notebook',
  397. rank: 0
  398. });
  399. app.contextMenu.addItem({
  400. command: CommandIDs.createConsole,
  401. selector: '.jp-Notebook',
  402. rank: 3
  403. });
  404. app.contextMenu.addItem({
  405. command: CommandIDs.clearAllOutputs,
  406. selector: '.jp-Notebook',
  407. rank: 3
  408. });
  409. return tracker;
  410. }
  411. /**
  412. * Add the notebook commands to the application's command registry.
  413. */
  414. function addCommands(app: JupyterLab, services: ServiceManager, tracker: NotebookTracker): void {
  415. const { commands, shell } = app;
  416. // Get the current widget and activate unless the args specify otherwise.
  417. function getCurrent(args: ReadonlyJSONObject): NotebookPanel | null {
  418. const widget = tracker.currentWidget;
  419. const activate = args['activate'] !== false;
  420. if (activate && widget) {
  421. shell.activateById(widget.id);
  422. }
  423. return widget;
  424. }
  425. /**
  426. * Whether there is an active notebook.
  427. */
  428. function isEnabled(): boolean {
  429. return tracker.currentWidget !== null &&
  430. tracker.currentWidget === app.shell.currentWidget;
  431. }
  432. /**
  433. * The name of the current notebook widget.
  434. */
  435. function currentName(): string {
  436. if (tracker.currentWidget && tracker.currentWidget.title.label) {
  437. return `"${tracker.currentWidget.title.label}"`;
  438. }
  439. return 'Notebook';
  440. }
  441. commands.addCommand(CommandIDs.runAndAdvance, {
  442. label: 'Run Cell(s) and Select Below',
  443. execute: args => {
  444. const current = getCurrent(args);
  445. if (current) {
  446. const { context, notebook } = current;
  447. return NotebookActions.runAndAdvance(notebook, context.session);
  448. }
  449. },
  450. isEnabled
  451. });
  452. commands.addCommand(CommandIDs.run, {
  453. label: 'Run Cell(s)',
  454. execute: args => {
  455. const current = getCurrent(args);
  456. if (current) {
  457. const { context, notebook } = current;
  458. return NotebookActions.run(notebook, context.session);
  459. }
  460. },
  461. isEnabled
  462. });
  463. commands.addCommand(CommandIDs.runAndInsert, {
  464. label: 'Run Cell(s) and Insert Below',
  465. execute: args => {
  466. const current = getCurrent(args);
  467. if (current) {
  468. const { context, notebook } = current;
  469. return NotebookActions.runAndInsert(notebook, context.session);
  470. }
  471. },
  472. isEnabled
  473. });
  474. commands.addCommand(CommandIDs.runAll, {
  475. label: 'Run All Cells',
  476. execute: args => {
  477. const current = getCurrent(args);
  478. if (current) {
  479. const { context, notebook } = current;
  480. return NotebookActions.runAll(notebook, context.session);
  481. }
  482. },
  483. isEnabled
  484. });
  485. commands.addCommand(CommandIDs.restart, {
  486. label: 'Restart Kernel',
  487. execute: args => {
  488. const current = getCurrent(args);
  489. if (current) {
  490. return current.session.restart();
  491. }
  492. },
  493. isEnabled
  494. });
  495. commands.addCommand(CommandIDs.closeAndShutdown, {
  496. label: 'Close and Shutdown',
  497. execute: args => {
  498. const current = getCurrent(args);
  499. if (!current) {
  500. return;
  501. }
  502. const fileName = current.title.label;
  503. return showDialog({
  504. title: 'Shutdown the notebook?',
  505. body: `Are you sure you want to close "${fileName}"?`,
  506. buttons: [Dialog.cancelButton(), Dialog.warnButton()]
  507. }).then(result => {
  508. if (result.button.accept) {
  509. return current.context.session.shutdown()
  510. .then(() => { current.dispose(); });
  511. }
  512. });
  513. },
  514. isEnabled
  515. });
  516. commands.addCommand(CommandIDs.trust, {
  517. label: () => `Trust ${currentName()}`,
  518. execute: args => {
  519. const current = getCurrent(args);
  520. if (current) {
  521. const { context, notebook } = current;
  522. return NotebookActions.trust(notebook).then(() => context.save());
  523. }
  524. },
  525. isEnabled
  526. });
  527. commands.addCommand(CommandIDs.exportToFormat, {
  528. label: args => {
  529. const formatLabel = (args['label']) as string;
  530. const name = currentName();
  531. return (args['isPalette'] ? `Export ${name} to ` : '') + formatLabel;
  532. },
  533. execute: args => {
  534. const current = getCurrent(args);
  535. if (!current) {
  536. return;
  537. }
  538. const notebookPath = URLExt.encodeParts(current.context.path);
  539. const url = URLExt.join(
  540. services.serverSettings.baseUrl,
  541. 'nbconvert',
  542. (args['format']) as string,
  543. notebookPath
  544. ) + '?download=true';
  545. const child = window.open('', '_blank');
  546. const { context } = current;
  547. if (context.model.dirty && !context.model.readOnly) {
  548. return context.save().then(() => { child.location.assign(url); });
  549. }
  550. return new Promise<void>((resolve) => {
  551. child.location.assign(url);
  552. resolve(undefined);
  553. });
  554. },
  555. isEnabled
  556. });
  557. commands.addCommand(CommandIDs.restartClear, {
  558. label: 'Restart Kernel & Clear Outputs',
  559. execute: args => {
  560. const current = getCurrent(args);
  561. if (current) {
  562. const { notebook, session } = current;
  563. return session.restart()
  564. .then(() => { NotebookActions.clearAllOutputs(notebook); });
  565. }
  566. },
  567. isEnabled
  568. });
  569. commands.addCommand(CommandIDs.restartRunAll, {
  570. label: 'Restart Kernel & Run All',
  571. execute: args => {
  572. const current = getCurrent(args);
  573. if (current) {
  574. const { context, notebook, session } = current;
  575. return session.restart()
  576. .then(() => { NotebookActions.runAll(notebook, context.session); });
  577. }
  578. },
  579. isEnabled
  580. });
  581. commands.addCommand(CommandIDs.clearAllOutputs, {
  582. label: 'Clear All Outputs',
  583. execute: args => {
  584. const current = getCurrent(args);
  585. if (current) {
  586. return NotebookActions.clearAllOutputs(current.notebook);
  587. }
  588. },
  589. isEnabled
  590. });
  591. commands.addCommand(CommandIDs.clearOutputs, {
  592. label: 'Clear Output(s)',
  593. execute: args => {
  594. const current = getCurrent(args);
  595. if (current) {
  596. return NotebookActions.clearOutputs(current.notebook);
  597. }
  598. },
  599. isEnabled
  600. });
  601. commands.addCommand(CommandIDs.interrupt, {
  602. label: 'Interrupt Kernel',
  603. execute: args => {
  604. const current = getCurrent(args);
  605. if (!current) {
  606. return;
  607. }
  608. const kernel = current.context.session.kernel;
  609. if (kernel) {
  610. return kernel.interrupt();
  611. }
  612. },
  613. isEnabled
  614. });
  615. commands.addCommand(CommandIDs.toCode, {
  616. label: 'Change to Code Cell Type',
  617. execute: args => {
  618. const current = getCurrent(args);
  619. if (current) {
  620. return NotebookActions.changeCellType(current.notebook, 'code');
  621. }
  622. },
  623. isEnabled
  624. });
  625. commands.addCommand(CommandIDs.toMarkdown, {
  626. label: 'Change to Markdown Cell Type',
  627. execute: args => {
  628. const current = getCurrent(args);
  629. if (current) {
  630. return NotebookActions.changeCellType(current.notebook, 'markdown');
  631. }
  632. },
  633. isEnabled
  634. });
  635. commands.addCommand(CommandIDs.toRaw, {
  636. label: 'Change to Raw Cell Type',
  637. execute: args => {
  638. const current = getCurrent(args);
  639. if (current) {
  640. return NotebookActions.changeCellType(current.notebook, 'raw');
  641. }
  642. },
  643. isEnabled
  644. });
  645. commands.addCommand(CommandIDs.cut, {
  646. label: 'Cut Cell(s)',
  647. execute: args => {
  648. const current = getCurrent(args);
  649. if (current) {
  650. return NotebookActions.cut(current.notebook);
  651. }
  652. },
  653. isEnabled
  654. });
  655. commands.addCommand(CommandIDs.copy, {
  656. label: 'Copy Cell(s)',
  657. execute: args => {
  658. const current = getCurrent(args);
  659. if (current) {
  660. return NotebookActions.copy(current.notebook);
  661. }
  662. },
  663. isEnabled
  664. });
  665. commands.addCommand(CommandIDs.paste, {
  666. label: 'Paste Cell(s) Below',
  667. execute: args => {
  668. const current = getCurrent(args);
  669. if (current) {
  670. return NotebookActions.paste(current.notebook);
  671. }
  672. },
  673. isEnabled
  674. });
  675. commands.addCommand(CommandIDs.deleteCell, {
  676. label: 'Delete Cell(s)',
  677. execute: args => {
  678. const current = getCurrent(args);
  679. if (current) {
  680. return NotebookActions.deleteCells(current.notebook);
  681. }
  682. },
  683. isEnabled
  684. });
  685. commands.addCommand(CommandIDs.split, {
  686. label: 'Split Cell',
  687. execute: args => {
  688. const current = getCurrent(args);
  689. if (current) {
  690. return NotebookActions.splitCell(current.notebook);
  691. }
  692. },
  693. isEnabled
  694. });
  695. commands.addCommand(CommandIDs.merge, {
  696. label: 'Merge Selected Cell(s)',
  697. execute: args => {
  698. const current = getCurrent(args);
  699. if (current) {
  700. return NotebookActions.mergeCells(current.notebook);
  701. }
  702. },
  703. isEnabled
  704. });
  705. commands.addCommand(CommandIDs.insertAbove, {
  706. label: 'Insert Cell Above',
  707. execute: args => {
  708. const current = getCurrent(args);
  709. if (current) {
  710. return NotebookActions.insertAbove(current.notebook);
  711. }
  712. },
  713. isEnabled
  714. });
  715. commands.addCommand(CommandIDs.insertBelow, {
  716. label: 'Insert Cell Below',
  717. execute: args => {
  718. const current = getCurrent(args);
  719. if (current) {
  720. return NotebookActions.insertBelow(current.notebook);
  721. }
  722. },
  723. isEnabled
  724. });
  725. commands.addCommand(CommandIDs.selectAbove, {
  726. label: 'Select Cell Above',
  727. execute: args => {
  728. const current = getCurrent(args);
  729. if (current) {
  730. return NotebookActions.selectAbove(current.notebook);
  731. }
  732. },
  733. isEnabled
  734. });
  735. commands.addCommand(CommandIDs.selectBelow, {
  736. label: 'Select Cell Below',
  737. execute: args => {
  738. const current = getCurrent(args);
  739. if (current) {
  740. return NotebookActions.selectBelow(current.notebook);
  741. }
  742. },
  743. isEnabled
  744. });
  745. commands.addCommand(CommandIDs.extendAbove, {
  746. label: 'Extend Selection Above',
  747. execute: args => {
  748. const current = getCurrent(args);
  749. if (current) {
  750. return NotebookActions.extendSelectionAbove(current.notebook);
  751. }
  752. },
  753. isEnabled
  754. });
  755. commands.addCommand(CommandIDs.extendBelow, {
  756. label: 'Extend Selection Below',
  757. execute: args => {
  758. const current = getCurrent(args);
  759. if (current) {
  760. return NotebookActions.extendSelectionBelow(current.notebook);
  761. }
  762. },
  763. isEnabled
  764. });
  765. commands.addCommand(CommandIDs.moveUp, {
  766. label: 'Move Cell(s) Up',
  767. execute: args => {
  768. const current = getCurrent(args);
  769. if (current) {
  770. return NotebookActions.moveUp(current.notebook);
  771. }
  772. },
  773. isEnabled
  774. });
  775. commands.addCommand(CommandIDs.moveDown, {
  776. label: 'Move Cell(s) Down',
  777. execute: args => {
  778. const current = getCurrent(args);
  779. if (current) {
  780. return NotebookActions.moveDown(current.notebook);
  781. }
  782. },
  783. isEnabled
  784. });
  785. commands.addCommand(CommandIDs.toggleAllLines, {
  786. label: 'Toggle All Line Numbers',
  787. execute: args => {
  788. const current = getCurrent(args);
  789. if (current) {
  790. return NotebookActions.toggleAllLineNumbers(current.notebook);
  791. }
  792. },
  793. isEnabled
  794. });
  795. commands.addCommand(CommandIDs.commandMode, {
  796. label: 'Enter Command Mode',
  797. execute: args => {
  798. const current = getCurrent(args);
  799. if (current) {
  800. current.notebook.mode = 'command';
  801. }
  802. },
  803. isEnabled
  804. });
  805. commands.addCommand(CommandIDs.editMode, {
  806. label: 'Enter Edit Mode',
  807. execute: args => {
  808. const current = getCurrent(args);
  809. if (current) {
  810. current.notebook.mode = 'edit';
  811. }
  812. },
  813. isEnabled
  814. });
  815. commands.addCommand(CommandIDs.undoCellAction, {
  816. label: 'Undo Cell Operation',
  817. execute: args => {
  818. const current = getCurrent(args);
  819. if (current) {
  820. return NotebookActions.undo(current.notebook);
  821. }
  822. },
  823. isEnabled
  824. });
  825. commands.addCommand(CommandIDs.redoCellAction, {
  826. label: 'Redo Cell Operation',
  827. execute: args => {
  828. const current = getCurrent(args);
  829. if (!current) {
  830. return NotebookActions.redo(current.notebook);
  831. }
  832. },
  833. isEnabled
  834. });
  835. commands.addCommand(CommandIDs.changeKernel, {
  836. label: 'Change Kernel',
  837. execute: args => {
  838. const current = getCurrent(args);
  839. if (current) {
  840. return current.context.session.selectKernel();
  841. }
  842. },
  843. isEnabled
  844. });
  845. commands.addCommand(CommandIDs.reconnectToKernel, {
  846. label: 'Reconnect To Kernel',
  847. execute: args => {
  848. const current = getCurrent(args);
  849. if (!current) {
  850. return;
  851. }
  852. const kernel = current.context.session.kernel;
  853. if (kernel) {
  854. return kernel.reconnect();
  855. }
  856. },
  857. isEnabled
  858. });
  859. commands.addCommand(CommandIDs.createCellView, {
  860. label: 'Create New View for Cell',
  861. execute: args => {
  862. const current = getCurrent(args);
  863. const nb = current.notebook;
  864. const newCell = nb.activeCell.clone();
  865. const CellPanel = class extends Panel {
  866. protected onCloseRequest(msg: Message): void {
  867. this.dispose();
  868. }
  869. };
  870. const p = new CellPanel();
  871. p.id = `Cell-${uuid()}`;
  872. p.title.closable = true;
  873. p.title.label = current.title.label ? `Cell: ${current.title.label}` : 'Cell';
  874. p.addWidget(newCell);
  875. shell.addToMainArea(p);
  876. },
  877. isEnabled
  878. });
  879. commands.addCommand(CommandIDs.createConsole, {
  880. label: 'Create Console for Notebook',
  881. execute: args => {
  882. const current = getCurrent(args);
  883. const widget = tracker.currentWidget;
  884. if (!current || !widget) {
  885. return;
  886. }
  887. const options: ReadonlyJSONObject = {
  888. path: widget.context.path,
  889. preferredLanguage: widget.context.model.defaultKernelLanguage,
  890. activate: args['activate']
  891. };
  892. return commands.execute('console:create', options);
  893. },
  894. isEnabled
  895. });
  896. commands.addCommand(CommandIDs.markdown1, {
  897. label: 'Change to Heading 1',
  898. execute: args => {
  899. const current = getCurrent(args);
  900. if (current) {
  901. return NotebookActions.setMarkdownHeader(current.notebook, 1);
  902. }
  903. },
  904. isEnabled
  905. });
  906. commands.addCommand(CommandIDs.markdown2, {
  907. label: 'Change to Heading 2',
  908. execute: args => {
  909. const current = getCurrent(args);
  910. if (current) {
  911. return NotebookActions.setMarkdownHeader(current.notebook, 2);
  912. }
  913. },
  914. isEnabled
  915. });
  916. commands.addCommand(CommandIDs.markdown3, {
  917. label: 'Change to Heading 3',
  918. execute: args => {
  919. const current = getCurrent(args);
  920. if (current) {
  921. return NotebookActions.setMarkdownHeader(current.notebook, 3);
  922. }
  923. },
  924. isEnabled
  925. });
  926. commands.addCommand(CommandIDs.markdown4, {
  927. label: 'Change to Heading 4',
  928. execute: args => {
  929. const current = getCurrent(args);
  930. if (current) {
  931. return NotebookActions.setMarkdownHeader(current.notebook, 4);
  932. }
  933. },
  934. isEnabled
  935. });
  936. commands.addCommand(CommandIDs.markdown5, {
  937. label: 'Change to Heading 5',
  938. execute: args => {
  939. const current = getCurrent(args);
  940. if (current) {
  941. return NotebookActions.setMarkdownHeader(current.notebook, 5);
  942. }
  943. },
  944. isEnabled
  945. });
  946. commands.addCommand(CommandIDs.markdown6, {
  947. label: 'Change to Heading 6',
  948. execute: args => {
  949. const current = getCurrent(args);
  950. if (current) {
  951. return NotebookActions.setMarkdownHeader(current.notebook, 6);
  952. }
  953. },
  954. isEnabled
  955. });
  956. commands.addCommand(CommandIDs.hideCode, {
  957. label: 'Hide Code',
  958. execute: args => {
  959. const current = getCurrent(args);
  960. if (current) {
  961. return NotebookActions.hideCode(current.notebook);
  962. }
  963. },
  964. isEnabled
  965. });
  966. commands.addCommand(CommandIDs.showCode, {
  967. label: 'Show Code',
  968. execute: args => {
  969. const current = getCurrent(args);
  970. if (current) {
  971. return NotebookActions.showCode(current.notebook);
  972. }
  973. },
  974. isEnabled
  975. });
  976. commands.addCommand(CommandIDs.hideAllCode, {
  977. label: 'Hide All Code',
  978. execute: args => {
  979. const current = getCurrent(args);
  980. if (current) {
  981. return NotebookActions.hideAllCode(current.notebook);
  982. }
  983. },
  984. isEnabled
  985. });
  986. commands.addCommand(CommandIDs.showAllCode, {
  987. label: 'Show All Code',
  988. execute: args => {
  989. const current = getCurrent(args);
  990. if (current) {
  991. return NotebookActions.showAllCode(current.notebook);
  992. }
  993. },
  994. isEnabled
  995. });
  996. commands.addCommand(CommandIDs.hideOutput, {
  997. label: 'Hide Output',
  998. execute: args => {
  999. const current = getCurrent(args);
  1000. if (current) {
  1001. return NotebookActions.hideOutput(current.notebook);
  1002. }
  1003. },
  1004. isEnabled
  1005. });
  1006. commands.addCommand(CommandIDs.showOutput, {
  1007. label: 'Show Output',
  1008. execute: args => {
  1009. const current = getCurrent(args);
  1010. if (current) {
  1011. return NotebookActions.showOutput(current.notebook);
  1012. }
  1013. },
  1014. isEnabled
  1015. });
  1016. commands.addCommand(CommandIDs.hideAllOutputs, {
  1017. label: 'Hide All Outputs',
  1018. execute: args => {
  1019. const current = getCurrent(args);
  1020. if (current) {
  1021. return NotebookActions.hideAllOutputs(current.notebook);
  1022. }
  1023. },
  1024. isEnabled
  1025. });
  1026. commands.addCommand(CommandIDs.showAllOutputs, {
  1027. label: 'Show All Outputs',
  1028. execute: args => {
  1029. const current = getCurrent(args);
  1030. if (current) {
  1031. return NotebookActions.showAllOutputs(current.notebook);
  1032. }
  1033. },
  1034. isEnabled
  1035. });
  1036. }
  1037. /**
  1038. * Populate the application's command palette with notebook commands.
  1039. */
  1040. function populatePalette(palette: ICommandPalette): void {
  1041. let category = 'Notebook Operations';
  1042. [
  1043. CommandIDs.interrupt,
  1044. CommandIDs.restart,
  1045. CommandIDs.restartClear,
  1046. CommandIDs.restartRunAll,
  1047. CommandIDs.runAll,
  1048. CommandIDs.clearAllOutputs,
  1049. CommandIDs.toggleAllLines,
  1050. CommandIDs.editMode,
  1051. CommandIDs.commandMode,
  1052. CommandIDs.changeKernel,
  1053. CommandIDs.reconnectToKernel,
  1054. CommandIDs.createConsole,
  1055. CommandIDs.closeAndShutdown,
  1056. CommandIDs.trust
  1057. ].forEach(command => { palette.addItem({ command, category }); });
  1058. EXPORT_TO_FORMATS.forEach(exportToFormat => {
  1059. let args = { 'format': exportToFormat['format'], 'label': exportToFormat['label'], 'isPalette': true };
  1060. palette.addItem({ command: CommandIDs.exportToFormat, category: category, args: args });
  1061. });
  1062. category = 'Notebook Cell Operations';
  1063. [
  1064. CommandIDs.run,
  1065. CommandIDs.runAndAdvance,
  1066. CommandIDs.runAndInsert,
  1067. CommandIDs.clearOutputs,
  1068. CommandIDs.toCode,
  1069. CommandIDs.toMarkdown,
  1070. CommandIDs.toRaw,
  1071. CommandIDs.cut,
  1072. CommandIDs.copy,
  1073. CommandIDs.paste,
  1074. CommandIDs.deleteCell,
  1075. CommandIDs.split,
  1076. CommandIDs.merge,
  1077. CommandIDs.insertAbove,
  1078. CommandIDs.insertBelow,
  1079. CommandIDs.selectAbove,
  1080. CommandIDs.selectBelow,
  1081. CommandIDs.extendAbove,
  1082. CommandIDs.extendBelow,
  1083. CommandIDs.moveDown,
  1084. CommandIDs.moveUp,
  1085. CommandIDs.undoCellAction,
  1086. CommandIDs.redoCellAction,
  1087. CommandIDs.markdown1,
  1088. CommandIDs.markdown2,
  1089. CommandIDs.markdown3,
  1090. CommandIDs.markdown4,
  1091. CommandIDs.markdown5,
  1092. CommandIDs.markdown6,
  1093. CommandIDs.hideCode,
  1094. CommandIDs.showCode,
  1095. CommandIDs.hideAllCode,
  1096. CommandIDs.showAllCode,
  1097. CommandIDs.hideOutput,
  1098. CommandIDs.showOutput,
  1099. CommandIDs.hideAllOutputs,
  1100. CommandIDs.showAllOutputs,
  1101. ].forEach(command => { palette.addItem({ command, category }); });
  1102. }
  1103. /**
  1104. * Populates the application menus for the notebook.
  1105. */
  1106. function populateMenus(app: JupyterLab, mainMenu: IMainMenu, tracker: INotebookTracker): void {
  1107. let { commands } = app;
  1108. // Add undo/redo hooks to the edit menu.
  1109. mainMenu.editMenu.undoers.set('Notebook', {
  1110. tracker,
  1111. undo: widget => { widget.notebook.activeCell.editor.undo(); },
  1112. redo: widget => { widget.notebook.activeCell.editor.redo(); }
  1113. } as IEditMenu.IUndoer<NotebookPanel>);
  1114. // Add editor view options.
  1115. // Add a clearer to the edit menu
  1116. mainMenu.editMenu.clearers.set('Notebook', {
  1117. tracker,
  1118. noun: 'All Cell Outputs',
  1119. clear: (current: NotebookPanel) => {
  1120. return NotebookActions.clearAllOutputs(current.notebook);
  1121. }
  1122. } as IEditMenu.IClearer<NotebookPanel>);
  1123. // Add new notebook creation to the file menu.
  1124. mainMenu.fileMenu.newMenu.addItem({ command: CommandIDs.createNew });
  1125. // Add a close and shutdown command to the file menu.
  1126. mainMenu.fileMenu.closeAndCleaners.set('Notebook', {
  1127. tracker,
  1128. action: 'Shutdown',
  1129. closeAndCleanup: (current: NotebookPanel) => {
  1130. const fileName = current.title.label;
  1131. return showDialog({
  1132. title: 'Shutdown the notebook?',
  1133. body: `Are you sure you want to close "${fileName}"?`,
  1134. buttons: [Dialog.cancelButton(), Dialog.warnButton()]
  1135. }).then(result => {
  1136. if (result.button.accept) {
  1137. return current.context.session.shutdown()
  1138. .then(() => { current.dispose(); });
  1139. }
  1140. });
  1141. }
  1142. } as IFileMenu.ICloseAndCleaner<NotebookPanel>);
  1143. // Add a notebook group to the File menu.
  1144. let exportTo = new Menu({ commands } );
  1145. exportTo.title.label = 'Export to ...';
  1146. EXPORT_TO_FORMATS.forEach(exportToFormat => {
  1147. exportTo.addItem({ command: CommandIDs.exportToFormat, args: exportToFormat });
  1148. });
  1149. const fileGroup = [
  1150. { command: CommandIDs.trust },
  1151. { type: 'submenu', submenu: exportTo } as Menu.IItemOptions
  1152. ];
  1153. mainMenu.fileMenu.addGroup(fileGroup, 10);
  1154. // Add a kernel user to the Kernel menu
  1155. mainMenu.kernelMenu.kernelUsers.set('Notebook', {
  1156. tracker,
  1157. interruptKernel: current => {
  1158. let kernel = current.session.kernel;
  1159. if (kernel) {
  1160. return kernel.interrupt();
  1161. }
  1162. return Promise.resolve(void 0);
  1163. },
  1164. restartKernel: current => current.session.restart(),
  1165. changeKernel: current => current.session.selectKernel(),
  1166. shutdownKernel: current => current.session.shutdown(),
  1167. } as IKernelMenu.IKernelUser<NotebookPanel>);
  1168. // Add a console creator the the Kernel menu
  1169. mainMenu.kernelMenu.consoleCreators.set('Notebook', {
  1170. tracker,
  1171. createConsole: current => {
  1172. const options: ReadonlyJSONObject = {
  1173. path: current.context.path,
  1174. preferredLanguage: current.context.model.defaultKernelLanguage
  1175. };
  1176. return commands.execute('console:create', options);
  1177. }
  1178. } as IKernelMenu.IConsoleCreator<NotebookPanel>);
  1179. // Add some commands to the application view menu.
  1180. const viewGroup = [
  1181. CommandIDs.hideAllCode,
  1182. CommandIDs.showAllCode,
  1183. CommandIDs.hideAllOutputs,
  1184. CommandIDs.showAllOutputs
  1185. ].map(command => { return { command }; });
  1186. mainMenu.viewMenu.addGroup(viewGroup, 10);
  1187. // Add an IEditorViewer to the application view menu
  1188. mainMenu.viewMenu.editorViewers.set('Notebook', {
  1189. tracker,
  1190. toggleLineNumbers: widget => {
  1191. NotebookActions.toggleAllLineNumbers(widget.notebook);
  1192. },
  1193. toggleMatchBrackets: widget => {
  1194. NotebookActions.toggleAllMatchBrackets(widget.notebook);
  1195. },
  1196. lineNumbersToggled: widget =>
  1197. widget.notebook.activeCell.editor.getOption('lineNumbers'),
  1198. matchBracketsToggled: widget =>
  1199. widget.notebook.activeCell.editor.getOption('matchBrackets'),
  1200. } as IViewMenu.IEditorViewer<NotebookPanel>);
  1201. // Add an ICodeRunner to the application run menu
  1202. mainMenu.runMenu.codeRunners.set('Notebook', {
  1203. tracker,
  1204. noun: 'Cell',
  1205. run: current => {
  1206. const { context, notebook } = current;
  1207. return NotebookActions.runAndAdvance(notebook, context.session)
  1208. .then(() => void 0);
  1209. },
  1210. runAll: current => {
  1211. const { context, notebook } = current;
  1212. return NotebookActions.runAll(notebook, context.session)
  1213. .then(() => void 0);
  1214. },
  1215. runAbove: current => {
  1216. const { context, notebook } = current;
  1217. return NotebookActions.runAllAbove(notebook, context.session)
  1218. .then(() => void 0);
  1219. },
  1220. runBelow: current => {
  1221. const { context, notebook } = current;
  1222. return NotebookActions.runAllBelow(notebook, context.session)
  1223. .then(() => void 0);
  1224. }
  1225. } as IRunMenu.ICodeRunner<NotebookPanel>);
  1226. // Add commands to the application edit menu.
  1227. const undoCellActionGroup = [
  1228. CommandIDs.undoCellAction,
  1229. CommandIDs.redoCellAction
  1230. ].map(command => { return { command }; });
  1231. const editGroup = [
  1232. CommandIDs.cut,
  1233. CommandIDs.copy,
  1234. CommandIDs.paste,
  1235. CommandIDs.deleteCell,
  1236. CommandIDs.split,
  1237. CommandIDs.merge
  1238. ].map(command => { return { command }; });
  1239. mainMenu.editMenu.addGroup(undoCellActionGroup, 4);
  1240. mainMenu.editMenu.addGroup(editGroup, 5);
  1241. }