index.ts 62 KB

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