From 97fc588e65bedcb1113baeddd2f67237e52c8c63 Mon Sep 17 00:00:00 2001 From: Johannes Rieken Date: Sat, 11 Jul 2020 11:48:43 +0200 Subject: [PATCH] use Action2 for call hierarchy title commands --- src/vs/editor/browser/editorExtensions.ts | 28 +++- src/vs/editor/contrib/peekView/peekView.ts | 12 +- .../keybinding/common/keybindingsRegistry.ts | 2 +- .../browser/callHierarchy.contribution.ts | 128 ++++++++++++------ .../browser/callHierarchyPeek.ts | 60 ++++---- .../callHierarchy/common/callHierarchy.ts | 4 +- 6 files changed, 153 insertions(+), 81 deletions(-) diff --git a/src/vs/editor/browser/editorExtensions.ts b/src/vs/editor/browser/editorExtensions.ts index 50a7f678d5a..9c113e6d3c8 100644 --- a/src/vs/editor/browser/editorExtensions.ts +++ b/src/vs/editor/browser/editorExtensions.ts @@ -14,7 +14,7 @@ import { IEditorContribution, IDiffEditorContribution } from 'vs/editor/common/e import { ITextModel } from 'vs/editor/common/model'; import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions'; +import { MenuId, MenuRegistry, Action2 } from 'vs/platform/actions/common/actions'; import { CommandsRegistry, ICommandHandlerDescription } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr, IContextKeyService, ContextKeyExpression } from 'vs/platform/contextkey/common/contextkey'; import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; @@ -339,6 +339,32 @@ export abstract class EditorAction extends EditorCommand { //#endregion EditorAction +//#region EditorAction2 + +export abstract class EditorAction2 extends Action2 { + + run(accessor: ServicesAccessor, ...args: any[]) { + // Find the editor with text focus or active + const codeEditorService = accessor.get(ICodeEditorService); + const editor = codeEditorService.getFocusedCodeEditor() || codeEditorService.getActiveCodeEditor(); + if (!editor) { + // well, at least we tried... + return; + } + // precondition does hold + return editor.invokeWithinContext((editorAccessor) => { + const kbService = editorAccessor.get(IContextKeyService); + if (kbService.contextMatchesRules(withNullAsUndefined(this.desc.precondition))) { + return this.runEditorCommand(editorAccessor, editor!, args); + } + }); + } + + abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, ...args: any[]): any; +} + +//#endregion + // --- Registration of commands and actions export function registerLanguageCommand(id: string, handler: (accessor: ServicesAccessor, args: Args) => any) { diff --git a/src/vs/editor/contrib/peekView/peekView.ts b/src/vs/editor/contrib/peekView/peekView.ts index 2c4fdbc21c5..6a5bd94648f 100644 --- a/src/vs/editor/contrib/peekView/peekView.ts +++ b/src/vs/editor/contrib/peekView/peekView.ts @@ -169,7 +169,7 @@ export abstract class PeekViewWidget extends ZoneWidget { container.appendChild(this._bodyElement); } - protected _fillHead(container: HTMLElement): void { + protected _fillHead(container: HTMLElement, noCloseAction?: boolean): void { const titleElement = dom.$('.peekview-title'); dom.append(this._headElement!, titleElement); dom.addStandardDisposableListener(titleElement, 'click', event => this._onTitleClick(event)); @@ -187,10 +187,12 @@ export abstract class PeekViewWidget extends ZoneWidget { this._actionbarWidget = new ActionBar(actionsContainer, actionBarOptions); this._disposables.add(this._actionbarWidget); - this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), Codicon.close.classNames, true, () => { - this.dispose(); - return Promise.resolve(); - }), { label: false, icon: true }); + if (!noCloseAction) { + this._actionbarWidget.push(new Action('peekview.close', nls.localize('label.close', "Close"), Codicon.close.classNames, true, () => { + this.dispose(); + return Promise.resolve(); + }), { label: false, icon: true }); + } } protected _fillTitleIcon(container: HTMLElement): void { diff --git a/src/vs/platform/keybinding/common/keybindingsRegistry.ts b/src/vs/platform/keybinding/common/keybindingsRegistry.ts index 6f249867f8e..b3901f68cd4 100644 --- a/src/vs/platform/keybinding/common/keybindingsRegistry.ts +++ b/src/vs/platform/keybinding/common/keybindingsRegistry.ts @@ -39,7 +39,7 @@ export interface IKeybindingRule extends IKeybindings { id: string; weight: number; args?: any; - when: ContextKeyExpression | null | undefined; + when?: ContextKeyExpression | null | undefined; } export interface IKeybindingRule2 { diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts index 6ffd11e782c..c32da9ad715 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchy.contribution.ts @@ -9,7 +9,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { CallHierarchyTreePeekWidget } from 'vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek'; import { Event } from 'vs/base/common/event'; -import { registerEditorContribution, registerEditorAction, EditorAction, registerEditorCommand, EditorCommand } from 'vs/editor/browser/editorExtensions'; +import { registerEditorContribution, EditorAction2 } from 'vs/editor/browser/editorExtensions'; import { IEditorContribution } from 'vs/editor/common/editorCommon'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { IContextKeyService, RawContextKey, IContextKey, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -22,10 +22,18 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Range } from 'vs/editor/common/core/range'; import { IPosition } from 'vs/editor/common/core/position'; -import { MenuId } from 'vs/platform/actions/common/actions'; +import { MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; +import { registerIcon, Codicon } from 'vs/base/common/codicons'; const _ctxHasCallHierarchyProvider = new RawContextKey('editorHasCallHierarchyProvider', false); const _ctxCallHierarchyVisible = new RawContextKey('callHierarchyVisible', false); +const _ctxCallHierarchyDirection = new RawContextKey('callHierarchyDirection', undefined); + +function sanitizedDirection(candidate: string): CallHierarchyDirection { + return candidate === CallHierarchyDirection.CallsFrom || candidate === CallHierarchyDirection.CallsTo + ? candidate + : CallHierarchyDirection.CallsTo; +} class CallHierarchyController implements IEditorContribution { @@ -39,6 +47,7 @@ class CallHierarchyController implements IEditorContribution { private readonly _ctxHasProvider: IContextKey; private readonly _ctxIsVisible: IContextKey; + private readonly _ctxDirection: IContextKey; private readonly _dispoables = new DisposableStore(); private readonly _sessionDisposables = new DisposableStore(); @@ -53,6 +62,7 @@ class CallHierarchyController implements IEditorContribution { ) { this._ctxIsVisible = _ctxCallHierarchyVisible.bindTo(this._contextKeyService); this._ctxHasProvider = _ctxHasCallHierarchyProvider.bindTo(this._contextKeyService); + this._ctxDirection = _ctxCallHierarchyDirection.bindTo(this._contextKeyService); this._dispoables.add(Event.any(_editor.onDidChangeModel, _editor.onDidChangeModelLanguage, CallHierarchyProviderRegistry.onDidChange)(() => { this._ctxHasProvider.set(_editor.hasModel() && CallHierarchyProviderRegistry.has(_editor.getModel())); })); @@ -80,7 +90,7 @@ class CallHierarchyController implements IEditorContribution { const cts = new CancellationTokenSource(); const model = CallHierarchyModel.create(document, position, cts.token); - const direction = this._storageService.getNumber(CallHierarchyController._StorageDirection, StorageScope.GLOBAL, CallHierarchyDirection.CallsFrom); + const direction = sanitizedDirection(this._storageService.get(CallHierarchyController._StorageDirection, StorageScope.GLOBAL, CallHierarchyDirection.CallsTo)); this._showCallHierarchyWidget(position, direction, model, cts); } @@ -109,12 +119,13 @@ class CallHierarchyController implements IEditorContribution { ); } - private _showCallHierarchyWidget(position: IPosition, direction: number, model: Promise, cts: CancellationTokenSource) { + private _showCallHierarchyWidget(position: IPosition, direction: CallHierarchyDirection, model: Promise, cts: CancellationTokenSource) { + this._ctxIsVisible.set(true); + this._ctxDirection.set(direction); Event.any(this._editor.onDidChangeModel, this._editor.onDidChangeModelLanguage)(this.endCallHierarchy, this, this._sessionDisposables); this._widget = this._instantiationService.createInstance(CallHierarchyTreePeekWidget, this._editor, position, direction); this._widget.showLoading(); - this._ctxIsVisible.set(true); this._sessionDisposables.add(this._widget.onDidClose(() => { this.endCallHierarchy(); this._storageService.store(CallHierarchyController._StorageDirection, this._widget!.direction, StorageScope.GLOBAL); @@ -139,10 +150,14 @@ class CallHierarchyController implements IEditorContribution { }); } - toggleCallHierarchyDirection(): void { - if (this._widget) { - this._widget.toggleDirection(); - } + showOutgoingCalls(): void { + this._widget?.updateDirection(CallHierarchyDirection.CallsFrom); + this._ctxDirection.set(CallHierarchyDirection.CallsFrom); + } + + showIncomingCalls(): void { + this._widget?.updateDirection(CallHierarchyDirection.CallsTo); + this._ctxDirection.set(CallHierarchyDirection.CallsTo); } endCallHierarchy(): void { @@ -154,20 +169,19 @@ class CallHierarchyController implements IEditorContribution { registerEditorContribution(CallHierarchyController.Id, CallHierarchyController); -registerEditorAction(class extends EditorAction { +registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.showCallHierarchy', - label: localize('title', "Peek Call Hierarchy"), - alias: 'Peek Call Hierarchy', - contextMenuOpts: { - menuId: MenuId.EditorContextPeek, + title: { value: localize('title', "Peek Call Hierarchy"), original: 'Peek Call Hierarchy' }, + menu: { + id: MenuId.EditorContextPeek, group: 'navigation', order: 1000 }, - kbOpts: { - kbExpr: EditorContextKeys.editorTextFocus, + keybinding: { + when: EditorContextKeys.editorTextFocus, weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H }, @@ -178,65 +192,101 @@ registerEditorAction(class extends EditorAction { }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { return CallHierarchyController.get(editor).startCallHierarchyFromEditor(); } }); -registerEditorAction(class extends EditorAction { +registerAction2(class extends EditorAction2 { constructor() { super({ - id: 'editor.toggleCallHierarchy', - label: localize('title.toggle', "Toggle Call Hierarchy"), - alias: 'Toggle Call Hierarchy', - kbOpts: { + id: 'editor.showIncomingCalls', + title: { value: localize('title.incoming', "Show Incoming Calls"), original: 'Show Incoming Calls' }, + icon: registerIcon('callhierarchy-incoming', Codicon.callIncoming), + precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom)), + keybinding: { weight: KeybindingWeight.WorkbenchContrib, - primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H + primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H, }, - precondition: _ctxCallHierarchyVisible + menu: { + id: CallHierarchyTreePeekWidget.TitleMenu, + when: _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsFrom), + order: 1, + } }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { - return CallHierarchyController.get(editor).toggleCallHierarchyDirection(); + runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { + return CallHierarchyController.get(editor).showIncomingCalls(); } }); -registerEditorAction(class extends EditorAction { +registerAction2(class extends EditorAction2 { + + constructor() { + super({ + id: 'editor.showOutgoingCalls', + title: { value: localize('title.outgoing', "Show Outgoing Calls"), original: 'Show Outgoing Calls' }, + icon: registerIcon('callhierarchy-outgoing', Codicon.callOutgoing), + precondition: ContextKeyExpr.and(_ctxCallHierarchyVisible, _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo)), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib, + primary: KeyMod.Shift + KeyMod.Alt + KeyCode.KEY_H, + }, + menu: { + id: CallHierarchyTreePeekWidget.TitleMenu, + when: _ctxCallHierarchyDirection.isEqualTo(CallHierarchyDirection.CallsTo), + order: 1 + } + }); + } + + runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor) { + return CallHierarchyController.get(editor).showOutgoingCalls(); + } +}); + + +registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.refocusCallHierarchy', - label: localize('title.refocus', "Refocus Call Hierarchy"), - alias: 'Refocus Call Hierarchy', - kbOpts: { + title: { value: localize('title.refocus', "Refocus Call Hierarchy"), original: 'Refocus Call Hierarchy' }, + precondition: _ctxCallHierarchyVisible, + keybinding: { weight: KeybindingWeight.WorkbenchContrib, primary: KeyMod.Shift + KeyCode.Enter - }, - precondition: _ctxCallHierarchyVisible + } }); } - async run(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { + async runEditorCommand(_accessor: ServicesAccessor, editor: ICodeEditor): Promise { return CallHierarchyController.get(editor).startCallHierarchyFromCallHierarchy(); } }); -registerEditorCommand(new class extends EditorCommand { +registerAction2(class extends EditorAction2 { constructor() { super({ id: 'editor.closeCallHierarchy', - kbOpts: { - weight: KeybindingWeight.WorkbenchContrib + 10, - primary: KeyCode.Escape - }, + title: localize('close', 'Close'), + icon: Codicon.close, precondition: ContextKeyExpr.and( _ctxCallHierarchyVisible, ContextKeyExpr.not('config.editor.stablePeek') - ) + ), + keybinding: { + weight: KeybindingWeight.WorkbenchContrib + 10, + primary: KeyCode.Escape + }, + menu: { + id: CallHierarchyTreePeekWidget.TitleMenu, + order: 1000 + } }); } diff --git a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts index e722340f27a..fde0bb6bc1a 100644 --- a/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts +++ b/src/vs/workbench/contrib/callHierarchy/browser/callHierarchyPeek.ts @@ -26,12 +26,15 @@ import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions, OverviewRulerLane } from 'vs/editor/common/model'; import { registerThemingParticipant, themeColorFromId, IThemeService, IColorTheme } from 'vs/platform/theme/common/themeService'; import { IPosition } from 'vs/editor/common/core/position'; -import { Action } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IActionBarOptions, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; import { Color } from 'vs/base/common/color'; import { TreeMouseEventTarget, ITreeNode } from 'vs/base/browser/ui/tree/tree'; import { URI } from 'vs/base/common/uri'; +import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createAndFillInActionBarActions, MenuEntryActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; const enum State { Loading = 'loading', @@ -39,27 +42,6 @@ const enum State { Data = 'data' } -class ChangeHierarchyDirectionAction extends Action { - - constructor(getDirection: () => CallHierarchyDirection, toggleDirection: () => void) { - super('', undefined, '', true, () => { - toggleDirection(); - update(); - return Promise.resolve(); - }); - const update = () => { - if (getDirection() === CallHierarchyDirection.CallsFrom) { - this.label = localize('toggle.from', "Show Incoming Calls"); - this.class = 'codicon codicon-call-incoming'; - } else { - this.label = localize('toggle.to', "Showing Outgoing Calls"); - this.class = 'codicon codicon-call-outgoing'; - } - }; - update(); - } -} - class LayoutInfo { static store(info: LayoutInfo, storageService: IStorageService): void { @@ -86,7 +68,8 @@ class CallHierarchyTree extends WorkbenchAsyncDataTree { + const actions: IAction[] = []; + createAndFillInActionBarActions(menu, undefined, actions); + this._actionbarWidget!.clear(); + this._actionbarWidget!.push(actions, { label: false, icon: true }); + }; + this._disposables.add(menu); + this._disposables.add(menu.onDidChange(updateToolbar)); + updateToolbar(); + } + protected _getActionBarOptions(): IActionBarOptions { return { - orientation: ActionsOrientation.HORIZONTAL_REVERSE + orientation: ActionsOrientation.HORIZONTAL, + actionViewItemProvider: action => action instanceof MenuItemAction ? this._instantiationService.createInstance(MenuEntryActionViewItem, action) : undefined }; } @@ -391,12 +392,6 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { this._tree.domFocus(); this._updatePreview(); } - - if (!this._changeDirectionAction) { - this._changeDirectionAction = new ChangeHierarchyDirectionAction(() => this._direction, () => this.toggleDirection()); - this._disposables.add(this._changeDirectionAction); - this._actionbarWidget!.push(this._changeDirectionAction, { icon: true, label: false }); - } } getModel(): CallHierarchyModel | undefined { @@ -407,10 +402,9 @@ export class CallHierarchyTreePeekWidget extends peekView.PeekViewWidget { return this._tree.getFocus()[0]; } - async toggleDirection(): Promise { + async updateDirection(newDirection: CallHierarchyDirection): Promise { const model = this._tree.getInput(); - if (model) { - const newDirection = this._direction === CallHierarchyDirection.CallsTo ? CallHierarchyDirection.CallsFrom : CallHierarchyDirection.CallsTo; + if (model && newDirection !== this._direction) { this._treeViewStates.set(this._direction, this._tree.getViewState()); this._direction = newDirection; await this.showModel(model); diff --git a/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts index 28a29548511..e895bd00e60 100644 --- a/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts +++ b/src/vs/workbench/contrib/callHierarchy/common/callHierarchy.ts @@ -19,8 +19,8 @@ import { IModelService } from 'vs/editor/common/services/modelService'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; export const enum CallHierarchyDirection { - CallsTo = 1, - CallsFrom = 2 + CallsTo = 'incomingCalls', + CallsFrom = 'outgoingCalls' } export interface CallHierarchyItem {