Skip to content

Commit f025dc8

Browse files
committed
cell bottom toolbar
1 parent b8e0f3f commit f025dc8

8 files changed

Lines changed: 182 additions & 18 deletions

File tree

src/vs/workbench/contrib/notebook/browser/constants.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@ export const CANCEL_CELL_COMMAND_ID = 'workbench.notebook.cell.cancelExecution';
2424
export const CELL_MARGIN = 20;
2525
export const CELL_RUN_GUTTER = 32; // TODO should be dynamic based on execution order width, and runnable enablement
2626

27-
export const EDITOR_TOOLBAR_HEIGHT = 22;
27+
export const EDITOR_TOOLBAR_HEIGHT = 20;
28+
export const BOTTOM_CELL_TOOLBAR_HEIGHT = 32;
2829

2930
// Top margin of editor
30-
export const EDITOR_TOP_MARGIN = 8;
31+
export const EDITOR_TOP_MARGIN = 0;
3132

3233
// Top and bottom padding inside the monaco editor in a cell, which are included in `cell.editorHeight`
3334
export const EDITOR_TOP_PADDING = 8;

src/vs/workbench/contrib/notebook/browser/contrib/notebookActions.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ export class CancelCellAction extends MenuItemAction {
113113
}
114114
}
115115

116+
116117
registerAction2(class extends Action2 {
117118
constructor() {
118119
super({
@@ -485,6 +486,24 @@ registerAction2(class extends InsertCellCommand {
485486
}
486487
});
487488

489+
export class InsertCodeCellAction extends MenuItemAction {
490+
constructor(
491+
@IContextKeyService contextKeyService: IContextKeyService,
492+
@ICommandService commandService: ICommandService
493+
) {
494+
super(
495+
{
496+
id: INSERT_CODE_CELL_BELOW_COMMAND_ID,
497+
title: localize('notebookActions.insertCodeCellBelow', "Insert Code Cell Below"),
498+
// icon: { id: 'codicon/add' },
499+
},
500+
undefined,
501+
{ shouldForwardArgs: true },
502+
contextKeyService,
503+
commandService);
504+
}
505+
}
506+
488507
registerAction2(class extends InsertCellCommand {
489508
constructor() {
490509
super(
@@ -520,6 +539,23 @@ registerAction2(class extends InsertCellCommand {
520539
}
521540
});
522541

542+
export class InsertMarkdownCellAction extends MenuItemAction {
543+
constructor(
544+
@IContextKeyService contextKeyService: IContextKeyService,
545+
@ICommandService commandService: ICommandService
546+
) {
547+
super(
548+
{
549+
id: INSERT_MARKDOWN_CELL_BELOW_COMMAND_ID,
550+
title: localize('notebookActions.insertMarkdownCellBelow', "Insert Markdown Cell Below")
551+
},
552+
undefined,
553+
{ shouldForwardArgs: true },
554+
contextKeyService,
555+
commandService);
556+
}
557+
}
558+
523559
registerAction2(class extends InsertCellCommand {
524560
constructor() {
525561
super(

src/vs/workbench/contrib/notebook/browser/notebook.css

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,42 @@
202202
visibility: visible;
203203
}
204204

205+
.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container {
206+
position: absolute;
207+
display: flex;
208+
opacity: 0;
209+
}
210+
211+
.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container:hover {
212+
opacity: 1;
213+
}
214+
215+
.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .seperator {
216+
height: 1px;
217+
flex-grow: 1;
218+
align-self: center;
219+
}
220+
221+
.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .seperator-short {
222+
height: 1px;
223+
width: 16px;
224+
align-self: center;
225+
}
226+
227+
.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container .button {
228+
display: flex;
229+
margin: 0 8px;
230+
align-self: center;
231+
align-items: center;
232+
white-space: pre;
233+
}
234+
235+
.monaco-workbench .part.editor > .content .notebook-editor > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-bottom-toolbar-container span.codicon {
236+
text-align: center;
237+
font-size: 14px;
238+
color: inherit;
239+
}
240+
205241
.notebook-webview {
206242
position: absolute;
207243
z-index: 1000000;

src/vs/workbench/contrib/notebook/browser/notebookBrowser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export interface CodeCellLayoutInfo {
4444
readonly outputContainerOffset: number;
4545
readonly outputTotalHeight: number;
4646
readonly indicatorHeight: number;
47+
readonly bottomToolbarOffset: number;
4748
}
4849

4950
export interface CodeCellLayoutChangeEvent {
@@ -265,6 +266,7 @@ export interface CodeCellRenderTemplate extends BaseCellRenderTemplate {
265266
outputContainer: HTMLElement;
266267
editor: CodeEditorWidget;
267268
progressBar: ProgressBar;
269+
betweenCellContainer: HTMLElement;
268270
}
269271

270272
export interface IOutputTransformContribution {

src/vs/workbench/contrib/notebook/browser/notebookEditor.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/com
2828
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
2929
import { IEditorGroupView } from 'vs/workbench/browser/parts/editor/editor';
3030
import { EditorOptions, IEditorCloseEvent, IEditorMemento } from 'vs/workbench/common/editor';
31-
import { CELL_MARGIN, CELL_RUN_GUTTER } from 'vs/workbench/contrib/notebook/browser/constants';
31+
import { CELL_MARGIN, CELL_RUN_GUTTER, EDITOR_TOP_MARGIN } from 'vs/workbench/contrib/notebook/browser/constants';
3232
import { NotebookFindWidget } from 'vs/workbench/contrib/notebook/browser/contrib/notebookFindWidget';
3333
import { CellEditState, CellFocusMode, ICellViewModel, INotebookEditor, NotebookLayoutInfo, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
3434
import { NotebookEditorInput, NotebookEditorModel } from 'vs/workbench/contrib/notebook/browser/notebookEditorInput';
@@ -780,6 +780,12 @@ export const notebookOutputContainerColor = registerColor('notebook.outputContai
780780
}
781781
, nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background."));
782782

783+
export const CELL_TOOLBAR_SEPERATOR = registerColor('notebook.cellToolbarSeperator', {
784+
dark: Color.fromHex('#808080').transparent(0.35),
785+
light: Color.fromHex('#808080').transparent(0.35),
786+
hc: contrastBorder
787+
}, nls.localize('cellToolbarSeperator', "The color of seperator in Cell bottom toolbar"));
788+
783789

784790
registerThemingParticipant((theme, collector) => {
785791
const color = getExtraColor(theme, embeddedEditorBackground, { dark: 'rgba(0, 0, 0, .4)', extra_dark: 'rgba(200, 235, 255, .064)', light: '#f4f4f4', hc: null });
@@ -826,9 +832,16 @@ registerThemingParticipant((theme, collector) => {
826832
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row.selected .notebook-cell-focus-indicator { border-color: ${focusedCellIndicatorColor}; }`);
827833
}
828834

835+
const cellToolbarSeperator = theme.getColor(CELL_TOOLBAR_SEPERATOR);
836+
if (cellToolbarSeperator) {
837+
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container .seperator { background-color: ${cellToolbarSeperator} }`);
838+
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container .seperator-short { background-color: ${cellToolbarSeperator} }`);
839+
}
840+
829841
// Cell Margin
830-
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { margin: 8px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`);
842+
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .monaco-list-row > div.cell { margin: ${EDITOR_TOP_MARGIN}px ${CELL_MARGIN}px 0px ${CELL_MARGIN}px; }`);
831843
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`);
844+
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell-bottom-toolbar-container { width: calc(100% - ${CELL_MARGIN * 2 + CELL_RUN_GUTTER}px); margin: 0px ${CELL_MARGIN}px 0px ${CELL_MARGIN + CELL_RUN_GUTTER}px }`);
832845

833846
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .cell-editor-container { width: calc(100% - ${CELL_RUN_GUTTER}px); }`);
834847
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .cell .markdown-editor-container { margin-left: ${CELL_RUN_GUTTER}px; }`);

src/vs/workbench/contrib/notebook/browser/view/renderers/cellRenderer.ts

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import * as DOM from 'vs/base/browser/dom';
88
import { IListRenderer, IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
99
import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar';
1010
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
11-
import { IAction } from 'vs/base/common/actions';
11+
import { IAction, ActionRunner } from 'vs/base/common/actions';
12+
import { escape } from 'vs/base/common/strings';
1213
import { DisposableStore } from 'vs/base/common/lifecycle';
1314
import { deepClone } from 'vs/base/common/objects';
1415
import 'vs/css!vs/workbench/contrib/notebook/browser/notebook';
@@ -24,8 +25,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
2425
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
2526
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2627
import { INotificationService } from 'vs/platform/notification/common/notification';
27-
import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY, NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY, NOTEBOOK_VIEW_TYPE } from 'vs/workbench/contrib/notebook/browser/constants';
28-
import { ExecuteCellAction, INotebookCellActionContext, CancelCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/notebookActions';
28+
import { EDITOR_BOTTOM_PADDING, EDITOR_TOOLBAR_HEIGHT, EDITOR_TOP_MARGIN, EDITOR_TOP_PADDING, NOTEBOOK_CELL_EDITABLE_CONTEXT_KEY, NOTEBOOK_CELL_MARKDOWN_EDIT_MODE_CONTEXT_KEY, NOTEBOOK_CELL_TYPE_CONTEXT_KEY, NOTEBOOK_CELL_RUN_STATE_CONTEXT_KEY, NOTEBOOK_VIEW_TYPE, BOTTOM_CELL_TOOLBAR_HEIGHT } from 'vs/workbench/contrib/notebook/browser/constants';
29+
import { ExecuteCellAction, INotebookCellActionContext, CancelCellAction, InsertCodeCellAction, InsertMarkdownCellAction } from 'vs/workbench/contrib/notebook/browser/contrib/notebookActions';
2930
import { BaseCellRenderTemplate, CellEditState, CellRunState, CodeCellRenderTemplate, ICellViewModel, INotebookEditor, MarkdownCellRenderTemplate } from 'vs/workbench/contrib/notebook/browser/notebookBrowser';
3031
import { CellMenus } from 'vs/workbench/contrib/notebook/browser/view/renderers/cellMenus';
3132
import { CodeCell } from 'vs/workbench/contrib/notebook/browser/view/renderers/codeCell';
@@ -34,12 +35,12 @@ import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewMod
3435
import { MarkdownCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markdownCellViewModel';
3536
import { CellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModel';
3637
import { CellKind } from 'vs/workbench/contrib/notebook/common/notebookCommon';
38+
import { renderCodicons } from 'vs/base/common/codicons';
3739

3840
const $ = DOM.$;
3941

4042
export class NotebookCellListDelegate implements IListVirtualDelegate<CellViewModel> {
4143
private _lineHeight: number;
42-
private _toolbarHeight = EDITOR_TOOLBAR_HEIGHT;
4344

4445
constructor(
4546
@IConfigurationService private readonly configurationService: IConfigurationService
@@ -49,7 +50,7 @@ export class NotebookCellListDelegate implements IListVirtualDelegate<CellViewMo
4950
}
5051

5152
getHeight(element: CellViewModel): number {
52-
return element.getHeight(this._lineHeight) + this._toolbarHeight;
53+
return element.getHeight(this._lineHeight);
5354
}
5455

5556
hasDynamicHeight(element: CellViewModel): boolean {
@@ -65,6 +66,22 @@ export class NotebookCellListDelegate implements IListVirtualDelegate<CellViewMo
6566
}
6667
}
6768

69+
export class CodiconActionViewItem extends ContextAwareMenuEntryActionViewItem {
70+
constructor(
71+
readonly _action: MenuItemAction,
72+
_keybindingService: IKeybindingService,
73+
_notificationService: INotificationService,
74+
_contextMenuService: IContextMenuService
75+
) {
76+
super(_action, _keybindingService, _notificationService, _contextMenuService);
77+
}
78+
updateLabel(): void {
79+
if (this.options.label && this.label) {
80+
this.label.innerHTML = renderCodicons(this._commandAction.label ?? '');
81+
}
82+
}
83+
}
84+
6885
abstract class AbstractCellRenderer {
6986
protected editorOptions: IEditorOptions;
7087

@@ -105,6 +122,22 @@ abstract class AbstractCellRenderer {
105122
};
106123
}
107124

125+
protected createBottomCellToolbar(container: HTMLElement): ToolBar {
126+
const toolbar = new ToolBar(container, this.contextMenuService, {
127+
actionViewItemProvider: action => {
128+
if (action instanceof MenuItemAction) {
129+
const item = new CodiconActionViewItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
130+
return item;
131+
}
132+
133+
return undefined;
134+
}
135+
});
136+
137+
toolbar.getContainer().style.height = `${BOTTOM_CELL_TOOLBAR_HEIGHT}px`;
138+
return toolbar;
139+
}
140+
108141
protected createToolbar(container: HTMLElement): ToolBar {
109142
const toolbar = new ToolBar(container, this.contextMenuService, {
110143
actionViewItemProvider: action => {
@@ -258,6 +291,8 @@ export class MarkdownCellRenderer extends AbstractCellRenderer implements IListR
258291
export class CodeCellRenderer extends AbstractCellRenderer implements IListRenderer<CodeCellViewModel, CodeCellRenderTemplate> {
259292
static readonly TEMPLATE_ID = 'code_cell';
260293
private disposables: Map<ICellViewModel, DisposableStore> = new Map();
294+
private actionRunner = new ActionRunner();
295+
261296

262297
constructor(
263298
protected notebookEditor: INotebookEditor,
@@ -286,6 +321,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
286321
const runToolbar = this.createToolbar(runButtonContainer);
287322
disposables.add(runToolbar);
288323

324+
const betweenCellContainer = DOM.append(container, $('.cell-bottom-toolbar-container'));
325+
289326
const executionOrderLabel = DOM.append(runButtonContainer, $('div.execution-count-label'));
290327

291328
const editorContainer = DOM.append(cellContainer, $('.cell-editor-container'));
@@ -320,9 +357,44 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
320357
outputContainer,
321358
editor,
322359
disposables,
360+
betweenCellContainer: betweenCellContainer,
361+
// betweenCellToolbar: betweenCellActionsToolbar
323362
};
324363
}
325364

365+
protected setupBetweenCellToolbarActions(element: CodeCellViewModel, templateData: CodeCellRenderTemplate, disposables: DisposableStore, context: INotebookCellActionContext): void {
366+
const container = templateData.betweenCellContainer;
367+
container.innerHTML = '';
368+
container.style.height = `${BOTTOM_CELL_TOOLBAR_HEIGHT}px`;
369+
370+
DOM.append(container, $('.seperator'));
371+
const addCodeCell = DOM.append(container, $('span.button'));
372+
addCodeCell.innerHTML = renderCodicons(escape(`$(add) Code `));
373+
const insertCellBelow = this.instantiationService.createInstance(InsertCodeCellAction);
374+
375+
disposables.add(DOM.addDisposableListener(addCodeCell, DOM.EventType.CLICK, () => {
376+
this.actionRunner.run(insertCellBelow, context);
377+
}));
378+
379+
DOM.append(container, $('.seperator-short'));
380+
const addMarkdownCell = DOM.append(container, $('span.button'));
381+
addMarkdownCell.innerHTML = renderCodicons(escape('$(add) Markdown '));
382+
const insertMarkdownBelow = this.instantiationService.createInstance(InsertMarkdownCellAction);
383+
disposables.add(DOM.addDisposableListener(addMarkdownCell, DOM.EventType.CLICK, () => {
384+
this.actionRunner.run(insertMarkdownBelow, context);
385+
}));
386+
387+
DOM.append(container, $('.seperator'));
388+
389+
const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset;
390+
container.style.top = `${bottomToolbarOffset}px`;
391+
392+
disposables.add(element.onDidChangeLayout(() => {
393+
const bottomToolbarOffset = element.layoutInfo.bottomToolbarOffset;
394+
container.style.top = `${bottomToolbarOffset}px`;
395+
}));
396+
}
397+
326398
private updateForRunState(element: CodeCellViewModel, templateData: CodeCellRenderTemplate, runStateKey: IContextKey<string>): void {
327399
runStateKey.set(CellRunState[element.runState]);
328400
if (element.runState === CellRunState.Running) {
@@ -395,6 +467,8 @@ export class CodeCellRenderer extends AbstractCellRenderer implements IListRende
395467
};
396468
templateData.toolbar.context = toolbarContext;
397469
templateData.runToolbar.context = toolbarContext;
470+
471+
this.setupBetweenCellToolbarActions(element, templateData, elementDisposable, toolbarContext);
398472
}
399473

400474
disposeTemplate(templateData: CodeCellRenderTemplate): void {

src/vs/workbench/contrib/notebook/browser/view/renderers/markdownCell.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,15 @@ export class StatefullMarkdownCell extends Disposable {
4747

4848
if (this.editor) {
4949
// not first time, we don't need to create editor or bind listeners
50-
this.editingContainer!.style.display = 'block';
50+
this.editingContainer!.style.display = 'flex';
5151
viewCell.attachTextEditor(this.editor!);
5252
if (notebookEditor.getActiveCell() === viewCell) {
5353
this.editor!.focus();
5454
}
5555

5656
this.bindEditorListeners(this.editor!.getModel()!);
5757
} else {
58-
this.editingContainer!.style.display = 'block';
58+
this.editingContainer!.style.display = 'flex';
5959
this.editingContainer!.innerHTML = '';
6060
this.editor = instantiationService.createInstance(CodeEditorWidget, this.editingContainer!, {
6161
...editorOptions,

0 commit comments

Comments
 (0)