@theia/notebook 1.48.2 → 1.49.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 (124) hide show
  1. package/lib/browser/contributions/notebook-actions-contribution.d.ts +7 -1
  2. package/lib/browser/contributions/notebook-actions-contribution.d.ts.map +1 -1
  3. package/lib/browser/contributions/notebook-actions-contribution.js +81 -7
  4. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  5. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts +5 -0
  6. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  7. package/lib/browser/contributions/notebook-cell-actions-contribution.js +86 -9
  8. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  9. package/lib/browser/contributions/notebook-label-provider-contribution.d.ts +16 -0
  10. package/lib/browser/contributions/notebook-label-provider-contribution.d.ts.map +1 -0
  11. package/lib/browser/contributions/notebook-label-provider-contribution.js +65 -0
  12. package/lib/browser/contributions/notebook-label-provider-contribution.js.map +1 -0
  13. package/lib/browser/contributions/notebook-outline-contribution.d.ts +30 -0
  14. package/lib/browser/contributions/notebook-outline-contribution.d.ts.map +1 -0
  15. package/lib/browser/contributions/notebook-outline-contribution.js +109 -0
  16. package/lib/browser/contributions/notebook-outline-contribution.js.map +1 -0
  17. package/lib/browser/contributions/notebook-output-action-contribution.d.ts +16 -0
  18. package/lib/browser/contributions/notebook-output-action-contribution.d.ts.map +1 -0
  19. package/lib/browser/contributions/notebook-output-action-contribution.js +85 -0
  20. package/lib/browser/contributions/notebook-output-action-contribution.js.map +1 -0
  21. package/lib/browser/contributions/notebook-preferences.d.ts +4 -0
  22. package/lib/browser/contributions/notebook-preferences.d.ts.map +1 -0
  23. package/lib/browser/contributions/notebook-preferences.js +31 -0
  24. package/lib/browser/contributions/notebook-preferences.js.map +1 -0
  25. package/lib/browser/notebook-cell-resource-resolver.d.ts +4 -0
  26. package/lib/browser/notebook-cell-resource-resolver.d.ts.map +1 -1
  27. package/lib/browser/notebook-cell-resource-resolver.js +35 -2
  28. package/lib/browser/notebook-cell-resource-resolver.js.map +1 -1
  29. package/lib/browser/notebook-editor-widget-factory.d.ts +1 -0
  30. package/lib/browser/notebook-editor-widget-factory.d.ts.map +1 -1
  31. package/lib/browser/notebook-editor-widget-factory.js +17 -3
  32. package/lib/browser/notebook-editor-widget-factory.js.map +1 -1
  33. package/lib/browser/notebook-editor-widget.d.ts +5 -1
  34. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  35. package/lib/browser/notebook-editor-widget.js +20 -2
  36. package/lib/browser/notebook-editor-widget.js.map +1 -1
  37. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  38. package/lib/browser/notebook-frontend-module.js +15 -2
  39. package/lib/browser/notebook-frontend-module.js.map +1 -1
  40. package/lib/browser/notebook-open-handler.d.ts +14 -9
  41. package/lib/browser/notebook-open-handler.d.ts.map +1 -1
  42. package/lib/browser/notebook-open-handler.js +38 -16
  43. package/lib/browser/notebook-open-handler.js.map +1 -1
  44. package/lib/browser/notebook-type-registry.d.ts +5 -1
  45. package/lib/browser/notebook-type-registry.d.ts.map +1 -1
  46. package/lib/browser/notebook-type-registry.js +27 -7
  47. package/lib/browser/notebook-type-registry.js.map +1 -1
  48. package/lib/browser/notebook-types.d.ts +2 -0
  49. package/lib/browser/notebook-types.d.ts.map +1 -1
  50. package/lib/browser/notebook-types.js.map +1 -1
  51. package/lib/browser/service/notebook-clipboard-service.d.ts +10 -0
  52. package/lib/browser/service/notebook-clipboard-service.d.ts.map +1 -0
  53. package/lib/browser/service/notebook-clipboard-service.js +42 -0
  54. package/lib/browser/service/notebook-clipboard-service.js.map +1 -0
  55. package/lib/browser/service/notebook-context-manager.d.ts +4 -1
  56. package/lib/browser/service/notebook-context-manager.d.ts.map +1 -1
  57. package/lib/browser/service/notebook-context-manager.js +33 -12
  58. package/lib/browser/service/notebook-context-manager.js.map +1 -1
  59. package/lib/browser/service/notebook-service.d.ts.map +1 -1
  60. package/lib/browser/service/notebook-service.js +4 -0
  61. package/lib/browser/service/notebook-service.js.map +1 -1
  62. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  63. package/lib/browser/view/notebook-cell-editor.js +11 -2
  64. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  65. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  66. package/lib/browser/view/notebook-cell-list-view.js +4 -2
  67. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  68. package/lib/browser/view/notebook-cell-toolbar-factory.d.ts +6 -4
  69. package/lib/browser/view/notebook-cell-toolbar-factory.d.ts.map +1 -1
  70. package/lib/browser/view/notebook-cell-toolbar-factory.js +21 -18
  71. package/lib/browser/view/notebook-cell-toolbar-factory.js.map +1 -1
  72. package/lib/browser/view/notebook-cell-toolbar.d.ts.map +1 -1
  73. package/lib/browser/view/notebook-cell-toolbar.js +3 -2
  74. package/lib/browser/view/notebook-cell-toolbar.js.map +1 -1
  75. package/lib/browser/view/notebook-code-cell-view.d.ts +5 -1
  76. package/lib/browser/view/notebook-code-cell-view.d.ts.map +1 -1
  77. package/lib/browser/view/notebook-code-cell-view.js +45 -17
  78. package/lib/browser/view/notebook-code-cell-view.js.map +1 -1
  79. package/lib/browser/view/notebook-main-toolbar.d.ts +5 -3
  80. package/lib/browser/view/notebook-main-toolbar.d.ts.map +1 -1
  81. package/lib/browser/view/notebook-main-toolbar.js +17 -9
  82. package/lib/browser/view/notebook-main-toolbar.js.map +1 -1
  83. package/lib/browser/view/notebook-markdown-cell-view.js +11 -8
  84. package/lib/browser/view/notebook-markdown-cell-view.js.map +1 -1
  85. package/lib/browser/view-model/notebook-cell-model.d.ts +20 -0
  86. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  87. package/lib/browser/view-model/notebook-cell-model.js +61 -2
  88. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  89. package/lib/browser/view-model/notebook-model.d.ts +4 -2
  90. package/lib/browser/view-model/notebook-model.d.ts.map +1 -1
  91. package/lib/browser/view-model/notebook-model.js +25 -14
  92. package/lib/browser/view-model/notebook-model.js.map +1 -1
  93. package/lib/common/notebook-common.d.ts +7 -1
  94. package/lib/common/notebook-common.d.ts.map +1 -1
  95. package/lib/common/notebook-common.js +28 -4
  96. package/lib/common/notebook-common.js.map +1 -1
  97. package/package.json +9 -7
  98. package/src/browser/contributions/notebook-actions-contribution.ts +90 -9
  99. package/src/browser/contributions/notebook-cell-actions-contribution.ts +88 -11
  100. package/src/browser/contributions/notebook-label-provider-contribution.ts +63 -0
  101. package/src/browser/contributions/notebook-outline-contribution.ts +112 -0
  102. package/src/browser/contributions/notebook-output-action-contribution.ts +82 -0
  103. package/src/browser/contributions/notebook-preferences.ts +31 -0
  104. package/src/browser/notebook-cell-resource-resolver.ts +39 -1
  105. package/src/browser/notebook-editor-widget-factory.ts +18 -5
  106. package/src/browser/notebook-editor-widget.tsx +20 -2
  107. package/src/browser/notebook-frontend-module.ts +20 -4
  108. package/src/browser/notebook-open-handler.ts +48 -20
  109. package/src/browser/notebook-type-registry.ts +26 -6
  110. package/src/browser/notebook-types.ts +2 -0
  111. package/src/browser/service/notebook-clipboard-service.ts +43 -0
  112. package/src/browser/service/notebook-context-manager.ts +36 -10
  113. package/src/browser/service/notebook-service.ts +4 -0
  114. package/src/browser/style/index.css +19 -4
  115. package/src/browser/view/notebook-cell-editor.tsx +12 -2
  116. package/src/browser/view/notebook-cell-list-view.tsx +5 -2
  117. package/src/browser/view/notebook-cell-toolbar-factory.tsx +17 -15
  118. package/src/browser/view/notebook-cell-toolbar.tsx +3 -2
  119. package/src/browser/view/notebook-code-cell-view.tsx +51 -18
  120. package/src/browser/view/notebook-main-toolbar.tsx +20 -11
  121. package/src/browser/view/notebook-markdown-cell-view.tsx +12 -7
  122. package/src/browser/view-model/notebook-cell-model.ts +70 -2
  123. package/src/browser/view-model/notebook-model.ts +29 -16
  124. package/src/common/notebook-common.ts +29 -4
@@ -13,22 +13,42 @@
13
13
  //
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
- import { Disposable } from '@theia/core';
17
- import { injectable } from '@theia/core/shared/inversify';
16
+
17
+ import { Disposable, DisposableCollection } from '@theia/core';
18
+ import { inject, injectable } from '@theia/core/shared/inversify';
19
+ import { OpenWithService } from '@theia/core/lib/browser';
18
20
  import { NotebookTypeDescriptor } from '../common/notebook-protocol';
21
+ import { NotebookOpenHandler } from './notebook-open-handler';
19
22
 
20
23
  @injectable()
21
24
  export class NotebookTypeRegistry {
25
+
26
+ @inject(OpenWithService)
27
+ protected readonly openWithService: OpenWithService;
28
+
29
+ @inject(NotebookOpenHandler)
30
+ protected readonly notebookOpenHandler: NotebookOpenHandler;
31
+
22
32
  private readonly _notebookTypes: NotebookTypeDescriptor[] = [];
23
33
 
24
34
  get notebookTypes(): readonly NotebookTypeDescriptor[] {
25
35
  return this._notebookTypes;
26
36
  }
27
37
 
28
- registerNotebookType(type: NotebookTypeDescriptor): Disposable {
29
- this._notebookTypes.push(type);
30
- return Disposable.create(() => {
38
+ registerNotebookType(type: NotebookTypeDescriptor, providerName: string): Disposable {
39
+ const toDispose = new DisposableCollection();
40
+ toDispose.push(Disposable.create(() => {
31
41
  this._notebookTypes.splice(this._notebookTypes.indexOf(type), 1);
32
- });
42
+ }));
43
+ this._notebookTypes.push(type);
44
+ toDispose.push(this.notebookOpenHandler.registerNotebookType(type));
45
+ toDispose.push(this.openWithService.registerHandler({
46
+ id: type.type,
47
+ label: type.displayName,
48
+ providerName,
49
+ canHandle: uri => this.notebookOpenHandler.canHandleType(uri, type),
50
+ open: uri => this.notebookOpenHandler.open(uri, { notebookType: type.type })
51
+ }));
52
+ return toDispose;
33
53
  }
34
54
  }
@@ -111,6 +111,7 @@ export interface CellOutputEdit {
111
111
  editType: CellEditType.Output;
112
112
  index: number;
113
113
  outputs: CellOutput[];
114
+ deleteCount?: number;
114
115
  append?: boolean;
115
116
  }
116
117
 
@@ -118,6 +119,7 @@ export interface CellOutputEditByHandle {
118
119
  editType: CellEditType.Output;
119
120
  handle: number;
120
121
  outputs: CellOutput[];
122
+ deleteCount?: number;
121
123
  append?: boolean;
122
124
  }
123
125
 
@@ -0,0 +1,43 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 Typefox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { ClipboardService } from '@theia/core/lib/browser/clipboard-service';
19
+ import { NotebookCellModel } from '../view-model/notebook-cell-model';
20
+ import { environment } from '@theia/core';
21
+ import { CellData } from '../../common';
22
+
23
+ @injectable()
24
+ export class NotebookClipboardService {
25
+
26
+ protected copiedCell: CellData | undefined;
27
+
28
+ @inject(ClipboardService)
29
+ protected readonly clipboardService: ClipboardService;
30
+
31
+ copyCell(cell: NotebookCellModel): void {
32
+ this.copiedCell = cell.getData();
33
+
34
+ if (environment.electron.is()) {
35
+ this.clipboardService.writeText(cell.text);
36
+ }
37
+ }
38
+
39
+ getCell(): CellData | undefined {
40
+ return this.copiedCell;
41
+ }
42
+
43
+ }
@@ -15,19 +15,19 @@
15
15
  // *****************************************************************************
16
16
 
17
17
  import { inject, injectable } from '@theia/core/shared/inversify';
18
- import { ContextKeyChangeEvent, ContextKeyService, ScopedValueStore } from '@theia/core/lib/browser/context-key-service';
18
+ import { ContextKeyChangeEvent, ContextKeyService, ContextMatcher, ScopedValueStore } from '@theia/core/lib/browser/context-key-service';
19
19
  import { DisposableCollection, Emitter } from '@theia/core';
20
20
  import { NotebookKernelService } from './notebook-kernel-service';
21
21
  import {
22
22
  NOTEBOOK_CELL_EDITABLE,
23
23
  NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE,
24
24
  NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE,
25
- NOTEBOOK_CELL_TYPE, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_SELECTED,
25
+ NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_SELECTED,
26
26
  NOTEBOOK_VIEW_TYPE
27
27
  } from '../contributions/notebook-context-keys';
28
28
  import { NotebookEditorWidget } from '../notebook-editor-widget';
29
29
  import { NotebookCellModel } from '../view-model/notebook-cell-model';
30
- import { CellKind } from '../../common';
30
+ import { CellKind, NotebookCellsChangeType } from '../../common';
31
31
  import { NotebookExecutionStateService } from './notebook-execution-state-service';
32
32
 
33
33
  @injectable()
@@ -53,6 +53,8 @@ export class NotebookContextManager {
53
53
  return this._context;
54
54
  }
55
55
 
56
+ protected cellContexts: Map<number, Record<string, unknown>> = new Map();
57
+
56
58
  init(widget: NotebookEditorWidget): void {
57
59
  this._context = widget.node;
58
60
  this.scopedStore = this.contextKeyService.createScoped(widget.node);
@@ -73,6 +75,15 @@ export class NotebookContextManager {
73
75
  }
74
76
  }));
75
77
 
78
+ widget.model?.onDidChangeContent(events => {
79
+ if (events.some(e => e.kind === NotebookCellsChangeType.ModelChange || e.kind === NotebookCellsChangeType.Output)) {
80
+ this.scopedStore.setContext(NOTEBOOK_HAS_OUTPUTS, widget.model?.cells.some(cell => cell.outputs.length > 0));
81
+ this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_HAS_OUTPUTS]));
82
+ }
83
+ });
84
+
85
+ this.scopedStore.setContext(NOTEBOOK_HAS_OUTPUTS, !!widget.model?.cells.find(cell => cell.outputs.length > 0));
86
+
76
87
  // Cell Selection realted keys
77
88
  this.scopedStore.setContext(NOTEBOOK_CELL_FOCUSED, !!widget.model?.selectedCell);
78
89
  widget.model?.onDidChangeSelectedCell(e => {
@@ -80,6 +91,14 @@ export class NotebookContextManager {
80
91
  this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_FOCUSED]));
81
92
  });
82
93
 
94
+ this.toDispose.push(this.executionStateService.onDidChangeExecution(e => {
95
+ if (e.notebook.toString() === widget.model?.uri.toString()) {
96
+ this.setCellContext(e.cellHandle, NOTEBOOK_CELL_EXECUTING, !!e.changed);
97
+ this.setCellContext(e.cellHandle, NOTEBOOK_CELL_EXECUTION_STATE, e.changed?.state ?? 'idle');
98
+ this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE]));
99
+ }
100
+ }));
101
+
83
102
  widget.model?.onDidChangeSelectedCell(e => this.selectedCellChanged(e));
84
103
 
85
104
  this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_VIEW_TYPE, NOTEBOOK_KERNEL_SELECTED, NOTEBOOK_KERNEL]));
@@ -100,19 +119,26 @@ export class NotebookContextManager {
100
119
  this.scopedStore.setContext(NOTEBOOK_CELL_EDITABLE, cell.cellKind === CellKind.Markup && !cellEdit);
101
120
  this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_MARKDOWN_EDIT_MODE]));
102
121
  }));
103
- this.cellDisposables.push(this.executionStateService.onDidChangeExecution(e => {
104
- if (cell && e.affectsCell(cell.uri)) {
105
- this.scopedStore.setContext(NOTEBOOK_CELL_EXECUTING, !!e.changed);
106
- this.scopedStore.setContext(NOTEBOOK_CELL_EXECUTION_STATE, e.changed?.state ?? 'idle');
107
- this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE]));
108
- }
109
- }));
110
122
  }
111
123
 
112
124
  this.onDidChangeContextEmitter.fire(this.createContextKeyChangedEvent([NOTEBOOK_CELL_TYPE]));
113
125
 
114
126
  }
115
127
 
128
+ protected setCellContext(cellHandle: number, key: string, value: unknown): void {
129
+ let cellContext = this.cellContexts.get(cellHandle);
130
+ if (!cellContext) {
131
+ cellContext = {};
132
+ this.cellContexts.set(cellHandle, cellContext);
133
+ }
134
+
135
+ cellContext[key] = value;
136
+ }
137
+
138
+ getCellContext(cellHandle: number): ContextMatcher {
139
+ return this.contextKeyService.createOverlay(Object.entries(this.cellContexts.get(cellHandle) ?? {}));
140
+ }
141
+
116
142
  onDidEditorTextFocus(focus: boolean): void {
117
143
  this.scopedStore.setContext('inputFocus', focus);
118
144
  }
@@ -121,6 +121,10 @@ export class NotebookService implements Disposable {
121
121
  // This ensures that all text models are available in the plugin host
122
122
  this.textModelService.createTextModelsForNotebook(model);
123
123
  this.didAddNotebookDocumentEmitter.fire(model);
124
+ model.onDidDispose(() => {
125
+ this.notebookModels.delete(resource.uri.toString());
126
+ this.didRemoveNotebookDocumentEmitter.fire(model);
127
+ });
124
128
  return model;
125
129
  }
126
130
 
@@ -109,8 +109,13 @@
109
109
  flex-grow: 1;
110
110
  }
111
111
 
112
- .notebook-cell-status-right {
113
- margin: 0 5px;
112
+ .notebook-cell-language-label {
113
+ padding: 0 5px;
114
+ }
115
+
116
+ .notebook-cell-language-label:hover {
117
+ cursor: pointer;
118
+ background-color: var(--theia-toolbar-hoverBackground);
114
119
  }
115
120
 
116
121
  .notebook-cell-status-item {
@@ -213,8 +218,9 @@
213
218
  cursor: pointer;
214
219
  }
215
220
 
216
- .theia-notebook-main-toolbar-item:hover {
217
- background-color: var(--theia-toolbar-hoverBackground);
221
+ .theia-notebook-main-toolbar-item.theia-mod-disabled:hover {
222
+ background-color: transparent;
223
+ cursor: default;
218
224
  }
219
225
 
220
226
  .theia-notebook-main-toolbar-item-text {
@@ -265,3 +271,12 @@
265
271
  background-color: var(--theia-notebook-focusedCellBorder);
266
272
  width: 100%;
267
273
  }
274
+
275
+ .theia-notebook-collapsed-output {
276
+ padding: 4px 8px;
277
+ color: var(--theia-foreground);
278
+ margin-left: 30px;
279
+ font-size: 14px;
280
+ line-height: 22px;
281
+ opacity: 0.7;
282
+ }
@@ -42,7 +42,8 @@ const DEFAULT_EDITOR_OPTIONS: MonacoEditor.IOptions = {
42
42
  scrollbar: {
43
43
  ...MonacoEditorProvider.inlineOptions.scrollbar,
44
44
  alwaysConsumeMouseWheel: false
45
- }
45
+ },
46
+ lineDecorationsWidth: 10,
46
47
  };
47
48
 
48
49
  export class CellEditor extends React.Component<CellEditorProps, {}> {
@@ -56,6 +57,15 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
56
57
  this.toDispose.push(this.props.cell.onWillFocusCellEditor(() => {
57
58
  this.editor?.getControl().focus();
58
59
  }));
60
+
61
+ this.toDispose.push(this.props.cell.onDidChangeEditorOptions(options => {
62
+ this.editor?.getControl().updateOptions(options);
63
+ }));
64
+
65
+ this.toDispose.push(this.props.cell.onDidChangeLanguage(language => {
66
+ this.editor?.setLanguage(language);
67
+ }));
68
+
59
69
  this.toDispose.push(this.props.notebookModel.onDidChangeSelectedCell(() => {
60
70
  if (this.props.notebookModel.selectedCell !== this.props.cell && this.editor?.getControl().hasTextFocus()) {
61
71
  if (document.activeElement && 'blur' in document.activeElement) {
@@ -96,7 +106,7 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
96
106
  editorModel,
97
107
  editorNode,
98
108
  monacoServices,
99
- DEFAULT_EDITOR_OPTIONS,
109
+ { ...DEFAULT_EDITOR_OPTIONS, ...cell.editorOptions },
100
110
  [[IContextKeyService, this.props.notebookContextManager.scopedStore]]);
101
111
  this.toDispose.push(this.editor);
102
112
  this.editor.setLanguage(cell.language);
@@ -45,7 +45,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
45
45
 
46
46
  constructor(props: CellListProps) {
47
47
  super(props);
48
- this.state = { selectedCell: undefined, dragOverIndicator: undefined };
48
+ this.state = { selectedCell: props.notebookModel.selectedCell, dragOverIndicator: undefined };
49
49
  this.toDispose.push(props.notebookModel.onDidAddOrRemoveCell(e => {
50
50
  if (e.newCellIds && e.newCellIds.length > 0) {
51
51
  this.setState({ ...this.state, selectedCell: this.props.notebookModel.cells.find(model => model.handle === e.newCellIds![e.newCellIds!.length - 1]) });
@@ -89,7 +89,10 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
89
89
  {this.renderCellContent(cell, index)}
90
90
  </div>
91
91
  {this.state.selectedCell === cell &&
92
- this.props.toolbarRenderer.renderCellToolbar(NotebookCellActionContribution.ACTION_MENU, this.props.notebookModel, cell)}
92
+ this.props.toolbarRenderer.renderCellToolbar(NotebookCellActionContribution.ACTION_MENU, cell, {
93
+ contextMenuArgs: () => [cell], commandArgs: () => [this.props.notebookModel]
94
+ })
95
+ }
93
96
  </li>
94
97
  {this.shouldRenderDragOverIndicator(cell, 'bottom') && <CellDropIndicator />}
95
98
  </React.Fragment>
@@ -20,9 +20,7 @@ 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';
22
22
  import { ContextMenuRenderer } from '@theia/core/lib/browser';
23
- import { NotebookModel } from '../view-model/notebook-model';
24
23
  import { NotebookCellModel } from '../view-model/notebook-cell-model';
25
- import { NotebookCellOutputModel } from '../view-model/notebook-cell-output-model';
26
24
  import { NotebookContextManager } from '../service/notebook-context-manager';
27
25
 
28
26
  export interface NotebookCellToolbarItem {
@@ -34,6 +32,11 @@ export interface NotebookCellToolbarItem {
34
32
  contextKeys?: Set<string>
35
33
  }
36
34
 
35
+ export interface toolbarItemOptions {
36
+ contextMenuArgs?: () => unknown[];
37
+ commandArgs?: () => unknown[];
38
+ }
39
+
37
40
  @injectable()
38
41
  export class NotebookCellToolbarFactory {
39
42
 
@@ -52,32 +55,31 @@ export class NotebookCellToolbarFactory {
52
55
  @inject(NotebookContextManager)
53
56
  protected readonly notebookContextManager: NotebookContextManager;
54
57
 
55
- renderCellToolbar(menuPath: string[], notebookModel: NotebookModel, cell: NotebookCellModel): React.ReactNode {
56
- return <NotebookCellToolbar getMenuItems={() => this.getMenuItems(menuPath, notebookModel, cell)}
58
+ renderCellToolbar(menuPath: string[], cell: NotebookCellModel, itemOptions: toolbarItemOptions): React.ReactNode {
59
+ return <NotebookCellToolbar getMenuItems={() => this.getMenuItems(menuPath, cell, itemOptions)}
57
60
  onContextKeysChanged={this.notebookContextManager.onDidChangeContext} />;
58
61
  }
59
62
 
60
- renderSidebar(menuPath: string[], notebookModel: NotebookModel, cell: NotebookCellModel, output?: NotebookCellOutputModel): React.ReactNode {
61
- return <NotebookCellSidebar getMenuItems={() => this.getMenuItems(menuPath, notebookModel, cell, output)}
63
+ renderSidebar(menuPath: string[], cell: NotebookCellModel, itemOptions: toolbarItemOptions): React.ReactNode {
64
+ return <NotebookCellSidebar getMenuItems={() => this.getMenuItems(menuPath, cell, itemOptions)}
62
65
  onContextKeysChanged={this.notebookContextManager.onDidChangeContext} />;
63
66
  }
64
67
 
65
- private getMenuItems(menuItemPath: string[], notebookModel: NotebookModel, cell: NotebookCellModel, output?: NotebookCellOutputModel): NotebookCellToolbarItem[] {
68
+ private getMenuItems(menuItemPath: string[], cell: NotebookCellModel, itemOptions: toolbarItemOptions): NotebookCellToolbarItem[] {
66
69
  const inlineItems: NotebookCellToolbarItem[] = [];
67
-
68
70
  for (const menuNode of this.menuRegistry.getMenu(menuItemPath).children) {
69
- if (!menuNode.when || this.contextKeyService.match(menuNode.when, this.notebookContextManager.context)) {
71
+ if (!menuNode.when || this.notebookContextManager.getCellContext(cell.handle).match(menuNode.when, this.notebookContextManager.context)) {
70
72
  if (menuNode.role === CompoundMenuNodeRole.Flat) {
71
- inlineItems.push(...menuNode.children?.map(child => this.createToolbarItem(child, notebookModel, cell, output)) ?? []);
73
+ inlineItems.push(...menuNode.children?.map(child => this.createToolbarItem(child, itemOptions)) ?? []);
72
74
  } else {
73
- inlineItems.push(this.createToolbarItem(menuNode, notebookModel, cell, output));
75
+ inlineItems.push(this.createToolbarItem(menuNode, itemOptions));
74
76
  }
75
77
  }
76
78
  }
77
79
  return inlineItems;
78
80
  }
79
81
 
80
- private createToolbarItem(menuNode: MenuNode, notebookModel: NotebookModel, cell: NotebookCellModel, output?: NotebookCellOutputModel): NotebookCellToolbarItem {
82
+ private createToolbarItem(menuNode: MenuNode, itemOptions: toolbarItemOptions): NotebookCellToolbarItem {
81
83
  const menuPath = menuNode.role === CompoundMenuNodeRole.Submenu ? this.menuRegistry.getPath(menuNode) : undefined;
82
84
  return {
83
85
  id: menuNode.id,
@@ -89,11 +91,11 @@ export class NotebookCellToolbarFactory {
89
91
  anchor: e.nativeEvent,
90
92
  menuPath,
91
93
  includeAnchorArg: false,
92
- args: [cell],
94
+ args: itemOptions.contextMenuArgs?.(),
93
95
  context: this.notebookContextManager.context
94
96
  }) :
95
- () => this.commandRegistry.executeCommand(menuNode.command!, notebookModel, cell, output),
96
- isVisible: () => menuPath ? true : Boolean(this.commandRegistry.getVisibleHandler(menuNode.command!, notebookModel, cell, output)),
97
+ () => this.commandRegistry.executeCommand(menuNode.command!, ...(itemOptions.commandArgs?.() ?? [])),
98
+ isVisible: () => menuPath ? true : Boolean(this.commandRegistry.getVisibleHandler(menuNode.command!, ...(itemOptions.commandArgs?.() ?? []))),
97
99
  contextKeys: menuNode.when ? this.contextKeyService.parseKeys(menuNode.when) : undefined
98
100
  };
99
101
  }
@@ -35,8 +35,9 @@ abstract class NotebookCellActionBar extends React.Component<NotebookCellToolbar
35
35
  constructor(props: NotebookCellToolbarProps) {
36
36
  super(props);
37
37
  this.toDispose.push(props.onContextKeysChanged(e => {
38
- if (this.props.getMenuItems().some(item => item.contextKeys ? e.affects(item.contextKeys) : false)) {
39
- this.setState({ inlineItems: this.props.getMenuItems() });
38
+ const menuItems = this.props.getMenuItems();
39
+ if (menuItems.some(item => item.contextKeys ? e.affects(item.contextKeys) : false)) {
40
+ this.setState({ inlineItems: menuItems });
40
41
  }
41
42
  }));
42
43
  this.state = { inlineItems: this.props.getMenuItems() };
@@ -24,11 +24,11 @@ import { NotebookModel } from '../view-model/notebook-model';
24
24
  import { CellEditor } from './notebook-cell-editor';
25
25
  import { CellRenderer } from './notebook-cell-list-view';
26
26
  import { NotebookCellToolbarFactory } from './notebook-cell-toolbar-factory';
27
- import { NotebookCellActionContribution } from '../contributions/notebook-cell-actions-contribution';
27
+ import { NotebookCellActionContribution, NotebookCellCommands } from '../contributions/notebook-cell-actions-contribution';
28
28
  import { CellExecution, NotebookExecutionStateService } from '../service/notebook-execution-state-service';
29
29
  import { codicon } from '@theia/core/lib/browser';
30
30
  import { NotebookCellExecutionState } from '../../common';
31
- import { DisposableCollection } from '@theia/core';
31
+ import { CommandRegistry, DisposableCollection, nls } from '@theia/core';
32
32
  import { NotebookContextManager } from '../service/notebook-context-manager';
33
33
  import { NotebookViewportService } from './notebook-viewport-service';
34
34
  import { EditorPreferences } from '@theia/editor/lib/browser';
@@ -61,13 +61,19 @@ export class NotebookCodeCellRenderer implements CellRenderer {
61
61
  @inject(EditorPreferences)
62
62
  protected readonly editorPreferences: EditorPreferences;
63
63
 
64
+ @inject(CommandRegistry)
65
+ protected readonly commandRegistry: CommandRegistry;
66
+
64
67
  protected fontInfo: BareFontInfo | undefined;
65
68
 
66
69
  render(notebookModel: NotebookModel, cell: NotebookCellModel, handle: number): React.ReactNode {
67
70
  return <div>
68
71
  <div className='theia-notebook-cell-with-sidebar'>
69
72
  <div className='theia-notebook-cell-sidebar'>
70
- {this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU, notebookModel, cell)}
73
+ {this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU, cell, {
74
+ contextMenuArgs: () => [cell], commandArgs: () => [notebookModel, cell]
75
+ })
76
+ }
71
77
  <CodeCellExecutionOrder cell={cell} />
72
78
  </div>
73
79
  <div className='theia-notebook-cell-editor-container'>
@@ -76,13 +82,19 @@ export class NotebookCodeCellRenderer implements CellRenderer {
76
82
  notebookContextManager={this.notebookContextManager}
77
83
  notebookViewportService={this.notebookViewportService}
78
84
  fontInfo={this.getOrCreateMonacoFontInfo()} />
79
- <NotebookCodeCellStatus cell={cell} executionStateService={this.executionStateService} onClick={() => cell.requestFocusEditor()}></NotebookCodeCellStatus>
85
+ <NotebookCodeCellStatus cell={cell} notebook={notebookModel}
86
+ commandRegistry={this.commandRegistry}
87
+ executionStateService={this.executionStateService}
88
+ onClick={() => cell.requestFocusEditor()} />
80
89
  </div >
81
90
  </div >
82
91
  <div className='theia-notebook-cell-with-sidebar'>
83
92
  <NotebookCodeCellOutputs cell={cell} notebook={notebookModel} outputWebviewFactory={this.cellOutputWebviewFactory}
84
93
  renderSidebar={() =>
85
- this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.OUTPUT_SIDEBAR_MENU, notebookModel, cell, cell.outputs[0])} />
94
+ this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.OUTPUT_SIDEBAR_MENU, cell, {
95
+ contextMenuArgs: () => [notebookModel, cell, cell.outputs[0]]
96
+ })
97
+ } />
86
98
  </div>
87
99
  </div >;
88
100
  }
@@ -108,7 +120,9 @@ export class NotebookCodeCellRenderer implements CellRenderer {
108
120
  }
109
121
 
110
122
  export interface NotebookCodeCellStatusProps {
123
+ notebook: NotebookModel;
111
124
  cell: NotebookCellModel;
125
+ commandRegistry: CommandRegistry;
112
126
  executionStateService: NotebookExecutionStateService;
113
127
  onClick: () => void;
114
128
  }
@@ -146,6 +160,10 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
146
160
  }
147
161
  }
148
162
  }));
163
+
164
+ this.toDispose.push(props.cell.onDidChangeLanguage(() => {
165
+ this.forceUpdate();
166
+ }));
149
167
  }
150
168
 
151
169
  override componentWillUnmount(): void {
@@ -158,7 +176,9 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
158
176
  {this.renderExecutionState()}
159
177
  </div>
160
178
  <div className='notebook-cell-status-right'>
161
- <span>{this.props.cell.language}</span>
179
+ <span className='notebook-cell-language-label' onClick={() => {
180
+ this.props.commandRegistry.executeCommand(NotebookCellCommands.CHANGE_CELL_LANGUAGE.id, this.props.notebook, this.props.cell);
181
+ }}>{this.props.cell.languageName}</span>
162
182
  </div>
163
183
  </div>;
164
184
  }
@@ -223,19 +243,15 @@ export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputP
223
243
 
224
244
  override async componentDidMount(): Promise<void> {
225
245
  const { cell, notebook, outputWebviewFactory } = this.props;
226
- this.toDispose.push(cell.onDidChangeOutputs(async () => {
227
- if (!this.outputsWebviewPromise && cell.outputs.length > 0) {
228
- this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
229
- this.outputsWebview = webview;
230
- this.forceUpdate();
231
- return webview;
232
- });
233
- this.forceUpdate();
234
- } else if (this.outputsWebviewPromise && cell.outputs.length === 0 && cell.internalMetadata.runEndTime) {
235
- (await this.outputsWebviewPromise).dispose();
246
+ this.toDispose.push(cell.onDidChangeOutputs(() => this.updateOutputs()));
247
+ this.toDispose.push(cell.onDidChangeOutputVisibility(visible => {
248
+ if (!visible && this.outputsWebview) {
249
+ this.outputsWebview?.dispose();
236
250
  this.outputsWebview = undefined;
237
251
  this.outputsWebviewPromise = undefined;
238
252
  this.forceUpdate();
253
+ } else {
254
+ this.updateOutputs();
239
255
  }
240
256
  }));
241
257
  if (cell.outputs.length > 0) {
@@ -247,6 +263,23 @@ export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputP
247
263
  }
248
264
  }
249
265
 
266
+ protected async updateOutputs(): Promise<void> {
267
+ const { cell, notebook, outputWebviewFactory } = this.props;
268
+ if (!this.outputsWebviewPromise && cell.outputs.length > 0) {
269
+ this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
270
+ this.outputsWebview = webview;
271
+ this.forceUpdate();
272
+ return webview;
273
+ });
274
+ this.forceUpdate();
275
+ } else if (this.outputsWebviewPromise && cell.outputs.length === 0 && cell.internalMetadata.runEndTime) {
276
+ (await this.outputsWebviewPromise).dispose();
277
+ this.outputsWebview = undefined;
278
+ this.outputsWebviewPromise = undefined;
279
+ this.forceUpdate();
280
+ }
281
+ }
282
+
250
283
  override async componentDidUpdate(): Promise<void> {
251
284
  if (!(await this.outputsWebviewPromise)?.isAttached()) {
252
285
  (await this.outputsWebviewPromise)?.attachWebview();
@@ -259,12 +292,12 @@ export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputP
259
292
  }
260
293
 
261
294
  override render(): React.ReactNode {
262
- return this.outputsWebview ?
295
+ return this.outputsWebview && this.props.cell.outputVisible ?
263
296
  <>
264
297
  {this.props.renderSidebar()}
265
298
  {this.outputsWebview.render()}
266
299
  </> :
267
- <></>;
300
+ this.props.cell.outputs?.length ? <i className='theia-notebook-collapsed-output'>{nls.localizeByDefault('Outputs are collapsed')}</i> : <></>;
268
301
 
269
302
  }
270
303
 
@@ -57,6 +57,10 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
57
57
 
58
58
  protected toDispose = new DisposableCollection();
59
59
 
60
+ protected nativeSubmenus = [
61
+ NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_CELL_ADD_GROUP[NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_CELL_ADD_GROUP.length - 1],
62
+ NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_EXECUTION_GROUP[NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_EXECUTION_GROUP.length - 1]];
63
+
60
64
  constructor(props: NotebookMainToolbarProps) {
61
65
  super(props);
62
66
 
@@ -107,41 +111,46 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
107
111
  </div>;
108
112
  }
109
113
 
110
- protected renderMenuItem(item: MenuNode): React.ReactNode {
114
+ protected renderMenuItem(item: MenuNode, submenu?: string): React.ReactNode {
111
115
  if (item.role === CompoundMenuNodeRole.Group) {
112
- const itemNodes = ArrayUtils.coalesce(item.children?.map(child => this.renderMenuItem(child)) ?? []);
116
+ const itemNodes = ArrayUtils.coalesce(item.children?.map(child => this.renderMenuItem(child, item.id)) ?? []);
113
117
  return <React.Fragment key={item.id}>
114
118
  {itemNodes}
115
119
  {itemNodes && itemNodes.length > 0 && <span key={`${item.id}-separator`} className='theia-notebook-toolbar-separator'></span>}
116
120
  </React.Fragment>;
117
- } else if (!item.when || this.props.contextKeyService.match(item.when, this.props.editorNode)) {
121
+ } else if ((this.nativeSubmenus.includes(submenu ?? '')) || !item.when || this.props.contextKeyService.match(item.when, this.props.editorNode)) {
118
122
  const visibleCommand = Boolean(this.props.commandRegistry.getVisibleHandler(item.command ?? '', this.props.notebookModel));
119
123
  if (!visibleCommand) {
120
124
  return undefined;
121
125
  }
122
- const title = (this.props.commandRegistry.getCommand(item.command ?? '') as NotebookCommand)?.tooltip ?? item.label;
123
- return <div key={item.id} title={title} className='theia-notebook-main-toolbar-item action-label'
126
+ const command = this.props.commandRegistry.getCommand(item.command ?? '') as NotebookCommand | undefined;
127
+ const label = command?.shortTitle ?? item.label;
128
+ const title = command?.tooltip ?? item.label;
129
+ return <div key={item.id} title={title} className={`theia-notebook-main-toolbar-item action-label${this.getAdditionalClasses(item)}`}
124
130
  onClick={() => {
125
- if (item.command) {
126
- this.props.commandRegistry.executeCommand(item.command, this.props.notebookModel);
131
+ if (item.command && (!item.when || this.props.contextKeyService.match(item.when, this.props.editorNode))) {
132
+ this.props.commandRegistry.executeCommand(item.command, this.props.notebookModel.uri);
127
133
  }
128
134
  }}>
129
135
  <span className={item.icon} />
130
- <span className='theia-notebook-main-toolbar-item-text'>{item.label}</span>
136
+ <span className='theia-notebook-main-toolbar-item-text'>{label}</span>
131
137
  </div>;
132
138
  }
133
139
  return undefined;
134
140
  }
135
141
 
136
- private getMenuItems(): readonly MenuNode[] {
142
+ protected getMenuItems(): readonly MenuNode[] {
137
143
  const menuPath = NotebookMenus.NOTEBOOK_MAIN_TOOLBAR;
138
144
  const pluginCommands = this.props.menuRegistry.getMenuNode(menuPath).children;
139
145
  const theiaCommands = this.props.menuRegistry.getMenu([menuPath]).children;
140
- // TODO add specifc arguments to commands
141
146
  return theiaCommands.concat(pluginCommands);
142
147
  }
143
148
 
144
- private getAllContextKeys(menus: readonly MenuNode[], keySet: Set<string>): void {
149
+ protected getAdditionalClasses(item: MenuNode): string {
150
+ return !item.when || this.props.contextKeyService.match(item.when, this.props.editorNode) ? '' : ' theia-mod-disabled';
151
+ }
152
+
153
+ protected getAllContextKeys(menus: readonly MenuNode[], keySet: Set<string>): void {
145
154
  menus.filter(item => item.when)
146
155
  .forEach(item => this.props.contextKeyService.parseKeys(item.when!)?.forEach(key => keySet.add(key)));
147
156