@theia/notebook 1.52.0 → 1.53.0-next.4

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 (70) 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 +3 -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 +17 -20
  8. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  9. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  10. package/lib/browser/contributions/notebook-cell-actions-contribution.js +21 -13
  11. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  12. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts +10 -0
  13. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts.map +1 -0
  14. package/lib/browser/contributions/notebook-undo-redo-handler.js +49 -0
  15. package/lib/browser/contributions/notebook-undo-redo-handler.js.map +1 -0
  16. package/lib/browser/notebook-editor-widget.d.ts +6 -0
  17. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  18. package/lib/browser/notebook-editor-widget.js +39 -1
  19. package/lib/browser/notebook-editor-widget.js.map +1 -1
  20. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  21. package/lib/browser/notebook-frontend-module.js +3 -0
  22. package/lib/browser/notebook-frontend-module.js.map +1 -1
  23. package/lib/browser/service/notebook-context-manager.d.ts +2 -1
  24. package/lib/browser/service/notebook-context-manager.d.ts.map +1 -1
  25. package/lib/browser/service/notebook-context-manager.js +6 -3
  26. package/lib/browser/service/notebook-context-manager.js.map +1 -1
  27. package/lib/browser/view/notebook-cell-editor.d.ts +7 -1
  28. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  29. package/lib/browser/view/notebook-cell-editor.js +75 -5
  30. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  31. package/lib/browser/view/notebook-cell-list-view.d.ts +2 -0
  32. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  33. package/lib/browser/view/notebook-cell-list-view.js +22 -1
  34. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  35. package/lib/browser/view/notebook-code-cell-view.d.ts +1 -1
  36. package/lib/browser/view/notebook-code-cell-view.d.ts.map +1 -1
  37. package/lib/browser/view/notebook-code-cell-view.js +19 -17
  38. package/lib/browser/view/notebook-code-cell-view.js.map +1 -1
  39. package/lib/browser/view/notebook-find-widget.d.ts +63 -0
  40. package/lib/browser/view/notebook-find-widget.d.ts.map +1 -0
  41. package/lib/browser/view/notebook-find-widget.js +225 -0
  42. package/lib/browser/view/notebook-find-widget.js.map +1 -0
  43. package/lib/browser/view/notebook-markdown-cell-view.d.ts +4 -0
  44. package/lib/browser/view/notebook-markdown-cell-view.d.ts.map +1 -1
  45. package/lib/browser/view/notebook-markdown-cell-view.js +105 -8
  46. package/lib/browser/view/notebook-markdown-cell-view.js.map +1 -1
  47. package/lib/browser/view-model/notebook-cell-model.d.ts +24 -1
  48. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  49. package/lib/browser/view-model/notebook-cell-model.js +71 -1
  50. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  51. package/lib/browser/view-model/notebook-model.d.ts +8 -1
  52. package/lib/browser/view-model/notebook-model.d.ts.map +1 -1
  53. package/lib/browser/view-model/notebook-model.js +66 -14
  54. package/lib/browser/view-model/notebook-model.js.map +1 -1
  55. package/package.json +8 -7
  56. package/src/browser/contributions/cell-operations.ts +3 -2
  57. package/src/browser/contributions/notebook-actions-contribution.ts +20 -20
  58. package/src/browser/contributions/notebook-cell-actions-contribution.ts +22 -14
  59. package/src/browser/contributions/notebook-undo-redo-handler.ts +41 -0
  60. package/src/browser/notebook-editor-widget.tsx +49 -2
  61. package/src/browser/notebook-frontend-module.ts +5 -1
  62. package/src/browser/service/notebook-context-manager.ts +8 -4
  63. package/src/browser/style/index.css +158 -3
  64. package/src/browser/view/notebook-cell-editor.tsx +79 -6
  65. package/src/browser/view/notebook-cell-list-view.tsx +29 -3
  66. package/src/browser/view/notebook-code-cell-view.tsx +19 -17
  67. package/src/browser/view/notebook-find-widget.tsx +335 -0
  68. package/src/browser/view/notebook-markdown-cell-view.tsx +134 -17
  69. package/src/browser/view-model/notebook-cell-model.ts +94 -8
  70. package/src/browser/view-model/notebook-model.ts +77 -20
@@ -22,7 +22,8 @@ import { NotebookCellModel } from '../view-model/notebook-cell-model';
22
22
  import {
23
23
  NOTEBOOK_CELL_MARKDOWN_EDIT_MODE, NOTEBOOK_CELL_TYPE,
24
24
  NotebookContextKeys, NOTEBOOK_CELL_EXECUTING, NOTEBOOK_EDITOR_FOCUSED,
25
- NOTEBOOK_CELL_FOCUSED
25
+ NOTEBOOK_CELL_FOCUSED,
26
+ NOTEBOOK_CELL_LIST_FOCUSED
26
27
  } from './notebook-context-keys';
27
28
  import { ContextKeyService } from '@theia/core/lib/browser/context-key-service';
28
29
  import { NotebookExecutionService } from '../service/notebook-execution-service';
@@ -369,18 +370,25 @@ export class NotebookCellActionContribution implements MenuContribution, Command
369
370
  return;
370
371
  }
371
372
  const language = await this.languageQuickPickService.pickEditorLanguage(selectedCell.language);
372
- if (!language?.value || language.value === 'autoDetect') {
373
+ if (!language?.value || language.value === 'autoDetect' || language.value.id === selectedCell.language) {
373
374
  return;
374
375
  }
375
- if (language.value.id === 'markdown') {
376
- selectedCell.language = 'markdown';
377
- changeCellType(activeNotebook, selectedCell, CellKind.Markup);
376
+ const isMarkdownCell = selectedCell.cellKind === CellKind.Markup;
377
+ const isMarkdownLanguage = language.value.id === 'markdown';
378
+ if (isMarkdownLanguage) {
379
+ if (!isMarkdownCell) {
380
+ changeCellType(activeNotebook, selectedCell, CellKind.Markup, language.value.id);
381
+ }
378
382
  } else {
379
- this.notebookEditorWidgetService.focusedEditor?.model?.applyEdits([{
380
- editType: CellEditType.CellLanguage,
381
- index: activeNotebook.cells.indexOf(selectedCell),
382
- language: language.value.id
383
- }], true);
383
+ if (isMarkdownCell) {
384
+ changeCellType(activeNotebook, selectedCell, CellKind.Code, language.value.id);
385
+ } else {
386
+ this.notebookEditorWidgetService.focusedEditor?.model?.applyEdits([{
387
+ editType: CellEditType.CellLanguage,
388
+ index: activeNotebook.cells.indexOf(selectedCell),
389
+ language: language.value.id
390
+ }], true);
391
+ }
384
392
  }
385
393
  }
386
394
  });
@@ -418,12 +426,12 @@ export class NotebookCellActionContribution implements MenuContribution, Command
418
426
  {
419
427
  command: NotebookCellCommands.EDIT_COMMAND.id,
420
428
  keybinding: 'Enter',
421
- when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
429
+ when: `!editorTextFocus && !inputFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
422
430
  },
423
431
  {
424
432
  command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
425
433
  keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Alt] }).toString(),
426
- when: `editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED}`,
434
+ when: `editorTextFocus && !inputFocus && ${NOTEBOOK_EDITOR_FOCUSED}`,
427
435
  },
428
436
  {
429
437
  command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
@@ -433,12 +441,12 @@ export class NotebookCellActionContribution implements MenuContribution, Command
433
441
  {
434
442
  command: NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.id,
435
443
  keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.CtrlCmd] }).toString(),
436
- when: `${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED} && ${NOTEBOOK_CELL_TYPE} == 'code'`,
444
+ when: `${NOTEBOOK_CELL_LIST_FOCUSED} && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED} && ${NOTEBOOK_CELL_TYPE} == 'code'`,
437
445
  },
438
446
  {
439
447
  command: NotebookCellCommands.EXECUTE_SINGLE_CELL_AND_FOCUS_NEXT_COMMAND.id,
440
448
  keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Shift] }).toString(),
441
- when: `${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
449
+ when: `${NOTEBOOK_CELL_LIST_FOCUSED} && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
442
450
  },
443
451
  {
444
452
  command: NotebookCellCommands.CLEAR_OUTPUTS_COMMAND.id,
@@ -0,0 +1,41 @@
1
+ // *****************************************************************************
2
+ // Copyright (C) 2024 TypeFox and others.
3
+ //
4
+ // This program and the accompanying materials are made available under the
5
+ // terms of the Eclipse Public License v. 2.0 which is available at
6
+ // http://www.eclipse.org/legal/epl-2.0.
7
+ //
8
+ // This Source Code may also be made available under the following Secondary
9
+ // Licenses when the conditions for such availability set forth in the Eclipse
10
+ // Public License v. 2.0 are satisfied: GNU General Public License, version 2
11
+ // with the GNU Classpath Exception which is available at
12
+ // https://www.gnu.org/software/classpath/license.html.
13
+ //
14
+ // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
15
+ // *****************************************************************************
16
+
17
+ import { inject, injectable } from '@theia/core/shared/inversify';
18
+ import { ApplicationShell, UndoRedoHandler } from '@theia/core/lib/browser';
19
+ import { NotebookEditorWidget } from '../notebook-editor-widget';
20
+
21
+ @injectable()
22
+ export class NotebookUndoRedoHandler implements UndoRedoHandler<NotebookEditorWidget> {
23
+
24
+ @inject(ApplicationShell)
25
+ protected readonly applicationShell: ApplicationShell;
26
+
27
+ priority = 200;
28
+ select(): NotebookEditorWidget | undefined {
29
+ const current = this.applicationShell.currentWidget;
30
+ if (current instanceof NotebookEditorWidget) {
31
+ return current;
32
+ }
33
+ return undefined;
34
+ }
35
+ undo(item: NotebookEditorWidget): void {
36
+ item.undo();
37
+ }
38
+ redo(item: NotebookEditorWidget): void {
39
+ item.redo();
40
+ }
41
+ }
@@ -33,6 +33,8 @@ import { MarkdownString } from '@theia/core/lib/common/markdown-rendering';
33
33
  import { NotebookContextManager } from './service/notebook-context-manager';
34
34
  import { NotebookViewportService } from './view/notebook-viewport-service';
35
35
  import { NotebookCellCommands } from './contributions/notebook-cell-actions-contribution';
36
+ import { NotebookFindWidget } from './view/notebook-find-widget';
37
+ import debounce = require('lodash/debounce');
36
38
  const PerfectScrollbar = require('react-perfect-scrollbar');
37
39
 
38
40
  export const NotebookEditorWidgetContainerFactory = Symbol('NotebookEditorWidgetContainerFactory');
@@ -126,7 +128,16 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
126
128
  protected readonly renderers = new Map<CellKind, CellRenderer>();
127
129
  protected _model?: NotebookModel;
128
130
  protected _ready: Deferred<NotebookModel> = new Deferred();
131
+ protected _findWidgetVisible = false;
132
+ protected _findWidgetRef = React.createRef<NotebookFindWidget>();
129
133
  protected scrollBarRef = React.createRef<{ updateScroll(): void }>();
134
+ protected debounceFind = debounce(() => {
135
+ this._findWidgetRef.current?.search({});
136
+ }, 30, {
137
+ trailing: true,
138
+ maxWait: 100,
139
+ leading: false
140
+ });
130
141
 
131
142
  get notebookType(): string {
132
143
  return this.props.notebookType;
@@ -177,6 +188,11 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
177
188
  // Wait one frame to ensure that the content has been rendered
178
189
  animationFrame().then(() => this.scrollBarRef.current?.updateScroll());
179
190
  }));
191
+ this.toDispose.push(this._model.onContentChanged(() => {
192
+ if (this._findWidgetVisible) {
193
+ this.debounceFind();
194
+ }
195
+ }));
180
196
  this.toDispose.push(this._model.onDidChangeReadOnly(readOnly => {
181
197
  if (readOnly) {
182
198
  lock(this.title);
@@ -220,18 +236,41 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
220
236
  protected render(): ReactNode {
221
237
  if (this._model) {
222
238
  return <div className='theia-notebook-main-container'>
239
+ <div className='theia-notebook-overlay'>
240
+ <NotebookFindWidget
241
+ ref={this._findWidgetRef}
242
+ hidden={!this._findWidgetVisible}
243
+ onClose={() => {
244
+ this._findWidgetVisible = false;
245
+ this._model?.findMatches({
246
+ activeFilters: [],
247
+ matchCase: false,
248
+ regex: false,
249
+ search: '',
250
+ wholeWord: false
251
+ });
252
+ this.update();
253
+ }}
254
+ onSearch={options => this._model?.findMatches(options) ?? []}
255
+ onReplace={(matches, replaceText) => this._model?.replaceAll(matches, replaceText)}
256
+ />
257
+ </div>
223
258
  {this.notebookMainToolbarRenderer.render(this._model, this.node)}
224
- <div className='theia-notebook-viewport' ref={(ref: HTMLDivElement) => this.viewportService.viewportElement = ref}>
259
+ <div
260
+ className='theia-notebook-viewport'
261
+ ref={(ref: HTMLDivElement) => this.viewportService.viewportElement = ref}
262
+ >
225
263
  <PerfectScrollbar className='theia-notebook-scroll-container'
226
264
  ref={this.scrollBarRef}
227
265
  onScrollY={(e: HTMLDivElement) => this.viewportService.onScroll(e)}>
228
266
  <NotebookCellListView renderers={this.renderers}
229
267
  notebookModel={this._model}
268
+ notebookContext={this.notebookContextManager}
230
269
  toolbarRenderer={this.cellToolbarFactory}
231
270
  commandRegistry={this.commandRegistry} />
232
271
  </PerfectScrollbar>
233
272
  </div>
234
- </div >;
273
+ </div>;
235
274
  } else {
236
275
  return <div className='theia-notebook-main-container'>
237
276
  <div className='theia-notebook-main-loading-indicator'></div>
@@ -260,6 +299,14 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
260
299
  this.onDidChangeOutputInputFocusEmitter.fire(focused);
261
300
  }
262
301
 
302
+ showFindWidget(): void {
303
+ if (!this._findWidgetVisible) {
304
+ this._findWidgetVisible = true;
305
+ this.update();
306
+ }
307
+ this._findWidgetRef.current?.focusSearch(this._model?.selectedText);
308
+ }
309
+
263
310
  override dispose(): void {
264
311
  this.notebookContextManager.dispose();
265
312
  this.onDidChangeModelEmitter.dispose();
@@ -16,7 +16,7 @@
16
16
  import '../../src/browser/style/index.css';
17
17
 
18
18
  import { ContainerModule } from '@theia/core/shared/inversify';
19
- import { FrontendApplicationContribution, KeybindingContribution, LabelProviderContribution, OpenHandler, WidgetFactory } from '@theia/core/lib/browser';
19
+ import { FrontendApplicationContribution, KeybindingContribution, LabelProviderContribution, OpenHandler, UndoRedoHandler, WidgetFactory } from '@theia/core/lib/browser';
20
20
  import { ColorContribution } from '@theia/core/lib/browser/color-application-contribution';
21
21
  import { NotebookOpenHandler } from './notebook-open-handler';
22
22
  import { CommandContribution, MenuContribution, ResourceResolver, } from '@theia/core';
@@ -46,6 +46,7 @@ import { NotebookOutputActionContribution } from './contributions/notebook-outpu
46
46
  import { NotebookClipboardService } from './service/notebook-clipboard-service';
47
47
  import { bindNotebookPreferences } from './contributions/notebook-preferences';
48
48
  import { NotebookOptionsService } from './service/notebook-options';
49
+ import { NotebookUndoRedoHandler } from './contributions/notebook-undo-redo-handler';
49
50
 
50
51
  export default new ContainerModule((bind, unbind, isBound, rebind) => {
51
52
  bind(NotebookColorContribution).toSelf().inSingletonScope();
@@ -108,4 +109,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
108
109
 
109
110
  bindNotebookPreferences(bind);
110
111
  bind(NotebookOptionsService).toSelf().inSingletonScope();
112
+
113
+ bind(NotebookUndoRedoHandler).toSelf().inSingletonScope();
114
+ bind(UndoRedoHandler).toService(NotebookUndoRedoHandler);
111
115
  });
@@ -21,7 +21,7 @@ import { NotebookKernelService } from './notebook-kernel-service';
21
21
  import {
22
22
  NOTEBOOK_CELL_EDITABLE,
23
23
  NOTEBOOK_CELL_EXECUTING, NOTEBOOK_CELL_EXECUTION_STATE,
24
- NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE,
24
+ NOTEBOOK_CELL_FOCUSED, NOTEBOOK_CELL_LIST_FOCUSED, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE,
25
25
  NOTEBOOK_CELL_TYPE, NOTEBOOK_HAS_OUTPUTS, NOTEBOOK_KERNEL, NOTEBOOK_KERNEL_SELECTED,
26
26
  NOTEBOOK_OUTPUT_INPUT_FOCUSED,
27
27
  NOTEBOOK_VIEW_TYPE
@@ -85,7 +85,7 @@ export class NotebookContextManager {
85
85
 
86
86
  this.scopedStore.setContext(NOTEBOOK_HAS_OUTPUTS, !!widget.model?.cells.find(cell => cell.outputs.length > 0));
87
87
 
88
- // Cell Selection realted keys
88
+ // Cell Selection related keys
89
89
  this.scopedStore.setContext(NOTEBOOK_CELL_FOCUSED, !!widget.model?.selectedCell);
90
90
  widget.model?.onDidChangeSelectedCell(e => {
91
91
  this.selectedCellChanged(e.cell);
@@ -144,8 +144,12 @@ export class NotebookContextManager {
144
144
  return this.contextKeyService.createOverlay(Object.entries(this.cellContexts.get(cellHandle) ?? {}));
145
145
  }
146
146
 
147
- onDidEditorTextFocus(focus: boolean): void {
148
- this.scopedStore.setContext('inputFocus', focus);
147
+ changeCellFocus(focus: boolean): void {
148
+ this.scopedStore.setContext(NOTEBOOK_CELL_FOCUSED, focus);
149
+ }
150
+
151
+ changeCellListFocus(focus: boolean): void {
152
+ this.scopedStore.setContext(NOTEBOOK_CELL_LIST_FOCUSED, focus);
149
153
  }
150
154
 
151
155
  createContextKeyChangedEvent(affectedKeys: string[]): ContextKeyChangeEvent {
@@ -62,31 +62,36 @@
62
62
  width: calc(100% - 15px);
63
63
  }
64
64
 
65
+ /* Rendered Markdown Content */
66
+
65
67
  .theia-notebook-markdown-content {
66
68
  padding: 8px 16px 8px 36px;
67
69
  font-size: var(--theia-notebook-markdown-size);
68
70
  }
69
71
 
72
+ .theia-notebook-markdown-content>* {
73
+ font-weight: 400;
74
+ }
75
+
70
76
  .theia-notebook-markdown-content>*:first-child {
71
77
  margin-top: 0;
72
78
  padding-top: 0;
73
79
  }
74
80
 
75
- .theia-notebook-markdown-content>*:only-child,
76
81
  .theia-notebook-markdown-content>*:last-child {
77
82
  margin-bottom: 0;
78
83
  padding-bottom: 0;
79
84
  }
80
85
 
81
86
  /* Markdown cell edit mode */
82
- .theia-notebook-cell-content:has(> .theia-notebook-cell-editor) {
87
+ .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
83
88
  margin-left: 37px;
84
89
  margin-right: var(--theia-notebook-cell-editor-margin-right);
85
90
  outline: 1px solid var(--theia-notebook-cellBorderColor);
86
91
  }
87
92
 
88
93
  /* Markdown cell edit mode focused */
89
- .theia-notebook-cell.focused .theia-notebook-cell-content:has(> .theia-notebook-cell-editor) {
94
+ .theia-notebook-cell.focused .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
90
95
  outline-color: var(--theia-notebook-focusedEditorBorder);
91
96
  }
92
97
 
@@ -310,3 +315,153 @@
310
315
  min-height: 100px;
311
316
  background-color: var(--theia-editor-background);
312
317
  }
318
+
319
+ /* Notebook Find Widget */
320
+
321
+ .theia-notebook-overlay {
322
+ position: absolute;
323
+ z-index: 100;
324
+ right: 18px;
325
+ }
326
+
327
+ .theia-notebook-find-widget {
328
+ /* position: absolute;
329
+ z-index: 35;
330
+ height: 33px;
331
+ overflow: hidden; */
332
+ line-height: 19px;
333
+ transition: transform 200ms linear;
334
+ display: flex;
335
+ flex-direction: row;
336
+ padding: 0 4px;
337
+ box-sizing: border-box;
338
+ box-shadow: 0 0 8px 2px var(--theia-widget-shadow);
339
+ background-color: var(--theia-editorWidget-background);
340
+ color: var(--theia-editorWidget-foreground);
341
+ border-left: 1px solid var(--theia-widget-border);
342
+ border-right: 1px solid var(--theia-widget-border);
343
+ border-bottom: 1px solid var(--theia-widget-border);
344
+ border-bottom-left-radius: 4px;
345
+ border-bottom-right-radius: 4px;
346
+ }
347
+
348
+ .theia-notebook-find-widget.hidden {
349
+ display: none;
350
+ transform: translateY(calc(-100% - 10px));
351
+ }
352
+
353
+ .theia-notebook-find-widget.search-mode > * > *:nth-child(2) {
354
+ display: none;
355
+ }
356
+
357
+ .theia-notebook-find-widget-expand {
358
+ display: flex;
359
+ flex-direction: row;
360
+ align-items: center;
361
+ cursor: pointer;
362
+ border-radius: 0;
363
+ margin-right: 4px;
364
+ }
365
+
366
+ .theia-notebook-find-widget-expand:focus {
367
+ outline: 1px solid var(--theia-focusBorder);
368
+ }
369
+
370
+ .theia-notebook-find-widget-expand:hover {
371
+ background-color: var(--theia-toolbar-hoverBackground);
372
+ }
373
+
374
+ .theia-notebook-find-widget-buttons-first {
375
+ margin-bottom: 4px;
376
+ height: 26px;
377
+ display: flex;
378
+ flex-direction: row;
379
+ align-items: center;
380
+ }
381
+
382
+ .theia-notebook-find-widget-buttons-first > div,
383
+ .theia-notebook-find-widget-buttons-second > div {
384
+ margin-right: 4px;
385
+ }
386
+
387
+ .theia-notebook-find-widget-buttons-second {
388
+ height: 26px;
389
+ display: flex;
390
+ flex-direction: row;
391
+ align-items: center;
392
+ }
393
+
394
+ .theia-notebook-find-widget-inputs {
395
+ margin-top: 4px;
396
+ display: flex;
397
+ flex-direction: column;
398
+ }
399
+
400
+ .theia-notebook-find-widget-buttons {
401
+ margin-top: 4px;
402
+ margin-left: 4px;
403
+ display: flex;
404
+ flex-direction: column;
405
+ }
406
+
407
+ .theia-notebook-find-widget-matches-count {
408
+ width: 72px;
409
+ box-sizing: border-box;
410
+ overflow: hidden;
411
+ text-align: center;
412
+ text-overflow: ellipsis;
413
+ white-space: nowrap;
414
+ }
415
+
416
+ .theia-notebook-find-widget-input-wrapper {
417
+ display: flex;
418
+ align-items: center;
419
+ background: var(--theia-input-background);
420
+ border-style: solid;
421
+ border-width: var(--theia-border-width);
422
+ border-color: var(--theia-input-background);
423
+ border-radius: 2px;
424
+ margin-bottom: 4px;
425
+ }
426
+
427
+ .theia-notebook-find-widget-input-wrapper:focus-within {
428
+ border-color: var(--theia-focusBorder);
429
+ }
430
+
431
+ .theia-notebook-find-widget-input-wrapper .option.enabled {
432
+ color: var(--theia-inputOption-activeForeground);
433
+ outline: 1px solid var(--theia-inputOption-activeBorder);
434
+ background-color: var(--theia-inputOption-activeBackground);
435
+ }
436
+
437
+ .theia-notebook-find-widget-input-wrapper .option {
438
+ margin: 2px;
439
+ }
440
+
441
+ .theia-notebook-find-widget-input-wrapper .theia-notebook-find-widget-input:focus {
442
+ border: none;
443
+ outline: none;
444
+ }
445
+
446
+ .theia-notebook-find-widget-input-wrapper .theia-notebook-find-widget-input {
447
+ background: none;
448
+ border: none;
449
+ }
450
+
451
+ .theia-notebook-find-widget-replace {
452
+ margin-bottom: 4px;
453
+ }
454
+
455
+ .theia-notebook-find-widget-buttons .disabled {
456
+ opacity: 0.5;
457
+ }
458
+
459
+ mark.theia-find-match {
460
+ color: var(--theia-editor-findMatchHighlightForeground);
461
+ background-color: var(--theia-editor-findMatchHighlightBackground);
462
+ }
463
+
464
+ mark.theia-find-match.theia-find-match-selected {
465
+ color: var(--theia-editor-findMatchForeground);
466
+ background-color: var(--theia-editor-findMatchBackground);
467
+ }
@@ -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
  }
@@ -66,6 +68,13 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
66
68
  }
67
69
  }));
68
70
 
71
+ this.toDispose.push(props.notebookModel.onDidChangeContent(events => {
72
+ if (events.some(e => e.kind === NotebookCellsChangeType.Move)) {
73
+ // When a cell has been moved, we need to rerender the whole component
74
+ this.forceUpdate();
75
+ }
76
+ }));
77
+
69
78
  this.toDispose.push(props.notebookModel.onDidChangeSelectedCell(e => {
70
79
  this.setState({
71
80
  ...this.state,
@@ -80,7 +89,24 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
80
89
  }
81
90
 
82
91
  override render(): React.ReactNode {
83
- return <ul className='theia-notebook-cell-list'>
92
+ return <ul className='theia-notebook-cell-list' ref={
93
+ ref => {
94
+ this.toDispose.push(onDomEvent(document, 'focusin', () => {
95
+ animationFrame().then(() => {
96
+ let hasCellFocus = false;
97
+ let hasFocus = false;
98
+ if (ref?.contains(document.activeElement)) {
99
+ if (this.props.notebookModel.selectedCell) {
100
+ hasCellFocus = true;
101
+ }
102
+ hasFocus = true;
103
+ }
104
+ this.props.notebookContext.changeCellFocus(hasCellFocus);
105
+ this.props.notebookContext.changeCellListFocus(hasFocus);
106
+ });
107
+ }));
108
+ }
109
+ }>
84
110
  {this.props.notebookModel.cells
85
111
  .map((cell, index) =>
86
112
  <React.Fragment key={'cell-' + cell.handle}>