@theia/notebook 1.61.0 → 1.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/lib/browser/contributions/notebook-actions-contribution.d.ts +2 -2
  2. package/lib/browser/contributions/notebook-actions-contribution.d.ts.map +1 -1
  3. package/lib/browser/contributions/notebook-actions-contribution.js +6 -8
  4. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  5. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts +2 -2
  6. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  7. package/lib/browser/contributions/notebook-cell-actions-contribution.js +11 -12
  8. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  9. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  10. package/lib/browser/notebook-frontend-module.js +2 -0
  11. package/lib/browser/notebook-frontend-module.js.map +1 -1
  12. package/lib/browser/service/notebook-cell-editor-service.d.ts +5 -1
  13. package/lib/browser/service/notebook-cell-editor-service.d.ts.map +1 -1
  14. package/lib/browser/service/notebook-cell-editor-service.js +12 -0
  15. package/lib/browser/service/notebook-cell-editor-service.js.map +1 -1
  16. package/lib/browser/service/notebook-context-manager.d.ts +1 -3
  17. package/lib/browser/service/notebook-context-manager.d.ts.map +1 -1
  18. package/lib/browser/service/notebook-context-manager.js +0 -10
  19. package/lib/browser/service/notebook-context-manager.js.map +1 -1
  20. package/lib/browser/view/notebook-cell-list-view.d.ts +2 -2
  21. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  22. package/lib/browser/view/notebook-cell-list-view.js +15 -8
  23. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  24. package/lib/browser/view/notebook-cell-toolbar-factory.d.ts +4 -2
  25. package/lib/browser/view/notebook-cell-toolbar-factory.d.ts.map +1 -1
  26. package/lib/browser/view/notebook-cell-toolbar-factory.js +30 -19
  27. package/lib/browser/view/notebook-cell-toolbar-factory.js.map +1 -1
  28. package/lib/browser/view/notebook-cell-toolbar.d.ts +1 -2
  29. package/lib/browser/view/notebook-cell-toolbar.d.ts.map +1 -1
  30. package/lib/browser/view/notebook-cell-toolbar.js +2 -4
  31. package/lib/browser/view/notebook-cell-toolbar.js.map +1 -1
  32. package/lib/browser/view/notebook-main-toolbar.d.ts +3 -4
  33. package/lib/browser/view/notebook-main-toolbar.d.ts.map +1 -1
  34. package/lib/browser/view/notebook-main-toolbar.js +22 -45
  35. package/lib/browser/view/notebook-main-toolbar.js.map +1 -1
  36. package/package.json +8 -8
  37. package/src/browser/contributions/notebook-actions-contribution.ts +7 -9
  38. package/src/browser/contributions/notebook-cell-actions-contribution.ts +14 -14
  39. package/src/browser/notebook-frontend-module.ts +2 -0
  40. package/src/browser/service/notebook-cell-editor-service.ts +13 -1
  41. package/src/browser/service/notebook-context-manager.ts +1 -15
  42. package/src/browser/view/notebook-cell-list-view.tsx +25 -18
  43. package/src/browser/view/notebook-cell-toolbar-factory.tsx +35 -23
  44. package/src/browser/view/notebook-cell-toolbar.tsx +3 -6
  45. package/src/browser/view/notebook-main-toolbar.tsx +23 -49
@@ -14,7 +14,7 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
 
17
- import { Command, CommandContribution, CommandHandler, CommandRegistry, CompoundMenuNodeRole, MenuContribution, MenuModelRegistry, nls } from '@theia/core';
17
+ import { Command, CommandContribution, CommandHandler, CommandRegistry, MenuContribution, MenuModelRegistry, nls } from '@theia/core';
18
18
  import { codicon, Key, KeybindingContribution, KeybindingRegistry, KeyCode, KeyModifier } from '@theia/core/lib/browser';
19
19
  import { inject, injectable, postConstruct } from '@theia/core/shared/inversify';
20
20
  import { NotebookModel } from '../view-model/notebook-model';
@@ -234,15 +234,17 @@ export class NotebookCellActionContribution implements MenuContribution, Command
234
234
  NotebookCellActionContribution.ADDITIONAL_ACTION_MENU,
235
235
  nls.localizeByDefault('More'),
236
236
  {
237
- icon: codicon('ellipsis'),
238
- role: CompoundMenuNodeRole.Submenu,
239
- order: '30'
237
+ sortString: '30',
238
+ icon: codicon('ellipsis')
240
239
  }
241
240
  );
242
241
 
243
- menus.registerIndependentSubmenu(NotebookCellActionContribution.CONTRIBUTED_CELL_ACTION_MENU, '', { role: CompoundMenuNodeRole.Flat });
242
+ menus.registerSubmenu(NotebookCellActionContribution.CONTRIBUTED_CELL_ACTION_MENU, '');
244
243
  // since contributions are adding to an independent submenu we have to manually add it to the more submenu
245
- menus.getMenu(NotebookCellActionContribution.ADDITIONAL_ACTION_MENU).addNode(menus.getMenuNode(NotebookCellActionContribution.CONTRIBUTED_CELL_ACTION_MENU));
244
+ menus.linkCompoundMenuNode({
245
+ newParentPath: NotebookCellActionContribution.ADDITIONAL_ACTION_MENU,
246
+ submenuPath: NotebookCellActionContribution.CONTRIBUTED_CELL_ACTION_MENU
247
+ });
246
248
 
247
249
  // code cell sidebar menu
248
250
  menus.registerMenuAction(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU, {
@@ -259,19 +261,17 @@ export class NotebookCellActionContribution implements MenuContribution, Command
259
261
  });
260
262
 
261
263
  // Notebook Cell extra execution options
262
- menus.registerIndependentSubmenu(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU,
264
+ menus.registerSubmenu(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU,
263
265
  nls.localizeByDefault('More...'),
264
- { role: CompoundMenuNodeRole.Flat, icon: codicon('chevron-down') });
266
+ { icon: codicon('chevron-down') });
265
267
  // menus.getMenu(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU).addNode(menus.getMenuNode(NotebookCellActionContribution.CONTRIBUTED_CELL_EXECUTION_MENU));
266
268
 
267
269
  // code cell output sidebar menu
268
270
  menus.registerSubmenu(
269
271
  NotebookCellActionContribution.ADDITIONAL_OUTPUT_SIDEBAR_MENU,
270
272
  nls.localizeByDefault('More'),
271
- {
272
- icon: codicon('ellipsis'),
273
- role: CompoundMenuNodeRole.Submenu
274
- });
273
+ { icon: codicon('ellipsis') }
274
+ );
275
275
  menus.registerMenuAction(NotebookCellActionContribution.ADDITIONAL_OUTPUT_SIDEBAR_MENU, {
276
276
  commandId: NotebookCellCommands.CLEAR_OUTPUTS_COMMAND.id,
277
277
  label: nls.localizeByDefault('Clear Cell Outputs'),
@@ -565,8 +565,8 @@ export class NotebookCellActionContribution implements MenuContribution, Command
565
565
  export namespace NotebookCellActionContribution {
566
566
  export const ACTION_MENU = ['notebook-cell-actions-menu'];
567
567
  export const ADDITIONAL_ACTION_MENU = [...ACTION_MENU, 'more'];
568
- export const CONTRIBUTED_CELL_ACTION_MENU = 'notebook/cell/title';
569
- export const CONTRIBUTED_CELL_EXECUTION_MENU = 'notebook/cell/execute';
568
+ export const CONTRIBUTED_CELL_ACTION_MENU = ['notebook/cell/title'];
569
+ export const CONTRIBUTED_CELL_EXECUTION_MENU = ['notebook/cell/execute'];
570
570
  export const CODE_CELL_SIDEBAR_MENU = ['code-cell-sidebar-menu'];
571
571
  export const OUTPUT_SIDEBAR_MENU = ['code-cell-output-sidebar-menu'];
572
572
  export const ADDITIONAL_OUTPUT_SIDEBAR_MENU = [...OUTPUT_SIDEBAR_MENU, 'more'];
@@ -53,6 +53,7 @@ import { NotebookStatusBarContribution } from './contributions/notebook-status-b
53
53
  import { NotebookCellEditorService } from './service/notebook-cell-editor-service';
54
54
  import { NotebookCellStatusBarService } from './service/notebook-cell-status-bar-service';
55
55
  import { MonacoEditorModelFilter } from '@theia/monaco/lib/browser/monaco-text-model-service';
56
+ import { ActiveMonacoEditorContribution } from '@theia/monaco/lib/browser/monaco-editor-service';
56
57
 
57
58
  export default new ContainerModule(bind => {
58
59
  bind(NotebookColorContribution).toSelf().inSingletonScope();
@@ -76,6 +77,7 @@ export default new ContainerModule(bind => {
76
77
  bind(NotebookKernelQuickPickService).toSelf().inSingletonScope();
77
78
  bind(NotebookClipboardService).toSelf().inSingletonScope();
78
79
  bind(NotebookCellEditorService).toSelf().inSingletonScope();
80
+ bind(ActiveMonacoEditorContribution).toService(NotebookCellEditorService);
79
81
  bind(NotebookCellStatusBarService).toSelf().inSingletonScope();
80
82
 
81
83
  bind(NotebookCellResourceResolver).toSelf().inSingletonScope();
@@ -19,13 +19,18 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify'
19
19
  import { SimpleMonacoEditor } from '@theia/monaco/lib/browser/simple-monaco-editor';
20
20
  import { NotebookEditorWidgetService } from './notebook-editor-widget-service';
21
21
  import { CellUri } from '../../common';
22
+ import { ActiveMonacoEditorContribution, MonacoEditorService } from '@theia/monaco/lib/browser/monaco-editor-service';
23
+ import { ICodeEditor } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorBrowser';
22
24
 
23
25
  @injectable()
24
- export class NotebookCellEditorService {
26
+ export class NotebookCellEditorService implements ActiveMonacoEditorContribution {
25
27
 
26
28
  @inject(NotebookEditorWidgetService)
27
29
  protected readonly notebookEditorWidgetService: NotebookEditorWidgetService;
28
30
 
31
+ @inject(MonacoEditorService)
32
+ protected readonly monacoEditorService: MonacoEditorService;
33
+
29
34
  protected onDidChangeCellEditorsEmitter = new Emitter<void>();
30
35
  readonly onDidChangeCellEditors = this.onDidChangeCellEditorsEmitter.event;
31
36
 
@@ -42,6 +47,8 @@ export class NotebookCellEditorService {
42
47
  // if defocus notebook editor or another notebook editor is focused, clear the active cell
43
48
  if (!editor || (this.currentActiveCell && CellUri.parse(this.currentActiveCell.uri)?.notebook.toString() !== editor?.model?.uri.toString())) {
44
49
  this.currentActiveCell = undefined;
50
+ // eslint-disable-next-line no-null/no-null
51
+ this.monacoEditorService.setActiveCodeEditor(null);
45
52
  this.onDidChangeFocusedCellEditorEmitter.fire(undefined);
46
53
  }
47
54
  });
@@ -64,6 +71,7 @@ export class NotebookCellEditorService {
64
71
  editorFocusChanged(editor?: SimpleMonacoEditor): void {
65
72
  if (editor) {
66
73
  this.currentActiveCell = editor;
74
+ this.monacoEditorService.setActiveCodeEditor(editor.getControl());
67
75
  this.onDidChangeFocusedCellEditorEmitter.fire(editor);
68
76
  }
69
77
  }
@@ -71,4 +79,8 @@ export class NotebookCellEditorService {
71
79
  getActiveCell(): SimpleMonacoEditor | undefined {
72
80
  return this.currentActiveCell;
73
81
  }
82
+
83
+ getActiveEditor(): ICodeEditor | undefined {
84
+ return this.getActiveCell()?.getControl();
85
+ }
74
86
  }
@@ -16,7 +16,7 @@
16
16
 
17
17
  import { inject, injectable } from '@theia/core/shared/inversify';
18
18
  import { ContextKeyChangeEvent, ContextKeyService, ContextMatcher, ScopedValueStore } from '@theia/core/lib/browser/context-key-service';
19
- import { DisposableCollection, Emitter } from '@theia/core';
19
+ import { DisposableCollection } from '@theia/core';
20
20
  import { NotebookKernelService } from './notebook-kernel-service';
21
21
  import {
22
22
  NOTEBOOK_CELL_EDITABLE,
@@ -43,9 +43,6 @@ export class NotebookContextManager {
43
43
 
44
44
  protected readonly toDispose = new DisposableCollection();
45
45
 
46
- protected readonly onDidChangeContextEmitter = new Emitter<ContextKeyChangeEvent>();
47
- readonly onDidChangeContext = this.onDidChangeContextEmitter.event;
48
-
49
46
  protected _context?: HTMLElement;
50
47
 
51
48
  scopedStore: ScopedValueStore;
@@ -72,14 +69,12 @@ export class NotebookContextManager {
72
69
  if (e.notebook.toString() === widget?.getResourceUri()?.toString()) {
73
70
  this.scopedStore.setContext(NOTEBOOK_KERNEL_SELECTED, !!e.newKernel);
74
71
  this.scopedStore.setContext(NOTEBOOK_KERNEL, e.newKernel);
75
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL]));
76
72
  }
77
73
  }));
78
74
 
79
75
  widget.model?.onDidChangeContent(events => {
80
76
  if (events.some(e => e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Output)) {
81
77
  this.scopedStore.setContext(NOTEBOOK_HAS_OUTPUTS, widget.model?.cells.some(cell => cell.outputs.length > 0));
82
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_HAS_OUTPUTS]));
83
78
  }
84
79
  });
85
80
 
@@ -91,23 +86,18 @@ export class NotebookContextManager {
91
86
  widget.model?.onDidChangeSelectedCell(e => {
92
87
  this.selectedCellChanged(e.cell);
93
88
  this.scopedStore.setContext(NOTEBOOK_CELL_FOCUSED, !!e);
94
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_FOCUSED]));
95
89
  });
96
90
 
97
91
  this.toDispose.push(this.executionStateService.onDidChangeExecution(e => {
98
92
  if (e.notebook.toString() === widget.model?.uri.toString()) {
99
93
  this.setCellContext(e.cellHandle, NOTEBOOK_CELL_EXECUTING, !!e.changed);
100
94
  this.setCellContext(e.cellHandle, NOTEBOOK_CELL_EXECUTION_STATE, e.changed?.state ?? 'idle');
101
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE]));
102
95
  }
103
96
  }));
104
97
 
105
98
  widget.onDidChangeOutputInputFocus(focus => {
106
99
  this.scopedStore.setContext(NOTEBOOK_OUTPUT_INPUT_FOCUSED, focus);
107
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_OUTPUT_INPUT_FOCUSED]));
108
100
  });
109
-
110
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_VIEW_TYPE, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL]));
111
101
  }
112
102
 
113
103
  protected cellDisposables = new DisposableCollection();
@@ -123,12 +113,8 @@ export class NotebookContextManager {
123
113
  this.cellDisposables.push(cell.onDidRequestCellEditChange(cellEdit => {
124
114
  this.scopedStore.setContext(NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, cellEdit);
125
115
  this.scopedStore.setContext(NOTEBOOK_CELL_EDITABLE, cell.cellKind === CellKind.Markup && !cellEdit);
126
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_MARKDOWN_EDIT_MODE]));
127
116
  }));
128
117
  }
129
-
130
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_TYPE]));
131
-
132
118
  }
133
119
 
134
120
  protected setCellContext(cellHandle: number, key: string, value: unknown): void {
@@ -19,7 +19,7 @@ import { NotebookCellModel } from '../view-model/notebook-cell-model';
19
19
  import { NotebookModel } from '../view-model/notebook-model';
20
20
  import { NotebookCellToolbarFactory } from './notebook-cell-toolbar-factory';
21
21
  import { animationFrame, onDomEvent } from '@theia/core/lib/browser';
22
- import { CommandRegistry, DisposableCollection, MenuModelRegistry, MenuNode, nls } from '@theia/core';
22
+ import { CommandMenu, CommandRegistry, DisposableCollection, MenuModelRegistry, nls } from '@theia/core';
23
23
  import { NotebookCommands, NotebookMenus } from '../contributions/notebook-actions-contribution';
24
24
  import { NotebookCellActionContribution } from '../contributions/notebook-cell-actions-contribution';
25
25
  import { NotebookContextManager } from '../service/notebook-context-manager';
@@ -126,7 +126,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
126
126
  <NotebookCellDivider
127
127
  menuRegistry={this.props.menuRegistry}
128
128
  isVisible={() => this.isEnabled()}
129
- onAddNewCell={(commandId: string) => this.onAddNewCell(commandId, index)}
129
+ onAddNewCell={handler => this.onAddNewCell(handler, index)}
130
130
  onDrop={e => this.onDrop(e, index)}
131
131
  onDragOver={e => this.onDragOver(e, cell, 'top')} />
132
132
  <CellDropIndicator visible={this.shouldRenderDragOverIndicator(cell, 'top')} />
@@ -173,7 +173,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
173
173
  <NotebookCellDivider
174
174
  menuRegistry={this.props.menuRegistry}
175
175
  isVisible={() => this.isEnabled()}
176
- onAddNewCell={(commandId: string) => this.onAddNewCell(commandId, this.props.notebookModel.cells.length)}
176
+ onAddNewCell={handler => this.onAddNewCell(handler, this.props.notebookModel.cells.length)}
177
177
  onDrop={e => this.onDrop(e, this.props.notebookModel.cells.length - 1)}
178
178
  onDragOver={e => this.onDragOver(e, this.props.notebookModel.cells[this.props.notebookModel.cells.length - 1], 'bottom')} />
179
179
  </ul>;
@@ -255,10 +255,10 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
255
255
  this.setState({ ...this.state, dragOverIndicator: undefined });
256
256
  }
257
257
 
258
- protected onAddNewCell(commandId: string, index: number): void {
258
+ protected onAddNewCell(handler: (...args: unknown[]) => void, index: number): void {
259
259
  if (this.isEnabled()) {
260
260
  this.props.commandRegistry.executeCommand(NotebookCommands.CHANGE_SELECTED_CELL.id, index - 1);
261
- this.props.commandRegistry.executeCommand(commandId,
261
+ handler(
262
262
  this.props.notebookModel,
263
263
  index
264
264
  );
@@ -276,7 +276,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
276
276
 
277
277
  export interface NotebookCellDividerProps {
278
278
  isVisible: () => boolean;
279
- onAddNewCell: (commandId: string) => void;
279
+ onAddNewCell: (createCommand: (...args: unknown[]) => void) => void;
280
280
  onDrop: (event: React.DragEvent<HTMLLIElement>) => void;
281
281
  onDragOver: (event: React.DragEvent<HTMLLIElement>) => void;
282
282
  menuRegistry: MenuModelRegistry;
@@ -286,21 +286,28 @@ export function NotebookCellDivider({ isVisible, onAddNewCell, onDrop, onDragOve
286
286
  const [hover, setHover] = React.useState(false);
287
287
 
288
288
  const menuPath = NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_CELL_ADD_GROUP;
289
- const menuItems = menuRegistry.getMenuNode(menuPath).children;
290
-
291
- const renderItem = (item: MenuNode): React.ReactNode => <button
292
- key={item.id}
293
- className='theia-notebook-add-cell-button'
294
- onClick={() => onAddNewCell(item.command || '')}
295
- title={nls.localizeByDefault(`Add ${item.label} Cell`)}
296
- >
297
- <div className={item.icon + ' theia-notebook-add-cell-button-icon'} />
298
- <div className='theia-notebook-add-cell-button-text'>{item.label}</div>
299
- </button>;
289
+ const menuItems: CommandMenu[] = menuRegistry.getMenu(menuPath).children.filter(item => CommandMenu.is(item)).map(item => item as CommandMenu);
290
+
291
+ const renderItem = (item: CommandMenu): React.ReactNode => {
292
+ const execute = (...args: unknown[]) => {
293
+ if (CommandMenu.is(item)) {
294
+ item.run([...menuPath, item.id], ...args);
295
+ }
296
+ };
297
+ return <button
298
+ key={item.id}
299
+ className='theia-notebook-add-cell-button'
300
+ onClick={() => onAddNewCell(execute)}
301
+ title={nls.localizeByDefault(`Add ${item.label} Cell`)}
302
+ >
303
+ <div className={item.icon + ' theia-notebook-add-cell-button-icon'} />
304
+ <div className='theia-notebook-add-cell-button-text'>{item.label}</div>
305
+ </button>;
306
+ };
300
307
 
301
308
  return <li className='theia-notebook-cell-divider' onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onDrop={onDrop} onDragOver={onDragOver}>
302
309
  {hover && isVisible() && <div className='theia-notebook-add-cell-buttons'>
303
- {menuItems.map((item: MenuNode) => renderItem(item))}
310
+ {menuItems.map((item: CommandMenu) => renderItem(item))}
304
311
  </div>}
305
312
  </li>;
306
313
  }
@@ -15,7 +15,7 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import * as React from '@theia/core/shared/react';
18
- import { CommandRegistry, CompoundMenuNodeRole, MenuModelRegistry, MenuNode } from '@theia/core';
18
+ import { CommandMenu, CommandRegistry, CompoundMenuNode, DisposableCollection, Emitter, Event, MenuModelRegistry, MenuPath, RenderedMenuNode } from '@theia/core';
19
19
  import { inject, injectable } from '@theia/core/shared/inversify';
20
20
  import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
21
21
  import { NotebookCellSidebar, NotebookCellToolbar } from './notebook-cell-toolbar';
@@ -29,7 +29,6 @@ export interface NotebookCellToolbarItem {
29
29
  label?: string;
30
30
  onClick: (e: React.MouseEvent) => void;
31
31
  isVisible: () => boolean;
32
- contextKeys?: Set<string>
33
32
  }
34
33
 
35
34
  export interface toolbarItemOptions {
@@ -55,48 +54,61 @@ export class NotebookCellToolbarFactory {
55
54
  @inject(NotebookContextManager)
56
55
  protected readonly notebookContextManager: NotebookContextManager;
57
56
 
57
+ protected readonly onDidChangeContextEmitter = new Emitter<void>;
58
+ readonly onDidChangeContext: Event<void> = this.onDidChangeContextEmitter.event;
59
+
60
+ protected toDisposeOnRender = new DisposableCollection();
61
+
58
62
  renderCellToolbar(menuPath: string[], cell: NotebookCellModel, itemOptions: toolbarItemOptions): React.ReactNode {
59
63
  return <NotebookCellToolbar getMenuItems={() => this.getMenuItems(menuPath, cell, itemOptions)}
60
- onContextKeysChanged={this.notebookContextManager.onDidChangeContext} />;
64
+ onContextChanged={this.onDidChangeContext} />;
61
65
  }
62
66
 
63
67
  renderSidebar(menuPath: string[], cell: NotebookCellModel, itemOptions: toolbarItemOptions): React.ReactNode {
64
68
  return <NotebookCellSidebar getMenuItems={() => this.getMenuItems(menuPath, cell, itemOptions)}
65
- onContextKeysChanged={this.notebookContextManager.onDidChangeContext} />;
69
+ onContextChanged={this.onDidChangeContext} />;
66
70
  }
67
71
 
68
72
  private getMenuItems(menuItemPath: string[], cell: NotebookCellModel, itemOptions: toolbarItemOptions): NotebookCellToolbarItem[] {
73
+ this.toDisposeOnRender.dispose();
74
+ this.toDisposeOnRender = new DisposableCollection();
69
75
  const inlineItems: NotebookCellToolbarItem[] = [];
70
76
  for (const menuNode of this.menuRegistry.getMenu(menuItemPath).children) {
71
- if (!menuNode.when || this.notebookContextManager.getCellContext(cell.handle).match(menuNode.when, this.notebookContextManager.context)) {
72
- if (menuNode.role === CompoundMenuNodeRole.Flat) {
73
- inlineItems.push(...menuNode.children?.map(child => this.createToolbarItem(child, itemOptions)) ?? []);
74
- } else {
75
- inlineItems.push(this.createToolbarItem(menuNode, itemOptions));
77
+
78
+ const itemPath = [...menuItemPath, menuNode.id];
79
+ if (menuNode.isVisible(itemPath, this.notebookContextManager.getCellContext(cell.handle), this.notebookContextManager.context, itemOptions.commandArgs?.() ?? [])) {
80
+ if (RenderedMenuNode.is(menuNode)) {
81
+ if (menuNode.onDidChange) {
82
+ this.toDisposeOnRender.push(menuNode.onDidChange(() => this.onDidChangeContextEmitter.fire()));
83
+ }
84
+ inlineItems.push(this.createToolbarItem(itemPath, menuNode, itemOptions));
76
85
  }
77
86
  }
78
87
  }
79
88
  return inlineItems;
80
89
  }
81
90
 
82
- private createToolbarItem(menuNode: MenuNode, itemOptions: toolbarItemOptions): NotebookCellToolbarItem {
83
- const menuPath = menuNode.role === CompoundMenuNodeRole.Submenu ? this.menuRegistry.getPath(menuNode) : undefined;
91
+ private createToolbarItem(menuPath: MenuPath, menuNode: RenderedMenuNode, itemOptions: toolbarItemOptions): NotebookCellToolbarItem {
84
92
  return {
85
93
  id: menuNode.id,
86
94
  icon: menuNode.icon,
87
95
  label: menuNode.label,
88
- onClick: menuPath ?
89
- e => this.contextMenuRenderer.render(
90
- {
91
- anchor: e.nativeEvent,
92
- menuPath,
93
- includeAnchorArg: false,
94
- args: itemOptions.contextMenuArgs?.(),
95
- context: this.notebookContextManager.context || (e.currentTarget as HTMLElement)
96
- }) :
97
- () => this.commandRegistry.executeCommand(menuNode.command!, ...(itemOptions.commandArgs?.() ?? [])),
98
- isVisible: () => menuPath ? true : Boolean(this.commandRegistry.getVisibleHandler(menuNode.command!, ...(itemOptions.commandArgs?.() ?? []))),
99
- contextKeys: menuNode.when ? this.contextKeyService.parseKeys(menuNode.when) : undefined
96
+ onClick: e => {
97
+ if (CompoundMenuNode.is(menuNode)) {
98
+ this.contextMenuRenderer.render(
99
+ {
100
+ anchor: e.nativeEvent,
101
+ menuPath: menuPath,
102
+ menu: menuNode,
103
+ includeAnchorArg: false,
104
+ args: itemOptions.contextMenuArgs?.(),
105
+ context: this.notebookContextManager.context || (e.currentTarget as HTMLElement)
106
+ });
107
+ } else if (CommandMenu.is(menuNode)) {
108
+ menuNode.run(menuPath, ...(itemOptions.commandArgs?.() ?? []));
109
+ };
110
+ },
111
+ isVisible: () => true
100
112
  };
101
113
  }
102
114
  }
@@ -17,11 +17,10 @@ import * as React from '@theia/core/shared/react';
17
17
  import { ACTION_ITEM } from '@theia/core/lib/browser';
18
18
  import { NotebookCellToolbarItem } from './notebook-cell-toolbar-factory';
19
19
  import { DisposableCollection, Event } from '@theia/core';
20
- import { ContextKeyChangeEvent } from '@theia/core/lib/browser/context-key-service';
21
20
 
22
21
  export interface NotebookCellToolbarProps {
23
22
  getMenuItems: () => NotebookCellToolbarItem[];
24
- onContextKeysChanged: Event<ContextKeyChangeEvent>;
23
+ onContextChanged: Event<void>;
25
24
  }
26
25
 
27
26
  interface NotebookCellToolbarState {
@@ -34,11 +33,9 @@ abstract class NotebookCellActionBar extends React.Component<NotebookCellToolbar
34
33
 
35
34
  constructor(props: NotebookCellToolbarProps) {
36
35
  super(props);
37
- this.toDispose.push(props.onContextKeysChanged(e => {
36
+ this.toDispose.push(props.onContextChanged(e => {
38
37
  const menuItems = this.props.getMenuItems();
39
- if (menuItems.some(item => item.contextKeys ? e.affects(item.contextKeys) : false)) {
40
- this.setState({ inlineItems: menuItems });
41
- }
38
+ this.setState({ inlineItems: menuItems });
42
39
  }));
43
40
  this.state = { inlineItems: this.props.getMenuItems() };
44
41
  }
@@ -13,7 +13,7 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
- import { ArrayUtils, CommandRegistry, CompoundMenuNodeRole, DisposableCollection, MenuModelRegistry, MenuNode, nls } from '@theia/core';
16
+ import { ArrayUtils, CommandMenu, CommandRegistry, DisposableCollection, Group, GroupImpl, MenuModelRegistry, MenuNode, MenuPath, nls } from '@theia/core';
17
17
  import * as React from '@theia/core/shared/react';
18
18
  import { codicon, ContextMenuRenderer } from '@theia/core/lib/browser';
19
19
  import { NotebookCommands, NotebookMenus } from '../contributions/notebook-actions-contribution';
@@ -21,7 +21,6 @@ import { NotebookModel } from '../view-model/notebook-model';
21
21
  import { NotebookKernelService } from '../service/notebook-kernel-service';
22
22
  import { inject, injectable } from '@theia/core/shared/inversify';
23
23
  import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
24
- import { NotebookCommand } from '../../common';
25
24
  import { NotebookContextManager } from '../service/notebook-context-manager';
26
25
 
27
26
  export interface NotebookMainToolbarProps {
@@ -97,19 +96,12 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
97
96
  }));
98
97
 
99
98
  // TODO maybe we need a mechanism to check for changes in the menu to update this toolbar
100
- const contextKeys = new Set<string>();
101
- this.getAllContextKeys(this.getMenuItems(), contextKeys);
102
- props.notebookContextManager.onDidChangeContext(e => {
103
- if (e.affects(contextKeys)) {
104
- this.forceUpdate();
105
- }
106
- });
107
- props.contextKeyService.onDidChange(e => {
108
- if (e.affects(contextKeys)) {
109
- this.forceUpdate();
99
+ const menuItems = this.getMenuItems();
100
+ for (const item of menuItems) {
101
+ if (item.onDidChange) {
102
+ item.onDidChange(() => this.forceUpdate());
110
103
  }
111
- });
112
-
104
+ }
113
105
  }
114
106
 
115
107
  override componentWillUnmount(): void {
@@ -137,14 +129,16 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
137
129
 
138
130
  protected renderContextMenu(event: MouseEvent, menuItems: readonly MenuNode[]): void {
139
131
  const hiddenItems = menuItems.slice(menuItems.length - this.calculateNumberOfHiddenItems(menuItems));
140
- const contextMenu = this.props.menuRegistry.getMenu([NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_HIDDEN_ITEMS_CONTEXT_MENU]);
141
132
 
142
- contextMenu.children.map(item => item.id).forEach(id => contextMenu.removeNode(id));
143
- hiddenItems.forEach(item => contextMenu.addNode(item));
133
+ const menu = new GroupImpl(NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_HIDDEN_ITEMS_CONTEXT_MENU[0]);
134
+
135
+ hiddenItems.forEach(item => menu.addNode(item));
144
136
 
145
137
  this.props.contextMenuRenderer.render({
146
138
  anchor: event,
147
- menuPath: [NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_HIDDEN_ITEMS_CONTEXT_MENU],
139
+ menuPath: NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_HIDDEN_ITEMS_CONTEXT_MENU,
140
+ menu: menu,
141
+ contextKeyService: this.props.contextKeyService,
148
142
  context: this.props.editorNode,
149
143
  args: [this.props.notebookModel.uri]
150
144
  });
@@ -153,7 +147,7 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
153
147
  override render(): React.ReactNode {
154
148
  const menuItems = this.getMenuItems();
155
149
  return <div className='theia-notebook-main-toolbar' id='notebook-main-toolbar'>
156
- {menuItems.slice(0, menuItems.length - this.calculateNumberOfHiddenItems(menuItems)).map(item => this.renderMenuItem(item))}
150
+ {menuItems.slice(0, menuItems.length - this.calculateNumberOfHiddenItems(menuItems)).map(item => this.renderMenuItem(NotebookMenus.NOTEBOOK_MAIN_TOOLBAR, item))}
157
151
  {
158
152
  this.state.numberOfHiddenItems > 0 &&
159
153
  <span className={`${codicon('ellipsis')} action-label theia-notebook-main-toolbar-item`} onClick={e => this.renderContextMenu(e.nativeEvent, menuItems)} />
@@ -180,51 +174,31 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
180
174
  }
181
175
  }
182
176
 
183
- protected renderMenuItem(item: MenuNode, submenu?: string): React.ReactNode {
184
- if (item.role === CompoundMenuNodeRole.Group) {
185
- const itemNodes = ArrayUtils.coalesce(item.children?.map(child => this.renderMenuItem(child, item.id)) ?? []);
177
+ protected renderMenuItem<T>(itemPath: MenuPath, item: MenuNode, submenu?: string): React.ReactNode {
178
+ if (Group.is(item)) {
179
+ const itemNodes = ArrayUtils.coalesce(item.children?.map(child => this.renderMenuItem([...itemPath, child.id], child, item.id)) ?? []);
186
180
  return <React.Fragment key={item.id}>
187
181
  {itemNodes}
188
182
  {itemNodes && itemNodes.length > 0 && <span key={`${item.id}-separator`} className='theia-notebook-toolbar-separator'></span>}
189
183
  </React.Fragment>;
190
- } else if ((this.nativeSubmenus.includes(submenu ?? '')) || !item.when || this.props.contextKeyService.match(item.when, this.props.editorNode)) {
191
- const visibleCommand = Boolean(this.props.commandRegistry.getVisibleHandler(item.command ?? '', this.props.notebookModel));
192
- if (!visibleCommand) {
193
- return undefined;
194
- }
195
- const command = this.props.commandRegistry.getCommand(item.command ?? '') as NotebookCommand | undefined;
196
- const label = command?.shortTitle ?? item.label;
197
- const title = command?.tooltip ?? item.label;
198
- return <div key={item.id} id={item.id} title={title} className={`theia-notebook-main-toolbar-item action-label${this.getAdditionalClasses(item)}`}
184
+ } else if (CommandMenu.is(item) && ((this.nativeSubmenus.includes(submenu ?? '')) || item.isVisible(itemPath, this.props.contextKeyService, this.props.editorNode))) {
185
+ return <div key={item.id} id={item.id} title={item.label} className={`theia-notebook-main-toolbar-item action-label${this.getAdditionalClasses(itemPath, item)}`}
199
186
  onClick={() => {
200
- if (item.command && (!item.when || this.props.contextKeyService.match(item.when, this.props.editorNode))) {
201
- this.props.commandRegistry.executeCommand(item.command, this.props.notebookModel.uri);
202
- }
187
+ item.run(itemPath, this.props.notebookModel.uri);
203
188
  }}>
204
189
  <span className={item.icon} />
205
- <span className='theia-notebook-main-toolbar-item-text'>{label}</span>
190
+ <span className='theia-notebook-main-toolbar-item-text'>{item.label}</span>
206
191
  </div>;
207
192
  }
208
193
  return undefined;
209
194
  }
210
195
 
211
196
  protected getMenuItems(): readonly MenuNode[] {
212
- const menuPath = NotebookMenus.NOTEBOOK_MAIN_TOOLBAR;
213
- const pluginCommands = this.props.menuRegistry.getMenuNode(menuPath).children;
214
- const theiaCommands = this.props.menuRegistry.getMenu([menuPath]).children;
215
- return theiaCommands.concat(pluginCommands);
197
+ return this.props.menuRegistry.getMenu(NotebookMenus.NOTEBOOK_MAIN_TOOLBAR).children;
216
198
  }
217
199
 
218
- protected getAdditionalClasses(item: MenuNode): string {
219
- return !item.when || this.props.contextKeyService.match(item.when, this.props.editorNode) ? '' : ' theia-mod-disabled';
220
- }
221
-
222
- protected getAllContextKeys(menus: readonly MenuNode[], keySet: Set<string>): void {
223
- menus.filter(item => item.when)
224
- .forEach(item => this.props.contextKeyService.parseKeys(item.when!)?.forEach(key => keySet.add(key)));
225
-
226
- menus.filter(item => item.children && item.children.length > 0)
227
- .forEach(item => this.getAllContextKeys(item.children!, keySet));
200
+ protected getAdditionalClasses(itemPath: MenuPath, item: CommandMenu): string {
201
+ return item.isEnabled(itemPath, this.props.editorNode) ? '' : ' theia-mod-disabled';
228
202
  }
229
203
 
230
204
  protected calculateNumberOfHiddenItems(allMenuItems: readonly MenuNode[]): number {