mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-22 21:00:30 -04:00
Vscode merge (#4582)
* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd * fix issues with merges * bump node version in azpipe * replace license headers * remove duplicate launch task * fix build errors * fix build errors * fix tslint issues * working through package and linux build issues * more work * wip * fix packaged builds * working through linux build errors * wip * wip * wip * fix mac and linux file limits * iterate linux pipeline * disable editor typing * revert series to parallel * remove optimize vscode from linux * fix linting issues * revert testing change * add work round for new node * readd packaging for extensions * fix issue with angular not resolving decorator dependencies
This commit is contained in:
583
src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts
Normal file
583
src/vs/workbench/contrib/quickopen/browser/commandsHandler.ts
Normal file
@@ -0,0 +1,583 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { Language } from 'vs/base/common/platform';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Mode, IEntryRunContext, IAutoFocus, IModel, IQuickNavigateConfiguration } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenEntryGroup, IHighlight, QuickOpenModel, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { QuickOpenHandler, IWorkbenchQuickOpenConfiguration } from 'vs/workbench/browser/quickopen';
|
||||
import { IEditorAction } from 'vs/editor/common/editorCommon';
|
||||
import { matchesWords, matchesPrefix, matchesContiguousSubString, or } from 'vs/base/common/filters';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { registerEditorAction, EditorAction, IEditorCommandMenuOptions } from 'vs/editor/browser/editorExtensions';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { LRUCache } from 'vs/base/common/map';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isPromiseCanceledError } from 'vs/base/common/errors';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
export const ALL_COMMANDS_PREFIX = '>';
|
||||
|
||||
let lastCommandPaletteInput: string;
|
||||
let commandHistory: LRUCache<string, number>;
|
||||
let commandCounter = 1;
|
||||
|
||||
interface ISerializedCommandHistory {
|
||||
usesLRU?: boolean;
|
||||
entries: { key: string; value: number }[];
|
||||
}
|
||||
|
||||
function resolveCommandHistory(configurationService: IConfigurationService): number {
|
||||
const config = <IWorkbenchQuickOpenConfiguration>configurationService.getValue();
|
||||
|
||||
let commandHistory = config.workbench && config.workbench.commandPalette && config.workbench.commandPalette.history;
|
||||
if (typeof commandHistory !== 'number') {
|
||||
commandHistory = CommandsHistory.DEFAULT_COMMANDS_HISTORY_LENGTH;
|
||||
}
|
||||
|
||||
return commandHistory;
|
||||
}
|
||||
|
||||
class CommandsHistory extends Disposable {
|
||||
|
||||
static readonly DEFAULT_COMMANDS_HISTORY_LENGTH = 50;
|
||||
|
||||
private static readonly PREF_KEY_CACHE = 'commandPalette.mru.cache';
|
||||
private static readonly PREF_KEY_COUNTER = 'commandPalette.mru.counter';
|
||||
|
||||
private commandHistoryLength: number;
|
||||
|
||||
constructor(
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.updateConfiguration();
|
||||
this.load();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration()));
|
||||
this._register(this.storageService.onWillSaveState(() => this.saveState()));
|
||||
}
|
||||
|
||||
private updateConfiguration(): void {
|
||||
this.commandHistoryLength = resolveCommandHistory(this.configurationService);
|
||||
|
||||
if (commandHistory) {
|
||||
commandHistory.limit = this.commandHistoryLength;
|
||||
}
|
||||
}
|
||||
|
||||
private load(): void {
|
||||
const raw = this.storageService.get(CommandsHistory.PREF_KEY_CACHE, StorageScope.GLOBAL);
|
||||
let serializedCache: ISerializedCommandHistory | undefined;
|
||||
if (raw) {
|
||||
try {
|
||||
serializedCache = JSON.parse(raw);
|
||||
} catch (error) {
|
||||
// invalid data
|
||||
}
|
||||
}
|
||||
|
||||
commandHistory = new LRUCache<string, number>(this.commandHistoryLength, 1);
|
||||
if (serializedCache) {
|
||||
let entries: { key: string; value: number }[];
|
||||
if (serializedCache.usesLRU) {
|
||||
entries = serializedCache.entries;
|
||||
} else {
|
||||
entries = serializedCache.entries.sort((a, b) => a.value - b.value);
|
||||
}
|
||||
entries.forEach(entry => commandHistory.set(entry.key, entry.value));
|
||||
}
|
||||
|
||||
commandCounter = this.storageService.getNumber(CommandsHistory.PREF_KEY_COUNTER, StorageScope.GLOBAL, commandCounter);
|
||||
}
|
||||
|
||||
push(commandId: string): void {
|
||||
commandHistory.set(commandId, commandCounter++); // set counter to command
|
||||
}
|
||||
|
||||
peek(commandId: string): number | undefined {
|
||||
return commandHistory.peek(commandId);
|
||||
}
|
||||
|
||||
private saveState(): void {
|
||||
const serializedCache: ISerializedCommandHistory = { usesLRU: true, entries: [] };
|
||||
commandHistory.forEach((value, key) => serializedCache.entries.push({ key, value }));
|
||||
|
||||
this.storageService.store(CommandsHistory.PREF_KEY_CACHE, JSON.stringify(serializedCache), StorageScope.GLOBAL);
|
||||
this.storageService.store(CommandsHistory.PREF_KEY_COUNTER, commandCounter, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
export class ShowAllCommandsAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.showCommands';
|
||||
static readonly LABEL = nls.localize('showTriggerActions', "Show All Commands");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: any): Promise<void> {
|
||||
const config = <IWorkbenchQuickOpenConfiguration>this.configurationService.getValue();
|
||||
const restoreInput = config.workbench && config.workbench.commandPalette && config.workbench.commandPalette.preserveInput === true;
|
||||
|
||||
// Show with last command palette input if any and configured
|
||||
let value = ALL_COMMANDS_PREFIX;
|
||||
if (restoreInput && lastCommandPaletteInput) {
|
||||
value = `${value}${lastCommandPaletteInput}`;
|
||||
}
|
||||
|
||||
this.quickOpenService.show(value, { inputSelection: lastCommandPaletteInput ? { start: 1 /* after prefix */, end: value.length } : undefined });
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearCommandHistoryAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.clearCommandHistory';
|
||||
static readonly LABEL = nls.localize('clearCommandHistory', "Clear Command History");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(context?: any): Promise<void> {
|
||||
const commandHistoryLength = resolveCommandHistory(this.configurationService);
|
||||
if (commandHistoryLength > 0) {
|
||||
commandHistory = new LRUCache<string, number>(commandHistoryLength);
|
||||
commandCounter = 1;
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
class CommandPaletteEditorAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: ShowAllCommandsAction.ID,
|
||||
label: nls.localize('showCommands.label', "Command Palette..."),
|
||||
alias: 'Command Palette',
|
||||
precondition: null,
|
||||
menuOpts: {
|
||||
group: 'z_commands',
|
||||
order: 1
|
||||
} as IEditorCommandMenuOptions
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const quickOpenService = accessor.get(IQuickOpenService);
|
||||
|
||||
// Show with prefix
|
||||
quickOpenService.show(ALL_COMMANDS_PREFIX);
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class BaseCommandEntry extends QuickOpenEntryGroup {
|
||||
private description: string;
|
||||
private alias: string;
|
||||
private labelLowercase: string;
|
||||
private readonly keybindingAriaLabel?: string;
|
||||
|
||||
constructor(
|
||||
private commandId: string,
|
||||
private keybinding: ResolvedKeybinding,
|
||||
private label: string,
|
||||
alias: string,
|
||||
highlights: { label: IHighlight[], alias?: IHighlight[] },
|
||||
private onBeforeRun: (commandId: string) => void,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@ITelemetryService protected telemetryService: ITelemetryService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.labelLowercase = this.label.toLowerCase();
|
||||
this.keybindingAriaLabel = keybinding ? keybinding.getAriaLabel() || undefined : undefined;
|
||||
|
||||
if (this.label !== alias) {
|
||||
this.alias = alias;
|
||||
} else {
|
||||
highlights.alias = undefined;
|
||||
}
|
||||
|
||||
this.setHighlights(highlights.label, undefined, highlights.alias);
|
||||
}
|
||||
|
||||
getCommandId(): string {
|
||||
return this.commandId;
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
getSortLabel(): string {
|
||||
return this.labelLowercase;
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
setDescription(description: string): void {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
getKeybinding(): ResolvedKeybinding {
|
||||
return this.keybinding;
|
||||
}
|
||||
|
||||
getDetail(): string {
|
||||
return this.alias;
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
if (this.keybindingAriaLabel) {
|
||||
return nls.localize('entryAriaLabelWithKey', "{0}, {1}, commands", this.getLabel(), this.keybindingAriaLabel);
|
||||
}
|
||||
|
||||
return nls.localize('entryAriaLabel', "{0}, commands", this.getLabel());
|
||||
}
|
||||
|
||||
run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
this.runAction(this.getAction());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected abstract getAction(): Action | IEditorAction;
|
||||
|
||||
protected runAction(action: Action | IEditorAction): void {
|
||||
|
||||
// Indicate onBeforeRun
|
||||
this.onBeforeRun(this.commandId);
|
||||
|
||||
// Use a timeout to give the quick open widget a chance to close itself first
|
||||
setTimeout(() => {
|
||||
if (action && (!(action instanceof Action) || action.enabled)) {
|
||||
try {
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: action.id, from: 'quick open' });
|
||||
(action.run() || Promise.resolve()).then(() => {
|
||||
if (action instanceof Action) {
|
||||
action.dispose();
|
||||
}
|
||||
}, err => this.onError(err));
|
||||
} catch (error) {
|
||||
this.onError(error);
|
||||
}
|
||||
} else {
|
||||
this.notificationService.info(nls.localize('actionNotEnabled', "Command '{0}' is not enabled in the current context.", this.getLabel()));
|
||||
}
|
||||
}, 50);
|
||||
}
|
||||
|
||||
private onError(error?: Error): void {
|
||||
if (isPromiseCanceledError(error)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.notificationService.error(error || nls.localize('canNotRun', "Command '{0}' resulted in an error.", this.label));
|
||||
}
|
||||
}
|
||||
|
||||
class EditorActionCommandEntry extends BaseCommandEntry {
|
||||
|
||||
constructor(
|
||||
commandId: string,
|
||||
keybinding: ResolvedKeybinding,
|
||||
label: string,
|
||||
meta: string,
|
||||
highlights: { label: IHighlight[], alias: IHighlight[] },
|
||||
private action: IEditorAction,
|
||||
onBeforeRun: (commandId: string) => void,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
super(commandId, keybinding, label, meta, highlights, onBeforeRun, notificationService, telemetryService);
|
||||
}
|
||||
|
||||
protected getAction(): Action | IEditorAction {
|
||||
return this.action;
|
||||
}
|
||||
}
|
||||
|
||||
class ActionCommandEntry extends BaseCommandEntry {
|
||||
|
||||
constructor(
|
||||
commandId: string,
|
||||
keybinding: ResolvedKeybinding,
|
||||
label: string,
|
||||
alias: string,
|
||||
highlights: { label: IHighlight[], alias: IHighlight[] },
|
||||
private action: Action,
|
||||
onBeforeRun: (commandId: string) => void,
|
||||
@INotificationService notificationService: INotificationService,
|
||||
@ITelemetryService telemetryService: ITelemetryService
|
||||
) {
|
||||
super(commandId, keybinding, label, alias, highlights, onBeforeRun, notificationService, telemetryService);
|
||||
}
|
||||
|
||||
protected getAction(): Action | IEditorAction {
|
||||
return this.action;
|
||||
}
|
||||
}
|
||||
|
||||
const wordFilter = or(matchesPrefix, matchesWords, matchesContiguousSubString);
|
||||
|
||||
export class CommandsHandler extends QuickOpenHandler {
|
||||
|
||||
static readonly ID = 'workbench.picker.commands';
|
||||
|
||||
private commandHistoryEnabled: boolean;
|
||||
private commandsHistory: CommandsHistory;
|
||||
private extensionsRegistered: boolean;
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.commandsHistory = this.instantiationService.createInstance(CommandsHistory);
|
||||
|
||||
this.extensionService.whenInstalledExtensionsRegistered().then(() => this.extensionsRegistered = true);
|
||||
|
||||
this.configurationService.onDidChangeConfiguration(e => this.updateConfiguration());
|
||||
this.updateConfiguration();
|
||||
}
|
||||
|
||||
private updateConfiguration(): void {
|
||||
this.commandHistoryEnabled = resolveCommandHistory(this.configurationService) > 0;
|
||||
}
|
||||
|
||||
getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
|
||||
if (this.extensionsRegistered) {
|
||||
return this.doGetResults(searchValue, token);
|
||||
}
|
||||
|
||||
// If extensions are not yet registered, we wait for a little moment to give them
|
||||
// a chance to register so that the complete set of commands shows up as result
|
||||
// We do not want to delay functionality beyond that time though to keep the commands
|
||||
// functional.
|
||||
return Promise.race([timeout(800), this.extensionService.whenInstalledExtensionsRegistered().then(() => undefined)]).then(() => this.doGetResults(searchValue, token));
|
||||
}
|
||||
|
||||
private doGetResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
|
||||
if (token.isCancellationRequested) {
|
||||
return Promise.resolve(new QuickOpenModel([]));
|
||||
}
|
||||
|
||||
searchValue = searchValue.trim();
|
||||
|
||||
// Remember as last command palette input
|
||||
lastCommandPaletteInput = searchValue;
|
||||
|
||||
// Editor Actions
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
let editorActions: IEditorAction[] = [];
|
||||
if (activeTextEditorWidget && types.isFunction(activeTextEditorWidget.getSupportedActions)) {
|
||||
editorActions = activeTextEditorWidget.getSupportedActions();
|
||||
}
|
||||
|
||||
const editorEntries = this.editorActionsToEntries(editorActions, searchValue);
|
||||
|
||||
// Other Actions
|
||||
const menu = this.editorService.invokeWithinEditorContext(accessor => this.menuService.createMenu(MenuId.CommandPalette, accessor.get(IContextKeyService)));
|
||||
const menuActions = menu.getActions().reduce((r, [, actions]) => [...r, ...actions], <MenuItemAction[]>[]).filter(action => action instanceof MenuItemAction) as MenuItemAction[];
|
||||
const commandEntries = this.menuItemActionsToEntries(menuActions, searchValue);
|
||||
menu.dispose();
|
||||
|
||||
// Concat
|
||||
let entries = [...editorEntries, ...commandEntries];
|
||||
|
||||
// Remove duplicates
|
||||
entries = arrays.distinct(entries, entry => `${entry.getLabel()}${entry.getGroupLabel()}${entry.getCommandId()}`);
|
||||
|
||||
// Handle label clashes
|
||||
const commandLabels = new Set<string>();
|
||||
entries.forEach(entry => {
|
||||
const commandLabel = `${entry.getLabel()}${entry.getGroupLabel()}`;
|
||||
if (commandLabels.has(commandLabel)) {
|
||||
entry.setDescription(entry.getCommandId());
|
||||
} else {
|
||||
commandLabels.add(commandLabel);
|
||||
}
|
||||
});
|
||||
|
||||
// Sort by MRU order and fallback to name otherwie
|
||||
entries = entries.sort((elementA, elementB) => {
|
||||
const counterA = this.commandsHistory.peek(elementA.getCommandId());
|
||||
const counterB = this.commandsHistory.peek(elementB.getCommandId());
|
||||
|
||||
if (counterA && counterB) {
|
||||
return counterA > counterB ? -1 : 1; // use more recently used command before older
|
||||
}
|
||||
|
||||
if (counterA) {
|
||||
return -1; // first command was used, so it wins over the non used one
|
||||
}
|
||||
|
||||
if (counterB) {
|
||||
return 1; // other command was used so it wins over the command
|
||||
}
|
||||
|
||||
// both commands were never used, so we sort by name
|
||||
return elementA.getSortLabel().localeCompare(elementB.getSortLabel());
|
||||
});
|
||||
|
||||
// Introduce group marker border between recently used and others
|
||||
// only if we have recently used commands in the result set
|
||||
const firstEntry = entries[0];
|
||||
if (firstEntry && this.commandsHistory.peek(firstEntry.getCommandId())) {
|
||||
firstEntry.setGroupLabel(nls.localize('recentlyUsed', "recently used"));
|
||||
for (let i = 1; i < entries.length; i++) {
|
||||
const entry = entries[i];
|
||||
if (!this.commandsHistory.peek(entry.getCommandId())) {
|
||||
entry.setShowBorder(true);
|
||||
entry.setGroupLabel(nls.localize('morecCommands', "other commands"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(new QuickOpenModel(entries));
|
||||
}
|
||||
|
||||
private editorActionsToEntries(actions: IEditorAction[], searchValue: string): EditorActionCommandEntry[] {
|
||||
const entries: EditorActionCommandEntry[] = [];
|
||||
|
||||
for (const action of actions) {
|
||||
if (action.id === ShowAllCommandsAction.ID) {
|
||||
continue; // avoid duplicates
|
||||
}
|
||||
|
||||
const label = action.label;
|
||||
if (label) {
|
||||
|
||||
// Alias for non default languages
|
||||
const alias = !Language.isDefaultVariant() ? action.alias : null;
|
||||
const labelHighlights = wordFilter(searchValue, label);
|
||||
const aliasHighlights = alias ? wordFilter(searchValue, alias) : null;
|
||||
|
||||
if (labelHighlights || aliasHighlights) {
|
||||
entries.push(this.instantiationService.createInstance(EditorActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
private onBeforeRunCommand(commandId: string): void {
|
||||
|
||||
// Remember in commands history
|
||||
this.commandsHistory.push(commandId);
|
||||
}
|
||||
|
||||
private menuItemActionsToEntries(actions: MenuItemAction[], searchValue: string): ActionCommandEntry[] {
|
||||
const entries: ActionCommandEntry[] = [];
|
||||
|
||||
for (let action of actions) {
|
||||
const title = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
|
||||
let category, label = title;
|
||||
if (action.item.category) {
|
||||
category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
|
||||
label = nls.localize('cat.title', "{0}: {1}", category, title);
|
||||
}
|
||||
|
||||
if (label) {
|
||||
const labelHighlights = wordFilter(searchValue, label);
|
||||
|
||||
// Add an 'alias' in original language when running in different locale
|
||||
const aliasTitle = (!Language.isDefaultVariant() && typeof action.item.title !== 'string') ? action.item.title.original : null;
|
||||
const aliasCategory = (!Language.isDefaultVariant() && category && action.item.category && typeof action.item.category !== 'string') ? action.item.category.original : null;
|
||||
let alias;
|
||||
if (aliasTitle && category) {
|
||||
alias = aliasCategory ? `${aliasCategory}: ${aliasTitle}` : `${category}: ${aliasTitle}`;
|
||||
} else if (aliasTitle) {
|
||||
alias = aliasTitle;
|
||||
}
|
||||
const aliasHighlights = alias ? wordFilter(searchValue, alias) : null;
|
||||
|
||||
if (labelHighlights || aliasHighlights) {
|
||||
entries.push(this.instantiationService.createInstance(ActionCommandEntry, action.id, this.keybindingService.lookupKeybinding(action.item.id), label, alias, { label: labelHighlights, alias: aliasHighlights }, action, (id: string) => this.onBeforeRunCommand(id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
let autoFocusPrefixMatch: string | undefined = searchValue.trim();
|
||||
|
||||
if (autoFocusPrefixMatch && this.commandHistoryEnabled) {
|
||||
const firstEntry = context.model && context.model.entries[0];
|
||||
if (firstEntry instanceof BaseCommandEntry && this.commandsHistory.peek(firstEntry.getCommandId())) {
|
||||
autoFocusPrefixMatch = undefined; // keep focus on MRU element if we have history elements
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
autoFocusFirstEntry: true,
|
||||
autoFocusPrefixMatch
|
||||
};
|
||||
}
|
||||
|
||||
getEmptyLabel(searchString: string): string {
|
||||
return nls.localize('noCommandsMatching', "No commands matching");
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(CommandPaletteEditorAction);
|
||||
344
src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts
Normal file
344
src/vs/workbench/contrib/quickopen/browser/gotoLineHandler.ts
Normal file
@@ -0,0 +1,344 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { IEntryRunContext, Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { QuickOpenHandler, EditorQuickOpenEntry, QuickOpenAction } from 'vs/workbench/browser/quickopen';
|
||||
import { IEditor, IEditorViewState, IDiffEditorModel, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { OverviewRulerLane, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { IEditorInput, GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { IEditorOptions, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isCodeEditor, isDiffEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const GOTO_LINE_PREFIX = ':';
|
||||
|
||||
export class GotoLineAction extends QuickOpenAction {
|
||||
|
||||
static readonly ID = 'workbench.action.gotoLine';
|
||||
static readonly LABEL = nls.localize('gotoLine', "Go to Line...");
|
||||
|
||||
constructor(actionId: string, actionLabel: string,
|
||||
@IQuickOpenService private readonly _quickOpenService: IQuickOpenService,
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
super(actionId, actionLabel, GOTO_LINE_PREFIX, _quickOpenService);
|
||||
}
|
||||
|
||||
run(): Promise<void> {
|
||||
|
||||
let activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (!activeTextEditorWidget) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
if (isDiffEditor(activeTextEditorWidget)) {
|
||||
activeTextEditorWidget = activeTextEditorWidget.getModifiedEditor();
|
||||
}
|
||||
let restoreOptions: IEditorOptions | null = null;
|
||||
|
||||
if (isCodeEditor(activeTextEditorWidget)) {
|
||||
const config = activeTextEditorWidget.getConfiguration();
|
||||
if (config.viewInfo.renderLineNumbers === RenderLineNumbersType.Relative) {
|
||||
activeTextEditorWidget.updateOptions({
|
||||
lineNumbers: 'on'
|
||||
});
|
||||
restoreOptions = {
|
||||
lineNumbers: 'relative'
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const result = super.run();
|
||||
|
||||
if (restoreOptions) {
|
||||
Event.once(this._quickOpenService.onHide)(() => {
|
||||
activeTextEditorWidget!.updateOptions(restoreOptions!);
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
class GotoLineEntry extends EditorQuickOpenEntry {
|
||||
private line: number;
|
||||
private column: number;
|
||||
private handler: GotoLineHandler;
|
||||
|
||||
constructor(line: string, editorService: IEditorService, handler: GotoLineHandler) {
|
||||
super(editorService);
|
||||
|
||||
this.parseInput(line);
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
private parseInput(line: string) {
|
||||
const numbers = line.split(/,|:|#/).map(part => parseInt(part, 10)).filter(part => !isNaN(part));
|
||||
this.line = numbers[0];
|
||||
this.column = numbers[1];
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
|
||||
// Inform user about valid range if input is invalid
|
||||
const maxLineNumber = this.getMaxLineNumber();
|
||||
|
||||
if (this.editorService.activeTextEditorWidget && this.invalidRange(maxLineNumber)) {
|
||||
const position = this.editorService.activeTextEditorWidget.getPosition();
|
||||
if (position) {
|
||||
const currentLine = position.lineNumber;
|
||||
|
||||
if (maxLineNumber > 0) {
|
||||
return nls.localize('gotoLineLabelEmptyWithLimit', "Current Line: {0}. Type a line number between 1 and {1} to navigate to.", currentLine, maxLineNumber);
|
||||
}
|
||||
|
||||
return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Input valid, indicate action
|
||||
return this.column ? nls.localize('gotoLineColumnLabel', "Go to line {0} and character {1}.", this.line, this.column) : nls.localize('gotoLineLabel', "Go to line {0}.", this.line);
|
||||
}
|
||||
|
||||
private invalidRange(maxLineNumber: number = this.getMaxLineNumber()): boolean {
|
||||
return !this.line || !types.isNumber(this.line) || (maxLineNumber > 0 && types.isNumber(this.line) && this.line > maxLineNumber);
|
||||
}
|
||||
|
||||
private getMaxLineNumber(): number {
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (!activeTextEditorWidget) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
let model = activeTextEditorWidget.getModel();
|
||||
if (model && (<IDiffEditorModel>model).modified && (<IDiffEditorModel>model).original) {
|
||||
model = (<IDiffEditorModel>model).modified; // Support for diff editor models
|
||||
}
|
||||
|
||||
return model && types.isFunction((<ITextModel>model).getLineCount) ? (<ITextModel>model).getLineCount() : -1;
|
||||
}
|
||||
|
||||
run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
return this.runOpen(context);
|
||||
}
|
||||
|
||||
return this.runPreview();
|
||||
}
|
||||
|
||||
getInput(): IEditorInput | null {
|
||||
return this.editorService.activeEditor || null;
|
||||
}
|
||||
|
||||
getOptions(pinned?: boolean): ITextEditorOptions {
|
||||
return {
|
||||
selection: this.toSelection(),
|
||||
pinned
|
||||
};
|
||||
}
|
||||
|
||||
runOpen(context: IEntryRunContext): boolean {
|
||||
|
||||
// No-op if range is not valid
|
||||
if (this.invalidRange()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check for sideBySide use
|
||||
const sideBySide = context.keymods.ctrlCmd;
|
||||
if (sideBySide) {
|
||||
this.editorService.openEditor(this.getInput()!, this.getOptions(context.keymods.alt), SIDE_GROUP);
|
||||
}
|
||||
|
||||
// Apply selection and focus
|
||||
const range = this.toSelection();
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
activeTextEditorWidget.setSelection(range);
|
||||
activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
runPreview(): boolean {
|
||||
|
||||
// No-op if range is not valid
|
||||
if (this.invalidRange()) {
|
||||
this.handler.clearDecorations();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Select Line Position
|
||||
const range = this.toSelection();
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth);
|
||||
|
||||
// Decorate if possible
|
||||
if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) {
|
||||
this.handler.decorateOutline(range, activeTextEditorWidget, this.editorService.activeControl.group);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private toSelection(): IRange {
|
||||
return {
|
||||
startLineNumber: this.line,
|
||||
startColumn: this.column || 1,
|
||||
endLineNumber: this.line,
|
||||
endColumn: this.column || 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface IEditorLineDecoration {
|
||||
groupId: GroupIdentifier;
|
||||
rangeHighlightId: string;
|
||||
lineDecorationId: string;
|
||||
}
|
||||
|
||||
export class GotoLineHandler extends QuickOpenHandler {
|
||||
|
||||
static readonly ID = 'workbench.picker.line';
|
||||
|
||||
private rangeHighlightDecorationId: IEditorLineDecoration | null;
|
||||
private lastKnownEditorViewState: IEditorViewState | null;
|
||||
|
||||
constructor(@IEditorService private readonly editorService: IEditorService) {
|
||||
super();
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
if (this.editorService.activeTextEditorWidget) {
|
||||
const position = this.editorService.activeTextEditorWidget.getPosition();
|
||||
if (position) {
|
||||
const currentLine = position.lineNumber;
|
||||
return nls.localize('gotoLineLabelEmpty', "Current Line: {0}. Type a line number to navigate to.", currentLine);
|
||||
}
|
||||
}
|
||||
|
||||
return nls.localize('cannotRunGotoLine', "Open a text file first to go to a line.");
|
||||
}
|
||||
|
||||
getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
|
||||
searchValue = searchValue.trim();
|
||||
|
||||
// Remember view state to be able to restore on cancel
|
||||
if (!this.lastKnownEditorViewState) {
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState();
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(new QuickOpenModel([new GotoLineEntry(searchValue, this.editorService, this)]));
|
||||
}
|
||||
|
||||
canRun(): boolean | string {
|
||||
const canRun = !!this.editorService.activeTextEditorWidget;
|
||||
|
||||
return canRun ? true : nls.localize('cannotRunGotoLine', "Open a text file first to go to a line.");
|
||||
}
|
||||
|
||||
decorateOutline(range: IRange, editor: IEditor, group: IEditorGroup): void {
|
||||
editor.changeDecorations(changeAccessor => {
|
||||
const deleteDecorations: string[] = [];
|
||||
|
||||
if (this.rangeHighlightDecorationId) {
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.lineDecorationId);
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
|
||||
const newDecorations: IModelDeltaDecoration[] = [
|
||||
// rangeHighlight at index 0
|
||||
{
|
||||
range: range,
|
||||
options: {
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
},
|
||||
|
||||
// lineDecoration at index 1
|
||||
{
|
||||
range: range,
|
||||
options: {
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(overviewRulerRangeHighlight),
|
||||
position: OverviewRulerLane.Full
|
||||
}
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const decorations = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);
|
||||
const rangeHighlightId = decorations[0];
|
||||
const lineDecorationId = decorations[1];
|
||||
|
||||
this.rangeHighlightDecorationId = {
|
||||
groupId: group.id,
|
||||
rangeHighlightId: rangeHighlightId,
|
||||
lineDecorationId: lineDecorationId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
clearDecorations(): void {
|
||||
const rangeHighlightDecorationId = this.rangeHighlightDecorationId;
|
||||
if (rangeHighlightDecorationId) {
|
||||
this.editorService.visibleControls.forEach(editor => {
|
||||
if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) {
|
||||
const editorControl = <IEditor>editor.getControl();
|
||||
editorControl.changeDecorations(changeAccessor => {
|
||||
changeAccessor.deltaDecorations([
|
||||
rangeHighlightDecorationId.lineDecorationId,
|
||||
rangeHighlightDecorationId.rangeHighlightId
|
||||
], []);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.rangeHighlightDecorationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled: boolean): void {
|
||||
|
||||
// Clear Highlight Decorations if present
|
||||
this.clearDecorations();
|
||||
|
||||
// Restore selection if canceled
|
||||
if (canceled && this.lastKnownEditorViewState) {
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
activeTextEditorWidget.restoreViewState(this.lastKnownEditorViewState);
|
||||
}
|
||||
}
|
||||
|
||||
this.lastKnownEditorViewState = null;
|
||||
}
|
||||
|
||||
getAutoFocus(searchValue: string): IAutoFocus {
|
||||
return {
|
||||
autoFocusFirstEntry: searchValue.trim().length > 0
|
||||
};
|
||||
}
|
||||
}
|
||||
610
src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts
Normal file
610
src/vs/workbench/contrib/quickopen/browser/gotoSymbolHandler.ts
Normal file
@@ -0,0 +1,610 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!vs/editor/contrib/documentSymbols/media/symbol-icons';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import * as strings from 'vs/base/common/strings';
|
||||
import { IEntryRunContext, Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenModel, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { QuickOpenHandler, EditorQuickOpenEntryGroup, QuickOpenAction } from 'vs/workbench/browser/quickopen';
|
||||
import * as filters from 'vs/base/common/filters';
|
||||
import { IEditor, IDiffEditorModel, IEditorViewState, ScrollType } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationsChangeAccessor, OverviewRulerLane, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { getDocumentSymbols } from 'vs/editor/contrib/quickOpen/quickOpen';
|
||||
import { DocumentSymbolProviderRegistry, DocumentSymbol, symbolKindToCssClass, SymbolKind } from 'vs/editor/common/modes';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { GroupIdentifier, IEditorInput } from 'vs/workbench/common/editor';
|
||||
import { IEditorService, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IEditorGroup } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { asPromise } from 'vs/base/common/async';
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
export const GOTO_SYMBOL_PREFIX = '@';
|
||||
export const SCOPE_PREFIX = ':';
|
||||
|
||||
const FALLBACK_NLS_SYMBOL_KIND = nls.localize('property', "properties ({0})");
|
||||
const NLS_SYMBOL_KIND_CACHE: { [type: number]: string } = {
|
||||
[SymbolKind.Method]: nls.localize('method', "methods ({0})"),
|
||||
[SymbolKind.Function]: nls.localize('function', "functions ({0})"),
|
||||
[SymbolKind.Constructor]: nls.localize('_constructor', "constructors ({0})"),
|
||||
[SymbolKind.Variable]: nls.localize('variable', "variables ({0})"),
|
||||
[SymbolKind.Class]: nls.localize('class', "classes ({0})"),
|
||||
[SymbolKind.Struct]: nls.localize('struct', "structs ({0})"),
|
||||
[SymbolKind.Event]: nls.localize('event', "events ({0})"),
|
||||
[SymbolKind.Operator]: nls.localize('operator', "operators ({0})"),
|
||||
[SymbolKind.Interface]: nls.localize('interface', "interfaces ({0})"),
|
||||
[SymbolKind.Namespace]: nls.localize('namespace', "namespaces ({0})"),
|
||||
[SymbolKind.Package]: nls.localize('package', "packages ({0})"),
|
||||
[SymbolKind.TypeParameter]: nls.localize('typeParameter', "type parameters ({0})"),
|
||||
[SymbolKind.Module]: nls.localize('modules', "modules ({0})"),
|
||||
[SymbolKind.Property]: nls.localize('property', "properties ({0})"),
|
||||
[SymbolKind.Enum]: nls.localize('enum', "enumerations ({0})"),
|
||||
[SymbolKind.EnumMember]: nls.localize('enumMember', "enumeration members ({0})"),
|
||||
[SymbolKind.String]: nls.localize('string', "strings ({0})"),
|
||||
[SymbolKind.File]: nls.localize('file', "files ({0})"),
|
||||
[SymbolKind.Array]: nls.localize('array', "arrays ({0})"),
|
||||
[SymbolKind.Number]: nls.localize('number', "numbers ({0})"),
|
||||
[SymbolKind.Boolean]: nls.localize('boolean', "booleans ({0})"),
|
||||
[SymbolKind.Object]: nls.localize('object', "objects ({0})"),
|
||||
[SymbolKind.Key]: nls.localize('key', "keys ({0})"),
|
||||
[SymbolKind.Field]: nls.localize('field', "fields ({0})"),
|
||||
[SymbolKind.Constant]: nls.localize('constant', "constants ({0})")
|
||||
};
|
||||
|
||||
export class GotoSymbolAction extends QuickOpenAction {
|
||||
|
||||
static readonly ID = 'workbench.action.gotoSymbol';
|
||||
static readonly LABEL = nls.localize('gotoSymbol', "Go to Symbol in File...");
|
||||
|
||||
constructor(actionId: string, actionLabel: string, @IQuickOpenService quickOpenService: IQuickOpenService) {
|
||||
super(actionId, actionLabel, GOTO_SYMBOL_PREFIX, quickOpenService);
|
||||
}
|
||||
}
|
||||
|
||||
class OutlineModel extends QuickOpenModel {
|
||||
|
||||
applyFilter(searchValue: string): void {
|
||||
|
||||
// Normalize search
|
||||
let normalizedSearchValue = searchValue;
|
||||
if (searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
normalizedSearchValue = normalizedSearchValue.substr(SCOPE_PREFIX.length);
|
||||
}
|
||||
|
||||
// Check for match and update visibility and group label
|
||||
this.entries.forEach((entry: SymbolEntry) => {
|
||||
|
||||
// Clear all state first
|
||||
entry.setGroupLabel(undefined);
|
||||
entry.setShowBorder(false);
|
||||
entry.setHighlights([]);
|
||||
entry.setHidden(false);
|
||||
|
||||
// Filter by search
|
||||
if (normalizedSearchValue) {
|
||||
const highlights = filters.matchesFuzzy2(normalizedSearchValue, entry.getLabel());
|
||||
if (highlights) {
|
||||
entry.setHighlights(highlights);
|
||||
entry.setHidden(false);
|
||||
} else if (!entry.isHidden()) {
|
||||
entry.setHidden(true);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Sort properly if actually searching
|
||||
if (searchValue) {
|
||||
if (searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
this.entries.sort(this.sortScoped.bind(this, searchValue.toLowerCase()));
|
||||
} else {
|
||||
this.entries.sort(this.sortNormal.bind(this, searchValue.toLowerCase()));
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise restore order as appearing in outline
|
||||
else {
|
||||
this.entries.sort((a: SymbolEntry, b: SymbolEntry) => a.getIndex() - b.getIndex());
|
||||
}
|
||||
|
||||
// Mark all type groups
|
||||
const visibleResults = <SymbolEntry[]>this.getEntries(true);
|
||||
if (visibleResults.length > 0 && searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
let currentType: SymbolKind | null = null;
|
||||
let currentResult: SymbolEntry | null = null;
|
||||
let typeCounter = 0;
|
||||
|
||||
for (let i = 0; i < visibleResults.length; i++) {
|
||||
const result = visibleResults[i];
|
||||
|
||||
// Found new type
|
||||
if (currentType !== result.getKind()) {
|
||||
|
||||
// Update previous result with count
|
||||
if (currentResult) {
|
||||
currentResult.setGroupLabel(typeof currentType === 'number' ? this.renderGroupLabel(currentType, typeCounter) : undefined);
|
||||
}
|
||||
|
||||
currentType = result.getKind();
|
||||
currentResult = result;
|
||||
typeCounter = 1;
|
||||
|
||||
result.setShowBorder(i > 0);
|
||||
}
|
||||
|
||||
// Existing type, keep counting
|
||||
else {
|
||||
typeCounter++;
|
||||
}
|
||||
}
|
||||
|
||||
// Update previous result with count
|
||||
if (currentResult) {
|
||||
currentResult.setGroupLabel(typeof currentType === 'number' ? this.renderGroupLabel(currentType, typeCounter) : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Mark first entry as outline
|
||||
else if (visibleResults.length > 0) {
|
||||
visibleResults[0].setGroupLabel(nls.localize('symbols', "symbols ({0})", visibleResults.length));
|
||||
}
|
||||
}
|
||||
|
||||
private sortNormal(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number {
|
||||
|
||||
// Handle hidden elements
|
||||
if (elementA.isHidden() && elementB.isHidden()) {
|
||||
return 0;
|
||||
} else if (elementA.isHidden()) {
|
||||
return 1;
|
||||
} else if (elementB.isHidden()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const elementAName = elementA.getLabel().toLowerCase();
|
||||
const elementBName = elementB.getLabel().toLowerCase();
|
||||
|
||||
// Compare by name
|
||||
const r = elementAName.localeCompare(elementBName);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// If name identical sort by range instead
|
||||
const elementARange = elementA.getRange();
|
||||
const elementBRange = elementB.getRange();
|
||||
|
||||
return elementARange.startLineNumber - elementBRange.startLineNumber;
|
||||
}
|
||||
|
||||
private sortScoped(searchValue: string, elementA: SymbolEntry, elementB: SymbolEntry): number {
|
||||
|
||||
// Handle hidden elements
|
||||
if (elementA.isHidden() && elementB.isHidden()) {
|
||||
return 0;
|
||||
} else if (elementA.isHidden()) {
|
||||
return 1;
|
||||
} else if (elementB.isHidden()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Remove scope char
|
||||
searchValue = searchValue.substr(SCOPE_PREFIX.length);
|
||||
|
||||
// Sort by type first if scoped search
|
||||
const elementATypeLabel = NLS_SYMBOL_KIND_CACHE[elementA.getKind()] || FALLBACK_NLS_SYMBOL_KIND;
|
||||
const elementBTypeLabel = NLS_SYMBOL_KIND_CACHE[elementB.getKind()] || FALLBACK_NLS_SYMBOL_KIND;
|
||||
let r = elementATypeLabel.localeCompare(elementBTypeLabel);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
|
||||
// Special sort when searching in scoped mode
|
||||
if (searchValue) {
|
||||
const elementAName = elementA.getLabel().toLowerCase();
|
||||
const elementBName = elementB.getLabel().toLowerCase();
|
||||
|
||||
// Compare by name
|
||||
r = elementAName.localeCompare(elementBName);
|
||||
if (r !== 0) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
// Default to sort by range
|
||||
const elementARange = elementA.getRange();
|
||||
const elementBRange = elementB.getRange();
|
||||
|
||||
return elementARange.startLineNumber - elementBRange.startLineNumber;
|
||||
}
|
||||
|
||||
private renderGroupLabel(type: SymbolKind, count: number): string {
|
||||
let pattern = NLS_SYMBOL_KIND_CACHE[type];
|
||||
if (!pattern) {
|
||||
pattern = FALLBACK_NLS_SYMBOL_KIND;
|
||||
}
|
||||
|
||||
return strings.format(pattern, count);
|
||||
}
|
||||
}
|
||||
|
||||
class SymbolEntry extends EditorQuickOpenEntryGroup {
|
||||
private editorService: IEditorService;
|
||||
private index: number;
|
||||
private name: string;
|
||||
private kind: SymbolKind;
|
||||
private icon: string;
|
||||
private description: string;
|
||||
private range: IRange;
|
||||
private revealRange: IRange;
|
||||
private handler: GotoSymbolHandler;
|
||||
|
||||
constructor(index: number, name: string, kind: SymbolKind, description: string, icon: string, range: IRange, revealRange: IRange, highlights: IHighlight[], editorService: IEditorService, handler: GotoSymbolHandler) {
|
||||
super();
|
||||
|
||||
this.index = index;
|
||||
this.name = name;
|
||||
this.kind = kind;
|
||||
this.icon = icon;
|
||||
this.description = description;
|
||||
this.range = range;
|
||||
this.revealRange = revealRange || range;
|
||||
this.setHighlights(highlights);
|
||||
this.editorService = editorService;
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
getIndex(): number {
|
||||
return this.index;
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, symbols", this.getLabel());
|
||||
}
|
||||
|
||||
getIcon(): string {
|
||||
return this.icon;
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
getKind(): SymbolKind {
|
||||
return this.kind;
|
||||
}
|
||||
|
||||
getRange(): IRange {
|
||||
return this.range;
|
||||
}
|
||||
|
||||
getInput(): IEditorInput | null {
|
||||
return this.editorService.activeEditor || null;
|
||||
}
|
||||
|
||||
getOptions(pinned?: boolean): ITextEditorOptions {
|
||||
return {
|
||||
selection: this.toSelection(),
|
||||
pinned
|
||||
};
|
||||
}
|
||||
|
||||
run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
return this.runOpen(context);
|
||||
}
|
||||
|
||||
return this.runPreview();
|
||||
}
|
||||
|
||||
private runOpen(context: IEntryRunContext): boolean {
|
||||
|
||||
// Check for sideBySide use
|
||||
const sideBySide = context.keymods.ctrlCmd;
|
||||
if (sideBySide) {
|
||||
this.editorService.openEditor(this.getInput()!, this.getOptions(context.keymods.alt), SIDE_GROUP);
|
||||
}
|
||||
|
||||
// Apply selection and focus
|
||||
else {
|
||||
const range = this.toSelection();
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
activeTextEditorWidget.setSelection(range);
|
||||
activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private runPreview(): boolean {
|
||||
|
||||
// Select Outline Position
|
||||
const range = this.toSelection();
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
activeTextEditorWidget.revealRangeInCenter(range, ScrollType.Smooth);
|
||||
|
||||
// Decorate if possible
|
||||
if (this.editorService.activeControl && types.isFunction(activeTextEditorWidget.changeDecorations)) {
|
||||
this.handler.decorateOutline(this.range, range, activeTextEditorWidget, this.editorService.activeControl.group);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private toSelection(): IRange {
|
||||
return {
|
||||
startLineNumber: this.revealRange.startLineNumber,
|
||||
startColumn: this.revealRange.startColumn || 1,
|
||||
endLineNumber: this.revealRange.startLineNumber,
|
||||
endColumn: this.revealRange.startColumn || 1
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
interface IEditorLineDecoration {
|
||||
groupId: GroupIdentifier;
|
||||
rangeHighlightId: string;
|
||||
lineDecorationId: string;
|
||||
}
|
||||
|
||||
export class GotoSymbolHandler extends QuickOpenHandler {
|
||||
|
||||
static readonly ID = 'workbench.picker.filesymbols';
|
||||
|
||||
private rangeHighlightDecorationId?: IEditorLineDecoration;
|
||||
private lastKnownEditorViewState: IEditorViewState | null;
|
||||
|
||||
private cachedOutlineRequest?: Promise<OutlineModel | null>;
|
||||
private pendingOutlineRequest?: CancellationTokenSource;
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.editorService.onDidActiveEditorChange(() => this.onDidActiveEditorChange());
|
||||
}
|
||||
|
||||
private onDidActiveEditorChange(): void {
|
||||
this.clearOutlineRequest();
|
||||
|
||||
this.lastKnownEditorViewState = null;
|
||||
this.rangeHighlightDecorationId = undefined;
|
||||
}
|
||||
|
||||
getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel | null> {
|
||||
searchValue = searchValue.trim();
|
||||
|
||||
// Support to cancel pending outline requests
|
||||
if (!this.pendingOutlineRequest) {
|
||||
this.pendingOutlineRequest = new CancellationTokenSource();
|
||||
}
|
||||
|
||||
// Remember view state to be able to restore on cancel
|
||||
if (!this.lastKnownEditorViewState) {
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
this.lastKnownEditorViewState = activeTextEditorWidget.saveViewState();
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve Outline Model
|
||||
return this.getOutline().then(outline => {
|
||||
if (!outline) {
|
||||
return outline;
|
||||
}
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return outline;
|
||||
}
|
||||
|
||||
// Filter by search
|
||||
outline.applyFilter(searchValue);
|
||||
|
||||
return outline;
|
||||
});
|
||||
}
|
||||
|
||||
getEmptyLabel(searchString: string): string {
|
||||
if (searchString.length > 0) {
|
||||
return nls.localize('noSymbolsMatching', "No symbols matching");
|
||||
}
|
||||
|
||||
return nls.localize('noSymbolsFound', "No symbols found");
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
return nls.localize('gotoSymbolHandlerAriaLabel', "Type to narrow down symbols of the currently active editor.");
|
||||
}
|
||||
|
||||
canRun(): boolean | string {
|
||||
let canRun = false;
|
||||
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
let model = activeTextEditorWidget.getModel();
|
||||
if (model && (<IDiffEditorModel>model).modified && (<IDiffEditorModel>model).original) {
|
||||
model = (<IDiffEditorModel>model).modified; // Support for diff editor models
|
||||
}
|
||||
|
||||
if (model && types.isFunction((<ITextModel>model).getLanguageIdentifier)) {
|
||||
canRun = DocumentSymbolProviderRegistry.has(<ITextModel>model);
|
||||
}
|
||||
}
|
||||
|
||||
return canRun ? true : activeTextEditorWidget !== null ? nls.localize('cannotRunGotoSymbolInFile', "No symbol information for the file") : nls.localize('cannotRunGotoSymbol', "Open a text file first to go to a symbol");
|
||||
}
|
||||
|
||||
getAutoFocus(searchValue: string): IAutoFocus {
|
||||
searchValue = searchValue.trim();
|
||||
|
||||
// Remove any type pattern (:) from search value as needed
|
||||
if (searchValue.indexOf(SCOPE_PREFIX) === 0) {
|
||||
searchValue = searchValue.substr(SCOPE_PREFIX.length);
|
||||
}
|
||||
|
||||
return {
|
||||
autoFocusPrefixMatch: searchValue,
|
||||
autoFocusFirstEntry: !!searchValue
|
||||
};
|
||||
}
|
||||
|
||||
private toQuickOpenEntries(flattened: DocumentSymbol[]): SymbolEntry[] {
|
||||
const results: SymbolEntry[] = [];
|
||||
|
||||
for (let i = 0; i < flattened.length; i++) {
|
||||
const element = flattened[i];
|
||||
const label = strings.trim(element.name);
|
||||
|
||||
// Show parent scope as description
|
||||
const description = element.containerName || '';
|
||||
const icon = symbolKindToCssClass(element.kind);
|
||||
|
||||
// Add
|
||||
results.push(new SymbolEntry(i,
|
||||
label, element.kind, description, `symbol-icon ${icon}`,
|
||||
element.range, element.selectionRange, [], this.editorService, this
|
||||
));
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private getOutline(): Promise<OutlineModel | null> {
|
||||
if (!this.cachedOutlineRequest) {
|
||||
this.cachedOutlineRequest = this.doGetActiveOutline();
|
||||
}
|
||||
|
||||
return this.cachedOutlineRequest;
|
||||
}
|
||||
|
||||
private doGetActiveOutline(): Promise<OutlineModel | null> {
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
let model = activeTextEditorWidget.getModel();
|
||||
if (model && (<IDiffEditorModel>model).modified && (<IDiffEditorModel>model).original) {
|
||||
model = (<IDiffEditorModel>model).modified; // Support for diff editor models
|
||||
}
|
||||
|
||||
if (model && types.isFunction((<ITextModel>model).getLanguageIdentifier)) {
|
||||
return Promise.resolve(asPromise(() => getDocumentSymbols(<ITextModel>model, true, this.pendingOutlineRequest!.token)).then(entries => {
|
||||
return new OutlineModel(this.toQuickOpenEntries(entries));
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
decorateOutline(fullRange: IRange, startRange: IRange, editor: IEditor, group: IEditorGroup): void {
|
||||
editor.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
const deleteDecorations: string[] = [];
|
||||
|
||||
if (this.rangeHighlightDecorationId) {
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.lineDecorationId);
|
||||
deleteDecorations.push(this.rangeHighlightDecorationId.rangeHighlightId);
|
||||
this.rangeHighlightDecorationId = undefined;
|
||||
}
|
||||
|
||||
const newDecorations: IModelDeltaDecoration[] = [
|
||||
|
||||
// rangeHighlight at index 0
|
||||
{
|
||||
range: fullRange,
|
||||
options: {
|
||||
className: 'rangeHighlight',
|
||||
isWholeLine: true
|
||||
}
|
||||
},
|
||||
|
||||
// lineDecoration at index 1
|
||||
{
|
||||
range: startRange,
|
||||
options: {
|
||||
overviewRuler: {
|
||||
color: themeColorFromId(overviewRulerRangeHighlight),
|
||||
position: OverviewRulerLane.Full
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
];
|
||||
|
||||
const decorations = changeAccessor.deltaDecorations(deleteDecorations, newDecorations);
|
||||
const rangeHighlightId = decorations[0];
|
||||
const lineDecorationId = decorations[1];
|
||||
|
||||
this.rangeHighlightDecorationId = {
|
||||
groupId: group.id,
|
||||
rangeHighlightId: rangeHighlightId,
|
||||
lineDecorationId: lineDecorationId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private clearDecorations(): void {
|
||||
const rangeHighlightDecorationId = this.rangeHighlightDecorationId;
|
||||
if (rangeHighlightDecorationId) {
|
||||
this.editorService.visibleControls.forEach(editor => {
|
||||
if (editor.group && editor.group.id === rangeHighlightDecorationId.groupId) {
|
||||
const editorControl = <IEditor>editor.getControl();
|
||||
editorControl.changeDecorations((changeAccessor: IModelDecorationsChangeAccessor) => {
|
||||
changeAccessor.deltaDecorations([
|
||||
rangeHighlightDecorationId.lineDecorationId,
|
||||
rangeHighlightDecorationId.rangeHighlightId
|
||||
], []);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
this.rangeHighlightDecorationId = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
onClose(canceled: boolean): void {
|
||||
|
||||
// Cancel any pending/cached outline request now
|
||||
this.clearOutlineRequest();
|
||||
|
||||
// Clear Highlight Decorations if present
|
||||
this.clearDecorations();
|
||||
|
||||
// Restore selection if canceled
|
||||
if (canceled && this.lastKnownEditorViewState) {
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
if (activeTextEditorWidget) {
|
||||
activeTextEditorWidget.restoreViewState(this.lastKnownEditorViewState);
|
||||
}
|
||||
|
||||
this.lastKnownEditorViewState = null;
|
||||
}
|
||||
}
|
||||
|
||||
private clearOutlineRequest(): void {
|
||||
if (this.pendingOutlineRequest) {
|
||||
this.pendingOutlineRequest.cancel();
|
||||
this.pendingOutlineRequest.dispose();
|
||||
this.pendingOutlineRequest = undefined;
|
||||
}
|
||||
|
||||
this.cachedOutlineRequest = undefined;
|
||||
}
|
||||
}
|
||||
138
src/vs/workbench/contrib/quickopen/browser/helpHandler.ts
Normal file
138
src/vs/workbench/contrib/quickopen/browser/helpHandler.ts
Normal file
@@ -0,0 +1,138 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Mode, IEntryRunContext, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenModel, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { IQuickOpenRegistry, Extensions, QuickOpenHandler, QuickOpenHandlerDescriptor, QuickOpenHandlerHelpEntry } from 'vs/workbench/browser/quickopen';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const HELP_PREFIX = '?';
|
||||
|
||||
class HelpEntry extends QuickOpenEntryGroup {
|
||||
private prefixLabel: string;
|
||||
private prefix: string;
|
||||
private description: string;
|
||||
private quickOpenService: IQuickOpenService;
|
||||
private openOnPreview: boolean;
|
||||
|
||||
constructor(prefix: string, description: string, quickOpenService: IQuickOpenService, openOnPreview: boolean) {
|
||||
super();
|
||||
|
||||
if (!prefix) {
|
||||
this.prefix = '';
|
||||
this.prefixLabel = '\u2026' /* ... */;
|
||||
} else {
|
||||
this.prefix = this.prefixLabel = prefix;
|
||||
}
|
||||
|
||||
this.description = description;
|
||||
this.quickOpenService = quickOpenService;
|
||||
this.openOnPreview = openOnPreview;
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
return this.prefixLabel;
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, picker help", this.getLabel());
|
||||
}
|
||||
|
||||
getDescription(): string {
|
||||
return this.description;
|
||||
}
|
||||
|
||||
run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN || this.openOnPreview) {
|
||||
this.quickOpenService.show(this.prefix);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class HelpHandler extends QuickOpenHandler {
|
||||
|
||||
static readonly ID = 'workbench.picker.help';
|
||||
|
||||
constructor(@IQuickOpenService private readonly quickOpenService: IQuickOpenService) {
|
||||
super();
|
||||
}
|
||||
|
||||
getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
|
||||
searchValue = searchValue.trim();
|
||||
|
||||
const registry = (Registry.as<IQuickOpenRegistry>(Extensions.Quickopen));
|
||||
const handlerDescriptors = registry.getQuickOpenHandlers();
|
||||
|
||||
const defaultHandler = registry.getDefaultQuickOpenHandler();
|
||||
if (defaultHandler) {
|
||||
handlerDescriptors.push(defaultHandler);
|
||||
}
|
||||
|
||||
const workbenchScoped: HelpEntry[] = [];
|
||||
const editorScoped: HelpEntry[] = [];
|
||||
|
||||
const matchingHandlers: Array<QuickOpenHandlerHelpEntry | QuickOpenHandlerDescriptor> = [];
|
||||
handlerDescriptors.sort((h1, h2) => h1.prefix.localeCompare(h2.prefix)).forEach(handlerDescriptor => {
|
||||
if (handlerDescriptor.prefix !== HELP_PREFIX) {
|
||||
|
||||
// Descriptor has multiple help entries
|
||||
if (types.isArray(handlerDescriptor.helpEntries)) {
|
||||
for (const helpEntry of handlerDescriptor.helpEntries) {
|
||||
if (helpEntry.prefix.indexOf(searchValue) === 0) {
|
||||
matchingHandlers.push(helpEntry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Single Help entry for descriptor
|
||||
else if (handlerDescriptor.prefix.indexOf(searchValue) === 0) {
|
||||
matchingHandlers.push(handlerDescriptor);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
matchingHandlers.forEach(handler => {
|
||||
if (handler instanceof QuickOpenHandlerDescriptor) {
|
||||
workbenchScoped.push(new HelpEntry(handler.prefix, handler.description, this.quickOpenService, matchingHandlers.length === 1));
|
||||
} else {
|
||||
const entry = new HelpEntry(handler.prefix, handler.description, this.quickOpenService, matchingHandlers.length === 1);
|
||||
if (handler.needsEditor) {
|
||||
editorScoped.push(entry);
|
||||
} else {
|
||||
workbenchScoped.push(entry);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Add separator for workbench scoped handlers
|
||||
if (workbenchScoped.length > 0) {
|
||||
workbenchScoped[0].setGroupLabel(nls.localize('globalCommands', "global commands"));
|
||||
}
|
||||
|
||||
// Add separator for editor scoped handlers
|
||||
if (editorScoped.length > 0) {
|
||||
editorScoped[0].setGroupLabel(nls.localize('editorCommands', "editor commands"));
|
||||
if (workbenchScoped.length > 0) {
|
||||
editorScoped[0].setShowBorder(true);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(new QuickOpenModel([...workbenchScoped, ...editorScoped]));
|
||||
}
|
||||
|
||||
getAutoFocus(searchValue: string): IAutoFocus {
|
||||
searchValue = searchValue.trim();
|
||||
return {
|
||||
autoFocusFirstEntry: searchValue.length > 0,
|
||||
autoFocusPrefixMatch: searchValue
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as env from 'vs/base/common/platform';
|
||||
import * as nls from 'vs/nls';
|
||||
import { QuickOpenHandlerDescriptor, IQuickOpenRegistry, Extensions as QuickOpenExtensions } from 'vs/workbench/browser/quickopen';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { GotoSymbolAction, GOTO_SYMBOL_PREFIX, SCOPE_PREFIX, GotoSymbolHandler } from 'vs/workbench/contrib/quickopen/browser/gotoSymbolHandler';
|
||||
import { ShowAllCommandsAction, ALL_COMMANDS_PREFIX, ClearCommandHistoryAction, CommandsHandler } from 'vs/workbench/contrib/quickopen/browser/commandsHandler';
|
||||
import { GotoLineAction, GOTO_LINE_PREFIX, GotoLineHandler } from 'vs/workbench/contrib/quickopen/browser/gotoLineHandler';
|
||||
import { HELP_PREFIX, HelpHandler } from 'vs/workbench/contrib/quickopen/browser/helpHandler';
|
||||
import { VIEW_PICKER_PREFIX, OpenViewPickerAction, QuickOpenViewPickerAction, ViewPickerHandler } from 'vs/workbench/contrib/quickopen/browser/viewPickerHandler';
|
||||
import { inQuickOpenContext, getQuickNavigateHandler } from 'vs/workbench/browser/parts/quickopen/quickopen';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
|
||||
// Register Actions
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearCommandHistoryAction, ClearCommandHistoryAction.ID, ClearCommandHistoryAction.LABEL), 'Clear Command History');
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllCommandsAction, ShowAllCommandsAction.ID, ShowAllCommandsAction.LABEL, {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_P,
|
||||
secondary: [KeyCode.F1]
|
||||
}), 'Show All Commands');
|
||||
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(GotoLineAction, GotoLineAction.ID, GotoLineAction.LABEL, {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_G,
|
||||
mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_G }
|
||||
}), 'Go to Line...');
|
||||
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(GotoSymbolAction, GotoSymbolAction.ID, GotoSymbolAction.LABEL, {
|
||||
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_O
|
||||
}), 'Go to Symbol in File...');
|
||||
|
||||
const inViewsPickerContextKey = 'inViewsPicker';
|
||||
const inViewsPickerContext = ContextKeyExpr.and(inQuickOpenContext, ContextKeyExpr.has(inViewsPickerContextKey));
|
||||
|
||||
const viewPickerKeybinding = { primary: KeyMod.CtrlCmd | KeyCode.KEY_Q, mac: { primary: KeyMod.WinCtrl | KeyCode.KEY_Q }, linux: { primary: 0 } };
|
||||
|
||||
const viewCategory = nls.localize('view', "View");
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenViewPickerAction, OpenViewPickerAction.ID, OpenViewPickerAction.LABEL), 'View: Open View', viewCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(QuickOpenViewPickerAction, QuickOpenViewPickerAction.ID, QuickOpenViewPickerAction.LABEL, viewPickerKeybinding), 'View: Quick Open View', viewCategory);
|
||||
|
||||
const quickOpenNavigateNextInViewPickerId = 'workbench.action.quickOpenNavigateNextInViewPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigateNextInViewPickerId,
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
handler: getQuickNavigateHandler(quickOpenNavigateNextInViewPickerId, true),
|
||||
when: inViewsPickerContext,
|
||||
primary: viewPickerKeybinding.primary,
|
||||
linux: viewPickerKeybinding.linux,
|
||||
mac: viewPickerKeybinding.mac
|
||||
});
|
||||
|
||||
const quickOpenNavigatePreviousInViewPickerId = 'workbench.action.quickOpenNavigatePreviousInViewPicker';
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: quickOpenNavigatePreviousInViewPickerId,
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
handler: getQuickNavigateHandler(quickOpenNavigatePreviousInViewPickerId, false),
|
||||
when: inViewsPickerContext,
|
||||
primary: viewPickerKeybinding.primary | KeyMod.Shift,
|
||||
linux: viewPickerKeybinding.linux,
|
||||
mac: {
|
||||
primary: viewPickerKeybinding.mac.primary | KeyMod.Shift
|
||||
}
|
||||
});
|
||||
|
||||
// Register Quick Open Handler
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
CommandsHandler,
|
||||
CommandsHandler.ID,
|
||||
ALL_COMMANDS_PREFIX,
|
||||
'inCommandsPicker',
|
||||
nls.localize('commandsHandlerDescriptionDefault', "Show and Run Commands")
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GotoLineHandler,
|
||||
GotoLineHandler.ID,
|
||||
GOTO_LINE_PREFIX,
|
||||
undefined,
|
||||
[
|
||||
{
|
||||
prefix: GOTO_LINE_PREFIX,
|
||||
needsEditor: true,
|
||||
description: env.isMacintosh ? nls.localize('gotoLineDescriptionMac', "Go to Line") : nls.localize('gotoLineDescriptionWin', "Go to Line")
|
||||
},
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
GotoSymbolHandler,
|
||||
GotoSymbolHandler.ID,
|
||||
GOTO_SYMBOL_PREFIX,
|
||||
'inFileSymbolsPicker',
|
||||
[
|
||||
{
|
||||
prefix: GOTO_SYMBOL_PREFIX,
|
||||
needsEditor: true,
|
||||
description: nls.localize('gotoSymbolDescription', "Go to Symbol in File")
|
||||
},
|
||||
{
|
||||
prefix: GOTO_SYMBOL_PREFIX + SCOPE_PREFIX,
|
||||
needsEditor: true,
|
||||
description: nls.localize('gotoSymbolDescriptionScoped', "Go to Symbol in File by Category")
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
HelpHandler,
|
||||
HelpHandler.ID,
|
||||
HELP_PREFIX,
|
||||
undefined,
|
||||
nls.localize('helpDescription', "Show Help")
|
||||
)
|
||||
);
|
||||
|
||||
Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
ViewPickerHandler,
|
||||
ViewPickerHandler.ID,
|
||||
VIEW_PICKER_PREFIX,
|
||||
inViewsPickerContextKey,
|
||||
[
|
||||
{
|
||||
prefix: VIEW_PICKER_PREFIX,
|
||||
needsEditor: false,
|
||||
description: nls.localize('viewPickerDescription', "Open View")
|
||||
}
|
||||
]
|
||||
)
|
||||
);
|
||||
|
||||
// View menu
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '1_open',
|
||||
command: {
|
||||
id: ShowAllCommandsAction.ID,
|
||||
title: nls.localize({ key: 'miCommandPalette', comment: ['&& denotes a mnemonic'] }, "&&Command Palette...")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
group: '1_open',
|
||||
command: {
|
||||
id: OpenViewPickerAction.ID,
|
||||
title: nls.localize({ key: 'miOpenView', comment: ['&& denotes a mnemonic'] }, "&&Open View...")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
// Go to menu
|
||||
|
||||
// {{SQL CARBON EDIT}} - Disable unused menu item
|
||||
// MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
// group: '4_symbol_nav',
|
||||
// command: {
|
||||
// id: 'workbench.action.gotoSymbol',
|
||||
// title: nls.localize({ key: 'miGotoSymbolInFile', comment: ['&& denotes a mnemonic'] }, "Go to &&Symbol in File...")
|
||||
// },
|
||||
// order: 1
|
||||
// });
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarGoMenu, {
|
||||
group: '5_infile_nav',
|
||||
command: {
|
||||
id: 'workbench.action.gotoLine',
|
||||
title: nls.localize({ key: 'miGotoLine', comment: ['&& denotes a mnemonic'] }, "Go to &&Line/Column...")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
247
src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts
Normal file
247
src/vs/workbench/contrib/quickopen/browser/viewPickerHandler.ts
Normal file
@@ -0,0 +1,247 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Mode, IEntryRunContext, IAutoFocus, IQuickNavigateConfiguration, IModel } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenModel, QuickOpenEntryGroup, QuickOpenEntry } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { QuickOpenHandler, QuickOpenAction } from 'vs/workbench/browser/quickopen';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import { ITerminalService } from 'vs/workbench/contrib/terminal/common/terminal';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { fuzzyContains, stripWildcards } from 'vs/base/common/strings';
|
||||
import { matchesFuzzy } from 'vs/base/common/filters';
|
||||
import { IViewsRegistry, ViewContainer, IViewsService, IViewContainersRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
|
||||
export const VIEW_PICKER_PREFIX = 'view ';
|
||||
|
||||
export class ViewEntry extends QuickOpenEntryGroup {
|
||||
|
||||
constructor(
|
||||
private label: string,
|
||||
private category: string,
|
||||
private open: () => void
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
getCategory(): string {
|
||||
return this.category;
|
||||
}
|
||||
|
||||
getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, view picker", this.getLabel());
|
||||
}
|
||||
|
||||
run(mode: Mode, context: IEntryRunContext): boolean {
|
||||
if (mode === Mode.OPEN) {
|
||||
return this.runOpen(context);
|
||||
}
|
||||
|
||||
return super.run(mode, context);
|
||||
}
|
||||
|
||||
private runOpen(context: IEntryRunContext): boolean {
|
||||
setTimeout(() => {
|
||||
this.open();
|
||||
}, 0);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class ViewPickerHandler extends QuickOpenHandler {
|
||||
|
||||
static readonly ID = 'workbench.picker.views';
|
||||
|
||||
constructor(
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@IViewsService private readonly viewsService: IViewsService,
|
||||
@IOutputService private readonly outputService: IOutputService,
|
||||
@ITerminalService private readonly terminalService: ITerminalService,
|
||||
@IPanelService private readonly panelService: IPanelService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
getResults(searchValue: string, token: CancellationToken): Promise<QuickOpenModel> {
|
||||
searchValue = searchValue.trim();
|
||||
const normalizedSearchValueLowercase = stripWildcards(searchValue).toLowerCase();
|
||||
|
||||
const viewEntries = this.getViewEntries();
|
||||
|
||||
const entries = viewEntries.filter(e => {
|
||||
if (!searchValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const highlights = matchesFuzzy(normalizedSearchValueLowercase, e.getLabel(), true);
|
||||
if (highlights) {
|
||||
e.setHighlights(highlights);
|
||||
}
|
||||
|
||||
if (!highlights && !fuzzyContains(e.getCategory(), normalizedSearchValueLowercase)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
const entryToCategory = {};
|
||||
entries.forEach(e => {
|
||||
if (!entryToCategory[e.getLabel()]) {
|
||||
entryToCategory[e.getLabel()] = e.getCategory();
|
||||
}
|
||||
});
|
||||
|
||||
let lastCategory: string;
|
||||
entries.forEach((e, index) => {
|
||||
if (lastCategory !== e.getCategory()) {
|
||||
lastCategory = e.getCategory();
|
||||
|
||||
e.setShowBorder(index > 0);
|
||||
e.setGroupLabel(lastCategory);
|
||||
|
||||
// When the entry category has a parent category, set group label as Parent / Child. For example, Views / Explorer.
|
||||
if (entryToCategory[lastCategory]) {
|
||||
e.setGroupLabel(`${entryToCategory[lastCategory]} / ${lastCategory}`);
|
||||
}
|
||||
} else {
|
||||
e.setShowBorder(false);
|
||||
e.setGroupLabel(undefined);
|
||||
}
|
||||
});
|
||||
|
||||
return Promise.resolve(new QuickOpenModel(entries));
|
||||
}
|
||||
|
||||
private getViewEntries(): ViewEntry[] {
|
||||
const viewEntries: ViewEntry[] = [];
|
||||
|
||||
const getViewEntriesForViewlet = (viewlet: ViewletDescriptor, viewContainer: ViewContainer): ViewEntry[] => {
|
||||
const views = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).getViews(viewContainer);
|
||||
const result: ViewEntry[] = [];
|
||||
if (views.length) {
|
||||
for (const view of views) {
|
||||
if (this.contextKeyService.contextMatchesRules(view.when || null)) {
|
||||
result.push(new ViewEntry(view.name, viewlet.name, () => this.viewsService.openView(view.id, true)));
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Viewlets
|
||||
const viewlets = this.viewletService.getViewlets();
|
||||
viewlets.forEach((viewlet, index) => {
|
||||
if (this.hasToShowViewlet(viewlet)) {
|
||||
viewEntries.push(new ViewEntry(viewlet.name, nls.localize('views', "Side Bar"), () => this.viewletService.openViewlet(viewlet.id, true)));
|
||||
}
|
||||
});
|
||||
|
||||
// Panels
|
||||
const panels = this.panelService.getPanels();
|
||||
panels.forEach((panel, index) => viewEntries.push(new ViewEntry(panel.name, nls.localize('panels', "Panel"), () => this.panelService.openPanel(panel.id, true))));
|
||||
|
||||
// Viewlet Views
|
||||
viewlets.forEach((viewlet, index) => {
|
||||
const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).get(viewlet.id);
|
||||
if (viewContainer) {
|
||||
const viewEntriesForViewlet: ViewEntry[] = getViewEntriesForViewlet(viewlet, viewContainer);
|
||||
viewEntries.push(...viewEntriesForViewlet);
|
||||
}
|
||||
});
|
||||
|
||||
// Terminals
|
||||
const terminalsCategory = nls.localize('terminals', "Terminal");
|
||||
this.terminalService.terminalTabs.forEach((tab, tabIndex) => {
|
||||
tab.terminalInstances.forEach((terminal, terminalIndex) => {
|
||||
const index = `${tabIndex + 1}.${terminalIndex + 1}`;
|
||||
const entry = new ViewEntry(nls.localize('terminalTitle', "{0}: {1}", index, terminal.title), terminalsCategory, () => {
|
||||
this.terminalService.showPanel(true).then(() => {
|
||||
this.terminalService.setActiveInstance(terminal);
|
||||
});
|
||||
});
|
||||
|
||||
viewEntries.push(entry);
|
||||
});
|
||||
});
|
||||
|
||||
// Output Channels
|
||||
const channels = this.outputService.getChannelDescriptors();
|
||||
channels.forEach((channel, index) => {
|
||||
const outputCategory = nls.localize('channels', "Output");
|
||||
const entry = new ViewEntry(channel.log ? nls.localize('logChannel', "Log ({0})", channel.label) : channel.label, outputCategory, () => this.outputService.showChannel(channel.id));
|
||||
|
||||
viewEntries.push(entry);
|
||||
});
|
||||
|
||||
return viewEntries;
|
||||
}
|
||||
|
||||
private hasToShowViewlet(viewlet: ViewletDescriptor): boolean {
|
||||
const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry).get(viewlet.id);
|
||||
if (viewContainer && viewContainer.hideIfEmpty) {
|
||||
const viewsCollection = this.viewsService.getViewDescriptors(viewContainer);
|
||||
return !!viewsCollection && viewsCollection.activeViewDescriptors.length > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getAutoFocus(searchValue: string, context: { model: IModel<QuickOpenEntry>, quickNavigateConfiguration?: IQuickNavigateConfiguration }): IAutoFocus {
|
||||
return {
|
||||
autoFocusFirstEntry: !!searchValue || !!context.quickNavigateConfiguration
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenViewPickerAction extends QuickOpenAction {
|
||||
|
||||
static readonly ID = 'workbench.action.openView';
|
||||
static readonly LABEL = nls.localize('openView', "Open View");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService
|
||||
) {
|
||||
super(id, label, VIEW_PICKER_PREFIX, quickOpenService);
|
||||
}
|
||||
}
|
||||
|
||||
export class QuickOpenViewPickerAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.quickOpenView';
|
||||
static readonly LABEL = nls.localize('quickOpenView', "Quick Open View");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(): Promise<boolean> {
|
||||
const keys = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickOpenService.show(VIEW_PICKER_PREFIX, { quickNavigateConfiguration: { keybindings: keys } });
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user