@theia/notebook 1.55.0-next.4 → 1.55.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 (81) hide show
  1. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  2. package/lib/browser/contributions/notebook-cell-actions-contribution.js +55 -10
  3. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  4. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts +7 -9
  5. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts.map +1 -1
  6. package/lib/browser/contributions/notebook-status-bar-contribution.js +21 -33
  7. package/lib/browser/contributions/notebook-status-bar-contribution.js.map +1 -1
  8. package/lib/browser/notebook-editor-widget.d.ts +4 -0
  9. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  10. package/lib/browser/notebook-editor-widget.js +23 -1
  11. package/lib/browser/notebook-editor-widget.js.map +1 -1
  12. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  13. package/lib/browser/notebook-frontend-module.js +3 -1
  14. package/lib/browser/notebook-frontend-module.js.map +1 -1
  15. package/lib/browser/renderers/cell-output-webview.d.ts +17 -3
  16. package/lib/browser/renderers/cell-output-webview.d.ts.map +1 -1
  17. package/lib/browser/renderers/cell-output-webview.js +2 -1
  18. package/lib/browser/renderers/cell-output-webview.js.map +1 -1
  19. package/lib/browser/service/notebook-cell-editor-service.d.ts +3 -0
  20. package/lib/browser/service/notebook-cell-editor-service.d.ts.map +1 -1
  21. package/lib/browser/service/notebook-cell-editor-service.js +26 -2
  22. package/lib/browser/service/notebook-cell-editor-service.js.map +1 -1
  23. package/lib/browser/service/notebook-cell-status-bar-service.d.ts +39 -0
  24. package/lib/browser/service/notebook-cell-status-bar-service.d.ts.map +1 -0
  25. package/lib/browser/service/notebook-cell-status-bar-service.js +69 -0
  26. package/lib/browser/service/notebook-cell-status-bar-service.js.map +1 -0
  27. package/lib/browser/service/notebook-editor-widget-service.d.ts +4 -0
  28. package/lib/browser/service/notebook-editor-widget-service.d.ts.map +1 -1
  29. package/lib/browser/service/notebook-editor-widget-service.js +19 -0
  30. package/lib/browser/service/notebook-editor-widget-service.js.map +1 -1
  31. package/lib/browser/view/notebook-cell-editor.d.ts +1 -0
  32. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  33. package/lib/browser/view/notebook-cell-editor.js +19 -13
  34. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  35. package/lib/browser/view/notebook-cell-list-view.d.ts +4 -1
  36. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  37. package/lib/browser/view/notebook-cell-list-view.js +36 -13
  38. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  39. package/lib/browser/view/notebook-cell-toolbar.js +1 -1
  40. package/lib/browser/view/notebook-cell-toolbar.js.map +1 -1
  41. package/lib/browser/view/notebook-code-cell-view.d.ts +30 -12
  42. package/lib/browser/view/notebook-code-cell-view.d.ts.map +1 -1
  43. package/lib/browser/view/notebook-code-cell-view.js +99 -71
  44. package/lib/browser/view/notebook-code-cell-view.js.map +1 -1
  45. package/lib/browser/view/notebook-main-toolbar.js +4 -4
  46. package/lib/browser/view/notebook-main-toolbar.js.map +1 -1
  47. package/lib/browser/view/notebook-markdown-cell-view.d.ts +5 -0
  48. package/lib/browser/view/notebook-markdown-cell-view.d.ts.map +1 -1
  49. package/lib/browser/view/notebook-markdown-cell-view.js +22 -5
  50. package/lib/browser/view/notebook-markdown-cell-view.js.map +1 -1
  51. package/lib/browser/view-model/notebook-cell-model.d.ts +12 -0
  52. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  53. package/lib/browser/view-model/notebook-cell-model.js +19 -0
  54. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  55. package/lib/browser/view-model/notebook-cell-output-model.d.ts +0 -3
  56. package/lib/browser/view-model/notebook-cell-output-model.d.ts.map +1 -1
  57. package/lib/browser/view-model/notebook-cell-output-model.js +0 -6
  58. package/lib/browser/view-model/notebook-cell-output-model.js.map +1 -1
  59. package/lib/browser/view-model/notebook-model.d.ts +2 -1
  60. package/lib/browser/view-model/notebook-model.d.ts.map +1 -1
  61. package/lib/browser/view-model/notebook-model.js +3 -0
  62. package/lib/browser/view-model/notebook-model.js.map +1 -1
  63. package/package.json +8 -8
  64. package/src/browser/contributions/notebook-cell-actions-contribution.ts +61 -11
  65. package/src/browser/contributions/notebook-status-bar-contribution.ts +23 -34
  66. package/src/browser/notebook-editor-widget.tsx +32 -7
  67. package/src/browser/notebook-frontend-module.ts +6 -2
  68. package/src/browser/renderers/cell-output-webview.ts +21 -3
  69. package/src/browser/service/notebook-cell-editor-service.ts +21 -3
  70. package/src/browser/service/notebook-cell-status-bar-service.ts +94 -0
  71. package/src/browser/service/notebook-editor-widget-service.ts +20 -0
  72. package/src/browser/style/index.css +54 -9
  73. package/src/browser/view/notebook-cell-editor.tsx +18 -11
  74. package/src/browser/view/notebook-cell-list-view.tsx +44 -13
  75. package/src/browser/view/notebook-cell-toolbar.tsx +1 -1
  76. package/src/browser/view/notebook-code-cell-view.tsx +143 -92
  77. package/src/browser/view/notebook-main-toolbar.tsx +4 -4
  78. package/src/browser/view/notebook-markdown-cell-view.tsx +30 -6
  79. package/src/browser/view-model/notebook-cell-model.ts +29 -0
  80. package/src/browser/view-model/notebook-cell-output-model.ts +0 -8
  81. package/src/browser/view-model/notebook-model.ts +5 -1
@@ -26,9 +26,19 @@ import { NotebookContextManager } from '../service/notebook-context-manager';
26
26
 
27
27
  export interface CellRenderer {
28
28
  render(notebookData: NotebookModel, cell: NotebookCellModel, index: number): React.ReactNode
29
+ renderSidebar(notebookModel: NotebookModel, cell: NotebookCellModel): React.ReactNode
29
30
  renderDragImage(cell: NotebookCellModel): HTMLElement
30
31
  }
31
32
 
33
+ export function observeCellHeight(ref: HTMLDivElement | null, cell: NotebookCellModel): void {
34
+ if (ref) {
35
+ cell.cellHeight = ref?.getBoundingClientRect().height ?? 0;
36
+ new ResizeObserver(entries =>
37
+ cell.cellHeight = ref?.getBoundingClientRect().height ?? 0
38
+ ).observe(ref);
39
+ }
40
+ }
41
+
32
42
  interface CellListProps {
33
43
  renderers: Map<CellKind, CellRenderer>;
34
44
  notebookModel: NotebookModel;
@@ -109,7 +119,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
109
119
  }
110
120
 
111
121
  override render(): React.ReactNode {
112
- return <ul className='theia-notebook-cell-list' ref={this.cellListRef}>
122
+ return <ul className='theia-notebook-cell-list' ref={this.cellListRef} onDragStart={e => this.onDragStart(e)}>
113
123
  {this.props.notebookModel.cells
114
124
  .map((cell, index) =>
115
125
  <React.Fragment key={'cell-' + cell.handle}>
@@ -119,13 +129,8 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
119
129
  onAddNewCell={(commandId: string) => this.onAddNewCell(commandId, index)}
120
130
  onDrop={e => this.onDrop(e, index)}
121
131
  onDragOver={e => this.onDragOver(e, cell, 'top')} />
122
- {this.shouldRenderDragOverIndicator(cell, 'top') && <CellDropIndicator />}
132
+ <CellDropIndicator visible={this.shouldRenderDragOverIndicator(cell, 'top')} />
123
133
  <li className={'theia-notebook-cell' + (this.state.selectedCell === cell ? ' focused' : '') + (this.isEnabled() ? ' draggable' : '')}
124
- onClick={e => {
125
- this.setState({ ...this.state, selectedCell: cell });
126
- this.props.notebookModel.setSelectedCell(cell, false);
127
- }}
128
- onDragStart={e => this.onDragStart(e, index, cell)}
129
134
  onDragEnd={e => {
130
135
  NotebookCellListView.dragGhost?.remove();
131
136
  this.setState({ ...this.state, dragOverIndicator: undefined });
@@ -134,6 +139,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
134
139
  onDrop={e => this.onDrop(e, index)}
135
140
  draggable={true}
136
141
  tabIndex={-1}
142
+ data-cell-handle={cell.handle}
137
143
  ref={ref => {
138
144
  if (ref && cell === this.state.selectedCell && this.state.scrollIntoView) {
139
145
  ref.scrollIntoView({ block: 'nearest' });
@@ -141,8 +147,16 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
141
147
  ref.focus();
142
148
  }
143
149
  }
144
- }}>
145
- <div className={'theia-notebook-cell-marker' + (this.state.selectedCell === cell ? ' theia-notebook-cell-marker-selected' : '')}></div>
150
+ }}
151
+ onClick={e => {
152
+ this.setState({ ...this.state, selectedCell: cell });
153
+ this.props.notebookModel.setSelectedCell(cell, false);
154
+ }}
155
+ >
156
+ <div className='theia-notebook-cell-sidebar'>
157
+ <div className={'theia-notebook-cell-marker' + (this.state.selectedCell === cell ? ' theia-notebook-cell-marker-selected' : '')}></div>
158
+ {this.renderCellSidebar(cell)}
159
+ </div>
146
160
  <div className='theia-notebook-cell-content'>
147
161
  {this.renderCellContent(cell, index)}
148
162
  </div>
@@ -152,7 +166,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
152
166
  })
153
167
  }
154
168
  </li>
155
- {this.shouldRenderDragOverIndicator(cell, 'bottom') && <CellDropIndicator />}
169
+ <CellDropIndicator visible={this.shouldRenderDragOverIndicator(cell, 'bottom')} />
156
170
  </React.Fragment>
157
171
  )
158
172
  }
@@ -173,13 +187,30 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
173
187
  return renderer.render(this.props.notebookModel, cell, index);
174
188
  }
175
189
 
176
- protected onDragStart(event: React.DragEvent<HTMLLIElement>, index: number, cell: NotebookCellModel): void {
190
+ renderCellSidebar(cell: NotebookCellModel): React.ReactNode {
191
+ const renderer = this.props.renderers.get(cell.cellKind);
192
+ if (!renderer) {
193
+ throw new Error(`No renderer found for cell type ${cell.cellKind}`);
194
+ }
195
+ return renderer.renderSidebar(this.props.notebookModel, cell);
196
+ }
197
+
198
+ protected onDragStart(event: React.DragEvent<HTMLElement>): void {
177
199
  event.stopPropagation();
178
200
  if (!this.isEnabled()) {
179
201
  event.preventDefault();
180
202
  return;
181
203
  }
182
204
 
205
+ const cellHandle = (event.target as HTMLLIElement).getAttribute('data-cell-handle');
206
+
207
+ if (!cellHandle) {
208
+ throw new Error('Cell handle not found in element for cell drag event');
209
+ }
210
+
211
+ const index = this.props.notebookModel.getCellIndexByHandle(parseInt(cellHandle));
212
+ const cell = this.props.notebookModel.cells[index];
213
+
183
214
  NotebookCellListView.dragGhost = document.createElement('div');
184
215
  NotebookCellListView.dragGhost.classList.add('theia-notebook-drag-ghost-image');
185
216
  NotebookCellListView.dragGhost.appendChild(this.props.renderers.get(cell.cellKind)?.renderDragImage(cell) ?? document.createElement('div'));
@@ -274,6 +305,6 @@ export function NotebookCellDivider({ isVisible, onAddNewCell, onDrop, onDragOve
274
305
  </li>;
275
306
  }
276
307
 
277
- function CellDropIndicator(): React.JSX.Element {
278
- return <div className='theia-notebook-cell-drop-indicator' />;
308
+ function CellDropIndicator(props: { visible: boolean }): React.JSX.Element {
309
+ return <div className='theia-notebook-cell-drop-indicator' style={{ visibility: props.visible ? 'visible' : 'hidden' }} />;
279
310
  }
@@ -48,7 +48,7 @@ abstract class NotebookCellActionBar extends React.Component<NotebookCellToolbar
48
48
  }
49
49
 
50
50
  protected renderItem(item: NotebookCellToolbarItem): React.ReactNode {
51
- return <div key={item.id} title={item.label} onClick={item.onClick} className={`${item.icon} ${ACTION_ITEM} theia-notebook-cell-toolbar-item`} />;
51
+ return <div key={item.id} id={item.id} title={item.label} onClick={item.onClick} className={`${item.icon} ${ACTION_ITEM} theia-notebook-cell-toolbar-item`} />;
52
52
  }
53
53
 
54
54
  }
@@ -17,18 +17,17 @@
17
17
  import { inject, injectable } from '@theia/core/shared/inversify';
18
18
  import * as React from '@theia/core/shared/react';
19
19
  import { MonacoEditorServices } from '@theia/monaco/lib/browser/monaco-editor';
20
- import { CellOutputWebviewFactory, CellOutputWebview } from '../renderers/cell-output-webview';
21
20
  import { NotebookRendererRegistry } from '../notebook-renderer-registry';
22
21
  import { NotebookCellModel } from '../view-model/notebook-cell-model';
23
22
  import { NotebookModel } from '../view-model/notebook-model';
24
23
  import { CellEditor } from './notebook-cell-editor';
25
- import { CellRenderer } from './notebook-cell-list-view';
24
+ import { CellRenderer, observeCellHeight } from './notebook-cell-list-view';
26
25
  import { NotebookCellToolbarFactory } from './notebook-cell-toolbar-factory';
27
26
  import { NotebookCellActionContribution, NotebookCellCommands } from '../contributions/notebook-cell-actions-contribution';
28
27
  import { CellExecution, NotebookExecutionStateService } from '../service/notebook-execution-state-service';
29
28
  import { codicon } from '@theia/core/lib/browser';
30
29
  import { NotebookCellExecutionState } from '../../common';
31
- import { CommandRegistry, DisposableCollection, nls } from '@theia/core';
30
+ import { CancellationToken, CommandRegistry, DisposableCollection, nls } from '@theia/core';
32
31
  import { NotebookContextManager } from '../service/notebook-context-manager';
33
32
  import { NotebookViewportService } from './notebook-viewport-service';
34
33
  import { EditorPreferences } from '@theia/editor/lib/browser';
@@ -36,6 +35,9 @@ import { NotebookOptionsService } from '../service/notebook-options';
36
35
  import { MarkdownRenderer } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
37
36
  import { MarkdownString } from '@theia/monaco-editor-core/esm/vs/base/common/htmlContent';
38
37
  import { NotebookCellEditorService } from '../service/notebook-cell-editor-service';
38
+ import { CellOutputWebview } from '../renderers/cell-output-webview';
39
+ import { NotebookCellStatusBarItem, NotebookCellStatusBarItemList, NotebookCellStatusBarService } from '../service/notebook-cell-status-bar-service';
40
+ import { LabelParser } from '@theia/core/lib/browser/label-parser';
39
41
 
40
42
  @injectable()
41
43
  export class NotebookCodeCellRenderer implements CellRenderer {
@@ -45,9 +47,6 @@ export class NotebookCodeCellRenderer implements CellRenderer {
45
47
  @inject(NotebookRendererRegistry)
46
48
  protected readonly notebookRendererRegistry: NotebookRendererRegistry;
47
49
 
48
- @inject(CellOutputWebviewFactory)
49
- protected readonly cellOutputWebviewFactory: CellOutputWebviewFactory;
50
-
51
50
  @inject(NotebookCellToolbarFactory)
52
51
  protected readonly notebookCellToolbarFactory: NotebookCellToolbarFactory;
53
52
 
@@ -75,40 +74,47 @@ export class NotebookCodeCellRenderer implements CellRenderer {
75
74
  @inject(MarkdownRenderer)
76
75
  protected readonly markdownRenderer: MarkdownRenderer;
77
76
 
77
+ @inject(CellOutputWebview)
78
+ protected readonly outputWebview: CellOutputWebview;
79
+
80
+ @inject(NotebookCellStatusBarService)
81
+ protected readonly notebookCellStatusBarService: NotebookCellStatusBarService;
82
+
83
+ @inject(LabelParser)
84
+ protected readonly labelParser: LabelParser;
85
+
78
86
  render(notebookModel: NotebookModel, cell: NotebookCellModel, handle: number): React.ReactNode {
79
- return <div>
80
- <div className='theia-notebook-cell-with-sidebar'>
81
- <div className='theia-notebook-cell-sidebar'>
82
- {this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU, cell, {
83
- contextMenuArgs: () => [cell], commandArgs: () => [notebookModel, cell]
84
- })
85
- }
86
- <CodeCellExecutionOrder cell={cell} />
87
- </div>
88
- <div className='theia-notebook-cell-editor-container'>
89
- <CellEditor notebookModel={notebookModel} cell={cell}
90
- monacoServices={this.monacoServices}
91
- notebookContextManager={this.notebookContextManager}
92
- notebookViewportService={this.notebookViewportService}
93
- notebookCellEditorService={this.notebookCellEditorService}
94
- fontInfo={this.notebookOptionsService.editorFontInfo} />
95
- <NotebookCodeCellStatus cell={cell} notebook={notebookModel}
96
- commandRegistry={this.commandRegistry}
97
- executionStateService={this.executionStateService}
98
- onClick={() => cell.requestFocusEditor()} />
99
- </div >
87
+ return <div className='theia-notebook-cell-with-sidebar' ref={ref => observeCellHeight(ref, cell)}>
88
+ <div className='theia-notebook-cell-editor-container'>
89
+ <CellEditor notebookModel={notebookModel} cell={cell}
90
+ monacoServices={this.monacoServices}
91
+ notebookContextManager={this.notebookContextManager}
92
+ notebookViewportService={this.notebookViewportService}
93
+ notebookCellEditorService={this.notebookCellEditorService}
94
+ fontInfo={this.notebookOptionsService.editorFontInfo} />
95
+ <NotebookCodeCellStatus cell={cell} notebook={notebookModel}
96
+ commandRegistry={this.commandRegistry}
97
+ executionStateService={this.executionStateService}
98
+ cellStatusBarService={this.notebookCellStatusBarService}
99
+ labelParser={this.labelParser}
100
+ onClick={() => cell.requestFocusEditor()} />
100
101
  </div >
101
- <div className='theia-notebook-cell-with-sidebar'>
102
- <NotebookCodeCellOutputs cell={cell} notebook={notebookModel} outputWebviewFactory={this.cellOutputWebviewFactory}
103
- renderSidebar={() =>
104
- this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.OUTPUT_SIDEBAR_MENU, cell, {
105
- contextMenuArgs: () => [notebookModel, cell, cell.outputs[0]]
106
- })
107
- } />
108
- </div>
109
102
  </div >;
110
103
  }
111
104
 
105
+ renderSidebar(notebookModel: NotebookModel, cell: NotebookCellModel): React.ReactNode {
106
+ return <div>
107
+ <NotebookCodeCellSidebar cell={cell} notebook={notebookModel} notebookCellToolbarFactory={this.notebookCellToolbarFactory} />
108
+ <NotebookCodeCellOutputs cell={cell} notebook={notebookModel} outputWebview={this.outputWebview}
109
+ renderSidebar={() =>
110
+ this.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.OUTPUT_SIDEBAR_MENU, cell, {
111
+ contextMenuArgs: () => [notebookModel, cell, cell.outputs[0]]
112
+ })
113
+ } />
114
+ </div>;
115
+
116
+ }
117
+
112
118
  renderDragImage(cell: NotebookCellModel): HTMLElement {
113
119
  const dragImage = document.createElement('div');
114
120
  dragImage.className = 'theia-notebook-drag-image';
@@ -151,11 +157,44 @@ export class NotebookCodeCellRenderer implements CellRenderer {
151
157
 
152
158
  }
153
159
 
160
+ export interface NotebookCodeCellSidebarProps {
161
+ cell: NotebookCellModel;
162
+ notebook: NotebookModel;
163
+ notebookCellToolbarFactory: NotebookCellToolbarFactory
164
+ }
165
+
166
+ export class NotebookCodeCellSidebar extends React.Component<NotebookCodeCellSidebarProps> {
167
+
168
+ protected toDispose = new DisposableCollection();
169
+
170
+ constructor(props: NotebookCodeCellSidebarProps) {
171
+ super(props);
172
+
173
+ this.toDispose.push(props.cell.onDidCellHeightChange(() => this.forceUpdate()));
174
+ }
175
+
176
+ override componentWillUnmount(): void {
177
+ this.toDispose.dispose();
178
+ }
179
+
180
+ override render(): React.ReactNode {
181
+ return <div className='theia-notebook-cell-sidebar-actions' style={{ height: `${this.props.cell.cellHeight}px` }}>
182
+ {this.props.notebookCellToolbarFactory.renderSidebar(NotebookCellActionContribution.CODE_CELL_SIDEBAR_MENU, this.props.cell, {
183
+ contextMenuArgs: () => [this.props.cell], commandArgs: () => [this.props.notebook, this.props.cell]
184
+ })
185
+ }
186
+ <CodeCellExecutionOrder cell={this.props.cell} />
187
+ </div>;
188
+ }
189
+ }
190
+
154
191
  export interface NotebookCodeCellStatusProps {
155
192
  notebook: NotebookModel;
156
193
  cell: NotebookCellModel;
157
194
  commandRegistry: CommandRegistry;
195
+ cellStatusBarService: NotebookCellStatusBarService;
158
196
  executionStateService?: NotebookExecutionStateService;
197
+ labelParser: LabelParser;
159
198
  onClick: () => void;
160
199
  }
161
200
 
@@ -168,6 +207,8 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
168
207
 
169
208
  protected toDispose = new DisposableCollection();
170
209
 
210
+ protected statusBarItems: NotebookCellStatusBarItemList[] = [];
211
+
171
212
  constructor(props: NotebookCodeCellStatusProps) {
172
213
  super(props);
173
214
 
@@ -198,6 +239,19 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
198
239
  this.toDispose.push(props.cell.onDidChangeLanguage(() => {
199
240
  this.forceUpdate();
200
241
  }));
242
+
243
+ this.updateStatusBarItems();
244
+ this.props.cellStatusBarService.onDidChangeItems(() => this.updateStatusBarItems());
245
+ this.props.notebook.onContentChanged(() => this.updateStatusBarItems());
246
+ }
247
+
248
+ async updateStatusBarItems(): Promise<void> {
249
+ this.statusBarItems = await this.props.cellStatusBarService.getStatusBarItemsForCell(
250
+ this.props.notebook.uri,
251
+ this.props.notebook.cells.indexOf(this.props.cell),
252
+ this.props.notebook.viewType,
253
+ CancellationToken.None);
254
+ this.forceUpdate();
201
255
  }
202
256
 
203
257
  override componentWillUnmount(): void {
@@ -208,6 +262,7 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
208
262
  return <div className='notebook-cell-status' onClick={() => this.props.onClick()}>
209
263
  <div className='notebook-cell-status-left'>
210
264
  {this.props.executionStateService && this.renderExecutionState()}
265
+ {this.statusBarItems?.length && this.renderStatusBarItems()}
211
266
  </div>
212
267
  <div className='notebook-cell-status-right'>
213
268
  <span className='notebook-cell-language-label' onClick={() => {
@@ -217,7 +272,7 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
217
272
  </div>;
218
273
  }
219
274
 
220
- private renderExecutionState(): React.ReactNode {
275
+ protected renderExecutionState(): React.ReactNode {
221
276
  const state = this.state.currentExecution?.state;
222
277
  const { lastRunSuccess } = this.props.cell.internalMetadata;
223
278
 
@@ -243,7 +298,7 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
243
298
  </>;
244
299
  }
245
300
 
246
- private getExecutionTime(): number {
301
+ protected getExecutionTime(): number {
247
302
  const { runStartTime, runEndTime } = this.props.cell.internalMetadata;
248
303
  const { executionTime } = this.state;
249
304
  if (runStartTime !== undefined && runEndTime !== undefined) {
@@ -252,87 +307,83 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
252
307
  return executionTime;
253
308
  }
254
309
 
255
- private renderTime(ms: number): string {
310
+ protected renderTime(ms: number): string {
256
311
  return `${(ms / 1000).toLocaleString(undefined, { maximumFractionDigits: 1, minimumFractionDigits: 1 })}s`;
257
312
  }
313
+
314
+ protected renderStatusBarItems(): React.ReactNode {
315
+ return <>
316
+ {
317
+ this.statusBarItems.flatMap((itemList, listIndex) =>
318
+ itemList.items.map((item, index) => this.renderStatusBarItem(item, `${listIndex}-${index}`)
319
+ )
320
+ )
321
+ }
322
+ </>;
323
+ }
324
+
325
+ protected renderStatusBarItem(item: NotebookCellStatusBarItem, key: string): React.ReactNode {
326
+ const content = this.props.labelParser.parse(item.text).map(part => {
327
+ if (typeof part === 'string') {
328
+ return part;
329
+ } else {
330
+ return <span key={part.name} className={`codicon codicon-${part.name}`}></span>;
331
+ }
332
+ });
333
+ return <div key={key} className={`cell-status-bar-item ${item.command ? 'cell-status-item-has-command' : ''}`} onClick={async () => {
334
+ if (item.command) {
335
+ if (typeof item.command === 'string') {
336
+ this.props.commandRegistry.executeCommand(item.command);
337
+ } else {
338
+ this.props.commandRegistry.executeCommand(item.command.id, ...(item.command.arguments ?? []));
339
+ }
340
+ }
341
+ }}>
342
+ {content}
343
+ </div>;
344
+ }
345
+
258
346
  }
259
347
 
260
348
  interface NotebookCellOutputProps {
261
349
  cell: NotebookCellModel;
262
350
  notebook: NotebookModel;
263
- outputWebviewFactory: CellOutputWebviewFactory;
351
+ outputWebview: CellOutputWebview;
264
352
  renderSidebar: () => React.ReactNode;
265
353
  }
266
354
 
267
355
  export class NotebookCodeCellOutputs extends React.Component<NotebookCellOutputProps> {
268
356
 
269
- protected outputsWebview: CellOutputWebview | undefined;
270
- protected outputsWebviewPromise: Promise<CellOutputWebview> | undefined;
271
-
272
357
  protected toDispose = new DisposableCollection();
273
358
 
274
- constructor(props: NotebookCellOutputProps) {
275
- super(props);
276
- }
359
+ protected outputHeight: number = 0;
277
360
 
278
361
  override async componentDidMount(): Promise<void> {
279
- const { cell, notebook, outputWebviewFactory } = this.props;
280
- this.toDispose.push(cell.onDidChangeOutputs(() => this.updateOutputs()));
281
- this.toDispose.push(cell.onDidChangeOutputVisibility(visible => {
282
- if (!visible && this.outputsWebview) {
283
- this.outputsWebview?.dispose();
284
- this.outputsWebview = undefined;
285
- this.outputsWebviewPromise = undefined;
362
+ const { cell } = this.props;
363
+ this.toDispose.push(cell.onDidChangeOutputs(() => this.forceUpdate()));
364
+ this.toDispose.push(this.props.cell.onDidChangeOutputVisibility(() => this.forceUpdate()));
365
+ this.toDispose.push(this.props.outputWebview.onDidRenderOutput(event => {
366
+ if (event.cellHandle === this.props.cell.handle) {
367
+ this.outputHeight = event.outputHeight;
286
368
  this.forceUpdate();
287
- } else {
288
- this.updateOutputs();
289
369
  }
290
370
  }));
291
- if (cell.outputs.length > 0) {
292
- this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
293
- this.outputsWebview = webview;
294
- this.forceUpdate();
295
- return webview;
296
- });
297
- }
298
- }
299
-
300
- protected async updateOutputs(): Promise<void> {
301
- const { cell, notebook, outputWebviewFactory } = this.props;
302
- if (!this.outputsWebviewPromise && cell.outputs.length > 0) {
303
- this.outputsWebviewPromise = outputWebviewFactory(cell, notebook).then(webview => {
304
- this.outputsWebview = webview;
305
- this.forceUpdate();
306
- return webview;
307
- });
308
- this.forceUpdate();
309
- } else if (this.outputsWebviewPromise && cell.outputs.length === 0 && cell.internalMetadata.runEndTime) {
310
- (await this.outputsWebviewPromise).dispose();
311
- this.outputsWebview = undefined;
312
- this.outputsWebviewPromise = undefined;
313
- this.forceUpdate();
314
- }
315
- }
316
-
317
- override async componentDidUpdate(): Promise<void> {
318
- if (!(await this.outputsWebviewPromise)?.isAttached()) {
319
- (await this.outputsWebviewPromise)?.attachWebview();
320
- }
321
371
  }
322
372
 
323
- override async componentWillUnmount(): Promise<void> {
373
+ override componentWillUnmount(): void {
324
374
  this.toDispose.dispose();
325
- (await this.outputsWebviewPromise)?.dispose();
326
375
  }
327
376
 
328
377
  override render(): React.ReactNode {
329
- return this.outputsWebview && this.props.cell.outputVisible ?
330
- <>
378
+ if (!this.props.cell.outputs?.length) {
379
+ return <></>;
380
+ }
381
+ if (this.props.cell.outputVisible) {
382
+ return <div style={{ minHeight: this.outputHeight }}>
331
383
  {this.props.renderSidebar()}
332
- {this.outputsWebview.render()}
333
- </> :
334
- this.props.cell.outputs?.length ? <i className='theia-notebook-collapsed-output'>{nls.localizeByDefault('Outputs are collapsed')}</i> : <></>;
335
-
384
+ </div>;
385
+ }
386
+ return <div className='theia-notebook-collapsed-output-container'><i className='theia-notebook-collapsed-output'>{nls.localizeByDefault('Outputs are collapsed')}</i></div>;
336
387
  }
337
388
 
338
389
  }
@@ -152,17 +152,17 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
152
152
 
153
153
  override render(): React.ReactNode {
154
154
  const menuItems = this.getMenuItems();
155
- return <div className='theia-notebook-main-toolbar'>
155
+ return <div className='theia-notebook-main-toolbar' id='notebook-main-toolbar'>
156
156
  {menuItems.slice(0, menuItems.length - this.calculateNumberOfHiddenItems(menuItems)).map(item => this.renderMenuItem(item))}
157
157
  {
158
158
  this.state.numberOfHiddenItems > 0 &&
159
159
  <span className={`${codicon('ellipsis')} action-label theia-notebook-main-toolbar-item`} onClick={e => this.renderContextMenu(e.nativeEvent, menuItems)} />
160
160
  }
161
161
  <div ref={element => this.gapElementChanged(element)} style={{ flexGrow: 1 }}></div>
162
- <div className='theia-notebook-main-toolbar-item action-label'
162
+ <div className='theia-notebook-main-toolbar-item action-label' id={NotebookCommands.SELECT_KERNEL_COMMAND.id}
163
163
  onClick={() => this.props.commandRegistry.executeCommand(NotebookCommands.SELECT_KERNEL_COMMAND.id, this.props.notebookModel)}>
164
164
  <span className={codicon('server-environment')} />
165
- <span className=' theia-notebook-main-toolbar-item-text'>
165
+ <span className=' theia-notebook-main-toolbar-item-text' id='kernel-text'>
166
166
  {this.state.selectedKernelLabel ?? nls.localizeByDefault('Select Kernel')}
167
167
  </span>
168
168
  </div>
@@ -195,7 +195,7 @@ export class NotebookMainToolbar extends React.Component<NotebookMainToolbarProp
195
195
  const command = this.props.commandRegistry.getCommand(item.command ?? '') as NotebookCommand | undefined;
196
196
  const label = command?.shortTitle ?? item.label;
197
197
  const title = command?.tooltip ?? item.label;
198
- return <div key={item.id} title={title} className={`theia-notebook-main-toolbar-item action-label${this.getAdditionalClasses(item)}`}
198
+ return <div key={item.id} id={item.id} title={title} className={`theia-notebook-main-toolbar-item action-label${this.getAdditionalClasses(item)}`}
199
199
  onClick={() => {
200
200
  if (item.command && (!item.when || this.props.contextKeyService.match(item.when, this.props.editorNode))) {
201
201
  this.props.commandRegistry.executeCommand(item.command, this.props.notebookModel.uri);
@@ -18,7 +18,7 @@ import * as React from '@theia/core/shared/react';
18
18
  import { MarkdownRenderer } from '@theia/core/lib/browser/markdown-rendering/markdown-renderer';
19
19
  import { MarkdownStringImpl } from '@theia/core/lib/common/markdown-rendering/markdown-string';
20
20
  import { NotebookModel } from '../view-model/notebook-model';
21
- import { CellRenderer } from './notebook-cell-list-view';
21
+ import { CellRenderer, observeCellHeight } from './notebook-cell-list-view';
22
22
  import { NotebookCellModel } from '../view-model/notebook-cell-model';
23
23
  import { CellEditor } from './notebook-cell-editor';
24
24
  import { inject, injectable } from '@theia/core/shared/inversify';
@@ -30,6 +30,8 @@ import { NotebookCodeCellStatus } from './notebook-code-cell-view';
30
30
  import { NotebookEditorFindMatch, NotebookEditorFindMatchOptions } from './notebook-find-widget';
31
31
  import * as mark from 'advanced-mark.js';
32
32
  import { NotebookCellEditorService } from '../service/notebook-cell-editor-service';
33
+ import { NotebookCellStatusBarService } from '../service/notebook-cell-status-bar-service';
34
+ import { LabelParser } from '@theia/core/lib/browser/label-parser';
33
35
 
34
36
  @injectable()
35
37
  export class NotebookMarkdownCellRenderer implements CellRenderer {
@@ -51,6 +53,12 @@ export class NotebookMarkdownCellRenderer implements CellRenderer {
51
53
  @inject(NotebookCellEditorService)
52
54
  protected readonly notebookCellEditorService: NotebookCellEditorService;
53
55
 
56
+ @inject(NotebookCellStatusBarService)
57
+ protected readonly notebookCellStatusBarService: NotebookCellStatusBarService;
58
+
59
+ @inject(LabelParser)
60
+ protected readonly labelParser: LabelParser;
61
+
54
62
  render(notebookModel: NotebookModel, cell: NotebookCellModel): React.ReactNode {
55
63
  return <MarkdownCell
56
64
  markdownRenderer={this.markdownRenderer}
@@ -60,7 +68,14 @@ export class NotebookMarkdownCellRenderer implements CellRenderer {
60
68
  cell={cell}
61
69
  notebookModel={notebookModel}
62
70
  notebookContextManager={this.notebookContextManager}
63
- notebookCellEditorService={this.notebookCellEditorService} />;
71
+ notebookCellEditorService={this.notebookCellEditorService}
72
+ notebookCellStatusBarService={this.notebookCellStatusBarService}
73
+ labelParser={this.labelParser}
74
+ />;
75
+ }
76
+
77
+ renderSidebar(notebookModel: NotebookModel, cell: NotebookCellModel): React.ReactNode {
78
+ return <div className='theia-notebook-markdown-sidebar'></div>;
64
79
  }
65
80
 
66
81
  renderDragImage(cell: NotebookCellModel): HTMLElement {
@@ -82,11 +97,15 @@ interface MarkdownCellProps {
82
97
  notebookModel: NotebookModel;
83
98
  notebookContextManager: NotebookContextManager;
84
99
  notebookOptionsService: NotebookOptionsService;
85
- notebookCellEditorService: NotebookCellEditorService
100
+ notebookCellEditorService: NotebookCellEditorService;
101
+ notebookCellStatusBarService: NotebookCellStatusBarService;
102
+ labelParser: LabelParser;
86
103
  }
87
104
 
88
105
  function MarkdownCell({
89
- markdownRenderer, monacoServices, cell, notebookModel, notebookContextManager, notebookOptionsService, commandRegistry, notebookCellEditorService
106
+ markdownRenderer, monacoServices, cell, notebookModel, notebookContextManager,
107
+ notebookOptionsService, commandRegistry, notebookCellEditorService, notebookCellStatusBarService,
108
+ labelParser
90
109
  }: MarkdownCellProps): React.JSX.Element {
91
110
  const [editMode, setEditMode] = React.useState(cell.editing);
92
111
  let empty = false;
@@ -135,7 +154,7 @@ function MarkdownCell({
135
154
  }
136
155
 
137
156
  return editMode ?
138
- (<div className='theia-notebook-markdown-editor-container' key="code">
157
+ (<div className='theia-notebook-markdown-editor-container' key="code" ref={ref => observeCellHeight(ref, cell)}>
139
158
  <CellEditor notebookModel={notebookModel} cell={cell}
140
159
  monacoServices={monacoServices}
141
160
  notebookContextManager={notebookContextManager}
@@ -143,11 +162,16 @@ function MarkdownCell({
143
162
  fontInfo={notebookOptionsService.editorFontInfo} />
144
163
  <NotebookCodeCellStatus cell={cell} notebook={notebookModel}
145
164
  commandRegistry={commandRegistry}
165
+ cellStatusBarService={notebookCellStatusBarService}
166
+ labelParser={labelParser}
146
167
  onClick={() => cell.requestFocusEditor()} />
147
168
  </div >) :
148
169
  (<div className='theia-notebook-markdown-content' key="markdown"
149
170
  onDoubleClick={() => cell.requestEdit()}
150
- ref={node => node?.replaceChildren(...markdownContent)}
171
+ ref={node => {
172
+ node?.replaceChildren(...markdownContent);
173
+ observeCellHeight(node, cell);
174
+ }}
151
175
  />);
152
176
  }
153
177
 
@@ -62,6 +62,10 @@ export interface NotebookCell {
62
62
  metadata: NotebookCellMetadata;
63
63
  internalMetadata: NotebookCellInternalMetadata;
64
64
  text: string;
65
+ /**
66
+ * The selection of the cell. Zero-based line/character coordinates.
67
+ */
68
+ selection: Range | undefined;
65
69
  onDidChangeOutputs?: Event<NotebookCellOutputsSplice>;
66
70
  onDidChangeOutputItems?: Event<CellOutput>;
67
71
  onDidChangeLanguage: Event<string>;
@@ -129,6 +133,9 @@ export class NotebookCellModel implements NotebookCell, Disposable {
129
133
  protected onDidRequestCenterEditorEmitter = new Emitter<void>();
130
134
  readonly onDidRequestCenterEditor = this.onDidRequestCenterEditorEmitter.event;
131
135
 
136
+ protected onDidCellHeightChangeEmitter = new Emitter<number>();
137
+ readonly onDidCellHeightChange = this.onDidCellHeightChangeEmitter.event;
138
+
132
139
  @inject(NotebookCellModelProps)
133
140
  protected readonly props: NotebookCellModelProps;
134
141
 
@@ -251,6 +258,28 @@ export class NotebookCellModel implements NotebookCell, Disposable {
251
258
  }
252
259
  }
253
260
 
261
+ protected _selection: Range | undefined = undefined;
262
+
263
+ get selection(): Range | undefined {
264
+ return this._selection;
265
+ }
266
+
267
+ set selection(selection: Range | undefined) {
268
+ this._selection = selection;
269
+ }
270
+
271
+ protected _cellheight: number = 0;
272
+ get cellHeight(): number {
273
+ return this._cellheight;
274
+ }
275
+
276
+ set cellHeight(height: number) {
277
+ if (height !== this._cellheight) {
278
+ this.onDidCellHeightChangeEmitter.fire(height);
279
+ this._cellheight = height;
280
+ }
281
+ }
282
+
254
283
  @postConstruct()
255
284
  protected init(): void {
256
285
  this._outputs = this.props.outputs.map(op => new NotebookCellOutputModel(op));