@theia/notebook 1.51.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 (75) 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 +23 -7
  11. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  12. package/lib/browser/contributions/notebook-preferences.d.ts +3 -0
  13. package/lib/browser/contributions/notebook-preferences.d.ts.map +1 -1
  14. package/lib/browser/contributions/notebook-preferences.js +9 -1
  15. package/lib/browser/contributions/notebook-preferences.js.map +1 -1
  16. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts +10 -0
  17. package/lib/browser/contributions/notebook-undo-redo-handler.d.ts.map +1 -0
  18. package/lib/browser/contributions/notebook-undo-redo-handler.js +49 -0
  19. package/lib/browser/contributions/notebook-undo-redo-handler.js.map +1 -0
  20. package/lib/browser/notebook-editor-widget.d.ts +6 -0
  21. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  22. package/lib/browser/notebook-editor-widget.js +39 -1
  23. package/lib/browser/notebook-editor-widget.js.map +1 -1
  24. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  25. package/lib/browser/notebook-frontend-module.js +4 -1
  26. package/lib/browser/notebook-frontend-module.js.map +1 -1
  27. package/lib/browser/service/notebook-context-manager.d.ts +2 -1
  28. package/lib/browser/service/notebook-context-manager.d.ts.map +1 -1
  29. package/lib/browser/service/notebook-context-manager.js +6 -3
  30. package/lib/browser/service/notebook-context-manager.js.map +1 -1
  31. package/lib/browser/view/notebook-cell-editor.d.ts +7 -1
  32. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  33. package/lib/browser/view/notebook-cell-editor.js +75 -5
  34. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  35. package/lib/browser/view/notebook-cell-list-view.d.ts +2 -0
  36. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  37. package/lib/browser/view/notebook-cell-list-view.js +22 -1
  38. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  39. package/lib/browser/view/notebook-code-cell-view.d.ts +1 -1
  40. package/lib/browser/view/notebook-code-cell-view.d.ts.map +1 -1
  41. package/lib/browser/view/notebook-code-cell-view.js +19 -17
  42. package/lib/browser/view/notebook-code-cell-view.js.map +1 -1
  43. package/lib/browser/view/notebook-find-widget.d.ts +63 -0
  44. package/lib/browser/view/notebook-find-widget.d.ts.map +1 -0
  45. package/lib/browser/view/notebook-find-widget.js +225 -0
  46. package/lib/browser/view/notebook-find-widget.js.map +1 -0
  47. package/lib/browser/view/notebook-markdown-cell-view.d.ts +4 -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 +105 -8
  50. package/lib/browser/view/notebook-markdown-cell-view.js.map +1 -1
  51. package/lib/browser/view-model/notebook-cell-model.d.ts +24 -1
  52. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  53. package/lib/browser/view-model/notebook-cell-model.js +71 -1
  54. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  55. package/lib/browser/view-model/notebook-model.d.ts +8 -1
  56. package/lib/browser/view-model/notebook-model.d.ts.map +1 -1
  57. package/lib/browser/view-model/notebook-model.js +66 -14
  58. package/lib/browser/view-model/notebook-model.js.map +1 -1
  59. package/package.json +9 -8
  60. package/src/browser/contributions/cell-operations.ts +3 -2
  61. package/src/browser/contributions/notebook-actions-contribution.ts +20 -20
  62. package/src/browser/contributions/notebook-cell-actions-contribution.ts +23 -8
  63. package/src/browser/contributions/notebook-preferences.ts +10 -1
  64. package/src/browser/contributions/notebook-undo-redo-handler.ts +41 -0
  65. package/src/browser/notebook-editor-widget.tsx +49 -2
  66. package/src/browser/notebook-frontend-module.ts +7 -3
  67. package/src/browser/service/notebook-context-manager.ts +8 -4
  68. package/src/browser/style/index.css +172 -5
  69. package/src/browser/view/notebook-cell-editor.tsx +79 -6
  70. package/src/browser/view/notebook-cell-list-view.tsx +29 -3
  71. package/src/browser/view/notebook-code-cell-view.tsx +19 -17
  72. package/src/browser/view/notebook-find-widget.tsx +335 -0
  73. package/src/browser/view/notebook-markdown-cell-view.tsx +134 -17
  74. package/src/browser/view-model/notebook-cell-model.ts +94 -8
  75. 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';
@@ -365,9 +366,23 @@ export class NotebookCellActionContribution implements MenuContribution, Command
365
366
  execute: async (notebook?: NotebookModel, cell?: NotebookCellModel) => {
366
367
  const selectedCell = cell ?? this.notebookEditorWidgetService.focusedEditor?.model?.selectedCell;
367
368
  const activeNotebook = notebook ?? this.notebookEditorWidgetService.focusedEditor?.model;
368
- if (selectedCell && activeNotebook) {
369
- const language = await this.languageQuickPickService.pickEditorLanguage(selectedCell.language);
370
- if (language?.value && language.value !== 'autoDetect') {
369
+ if (!selectedCell || !activeNotebook) {
370
+ return;
371
+ }
372
+ const language = await this.languageQuickPickService.pickEditorLanguage(selectedCell.language);
373
+ if (!language?.value || language.value === 'autoDetect' || language.value.id === selectedCell.language) {
374
+ return;
375
+ }
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
+ }
382
+ } else {
383
+ if (isMarkdownCell) {
384
+ changeCellType(activeNotebook, selectedCell, CellKind.Code, language.value.id);
385
+ } else {
371
386
  this.notebookEditorWidgetService.focusedEditor?.model?.applyEdits([{
372
387
  editType: CellEditType.CellLanguage,
373
388
  index: activeNotebook.cells.indexOf(selectedCell),
@@ -411,12 +426,12 @@ export class NotebookCellActionContribution implements MenuContribution, Command
411
426
  {
412
427
  command: NotebookCellCommands.EDIT_COMMAND.id,
413
428
  keybinding: 'Enter',
414
- when: `!editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
429
+ when: `!editorTextFocus && !inputFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
415
430
  },
416
431
  {
417
432
  command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
418
433
  keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Alt] }).toString(),
419
- when: `editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED}`,
434
+ when: `editorTextFocus && !inputFocus && ${NOTEBOOK_EDITOR_FOCUSED}`,
420
435
  },
421
436
  {
422
437
  command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
@@ -426,12 +441,12 @@ export class NotebookCellActionContribution implements MenuContribution, Command
426
441
  {
427
442
  command: NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.id,
428
443
  keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.CtrlCmd] }).toString(),
429
- 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'`,
430
445
  },
431
446
  {
432
447
  command: NotebookCellCommands.EXECUTE_SINGLE_CELL_AND_FOCUS_NEXT_COMMAND.id,
433
448
  keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Shift] }).toString(),
434
- when: `${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
449
+ when: `${NOTEBOOK_CELL_LIST_FOCUSED} && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
435
450
  },
436
451
  {
437
452
  command: NotebookCellCommands.CLEAR_OUTPUTS_COMMAND.id,
@@ -19,7 +19,8 @@
19
19
  *--------------------------------------------------------------------------------------------*/
20
20
 
21
21
  import { nls } from '@theia/core';
22
- import { PreferenceSchema } from '@theia/core/lib/browser';
22
+ import { interfaces } from '@theia/core/shared/inversify';
23
+ import { PreferenceContribution, PreferenceSchema } from '@theia/core/lib/browser';
23
24
 
24
25
  export namespace NotebookPreferences {
25
26
  export const NOTEBOOK_LINE_NUMBERS = 'notebook.lineNumbers';
@@ -81,3 +82,11 @@ export const notebookPreferenceSchema: PreferenceSchema = {
81
82
 
82
83
  }
83
84
  };
85
+
86
+ export const NotebookPreferenceContribution = Symbol('NotebookPreferenceContribution');
87
+
88
+ export function bindNotebookPreferences(bind: interfaces.Bind): void {
89
+ // We don't need a NotebookPreferenceConfiguration class, so there's no preference proxy to bind
90
+ bind(NotebookPreferenceContribution).toConstantValue({ schema: notebookPreferenceSchema });
91
+ bind(PreferenceContribution).toService(NotebookPreferenceContribution);
92
+ }
@@ -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, PreferenceContribution, 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';
@@ -44,8 +44,9 @@ import { NotebookOutlineContribution } from './contributions/notebook-outline-co
44
44
  import { NotebookLabelProviderContribution } from './contributions/notebook-label-provider-contribution';
45
45
  import { NotebookOutputActionContribution } from './contributions/notebook-output-action-contribution';
46
46
  import { NotebookClipboardService } from './service/notebook-clipboard-service';
47
- import { notebookPreferenceSchema } from './contributions/notebook-preferences';
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();
@@ -106,6 +107,9 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
106
107
  bind(NotebookLabelProviderContribution).toSelf().inSingletonScope();
107
108
  bind(LabelProviderContribution).toService(NotebookLabelProviderContribution);
108
109
 
109
- bind(PreferenceContribution).toConstantValue({ schema: notebookPreferenceSchema });
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 {
@@ -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 {
@@ -61,22 +62,39 @@
61
62
  width: calc(100% - 15px);
62
63
  }
63
64
 
65
+ /* Rendered Markdown Content */
66
+
64
67
  .theia-notebook-markdown-content {
65
68
  padding: 8px 16px 8px 36px;
66
69
  font-size: var(--theia-notebook-markdown-size);
67
70
  }
68
71
 
69
- .theia-notebook-markdown-content > *:first-child {
72
+ .theia-notebook-markdown-content>* {
73
+ font-weight: 400;
74
+ }
75
+
76
+ .theia-notebook-markdown-content>*:first-child {
70
77
  margin-top: 0;
71
78
  padding-top: 0;
72
79
  }
73
80
 
74
- .theia-notebook-markdown-content > *:only-child,
75
- .theia-notebook-markdown-content > *:last-child {
81
+ .theia-notebook-markdown-content>*:last-child {
76
82
  margin-bottom: 0;
77
83
  padding-bottom: 0;
78
84
  }
79
85
 
86
+ /* Markdown cell edit mode */
87
+ .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
88
+ margin-left: 37px;
89
+ margin-right: var(--theia-notebook-cell-editor-margin-right);
90
+ outline: 1px solid var(--theia-notebook-cellBorderColor);
91
+ }
92
+
93
+ /* Markdown cell edit mode focused */
94
+ .theia-notebook-cell.focused .theia-notebook-cell-content:has(.theia-notebook-markdown-editor-container>.theia-notebook-cell-editor) {
95
+ outline-color: var(--theia-notebook-focusedEditorBorder);
96
+ }
97
+
80
98
  .theia-notebook-empty-markdown {
81
99
  opacity: 0.6;
82
100
  }
@@ -189,8 +207,7 @@
189
207
 
190
208
  .theia-notebook-main-container .theia-notebook-main-loading-indicator {
191
209
  /* `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);
210
+ animation: progress-animation 1.8s 0s infinite cubic-bezier(0.645, 0.045, 0.355, 1);
194
211
  background-color: var(--theia-progressBar-background);
195
212
  height: 2px;
196
213
  }
@@ -298,3 +315,153 @@
298
315
  min-height: 100px;
299
316
  background-color: var(--theia-editor-background);
300
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 {