index.ts 69 KB

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