mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
notebook toolbar rewrite. Adopt WorkbenchToolbar (#185168)
* white * okay now it all works (except the corner cases i didn't find yet) * testing + small fixes * yet another corner case woohoo * setting changes + test skips Co-authored-by: Peng Lyu <penn.lv@gmail.com> --------- Co-authored-by: Peng Lyu <penn.lv@gmail.com>
This commit is contained in:
parent
22705e6f41
commit
1eec40969e
|
@ -65,12 +65,12 @@ import { NotebookEventDispatcher } from 'vs/workbench/contrib/notebook/browser/v
|
|||
import { MarkupCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/markupCellViewModel';
|
||||
import { CellViewModel, NotebookViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/notebookViewModelImpl';
|
||||
import { ViewContext } from 'vs/workbench/contrib/notebook/browser/viewModel/viewContext';
|
||||
import { NotebookEditorToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar';
|
||||
import { NotebookEditorToolbar, NotebookEditorWorkbenchToolbar, RenderLabel, RenderLabelWithFallback, convertConfiguration } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar';
|
||||
import { NotebookEditorContextKeys } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys';
|
||||
import { NotebookOverviewRuler } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookOverviewRuler';
|
||||
import { ListTopCellToolbar } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookTopCellToolbar';
|
||||
import { NotebookTextModel } from 'vs/workbench/contrib/notebook/common/model/notebookTextModel';
|
||||
import { CellEditType, CellKind, INotebookSearchOptions, RENDERER_NOT_AVAILABLE, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { CellEditType, CellKind, INotebookSearchOptions, NotebookSetting, RENDERER_NOT_AVAILABLE, SelectionStateType } from 'vs/workbench/contrib/notebook/common/notebookCommon';
|
||||
import { NOTEBOOK_CURSOR_NAVIGATION_MODE, NOTEBOOK_EDITOR_EDITABLE, NOTEBOOK_EDITOR_FOCUSED, NOTEBOOK_OUTPUT_FOCUSED, NOTEBOOK_OUPTUT_INPUT_FOCUSED } from 'vs/workbench/contrib/notebook/common/notebookContextKeys';
|
||||
import { INotebookExecutionService } from 'vs/workbench/contrib/notebook/common/notebookExecutionService';
|
||||
import { INotebookExecutionStateService } from 'vs/workbench/contrib/notebook/common/notebookExecutionStateService';
|
||||
|
@ -170,7 +170,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
|||
//#endregion
|
||||
private _overlayContainer!: HTMLElement;
|
||||
private _notebookTopToolbarContainer!: HTMLElement;
|
||||
private _notebookTopToolbar!: NotebookEditorToolbar;
|
||||
private _notebookTopToolbar!: NotebookEditorToolbar | NotebookEditorWorkbenchToolbar;
|
||||
private _notebookOverviewRulerContainer!: HTMLElement;
|
||||
private _notebookOverviewRuler!: NotebookOverviewRuler;
|
||||
private _body!: HTMLElement;
|
||||
|
@ -1013,12 +1013,42 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD
|
|||
}
|
||||
|
||||
private _registerNotebookActionsToolbar() {
|
||||
this._notebookTopToolbar = this._register(this.instantiationService.createInstance(NotebookEditorToolbar, this, this.scopedContextKeyService, this._notebookOptions, this._notebookTopToolbarContainer));
|
||||
this._register(this._notebookTopToolbar.onDidChangeVisibility(() => {
|
||||
if (this._dimension && this._isVisible) {
|
||||
this.layout(this._dimension);
|
||||
const store = new DisposableStore();
|
||||
let currentLabel = convertConfiguration(this.configurationService.getValue<RenderLabelWithFallback>(NotebookSetting.globalToolbarShowLabel));
|
||||
|
||||
const render = () => {
|
||||
if (currentLabel === RenderLabel.Dynamic) {
|
||||
this._notebookTopToolbar = this._register(this.instantiationService.createInstance(NotebookEditorToolbar, this, this.scopedContextKeyService, this._notebookOptions, this._notebookTopToolbarContainer));
|
||||
} else {
|
||||
this._notebookTopToolbar = this._register(this.instantiationService.createInstance(NotebookEditorWorkbenchToolbar, this, this.scopedContextKeyService, this._notebookOptions, this._notebookTopToolbarContainer));
|
||||
}
|
||||
|
||||
store.add(this._notebookTopToolbar.onDidChangeVisibility(() => {
|
||||
if (this._dimension && this._isVisible) {
|
||||
this.layout(this._dimension);
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
render();
|
||||
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (!e.affectsConfiguration(NotebookSetting.globalToolbarShowLabel)) {
|
||||
return;
|
||||
}
|
||||
const newRenderLabel = convertConfiguration(this.configurationService.getValue<RenderLabelWithFallback>(NotebookSetting.globalToolbarShowLabel));
|
||||
|
||||
if (newRenderLabel !== currentLabel && (newRenderLabel === RenderLabel.Dynamic || currentLabel === RenderLabel.Dynamic)) {
|
||||
// switch to the other implementation
|
||||
store.clear();
|
||||
this._notebookTopToolbar.dispose();
|
||||
DOM.clearNode(this._notebookTopToolbarContainer);
|
||||
currentLabel = newRenderLabel;
|
||||
render();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(store);
|
||||
}
|
||||
|
||||
private _updateOutputRenderers() {
|
||||
|
|
|
@ -11,7 +11,7 @@ import { Emitter, Event } from 'vs/base/common/event';
|
|||
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import { MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { IMenu, IMenuService, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IMenu, IMenuService, MenuId, MenuItemAction, SubmenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
|
@ -28,6 +28,7 @@ import { NotebookOptions } from 'vs/workbench/contrib/notebook/browser/notebookO
|
|||
import { IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { disposableTimeout } from 'vs/base/common/async';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { HiddenItemStrategy, IWorkbenchToolBarOptions, WorkbenchToolBar } from 'vs/platform/actions/browser/toolbar';
|
||||
|
||||
interface IActionModel {
|
||||
action: IAction;
|
||||
|
@ -36,13 +37,28 @@ interface IActionModel {
|
|||
renderLabel: boolean;
|
||||
}
|
||||
|
||||
enum RenderLabel {
|
||||
export enum RenderLabel {
|
||||
Always = 0,
|
||||
Never = 1,
|
||||
Dynamic = 2
|
||||
}
|
||||
|
||||
type RenderLabelWithFallback = true | false | 'always' | 'never' | 'dynamic';
|
||||
export type RenderLabelWithFallback = true | false | 'always' | 'never' | 'dynamic';
|
||||
|
||||
export function convertConfiguration(value: RenderLabelWithFallback): RenderLabel {
|
||||
switch (value) {
|
||||
case true:
|
||||
return RenderLabel.Always;
|
||||
case false:
|
||||
return RenderLabel.Never;
|
||||
case 'always':
|
||||
return RenderLabel.Always;
|
||||
case 'never':
|
||||
return RenderLabel.Never;
|
||||
case 'dynamic':
|
||||
return RenderLabel.Dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
const ICON_ONLY_ACTION_WIDTH = 21;
|
||||
const TOGGLE_MORE_ACTION_WIDTH = 21;
|
||||
|
@ -104,7 +120,6 @@ class FixedLabelStrategy implements IActionLayoutStrategy {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
class FixedLabellessStrategy extends FixedLabelStrategy {
|
||||
constructor(
|
||||
notebookEditor: INotebookEditorDelegate,
|
||||
|
@ -354,7 +369,7 @@ export class NotebookEditorToolbar extends Disposable {
|
|||
this._register(this._notebookGlobalActionsMenu);
|
||||
|
||||
this._useGlobalToolbar = this.notebookOptions.getLayoutConfiguration().globalToolbar;
|
||||
this._renderLabel = this._convertConfiguration(this.configurationService.getValue<RenderLabelWithFallback>(NotebookSetting.globalToolbarShowLabel));
|
||||
this._renderLabel = convertConfiguration(this.configurationService.getValue<RenderLabelWithFallback>(NotebookSetting.globalToolbarShowLabel));
|
||||
this._updateStrategy();
|
||||
|
||||
const context = {
|
||||
|
@ -433,7 +448,7 @@ export class NotebookEditorToolbar extends Disposable {
|
|||
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(NotebookSetting.globalToolbarShowLabel)) {
|
||||
this._renderLabel = this._convertConfiguration(this.configurationService.getValue<RenderLabelWithFallback>(NotebookSetting.globalToolbarShowLabel));
|
||||
this._renderLabel = convertConfiguration(this.configurationService.getValue<RenderLabelWithFallback>(NotebookSetting.globalToolbarShowLabel));
|
||||
this._updateStrategy();
|
||||
const oldElement = this._notebookLeftToolbar.getElement();
|
||||
oldElement.parentElement?.removeChild(oldElement);
|
||||
|
@ -479,21 +494,6 @@ export class NotebookEditorToolbar extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
private _convertConfiguration(value: RenderLabelWithFallback): RenderLabel {
|
||||
switch (value) {
|
||||
case true:
|
||||
return RenderLabel.Always;
|
||||
case false:
|
||||
return RenderLabel.Never;
|
||||
case 'always':
|
||||
return RenderLabel.Always;
|
||||
case 'never':
|
||||
return RenderLabel.Never;
|
||||
case 'dynamic':
|
||||
return RenderLabel.Dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
private _showNotebookActionsinEditorToolbar() {
|
||||
// when there is no view model, just ignore.
|
||||
if (!this.notebookEditor.hasModel()) {
|
||||
|
@ -642,3 +642,532 @@ export class NotebookEditorToolbar extends Disposable {
|
|||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class WorkbenchAlwaysLabelStrategy implements IActionLayoutStrategy {
|
||||
constructor(
|
||||
readonly notebookEditor: INotebookEditorDelegate,
|
||||
readonly editorToolbar: NotebookEditorWorkbenchToolbar,
|
||||
readonly instantiationService: IInstantiationService) { }
|
||||
|
||||
actionProvider(action: IAction): ActionViewItem | undefined {
|
||||
if (action.id === SELECT_KERNEL_ID) {
|
||||
// this is being disposed by the consumer
|
||||
return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor);
|
||||
}
|
||||
|
||||
return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
|
||||
}
|
||||
|
||||
calculateActions(leftToolbarContainerMaxWidth: number): { primaryActions: IAction[]; secondaryActions: IAction[] } {
|
||||
const initialPrimaryActions = this.editorToolbar.primaryActions;
|
||||
const initialSecondaryActions = this.editorToolbar.secondaryActions;
|
||||
|
||||
return workbenchCalculateActions(initialPrimaryActions, initialSecondaryActions, leftToolbarContainerMaxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
class WorkbenchNeverLabelStrategy implements IActionLayoutStrategy {
|
||||
constructor(
|
||||
readonly notebookEditor: INotebookEditorDelegate,
|
||||
readonly editorToolbar: NotebookEditorWorkbenchToolbar,
|
||||
readonly instantiationService: IInstantiationService) { }
|
||||
|
||||
actionProvider(action: IAction): ActionViewItem | undefined {
|
||||
if (action.id === SELECT_KERNEL_ID) {
|
||||
// this is being disposed by the consumer
|
||||
return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor);
|
||||
}
|
||||
|
||||
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
|
||||
}
|
||||
|
||||
calculateActions(leftToolbarContainerMaxWidth: number): { primaryActions: IAction[]; secondaryActions: IAction[] } {
|
||||
const initialPrimaryActions = this.editorToolbar.primaryActions;
|
||||
const initialSecondaryActions = this.editorToolbar.secondaryActions;
|
||||
|
||||
return workbenchCalculateActions(initialPrimaryActions, initialSecondaryActions, leftToolbarContainerMaxWidth);
|
||||
}
|
||||
}
|
||||
|
||||
// class WorkbenchDynamicLabelStrategy implements IActionLayoutStrategy {
|
||||
|
||||
// constructor(
|
||||
// readonly notebookEditor: INotebookEditorDelegate,
|
||||
// readonly editorToolbar: NotebookEditorToolbar,
|
||||
// readonly instantiationService: IInstantiationService) {
|
||||
// }
|
||||
|
||||
// actionProvider(action: IAction) {
|
||||
// if (action.id === SELECT_KERNEL_ID) {
|
||||
// // // this is being disposed by the consumer
|
||||
// return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor);
|
||||
// }
|
||||
|
||||
// const a = this.editorToolbar.primaryActions.find(a => a.action.id === action.id);
|
||||
// if (!a || a.renderLabel) {
|
||||
// // render new action with label to get a correct full width.
|
||||
// return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
|
||||
// } else {
|
||||
// return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
|
||||
// }
|
||||
// }
|
||||
|
||||
// calculateActions(leftToolbarContainerMaxWidth: number) {
|
||||
// }
|
||||
|
||||
// }
|
||||
|
||||
export class NotebookEditorWorkbenchToolbar extends Disposable {
|
||||
private _leftToolbarScrollable!: DomScrollableElement;
|
||||
private _notebookTopLeftToolbarContainer!: HTMLElement;
|
||||
private _notebookTopRightToolbarContainer!: HTMLElement;
|
||||
private _notebookGlobalActionsMenu!: IMenu;
|
||||
private _notebookLeftToolbar!: WorkbenchToolBar;
|
||||
private _primaryActions: IActionModel[];
|
||||
get primaryActions(): IActionModel[] {
|
||||
return this._primaryActions;
|
||||
}
|
||||
private _secondaryActions: IAction[];
|
||||
get secondaryActions(): IAction[] {
|
||||
return this._secondaryActions;
|
||||
}
|
||||
private _notebookRightToolbar!: ToolBar;
|
||||
private _useGlobalToolbar: boolean = false;
|
||||
private _strategy!: IActionLayoutStrategy;
|
||||
private _renderLabel: RenderLabel = RenderLabel.Always;
|
||||
|
||||
private _visible: boolean = false;
|
||||
set visible(visible: boolean) {
|
||||
if (this._visible !== visible) {
|
||||
this._visible = visible;
|
||||
this._onDidChangeVisibility.fire(visible);
|
||||
}
|
||||
}
|
||||
private readonly _onDidChangeVisibility = this._register(new Emitter<boolean>());
|
||||
onDidChangeVisibility: Event<boolean> = this._onDidChangeVisibility.event;
|
||||
|
||||
get useGlobalToolbar(): boolean {
|
||||
return this._useGlobalToolbar;
|
||||
}
|
||||
|
||||
private _dimension: DOM.Dimension | null = null;
|
||||
|
||||
private _deferredActionUpdate: IDisposable | undefined;
|
||||
|
||||
constructor(
|
||||
readonly notebookEditor: INotebookEditorDelegate,
|
||||
readonly contextKeyService: IContextKeyService,
|
||||
readonly notebookOptions: NotebookOptions,
|
||||
readonly domNode: HTMLElement,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IWorkbenchAssignmentService private readonly experimentService: IWorkbenchAssignmentService,
|
||||
) {
|
||||
super();
|
||||
|
||||
this._primaryActions = [];
|
||||
this._secondaryActions = [];
|
||||
this._buildBody();
|
||||
|
||||
this._register(Event.debounce<void, void>(
|
||||
this.editorService.onDidActiveEditorChange,
|
||||
(last, _current) => last,
|
||||
200
|
||||
)(this._updatePerEditorChange, this));
|
||||
|
||||
this._registerNotebookActionsToolbar();
|
||||
}
|
||||
|
||||
private _buildBody() {
|
||||
this._notebookTopLeftToolbarContainer = document.createElement('div');
|
||||
this._notebookTopLeftToolbarContainer.classList.add('notebook-toolbar-left');
|
||||
this._leftToolbarScrollable = new DomScrollableElement(this._notebookTopLeftToolbarContainer, {
|
||||
vertical: ScrollbarVisibility.Hidden,
|
||||
horizontal: ScrollbarVisibility.Visible,
|
||||
horizontalScrollbarSize: 3,
|
||||
useShadows: false,
|
||||
scrollYToX: true
|
||||
});
|
||||
this._register(this._leftToolbarScrollable);
|
||||
|
||||
DOM.append(this.domNode, this._leftToolbarScrollable.getDomNode());
|
||||
this._notebookTopRightToolbarContainer = document.createElement('div');
|
||||
this._notebookTopRightToolbarContainer.classList.add('notebook-toolbar-right');
|
||||
DOM.append(this.domNode, this._notebookTopRightToolbarContainer);
|
||||
}
|
||||
|
||||
private _updatePerEditorChange() {
|
||||
if (this.editorService.activeEditorPane?.getId() === NOTEBOOK_EDITOR_ID) {
|
||||
const notebookEditor = this.editorService.activeEditorPane.getControl() as INotebookEditorDelegate;
|
||||
if (notebookEditor === this.notebookEditor) {
|
||||
// this is the active editor
|
||||
this._showNotebookActionsinEditorToolbar();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _registerNotebookActionsToolbar() {
|
||||
this._notebookGlobalActionsMenu = this._register(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.notebookToolbar, this.contextKeyService));
|
||||
this._register(this._notebookGlobalActionsMenu);
|
||||
|
||||
this._useGlobalToolbar = this.notebookOptions.getLayoutConfiguration().globalToolbar;
|
||||
this._renderLabel = this._convertConfiguration(this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel));
|
||||
this._updateStrategy();
|
||||
|
||||
const context = {
|
||||
ui: true,
|
||||
notebookEditor: this.notebookEditor
|
||||
};
|
||||
|
||||
const actionProvider = (action: IAction) => {
|
||||
if (action.id === SELECT_KERNEL_ID) {
|
||||
// this is being disposed by the consumer
|
||||
return this.instantiationService.createInstance(NotebooKernelActionViewItem, action, this.notebookEditor);
|
||||
}
|
||||
|
||||
if (this._renderLabel !== RenderLabel.Never) {
|
||||
const a = this._primaryActions.find(a => a.action.id === action.id);
|
||||
// if (a && a.renderLabel) {
|
||||
if (a) {
|
||||
return action instanceof MenuItemAction ? this.instantiationService.createInstance(ActionViewWithLabel, action, undefined) : undefined;
|
||||
} else {
|
||||
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
|
||||
}
|
||||
} else {
|
||||
return action instanceof MenuItemAction ? this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined) : undefined;
|
||||
}
|
||||
};
|
||||
|
||||
const leftToolbarOptions: IWorkbenchToolBarOptions = {
|
||||
hiddenItemStrategy: HiddenItemStrategy.RenderInSecondaryGroup,
|
||||
resetMenu: MenuId.NotebookToolbar,
|
||||
actionViewItemProvider: (action, options) => {
|
||||
return this._strategy.actionProvider(action, options);
|
||||
},
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
renderDropdownAsChildElement: true,
|
||||
};
|
||||
|
||||
this._notebookLeftToolbar = this.instantiationService.createInstance(
|
||||
WorkbenchToolBar,
|
||||
this._notebookTopLeftToolbarContainer,
|
||||
leftToolbarOptions
|
||||
);
|
||||
|
||||
|
||||
|
||||
this._register(this._notebookLeftToolbar);
|
||||
this._notebookLeftToolbar.context = context;
|
||||
|
||||
this._notebookRightToolbar = new ToolBar(this._notebookTopRightToolbarContainer, this.contextMenuService, {
|
||||
getKeyBinding: action => this.keybindingService.lookupKeybinding(action.id),
|
||||
actionViewItemProvider: actionProvider,
|
||||
renderDropdownAsChildElement: true
|
||||
});
|
||||
this._register(this._notebookRightToolbar);
|
||||
this._notebookRightToolbar.context = context;
|
||||
|
||||
this._showNotebookActionsinEditorToolbar();
|
||||
let dropdownIsVisible = false;
|
||||
let deferredUpdate: (() => void) | undefined;
|
||||
|
||||
this._register(this._notebookGlobalActionsMenu.onDidChange(() => {
|
||||
if (dropdownIsVisible) {
|
||||
deferredUpdate = () => this._showNotebookActionsinEditorToolbar();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.notebookEditor.isVisible) {
|
||||
this._showNotebookActionsinEditorToolbar();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this._notebookLeftToolbar.onDidChangeDropdownVisibility(visible => {
|
||||
dropdownIsVisible = visible;
|
||||
|
||||
if (deferredUpdate && !visible) {
|
||||
setTimeout(() => {
|
||||
deferredUpdate?.();
|
||||
}, 0);
|
||||
deferredUpdate = undefined;
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.notebookOptions.onDidChangeOptions(e => {
|
||||
if (e.globalToolbar !== undefined) {
|
||||
this._useGlobalToolbar = this.notebookOptions.getLayoutConfiguration().globalToolbar;
|
||||
this._showNotebookActionsinEditorToolbar();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(NotebookSetting.globalToolbarShowLabel)) {
|
||||
this._renderLabel = this._convertConfiguration(this.configurationService.getValue<RenderLabelWithFallback>(NotebookSetting.globalToolbarShowLabel));
|
||||
this._updateStrategy();
|
||||
const oldElement = this._notebookLeftToolbar.getElement();
|
||||
oldElement.parentElement?.removeChild(oldElement);
|
||||
this._notebookLeftToolbar.dispose();
|
||||
|
||||
this._notebookLeftToolbar = this.instantiationService.createInstance(
|
||||
WorkbenchToolBar,
|
||||
this._notebookTopLeftToolbarContainer,
|
||||
leftToolbarOptions
|
||||
);
|
||||
|
||||
this._register(this._notebookLeftToolbar);
|
||||
this._notebookLeftToolbar.context = context;
|
||||
this._showNotebookActionsinEditorToolbar();
|
||||
return;
|
||||
}
|
||||
}));
|
||||
|
||||
if (this.experimentService) {
|
||||
this.experimentService.getTreatment<boolean>('nbtoolbarineditor').then(treatment => {
|
||||
if (treatment === undefined) {
|
||||
return;
|
||||
}
|
||||
if (this._useGlobalToolbar !== treatment) {
|
||||
this._useGlobalToolbar = treatment;
|
||||
this._showNotebookActionsinEditorToolbar();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private _updateStrategy() {
|
||||
switch (this._renderLabel) {
|
||||
case RenderLabel.Always:
|
||||
this._strategy = new WorkbenchAlwaysLabelStrategy(this.notebookEditor, this, this.instantiationService);
|
||||
break;
|
||||
case RenderLabel.Never:
|
||||
this._strategy = new WorkbenchNeverLabelStrategy(this.notebookEditor, this, this.instantiationService);
|
||||
break;
|
||||
case RenderLabel.Dynamic:
|
||||
this._strategy = new WorkbenchAlwaysLabelStrategy(this.notebookEditor, this, this.instantiationService); // todo: defer to always, incorrect
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private _convertConfiguration(value: RenderLabelWithFallback): RenderLabel {
|
||||
switch (value) {
|
||||
case true:
|
||||
return RenderLabel.Always;
|
||||
case false:
|
||||
return RenderLabel.Never;
|
||||
case 'always':
|
||||
return RenderLabel.Always;
|
||||
case 'never':
|
||||
return RenderLabel.Never;
|
||||
case 'dynamic':
|
||||
return RenderLabel.Dynamic;
|
||||
}
|
||||
}
|
||||
|
||||
private _showNotebookActionsinEditorToolbar() {
|
||||
// when there is no view model, just ignore.
|
||||
if (!this.notebookEditor.hasModel()) {
|
||||
this._deferredActionUpdate?.dispose();
|
||||
this._deferredActionUpdate = undefined;
|
||||
this.visible = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (this._deferredActionUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this._useGlobalToolbar) {
|
||||
this.domNode.style.display = 'none';
|
||||
this._deferredActionUpdate = undefined;
|
||||
this.visible = false;
|
||||
} else {
|
||||
this._deferredActionUpdate = disposableTimeout(async () => {
|
||||
await this._setNotebookActions();
|
||||
this.visible = true;
|
||||
this._deferredActionUpdate = undefined;
|
||||
}, 50);
|
||||
}
|
||||
}
|
||||
|
||||
private async _setNotebookActions() {
|
||||
const groups = this._notebookGlobalActionsMenu.getActions({ shouldForwardArgs: true, renderShortTitle: true });
|
||||
this.domNode.style.display = 'flex';
|
||||
const primaryLeftGroups = groups.filter(group => /^navigation/.test(group[0]));
|
||||
const primaryActions: IAction[] = [];
|
||||
primaryLeftGroups.sort((a, b) => {
|
||||
if (a[0] === 'navigation') {
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (b[0] === 'navigation') {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}).forEach((group, index) => {
|
||||
primaryActions.push(...group[1]);
|
||||
if (index < primaryLeftGroups.length - 1) {
|
||||
primaryActions.push(new Separator());
|
||||
}
|
||||
});
|
||||
const primaryRightGroup = groups.find(group => /^status/.test(group[0]));
|
||||
const primaryRightActions = primaryRightGroup ? primaryRightGroup[1] : [];
|
||||
const secondaryActions = groups.filter(group => !/^navigation/.test(group[0]) && !/^status/.test(group[0])).reduce((prev: (MenuItemAction | SubmenuItemAction)[], curr) => { prev.push(...curr[1]); return prev; }, []);
|
||||
|
||||
this._notebookLeftToolbar.setActions([], []);
|
||||
|
||||
// this._primaryActions.forEach(action => action.renderLabel = true);
|
||||
this._notebookLeftToolbar.setActions(primaryActions, secondaryActions);
|
||||
this._primaryActions = primaryActions.map(action => ({
|
||||
action: action,
|
||||
size: 0,
|
||||
renderLabel: true,
|
||||
visible: true
|
||||
}));
|
||||
this._secondaryActions = secondaryActions;
|
||||
|
||||
this._notebookRightToolbar.setActions(primaryRightActions, []);
|
||||
this._secondaryActions = secondaryActions;
|
||||
// flush to make sure it can be updated later
|
||||
// this._primaryActions = [];
|
||||
|
||||
if (this._dimension && this._dimension.width >= 0 && this._dimension.height >= 0) {
|
||||
this._cacheItemSizes(this._notebookLeftToolbar);
|
||||
}
|
||||
|
||||
this._computeSizes();
|
||||
}
|
||||
|
||||
private _cacheItemSizes(toolbar: WorkbenchToolBar) {
|
||||
for (let i = 0; i < toolbar.getItemsLength(); i++) {
|
||||
const action = toolbar.getItemAction(i);
|
||||
if (action && action.id !== 'toolbar.toggle.more') {
|
||||
const existing = this._primaryActions.find(a => a.action.id === action.id);
|
||||
if (existing) {
|
||||
existing.size = toolbar.getItemWidth(i);
|
||||
existing.renderLabel = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _computeSizes() {
|
||||
const toolbar = this._notebookLeftToolbar;
|
||||
const rightToolbar = this._notebookRightToolbar;
|
||||
if (toolbar && rightToolbar && this._dimension && this._dimension.height >= 0 && this._dimension.width >= 0) {
|
||||
// compute size only if it's visible
|
||||
if (this._primaryActions.length === 0 && toolbar.getItemsLength() !== this._primaryActions.length) {
|
||||
this._cacheItemSizes(this._notebookLeftToolbar);
|
||||
}
|
||||
|
||||
if (this._primaryActions.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const kernelWidth = (rightToolbar.getItemsLength() ? rightToolbar.getItemWidth(0) : 0) + ACTION_PADDING;
|
||||
const leftToolbarContainerMaxWidth = this._dimension.width - kernelWidth - (ACTION_PADDING + TOGGLE_MORE_ACTION_WIDTH) - (/** toolbar left margin */ACTION_PADDING) - (/** toolbar right margin */ACTION_PADDING);
|
||||
const calculatedActions = this._strategy.calculateActions(leftToolbarContainerMaxWidth);
|
||||
this._notebookLeftToolbar.setActions(calculatedActions.primaryActions, calculatedActions.secondaryActions);
|
||||
}
|
||||
}
|
||||
|
||||
layout(dimension: DOM.Dimension) {
|
||||
this._dimension = dimension;
|
||||
|
||||
if (!this._useGlobalToolbar) {
|
||||
this.domNode.style.display = 'none';
|
||||
} else {
|
||||
this.domNode.style.display = 'flex';
|
||||
}
|
||||
this._computeSizes();
|
||||
}
|
||||
|
||||
override dispose() {
|
||||
this._notebookLeftToolbar.context = undefined;
|
||||
this._notebookRightToolbar.context = undefined;
|
||||
this._notebookLeftToolbar.dispose();
|
||||
this._notebookRightToolbar.dispose();
|
||||
this._notebookLeftToolbar = null!;
|
||||
this._notebookRightToolbar = null!;
|
||||
this._deferredActionUpdate?.dispose();
|
||||
this._deferredActionUpdate = undefined;
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
export function workbenchCalculateActions(initialPrimaryActions: IActionModel[], initialSecondaryActions: IAction[], leftToolbarContainerMaxWidth: number): { primaryActions: IAction[]; secondaryActions: IAction[] } {
|
||||
let currentSize = 0;
|
||||
const renderActions: IActionModel[] = [];
|
||||
const overflow: IAction[] = [];
|
||||
let containerFull = false;
|
||||
let nonZeroAction = false;
|
||||
|
||||
if (initialPrimaryActions.length === 0) {
|
||||
return { primaryActions: [], secondaryActions: initialSecondaryActions };
|
||||
}
|
||||
|
||||
for (let i = 0; i < initialPrimaryActions.length; i++) {
|
||||
const actionModel = initialPrimaryActions[i];
|
||||
const itemSize = actionModel.size;
|
||||
|
||||
// if two separators in a row, ignore the second
|
||||
if (actionModel.action instanceof Separator && renderActions.length > 0 && renderActions[renderActions.length - 1].action instanceof Separator) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// if a separator is the first nonZero action, ignore it
|
||||
if (actionModel.action instanceof Separator && !nonZeroAction) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentSize + itemSize <= leftToolbarContainerMaxWidth && !containerFull) { // if next item fits within left container width, push to rendered actions
|
||||
currentSize += ACTION_PADDING + itemSize;
|
||||
renderActions.push(actionModel);
|
||||
if (itemSize !== 0) {
|
||||
nonZeroAction = true;
|
||||
}
|
||||
} else {
|
||||
containerFull = true;
|
||||
if (itemSize === 0) { // size 0 implies a hidden item, keep in primary to allow for Workbench to handle visibility
|
||||
renderActions.push(actionModel);
|
||||
} else {
|
||||
if (actionModel.action instanceof Separator) { // never push a separator to overflow
|
||||
continue;
|
||||
}
|
||||
overflow.push(actionModel.action);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = (renderActions.length - 1); i !== 0; i--) {
|
||||
const temp = renderActions[i];
|
||||
if (temp.size === 0) {
|
||||
continue;
|
||||
}
|
||||
if (temp.action instanceof Separator) {
|
||||
renderActions.splice(i, 1);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
if (renderActions.length && renderActions[renderActions.length - 1].action instanceof Separator) {
|
||||
renderActions.pop();
|
||||
}
|
||||
|
||||
if (overflow.length !== 0) {
|
||||
overflow.push(new Separator());
|
||||
}
|
||||
|
||||
return {
|
||||
primaryActions: renderActions.map(action => action.action),
|
||||
secondaryActions: [...overflow, ...initialSecondaryActions]
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -78,7 +78,7 @@ suite('NotebookCellList focus/selection', () => {
|
|||
});
|
||||
});
|
||||
|
||||
test('notebook cell list setFocus', async function () {
|
||||
test('notebook cell list setFocus2', async function () {
|
||||
await withTestNotebook(
|
||||
[
|
||||
['var a = 1;', 'javascript', CellKind.Code, [], {}],
|
||||
|
@ -97,6 +97,7 @@ suite('NotebookCellList focus/selection', () => {
|
|||
|
||||
cellList.setSelection([1]);
|
||||
assert.deepStrictEqual(viewModel.getSelections(), [{ start: 1, end: 2 }]);
|
||||
cellList.detachViewModel();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workbenchCalculateActions } from 'vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar';
|
||||
import { Action, IAction, Separator } from 'vs/base/common/actions';
|
||||
import * as assert from 'assert';
|
||||
|
||||
interface IActionModel {
|
||||
action: IAction;
|
||||
size: number;
|
||||
visible: boolean;
|
||||
renderLabel: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the visible actions in the toolbar.
|
||||
* @param action The action to measure.
|
||||
* @param container The container the action will be placed in.
|
||||
* @returns The primary and secondary actions to be rendered
|
||||
*
|
||||
* NOTE: every action requires space for ACTION_PADDING +8 to the right.
|
||||
*
|
||||
* ex: action with size 50 requires 58px of space
|
||||
*/
|
||||
suite('workbenchCalculateActions', () => {
|
||||
test('should return empty primary and secondary actions when given empty initial actions', () => {
|
||||
const result = workbenchCalculateActions([], [], 100);
|
||||
assert.deepEqual(result.primaryActions, []);
|
||||
assert.deepEqual(result.secondaryActions, []);
|
||||
});
|
||||
|
||||
test('should return all primary actions when they fit within the container width', () => {
|
||||
const actions: IActionModel[] = [
|
||||
{ action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true },
|
||||
];
|
||||
const result = workbenchCalculateActions(actions, [], 200);
|
||||
assert.deepEqual(result.primaryActions, actions.map(action => action.action));
|
||||
assert.deepEqual(result.secondaryActions, []);
|
||||
});
|
||||
|
||||
test.skip('should move actions to secondary when they do not fit within the container width', () => {
|
||||
const actions: IActionModel[] = [
|
||||
{ action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true },
|
||||
];
|
||||
const result = workbenchCalculateActions(actions, [], 100);
|
||||
assert.deepEqual(result.primaryActions, [actions[0]].map(action => action.action));
|
||||
assert.deepEqual(result.secondaryActions, [actions[1], actions[2]].map(action => action.action));
|
||||
});
|
||||
|
||||
test('should ignore second separator when two separators are in a row', () => {
|
||||
const actions: IActionModel[] = [
|
||||
{ action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Separator(), size: 1, visible: true, renderLabel: true },
|
||||
{ action: new Separator(), size: 1, visible: true, renderLabel: true },
|
||||
{ action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true },
|
||||
];
|
||||
const result = workbenchCalculateActions(actions, [], 125);
|
||||
assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[3]].map(action => action.action));
|
||||
assert.deepEqual(result.secondaryActions, []);
|
||||
});
|
||||
|
||||
test('should ignore separators when they are at the end of the resulting primary actions', () => {
|
||||
const actions: IActionModel[] = [
|
||||
{ action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Separator(), size: 1, visible: true, renderLabel: true },
|
||||
{ action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Separator(), size: 1, visible: true, renderLabel: true },
|
||||
];
|
||||
const result = workbenchCalculateActions(actions, [], 200);
|
||||
assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[2]].map(action => action.action));
|
||||
assert.deepEqual(result.secondaryActions, []);
|
||||
});
|
||||
|
||||
test.skip('should keep actions with size 0 in primary actions', () => {
|
||||
const actions: IActionModel[] = [
|
||||
{ action: new Action('action0', 'Action 0'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Action('action2', 'Action 2'), size: 50, visible: true, renderLabel: true },
|
||||
{ action: new Action('action3', 'Action 3'), size: 0, visible: true, renderLabel: true },
|
||||
];
|
||||
const result = workbenchCalculateActions(actions, [], 116);
|
||||
assert.deepEqual(result.primaryActions, [actions[0], actions[1], actions[3]].map(action => action.action));
|
||||
assert.deepEqual(result.secondaryActions, [actions[2]].map(action => action.action));
|
||||
});
|
||||
|
||||
test('should not render separator if preceeded by size 0 action(s).', () => {
|
||||
const actions: IActionModel[] = [
|
||||
{ action: new Action('action0', 'Action 0'), size: 0, visible: true, renderLabel: true },
|
||||
{ action: new Separator(), size: 1, visible: true, renderLabel: true },
|
||||
{ action: new Action('action1', 'Action 1'), size: 50, visible: true, renderLabel: true },
|
||||
];
|
||||
const result = workbenchCalculateActions(actions, [], 116);
|
||||
assert.deepEqual(result.primaryActions, [actions[0], actions[2]].map(action => action.action));
|
||||
assert.deepEqual(result.secondaryActions, []);
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue