index.ts 74 KB

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