plugin.ts 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705
  1. // Copyright (c) Jupyter Development Team.
  2. // Distributed under the terms of the Modified BSD License.
  3. import {
  4. Menu
  5. } from 'phosphor/lib/ui/menu';
  6. import {
  7. JupyterLab, JupyterLabPlugin
  8. } from '../application';
  9. import {
  10. IClipboard
  11. } from '../clipboard';
  12. import {
  13. IEditorServices
  14. } from '../codeeditor';
  15. import {
  16. ICommandPalette
  17. } from '../commandpalette';
  18. import {
  19. IMainMenu
  20. } from '../mainmenu';
  21. import {
  22. IDocumentRegistry,
  23. restartKernel, selectKernelForContext
  24. } from '../docregistry';
  25. import {
  26. IInspector
  27. } from '../inspector';
  28. import {
  29. ILayoutRestorer
  30. } from '../layoutrestorer';
  31. import {
  32. IRenderMime
  33. } from '../rendermime';
  34. import {
  35. IServiceManager
  36. } from '../services';
  37. import {
  38. INotebookTracker, NotebookModelFactory, NotebookPanel, NotebookTracker,
  39. NotebookWidgetFactory, NotebookActions, Notebook
  40. } from './index';
  41. /**
  42. * The class name for all main area portrait tab icons.
  43. */
  44. const PORTRAIT_ICON_CLASS = 'jp-MainAreaPortraitIcon';
  45. /**
  46. * The class name for the notebook icon from the default theme.
  47. */
  48. const NOTEBOOK_ICON_CLASS = 'jp-ImageNotebook';
  49. /**
  50. * The map of command ids used by the notebook.
  51. */
  52. const cmdIds = {
  53. interrupt: 'notebook:interrupt-kernel',
  54. restart: 'notebook:restart-kernel',
  55. restartClear: 'notebook:restart-clear',
  56. restartRunAll: 'notebook:restart-runAll',
  57. switchKernel: 'notebook:switch-kernel',
  58. clearAllOutputs: 'notebook:clear-outputs',
  59. closeAndHalt: 'notebook:close-and-halt',
  60. run: 'notebook-cells:run',
  61. runAndAdvance: 'notebook-cells:run-and-advance',
  62. runAndInsert: 'notebook-cells:run-and-insert',
  63. runAll: 'notebook:run-all',
  64. toCode: 'notebook-cells:to-code',
  65. toMarkdown: 'notebook-cells:to-markdown',
  66. toRaw: 'notebook-cells:to-raw',
  67. cut: 'notebook-cells:cut',
  68. copy: 'notebook-cells:copy',
  69. paste: 'notebook-cells:paste',
  70. moveUp: 'notebook-cells:move-up',
  71. moveDown: 'notebook-cells:move-down',
  72. clearOutputs: 'notebook-cells:clear-output',
  73. deleteCell: 'notebook-cells:delete',
  74. insertAbove: 'notebook-cells:insert-above',
  75. insertBelow: 'notebook-cells:insert-below',
  76. selectAbove: 'notebook-cells:select-above',
  77. selectBelow: 'notebook-cells:select-below',
  78. extendAbove: 'notebook-cells:extend-above',
  79. extendBelow: 'notebook-cells:extend-below',
  80. editMode: 'notebook:edit-mode',
  81. merge: 'notebook-cells:merge',
  82. split: 'notebook-cells:split',
  83. commandMode: 'notebook:command-mode',
  84. toggleLines: 'notebook-cells:toggle-line-numbers',
  85. toggleAllLines: 'notebook-cells:toggle-all-line-numbers',
  86. undo: 'notebook-cells:undo',
  87. redo: 'notebook-cells:redo',
  88. markdown1: 'notebook-cells:markdown-header1',
  89. markdown2: 'notebook-cells:markdown-header2',
  90. markdown3: 'notebook-cells:markdown-header3',
  91. markdown4: 'notebook-cells:markdown-header4',
  92. markdown5: 'notebook-cells:markdown-header5',
  93. markdown6: 'notebook-cells:markdown-header6',
  94. };
  95. /**
  96. * The name of the factory that creates notebooks.
  97. */
  98. const FACTORY = 'Notebook';
  99. /**
  100. * The notebook widget tracker provider.
  101. */
  102. export
  103. const trackerPlugin: JupyterLabPlugin<INotebookTracker> = {
  104. id: 'jupyter.services.notebook-tracker',
  105. provides: INotebookTracker,
  106. requires: [
  107. IDocumentRegistry,
  108. IServiceManager,
  109. IRenderMime,
  110. IClipboard,
  111. IMainMenu,
  112. ICommandPalette,
  113. IInspector,
  114. NotebookPanel.IRenderer,
  115. ILayoutRestorer
  116. ],
  117. activate: activateNotebookHandler,
  118. autoStart: true
  119. };
  120. /**
  121. * The notebook renderer provider.
  122. */
  123. export
  124. const rendererPlugin: JupyterLabPlugin<NotebookPanel.IRenderer> = {
  125. id: 'jupyter.services.notebook-renderer',
  126. provides: NotebookPanel.IRenderer,
  127. requires: [IEditorServices],
  128. autoStart: true,
  129. activate: (app: JupyterLab, editorServices: IEditorServices) => {
  130. const notebookRenderer = new Notebook.Renderer({ editorServices });
  131. return new NotebookPanel.Renderer({ notebookRenderer });
  132. }
  133. };
  134. /**
  135. * Activate the notebook handler extension.
  136. */
  137. function activateNotebookHandler(app: JupyterLab, registry: IDocumentRegistry, services: IServiceManager, rendermime: IRenderMime, clipboard: IClipboard, mainMenu: IMainMenu, palette: ICommandPalette, inspector: IInspector, renderer: NotebookPanel.IRenderer, layout: ILayoutRestorer): INotebookTracker {
  138. const factory = new NotebookWidgetFactory({
  139. name: FACTORY,
  140. fileExtensions: ['.ipynb'],
  141. modelName: 'notebook',
  142. defaultFor: ['.ipynb'],
  143. preferKernel: true,
  144. canStartKernel: true,
  145. rendermime,
  146. clipboard,
  147. renderer
  148. });
  149. const tracker = new NotebookTracker({ namespace: 'notebook' });
  150. // Handle state restoration.
  151. layout.restore(tracker, {
  152. command: 'file-operations:open',
  153. args: panel => ({ path: panel.context.path, factory: FACTORY }),
  154. name: panel => panel.context.path,
  155. when: services.ready
  156. });
  157. // Sync tracker and set the source of the code inspector.
  158. app.shell.currentChanged.connect((sender, args) => {
  159. let widget = tracker.sync(args.newValue);
  160. if (widget) {
  161. inspector.source = widget.content.inspectionHandler;
  162. }
  163. });
  164. registry.addModelFactory(new NotebookModelFactory());
  165. registry.addWidgetFactory(factory);
  166. registry.addFileType({
  167. name: 'Notebook',
  168. extension: '.ipynb',
  169. contentType: 'notebook',
  170. fileFormat: 'json'
  171. });
  172. registry.addCreator({
  173. name: 'Notebook',
  174. fileType: 'Notebook',
  175. widgetName: 'Notebook'
  176. });
  177. addCommands(app, services, tracker);
  178. populatePalette(palette);
  179. let id = 0; // The ID counter for notebook panels.
  180. factory.widgetCreated.connect((sender, widget) => {
  181. // If the notebook panel does not have an ID, assign it one.
  182. widget.id = widget.id || `notebook-${++id}`;
  183. widget.title.icon = `${PORTRAIT_ICON_CLASS} ${NOTEBOOK_ICON_CLASS}`;
  184. // Immediately set the inspector source to the current notebook.
  185. inspector.source = widget.content.inspectionHandler;
  186. // Notify the instance tracker if restore data needs to update.
  187. widget.context.pathChanged.connect(() => { tracker.save(widget); });
  188. // Add the notebook panel to the tracker.
  189. tracker.add(widget);
  190. });
  191. // Add main menu notebook menu.
  192. mainMenu.addMenu(createMenu(app), { rank: 20 });
  193. return tracker;
  194. }
  195. /**
  196. * Add the notebook commands to the application's command registry.
  197. */
  198. function addCommands(app: JupyterLab, services: IServiceManager, tracker: NotebookTracker): void {
  199. let commands = app.commands;
  200. commands.addCommand(cmdIds.runAndAdvance, {
  201. label: 'Run Cell(s) and Advance',
  202. execute: () => {
  203. let current = tracker.currentWidget;
  204. if (current) {
  205. let content = current.content;
  206. NotebookActions.runAndAdvance(content, current.context.kernel);
  207. }
  208. }
  209. });
  210. commands.addCommand(cmdIds.run, {
  211. label: 'Run Cell(s)',
  212. execute: () => {
  213. let current = tracker.currentWidget;
  214. if (current) {
  215. NotebookActions.run(current.content, current.context.kernel);
  216. }
  217. }
  218. });
  219. commands.addCommand(cmdIds.runAndInsert, {
  220. label: 'Run Cell(s) and Insert',
  221. execute: () => {
  222. let current = tracker.currentWidget;
  223. if (current) {
  224. NotebookActions.runAndInsert(current.content, current.context.kernel);
  225. }
  226. }
  227. });
  228. commands.addCommand(cmdIds.runAll, {
  229. label: 'Run All Cells',
  230. execute: () => {
  231. let current = tracker.currentWidget;
  232. if (current) {
  233. NotebookActions.runAll(current.content, current.context.kernel);
  234. }
  235. }
  236. });
  237. commands.addCommand(cmdIds.restart, {
  238. label: 'Restart Kernel',
  239. execute: () => {
  240. let current = tracker.currentWidget;
  241. if (current) {
  242. restartKernel(current.kernel, current.node).then(() => {
  243. current.activate();
  244. });
  245. }
  246. }
  247. });
  248. commands.addCommand(cmdIds.closeAndHalt, {
  249. label: 'Close and Halt',
  250. execute: () => {
  251. let current = tracker.currentWidget;
  252. if (current) {
  253. current.context.changeKernel(null).then(() => { current.dispose(); });
  254. }
  255. }
  256. });
  257. commands.addCommand(cmdIds.restartClear, {
  258. label: 'Restart Kernel & Clear Outputs',
  259. execute: () => {
  260. let current = tracker.currentWidget;
  261. if (current) {
  262. let promise = restartKernel(current.kernel, current.node);
  263. promise.then(result => {
  264. current.activate();
  265. if (result) {
  266. NotebookActions.clearAllOutputs(current.content);
  267. }
  268. });
  269. }
  270. }
  271. });
  272. commands.addCommand(cmdIds.restartRunAll, {
  273. label: 'Restart Kernel & Run All',
  274. execute: () => {
  275. let current = tracker.currentWidget;
  276. if (current) {
  277. let promise = restartKernel(current.kernel, current.node);
  278. promise.then(result => {
  279. current.activate();
  280. NotebookActions.runAll(current.content, current.context.kernel);
  281. });
  282. }
  283. }
  284. });
  285. commands.addCommand(cmdIds.clearAllOutputs, {
  286. label: 'Clear All Outputs',
  287. execute: () => {
  288. let current = tracker.currentWidget;
  289. if (current) {
  290. NotebookActions.clearAllOutputs(current.content);
  291. }
  292. }
  293. });
  294. commands.addCommand(cmdIds.clearOutputs, {
  295. label: 'Clear Output(s)',
  296. execute: () => {
  297. let current = tracker.currentWidget;
  298. if (current) {
  299. NotebookActions.clearOutputs(current.content);
  300. }
  301. }
  302. });
  303. commands.addCommand(cmdIds.interrupt, {
  304. label: 'Interrupt Kernel',
  305. execute: () => {
  306. let current = tracker.currentWidget;
  307. if (current) {
  308. let kernel = current.context.kernel;
  309. if (kernel) {
  310. kernel.interrupt();
  311. }
  312. }
  313. }
  314. });
  315. commands.addCommand(cmdIds.toCode, {
  316. label: 'Convert to Code',
  317. execute: () => {
  318. let current = tracker.currentWidget;
  319. if (current) {
  320. NotebookActions.changeCellType(current.content, 'code');
  321. }
  322. }
  323. });
  324. commands.addCommand(cmdIds.toMarkdown, {
  325. label: 'Convert to Markdown',
  326. execute: () => {
  327. let current = tracker.currentWidget;
  328. if (current) {
  329. NotebookActions.changeCellType(current.content, 'markdown');
  330. }
  331. }
  332. });
  333. commands.addCommand(cmdIds.toRaw, {
  334. label: 'Convert to Raw',
  335. execute: () => {
  336. let current = tracker.currentWidget;
  337. if (current) {
  338. NotebookActions.changeCellType(current.content, 'raw');
  339. }
  340. }
  341. });
  342. commands.addCommand(cmdIds.cut, {
  343. label: 'Cut Cell(s)',
  344. execute: () => {
  345. let current = tracker.currentWidget;
  346. if (current) {
  347. NotebookActions.cut(current.content, current.clipboard);
  348. }
  349. }
  350. });
  351. commands.addCommand(cmdIds.copy, {
  352. label: 'Copy Cell(s)',
  353. execute: () => {
  354. let current = tracker.currentWidget;
  355. if (current) {
  356. NotebookActions.copy(current.content, current.clipboard);
  357. }
  358. }
  359. });
  360. commands.addCommand(cmdIds.paste, {
  361. label: 'Paste Cell(s)',
  362. execute: () => {
  363. let current = tracker.currentWidget;
  364. if (current) {
  365. NotebookActions.paste(current.content, current.clipboard);
  366. }
  367. }
  368. });
  369. commands.addCommand(cmdIds.deleteCell, {
  370. label: 'Delete Cell(s)',
  371. execute: () => {
  372. let current = tracker.currentWidget;
  373. if (current) {
  374. NotebookActions.deleteCells(current.content);
  375. }
  376. }
  377. });
  378. commands.addCommand(cmdIds.split, {
  379. label: 'Split Cell',
  380. execute: () => {
  381. let current = tracker.currentWidget;
  382. if (current) {
  383. NotebookActions.splitCell(current.content);
  384. }
  385. }
  386. });
  387. commands.addCommand(cmdIds.merge, {
  388. label: 'Merge Selected Cell(s)',
  389. execute: () => {
  390. let current = tracker.currentWidget;
  391. if (current) {
  392. NotebookActions.mergeCells(current.content);
  393. }
  394. }
  395. });
  396. commands.addCommand(cmdIds.insertAbove, {
  397. label: 'Insert Cell Above',
  398. execute: () => {
  399. let current = tracker.currentWidget;
  400. if (current) {
  401. NotebookActions.insertAbove(current.content);
  402. }
  403. }
  404. });
  405. commands.addCommand(cmdIds.insertBelow, {
  406. label: 'Insert Cell Below',
  407. execute: () => {
  408. let current = tracker.currentWidget;
  409. if (current) {
  410. NotebookActions.insertBelow(current.content);
  411. }
  412. }
  413. });
  414. commands.addCommand(cmdIds.selectAbove, {
  415. label: 'Select Cell Above',
  416. execute: () => {
  417. let current = tracker.currentWidget;
  418. if (current) {
  419. NotebookActions.selectAbove(current.content);
  420. }
  421. }
  422. });
  423. commands.addCommand(cmdIds.selectBelow, {
  424. label: 'Select Cell Below',
  425. execute: () => {
  426. let current = tracker.currentWidget;
  427. if (current) {
  428. NotebookActions.selectBelow(current.content);
  429. }
  430. }
  431. });
  432. commands.addCommand(cmdIds.extendAbove, {
  433. label: 'Extend Selection Above',
  434. execute: () => {
  435. let current = tracker.currentWidget;
  436. if (current) {
  437. NotebookActions.extendSelectionAbove(current.content);
  438. }
  439. }
  440. });
  441. commands.addCommand(cmdIds.extendBelow, {
  442. label: 'Extend Selection Below',
  443. execute: () => {
  444. let current = tracker.currentWidget;
  445. if (current) {
  446. NotebookActions.extendSelectionBelow(current.content);
  447. }
  448. }
  449. });
  450. commands.addCommand(cmdIds.moveUp, {
  451. label: 'Move Cell(s) Up',
  452. execute: () => {
  453. let current = tracker.currentWidget;
  454. if (current) {
  455. NotebookActions.moveUp(current.content);
  456. }
  457. }
  458. });
  459. commands.addCommand(cmdIds.moveDown, {
  460. label: 'Move Cell(s) Down',
  461. execute: () => {
  462. let current = tracker.currentWidget;
  463. if (current) {
  464. NotebookActions.moveDown(current.content);
  465. }
  466. }
  467. });
  468. commands.addCommand(cmdIds.toggleLines, {
  469. label: 'Toggle Line Numbers',
  470. execute: () => {
  471. let current = tracker.currentWidget;
  472. if (current) {
  473. NotebookActions.toggleLineNumbers(current.content);
  474. }
  475. }
  476. });
  477. commands.addCommand(cmdIds.toggleAllLines, {
  478. label: 'Toggle All Line Numbers',
  479. execute: () => {
  480. let current = tracker.currentWidget;
  481. if (current) {
  482. NotebookActions.toggleAllLineNumbers(current.content);
  483. }
  484. }
  485. });
  486. commands.addCommand(cmdIds.commandMode, {
  487. label: 'To Command Mode',
  488. execute: () => {
  489. let current = tracker.currentWidget;
  490. if (current) {
  491. current.content.mode = 'command';
  492. }
  493. }
  494. });
  495. commands.addCommand(cmdIds.editMode, {
  496. label: 'To Edit Mode',
  497. execute: () => {
  498. let current = tracker.currentWidget;
  499. if (current) {
  500. current.content.mode = 'edit';
  501. }
  502. }
  503. });
  504. commands.addCommand(cmdIds.undo, {
  505. label: 'Undo Cell Operation',
  506. execute: () => {
  507. let current = tracker.currentWidget;
  508. if (current) {
  509. NotebookActions.undo(current.content);
  510. }
  511. }
  512. });
  513. commands.addCommand(cmdIds.redo, {
  514. label: 'Redo Cell Operation',
  515. execute: () => {
  516. let current = tracker.currentWidget;
  517. if (current) {
  518. NotebookActions.redo(current.content);
  519. }
  520. }
  521. });
  522. commands.addCommand(cmdIds.switchKernel, {
  523. label: 'Switch Kernel',
  524. execute: () => {
  525. let current = tracker.currentWidget;
  526. if (current) {
  527. let context = current.context;
  528. let node = current.node;
  529. selectKernelForContext(context, services.sessions, node).then(() => {
  530. current.activate();
  531. });
  532. }
  533. }
  534. });
  535. commands.addCommand(cmdIds.markdown1, {
  536. label: 'Markdown Header 1',
  537. execute: () => {
  538. let current = tracker.currentWidget;
  539. if (current) {
  540. NotebookActions.setMarkdownHeader(current.content, 1);
  541. }
  542. }
  543. });
  544. commands.addCommand(cmdIds.markdown2, {
  545. label: 'Markdown Header 2',
  546. execute: () => {
  547. let current = tracker.currentWidget;
  548. if (current) {
  549. NotebookActions.setMarkdownHeader(current.content, 2);
  550. }
  551. }
  552. });
  553. commands.addCommand(cmdIds.markdown3, {
  554. label: 'Markdown Header 3',
  555. execute: () => {
  556. let current = tracker.currentWidget;
  557. if (current) {
  558. NotebookActions.setMarkdownHeader(current.content, 3);
  559. }
  560. }
  561. });
  562. commands.addCommand(cmdIds.markdown4, {
  563. label: 'Markdown Header 4',
  564. execute: () => {
  565. let current = tracker.currentWidget;
  566. if (current) {
  567. NotebookActions.setMarkdownHeader(current.content, 4);
  568. }
  569. }
  570. });
  571. commands.addCommand(cmdIds.markdown5, {
  572. label: 'Markdown Header 5',
  573. execute: () => {
  574. let current = tracker.currentWidget;
  575. if (current) {
  576. NotebookActions.setMarkdownHeader(current.content, 5);
  577. }
  578. }
  579. });
  580. commands.addCommand(cmdIds.markdown6, {
  581. label: 'Markdown Header 6',
  582. execute: () => {
  583. let current = tracker.currentWidget;
  584. if (current) {
  585. NotebookActions.setMarkdownHeader(current.content, 6);
  586. }
  587. }
  588. });
  589. }
  590. /**
  591. * Populate the application's command palette with notebook commands.
  592. */
  593. function populatePalette(palette: ICommandPalette): void {
  594. let category = 'Notebook Operations';
  595. [
  596. cmdIds.interrupt,
  597. cmdIds.restart,
  598. cmdIds.restartClear,
  599. cmdIds.restartRunAll,
  600. cmdIds.runAll,
  601. cmdIds.clearAllOutputs,
  602. cmdIds.toggleAllLines,
  603. cmdIds.editMode,
  604. cmdIds.commandMode,
  605. cmdIds.switchKernel,
  606. cmdIds.closeAndHalt
  607. ].forEach(command => { palette.addItem({ command, category }); });
  608. category = 'Notebook Cell Operations';
  609. [
  610. cmdIds.run,
  611. cmdIds.runAndAdvance,
  612. cmdIds.runAndInsert,
  613. cmdIds.clearOutputs,
  614. cmdIds.toCode,
  615. cmdIds.toMarkdown,
  616. cmdIds.toRaw,
  617. cmdIds.cut,
  618. cmdIds.copy,
  619. cmdIds.paste,
  620. cmdIds.deleteCell,
  621. cmdIds.split,
  622. cmdIds.merge,
  623. cmdIds.insertAbove,
  624. cmdIds.insertBelow,
  625. cmdIds.selectAbove,
  626. cmdIds.selectBelow,
  627. cmdIds.extendAbove,
  628. cmdIds.extendBelow,
  629. cmdIds.moveDown,
  630. cmdIds.moveUp,
  631. cmdIds.toggleLines,
  632. cmdIds.undo,
  633. cmdIds.redo,
  634. cmdIds.markdown1,
  635. cmdIds.markdown2,
  636. cmdIds.markdown3,
  637. cmdIds.markdown4,
  638. cmdIds.markdown5,
  639. cmdIds.markdown6
  640. ].forEach(command => { palette.addItem({ command, category }); });
  641. }
  642. /**
  643. * Creates a menu for the notebook.
  644. */
  645. function createMenu(app: JupyterLab): Menu {
  646. let { commands, keymap } = app;
  647. let menu = new Menu({ commands, keymap });
  648. let settings = new Menu({ commands, keymap });
  649. menu.title.label = 'Notebook';
  650. settings.title.label = 'Settings';
  651. settings.addItem({ command: cmdIds.toggleAllLines });
  652. menu.addItem({ command: cmdIds.undo });
  653. menu.addItem({ command: cmdIds.redo });
  654. menu.addItem({ command: cmdIds.split });
  655. menu.addItem({ command: cmdIds.deleteCell });
  656. menu.addItem({ command: cmdIds.clearAllOutputs });
  657. menu.addItem({ command: cmdIds.runAll });
  658. menu.addItem({ command: cmdIds.restart });
  659. menu.addItem({ command: cmdIds.switchKernel });
  660. menu.addItem({ command: cmdIds.closeAndHalt });
  661. menu.addItem({ type: 'separator' });
  662. menu.addItem({ type: 'submenu', menu: settings });
  663. return menu;
  664. }