@theia/notebook 1.52.0 → 1.53.0-next.5

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/cell-operations.d.ts +1 -1
  2. package/lib/browser/contributions/cell-operations.d.ts.map +1 -1
  3. package/lib/browser/contributions/cell-operations.js +10 -2
  4. package/lib/browser/contributions/cell-operations.js.map +1 -1
  5. package/lib/browser/contributions/notebook-actions-contribution.d.ts +1 -0
  6. package/lib/browser/contributions/notebook-actions-contribution.d.ts.map +1 -1
  7. package/lib/browser/contributions/notebook-actions-contribution.js +19 -23
  8. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  9. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts +2 -0
  10. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  11. package/lib/browser/contributions/notebook-cell-actions-contribution.js +26 -15
  12. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  13. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts +10 -0
  14. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts.map +1 -0
  15. package/lib/browser/contributions/notebook-undo-redo-handler.js +49 -0
  16. package/lib/browser/contributions/notebook-undo-redo-handler.js.map +1 -0
  17. package/lib/browser/notebook-editor-widget.d.ts +6 -0
  18. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  19. package/lib/browser/notebook-editor-widget.js +39 -1
  20. package/lib/browser/notebook-editor-widget.js.map +1 -1
  21. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  22. package/lib/browser/notebook-frontend-module.js +3 -0
  23. package/lib/browser/notebook-frontend-module.js.map +1 -1
  24. package/lib/browser/service/notebook-context-manager.d.ts +2 -1
  25. package/lib/browser/service/notebook-context-manager.d.ts.map +1 -1
  26. package/lib/browser/service/notebook-context-manager.js +6 -3
  27. package/lib/browser/service/notebook-context-manager.js.map +1 -1
  28. package/lib/browser/service/notebook-options.d.ts +1 -0
  29. package/lib/browser/service/notebook-options.d.ts.map +1 -1
  30. package/lib/browser/service/notebook-options.js +1 -0
  31. package/lib/browser/service/notebook-options.js.map +1 -1
  32. package/lib/browser/service/notebook-service.d.ts +1 -0
  33. package/lib/browser/service/notebook-service.d.ts.map +1 -1
  34. package/lib/browser/service/notebook-service.js +7 -0
  35. package/lib/browser/service/notebook-service.js.map +1 -1
  36. package/lib/browser/view/notebook-cell-editor.d.ts +7 -1
  37. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  38. package/lib/browser/view/notebook-cell-editor.js +75 -5
  39. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  40. package/lib/browser/view/notebook-cell-list-view.d.ts +3 -0
  41. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  42. package/lib/browser/view/notebook-cell-list-view.js +26 -2
  43. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  44. package/lib/browser/view/notebook-code-cell-view.d.ts +1 -1
  45. package/lib/browser/view/notebook-code-cell-view.d.ts.map +1 -1
  46. package/lib/browser/view/notebook-code-cell-view.js +19 -17
  47. package/lib/browser/view/notebook-code-cell-view.js.map +1 -1
  48. package/lib/browser/view/notebook-find-widget.d.ts +63 -0
  49. package/lib/browser/view/notebook-find-widget.d.ts.map +1 -0
  50. package/lib/browser/view/notebook-find-widget.js +225 -0
  51. package/lib/browser/view/notebook-find-widget.js.map +1 -0
  52. package/lib/browser/view/notebook-markdown-cell-view.d.ts +4 -0
  53. package/lib/browser/view/notebook-markdown-cell-view.d.ts.map +1 -1
  54. package/lib/browser/view/notebook-markdown-cell-view.js +105 -8
  55. package/lib/browser/view/notebook-markdown-cell-view.js.map +1 -1
  56. package/lib/browser/view-model/notebook-cell-model.d.ts +24 -1
  57. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  58. package/lib/browser/view-model/notebook-cell-model.js +71 -1
  59. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  60. package/lib/browser/view-model/notebook-model.d.ts +8 -1
  61. package/lib/browser/view-model/notebook-model.d.ts.map +1 -1
  62. package/lib/browser/view-model/notebook-model.js +66 -14
  63. package/lib/browser/view-model/notebook-model.js.map +1 -1
  64. package/package.json +8 -7
  65. package/src/browser/contributions/cell-operations.ts +8 -2
  66. package/src/browser/contributions/notebook-actions-contribution.ts +21 -22
  67. package/src/browser/contributions/notebook-cell-actions-contribution.ts +26 -16
  68. package/src/browser/contributions/notebook-undo-redo-handler.ts +41 -0
  69. package/src/browser/notebook-editor-widget.tsx +49 -2
  70. package/src/browser/notebook-frontend-module.ts +5 -1
  71. package/src/browser/service/notebook-context-manager.ts +8 -4
  72. package/src/browser/service/notebook-options.ts +2 -1
  73. package/src/browser/service/notebook-service.ts +7 -1
  74. package/src/browser/style/index.css +165 -6
  75. package/src/browser/view/notebook-cell-editor.tsx +79 -6
  76. package/src/browser/view/notebook-cell-list-view.tsx +32 -3
  77. package/src/browser/view/notebook-code-cell-view.tsx +19 -17
  78. package/src/browser/view/notebook-find-widget.tsx +335 -0
  79. package/src/browser/view/notebook-markdown-cell-view.tsx +134 -17
  80. package/src/browser/view-model/notebook-cell-model.ts +94 -8
  81. package/src/browser/view-model/notebook-model.ts +77 -20
@@ -31,6 +31,10 @@
31
31
  margin: 10px 0px;
32
32
  }
33
33
 
34
+ .theia-notebook-cell:focus {
35
+ outline: none;
36
+ }
37
+
34
38
  .theia-notebook-cell.draggable {
35
39
  cursor: grab;
36
40
  }
@@ -62,31 +66,36 @@
62
66
  width: calc(100% - 15px);
63
67
  }
64
68
 
69
+ /* Rendered Markdown Content */
70
+
65
71
  .theia-notebook-markdown-content {
66
72
  padding: 8px 16px 8px 36px;
67
73
  font-size: var(--theia-notebook-markdown-size);
68
74
  }
69
75
 
76
+ .theia-notebook-markdown-content>* {
77
+ font-weight: 400;
78
+ }
79
+
70
80
  .theia-notebook-markdown-content>*:first-child {
71
81
  margin-top: 0;
72
82
  padding-top: 0;
73
83
  }
74
84
 
75
- .theia-notebook-markdown-content>*:only-child,
76
85
  .theia-notebook-markdown-content>*:last-child {
77
86
  margin-bottom: 0;
78
87
  padding-bottom: 0;
79
88
  }
80
89
 
81
90
  /* Markdown cell edit mode */
82
- .theia-notebook-cell-content:has(> .theia-notebook-cell-editor) {
83
- margin-left: 37px;
91
+ .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
92
+ margin-left: 36px;
84
93
  margin-right: var(--theia-notebook-cell-editor-margin-right);
85
94
  outline: 1px solid var(--theia-notebook-cellBorderColor);
86
95
  }
87
96
 
88
97
  /* Markdown cell edit mode focused */
89
- .theia-notebook-cell.focused .theia-notebook-cell-content:has(> .theia-notebook-cell-editor) {
98
+ .theia-notebook-cell.focused .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
90
99
  outline-color: var(--theia-notebook-focusedEditorBorder);
91
100
  }
92
101
 
@@ -102,7 +111,7 @@
102
111
  width: calc(100% - 46px);
103
112
  flex: 1;
104
113
  outline: 1px solid var(--theia-notebook-cellBorderColor);
105
- margin: 0px 10px;
114
+ margin: 0px 16px 0px 10px;
106
115
  }
107
116
 
108
117
  .theia-notebook-cell.focused .theia-notebook-cell-editor-container {
@@ -283,7 +292,7 @@
283
292
 
284
293
  .theia-notebook-cell-output-webview {
285
294
  padding: 5px 0px;
286
- margin: 0px 10px;
295
+ margin: 0px 15px 0px 9px;
287
296
  width: 100%;
288
297
  }
289
298
 
@@ -310,3 +319,153 @@
310
319
  min-height: 100px;
311
320
  background-color: var(--theia-editor-background);
312
321
  }
322
+
323
+ /* Notebook Find Widget */
324
+
325
+ .theia-notebook-overlay {
326
+ position: absolute;
327
+ z-index: 100;
328
+ right: 18px;
329
+ }
330
+
331
+ .theia-notebook-find-widget {
332
+ /* position: absolute;
333
+ z-index: 35;
334
+ height: 33px;
335
+ overflow: hidden; */
336
+ line-height: 19px;
337
+ transition: transform 200ms linear;
338
+ display: flex;
339
+ flex-direction: row;
340
+ padding: 0 4px;
341
+ box-sizing: border-box;
342
+ box-shadow: 0 0 8px 2px var(--theia-widget-shadow);
343
+ background-color: var(--theia-editorWidget-background);
344
+ color: var(--theia-editorWidget-foreground);
345
+ border-left: 1px solid var(--theia-widget-border);
346
+ border-right: 1px solid var(--theia-widget-border);
347
+ border-bottom: 1px solid var(--theia-widget-border);
348
+ border-bottom-left-radius: 4px;
349
+ border-bottom-right-radius: 4px;
350
+ }
351
+
352
+ .theia-notebook-find-widget.hidden {
353
+ display: none;
354
+ transform: translateY(calc(-100% - 10px));
355
+ }
356
+
357
+ .theia-notebook-find-widget.search-mode>*>*:nth-child(2) {
358
+ display: none;
359
+ }
360
+
361
+ .theia-notebook-find-widget-expand {
362
+ display: flex;
363
+ flex-direction: row;
364
+ align-items: center;
365
+ cursor: pointer;
366
+ border-radius: 0;
367
+ margin-right: 4px;
368
+ }
369
+
370
+ .theia-notebook-find-widget-expand:focus {
371
+ outline: 1px solid var(--theia-focusBorder);
372
+ }
373
+
374
+ .theia-notebook-find-widget-expand:hover {
375
+ background-color: var(--theia-toolbar-hoverBackground);
376
+ }
377
+
378
+ .theia-notebook-find-widget-buttons-first {
379
+ margin-bottom: 4px;
380
+ height: 26px;
381
+ display: flex;
382
+ flex-direction: row;
383
+ align-items: center;
384
+ }
385
+
386
+ .theia-notebook-find-widget-buttons-first>div,
387
+ .theia-notebook-find-widget-buttons-second>div {
388
+ margin-right: 4px;
389
+ }
390
+
391
+ .theia-notebook-find-widget-buttons-second {
392
+ height: 26px;
393
+ display: flex;
394
+ flex-direction: row;
395
+ align-items: center;
396
+ }
397
+
398
+ .theia-notebook-find-widget-inputs {
399
+ margin-top: 4px;
400
+ display: flex;
401
+ flex-direction: column;
402
+ }
403
+
404
+ .theia-notebook-find-widget-buttons {
405
+ margin-top: 4px;
406
+ margin-left: 4px;
407
+ display: flex;
408
+ flex-direction: column;
409
+ }
410
+
411
+ .theia-notebook-find-widget-matches-count {
412
+ width: 72px;
413
+ box-sizing: border-box;
414
+ overflow: hidden;
415
+ text-align: center;
416
+ text-overflow: ellipsis;
417
+ white-space: nowrap;
418
+ }
419
+
420
+ .theia-notebook-find-widget-input-wrapper {
421
+ display: flex;
422
+ align-items: center;
423
+ background: var(--theia-input-background);
424
+ border-style: solid;
425
+ border-width: var(--theia-border-width);
426
+ border-color: var(--theia-input-background);
427
+ border-radius: 2px;
428
+ margin-bottom: 4px;
429
+ }
430
+
431
+ .theia-notebook-find-widget-input-wrapper:focus-within {
432
+ border-color: var(--theia-focusBorder);
433
+ }
434
+
435
+ .theia-notebook-find-widget-input-wrapper .option.enabled {
436
+ color: var(--theia-inputOption-activeForeground);
437
+ outline: 1px solid var(--theia-inputOption-activeBorder);
438
+ background-color: var(--theia-inputOption-activeBackground);
439
+ }
440
+
441
+ .theia-notebook-find-widget-input-wrapper .option {
442
+ margin: 2px;
443
+ }
444
+
445
+ .theia-notebook-find-widget-input-wrapper .theia-notebook-find-widget-input:focus {
446
+ border: none;
447
+ outline: none;
448
+ }
449
+
450
+ .theia-notebook-find-widget-input-wrapper .theia-notebook-find-widget-input {
451
+ background: none;
452
+ border: none;
453
+ }
454
+
455
+ .theia-notebook-find-widget-replace {
456
+ margin-bottom: 4px;
457
+ }
458
+
459
+ .theia-notebook-find-widget-buttons .disabled {
460
+ opacity: 0.5;
461
+ }
462
+
463
+ mark.theia-find-match {
464
+ color: var(--theia-editor-findMatchHighlightForeground);
465
+ background-color: var(--theia-editor-findMatchHighlightBackground);
466
+ }
467
+
468
+ mark.theia-find-match.theia-find-match-selected {
469
+ color: var(--theia-editor-findMatchForeground);
470
+ background-color: var(--theia-editor-findMatchBackground);
471
+ }
@@ -16,7 +16,7 @@
16
16
 
17
17
  import * as React from '@theia/core/shared/react';
18
18
  import { NotebookModel } from '../view-model/notebook-model';
19
- import { NotebookCellModel } from '../view-model/notebook-cell-model';
19
+ import { NotebookCellModel, NotebookCodeEditorFindMatch } from '../view-model/notebook-cell-model';
20
20
  import { SimpleMonacoEditor } from '@theia/monaco/lib/browser/simple-monaco-editor';
21
21
  import { MonacoEditor, MonacoEditorServices } from '@theia/monaco/lib/browser/monaco-editor';
22
22
  import { MonacoEditorProvider } from '@theia/monaco/lib/browser/monaco-editor-provider';
@@ -27,6 +27,9 @@ import { NotebookViewportService } from './notebook-viewport-service';
27
27
  import { BareFontInfo } from '@theia/monaco-editor-core/esm/vs/editor/common/config/fontInfo';
28
28
  import { NOTEBOOK_CELL_CURSOR_FIRST_LINE, NOTEBOOK_CELL_CURSOR_LAST_LINE } from '../contributions/notebook-context-keys';
29
29
  import { EditorExtensionsRegistry } from '@theia/monaco-editor-core/esm/vs/editor/browser/editorExtensions';
30
+ import { ModelDecorationOptions } from '@theia/monaco-editor-core/esm/vs/editor/common/model/textModel';
31
+ import { IModelDeltaDecoration, OverviewRulerLane, TrackedRangeStickiness } from '@theia/monaco-editor-core/esm/vs/editor/common/model';
32
+ import { animationFrame } from '@theia/core/lib/browser';
30
33
 
31
34
  interface CellEditorProps {
32
35
  notebookModel: NotebookModel,
@@ -48,18 +51,46 @@ const DEFAULT_EDITOR_OPTIONS: MonacoEditor.IOptions = {
48
51
  lineDecorationsWidth: 10,
49
52
  };
50
53
 
54
+ export const CURRENT_FIND_MATCH_DECORATION = ModelDecorationOptions.register({
55
+ description: 'current-find-match',
56
+ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
57
+ zIndex: 13,
58
+ className: 'currentFindMatch',
59
+ inlineClassName: 'currentFindMatchInline',
60
+ showIfCollapsed: true,
61
+ overviewRuler: {
62
+ color: 'editorOverviewRuler.findMatchForeground',
63
+ position: OverviewRulerLane.Center
64
+ }
65
+ });
66
+
67
+ export const FIND_MATCH_DECORATION = ModelDecorationOptions.register({
68
+ description: 'find-match',
69
+ stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
70
+ zIndex: 10,
71
+ className: 'findMatch',
72
+ inlineClassName: 'findMatchInline',
73
+ showIfCollapsed: true,
74
+ overviewRuler: {
75
+ color: 'editorOverviewRuler.findMatchForeground',
76
+ position: OverviewRulerLane.Center
77
+ }
78
+ });
79
+
51
80
  export class CellEditor extends React.Component<CellEditorProps, {}> {
52
81
 
53
82
  protected editor?: SimpleMonacoEditor;
54
83
  protected toDispose = new DisposableCollection();
55
84
  protected container?: HTMLDivElement;
85
+ protected matches: NotebookCodeEditorFindMatch[] = [];
86
+ protected oldMatchDecorations: string[] = [];
56
87
 
57
88
  override componentDidMount(): void {
58
89
  this.disposeEditor();
59
90
  this.toDispose.push(this.props.cell.onWillFocusCellEditor(focusRequest => {
60
91
  this.editor?.getControl().focus();
61
92
  const lineCount = this.editor?.getControl().getModel()?.getLineCount();
62
- if (focusRequest && lineCount) {
93
+ if (focusRequest && lineCount !== undefined) {
63
94
  this.editor?.getControl().setPosition(focusRequest === 'lastLine' ?
64
95
  { lineNumber: lineCount, column: 1 } :
65
96
  { lineNumber: focusRequest, column: 1 },
@@ -68,7 +99,6 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
68
99
  const currentLine = this.editor?.getControl().getPosition()?.lineNumber;
69
100
  this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_FIRST_LINE, currentLine === 1);
70
101
  this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_LAST_LINE, currentLine === lineCount);
71
-
72
102
  }));
73
103
 
74
104
  this.toDispose.push(this.props.cell.onDidChangeEditorOptions(options => {
@@ -79,6 +109,26 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
79
109
  this.editor?.setLanguage(language);
80
110
  }));
81
111
 
112
+ this.toDispose.push(this.props.cell.onDidFindMatches(matches => {
113
+ this.matches = matches;
114
+ animationFrame().then(() => this.setMatches());
115
+ }));
116
+
117
+ this.toDispose.push(this.props.cell.onDidSelectFindMatch(match => {
118
+ const editorDomNode = this.editor?.getControl().getDomNode();
119
+ if (editorDomNode) {
120
+ editorDomNode.scrollIntoView({
121
+ behavior: 'instant',
122
+ block: 'center'
123
+ });
124
+ } else {
125
+ this.container?.scrollIntoView({
126
+ behavior: 'instant',
127
+ block: 'center'
128
+ });
129
+ }
130
+ }));
131
+
82
132
  this.toDispose.push(this.props.notebookModel.onDidChangeSelectedCell(e => {
83
133
  if (e.cell !== this.props.cell && this.editor?.getControl().hasTextFocus()) {
84
134
  this.props.notebookContextManager.context?.focus();
@@ -130,11 +180,11 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
130
180
  notebookModel.cellDirtyChanged(cell, true);
131
181
  }));
132
182
  this.toDispose.push(this.editor.getControl().onDidFocusEditorText(() => {
133
- this.props.notebookContextManager.onDidEditorTextFocus(true);
134
183
  this.props.notebookModel.setSelectedCell(cell, false);
135
184
  }));
136
- this.toDispose.push(this.editor.getControl().onDidBlurEditorText(() => {
137
- this.props.notebookContextManager.onDidEditorTextFocus(false);
185
+ this.toDispose.push(this.editor.getControl().onDidChangeCursorSelection(e => {
186
+ const selectedText = this.editor!.getControl().getModel()!.getValueInRange(e.selection);
187
+ this.props.notebookModel.selectedText = selectedText;
138
188
  }));
139
189
  this.toDispose.push(this.editor.getControl().onDidChangeCursorPosition(e => {
140
190
  if (e.secondaryPositions.length === 0) {
@@ -149,7 +199,30 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
149
199
  if (cell.editing && notebookModel.selectedCell === cell) {
150
200
  this.editor.getControl().focus();
151
201
  }
202
+ this.setMatches();
203
+ }
204
+ }
205
+
206
+ protected setMatches(): void {
207
+ if (!this.editor) {
208
+ return;
152
209
  }
210
+ const decorations: IModelDeltaDecoration[] = [];
211
+ for (const match of this.matches) {
212
+ const decoration = match.selected ? CURRENT_FIND_MATCH_DECORATION : FIND_MATCH_DECORATION;
213
+ decorations.push({
214
+ range: {
215
+ startLineNumber: match.range.start.line,
216
+ startColumn: match.range.start.character,
217
+ endLineNumber: match.range.end.line,
218
+ endColumn: match.range.end.character
219
+ },
220
+ options: decoration
221
+ });
222
+ }
223
+
224
+ this.oldMatchDecorations = this.editor.getControl()
225
+ .changeDecorations(accessor => accessor.deltaDecorations(this.oldMatchDecorations, decorations));
153
226
  }
154
227
 
155
228
  protected setContainer(component: HTMLDivElement | null): void {
@@ -14,14 +14,15 @@
14
14
  // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
15
  // *****************************************************************************
16
16
  import * as React from '@theia/core/shared/react';
17
- import { CellEditType, CellKind } from '../../common';
17
+ import { CellEditType, CellKind, NotebookCellsChangeType } from '../../common';
18
18
  import { NotebookCellModel } from '../view-model/notebook-cell-model';
19
19
  import { NotebookModel } from '../view-model/notebook-model';
20
20
  import { NotebookCellToolbarFactory } from './notebook-cell-toolbar-factory';
21
- import { codicon } from '@theia/core/lib/browser';
21
+ import { animationFrame, codicon, onDomEvent } from '@theia/core/lib/browser';
22
22
  import { CommandRegistry, DisposableCollection, nls } from '@theia/core';
23
23
  import { NotebookCommands } from '../contributions/notebook-actions-contribution';
24
24
  import { NotebookCellActionContribution } from '../contributions/notebook-cell-actions-contribution';
25
+ import { NotebookContextManager } from '../service/notebook-context-manager';
25
26
 
26
27
  export interface CellRenderer {
27
28
  render(notebookData: NotebookModel, cell: NotebookCellModel, index: number): React.ReactNode
@@ -31,6 +32,7 @@ export interface CellRenderer {
31
32
  interface CellListProps {
32
33
  renderers: Map<CellKind, CellRenderer>;
33
34
  notebookModel: NotebookModel;
35
+ notebookContext: NotebookContextManager;
34
36
  toolbarRenderer: NotebookCellToolbarFactory;
35
37
  commandRegistry: CommandRegistry
36
38
  }
@@ -46,6 +48,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
46
48
  protected toDispose = new DisposableCollection();
47
49
 
48
50
  protected static dragGhost: HTMLElement | undefined;
51
+ protected cellListRef: React.RefObject<HTMLUListElement> = React.createRef();
49
52
 
50
53
  constructor(props: CellListProps) {
51
54
  super(props);
@@ -66,6 +69,13 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
66
69
  }
67
70
  }));
68
71
 
72
+ this.toDispose.push(props.notebookModel.onDidChangeContent(events => {
73
+ if (events.some(e => e.kind === NotebookCellsChangeType.Move)) {
74
+ // When a cell has been moved, we need to rerender the whole component
75
+ this.forceUpdate();
76
+ }
77
+ }));
78
+
69
79
  this.toDispose.push(props.notebookModel.onDidChangeSelectedCell(e => {
70
80
  this.setState({
71
81
  ...this.state,
@@ -73,6 +83,24 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
73
83
  scrollIntoView: e.scrollIntoView
74
84
  });
75
85
  }));
86
+
87
+ this.toDispose.push(onDomEvent(document, 'focusin', () => {
88
+ animationFrame().then(() => {
89
+ if (!this.cellListRef.current) {
90
+ return;
91
+ }
92
+ let hasCellFocus = false;
93
+ let hasFocus = false;
94
+ if (this.cellListRef.current.contains(document.activeElement)) {
95
+ if (this.props.notebookModel.selectedCell) {
96
+ hasCellFocus = true;
97
+ }
98
+ hasFocus = true;
99
+ }
100
+ this.props.notebookContext.changeCellFocus(hasCellFocus);
101
+ this.props.notebookContext.changeCellListFocus(hasFocus);
102
+ });
103
+ }));
76
104
  }
77
105
 
78
106
  override componentWillUnmount(): void {
@@ -80,7 +108,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
80
108
  }
81
109
 
82
110
  override render(): React.ReactNode {
83
- return <ul className='theia-notebook-cell-list'>
111
+ return <ul className='theia-notebook-cell-list' ref={this.cellListRef}>
84
112
  {this.props.notebookModel.cells
85
113
  .map((cell, index) =>
86
114
  <React.Fragment key={'cell-' + cell.handle}>
@@ -103,6 +131,7 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
103
131
  onDragOver={e => this.onDragOver(e, cell)}
104
132
  onDrop={e => this.onDrop(e, index)}
105
133
  draggable={true}
134
+ tabIndex={-1}
106
135
  ref={ref => cell === this.state.selectedCell && this.state.scrollIntoView && ref?.scrollIntoView({ block: 'nearest' })}>
107
136
  <div className={'theia-notebook-cell-marker' + (this.state.selectedCell === cell ? ' theia-notebook-cell-marker-selected' : '')}></div>
108
137
  <div className='theia-notebook-cell-content'>
@@ -150,7 +150,7 @@ export interface NotebookCodeCellStatusProps {
150
150
  notebook: NotebookModel;
151
151
  cell: NotebookCellModel;
152
152
  commandRegistry: CommandRegistry;
153
- executionStateService: NotebookExecutionStateService;
153
+ executionStateService?: NotebookExecutionStateService;
154
154
  onClick: () => void;
155
155
  }
156
156
 
@@ -171,22 +171,24 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
171
171
  };
172
172
 
173
173
  let currentInterval: NodeJS.Timeout | undefined;
174
- this.toDispose.push(props.executionStateService.onDidChangeExecution(event => {
175
- if (event.affectsCell(this.props.cell.uri)) {
176
- this.setState({ currentExecution: event.changed, executionTime: 0 });
177
- clearInterval(currentInterval);
178
- if (event.changed?.state === NotebookCellExecutionState.Executing) {
179
- const startTime = Date.now();
180
- // The resolution of the time display is only a single digit after the decimal point.
181
- // Therefore, we only need to update the display every 100ms.
182
- currentInterval = setInterval(() => {
183
- this.setState({
184
- executionTime: Date.now() - startTime
185
- });
186
- }, 100);
174
+ if (props.executionStateService) {
175
+ this.toDispose.push(props.executionStateService.onDidChangeExecution(event => {
176
+ if (event.affectsCell(this.props.cell.uri)) {
177
+ this.setState({ currentExecution: event.changed, executionTime: 0 });
178
+ clearInterval(currentInterval);
179
+ if (event.changed?.state === NotebookCellExecutionState.Executing) {
180
+ const startTime = Date.now();
181
+ // The resolution of the time display is only a single digit after the decimal point.
182
+ // Therefore, we only need to update the display every 100ms.
183
+ currentInterval = setInterval(() => {
184
+ this.setState({
185
+ executionTime: Date.now() - startTime
186
+ });
187
+ }, 100);
188
+ }
187
189
  }
188
- }
189
- }));
190
+ }));
191
+ }
190
192
 
191
193
  this.toDispose.push(props.cell.onDidChangeLanguage(() => {
192
194
  this.forceUpdate();
@@ -200,7 +202,7 @@ export class NotebookCodeCellStatus extends React.Component<NotebookCodeCellStat
200
202
  override render(): React.ReactNode {
201
203
  return <div className='notebook-cell-status' onClick={() => this.props.onClick()}>
202
204
  <div className='notebook-cell-status-left'>
203
- {this.renderExecutionState()}
205
+ {this.props.executionStateService && this.renderExecutionState()}
204
206
  </div>
205
207
  <div className='notebook-cell-status-right'>
206
208
  <span className='notebook-cell-language-label' onClick={() => {