@theia/notebook 1.47.0-next.0 → 1.47.1

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 (113) hide show
  1. package/lib/browser/contributions/notebook-actions-contribution.d.ts +3 -1
  2. package/lib/browser/contributions/notebook-actions-contribution.d.ts.map +1 -1
  3. package/lib/browser/contributions/notebook-actions-contribution.js +34 -35
  4. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  5. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts +5 -1
  6. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  7. package/lib/browser/contributions/notebook-cell-actions-contribution.js +29 -36
  8. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  9. package/lib/browser/contributions/notebook-color-contribution.js +2 -7
  10. package/lib/browser/contributions/notebook-color-contribution.js.map +1 -1
  11. package/lib/browser/index.js +12 -21
  12. package/lib/browser/index.js.map +1 -1
  13. package/lib/browser/notebook-cell-resource-resolver.d.ts +10 -5
  14. package/lib/browser/notebook-cell-resource-resolver.d.ts.map +1 -1
  15. package/lib/browser/notebook-cell-resource-resolver.js +18 -17
  16. package/lib/browser/notebook-cell-resource-resolver.js.map +1 -1
  17. package/lib/browser/notebook-editor-widget-factory.js +10 -18
  18. package/lib/browser/notebook-editor-widget-factory.js.map +1 -1
  19. package/lib/browser/notebook-editor-widget.d.ts +20 -0
  20. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  21. package/lib/browser/notebook-editor-widget.js +69 -32
  22. package/lib/browser/notebook-editor-widget.js.map +1 -1
  23. package/lib/browser/notebook-open-handler.js +4 -12
  24. package/lib/browser/notebook-open-handler.js.map +1 -1
  25. package/lib/browser/notebook-renderer-registry.d.ts +7 -0
  26. package/lib/browser/notebook-renderer-registry.d.ts.map +1 -1
  27. package/lib/browser/notebook-renderer-registry.js +13 -7
  28. package/lib/browser/notebook-renderer-registry.js.map +1 -1
  29. package/lib/browser/notebook-type-registry.js +2 -7
  30. package/lib/browser/notebook-type-registry.js.map +1 -1
  31. package/lib/browser/renderers/cell-output-webview.d.ts +2 -1
  32. package/lib/browser/renderers/cell-output-webview.d.ts.map +1 -1
  33. package/lib/browser/renderers/cell-output-webview.js.map +1 -1
  34. package/lib/browser/service/notebook-cell-context-manager.d.ts.map +1 -1
  35. package/lib/browser/service/notebook-cell-context-manager.js +6 -16
  36. package/lib/browser/service/notebook-cell-context-manager.js.map +1 -1
  37. package/lib/browser/service/notebook-editor-widget-service.js +8 -16
  38. package/lib/browser/service/notebook-editor-widget-service.js.map +1 -1
  39. package/lib/browser/service/notebook-execution-service.js +12 -20
  40. package/lib/browser/service/notebook-execution-service.js.map +1 -1
  41. package/lib/browser/service/notebook-execution-state-service.d.ts.map +1 -1
  42. package/lib/browser/service/notebook-execution-state-service.js +5 -14
  43. package/lib/browser/service/notebook-execution-state-service.js.map +1 -1
  44. package/lib/browser/service/notebook-kernel-history-service.js +12 -20
  45. package/lib/browser/service/notebook-kernel-history-service.js.map +1 -1
  46. package/lib/browser/service/notebook-kernel-quick-pick-service.js +12 -20
  47. package/lib/browser/service/notebook-kernel-quick-pick-service.js.map +1 -1
  48. package/lib/browser/service/notebook-kernel-service.d.ts +4 -0
  49. package/lib/browser/service/notebook-kernel-service.d.ts.map +1 -1
  50. package/lib/browser/service/notebook-kernel-service.js +10 -18
  51. package/lib/browser/service/notebook-kernel-service.js.map +1 -1
  52. package/lib/browser/service/notebook-model-resolver-service.d.ts +3 -2
  53. package/lib/browser/service/notebook-model-resolver-service.d.ts.map +1 -1
  54. package/lib/browser/service/notebook-model-resolver-service.js +21 -22
  55. package/lib/browser/service/notebook-model-resolver-service.js.map +1 -1
  56. package/lib/browser/service/notebook-renderer-messaging-service.d.ts +1 -0
  57. package/lib/browser/service/notebook-renderer-messaging-service.d.ts.map +1 -1
  58. package/lib/browser/service/notebook-renderer-messaging-service.js +12 -7
  59. package/lib/browser/service/notebook-renderer-messaging-service.js.map +1 -1
  60. package/lib/browser/service/notebook-service.d.ts +2 -2
  61. package/lib/browser/service/notebook-service.d.ts.map +1 -1
  62. package/lib/browser/service/notebook-service.js +13 -21
  63. package/lib/browser/service/notebook-service.js.map +1 -1
  64. package/lib/browser/view/notebook-cell-list-view.d.ts +3 -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 +24 -7
  67. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  68. package/lib/browser/view/notebook-cell-toolbar-factory.d.ts +1 -0
  69. package/lib/browser/view/notebook-cell-toolbar-factory.d.ts.map +1 -1
  70. package/lib/browser/view/notebook-cell-toolbar-factory.js +11 -18
  71. package/lib/browser/view/notebook-cell-toolbar-factory.js.map +1 -1
  72. package/lib/browser/view/notebook-cell-toolbar.js +2 -2
  73. package/lib/browser/view/notebook-cell-toolbar.js.map +1 -1
  74. package/lib/browser/view/notebook-code-cell-view.d.ts +1 -0
  75. package/lib/browser/view/notebook-code-cell-view.d.ts.map +1 -1
  76. package/lib/browser/view/notebook-code-cell-view.js +16 -24
  77. package/lib/browser/view/notebook-code-cell-view.js.map +1 -1
  78. package/lib/browser/view/notebook-main-toolbar.d.ts.map +1 -1
  79. package/lib/browser/view/notebook-main-toolbar.js +17 -21
  80. package/lib/browser/view/notebook-main-toolbar.js.map +1 -1
  81. package/lib/browser/view/notebook-markdown-cell-view.js +6 -14
  82. package/lib/browser/view/notebook-markdown-cell-view.js.map +1 -1
  83. package/lib/browser/view-model/notebook-cell-model.d.ts +1 -1
  84. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  85. package/lib/browser/view-model/notebook-cell-model.js +17 -22
  86. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  87. package/lib/browser/view-model/notebook-model.d.ts +17 -14
  88. package/lib/browser/view-model/notebook-model.d.ts.map +1 -1
  89. package/lib/browser/view-model/notebook-model.js +30 -27
  90. package/lib/browser/view-model/notebook-model.js.map +1 -1
  91. package/lib/common/index.js +3 -12
  92. package/lib/common/index.js.map +1 -1
  93. package/package.json +9 -8
  94. package/src/browser/contributions/notebook-actions-contribution.ts +34 -19
  95. package/src/browser/contributions/notebook-cell-actions-contribution.ts +24 -15
  96. package/src/browser/notebook-cell-resource-resolver.ts +24 -7
  97. package/src/browser/notebook-editor-widget.tsx +68 -6
  98. package/src/browser/notebook-renderer-registry.ts +19 -0
  99. package/src/browser/renderers/cell-output-webview.ts +2 -1
  100. package/src/browser/service/notebook-cell-context-manager.ts +0 -1
  101. package/src/browser/service/notebook-execution-state-service.ts +2 -3
  102. package/src/browser/service/notebook-kernel-service.ts +5 -0
  103. package/src/browser/service/notebook-model-resolver-service.ts +14 -10
  104. package/src/browser/service/notebook-renderer-messaging-service.ts +9 -1
  105. package/src/browser/service/notebook-service.ts +4 -4
  106. package/src/browser/style/index.css +17 -3
  107. package/src/browser/view/notebook-cell-list-view.tsx +34 -11
  108. package/src/browser/view/notebook-cell-toolbar-factory.tsx +2 -0
  109. package/src/browser/view/notebook-cell-toolbar.tsx +2 -2
  110. package/src/browser/view/notebook-code-cell-view.tsx +6 -5
  111. package/src/browser/view/notebook-main-toolbar.tsx +6 -2
  112. package/src/browser/view-model/notebook-cell-model.ts +5 -3
  113. package/src/browser/view-model/notebook-model.ts +27 -14
@@ -18,7 +18,7 @@
18
18
  * Licensed under the MIT License. See License.txt in the project root for license information.
19
19
  *--------------------------------------------------------------------------------------------*/
20
20
 
21
- import { Disposable, DisposableCollection, Emitter, URI } from '@theia/core';
21
+ import { Disposable, DisposableCollection, Emitter, URI, generateUuid } from '@theia/core';
22
22
  import { inject, injectable } from '@theia/core/shared/inversify';
23
23
  import { NotebookService } from './notebook-service';
24
24
  import {
@@ -27,7 +27,6 @@ import {
27
27
  } from '../../common';
28
28
  import { CellPartialInternalMetadataEditByHandle, CellEditOperation } from '../notebook-types';
29
29
  import { NotebookModel } from '../view-model/notebook-model';
30
- import { v4 } from 'uuid';
31
30
 
32
31
  export type CellExecuteUpdate = CellExecuteOutputEdit | CellExecuteOutputItemEdit | CellExecutionStateUpdate;
33
32
 
@@ -178,7 +177,7 @@ export class CellExecution implements Disposable {
178
177
  editType: CellEditType.PartialInternalMetadata,
179
178
  handle: this.cellHandle,
180
179
  internalMetadata: {
181
- executionId: v4(),
180
+ executionId: generateUuid(),
182
181
  runStartTime: undefined,
183
182
  runEndTime: undefined,
184
183
  lastRunSuccess: undefined,
@@ -54,6 +54,11 @@ export interface NotebookKernel {
54
54
  // ID of the extension providing this kernel
55
55
  readonly extensionId: string;
56
56
 
57
+ readonly localResourceRoot: URI;
58
+ readonly preloadUris: URI[];
59
+ readonly preloadProvides: string[];
60
+
61
+ readonly handle: number;
57
62
  label: string;
58
63
  description?: string;
59
64
  detail?: string;
@@ -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 { Emitter, URI } from '@theia/core';
17
+ import { Emitter, Resource, ResourceProvider, URI } from '@theia/core';
18
18
  import { inject, injectable } from '@theia/core/shared/inversify';
19
19
  import { UriComponents } from '@theia/core/lib/common/uri';
20
20
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
@@ -34,6 +34,9 @@ export class NotebookModelResolverService {
34
34
  @inject(FileService)
35
35
  protected fileService: FileService;
36
36
 
37
+ @inject(ResourceProvider)
38
+ protected resourceProvider: ResourceProvider;
39
+
37
40
  @inject(NotebookService)
38
41
  protected notebookService: NotebookService;
39
42
 
@@ -60,9 +63,9 @@ export class NotebookModelResolverService {
60
63
  throw new Error(`Missing viewType for '${resource}'`);
61
64
  }
62
65
 
63
- const notebookData = await this.resolveExistingNotebookData(resource, viewType!);
64
-
65
- const notebookModel = await this.notebookService.createNotebookModel(notebookData, viewType, resource);
66
+ const actualResource = await this.resourceProvider(resource);
67
+ const notebookData = await this.resolveExistingNotebookData(actualResource, viewType!);
68
+ const notebookModel = await this.notebookService.createNotebookModel(notebookData, viewType, actualResource);
66
69
 
67
70
  notebookModel.onDirtyChanged(() => this.onDidChangeDirtyEmitter.fire(notebookModel));
68
71
  notebookModel.onDidSaveNotebook(() => this.onDidSaveNotebookEmitter.fire(notebookModel.uri.toComponents()));
@@ -103,8 +106,8 @@ export class NotebookModelResolverService {
103
106
  return this.resolve(resource, viewType);
104
107
  }
105
108
 
106
- protected async resolveExistingNotebookData(resource: URI, viewType: string): Promise<NotebookData> {
107
- if (resource.scheme === 'untitled') {
109
+ protected async resolveExistingNotebookData(resource: Resource, viewType: string): Promise<NotebookData> {
110
+ if (resource.uri.scheme === 'untitled') {
108
111
 
109
112
  return {
110
113
  cells: [
@@ -118,10 +121,11 @@ export class NotebookModelResolverService {
118
121
  metadata: {}
119
122
  };
120
123
  } else {
121
- const file = await this.fileService.readFile(resource);
122
-
123
- const dataProvider = await this.notebookService.getNotebookDataProvider(viewType);
124
- const notebook = await dataProvider.serializer.toNotebook(file.value);
124
+ const [dataProvider, contents] = await Promise.all([
125
+ this.notebookService.getNotebookDataProvider(viewType),
126
+ this.fileService.readFile(resource.uri)
127
+ ]);
128
+ const notebook = await dataProvider.serializer.toNotebook(contents.value);
125
129
 
126
130
  return notebook;
127
131
  }
@@ -19,8 +19,9 @@
19
19
  *--------------------------------------------------------------------------------------------*/
20
20
 
21
21
  import { Emitter } from '@theia/core';
22
- import { injectable } from '@theia/core/shared/inversify';
22
+ import { inject, injectable } from '@theia/core/shared/inversify';
23
23
  import { Disposable } from '@theia/core/shared/vscode-languageserver-protocol';
24
+ import { NotebookEditorWidgetService } from './notebook-editor-widget-service';
24
25
 
25
26
  interface RendererMessage {
26
27
  editorId: string;
@@ -50,6 +51,9 @@ export class NotebookRendererMessagingService implements Disposable {
50
51
  private readonly willActivateRendererEmitter = new Emitter<string>();
51
52
  readonly onWillActivateRenderer = this.willActivateRendererEmitter.event;
52
53
 
54
+ @inject(NotebookEditorWidgetService)
55
+ private readonly editorWidgetService: NotebookEditorWidgetService;
56
+
53
57
  private readonly activations = new Map<string /* rendererId */, undefined | RendererMessage[]>();
54
58
  private readonly scopedMessaging = new Map<string /* editorId */, RendererMessaging>();
55
59
 
@@ -86,6 +90,10 @@ export class NotebookRendererMessagingService implements Disposable {
86
90
 
87
91
  const messaging: RendererMessaging = {
88
92
  postMessage: (rendererId, message) => this.postMessage(editorId, rendererId, message),
93
+ receiveMessage: async (rendererId, message) => {
94
+ this.editorWidgetService.getNotebookEditor(editorId)?.postRendererMessage(rendererId, message);
95
+ return true;
96
+ },
89
97
  dispose: () => this.scopedMessaging.delete(editorId),
90
98
  };
91
99
 
@@ -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 { Disposable, DisposableCollection, Emitter, URI } from '@theia/core';
17
+ import { Disposable, DisposableCollection, Emitter, Resource, URI } from '@theia/core';
18
18
  import { inject, injectable } from '@theia/core/shared/inversify';
19
19
  import { BinaryBuffer } from '@theia/core/lib/common/buffer';
20
20
  import { NotebookData, TransientOptions } from '../../common';
@@ -101,14 +101,14 @@ export class NotebookService implements Disposable {
101
101
  });
102
102
  }
103
103
 
104
- async createNotebookModel(data: NotebookData, viewType: string, uri: URI): Promise<NotebookModel> {
104
+ async createNotebookModel(data: NotebookData, viewType: string, resource: Resource): Promise<NotebookModel> {
105
105
  const serializer = this.notebookProviders.get(viewType)?.serializer;
106
106
  if (!serializer) {
107
107
  throw new Error('no notebook serializer for ' + viewType);
108
108
  }
109
109
 
110
- const model = this.notebookModelFactory({ data, uri, viewType, serializer });
111
- this.notebookModels.set(uri.toString(), model);
110
+ const model = this.notebookModelFactory({ data, resource, viewType, serializer });
111
+ this.notebookModels.set(resource.uri.toString(), model);
112
112
  // Resolve cell text models right after creating the notebook model
113
113
  // This ensures that all text models are available in the plugin host
114
114
  await Promise.all(model.cells.map(e => e.resolveTextModel()));
@@ -26,11 +26,14 @@
26
26
  }
27
27
 
28
28
  .theia-notebook-cell {
29
- cursor: grab;
30
29
  display: flex;
31
30
  margin: 10px 0px;
32
31
  }
33
32
 
33
+ .theia-notebook-cell.draggable {
34
+ cursor: grab;
35
+ }
36
+
34
37
  .theia-notebook-cell:hover .theia-notebook-cell-marker {
35
38
  visibility: visible;
36
39
  }
@@ -161,9 +164,20 @@
161
164
  flex-direction: column;
162
165
  }
163
166
 
167
+ .theia-notebook-main-container {
168
+ display: flex;
169
+ flex-direction: column;
170
+ height: 100%;
171
+ overflow: hidden;
172
+ }
173
+
174
+ .theia-notebook-scroll-container {
175
+ flex: 1;
176
+ overflow: hidden;
177
+ position: relative;
178
+ }
179
+
164
180
  .theia-notebook-main-toolbar {
165
- position: sticky;
166
- top: 0;
167
181
  background: var(--theia-editor-background);
168
182
  display: flex;
169
183
  flex-direction: row;
@@ -64,11 +64,13 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
64
64
  {this.props.notebookModel.cells
65
65
  .map((cell, index) =>
66
66
  <React.Fragment key={'cell-' + cell.handle}>
67
- <NotebookCellDivider onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, index)}
67
+ <NotebookCellDivider
68
+ isVisible={() => this.isEnabled()}
69
+ onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, index)}
68
70
  onDrop={e => this.onDrop(e, index)}
69
71
  onDragOver={e => this.onDragOver(e, cell, 'top')} />
70
72
  {this.shouldRenderDragOverIndicator(cell, 'top') && <CellDropIndicator />}
71
- <li className={'theia-notebook-cell' + (this.state.selectedCell === cell ? ' focused' : '')}
73
+ <li className={'theia-notebook-cell' + (this.state.selectedCell === cell ? ' focused' : '') + (this.isEnabled() ? ' draggable' : '')}
72
74
  onClick={() => {
73
75
  this.setState({ selectedCell: cell });
74
76
  this.props.notebookModel.setSelectedCell(cell);
@@ -89,7 +91,9 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
89
91
  </React.Fragment>
90
92
  )
91
93
  }
92
- <NotebookCellDivider onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, this.props.notebookModel.cells.length)}
94
+ <NotebookCellDivider
95
+ isVisible={() => this.isEnabled()}
96
+ onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, this.props.notebookModel.cells.length)}
93
97
  onDrop={e => this.onDrop(e, this.props.notebookModel.cells.length - 1)}
94
98
  onDragOver={e => this.onDragOver(e, this.props.notebookModel.cells[this.props.notebookModel.cells.length - 1], 'bottom')} />
95
99
  </ul>;
@@ -105,18 +109,33 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
105
109
 
106
110
  protected onDragStart(event: React.DragEvent<HTMLLIElement>, index: number): void {
107
111
  event.stopPropagation();
112
+ if (!this.isEnabled()) {
113
+ event.preventDefault();
114
+ return;
115
+ }
108
116
  event.dataTransfer.setData('text/theia-notebook-cell-index', index.toString());
109
117
  event.dataTransfer.setData('text/plain', this.props.notebookModel.cells[index].source);
110
118
  }
111
119
 
112
120
  protected onDragOver(event: React.DragEvent<HTMLLIElement>, cell: NotebookCellModel, position?: 'top' | 'bottom'): void {
121
+ if (!this.isEnabled()) {
122
+ return;
123
+ }
113
124
  event.preventDefault();
114
125
  event.stopPropagation();
115
126
  // show indicator
116
127
  this.setState({ ...this.state, dragOverIndicator: { cell, position: position ?? event.nativeEvent.offsetY < event.currentTarget.clientHeight / 2 ? 'top' : 'bottom' } });
117
128
  }
118
129
 
130
+ protected isEnabled(): boolean {
131
+ return !Boolean(this.props.notebookModel.readOnly);
132
+ }
133
+
119
134
  protected onDrop(event: React.DragEvent<HTMLLIElement>, dropElementIndex: number): void {
135
+ if (!this.isEnabled()) {
136
+ this.setState({ dragOverIndicator: undefined });
137
+ return;
138
+ }
120
139
  const index = parseInt(event.dataTransfer.getData('text/theia-notebook-cell-index'));
121
140
  const isTargetBelow = index < dropElementIndex;
122
141
  let newIdx = this.state.dragOverIndicator?.position === 'top' ? dropElementIndex : dropElementIndex + 1;
@@ -133,15 +152,18 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
133
152
  }
134
153
 
135
154
  protected onAddNewCell(kind: CellKind, index: number): void {
136
- this.props.commandRegistry.executeCommand(NotebookCommands.ADD_NEW_CELL_COMMAND.id,
137
- this.props.notebookModel,
138
- kind,
139
- index
140
- );
155
+ if (this.isEnabled()) {
156
+ this.props.commandRegistry.executeCommand(NotebookCommands.ADD_NEW_CELL_COMMAND.id,
157
+ this.props.notebookModel,
158
+ kind,
159
+ index
160
+ );
161
+ }
141
162
  }
142
163
 
143
164
  protected shouldRenderDragOverIndicator(cell: NotebookCellModel, position: 'top' | 'bottom'): boolean {
144
- return this.state.dragOverIndicator !== undefined &&
165
+ return this.isEnabled() &&
166
+ this.state.dragOverIndicator !== undefined &&
145
167
  this.state.dragOverIndicator.cell === cell &&
146
168
  this.state.dragOverIndicator.position === position;
147
169
  }
@@ -149,16 +171,17 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
149
171
  }
150
172
 
151
173
  export interface NotebookCellDividerProps {
174
+ isVisible: () => boolean;
152
175
  onAddNewCell: (type: CellKind) => void;
153
176
  onDrop: (event: React.DragEvent<HTMLLIElement>) => void;
154
177
  onDragOver: (event: React.DragEvent<HTMLLIElement>) => void;
155
178
  }
156
179
 
157
- export function NotebookCellDivider({ onAddNewCell, onDrop, onDragOver }: NotebookCellDividerProps): React.JSX.Element {
180
+ export function NotebookCellDivider({ isVisible, onAddNewCell, onDrop, onDragOver }: NotebookCellDividerProps): React.JSX.Element {
158
181
  const [hover, setHover] = React.useState(false);
159
182
 
160
183
  return <li className='theia-notebook-cell-divider' onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onDrop={onDrop} onDragOver={onDragOver}>
161
- {hover && <div className='theia-notebook-add-cell-buttons'>
184
+ {hover && isVisible() && <div className='theia-notebook-add-cell-buttons'>
162
185
  <button className='theia-notebook-add-cell-button' onClick={() => onAddNewCell(CellKind.Code)} title={nls.localizeByDefault('Add Code Cell')}>
163
186
  <div className={codicon('add') + ' theia-notebook-add-cell-button-icon'} />
164
187
  {nls.localizeByDefault('Code')}
@@ -29,6 +29,7 @@ export interface NotebookCellToolbarItem {
29
29
  icon?: string;
30
30
  label?: string;
31
31
  onClick: (e: React.MouseEvent) => void;
32
+ isVisible: () => boolean;
32
33
  contextKeys?: Set<string>
33
34
  }
34
35
 
@@ -87,6 +88,7 @@ export class NotebookCellToolbarFactory {
87
88
  args: [notebookModel, cell, output]
88
89
  }) :
89
90
  () => this.commandRegistry.executeCommand(menuNode.command!, notebookModel, cell, output),
91
+ isVisible: () => menuPath ? true : Boolean(this.commandRegistry.getVisibleHandler(menuNode.command!, notebookModel, cell, output)),
90
92
  contextKeys: menuNode.when ? this.contextKeyService.parseKeys(menuNode.when) : undefined
91
93
  };
92
94
  }
@@ -56,7 +56,7 @@ export class NotebookCellToolbar extends NotebookCellActionBar {
56
56
 
57
57
  override render(): React.ReactNode {
58
58
  return <div className='theia-notebook-cell-toolbar'>
59
- {this.state.inlineItems.map(item => this.renderItem(item))}
59
+ {this.state.inlineItems.filter(e => e.isVisible()).map(item => this.renderItem(item))}
60
60
  </div>;
61
61
  }
62
62
 
@@ -66,7 +66,7 @@ export class NotebookCellSidebar extends NotebookCellActionBar {
66
66
 
67
67
  override render(): React.ReactNode {
68
68
  return <div className='theia-notebook-cell-sidebar'>
69
- {this.state.inlineItems.map(item => this.renderItem(item))}
69
+ {this.state.inlineItems.filter(e => e.isVisible()).map(item => this.renderItem(item))}
70
70
  </div>;
71
71
  }
72
72
  }
@@ -61,7 +61,7 @@ export class NotebookCodeCellRenderer implements CellRenderer {
61
61
  </div>
62
62
  </div>
63
63
  <div className='theia-notebook-cell-with-sidebar'>
64
- <NotebookCodeCellOutputs cell={cell} outputWebviewFactory={this.cellOutputWebviewFactory}
64
+ <NotebookCodeCellOutputs cell={cell} notebook={notebookModel} outputWebviewFactory={this.cellOutputWebviewFactory}
65
65
  renderSidebar={() =>
66
66
  this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.OUTPUT_SIDEBAR_MENU, notebookModel, cell, cell.outputs[0])} />
67
67
  </div>
@@ -166,6 +166,7 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
166
166
 
167
167
  interface NotebookCellOutputProps {
168
168
  cell: NotebookCellModel;
169
+ notebook: NotebookModel;
169
170
  outputWebviewFactory: CellOutputWebviewFactory;
170
171
  renderSidebar: () => React.ReactNode;
171
172
  }
@@ -182,14 +183,14 @@ export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputP
182
183
  }
183
184
 
184
185
  override async componentDidMount(): Promise<void> {
185
- const { cell, outputWebviewFactory } = this.props;
186
+ const { cell, notebook, outputWebviewFactory } = this.props;
186
187
  this.toDispose.push(cell.onDidChangeOutputs(async () => {
187
188
  if (!this.outputsWebviewPromise && cell.outputs.length > 0) {
188
- this.outputsWebviewPromise = outputWebviewFactory(cell).then(webview => {
189
+ this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
189
190
  this.outputsWebview = webview;
190
191
  this.forceUpdate();
191
192
  return webview;
192
- });
193
+ });
193
194
  this.forceUpdate();
194
195
  } else if (this.outputsWebviewPromise && cell.outputs.length === 0 && cell.internalMetadata.runEndTime) {
195
196
  (await this.outputsWebviewPromise).dispose();
@@ -199,7 +200,7 @@ export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputP
199
200
  }
200
201
  }));
201
202
  if (cell.outputs.length > 0) {
202
- this.outputsWebviewPromise = outputWebviewFactory(cell).then(webview => {
203
+ this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
203
204
  this.outputsWebview = webview;
204
205
  this.forceUpdate();
205
206
  return webview;
@@ -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 { CommandRegistry, CompoundMenuNodeRole, DisposableCollection, MenuModelRegistry, MenuNode, nls } from '@theia/core';
16
+ import { ArrayUtils, CommandRegistry, CompoundMenuNodeRole, DisposableCollection, MenuModelRegistry, MenuNode, nls } from '@theia/core';
17
17
  import * as React from '@theia/core/shared/react';
18
18
  import { codicon } from '@theia/core/lib/browser';
19
19
  import { NotebookCommands, NotebookMenus } from '../contributions/notebook-actions-contribution';
@@ -98,12 +98,16 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
98
98
 
99
99
  protected renderMenuItem(item: MenuNode): React.ReactNode {
100
100
  if (item.role === CompoundMenuNodeRole.Group) {
101
- const itemNodes = item.children?.map(child => this.renderMenuItem(child)).filter(child => !!child);
101
+ const itemNodes = ArrayUtils.coalesce(item.children?.map(child => this.renderMenuItem(child)) ?? []);
102
102
  return <React.Fragment key={item.id}>
103
103
  {itemNodes}
104
104
  {itemNodes && itemNodes.length > 0 && <span key={`${item.id}-separator`} className='theia-notebook-toolbar-separator'></span>}
105
105
  </React.Fragment>;
106
106
  } else if (!item.when || this.props.contextKeyService.match(item.when)) {
107
+ const visibleCommand = Boolean(this.props.commandRegistry.getVisibleHandler(item.command ?? '', this.props.notebookModel));
108
+ if (!visibleCommand) {
109
+ return undefined;
110
+ }
107
111
  const title = (this.props.commandRegistry.getCommand(item.command ?? '') as NotebookCommand)?.tooltip ?? item.label;
108
112
  return <div key={item.id} title={title} className='theia-notebook-main-toolbar-item action-label'
109
113
  onClick={() => {
@@ -150,7 +150,7 @@ export class NotebookCellModel implements NotebookCell, Disposable {
150
150
 
151
151
  }
152
152
 
153
- textModel: MonacoEditorModel;
153
+ protected textModel?: MonacoEditorModel;
154
154
 
155
155
  protected htmlContext: HTMLLIElement;
156
156
 
@@ -220,12 +220,14 @@ export class NotebookCellModel implements NotebookCell, Disposable {
220
220
  this.onDidChangeInternalMetadataEmitter.dispose();
221
221
  this.onDidChangeLanguageEmitter.dispose();
222
222
  this.notebookCellContextManager.dispose();
223
- this.textModel.dispose();
223
+ this.textModel?.dispose();
224
224
  this.toDispose.dispose();
225
225
  }
226
226
 
227
227
  requestEdit(): void {
228
- this.onDidRequestCellEditChangeEmitter.fire(true);
228
+ if (!this.textModel || !this.textModel.readOnly) {
229
+ this.onDidRequestCellEditChangeEmitter.fire(true);
230
+ }
229
231
  }
230
232
 
231
233
  requestStopEdit(): void {
@@ -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 { Disposable, Emitter, URI } from '@theia/core';
17
+ import { Disposable, Emitter, Event, Resource, URI } from '@theia/core';
18
18
  import { Saveable, SaveOptions } from '@theia/core/lib/browser';
19
19
  import {
20
20
  CellData, CellEditType, CellUri, NotebookCellInternalMetadata,
@@ -28,6 +28,7 @@ import { NotebookCellModel, NotebookCellModelFactory } from './notebook-cell-mod
28
28
  import { MonacoTextModelService } from '@theia/monaco/lib/browser/monaco-text-model-service';
29
29
  import { inject, injectable, interfaces, postConstruct } from '@theia/core/shared/inversify';
30
30
  import { UndoRedoService } from '@theia/editor/lib/browser/undo-redo-service';
31
+ import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
31
32
 
32
33
  export const NotebookModelFactory = Symbol('NotebookModelFactory');
33
34
 
@@ -42,10 +43,10 @@ export function createNotebookModelContainer(parent: interfaces.Container, props
42
43
 
43
44
  const NotebookModelProps = Symbol('NotebookModelProps');
44
45
  export interface NotebookModelProps {
45
- data: NotebookData,
46
- uri: URI,
47
- viewType: string,
48
- serializer: NotebookSerializer,
46
+ data: NotebookData;
47
+ resource: Resource;
48
+ viewType: string;
49
+ serializer: NotebookSerializer;
49
50
  }
50
51
 
51
52
  @injectable()
@@ -63,6 +64,10 @@ export class NotebookModel implements Saveable, Disposable {
63
64
  protected readonly onDidChangeContentEmitter = new Emitter<NotebookContentChangedEvent[]>();
64
65
  readonly onDidChangeContent = this.onDidChangeContentEmitter.event;
65
66
 
67
+ get onDidChangeReadOnly(): Event<boolean | MarkdownString> {
68
+ return this.props.resource.onDidChangeReadOnly ?? Event.None;
69
+ }
70
+
66
71
  @inject(FileService)
67
72
  protected readonly fileService: FileService;
68
73
 
@@ -81,7 +86,7 @@ export class NotebookModel implements Saveable, Disposable {
81
86
 
82
87
  protected nextHandle: number = 0;
83
88
 
84
- protected _dirty: boolean = false;
89
+ protected _dirty = false;
85
90
 
86
91
  set dirty(dirty: boolean) {
87
92
  this._dirty = dirty;
@@ -92,13 +97,17 @@ export class NotebookModel implements Saveable, Disposable {
92
97
  return this._dirty;
93
98
  }
94
99
 
100
+ get readOnly(): boolean | MarkdownString {
101
+ return this.props.resource.readOnly ?? false;
102
+ }
103
+
95
104
  selectedCell?: NotebookCellModel;
96
105
  protected dirtyCells: NotebookCellModel[] = [];
97
106
 
98
107
  cells: NotebookCellModel[];
99
108
 
100
109
  get uri(): URI {
101
- return this.props.uri;
110
+ return this.props.resource.uri;
102
111
  }
103
112
 
104
113
  get viewType(): string {
@@ -112,7 +121,7 @@ export class NotebookModel implements Saveable, Disposable {
112
121
  this.dirty = false;
113
122
 
114
123
  this.cells = this.props.data.cells.map((cell, index) => this.cellModelFactory({
115
- uri: CellUri.generate(this.props.uri, index),
124
+ uri: CellUri.generate(this.props.resource.uri, index),
116
125
  handle: index,
117
126
  source: cell.source,
118
127
  language: cell.language,
@@ -294,7 +303,7 @@ export class NotebookModel implements Saveable, Disposable {
294
303
  }
295
304
  }
296
305
 
297
- private replaceCells(start: number, deleteCount: number, newCells: CellData[], computeUndoRedo: boolean): void {
306
+ protected async replaceCells(start: number, deleteCount: number, newCells: CellData[], computeUndoRedo: boolean): Promise<void> {
298
307
  const cells = newCells.map(cell => {
299
308
  const handle = this.nextHandle++;
300
309
  return this.cellModelFactory({
@@ -325,11 +334,15 @@ export class NotebookModel implements Saveable, Disposable {
325
334
  async () => this.replaceCells(start, deleteCount, newCells, false));
326
335
  }
327
336
 
337
+ // Ensure that all text model have been created
338
+ // Otherwise we run into a race condition once we fire `onDidChangeContent`
339
+ await Promise.all(cells.map(cell => cell.resolveTextModel()));
340
+
328
341
  this.onDidAddOrRemoveCellEmitter.fire({ rawEvent: { kind: NotebookCellsChangeType.ModelChange, changes }, newCellIds: cells.map(cell => cell.handle) });
329
342
  this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ModelChange, changes }]);
330
343
  }
331
344
 
332
- private changeCellInternalMetadataPartial(cell: NotebookCellModel, internalMetadata: NullablePartialNotebookCellInternalMetadata): void {
345
+ protected changeCellInternalMetadataPartial(cell: NotebookCellModel, internalMetadata: NullablePartialNotebookCellInternalMetadata): void {
333
346
  const newInternalMetadata: NotebookCellInternalMetadata = {
334
347
  ...cell.internalMetadata
335
348
  };
@@ -345,7 +358,7 @@ export class NotebookModel implements Saveable, Disposable {
345
358
  ]);
346
359
  }
347
360
 
348
- private updateNotebookMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean): void {
361
+ protected updateNotebookMetadata(metadata: NotebookDocumentMetadata, computeUndoRedo: boolean): void {
349
362
  const oldMetadata = this.metadata;
350
363
  if (computeUndoRedo) {
351
364
  this.undoRedoService.pushElement(this.uri,
@@ -358,7 +371,7 @@ export class NotebookModel implements Saveable, Disposable {
358
371
  this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeDocumentMetadata, metadata: this.metadata }]);
359
372
  }
360
373
 
361
- private changeCellLanguage(cell: NotebookCellModel, languageId: string, computeUndoRedo: boolean): void {
374
+ protected changeCellLanguage(cell: NotebookCellModel, languageId: string, computeUndoRedo: boolean): void {
362
375
  if (cell.language === languageId) {
363
376
  return;
364
377
  }
@@ -368,7 +381,7 @@ export class NotebookModel implements Saveable, Disposable {
368
381
  this.onDidChangeContentEmitter.fire([{ kind: NotebookCellsChangeType.ChangeCellLanguage, index: this.cells.indexOf(cell), language: languageId }]);
369
382
  }
370
383
 
371
- private moveCellToIndex(fromIndex: number, length: number, toIndex: number, computeUndoRedo: boolean): boolean {
384
+ protected moveCellToIndex(fromIndex: number, length: number, toIndex: number, computeUndoRedo: boolean): boolean {
372
385
  if (computeUndoRedo) {
373
386
  this.undoRedoService.pushElement(this.uri,
374
387
  async () => { this.moveCellToIndex(toIndex, length, fromIndex, false); },
@@ -383,7 +396,7 @@ export class NotebookModel implements Saveable, Disposable {
383
396
  return true;
384
397
  }
385
398
 
386
- private getCellIndexByHandle(handle: number): number {
399
+ protected getCellIndexByHandle(handle: number): number {
387
400
  return this.cells.findIndex(c => c.handle === handle);
388
401
  }
389
402
  }