index.ts 58 KB

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