Merge pull request #128022 from microsoft/aweinand/splitRunDropdown

A run & debug dropdown with default button
This commit is contained in:
Andre Weinand 2021-07-06 11:56:16 +02:00 committed by GitHub
commit 3918b3e0fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 140 additions and 7 deletions

View file

@ -4,21 +4,25 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./menuEntryActionViewItem';
import { addDisposableListener, asCSSUrl, ModifierKeyEmitter } from 'vs/base/browser/dom';
import { IAction, Separator, SubmenuAction } from 'vs/base/common/actions';
import { addDisposableListener, asCSSUrl, ModifierKeyEmitter, append, EventType, $ } from 'vs/base/browser/dom';
import { IAction, IRunEvent, Separator, SubmenuAction } from 'vs/base/common/actions';
import { IDisposable, toDisposable, MutableDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { localize } from 'vs/nls';
import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon } from 'vs/platform/actions/common/actions';
import { ICommandAction, IMenu, IMenuActionOptions, MenuItemAction, SubmenuItemAction, Icon, IMenuService } from 'vs/platform/actions/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { UILabelProvider } from 'vs/base/common/keybindingLabels';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { ActionViewItem, BaseActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { isWindows, isLinux, OS } from 'vs/base/common/platform';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Event } from 'vs/base/common/event';
import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
export function createAndFillInContextMenuActions(menu: IMenu, options: IMenuActionOptions | undefined, target: IAction[] | { primary: IAction[]; secondary: IAction[]; }, primaryGroup?: string): IDisposable {
const groups = menu.getActions(options);
@ -304,14 +308,142 @@ export class SubmenuEntryActionViewItem extends DropdownMenuActionViewItem {
}
}
class DropdownWithDefaultActionViewItem extends BaseActionViewItem {
private _defaultAction: ActionViewItem;
private _dropdown: DropdownMenuActionViewItem;
private _container: HTMLElement | null = null;
private _storageKey: string;
get onDidChangeDropdownVisibility(): Event<boolean> {
return this._dropdown.onDidChangeVisibility;
}
constructor(
submenuAction: SubmenuItemAction,
@IKeybindingService protected readonly _keybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService,
@IContextMenuService protected _contextMenuService: IContextMenuService,
@IMenuService protected _menuService: IMenuService,
@IInstantiationService protected _instaService: IInstantiationService,
@IStorageService protected _storageService: IStorageService
) {
super(null, submenuAction);
this._storageKey = `${submenuAction.item.submenu._debugName}_lastActionId`;
// determine default action
let defaultAction: IAction | undefined;
let defaultActionId = _storageService.get(this._storageKey, StorageScope.WORKSPACE);
if (defaultActionId) {
defaultAction = submenuAction.actions.find(a => defaultActionId === a.id);
}
if (!defaultAction) {
defaultAction = submenuAction.actions[0];
}
this._defaultAction = this._instaService.createInstance(MenuEntryActionViewItem, <MenuItemAction>defaultAction, undefined);
this._dropdown = new DropdownMenuActionViewItem(submenuAction, submenuAction.actions, this._contextMenuService, {
menuAsChild: true,
classNames: ['codicon', 'codicon-chevron-down']
});
this._dropdown.actionRunner.onDidRun((e: IRunEvent) => {
if (e.action instanceof MenuItemAction) {
this.update(e.action);
}
});
}
private update(lastAction: MenuItemAction): void {
this._storageService.store(this._storageKey, lastAction.id, StorageScope.WORKSPACE, StorageTarget.USER);
this._defaultAction.dispose();
this._defaultAction = this._instaService.createInstance(MenuEntryActionViewItem, lastAction, undefined);
if (this._container) {
this.render(this._container);
}
}
override setActionContext(newContext: unknown): void {
super.setActionContext(newContext);
this._defaultAction.setActionContext(newContext);
this._dropdown.setActionContext(newContext);
}
override render(container: HTMLElement): void {
this._container = container;
super.render(this._container);
this._container.classList.add('monaco-dropdown-with-primary');
const primaryContainer = $('.action-container');
this._defaultAction.render(append(this._container, primaryContainer));
this._register(addDisposableListener(primaryContainer, EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.RightArrow)) {
this._defaultAction.element!.tabIndex = -1;
this._dropdown.focus();
event.stopPropagation();
}
}));
const dropdownContainer = $('.dropdown-action-container');
this._dropdown.render(append(this._container, dropdownContainer));
this._register(addDisposableListener(dropdownContainer, EventType.KEY_DOWN, (e: KeyboardEvent) => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.LeftArrow)) {
this._defaultAction.element!.tabIndex = 0;
this._dropdown.setFocusable(false);
this._defaultAction.element?.focus();
event.stopPropagation();
}
}));
}
override focus(fromRight?: boolean): void {
if (fromRight) {
this._dropdown.focus();
} else {
this._defaultAction.element!.tabIndex = 0;
this._defaultAction.element!.focus();
}
}
override blur(): void {
this._defaultAction.element!.tabIndex = -1;
this._dropdown.blur();
this._container!.blur();
}
override setFocusable(focusable: boolean): void {
if (focusable) {
this._defaultAction.element!.tabIndex = 0;
} else {
this._defaultAction.element!.tabIndex = -1;
this._dropdown.setFocusable(false);
}
}
override dispose() {
this._defaultAction.dispose();
this._dropdown.dispose();
super.dispose();
}
}
/**
* Creates action view items for menu actions or submenu actions.
*/
export function createActionViewItem(instaService: IInstantiationService, action: IAction): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem {
export function createActionViewItem(instaService: IInstantiationService, action: IAction): undefined | MenuEntryActionViewItem | SubmenuEntryActionViewItem | BaseActionViewItem {
if (action instanceof MenuItemAction) {
return instaService.createInstance(MenuEntryActionViewItem, action, undefined);
} else if (action instanceof SubmenuItemAction) {
return instaService.createInstance(SubmenuEntryActionViewItem, action);
if (action.item.rememberDefaultAction) {
return instaService.createInstance(DropdownWithDefaultActionViewItem, action);
} else {
return instaService.createInstance(SubmenuEntryActionViewItem, action);
}
} else {
return undefined;
}

View file

@ -66,6 +66,7 @@ export interface ISubmenuItem {
when?: ContextKeyExpression;
group?: 'navigation' | string;
order?: number;
rememberDefaultAction?: boolean; // for dropdown menu: if true the last executed action is remembered as the default action
}
export function isIMenuItem(item: IMenuItem | ISubmenuItem): item is IMenuItem {

View file

@ -320,7 +320,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: SHOW_EDITORS_IN
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeAll', "Close All") }, group: '5_close', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: CLOSE_SAVED_EDITORS_COMMAND_ID, title: localize('closeAllSaved', "Close Saved") }, group: '5_close', order: 20 });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { command: { id: TOGGLE_KEEP_EDITORS_COMMAND_ID, title: localize('toggleKeepEditors', "Keep Editors Open"), toggled: ContextKeyExpr.not('config.workbench.editor.enablePreview') }, group: '7_settings', order: 10 });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, title: { value: localize('run', "Run"), original: 'Run', }, icon: Codicon.run, group: 'navigation', order: -1 });
MenuRegistry.appendMenuItem(MenuId.EditorTitle, { submenu: MenuId.EditorTitleRun, rememberDefaultAction: true, title: { value: localize('run', "Run or Debug..."), original: 'Run or Debug...', }, icon: Codicon.run, group: 'navigation', order: -1 });
interface IEditorToolItem { id: string; title: string; icon?: { dark?: URI; light?: URI; } | ThemeIcon; }