@theia/notebook 1.51.0 → 1.53.0-next.18

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 (91) 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 +2 -0
  6. package/lib/browser/contributions/notebook-actions-contribution.d.ts.map +1 -1
  7. package/lib/browser/contributions/notebook-actions-contribution.js +43 -24
  8. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  9. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts +4 -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 +63 -10
  12. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  13. package/lib/browser/contributions/notebook-preferences.d.ts +3 -0
  14. package/lib/browser/contributions/notebook-preferences.d.ts.map +1 -1
  15. package/lib/browser/contributions/notebook-preferences.js +9 -1
  16. package/lib/browser/contributions/notebook-preferences.js.map +1 -1
  17. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts +14 -0
  18. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts.map +1 -0
  19. package/lib/browser/contributions/notebook-status-bar-contribution.js +75 -0
  20. package/lib/browser/contributions/notebook-status-bar-contribution.js.map +1 -0
  21. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts +10 -0
  22. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts.map +1 -0
  23. package/lib/browser/contributions/notebook-undo-redo-handler.js +49 -0
  24. package/lib/browser/contributions/notebook-undo-redo-handler.js.map +1 -0
  25. package/lib/browser/notebook-editor-widget.d.ts +6 -0
  26. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  27. package/lib/browser/notebook-editor-widget.js +40 -4
  28. package/lib/browser/notebook-editor-widget.js.map +1 -1
  29. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  30. package/lib/browser/notebook-frontend-module.js +7 -1
  31. package/lib/browser/notebook-frontend-module.js.map +1 -1
  32. package/lib/browser/service/notebook-context-manager.d.ts +2 -1
  33. package/lib/browser/service/notebook-context-manager.d.ts.map +1 -1
  34. package/lib/browser/service/notebook-context-manager.js +6 -3
  35. package/lib/browser/service/notebook-context-manager.js.map +1 -1
  36. package/lib/browser/service/notebook-options.d.ts +1 -0
  37. package/lib/browser/service/notebook-options.d.ts.map +1 -1
  38. package/lib/browser/service/notebook-options.js +1 -0
  39. package/lib/browser/service/notebook-options.js.map +1 -1
  40. package/lib/browser/service/notebook-service.d.ts +1 -0
  41. package/lib/browser/service/notebook-service.d.ts.map +1 -1
  42. package/lib/browser/service/notebook-service.js +7 -0
  43. package/lib/browser/service/notebook-service.js.map +1 -1
  44. package/lib/browser/view/notebook-cell-editor.d.ts +8 -1
  45. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  46. package/lib/browser/view/notebook-cell-editor.js +89 -5
  47. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  48. package/lib/browser/view/notebook-cell-list-view.d.ts +3 -0
  49. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  50. package/lib/browser/view/notebook-cell-list-view.js +33 -2
  51. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  52. package/lib/browser/view/notebook-code-cell-view.d.ts +1 -1
  53. package/lib/browser/view/notebook-code-cell-view.d.ts.map +1 -1
  54. package/lib/browser/view/notebook-code-cell-view.js +19 -17
  55. package/lib/browser/view/notebook-code-cell-view.js.map +1 -1
  56. package/lib/browser/view/notebook-find-widget.d.ts +63 -0
  57. package/lib/browser/view/notebook-find-widget.d.ts.map +1 -0
  58. package/lib/browser/view/notebook-find-widget.js +225 -0
  59. package/lib/browser/view/notebook-find-widget.js.map +1 -0
  60. package/lib/browser/view/notebook-markdown-cell-view.d.ts +4 -0
  61. package/lib/browser/view/notebook-markdown-cell-view.d.ts.map +1 -1
  62. package/lib/browser/view/notebook-markdown-cell-view.js +105 -8
  63. package/lib/browser/view/notebook-markdown-cell-view.js.map +1 -1
  64. package/lib/browser/view-model/notebook-cell-model.d.ts +27 -1
  65. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  66. package/lib/browser/view-model/notebook-cell-model.js +76 -1
  67. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  68. package/lib/browser/view-model/notebook-model.d.ts +8 -1
  69. package/lib/browser/view-model/notebook-model.d.ts.map +1 -1
  70. package/lib/browser/view-model/notebook-model.js +66 -14
  71. package/lib/browser/view-model/notebook-model.js.map +1 -1
  72. package/package.json +9 -8
  73. package/src/browser/contributions/cell-operations.ts +8 -2
  74. package/src/browser/contributions/notebook-actions-contribution.ts +47 -22
  75. package/src/browser/contributions/notebook-cell-actions-contribution.ts +66 -11
  76. package/src/browser/contributions/notebook-preferences.ts +10 -1
  77. package/src/browser/contributions/notebook-status-bar-contribution.ts +77 -0
  78. package/src/browser/contributions/notebook-undo-redo-handler.ts +41 -0
  79. package/src/browser/notebook-editor-widget.tsx +50 -5
  80. package/src/browser/notebook-frontend-module.ts +11 -3
  81. package/src/browser/service/notebook-context-manager.ts +8 -4
  82. package/src/browser/service/notebook-options.ts +2 -1
  83. package/src/browser/service/notebook-service.ts +7 -1
  84. package/src/browser/style/index.css +178 -7
  85. package/src/browser/view/notebook-cell-editor.tsx +94 -5
  86. package/src/browser/view/notebook-cell-list-view.tsx +40 -4
  87. package/src/browser/view/notebook-code-cell-view.tsx +19 -17
  88. package/src/browser/view/notebook-find-widget.tsx +335 -0
  89. package/src/browser/view/notebook-markdown-cell-view.tsx +134 -17
  90. package/src/browser/view-model/notebook-cell-model.ts +101 -8
  91. package/src/browser/view-model/notebook-model.ts +77 -20
@@ -37,7 +37,7 @@ const notebookOutputOptionsRelevantPreferences = [
37
37
 
38
38
  export interface NotebookOutputOptions {
39
39
  // readonly outputNodePadding: number;
40
- // readonly outputNodeLeftPadding: number;
40
+ readonly outputNodeLeftPadding: number;
41
41
  // readonly previewNodePadding: number;
42
42
  // readonly markdownLeftMargin: number;
43
43
  // readonly leftMargin: number;
@@ -95,6 +95,7 @@ export class NotebookOptionsService {
95
95
  fontSize,
96
96
  outputFontSize: outputFontSize,
97
97
  fontFamily: this.preferenceService.get<string>('editor.fontFamily')!,
98
+ outputNodeLeftPadding: 8,
98
99
  outputFontFamily: this.getNotebookPreferenceWithDefault<string>(NotebookPreferences.OUTPUT_FONT_FAMILY),
99
100
  outputLineHeight: this.computeOutputLineHeight(outputLineHeight, outputFontSize ?? fontSize),
100
101
  outputScrolling: this.getNotebookPreferenceWithDefault<boolean>(NotebookPreferences.OUTPUT_SCROLLING)!,
@@ -17,7 +17,7 @@
17
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
- import { NotebookData, TransientOptions } from '../../common';
20
+ import { CellKind, NotebookData, TransientOptions } from '../../common';
21
21
  import { NotebookModel, NotebookModelFactory, NotebookModelProps } from '../view-model/notebook-model';
22
22
  import { FileService } from '@theia/filesystem/lib/browser/file-service';
23
23
  import { NotebookCellModel, NotebookCellModelFactory, NotebookCellModelProps } from '../view-model/notebook-cell-model';
@@ -206,4 +206,10 @@ export class NotebookService implements Disposable {
206
206
  return false;
207
207
  }
208
208
  }
209
+
210
+ getCodeCellLanguage(model: NotebookModel): string {
211
+ const firstCodeCell = model.cells.find(cellModel => cellModel.cellKind === CellKind.Code);
212
+ const cellLanguage = firstCodeCell?.language ?? 'plaintext';
213
+ return cellLanguage;
214
+ }
209
215
  }
@@ -16,6 +16,7 @@
16
16
 
17
17
  :root {
18
18
  --theia-notebook-markdown-size: 17px;
19
+ --theia-notebook-cell-editor-margin-right: 10px;
19
20
  }
20
21
 
21
22
  .theia-notebook-cell-list {
@@ -30,6 +31,10 @@
30
31
  margin: 10px 0px;
31
32
  }
32
33
 
34
+ .theia-notebook-cell:focus {
35
+ outline: none;
36
+ }
37
+
33
38
  .theia-notebook-cell.draggable {
34
39
  cursor: grab;
35
40
  }
@@ -61,22 +66,39 @@
61
66
  width: calc(100% - 15px);
62
67
  }
63
68
 
69
+ /* Rendered Markdown Content */
70
+
64
71
  .theia-notebook-markdown-content {
65
72
  padding: 8px 16px 8px 36px;
66
73
  font-size: var(--theia-notebook-markdown-size);
67
74
  }
68
75
 
69
- .theia-notebook-markdown-content > *:first-child {
76
+ .theia-notebook-markdown-content>* {
77
+ font-weight: 400;
78
+ }
79
+
80
+ .theia-notebook-markdown-content>*:first-child {
70
81
  margin-top: 0;
71
82
  padding-top: 0;
72
83
  }
73
84
 
74
- .theia-notebook-markdown-content > *:only-child,
75
- .theia-notebook-markdown-content > *:last-child {
85
+ .theia-notebook-markdown-content>*:last-child {
76
86
  margin-bottom: 0;
77
87
  padding-bottom: 0;
78
88
  }
79
89
 
90
+ /* Markdown cell edit mode */
91
+ .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
92
+ margin-left: 36px;
93
+ margin-right: var(--theia-notebook-cell-editor-margin-right);
94
+ outline: 1px solid var(--theia-notebook-cellBorderColor);
95
+ }
96
+
97
+ /* Markdown cell edit mode focused */
98
+ .theia-notebook-cell.focused .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
99
+ outline-color: var(--theia-notebook-focusedEditorBorder);
100
+ }
101
+
80
102
  .theia-notebook-empty-markdown {
81
103
  opacity: 0.6;
82
104
  }
@@ -89,7 +111,7 @@
89
111
  width: calc(100% - 46px);
90
112
  flex: 1;
91
113
  outline: 1px solid var(--theia-notebook-cellBorderColor);
92
- margin: 0px 10px;
114
+ margin: 0px 16px 0px 10px;
93
115
  }
94
116
 
95
117
  .theia-notebook-cell.focused .theia-notebook-cell-editor-container {
@@ -189,8 +211,7 @@
189
211
 
190
212
  .theia-notebook-main-container .theia-notebook-main-loading-indicator {
191
213
  /* `progress-animation` is defined in `packages/core/src/browser/style/progress-bar.css` */
192
- animation: progress-animation 1.8s 0s infinite
193
- cubic-bezier(0.645, 0.045, 0.355, 1);
214
+ animation: progress-animation 1.8s 0s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
194
215
  background-color: var(--theia-progressBar-background);
195
216
  height: 2px;
196
217
  }
@@ -271,7 +292,7 @@
271
292
 
272
293
  .theia-notebook-cell-output-webview {
273
294
  padding: 5px 0px;
274
- margin: 0px 10px;
295
+ margin: 0px 15px 0px 9px;
275
296
  width: 100%;
276
297
  }
277
298
 
@@ -298,3 +319,153 @@
298
319
  min-height: 100px;
299
320
  background-color: var(--theia-editor-background);
300
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,16 @@ 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);
102
+ }));
71
103
 
104
+ this.toDispose.push(this.props.cell.onWillBlurCellEditor(() => {
105
+ let parent = this.container?.parentElement;
106
+ while (parent && !parent.classList.contains('theia-notebook-cell')) {
107
+ parent = parent.parentElement;
108
+ }
109
+ if (parent) {
110
+ parent.focus();
111
+ }
72
112
  }));
73
113
 
74
114
  this.toDispose.push(this.props.cell.onDidChangeEditorOptions(options => {
@@ -79,6 +119,13 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
79
119
  this.editor?.setLanguage(language);
80
120
  }));
81
121
 
122
+ this.toDispose.push(this.props.cell.onDidFindMatches(matches => {
123
+ this.matches = matches;
124
+ animationFrame().then(() => this.setMatches());
125
+ }));
126
+
127
+ this.toDispose.push(this.props.cell.onDidSelectFindMatch(match => this.centerEditorInView()));
128
+
82
129
  this.toDispose.push(this.props.notebookModel.onDidChangeSelectedCell(e => {
83
130
  if (e.cell !== this.props.cell && this.editor?.getControl().hasTextFocus()) {
84
131
  this.props.notebookContextManager.context?.focus();
@@ -95,6 +142,10 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
95
142
  });
96
143
  this.toDispose.push(disposable);
97
144
  }
145
+
146
+ this.toDispose.push(this.props.cell.onDidRequestCenterEditor(() => {
147
+ this.centerEditorInView();
148
+ }));
98
149
  }
99
150
 
100
151
  override componentWillUnmount(): void {
@@ -106,6 +157,21 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
106
157
  this.toDispose = new DisposableCollection();
107
158
  }
108
159
 
160
+ protected centerEditorInView(): void {
161
+ const editorDomNode = this.editor?.getControl().getDomNode();
162
+ if (editorDomNode) {
163
+ editorDomNode.scrollIntoView({
164
+ behavior: 'instant',
165
+ block: 'center'
166
+ });
167
+ } else {
168
+ this.container?.scrollIntoView({
169
+ behavior: 'instant',
170
+ block: 'center'
171
+ });
172
+ }
173
+ }
174
+
109
175
  protected async initEditor(): Promise<void> {
110
176
  const { cell, notebookModel, monacoServices } = this.props;
111
177
  if (this.container) {
@@ -130,11 +196,11 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
130
196
  notebookModel.cellDirtyChanged(cell, true);
131
197
  }));
132
198
  this.toDispose.push(this.editor.getControl().onDidFocusEditorText(() => {
133
- this.props.notebookContextManager.onDidEditorTextFocus(true);
134
199
  this.props.notebookModel.setSelectedCell(cell, false);
135
200
  }));
136
- this.toDispose.push(this.editor.getControl().onDidBlurEditorText(() => {
137
- this.props.notebookContextManager.onDidEditorTextFocus(false);
201
+ this.toDispose.push(this.editor.getControl().onDidChangeCursorSelection(e => {
202
+ const selectedText = this.editor!.getControl().getModel()!.getValueInRange(e.selection);
203
+ this.props.notebookModel.selectedText = selectedText;
138
204
  }));
139
205
  this.toDispose.push(this.editor.getControl().onDidChangeCursorPosition(e => {
140
206
  if (e.secondaryPositions.length === 0) {
@@ -149,9 +215,32 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
149
215
  if (cell.editing && notebookModel.selectedCell === cell) {
150
216
  this.editor.getControl().focus();
151
217
  }
218
+ this.setMatches();
152
219
  }
153
220
  }
154
221
 
222
+ protected setMatches(): void {
223
+ if (!this.editor) {
224
+ return;
225
+ }
226
+ const decorations: IModelDeltaDecoration[] = [];
227
+ for (const match of this.matches) {
228
+ const decoration = match.selected ? CURRENT_FIND_MATCH_DECORATION : FIND_MATCH_DECORATION;
229
+ decorations.push({
230
+ range: {
231
+ startLineNumber: match.range.start.line,
232
+ startColumn: match.range.start.character,
233
+ endLineNumber: match.range.end.line,
234
+ endColumn: match.range.end.character
235
+ },
236
+ options: decoration
237
+ });
238
+ }
239
+
240
+ this.oldMatchDecorations = this.editor.getControl()
241
+ .changeDecorations(accessor => accessor.deltaDecorations(this.oldMatchDecorations, decorations));
242
+ }
243
+
155
244
  protected setContainer(component: HTMLDivElement | null): void {
156
245
  this.container = component ?? undefined;
157
246
  };
@@ -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,7 +131,15 @@ 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}
106
- ref={ref => cell === this.state.selectedCell && this.state.scrollIntoView && ref?.scrollIntoView({ block: 'nearest' })}>
134
+ tabIndex={-1}
135
+ ref={ref => {
136
+ if (ref && cell === this.state.selectedCell && this.state.scrollIntoView) {
137
+ ref.scrollIntoView({ block: 'nearest' });
138
+ if (cell.cellKind === CellKind.Markup && !cell.editing) {
139
+ ref.focus();
140
+ }
141
+ }
142
+ }}>
107
143
  <div className={'theia-notebook-cell-marker' + (this.state.selectedCell === cell ? ' theia-notebook-cell-marker-selected' : '')}></div>
108
144
  <div className='theia-notebook-cell-content'>
109
145
  {this.renderCellContent(cell, index)}
@@ -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={() => {