index.ts 50 KB

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