index.ts 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832
  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, MainAreaWidget, showDialog
  8. } from '@jupyterlab/apputils';
  9. import {
  10. CodeCell
  11. } from '@jupyterlab/cells';
  12. import {
  13. CodeEditor, IEditorServices
  14. } from '@jupyterlab/codeeditor';
  15. import {
  16. ISettingRegistry,
  17. IStateDB,
  18. PageConfig,
  19. URLExt
  20. } from '@jupyterlab/coreutils';
  21. import { UUID } from '@phosphor/coreutils';
  22. import {
  23. IFileBrowserFactory
  24. } from '@jupyterlab/filebrowser';
  25. import {
  26. ILauncher
  27. } from '@jupyterlab/launcher';
  28. import {
  29. IMainMenu, IEditMenu, IFileMenu, IHelpMenu, IKernelMenu, IRunMenu, IViewMenu
  30. } from '@jupyterlab/mainmenu';
  31. import {
  32. CellTools, ICellTools, INotebookTracker, NotebookActions,
  33. NotebookModelFactory, NotebookPanel, NotebookTracker, NotebookWidgetFactory,
  34. StaticNotebook
  35. } from '@jupyterlab/notebook';
  36. import {
  37. IRenderMimeRegistry
  38. } from '@jupyterlab/rendermime';
  39. import {
  40. ServiceManager
  41. } from '@jupyterlab/services';
  42. import {
  43. ReadonlyJSONObject
  44. } from '@phosphor/coreutils';
  45. import {
  46. Message, MessageLoop
  47. } from '@phosphor/messaging';
  48. import {
  49. Menu
  50. } from '@phosphor/widgets';
  51. /**
  52. * The command IDs used by the notebook plugin.
  53. */
  54. namespace CommandIDs {
  55. export
  56. const createNew = 'notebook:create-new';
  57. export
  58. const interrupt = 'notebook:interrupt-kernel';
  59. export
  60. const restart = 'notebook:restart-kernel';
  61. export
  62. const restartClear = 'notebook:restart-clear-output';
  63. export
  64. const restartRunAll = 'notebook:restart-run-all';
  65. export
  66. const reconnectToKernel = 'notebook:reconnect-to-kernel';
  67. export
  68. const changeKernel = 'notebook:change-kernel';
  69. export
  70. const createConsole = 'notebook:create-console';
  71. export
  72. const createOutputView = 'notebook:create-output-view';
  73. export
  74. const clearAllOutputs = 'notebook:clear-all-cell-outputs';
  75. export
  76. const closeAndShutdown = 'notebook:close-and-shutdown';
  77. export
  78. const trust = 'notebook:trust';
  79. export
  80. const exportToFormat = 'notebook:export-to-format';
  81. export
  82. const run = 'notebook:run-cell';
  83. export
  84. const runAndAdvance = 'notebook:run-cell-and-select-next';
  85. export
  86. const runAndInsert = 'notebook:run-cell-and-insert-below';
  87. export
  88. const runAll = 'notebook:run-all-cells';
  89. export
  90. const runAllAbove = 'notebook:run-all-above';
  91. export
  92. const runAllBelow = 'notebook:run-all-below';
  93. export
  94. const toCode = 'notebook:change-cell-to-code';
  95. export
  96. const toMarkdown = 'notebook:change-cell-to-markdown';
  97. export
  98. const toRaw = 'notebook:change-cell-to-raw';
  99. export
  100. const cut = 'notebook:cut-cell';
  101. export
  102. const copy = 'notebook:copy-cell';
  103. export
  104. const pasteAbove = 'notebook:paste-cell-above';
  105. export
  106. const pasteBelow = 'notebook:paste-cell-below';
  107. export
  108. const pasteAndReplace = 'notebook:paste-and-replace-cell';
  109. export
  110. const moveUp = 'notebook:move-cell-up';
  111. export
  112. const moveDown = 'notebook:move-cell-down';
  113. export
  114. const clearOutputs = 'notebook:clear-cell-output';
  115. export
  116. const deleteCell = 'notebook:delete-cell';
  117. export
  118. const insertAbove = 'notebook:insert-cell-above';
  119. export
  120. const insertBelow = 'notebook:insert-cell-below';
  121. export
  122. const selectAbove = 'notebook:move-cursor-up';
  123. export
  124. const selectBelow = 'notebook:move-cursor-down';
  125. export
  126. const extendAbove = 'notebook:extend-marked-cells-above';
  127. export
  128. const extendBelow = 'notebook:extend-marked-cells-below';
  129. export
  130. const selectAll = 'notebook:select-all';
  131. export
  132. const deselectAll = 'notebook:deselect-all';
  133. export
  134. const editMode = 'notebook:enter-edit-mode';
  135. export
  136. const merge = 'notebook:merge-cells';
  137. export
  138. const split = 'notebook:split-cell-at-cursor';
  139. export
  140. const commandMode = 'notebook:enter-command-mode';
  141. export
  142. const toggleAllLines = 'notebook:toggle-all-cell-line-numbers';
  143. export
  144. const undoCellAction = 'notebook:undo-cell-action';
  145. export
  146. const redoCellAction = 'notebook:redo-cell-action';
  147. export
  148. const markdown1 = 'notebook:change-cell-to-heading-1';
  149. export
  150. const markdown2 = 'notebook:change-cell-to-heading-2';
  151. export
  152. const markdown3 = 'notebook:change-cell-to-heading-3';
  153. export
  154. const markdown4 = 'notebook:change-cell-to-heading-4';
  155. export
  156. const markdown5 = 'notebook:change-cell-to-heading-5';
  157. export
  158. const markdown6 = 'notebook:change-cell-to-heading-6';
  159. export
  160. const hideCode = 'notebook:hide-cell-code';
  161. export
  162. const showCode = 'notebook:show-cell-code';
  163. export
  164. const hideAllCode = 'notebook:hide-all-cell-code';
  165. export
  166. const showAllCode = 'notebook:show-all-cell-code';
  167. export
  168. const hideOutput = 'notebook:hide-cell-outputs';
  169. export
  170. const showOutput = 'notebook:show-cell-outputs';
  171. export
  172. const hideAllOutputs = 'notebook:hide-all-cell-outputs';
  173. export
  174. const showAllOutputs = 'notebook:show-all-cell-outputs';
  175. export
  176. const enableOutputScrolling = 'notebook:enable-output-scrolling';
  177. export
  178. const disableOutputScrolling = 'notebook:disable-output-scrolling';
  179. export
  180. const saveWithView = 'notebook:save-with-view';
  181. }
  182. /**
  183. * The class name for the notebook icon from the default theme.
  184. */
  185. const NOTEBOOK_ICON_CLASS = 'jp-NotebookIcon';
  186. /**
  187. * The name of the factory that creates notebooks.
  188. */
  189. const FACTORY = 'Notebook';
  190. /**
  191. * The allowed Export To ... formats and their human readable labels.
  192. */
  193. const EXPORT_TO_FORMATS = [
  194. { 'format': 'html', 'label': 'HTML' },
  195. { 'format': 'latex', 'label': 'LaTeX' },
  196. { 'format': 'markdown', 'label': 'Markdown' },
  197. { 'format': 'pdf', 'label': 'PDF' },
  198. { 'format': 'rst', 'label': 'ReStructured Text' },
  199. { 'format': 'script', 'label': 'Executable Script' },
  200. { 'format': 'slides', 'label': 'Reveal.js Slides' }
  201. ];
  202. /**
  203. * The notebook widget tracker provider.
  204. */
  205. const trackerPlugin: JupyterLabPlugin<INotebookTracker> = {
  206. id: '@jupyterlab/notebook-extension:tracker',
  207. provides: INotebookTracker,
  208. requires: [
  209. IMainMenu,
  210. ICommandPalette,
  211. NotebookPanel.IContentFactory,
  212. IEditorServices,
  213. ILayoutRestorer,
  214. IRenderMimeRegistry,
  215. ISettingRegistry
  216. ],
  217. optional: [IFileBrowserFactory, ILauncher],
  218. activate: activateNotebookHandler,
  219. autoStart: true
  220. };
  221. /**
  222. * The notebook cell factory provider.
  223. */
  224. const factory: JupyterLabPlugin<NotebookPanel.IContentFactory> = {
  225. id: '@jupyterlab/notebook-extension:factory',
  226. provides: NotebookPanel.IContentFactory,
  227. requires: [IEditorServices],
  228. autoStart: true,
  229. activate: (app: JupyterLab, editorServices: IEditorServices) => {
  230. let editorFactory = editorServices.factoryService.newInlineEditor;
  231. return new NotebookPanel.ContentFactory({ editorFactory });
  232. }
  233. };
  234. /**
  235. * The cell tools extension.
  236. */
  237. const tools: JupyterLabPlugin<ICellTools> = {
  238. activate: activateCellTools,
  239. provides: ICellTools,
  240. id: '@jupyterlab/notebook-extension:tools',
  241. autoStart: true,
  242. requires: [INotebookTracker, IEditorServices, IStateDB]
  243. };
  244. /**
  245. * Export the plugins as default.
  246. */
  247. const plugins: JupyterLabPlugin<any>[] = [factory, trackerPlugin, tools];
  248. export default plugins;
  249. /**
  250. * Activate the cell tools extension.
  251. */
  252. function activateCellTools(app: JupyterLab, tracker: INotebookTracker, editorServices: IEditorServices, state: IStateDB): Promise<ICellTools> {
  253. const id = 'cell-tools';
  254. const celltools = new CellTools({ tracker });
  255. const activeCellTool = new CellTools.ActiveCellTool();
  256. const slideShow = CellTools.createSlideShowSelector();
  257. const nbConvert = CellTools.createNBConvertSelector();
  258. const editorFactory = editorServices.factoryService.newInlineEditor;
  259. const metadataEditor = new CellTools.MetadataEditorTool({ editorFactory });
  260. // Create message hook for triggers to save to the database.
  261. const hook = (sender: any, message: Message): boolean => {
  262. switch (message.type) {
  263. case 'activate-request':
  264. state.save(id, { open: true });
  265. break;
  266. case 'after-hide':
  267. case 'close-request':
  268. state.remove(id);
  269. break;
  270. default:
  271. break;
  272. }
  273. return true;
  274. };
  275. celltools.title.label = 'Cell Tools';
  276. celltools.id = id;
  277. celltools.addItem({ tool: activeCellTool, rank: 1 });
  278. celltools.addItem({ tool: slideShow, rank: 2 });
  279. celltools.addItem({ tool: nbConvert, rank: 3 });
  280. celltools.addItem({ tool: metadataEditor, rank: 4 });
  281. MessageLoop.installMessageHook(celltools, hook);
  282. // Wait until the application has finished restoring before rendering.
  283. Promise.all([state.fetch(id), app.restored]).then(([args]) => {
  284. const open = !!(args && (args as ReadonlyJSONObject)['open'] as boolean);
  285. // After initial restoration, check if the cell tools should render.
  286. if (tracker.size) {
  287. app.shell.addToLeftArea(celltools);
  288. if (open) {
  289. app.shell.activateById(celltools.id);
  290. }
  291. }
  292. // For all subsequent widget changes, check if the cell tools should render.
  293. app.shell.currentChanged.connect((sender, args) => {
  294. // If there are any open notebooks, add cell tools to the side panel if
  295. // it is not already there.
  296. if (tracker.size) {
  297. if (!celltools.isAttached) {
  298. app.shell.addToLeftArea(celltools);
  299. }
  300. return;
  301. }
  302. // If there are no notebooks, close cell tools.
  303. celltools.close();
  304. });
  305. });
  306. return Promise.resolve(celltools);
  307. }
  308. /**
  309. * Activate the notebook handler extension.
  310. */
  311. function activateNotebookHandler(app: JupyterLab, mainMenu: IMainMenu, palette: ICommandPalette, contentFactory: NotebookPanel.IContentFactory, editorServices: IEditorServices, restorer: ILayoutRestorer, rendermime: IRenderMimeRegistry, settingRegistry: ISettingRegistry, browserFactory: IFileBrowserFactory | null, launcher: ILauncher | null): INotebookTracker {
  312. const services = app.serviceManager;
  313. // An object for tracking the current notebook settings.
  314. let editorConfig = StaticNotebook.defaultEditorConfig;
  315. const factory = new NotebookWidgetFactory({
  316. name: FACTORY,
  317. fileTypes: ['notebook'],
  318. modelName: 'notebook',
  319. defaultFor: ['notebook'],
  320. preferKernel: true,
  321. canStartKernel: true,
  322. rendermime: rendermime,
  323. contentFactory,
  324. editorConfig,
  325. mimeTypeService: editorServices.mimeTypeService
  326. });
  327. const { commands, restored } = app;
  328. const tracker = new NotebookTracker({ namespace: 'notebook' });
  329. // Handle state restoration.
  330. restorer.restore(tracker, {
  331. command: 'docmanager:open',
  332. args: panel => ({ path: panel.context.path, factory: FACTORY }),
  333. name: panel => panel.context.path,
  334. when: services.ready
  335. });
  336. let registry = app.docRegistry;
  337. registry.addModelFactory(new NotebookModelFactory({}));
  338. registry.addWidgetFactory(factory);
  339. addCommands(app, services, tracker);
  340. populatePalette(palette);
  341. let id = 0; // The ID counter for notebook panels.
  342. factory.widgetCreated.connect((sender, widget) => {
  343. // If the notebook panel does not have an ID, assign it one.
  344. widget.id = widget.id || `notebook-${++id}`;
  345. widget.title.icon = NOTEBOOK_ICON_CLASS;
  346. // Notify the instance tracker if restore data needs to update.
  347. widget.context.pathChanged.connect(() => { tracker.save(widget); });
  348. // Add the notebook panel to the tracker.
  349. tracker.add(widget);
  350. });
  351. /**
  352. * Update the setting values.
  353. */
  354. function updateConfig(settings: ISettingRegistry.ISettings): void {
  355. let cached =
  356. settings.get('codeCellConfig').composite as Partial<CodeEditor.IConfig>;
  357. let code = { ...StaticNotebook.defaultEditorConfig.code };
  358. Object.keys(code).forEach((key: keyof CodeEditor.IConfig) => {
  359. code[key] = (cached[key] === null || cached[key] === undefined)
  360. ? code[key]
  361. : cached[key];
  362. });
  363. cached =
  364. settings.get('markdownCellConfig').composite as Partial<CodeEditor.IConfig>;
  365. let markdown = { ...StaticNotebook.defaultEditorConfig.markdown };
  366. Object.keys(markdown).forEach((key: keyof CodeEditor.IConfig) => {
  367. markdown[key] = (cached[key] === null || cached[key] === undefined)
  368. ? markdown[key]
  369. : cached[key];
  370. });
  371. cached =
  372. settings.get('rawCellConfig').composite as Partial<CodeEditor.IConfig>;
  373. let raw = { ...StaticNotebook.defaultEditorConfig.raw };
  374. Object.keys(raw).forEach((key: keyof CodeEditor.IConfig) => {
  375. raw[key] = (cached[key] === null || cached[key] === undefined)
  376. ? raw[key]
  377. : cached[key];
  378. });
  379. factory.editorConfig = editorConfig = { code, markdown, raw };
  380. }
  381. /**
  382. * Update the settings of the current tracker instances.
  383. */
  384. function updateTracker(): void {
  385. tracker.forEach(widget => { widget.content.editorConfig = editorConfig; });
  386. }
  387. // Fetch the initial state of the settings.
  388. Promise.all([settingRegistry.load(trackerPlugin.id), restored]).then(([settings]) => {
  389. updateConfig(settings);
  390. updateTracker();
  391. settings.changed.connect(() => {
  392. updateConfig(settings);
  393. updateTracker();
  394. });
  395. }).catch((reason: Error) => {
  396. console.error(reason.message);
  397. updateTracker();
  398. });
  399. // Add main menu notebook menu.
  400. populateMenus(app, mainMenu, tracker);
  401. // Utility function to create a new notebook.
  402. const createNew = (cwd: string, kernelName?: string) => {
  403. return commands.execute(
  404. 'docmanager:new-untitled', { path: cwd, type: 'notebook' }
  405. ).then(model => {
  406. return commands.execute('docmanager:open', {
  407. path: model.path, factory: FACTORY,
  408. kernel: { name: kernelName }
  409. });
  410. });
  411. };
  412. // Add a command for creating a new notebook.
  413. commands.addCommand(CommandIDs.createNew, {
  414. label: args => {
  415. const kernelName = args['kernelName'] as string || '';
  416. if (args['isLauncher'] && args['kernelName']) {
  417. return services.specs.kernelspecs[kernelName].display_name;
  418. }
  419. return 'Notebook';
  420. },
  421. caption: 'Create a new notebook',
  422. iconClass: args => args['isLauncher'] ? '' : 'jp-NotebookIcon',
  423. execute: args => {
  424. const cwd = args['cwd'] || browserFactory ?
  425. browserFactory.defaultBrowser.model.path : '';
  426. const kernelName = args['kernelName'] as string || '';
  427. return createNew(cwd, kernelName);
  428. }
  429. });
  430. // Add a launcher item if the launcher is available.
  431. if (launcher) {
  432. services.ready.then(() => {
  433. const specs = services.specs;
  434. const baseUrl = PageConfig.getBaseUrl();
  435. for (let name in specs.kernelspecs) {
  436. let rank = name === specs.default ? 0 : Infinity;
  437. let kernelIconUrl = specs.kernelspecs[name].resources['logo-64x64'];
  438. if (kernelIconUrl) {
  439. let index = kernelIconUrl.indexOf('kernelspecs');
  440. kernelIconUrl = baseUrl + kernelIconUrl.slice(index);
  441. }
  442. launcher.add({
  443. command: CommandIDs.createNew,
  444. args: { isLauncher: true, kernelName: name },
  445. category: 'Notebook',
  446. rank,
  447. kernelIconUrl
  448. });
  449. }
  450. });
  451. }
  452. // Cell context menu groups
  453. app.contextMenu.addItem({
  454. type: 'separator',
  455. selector: '.jp-Notebook .jp-Cell',
  456. rank: 0
  457. });
  458. app.contextMenu.addItem({
  459. command: CommandIDs.cut,
  460. selector: '.jp-Notebook .jp-Cell',
  461. rank: 1
  462. });
  463. app.contextMenu.addItem({
  464. command: CommandIDs.copy,
  465. selector: '.jp-Notebook .jp-Cell',
  466. rank: 2
  467. });
  468. app.contextMenu.addItem({
  469. command: CommandIDs.pasteBelow,
  470. selector: '.jp-Notebook .jp-Cell',
  471. rank: 3
  472. });
  473. app.contextMenu.addItem({
  474. type: 'separator',
  475. selector: '.jp-Notebook .jp-Cell',
  476. rank: 4
  477. });
  478. app.contextMenu.addItem({
  479. command: CommandIDs.deleteCell,
  480. selector: '.jp-Notebook .jp-Cell',
  481. rank: 5
  482. });
  483. app.contextMenu.addItem({
  484. type: 'separator',
  485. selector: '.jp-Notebook .jp-Cell',
  486. rank: 6
  487. });
  488. app.contextMenu.addItem({
  489. command: CommandIDs.split,
  490. selector: '.jp-Notebook .jp-Cell',
  491. rank: 7
  492. });
  493. app.contextMenu.addItem({
  494. type: 'separator',
  495. selector: '.jp-Notebook .jp-Cell',
  496. rank: 8
  497. });
  498. // CodeCell context menu groups
  499. app.contextMenu.addItem({
  500. command: CommandIDs.createOutputView,
  501. selector: '.jp-Notebook .jp-CodeCell',
  502. rank: 9
  503. });
  504. app.contextMenu.addItem({
  505. type: 'separator',
  506. selector: '.jp-Notebook .jp-CodeCell',
  507. rank: 10
  508. });
  509. app.contextMenu.addItem({
  510. command: CommandIDs.clearOutputs,
  511. selector: '.jp-Notebook .jp-CodeCell',
  512. rank: 11
  513. });
  514. // Notebook context menu groups
  515. app.contextMenu.addItem({
  516. command: CommandIDs.clearAllOutputs,
  517. selector: '.jp-Notebook',
  518. rank: 0
  519. });
  520. app.contextMenu.addItem({
  521. type: 'separator',
  522. selector: '.jp-Notebook',
  523. rank: 1
  524. });
  525. app.contextMenu.addItem({
  526. command: CommandIDs.enableOutputScrolling,
  527. selector: '.jp-Notebook',
  528. rank: 2
  529. });
  530. app.contextMenu.addItem({
  531. command: CommandIDs.disableOutputScrolling,
  532. selector: '.jp-Notebook',
  533. rank: 3
  534. });
  535. app.contextMenu.addItem({
  536. type: 'separator',
  537. selector: '.jp-Notebook',
  538. rank: 4
  539. });
  540. app.contextMenu.addItem({
  541. command: CommandIDs.undoCellAction,
  542. selector: '.jp-Notebook',
  543. rank: 5
  544. });
  545. app.contextMenu.addItem({
  546. command: CommandIDs.redoCellAction,
  547. selector: '.jp-Notebook',
  548. rank: 6
  549. });
  550. app.contextMenu.addItem({
  551. command: CommandIDs.restart,
  552. selector: '.jp-Notebook',
  553. rank: 7
  554. });
  555. app.contextMenu.addItem({
  556. type: 'separator',
  557. selector: '.jp-Notebook',
  558. rank: 8
  559. });
  560. app.contextMenu.addItem({
  561. command: CommandIDs.createConsole,
  562. selector: '.jp-Notebook',
  563. rank: 9
  564. });
  565. return tracker;
  566. }
  567. /**
  568. * Add the notebook commands to the application's command registry.
  569. */
  570. function addCommands(app: JupyterLab, services: ServiceManager, tracker: NotebookTracker): void {
  571. const { commands, shell } = app;
  572. // Get the current widget and activate unless the args specify otherwise.
  573. function getCurrent(args: ReadonlyJSONObject): NotebookPanel | null {
  574. const widget = tracker.currentWidget;
  575. const activate = args['activate'] !== false;
  576. if (activate && widget) {
  577. shell.activateById(widget.id);
  578. }
  579. return widget;
  580. }
  581. /**
  582. * Whether there is an active notebook.
  583. */
  584. function isEnabled(): boolean {
  585. return tracker.currentWidget !== null &&
  586. tracker.currentWidget === app.shell.currentWidget;
  587. }
  588. /**
  589. * Whether there is an notebook active, with a single selected cell.
  590. */
  591. function isEnabledAndSingleSelected(): boolean {
  592. if (!isEnabled()) { return false; }
  593. const { content } = tracker.currentWidget;
  594. const index = content.activeCellIndex;
  595. // If there are selections that are not the active cell,
  596. // this command is confusing, so disable it.
  597. for (let i = 0; i < content.widgets.length; ++i) {
  598. if (content.isSelected(content.widgets[i]) && i !== index) {
  599. return false;
  600. }
  601. }
  602. return true;
  603. }
  604. commands.addCommand(CommandIDs.runAndAdvance, {
  605. label: 'Run Selected Cells',
  606. execute: args => {
  607. const current = getCurrent(args);
  608. if (current) {
  609. const { context, content } = current;
  610. return NotebookActions.runAndAdvance(content, context.session);
  611. }
  612. },
  613. isEnabled
  614. });
  615. commands.addCommand(CommandIDs.run, {
  616. label: 'Run Selected Cells and Don\'t Advance',
  617. execute: args => {
  618. const current = getCurrent(args);
  619. if (current) {
  620. const { context, content } = current;
  621. return NotebookActions.run(content, context.session);
  622. }
  623. },
  624. isEnabled
  625. });
  626. commands.addCommand(CommandIDs.runAndInsert, {
  627. label: 'Run Selected Cells and Insert Below',
  628. execute: args => {
  629. const current = getCurrent(args);
  630. if (current) {
  631. const { context, content } = current;
  632. return NotebookActions.runAndInsert(content, context.session);
  633. }
  634. },
  635. isEnabled
  636. });
  637. commands.addCommand(CommandIDs.runAll, {
  638. label: 'Run All Cells',
  639. execute: args => {
  640. const current = getCurrent(args);
  641. if (current) {
  642. const { context, content } = current;
  643. return NotebookActions.runAll(content, context.session);
  644. }
  645. },
  646. isEnabled
  647. });
  648. commands.addCommand(CommandIDs.runAllAbove, {
  649. label: 'Run All Above Selected Cell',
  650. execute: args => {
  651. const current = getCurrent(args);
  652. if (current) {
  653. const { context, content } = current;
  654. return NotebookActions.runAllAbove(content, context.session);
  655. }
  656. },
  657. isEnabled: () => {
  658. // Can't run above if there are multiple cells selected,
  659. // or if we are at the top of the notebook.
  660. return isEnabledAndSingleSelected() &&
  661. tracker.currentWidget.content.activeCellIndex !== 0;
  662. }
  663. });
  664. commands.addCommand(CommandIDs.runAllBelow, {
  665. label: 'Run Selected Cell and All Below',
  666. execute: args => {
  667. const current = getCurrent(args);
  668. if (current) {
  669. const { context, content } = current;
  670. return NotebookActions.runAllBelow(content, context.session);
  671. }
  672. },
  673. isEnabled: () => {
  674. // Can't run below if there are multiple cells selected,
  675. // or if we are at the bottom of the notebook.
  676. return isEnabledAndSingleSelected() &&
  677. tracker.currentWidget.content.activeCellIndex !==
  678. tracker.currentWidget.content.widgets.length - 1;
  679. }
  680. });
  681. commands.addCommand(CommandIDs.restart, {
  682. label: 'Restart Kernel…',
  683. execute: args => {
  684. const current = getCurrent(args);
  685. if (current) {
  686. return current.session.restart();
  687. }
  688. },
  689. isEnabled
  690. });
  691. commands.addCommand(CommandIDs.closeAndShutdown, {
  692. label: 'Close and Shutdown',
  693. execute: args => {
  694. const current = getCurrent(args);
  695. if (!current) {
  696. return;
  697. }
  698. const fileName = current.title.label;
  699. return showDialog({
  700. title: 'Shutdown the notebook?',
  701. body: `Are you sure you want to close "${fileName}"?`,
  702. buttons: [Dialog.cancelButton(), Dialog.warnButton()]
  703. }).then(result => {
  704. if (result.button.accept) {
  705. return current.context.session.shutdown()
  706. .then(() => { current.dispose(); });
  707. }
  708. });
  709. },
  710. isEnabled
  711. });
  712. commands.addCommand(CommandIDs.trust, {
  713. label: () => 'Trust Notebook',
  714. execute: args => {
  715. const current = getCurrent(args);
  716. if (current) {
  717. const { context, content } = current;
  718. return NotebookActions.trust(content).then(() => context.save());
  719. }
  720. },
  721. isEnabled
  722. });
  723. commands.addCommand(CommandIDs.exportToFormat, {
  724. label: args => {
  725. const formatLabel = (args['label']) as string;
  726. return (args['isPalette'] ? 'Export Notebook to ' : '') + formatLabel;
  727. },
  728. execute: args => {
  729. const current = getCurrent(args);
  730. if (!current) {
  731. return;
  732. }
  733. const notebookPath = URLExt.encodeParts(current.context.path);
  734. const url = URLExt.join(
  735. services.serverSettings.baseUrl,
  736. 'nbconvert',
  737. (args['format']) as string,
  738. notebookPath
  739. ) + '?download=true';
  740. const child = window.open('', '_blank');
  741. const { context } = current;
  742. if (context.model.dirty && !context.model.readOnly) {
  743. return context.save().then(() => { child.location.assign(url); });
  744. }
  745. return new Promise<void>((resolve) => {
  746. child.location.assign(url);
  747. resolve(undefined);
  748. });
  749. },
  750. isEnabled
  751. });
  752. commands.addCommand(CommandIDs.restartClear, {
  753. label: 'Restart Kernel and Clear All Outputs…',
  754. execute: args => {
  755. const current = getCurrent(args);
  756. if (current) {
  757. const { content, session } = current;
  758. return session.restart()
  759. .then(() => { NotebookActions.clearAllOutputs(content); });
  760. }
  761. },
  762. isEnabled
  763. });
  764. commands.addCommand(CommandIDs.restartRunAll, {
  765. label: 'Restart Kernel and Run All Cells…',
  766. execute: args => {
  767. const current = getCurrent(args);
  768. if (current) {
  769. const { context, content, session } = current;
  770. return session.restart().then(restarted => {
  771. if (restarted) {
  772. NotebookActions.runAll(content, context.session);
  773. }
  774. return restarted;
  775. });
  776. }
  777. },
  778. isEnabled
  779. });
  780. commands.addCommand(CommandIDs.clearAllOutputs, {
  781. label: 'Clear All Outputs',
  782. execute: args => {
  783. const current = getCurrent(args);
  784. if (current) {
  785. return NotebookActions.clearAllOutputs(current.content);
  786. }
  787. },
  788. isEnabled
  789. });
  790. commands.addCommand(CommandIDs.clearOutputs, {
  791. label: 'Clear Outputs',
  792. execute: args => {
  793. const current = getCurrent(args);
  794. if (current) {
  795. return NotebookActions.clearOutputs(current.content);
  796. }
  797. },
  798. isEnabled
  799. });
  800. commands.addCommand(CommandIDs.interrupt, {
  801. label: 'Interrupt Kernel',
  802. execute: args => {
  803. const current = getCurrent(args);
  804. if (!current) {
  805. return;
  806. }
  807. const kernel = current.context.session.kernel;
  808. if (kernel) {
  809. return kernel.interrupt();
  810. }
  811. },
  812. isEnabled
  813. });
  814. commands.addCommand(CommandIDs.toCode, {
  815. label: 'Change to Code Cell Type',
  816. execute: args => {
  817. const current = getCurrent(args);
  818. if (current) {
  819. return NotebookActions.changeCellType(current.content, 'code');
  820. }
  821. },
  822. isEnabled
  823. });
  824. commands.addCommand(CommandIDs.toMarkdown, {
  825. label: 'Change to Markdown Cell Type',
  826. execute: args => {
  827. const current = getCurrent(args);
  828. if (current) {
  829. return NotebookActions.changeCellType(current.content, 'markdown');
  830. }
  831. },
  832. isEnabled
  833. });
  834. commands.addCommand(CommandIDs.toRaw, {
  835. label: 'Change to Raw Cell Type',
  836. execute: args => {
  837. const current = getCurrent(args);
  838. if (current) {
  839. return NotebookActions.changeCellType(current.content, 'raw');
  840. }
  841. },
  842. isEnabled
  843. });
  844. commands.addCommand(CommandIDs.cut, {
  845. label: 'Cut Cells',
  846. execute: args => {
  847. const current = getCurrent(args);
  848. if (current) {
  849. return NotebookActions.cut(current.content);
  850. }
  851. },
  852. isEnabled
  853. });
  854. commands.addCommand(CommandIDs.copy, {
  855. label: 'Copy Cells',
  856. execute: args => {
  857. const current = getCurrent(args);
  858. if (current) {
  859. return NotebookActions.copy(current.content);
  860. }
  861. },
  862. isEnabled
  863. });
  864. commands.addCommand(CommandIDs.pasteBelow, {
  865. label: 'Paste Cells Below',
  866. execute: args => {
  867. const current = getCurrent(args);
  868. if (current) {
  869. return NotebookActions.paste(current.content, 'below');
  870. }
  871. },
  872. isEnabled
  873. });
  874. commands.addCommand(CommandIDs.pasteAbove, {
  875. label: 'Paste Cells Above',
  876. execute: args => {
  877. const current = getCurrent(args);
  878. if (current) {
  879. return NotebookActions.paste(current.content, 'above');
  880. }
  881. },
  882. isEnabled
  883. });
  884. commands.addCommand(CommandIDs.pasteAndReplace, {
  885. label: 'Paste Cells and Replace',
  886. execute: args => {
  887. const current = getCurrent(args);
  888. if (current) {
  889. return NotebookActions.paste(current.content, 'replace');
  890. }
  891. },
  892. isEnabled
  893. });
  894. commands.addCommand(CommandIDs.deleteCell, {
  895. label: 'Delete Cells',
  896. execute: args => {
  897. const current = getCurrent(args);
  898. if (current) {
  899. return NotebookActions.deleteCells(current.content);
  900. }
  901. },
  902. isEnabled
  903. });
  904. commands.addCommand(CommandIDs.split, {
  905. label: 'Split Cell',
  906. execute: args => {
  907. const current = getCurrent(args);
  908. if (current) {
  909. return NotebookActions.splitCell(current.content);
  910. }
  911. },
  912. isEnabled
  913. });
  914. commands.addCommand(CommandIDs.merge, {
  915. label: 'Merge Selected Cells',
  916. execute: args => {
  917. const current = getCurrent(args);
  918. if (current) {
  919. return NotebookActions.mergeCells(current.content);
  920. }
  921. },
  922. isEnabled
  923. });
  924. commands.addCommand(CommandIDs.insertAbove, {
  925. label: 'Insert Cell Above',
  926. execute: args => {
  927. const current = getCurrent(args);
  928. if (current) {
  929. return NotebookActions.insertAbove(current.content);
  930. }
  931. },
  932. isEnabled
  933. });
  934. commands.addCommand(CommandIDs.insertBelow, {
  935. label: 'Insert Cell Below',
  936. execute: args => {
  937. const current = getCurrent(args);
  938. if (current) {
  939. return NotebookActions.insertBelow(current.content);
  940. }
  941. },
  942. isEnabled
  943. });
  944. commands.addCommand(CommandIDs.selectAbove, {
  945. label: 'Select Cell Above',
  946. execute: args => {
  947. const current = getCurrent(args);
  948. if (current) {
  949. return NotebookActions.selectAbove(current.content);
  950. }
  951. },
  952. isEnabled
  953. });
  954. commands.addCommand(CommandIDs.selectBelow, {
  955. label: 'Select Cell Below',
  956. execute: args => {
  957. const current = getCurrent(args);
  958. if (current) {
  959. return NotebookActions.selectBelow(current.content);
  960. }
  961. },
  962. isEnabled
  963. });
  964. commands.addCommand(CommandIDs.extendAbove, {
  965. label: 'Extend Selection Above',
  966. execute: args => {
  967. const current = getCurrent(args);
  968. if (current) {
  969. return NotebookActions.extendSelectionAbove(current.content);
  970. }
  971. },
  972. isEnabled
  973. });
  974. commands.addCommand(CommandIDs.extendBelow, {
  975. label: 'Extend Selection Below',
  976. execute: args => {
  977. const current = getCurrent(args);
  978. if (current) {
  979. return NotebookActions.extendSelectionBelow(current.content);
  980. }
  981. },
  982. isEnabled
  983. });
  984. commands.addCommand(CommandIDs.selectAll, {
  985. label: 'Select All Cells',
  986. execute: args => {
  987. const current = getCurrent(args);
  988. if (current) {
  989. return NotebookActions.selectAll(current.content);
  990. }
  991. },
  992. isEnabled
  993. });
  994. commands.addCommand(CommandIDs.deselectAll, {
  995. label: 'Deselect All Cells',
  996. execute: args => {
  997. const current = getCurrent(args);
  998. if (current) {
  999. return NotebookActions.deselectAll(current.content);
  1000. }
  1001. },
  1002. isEnabled
  1003. });
  1004. commands.addCommand(CommandIDs.moveUp, {
  1005. label: 'Move Cells Up',
  1006. execute: args => {
  1007. const current = getCurrent(args);
  1008. if (current) {
  1009. return NotebookActions.moveUp(current.content);
  1010. }
  1011. },
  1012. isEnabled
  1013. });
  1014. commands.addCommand(CommandIDs.moveDown, {
  1015. label: 'Move Cells Down',
  1016. execute: args => {
  1017. const current = getCurrent(args);
  1018. if (current) {
  1019. return NotebookActions.moveDown(current.content);
  1020. }
  1021. },
  1022. isEnabled
  1023. });
  1024. commands.addCommand(CommandIDs.toggleAllLines, {
  1025. label: 'Toggle All Line Numbers',
  1026. execute: args => {
  1027. const current = getCurrent(args);
  1028. if (current) {
  1029. return NotebookActions.toggleAllLineNumbers(current.content);
  1030. }
  1031. },
  1032. isEnabled
  1033. });
  1034. commands.addCommand(CommandIDs.commandMode, {
  1035. label: 'Enter Command Mode',
  1036. execute: args => {
  1037. const current = getCurrent(args);
  1038. if (current) {
  1039. current.content.mode = 'command';
  1040. }
  1041. },
  1042. isEnabled
  1043. });
  1044. commands.addCommand(CommandIDs.editMode, {
  1045. label: 'Enter Edit Mode',
  1046. execute: args => {
  1047. const current = getCurrent(args);
  1048. if (current) {
  1049. current.content.mode = 'edit';
  1050. }
  1051. },
  1052. isEnabled
  1053. });
  1054. commands.addCommand(CommandIDs.undoCellAction, {
  1055. label: 'Undo Cell Operation',
  1056. execute: args => {
  1057. const current = getCurrent(args);
  1058. if (current) {
  1059. return NotebookActions.undo(current.content);
  1060. }
  1061. },
  1062. isEnabled
  1063. });
  1064. commands.addCommand(CommandIDs.redoCellAction, {
  1065. label: 'Redo Cell Operation',
  1066. execute: args => {
  1067. const current = getCurrent(args);
  1068. if (current) {
  1069. return NotebookActions.redo(current.content);
  1070. }
  1071. },
  1072. isEnabled
  1073. });
  1074. commands.addCommand(CommandIDs.changeKernel, {
  1075. label: 'Change Kernel…',
  1076. execute: args => {
  1077. const current = getCurrent(args);
  1078. if (current) {
  1079. return current.context.session.selectKernel();
  1080. }
  1081. },
  1082. isEnabled
  1083. });
  1084. commands.addCommand(CommandIDs.reconnectToKernel, {
  1085. label: 'Reconnect To Kernel',
  1086. execute: args => {
  1087. const current = getCurrent(args);
  1088. if (!current) {
  1089. return;
  1090. }
  1091. const kernel = current.context.session.kernel;
  1092. if (kernel) {
  1093. return kernel.reconnect();
  1094. }
  1095. },
  1096. isEnabled
  1097. });
  1098. commands.addCommand(CommandIDs.createOutputView, {
  1099. label: 'Create New View for Output',
  1100. execute: args => {
  1101. // Clone the OutputArea
  1102. const current = getCurrent({ ...args, activate: false });
  1103. const nb = current.content;
  1104. const content = (nb.activeCell as CodeCell).cloneOutputArea();
  1105. // Create a MainAreaWidget
  1106. const widget = new MainAreaWidget({ content });
  1107. widget.id = `LinkedOutputView-${UUID.uuid4()}`;
  1108. widget.title.label = 'Output View';
  1109. widget.title.icon = NOTEBOOK_ICON_CLASS;
  1110. widget.title.caption = current.title.label ? `For Notebook: ${current.title.label}` : 'For Notebook:';
  1111. widget.addClass('jp-LinkedOutputView');
  1112. current.context.addSibling(
  1113. widget, { ref: current.id, mode: 'split-bottom' }
  1114. );
  1115. // Remove the output view if the parent notebook is closed.
  1116. nb.disposed.connect(widget.dispose);
  1117. },
  1118. isEnabled: isEnabledAndSingleSelected
  1119. });
  1120. commands.addCommand(CommandIDs.createConsole, {
  1121. label: 'New Console for Notebook',
  1122. execute: args => {
  1123. const current = getCurrent({ ...args, activate: false });
  1124. const widget = tracker.currentWidget;
  1125. if (!current || !widget) {
  1126. return;
  1127. }
  1128. const options: ReadonlyJSONObject = {
  1129. path: widget.context.path,
  1130. preferredLanguage: widget.context.model.defaultKernelLanguage,
  1131. activate: args['activate'],
  1132. ref: current.id,
  1133. insertMode: 'split-bottom'
  1134. };
  1135. return commands.execute('console:create', options);
  1136. },
  1137. isEnabled
  1138. });
  1139. commands.addCommand(CommandIDs.markdown1, {
  1140. label: 'Change to Heading 1',
  1141. execute: args => {
  1142. const current = getCurrent(args);
  1143. if (current) {
  1144. return NotebookActions.setMarkdownHeader(current.content, 1);
  1145. }
  1146. },
  1147. isEnabled
  1148. });
  1149. commands.addCommand(CommandIDs.markdown2, {
  1150. label: 'Change to Heading 2',
  1151. execute: args => {
  1152. const current = getCurrent(args);
  1153. if (current) {
  1154. return NotebookActions.setMarkdownHeader(current.content, 2);
  1155. }
  1156. },
  1157. isEnabled
  1158. });
  1159. commands.addCommand(CommandIDs.markdown3, {
  1160. label: 'Change to Heading 3',
  1161. execute: args => {
  1162. const current = getCurrent(args);
  1163. if (current) {
  1164. return NotebookActions.setMarkdownHeader(current.content, 3);
  1165. }
  1166. },
  1167. isEnabled
  1168. });
  1169. commands.addCommand(CommandIDs.markdown4, {
  1170. label: 'Change to Heading 4',
  1171. execute: args => {
  1172. const current = getCurrent(args);
  1173. if (current) {
  1174. return NotebookActions.setMarkdownHeader(current.content, 4);
  1175. }
  1176. },
  1177. isEnabled
  1178. });
  1179. commands.addCommand(CommandIDs.markdown5, {
  1180. label: 'Change to Heading 5',
  1181. execute: args => {
  1182. const current = getCurrent(args);
  1183. if (current) {
  1184. return NotebookActions.setMarkdownHeader(current.content, 5);
  1185. }
  1186. },
  1187. isEnabled
  1188. });
  1189. commands.addCommand(CommandIDs.markdown6, {
  1190. label: 'Change to Heading 6',
  1191. execute: args => {
  1192. const current = getCurrent(args);
  1193. if (current) {
  1194. return NotebookActions.setMarkdownHeader(current.content, 6);
  1195. }
  1196. },
  1197. isEnabled
  1198. });
  1199. commands.addCommand(CommandIDs.hideCode, {
  1200. label: 'Collapse Selected Code',
  1201. execute: args => {
  1202. const current = getCurrent(args);
  1203. if (current) {
  1204. return NotebookActions.hideCode(current.content);
  1205. }
  1206. },
  1207. isEnabled
  1208. });
  1209. commands.addCommand(CommandIDs.showCode, {
  1210. label: 'Expand Selected Code',
  1211. execute: args => {
  1212. const current = getCurrent(args);
  1213. if (current) {
  1214. return NotebookActions.showCode(current.content);
  1215. }
  1216. },
  1217. isEnabled
  1218. });
  1219. commands.addCommand(CommandIDs.hideAllCode, {
  1220. label: 'Collapse All Code',
  1221. execute: args => {
  1222. const current = getCurrent(args);
  1223. if (current) {
  1224. return NotebookActions.hideAllCode(current.content);
  1225. }
  1226. },
  1227. isEnabled
  1228. });
  1229. commands.addCommand(CommandIDs.showAllCode, {
  1230. label: 'Expand All Code',
  1231. execute: args => {
  1232. const current = getCurrent(args);
  1233. if (current) {
  1234. return NotebookActions.showAllCode(current.content);
  1235. }
  1236. },
  1237. isEnabled
  1238. });
  1239. commands.addCommand(CommandIDs.hideOutput, {
  1240. label: 'Collapse Selected Outputs',
  1241. execute: args => {
  1242. const current = getCurrent(args);
  1243. if (current) {
  1244. return NotebookActions.hideOutput(current.content);
  1245. }
  1246. },
  1247. isEnabled
  1248. });
  1249. commands.addCommand(CommandIDs.showOutput, {
  1250. label: 'Expand Selected Outputs',
  1251. execute: args => {
  1252. const current = getCurrent(args);
  1253. if (current) {
  1254. return NotebookActions.showOutput(current.content);
  1255. }
  1256. },
  1257. isEnabled
  1258. });
  1259. commands.addCommand(CommandIDs.hideAllOutputs, {
  1260. label: 'Collapse All Outputs',
  1261. execute: args => {
  1262. const current = getCurrent(args);
  1263. if (current) {
  1264. return NotebookActions.hideAllOutputs(current.content);
  1265. }
  1266. },
  1267. isEnabled
  1268. });
  1269. commands.addCommand(CommandIDs.showAllOutputs, {
  1270. label: 'Expand All Outputs',
  1271. execute: args => {
  1272. const current = getCurrent(args);
  1273. if (current) {
  1274. return NotebookActions.showAllOutputs(current.content);
  1275. }
  1276. },
  1277. isEnabled
  1278. });
  1279. commands.addCommand(CommandIDs.enableOutputScrolling, {
  1280. label: 'Enable Scrolling for Outputs',
  1281. execute: args => {
  1282. const current = getCurrent(args);
  1283. if (current) {
  1284. return NotebookActions.enableOutputScrolling(current.content);
  1285. }
  1286. },
  1287. isEnabled
  1288. });
  1289. commands.addCommand(CommandIDs.disableOutputScrolling, {
  1290. label: 'Disable Scrolling for Outputs',
  1291. execute: args => {
  1292. const current = getCurrent(args);
  1293. if (current) {
  1294. return NotebookActions.disableOutputScrolling(current.content);
  1295. }
  1296. },
  1297. isEnabled
  1298. });
  1299. commands.addCommand(CommandIDs.saveWithView, {
  1300. label: 'Save Notebook with View State',
  1301. execute: args => {
  1302. const current = getCurrent(args);
  1303. if (current) {
  1304. NotebookActions.persistViewState(current.content);
  1305. app.commands.execute('docmanager:save');
  1306. }
  1307. },
  1308. isEnabled
  1309. });
  1310. }
  1311. /**
  1312. * Populate the application's command palette with notebook commands.
  1313. */
  1314. function populatePalette(palette: ICommandPalette): void {
  1315. let category = 'Notebook Operations';
  1316. [
  1317. CommandIDs.createNew,
  1318. CommandIDs.interrupt,
  1319. CommandIDs.restart,
  1320. CommandIDs.restartClear,
  1321. CommandIDs.restartRunAll,
  1322. CommandIDs.runAll,
  1323. CommandIDs.runAllAbove,
  1324. CommandIDs.runAllBelow,
  1325. CommandIDs.selectAll,
  1326. CommandIDs.deselectAll,
  1327. CommandIDs.clearAllOutputs,
  1328. CommandIDs.toggleAllLines,
  1329. CommandIDs.editMode,
  1330. CommandIDs.commandMode,
  1331. CommandIDs.changeKernel,
  1332. CommandIDs.reconnectToKernel,
  1333. CommandIDs.createConsole,
  1334. CommandIDs.closeAndShutdown,
  1335. CommandIDs.trust,
  1336. CommandIDs.saveWithView
  1337. ].forEach(command => { palette.addItem({ command, category }); });
  1338. EXPORT_TO_FORMATS.forEach(exportToFormat => {
  1339. let args = {
  1340. 'format': exportToFormat['format'],
  1341. 'label': exportToFormat['label'],
  1342. 'isPalette': true
  1343. };
  1344. palette.addItem({ command: CommandIDs.exportToFormat, category, args });
  1345. });
  1346. category = 'Notebook Cell Operations';
  1347. [
  1348. CommandIDs.run,
  1349. CommandIDs.runAndAdvance,
  1350. CommandIDs.runAndInsert,
  1351. CommandIDs.clearOutputs,
  1352. CommandIDs.toCode,
  1353. CommandIDs.toMarkdown,
  1354. CommandIDs.toRaw,
  1355. CommandIDs.cut,
  1356. CommandIDs.copy,
  1357. CommandIDs.pasteBelow,
  1358. CommandIDs.pasteAbove,
  1359. CommandIDs.pasteAndReplace,
  1360. CommandIDs.deleteCell,
  1361. CommandIDs.split,
  1362. CommandIDs.merge,
  1363. CommandIDs.insertAbove,
  1364. CommandIDs.insertBelow,
  1365. CommandIDs.selectAbove,
  1366. CommandIDs.selectBelow,
  1367. CommandIDs.extendAbove,
  1368. CommandIDs.extendBelow,
  1369. CommandIDs.moveDown,
  1370. CommandIDs.moveUp,
  1371. CommandIDs.undoCellAction,
  1372. CommandIDs.redoCellAction,
  1373. CommandIDs.markdown1,
  1374. CommandIDs.markdown2,
  1375. CommandIDs.markdown3,
  1376. CommandIDs.markdown4,
  1377. CommandIDs.markdown5,
  1378. CommandIDs.markdown6,
  1379. CommandIDs.hideCode,
  1380. CommandIDs.showCode,
  1381. CommandIDs.hideAllCode,
  1382. CommandIDs.showAllCode,
  1383. CommandIDs.hideOutput,
  1384. CommandIDs.showOutput,
  1385. CommandIDs.hideAllOutputs,
  1386. CommandIDs.showAllOutputs,
  1387. CommandIDs.enableOutputScrolling,
  1388. CommandIDs.disableOutputScrolling
  1389. ].forEach(command => { palette.addItem({ command, category }); });
  1390. }
  1391. /**
  1392. * Populates the application menus for the notebook.
  1393. */
  1394. function populateMenus(app: JupyterLab, mainMenu: IMainMenu, tracker: INotebookTracker): void {
  1395. let { commands } = app;
  1396. // Add undo/redo hooks to the edit menu.
  1397. mainMenu.editMenu.undoers.add({
  1398. tracker,
  1399. undo: widget => { widget.content.activeCell.editor.undo(); },
  1400. redo: widget => { widget.content.activeCell.editor.redo(); }
  1401. } as IEditMenu.IUndoer<NotebookPanel>);
  1402. // Add a clearer to the edit menu
  1403. mainMenu.editMenu.clearers.add({
  1404. tracker,
  1405. noun: 'Outputs',
  1406. pluralNoun: 'Outputs',
  1407. clearCurrent: (current: NotebookPanel) => {
  1408. return NotebookActions.clearOutputs(current.content);
  1409. },
  1410. clearAll: (current: NotebookPanel) => {
  1411. return NotebookActions.clearAllOutputs(current.content);
  1412. }
  1413. } as IEditMenu.IClearer<NotebookPanel>);
  1414. // Add new notebook creation to the file menu.
  1415. mainMenu.fileMenu.newMenu.addGroup([{ command: CommandIDs.createNew }], 10);
  1416. // Add a close and shutdown command to the file menu.
  1417. mainMenu.fileMenu.closeAndCleaners.add({
  1418. tracker,
  1419. action: 'Shutdown',
  1420. name: 'Notebook',
  1421. closeAndCleanup: (current: NotebookPanel) => {
  1422. const fileName = current.title.label;
  1423. return showDialog({
  1424. title: 'Shutdown the notebook?',
  1425. body: `Are you sure you want to close "${fileName}"?`,
  1426. buttons: [Dialog.cancelButton(), Dialog.warnButton()]
  1427. }).then(result => {
  1428. if (result.button.accept) {
  1429. return current.context.session.shutdown()
  1430. .then(() => { current.dispose(); });
  1431. }
  1432. });
  1433. }
  1434. } as IFileMenu.ICloseAndCleaner<NotebookPanel>);
  1435. // Add a save with view command to the file menu.
  1436. mainMenu.fileMenu.persistAndSavers.add({
  1437. tracker,
  1438. action: 'with View State',
  1439. name: 'Notebook',
  1440. persistAndSave: (current: NotebookPanel) => {
  1441. NotebookActions.persistViewState(current.content);
  1442. return app.commands.execute('docmanager:save');
  1443. }
  1444. } as IFileMenu.IPersistAndSave<NotebookPanel>);
  1445. // Add a notebook group to the File menu.
  1446. let exportTo = new Menu({ commands } );
  1447. exportTo.title.label = 'Export Notebook As…';
  1448. EXPORT_TO_FORMATS.forEach(exportToFormat => {
  1449. exportTo.addItem({ command: CommandIDs.exportToFormat, args: exportToFormat });
  1450. });
  1451. const fileGroup = [
  1452. { command: CommandIDs.trust },
  1453. { type: 'submenu', submenu: exportTo } as Menu.IItemOptions
  1454. ];
  1455. mainMenu.fileMenu.addGroup(fileGroup, 10);
  1456. // Add a kernel user to the Kernel menu
  1457. mainMenu.kernelMenu.kernelUsers.add({
  1458. tracker,
  1459. interruptKernel: current => {
  1460. let kernel = current.session.kernel;
  1461. if (kernel) {
  1462. return kernel.interrupt();
  1463. }
  1464. return Promise.resolve(void 0);
  1465. },
  1466. noun: 'All Outputs',
  1467. restartKernel: current => current.session.restart(),
  1468. restartKernelAndClear: current => {
  1469. return current.session.restart().then(restarted => {
  1470. if (restarted) {
  1471. NotebookActions.clearAllOutputs(current.content);
  1472. }
  1473. return restarted;
  1474. });
  1475. },
  1476. changeKernel: current => current.session.selectKernel(),
  1477. shutdownKernel: current => current.session.shutdown(),
  1478. } as IKernelMenu.IKernelUser<NotebookPanel>);
  1479. // Add a console creator the the Kernel menu
  1480. mainMenu.fileMenu.consoleCreators.add({
  1481. tracker,
  1482. name: 'Notebook',
  1483. createConsole: current => {
  1484. const options: ReadonlyJSONObject = {
  1485. path: current.context.path,
  1486. preferredLanguage: current.context.model.defaultKernelLanguage,
  1487. activate: true,
  1488. ref: current.id,
  1489. insertMode: 'split-bottom'
  1490. };
  1491. return commands.execute('console:create', options);
  1492. }
  1493. } as IFileMenu.IConsoleCreator<NotebookPanel>);
  1494. // Add some commands to the application view menu.
  1495. const collapseGroup = [
  1496. CommandIDs.hideCode,
  1497. CommandIDs.hideOutput,
  1498. CommandIDs.hideAllCode,
  1499. CommandIDs.hideAllOutputs
  1500. ].map(command => { return { command }; });
  1501. mainMenu.viewMenu.addGroup(collapseGroup, 10);
  1502. const expandGroup = [
  1503. CommandIDs.showCode,
  1504. CommandIDs.showOutput,
  1505. CommandIDs.showAllCode,
  1506. CommandIDs.showAllOutputs
  1507. ].map(command => { return { command }; });
  1508. mainMenu.viewMenu.addGroup(expandGroup, 11);
  1509. // Add an IEditorViewer to the application view menu
  1510. mainMenu.viewMenu.editorViewers.add({
  1511. tracker,
  1512. toggleLineNumbers: widget => {
  1513. NotebookActions.toggleAllLineNumbers(widget.content);
  1514. },
  1515. lineNumbersToggled: widget => {
  1516. const config = widget.content.editorConfig;
  1517. return !!(config.code.lineNumbers && config.markdown.lineNumbers &&
  1518. config.raw.lineNumbers);
  1519. }
  1520. } as IViewMenu.IEditorViewer<NotebookPanel>);
  1521. // Add an ICodeRunner to the application run menu
  1522. mainMenu.runMenu.codeRunners.add({
  1523. tracker,
  1524. noun: 'Cells',
  1525. run: current => {
  1526. const { context, content } = current;
  1527. return NotebookActions.runAndAdvance(content, context.session)
  1528. .then(() => void 0);
  1529. },
  1530. runAll: current => {
  1531. const { context, content } = current;
  1532. return NotebookActions.runAll(content, context.session)
  1533. .then(() => void 0);
  1534. },
  1535. restartAndRunAll: current => {
  1536. const { context, content } = current;
  1537. return context.session.restart()
  1538. .then(restarted => {
  1539. if (restarted) {
  1540. NotebookActions.runAll(content, context.session);
  1541. }
  1542. return restarted;
  1543. });
  1544. }
  1545. } as IRunMenu.ICodeRunner<NotebookPanel>);
  1546. // Add a run+insert and run+don't advance group to the run menu.
  1547. const runExtras = [
  1548. CommandIDs.runAndInsert,
  1549. CommandIDs.run
  1550. ].map(command => { return { command }; });
  1551. // Add a run all above/below group to the run menu.
  1552. const runAboveBelowGroup = [
  1553. CommandIDs.runAllAbove,
  1554. CommandIDs.runAllBelow
  1555. ].map(command => { return { command }; });
  1556. // Add commands to the application edit menu.
  1557. const undoCellActionGroup = [
  1558. CommandIDs.undoCellAction,
  1559. CommandIDs.redoCellAction
  1560. ].map(command => { return { command }; });
  1561. const copyGroup = [
  1562. CommandIDs.cut,
  1563. CommandIDs.copy,
  1564. CommandIDs.pasteBelow,
  1565. CommandIDs.pasteAbove,
  1566. CommandIDs.pasteAndReplace,
  1567. ].map(command => { return { command }; });
  1568. const selectGroup = [
  1569. CommandIDs.selectAll,
  1570. CommandIDs.deselectAll,
  1571. ].map(command => { return { command }; });
  1572. const splitMergeGroup = [
  1573. CommandIDs.split,
  1574. CommandIDs.merge
  1575. ].map(command => { return { command }; });
  1576. const moveCellsGroup = [
  1577. CommandIDs.moveUp,
  1578. CommandIDs.moveDown
  1579. ].map(command => { return { command }; });
  1580. mainMenu.editMenu.addGroup(undoCellActionGroup, 4);
  1581. mainMenu.editMenu.addGroup(copyGroup, 5);
  1582. mainMenu.editMenu.addGroup([{ command: CommandIDs.deleteCell }], 6);
  1583. mainMenu.editMenu.addGroup(selectGroup, 7);
  1584. mainMenu.editMenu.addGroup(moveCellsGroup, 8);
  1585. mainMenu.editMenu.addGroup(splitMergeGroup, 9);
  1586. mainMenu.runMenu.addGroup(runExtras, 10);
  1587. mainMenu.runMenu.addGroup(runAboveBelowGroup, 11);
  1588. // Add kernel information to the application help menu.
  1589. mainMenu.helpMenu.kernelUsers.add({
  1590. tracker,
  1591. getKernel: current => current.session.kernel
  1592. } as IHelpMenu.IKernelUser<NotebookPanel>);
  1593. }