@theia/notebook 1.53.0-next.5 → 1.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/lib/browser/contributions/notebook-actions-contribution.d.ts +1 -0
  2. package/lib/browser/contributions/notebook-actions-contribution.d.ts.map +1 -1
  3. package/lib/browser/contributions/notebook-actions-contribution.js +26 -3
  4. package/lib/browser/contributions/notebook-actions-contribution.js.map +1 -1
  5. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts +2 -0
  6. package/lib/browser/contributions/notebook-cell-actions-contribution.d.ts.map +1 -1
  7. package/lib/browser/contributions/notebook-cell-actions-contribution.js +36 -2
  8. package/lib/browser/contributions/notebook-cell-actions-contribution.js.map +1 -1
  9. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts +14 -0
  10. package/lib/browser/contributions/notebook-status-bar-contribution.d.ts.map +1 -0
  11. package/lib/browser/contributions/notebook-status-bar-contribution.js +75 -0
  12. package/lib/browser/contributions/notebook-status-bar-contribution.js.map +1 -0
  13. package/lib/browser/notebook-editor-widget.d.ts.map +1 -1
  14. package/lib/browser/notebook-editor-widget.js +2 -4
  15. package/lib/browser/notebook-editor-widget.js.map +1 -1
  16. package/lib/browser/notebook-frontend-module.d.ts.map +1 -1
  17. package/lib/browser/notebook-frontend-module.js +3 -0
  18. package/lib/browser/notebook-frontend-module.js.map +1 -1
  19. package/lib/browser/view/notebook-cell-editor.d.ts +1 -0
  20. package/lib/browser/view/notebook-cell-editor.d.ts.map +1 -1
  21. package/lib/browser/view/notebook-cell-editor.js +30 -16
  22. package/lib/browser/view/notebook-cell-editor.js.map +1 -1
  23. package/lib/browser/view/notebook-cell-list-view.d.ts +6 -4
  24. package/lib/browser/view/notebook-cell-list-view.d.ts.map +1 -1
  25. package/lib/browser/view/notebook-cell-list-view.js +20 -13
  26. package/lib/browser/view/notebook-cell-list-view.js.map +1 -1
  27. package/lib/browser/view-model/notebook-cell-model.d.ts +3 -0
  28. package/lib/browser/view-model/notebook-cell-model.d.ts.map +1 -1
  29. package/lib/browser/view-model/notebook-cell-model.js +5 -0
  30. package/lib/browser/view-model/notebook-cell-model.js.map +1 -1
  31. package/package.json +8 -8
  32. package/src/browser/contributions/notebook-actions-contribution.ts +28 -2
  33. package/src/browser/contributions/notebook-cell-actions-contribution.ts +40 -2
  34. package/src/browser/contributions/notebook-status-bar-contribution.ts +77 -0
  35. package/src/browser/notebook-editor-widget.tsx +3 -4
  36. package/src/browser/notebook-frontend-module.ts +4 -0
  37. package/src/browser/style/index.css +11 -3
  38. package/src/browser/view/notebook-cell-editor.tsx +30 -14
  39. package/src/browser/view/notebook-cell-list-view.tsx +37 -20
  40. package/src/browser/view-model/notebook-cell-model.ts +7 -0
@@ -39,11 +39,13 @@ export namespace NotebookCellCommands {
39
39
  /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */
40
40
  export const EDIT_COMMAND = Command.toDefaultLocalizedCommand({
41
41
  id: 'notebook.cell.edit',
42
+ category: 'Notebook',
42
43
  iconClass: codicon('edit')
43
44
  });
44
45
  /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */
45
46
  export const STOP_EDIT_COMMAND = Command.toDefaultLocalizedCommand({
46
47
  id: 'notebook.cell.stop-edit',
48
+ category: 'Notebook',
47
49
  iconClass: codicon('check')
48
50
  });
49
51
  /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */
@@ -59,11 +61,21 @@ export namespace NotebookCellCommands {
59
61
  /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */
60
62
  export const EXECUTE_SINGLE_CELL_COMMAND = Command.toDefaultLocalizedCommand({
61
63
  id: 'notebook.cell.execute-cell',
64
+ category: 'Notebook',
65
+ label: nls.localizeByDefault('Execute Cell'),
62
66
  iconClass: codicon('play'),
63
67
  });
64
68
  /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */
65
69
  export const EXECUTE_SINGLE_CELL_AND_FOCUS_NEXT_COMMAND = Command.toDefaultLocalizedCommand({
66
70
  id: 'notebook.cell.execute-cell-and-focus-next',
71
+ label: nls.localizeByDefault('Execute Notebook Cell and Select Below'),
72
+ category: 'Notebook',
73
+ });
74
+ /** Parameters: notebookModel: NotebookModel, cell: NotebookCellModel */
75
+ export const EXECUTE_SINGLE_CELL_AND_INSERT_BELOW_COMMAND = Command.toDefaultLocalizedCommand({
76
+ id: 'notebook.cell.execute-cell-and-insert-below',
77
+ label: nls.localizeByDefault('Execute Notebook Cell and Insert Below'),
78
+ category: 'Notebook',
67
79
  });
68
80
 
69
81
  export const EXECUTE_ABOVE_CELLS_COMMAND = Command.toDefaultLocalizedCommand({
@@ -85,11 +97,13 @@ export namespace NotebookCellCommands {
85
97
  /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel */
86
98
  export const CLEAR_OUTPUTS_COMMAND = Command.toDefaultLocalizedCommand({
87
99
  id: 'notebook.cell.clear-outputs',
100
+ category: 'Notebook',
88
101
  label: 'Clear Cell Outputs',
89
102
  });
90
103
  /** Parameters: notebookModel: NotebookModel | undefined, cell: NotebookCellModel | undefined, output: NotebookCellOutputModel */
91
104
  export const CHANGE_OUTPUT_PRESENTATION_COMMAND = Command.toDefaultLocalizedCommand({
92
105
  id: 'notebook.cell.change-presentation',
106
+ category: 'Notebook',
93
107
  label: 'Change Presentation',
94
108
  });
95
109
 
@@ -114,11 +128,13 @@ export namespace NotebookCellCommands {
114
128
 
115
129
  export const TO_CODE_CELL_COMMAND = Command.toLocalizedCommand({
116
130
  id: 'notebook.cell.changeToCode',
131
+ category: 'Notebook',
117
132
  label: 'Change Cell to Code'
118
133
  });
119
134
 
120
135
  export const TO_MARKDOWN_CELL_COMMAND = Command.toLocalizedCommand({
121
136
  id: 'notebook.cell.changeToMarkdown',
137
+ category: 'Notebook',
122
138
  label: 'Change Cell to Markdown'
123
139
  });
124
140
 
@@ -302,6 +318,23 @@ export class NotebookCellActionContribution implements MenuContribution, Command
302
318
  }
303
319
  })
304
320
  );
321
+ commands.registerCommand(NotebookCellCommands.EXECUTE_SINGLE_CELL_AND_INSERT_BELOW_COMMAND, this.editableCellCommandHandler(
322
+ async (notebookModel, cell) => {
323
+ if (cell.cellKind === CellKind.Code) {
324
+ await commands.executeCommand(NotebookCellCommands.EXECUTE_SINGLE_CELL_COMMAND.id, notebookModel, cell);
325
+ }
326
+ await commands.executeCommand(NotebookCellCommands.STOP_EDIT_COMMAND.id, notebookModel, cell);
327
+
328
+ if (cell.cellKind === CellKind.Code) {
329
+ await commands.executeCommand(NotebookCellCommands.INSERT_NEW_CELL_BELOW_COMMAND.id);
330
+ } else {
331
+ await commands.executeCommand(NotebookCellCommands.INSERT_MARKDOWN_CELL_BELOW_COMMAND.id);
332
+ }
333
+
334
+ const index = notebookModel.cells.indexOf(cell);
335
+ notebookModel.setSelectedCell(notebookModel.cells[index + 1]);
336
+ })
337
+ );
305
338
 
306
339
  commands.registerCommand(NotebookCellCommands.EXECUTE_ABOVE_CELLS_COMMAND, this.editableCellCommandHandler(
307
340
  (notebookModel, cell) => {
@@ -432,8 +465,8 @@ export class NotebookCellActionContribution implements MenuContribution, Command
432
465
  },
433
466
  {
434
467
  command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
435
- keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Alt] }).toString(),
436
- when: `editorTextFocus && !inputFocus && ${NOTEBOOK_EDITOR_FOCUSED}`,
468
+ keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Alt, KeyModifier.CtrlCmd] }).toString(),
469
+ when: `editorTextFocus && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_TYPE} == 'markdown'`,
437
470
  },
438
471
  {
439
472
  command: NotebookCellCommands.STOP_EDIT_COMMAND.id,
@@ -450,6 +483,11 @@ export class NotebookCellActionContribution implements MenuContribution, Command
450
483
  keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Shift] }).toString(),
451
484
  when: `${NOTEBOOK_CELL_LIST_FOCUSED} && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
452
485
  },
486
+ {
487
+ command: NotebookCellCommands.EXECUTE_SINGLE_CELL_AND_INSERT_BELOW_COMMAND.id,
488
+ keybinding: KeyCode.createKeyCode({ first: Key.ENTER, modifiers: [KeyModifier.Alt] }).toString(),
489
+ when: `${NOTEBOOK_CELL_LIST_FOCUSED} && ${NOTEBOOK_EDITOR_FOCUSED} && ${NOTEBOOK_CELL_FOCUSED}`,
490
+ },
453
491
  {
454
492
  command: NotebookCellCommands.CLEAR_OUTPUTS_COMMAND.id,
455
493
  keybinding: KeyCode.createKeyCode({ first: Key.KEY_O, modifiers: [KeyModifier.Alt] }).toString(),
@@ -0,0 +1,77 @@
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 { FrontendApplicationContribution, StatusBar, StatusBarAlignment } from '@theia/core/lib/browser';
19
+ import { Disposable } from '@theia/core/lib/common';
20
+ import { NotebookEditorWidgetService } from '../service/notebook-editor-widget-service';
21
+ import { NotebookEditorWidget } from '../notebook-editor-widget';
22
+ import { nls } from '@theia/core';
23
+ import { NotebookCommands } from './notebook-actions-contribution';
24
+
25
+ export const NOTEBOOK_CELL_SELECTION_STATUS_BAR_ID = 'notebook-cell-selection-position';
26
+
27
+ @injectable()
28
+ export class NotebookStatusBarContribution implements FrontendApplicationContribution {
29
+
30
+ @inject(StatusBar) protected readonly statusBar: StatusBar;
31
+ @inject(NotebookEditorWidgetService) protected readonly editorWidgetService: NotebookEditorWidgetService;
32
+
33
+ protected currentCellSelectionListener: Disposable | undefined;
34
+ protected lastFocusedEditor: NotebookEditorWidget | undefined;
35
+
36
+ onStart(): void {
37
+ this.editorWidgetService.onDidChangeFocusedEditor(editor => {
38
+ this.currentCellSelectionListener?.dispose();
39
+ this.currentCellSelectionListener = editor?.model?.onDidChangeSelectedCell(() =>
40
+ this.updateStatusbar(editor)
41
+ );
42
+ editor?.onDidDispose(() => {
43
+ this.lastFocusedEditor = undefined;
44
+ this.updateStatusbar();
45
+ });
46
+ this.updateStatusbar(editor);
47
+ this.lastFocusedEditor = editor;
48
+ });
49
+ if (this.editorWidgetService.focusedEditor) {
50
+ this.updateStatusbar();
51
+ }
52
+ }
53
+
54
+ protected async updateStatusbar(editor?: NotebookEditorWidget): Promise<void> {
55
+ if ((!editor && !this.lastFocusedEditor?.isVisible) || editor?.model?.cells.length === 0) {
56
+ this.statusBar.removeElement(NOTEBOOK_CELL_SELECTION_STATUS_BAR_ID);
57
+ return;
58
+ }
59
+
60
+ await editor?.ready;
61
+ if (!editor?.model) {
62
+ return;
63
+ }
64
+
65
+ const selectedCellIndex = editor.model.selectedCell ? editor.model.cells.indexOf(editor.model.selectedCell) + 1 : '';
66
+
67
+ this.statusBar.setElement(NOTEBOOK_CELL_SELECTION_STATUS_BAR_ID, {
68
+ text: nls.localizeByDefault('Cell {0} of {1}', selectedCellIndex, editor.model.cells.length),
69
+ alignment: StatusBarAlignment.RIGHT,
70
+ priority: 100,
71
+ command: NotebookCommands.CENTER_ACTIVE_CELL.id,
72
+ arguments: [editor]
73
+ });
74
+
75
+ }
76
+
77
+ }
@@ -154,7 +154,6 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
154
154
  @postConstruct()
155
155
  protected init(): void {
156
156
  this.id = NOTEBOOK_EDITOR_ID_PREFIX + this.props.uri.toString();
157
- this.node.tabIndex = -1;
158
157
 
159
158
  this.scrollOptions = {
160
159
  suppressScrollY: true
@@ -174,8 +173,6 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
174
173
  this.commandRegistry.executeCommand(NotebookCellCommands.EDIT_COMMAND.id, model, model.cells[0]);
175
174
  model.setSelectedCell(model.cells[0]);
176
175
  }
177
- model.cells.forEach(cell => cell.onWillBlurCellEditor(() => this.node.focus()));
178
- model.onDidAddOrRemoveCell(e => e.newCellIds?.forEach(cellId => model.cells.find(cell => cell.handle === cellId)?.onWillBlurCellEditor(() => this.node.focus())));
179
176
  });
180
177
  }
181
178
 
@@ -207,6 +204,7 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
207
204
  }
208
205
  // Ensure that the model is loaded before adding the editor
209
206
  this.notebookEditorService.addNotebookEditor(this);
207
+ this._model.selectedCell = this._model.cells[0];
210
208
  this.update();
211
209
  this.notebookContextManager.init(this);
212
210
  return this._model;
@@ -267,7 +265,8 @@ export class NotebookEditorWidget extends ReactWidget implements Navigatable, Sa
267
265
  notebookModel={this._model}
268
266
  notebookContext={this.notebookContextManager}
269
267
  toolbarRenderer={this.cellToolbarFactory}
270
- commandRegistry={this.commandRegistry} />
268
+ commandRegistry={this.commandRegistry}
269
+ menuRegistry={this.menuRegistry} />
271
270
  </PerfectScrollbar>
272
271
  </div>
273
272
  </div>;
@@ -47,6 +47,7 @@ import { NotebookClipboardService } from './service/notebook-clipboard-service';
47
47
  import { bindNotebookPreferences } from './contributions/notebook-preferences';
48
48
  import { NotebookOptionsService } from './service/notebook-options';
49
49
  import { NotebookUndoRedoHandler } from './contributions/notebook-undo-redo-handler';
50
+ import { NotebookStatusBarContribution } from './contributions/notebook-status-bar-contribution';
50
51
 
51
52
  export default new ContainerModule((bind, unbind, isBound, rebind) => {
52
53
  bind(NotebookColorContribution).toSelf().inSingletonScope();
@@ -112,4 +113,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
112
113
 
113
114
  bind(NotebookUndoRedoHandler).toSelf().inSingletonScope();
114
115
  bind(UndoRedoHandler).toService(NotebookUndoRedoHandler);
116
+
117
+ bind(NotebookStatusBarContribution).toSelf().inSingletonScope();
118
+ bind(FrontendApplicationContribution).toService(NotebookStatusBarContribution);
115
119
  });
@@ -272,10 +272,10 @@
272
272
  border: 1px solid var(--theia-notebook-cellToolbarSeparator);
273
273
  background-color: var(--theia-editor-background);
274
274
  color: var(--theia-foreground);
275
- vertical-align: middle;
276
- text-align: center;
275
+ display: flex;
277
276
  height: 24px;
278
277
  margin: 0 8px;
278
+ padding: 2px 4px;
279
279
  }
280
280
 
281
281
  .theia-notebook-add-cell-button:hover {
@@ -286,10 +286,18 @@
286
286
  background-color: var(--theia-toolbar-active);
287
287
  }
288
288
 
289
- .theia-notebook-add-cell-button-icon {
289
+ .theia-notebook-add-cell-button>* {
290
290
  vertical-align: middle;
291
291
  }
292
292
 
293
+ .theia-notebook-add-cell-button-icon::before {
294
+ font: normal normal normal 14px/1 codicon;
295
+ }
296
+
297
+ .theia-notebook-add-cell-button-text {
298
+ margin: 1px 0 0 4px;
299
+ }
300
+
293
301
  .theia-notebook-cell-output-webview {
294
302
  padding: 5px 0px;
295
303
  margin: 0px 15px 0px 9px;
@@ -101,6 +101,16 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
101
101
  this.props.notebookContextManager.scopedStore.setContext(NOTEBOOK_CELL_CURSOR_LAST_LINE, currentLine === lineCount);
102
102
  }));
103
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
+ }
112
+ }));
113
+
104
114
  this.toDispose.push(this.props.cell.onDidChangeEditorOptions(options => {
105
115
  this.editor?.getControl().updateOptions(options);
106
116
  }));
@@ -114,20 +124,7 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
114
124
  animationFrame().then(() => this.setMatches());
115
125
  }));
116
126
 
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
- }));
127
+ this.toDispose.push(this.props.cell.onDidSelectFindMatch(match => this.centerEditorInView()));
131
128
 
132
129
  this.toDispose.push(this.props.notebookModel.onDidChangeSelectedCell(e => {
133
130
  if (e.cell !== this.props.cell && this.editor?.getControl().hasTextFocus()) {
@@ -145,6 +142,10 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
145
142
  });
146
143
  this.toDispose.push(disposable);
147
144
  }
145
+
146
+ this.toDispose.push(this.props.cell.onDidRequestCenterEditor(() => {
147
+ this.centerEditorInView();
148
+ }));
148
149
  }
149
150
 
150
151
  override componentWillUnmount(): void {
@@ -156,6 +157,21 @@ export class CellEditor extends React.Component<CellEditorProps, {}> {
156
157
  this.toDispose = new DisposableCollection();
157
158
  }
158
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
+
159
175
  protected async initEditor(): Promise<void> {
160
176
  const { cell, notebookModel, monacoServices } = this.props;
161
177
  if (this.container) {
@@ -18,9 +18,9 @@ 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 { animationFrame, codicon, onDomEvent } from '@theia/core/lib/browser';
22
- import { CommandRegistry, DisposableCollection, nls } from '@theia/core';
23
- import { NotebookCommands } from '../contributions/notebook-actions-contribution';
21
+ import { animationFrame, onDomEvent } from '@theia/core/lib/browser';
22
+ import { CommandRegistry, DisposableCollection, MenuModelRegistry, MenuNode, nls } from '@theia/core';
23
+ import { NotebookCommands, NotebookMenus } from '../contributions/notebook-actions-contribution';
24
24
  import { NotebookCellActionContribution } from '../contributions/notebook-cell-actions-contribution';
25
25
  import { NotebookContextManager } from '../service/notebook-context-manager';
26
26
 
@@ -34,7 +34,8 @@ interface CellListProps {
34
34
  notebookModel: NotebookModel;
35
35
  notebookContext: NotebookContextManager;
36
36
  toolbarRenderer: NotebookCellToolbarFactory;
37
- commandRegistry: CommandRegistry
37
+ commandRegistry: CommandRegistry;
38
+ menuRegistry: MenuModelRegistry;
38
39
  }
39
40
 
40
41
  interface NotebookCellListState {
@@ -113,8 +114,9 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
113
114
  .map((cell, index) =>
114
115
  <React.Fragment key={'cell-' + cell.handle}>
115
116
  <NotebookCellDivider
117
+ menuRegistry={this.props.menuRegistry}
116
118
  isVisible={() => this.isEnabled()}
117
- onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, index)}
119
+ onAddNewCell={(commandId: string) => this.onAddNewCell(commandId, index)}
118
120
  onDrop={e => this.onDrop(e, index)}
119
121
  onDragOver={e => this.onDragOver(e, cell, 'top')} />
120
122
  {this.shouldRenderDragOverIndicator(cell, 'top') && <CellDropIndicator />}
@@ -132,7 +134,14 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
132
134
  onDrop={e => this.onDrop(e, index)}
133
135
  draggable={true}
134
136
  tabIndex={-1}
135
- ref={ref => cell === this.state.selectedCell && this.state.scrollIntoView && ref?.scrollIntoView({ block: 'nearest' })}>
137
+ ref={ref => {
138
+ if (ref && cell === this.state.selectedCell && this.state.scrollIntoView) {
139
+ ref.scrollIntoView({ block: 'nearest' });
140
+ if (cell.cellKind === CellKind.Markup && !cell.editing) {
141
+ ref.focus();
142
+ }
143
+ }
144
+ }}>
136
145
  <div className={'theia-notebook-cell-marker' + (this.state.selectedCell === cell ? ' theia-notebook-cell-marker-selected' : '')}></div>
137
146
  <div className='theia-notebook-cell-content'>
138
147
  {this.renderCellContent(cell, index)}
@@ -148,8 +157,9 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
148
157
  )
149
158
  }
150
159
  <NotebookCellDivider
160
+ menuRegistry={this.props.menuRegistry}
151
161
  isVisible={() => this.isEnabled()}
152
- onAddNewCell={(kind: CellKind) => this.onAddNewCell(kind, this.props.notebookModel.cells.length)}
162
+ onAddNewCell={(commandId: string) => this.onAddNewCell(commandId, this.props.notebookModel.cells.length)}
153
163
  onDrop={e => this.onDrop(e, this.props.notebookModel.cells.length - 1)}
154
164
  onDragOver={e => this.onDragOver(e, this.props.notebookModel.cells[this.props.notebookModel.cells.length - 1], 'bottom')} />
155
165
  </ul>;
@@ -214,11 +224,11 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
214
224
  this.setState({ ...this.state, dragOverIndicator: undefined });
215
225
  }
216
226
 
217
- protected onAddNewCell(kind: CellKind, index: number): void {
227
+ protected onAddNewCell(commandId: string, index: number): void {
218
228
  if (this.isEnabled()) {
219
- this.props.commandRegistry.executeCommand(NotebookCommands.ADD_NEW_CELL_COMMAND.id,
229
+ this.props.commandRegistry.executeCommand(NotebookCommands.CHANGE_SELECTED_CELL.id, index - 1);
230
+ this.props.commandRegistry.executeCommand(commandId,
220
231
  this.props.notebookModel,
221
- kind,
222
232
  index
223
233
  );
224
234
  }
@@ -235,24 +245,31 @@ export class NotebookCellListView extends React.Component<CellListProps, Noteboo
235
245
 
236
246
  export interface NotebookCellDividerProps {
237
247
  isVisible: () => boolean;
238
- onAddNewCell: (type: CellKind) => void;
248
+ onAddNewCell: (commandId: string) => void;
239
249
  onDrop: (event: React.DragEvent<HTMLLIElement>) => void;
240
250
  onDragOver: (event: React.DragEvent<HTMLLIElement>) => void;
251
+ menuRegistry: MenuModelRegistry;
241
252
  }
242
253
 
243
- export function NotebookCellDivider({ isVisible, onAddNewCell, onDrop, onDragOver }: NotebookCellDividerProps): React.JSX.Element {
254
+ export function NotebookCellDivider({ isVisible, onAddNewCell, onDrop, onDragOver, menuRegistry }: NotebookCellDividerProps): React.JSX.Element {
244
255
  const [hover, setHover] = React.useState(false);
245
256
 
257
+ const menuPath = NotebookMenus.NOTEBOOK_MAIN_TOOLBAR_CELL_ADD_GROUP;
258
+ const menuItems = menuRegistry.getMenuNode(menuPath).children;
259
+
260
+ const renderItem = (item: MenuNode): React.ReactNode => <button
261
+ key={item.id}
262
+ className='theia-notebook-add-cell-button'
263
+ onClick={() => onAddNewCell(item.command || '')}
264
+ title={nls.localizeByDefault(`Add ${item.label} Cell`)}
265
+ >
266
+ <div className={item.icon + ' theia-notebook-add-cell-button-icon'} />
267
+ <div className='theia-notebook-add-cell-button-text'>{item.label}</div>
268
+ </button>;
269
+
246
270
  return <li className='theia-notebook-cell-divider' onMouseEnter={() => setHover(true)} onMouseLeave={() => setHover(false)} onDrop={onDrop} onDragOver={onDragOver}>
247
271
  {hover && isVisible() && <div className='theia-notebook-add-cell-buttons'>
248
- <button className='theia-notebook-add-cell-button' onClick={() => onAddNewCell(CellKind.Code)} title={nls.localizeByDefault('Add Code Cell')}>
249
- <div className={codicon('add') + ' theia-notebook-add-cell-button-icon'} />
250
- {nls.localizeByDefault('Code')}
251
- </button>
252
- <button className='theia-notebook-add-cell-button' onClick={() => onAddNewCell(CellKind.Markup)} title={nls.localizeByDefault('Add Markdown Cell')}>
253
- <div className={codicon('add') + ' theia-notebook-add-cell-button-icon'} />
254
- {nls.localizeByDefault('Markdown')}
255
- </button>
272
+ {menuItems.map((item: MenuNode) => renderItem(item))}
256
273
  </div>}
257
274
  </li>;
258
275
  }
@@ -126,6 +126,9 @@ export class NotebookCellModel implements NotebookCell, Disposable {
126
126
  protected readonly onDidSelectFindMatchEmitter = new Emitter<NotebookCodeEditorFindMatch>();
127
127
  readonly onDidSelectFindMatch: Event<NotebookCodeEditorFindMatch> = this.onDidSelectFindMatchEmitter.event;
128
128
 
129
+ protected onDidRequestCenterEditorEmitter = new Emitter<void>();
130
+ readonly onDidRequestCenterEditor = this.onDidRequestCenterEditorEmitter.event;
131
+
129
132
  @inject(NotebookCellModelProps)
130
133
  protected readonly props: NotebookCellModelProps;
131
134
 
@@ -299,6 +302,10 @@ export class NotebookCellModel implements NotebookCell, Disposable {
299
302
  this.onWillBlurCellEditorEmitter.fire();
300
303
  }
301
304
 
305
+ requestCenterEditor(): void {
306
+ this.onDidRequestCenterEditorEmitter.fire();
307
+ }
308
+
302
309
  spliceNotebookCellOutputs(splice: NotebookCellOutputsSplice): void {
303
310
  if (splice.deleteCount > 0 && splice.newOutputs.length > 0) {
304
311
  const commonLen = Math.min(splice.deleteCount, splice.newOutputs.length);