提交 0e587865 编写于 作者: B Benjamin Pasero

quick access - first cut workspace symbols

上级 a3baf9af
......@@ -196,18 +196,18 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
}
if (includeSymbol) {
const labelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`;
const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`;
const deprecated = symbol.tags && symbol.tags.indexOf(SymbolTag.Deprecated) >= 0;
filteredSymbolPicks.push({
index,
kind: symbol.kind,
score: symbolScore,
label: labelWithIcon,
label: symbolLabelWithIcon,
ariaLabel: localize('symbolsAriaLabel', "{0}, symbols picker", symbolLabel),
description: containerLabel,
highlights: deprecated ? undefined : {
label: createMatches(symbolScore, labelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */),
label: createMatches(symbolScore, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */),
description: createMatches(containerScore)
},
range: {
......
......@@ -10,7 +10,7 @@ import { first } from 'vs/base/common/arrays';
import { startsWith } from 'vs/base/common/strings';
import { assertIsDefined } from 'vs/base/common/types';
import { IDisposable, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { IQuickPickSeparator } from 'vs/base/parts/quickinput/common/quickInput';
import { IQuickPickSeparator, IKeyMods } from 'vs/base/parts/quickinput/common/quickInput';
export interface IQuickAccessController {
......@@ -167,8 +167,10 @@ export interface IPickerQuickAccessItem extends IQuickPickItem {
/**
* A method that will be executed when the pick item is accepted from
* the picker. The picker will close automatically before running this.
*
* @param keyMods the state of modifier keys when the item was accepted.
*/
accept?(): void;
accept?(keyMods: IKeyMods): void;
/**
* A method that will be executed when a button of the pick item was
......@@ -177,10 +179,12 @@ export interface IPickerQuickAccessItem extends IQuickPickItem {
* @param buttonIndex index of the button of the item that
* was clicked.
*
* @param the state of modifier keys when the button was triggered.
*
* @returns a value that indicates what should happen after the trigger
* which can be a `Promise` for long running operations.
*/
trigger?(buttonIndex: number): TriggerAction | Promise<TriggerAction>;
trigger?(buttonIndex: number, keyMods: IKeyMods): TriggerAction | Promise<TriggerAction>;
}
export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem> implements IQuickAccessProvider {
......@@ -232,7 +236,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
const [item] = picker.selectedItems;
if (typeof item?.accept === 'function') {
picker.hide();
item.accept();
item.accept(picker.keyMods);
}
}));
......@@ -241,7 +245,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
if (typeof item.trigger === 'function') {
const buttonIndex = item.buttons?.indexOf(button) ?? -1;
if (buttonIndex >= 0) {
const result = item.trigger(buttonIndex);
const result = item.trigger(buttonIndex, picker.keyMods);
const action = (typeof result === 'number') ? result : await result;
if (token.isCancellationRequested) {
......
......@@ -11,12 +11,17 @@ import { IRange } from 'vs/editor/common/core/range';
import { AbstractGotoLineQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoLineQuickAccess';
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProvider {
protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange;
constructor(@IEditorService private readonly editorService: IEditorService) {
constructor(
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
}
......@@ -25,10 +30,14 @@ export class GotoLineQuickAccessProvider extends AbstractGotoLineQuickAccessProv
}
protected gotoLine(editor: IEditor, range: IRange, keyMods: IKeyMods): void {
const enablePreviewFromQuickAccess = this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor.enablePreviewFromQuickOpen;
// Check for sideBySide use
if (keyMods.ctrlCmd && this.editorService.activeEditor) {
this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP);
this.editorService.openEditor(this.editorService.activeEditor, {
selection: range,
pinned: keyMods.alt || !enablePreviewFromQuickAccess
}, SIDE_GROUP);
}
// Otherwise let parent handle it
......
......@@ -11,12 +11,17 @@ import { IRange } from 'vs/editor/common/core/range';
import { Registry } from 'vs/platform/registry/common/platform';
import { IQuickAccessRegistry, Extensions } from 'vs/platform/quickinput/common/quickAccess';
import { AbstractGotoSymbolQuickAccessProvider } from 'vs/editor/contrib/quickAccess/gotoSymbolQuickAccess';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccessProvider {
protected readonly onDidActiveTextEditorControlChange = this.editorService.onDidActiveEditorChange;
constructor(@IEditorService private readonly editorService: IEditorService) {
constructor(
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super();
}
......@@ -25,10 +30,14 @@ export class GotoSymbolQuickAccessProvider extends AbstractGotoSymbolQuickAccess
}
protected gotoSymbol(editor: IEditor, range: IRange, keyMods: IKeyMods): void {
const enablePreviewFromQuickAccess = this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor.enablePreviewFromQuickOpen;
// Check for sideBySide use
if (keyMods.ctrlCmd && this.editorService.activeEditor) {
this.editorService.openEditor(this.editorService.activeEditor, { selection: range, pinned: keyMods.alt }, SIDE_GROUP);
this.editorService.openEditor(this.editorService.activeEditor, {
selection: range,
pinned: keyMods.alt || !enablePreviewFromQuickAccess
}, SIDE_GROUP);
}
// Otherwise let parent handle it
......@@ -43,7 +52,7 @@ Registry.as<IQuickAccessRegistry>(Extensions.Quickaccess).registerQuickAccessPro
prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX,
placeholder: localize('gotoSymbolQuickAccessPlaceholder', "Type the name of a symbol to go to."),
helpEntries: [
{ description: localize('gotoSymbolQuickAccess', "Go to Symol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true },
{ description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true }
{ description: localize('gotoSymbolQuickAccess', "Go to Symbol in Editor"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX, needsEditor: true },
{ description: localize('gotoSymbolByCategoryQuickAccess', "Go to Symbol in Editor by Category"), prefix: AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY, needsEditor: true }
]
});
......@@ -52,7 +52,7 @@ export class StartDebugQuickAccessProvider extends PickerQuickAccessProvider<IPi
highlights: { label: highlights },
buttons: [{
iconClass: 'codicon-gear',
tooltip: localize('customizeTask', "Configure Launch Configuration")
tooltip: localize('customizeLaunchConfig', "Configure Launch Configuration")
}],
trigger: () => {
config.launch.openConfigFile(false, false);
......
......@@ -55,6 +55,8 @@ import { assertType, assertIsDefined } from 'vs/base/common/types';
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
import { SearchEditor } from 'vs/workbench/contrib/searchEditor/browser/searchEditor';
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/platform/quickinput/common/quickAccess';
import { SymbolsQuickAccessProvider } from 'vs/workbench/contrib/search/browser/symbolsQuickAccess';
registerSingleton(ISearchWorkbenchService, SearchWorkbenchService, true);
registerSingleton(ISearchHistoryService, SearchHistoryService, true);
......@@ -651,6 +653,16 @@ Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen).registerQuickOpen
)
);
// Register Quick Access Handler
Registry.as<IQuickAccessRegistry>(QuickAccessExtensions.Quickaccess).registerQuickAccessProvider({
ctor: SymbolsQuickAccessProvider,
prefix: SymbolsQuickAccessProvider.PREFIX,
placeholder: nls.localize('symbolsQuickAccessPlaceholder', "Type the name of a symbol to open."),
contextKey: 'inWorkspaceSymbolsPicker',
helpEntries: [{ description: nls.localize('symbolsQuickAccess', "Go to Symbol in Workspace"), needsEditor: false }]
});
// Configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { IPickerQuickAccessItem, PickerQuickAccessProvider, TriggerAction } from 'vs/platform/quickinput/common/quickAccess';
import { fuzzyScore, createMatches, FuzzyScore } from 'vs/base/common/filters';
import { stripWildcards } from 'vs/base/common/strings';
import { CancellationToken } from 'vs/base/common/cancellation';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ThrottledDelayer } from 'vs/base/common/async';
import { getWorkspaceSymbols, IWorkspaceSymbol, IWorkspaceSymbolProvider } from 'vs/workbench/contrib/search/common/search';
import { SymbolKinds, SymbolTag } from 'vs/editor/common/modes';
import { basename } from 'vs/base/common/resources';
import { ILabelService } from 'vs/platform/label/common/label';
import { Schemas } from 'vs/base/common/network';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
import { Range } from 'vs/editor/common/core/range';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
import { IKeyMods } from 'vs/platform/quickinput/common/quickInput';
interface ISymbolsQuickPickItem extends IPickerQuickAccessItem {
score: FuzzyScore;
symbol: IWorkspaceSymbol;
}
export class SymbolsQuickAccessProvider extends PickerQuickAccessProvider<ISymbolsQuickPickItem> {
static PREFIX = '#';
private static readonly TYPING_SEARCH_DELAY = 200; // this delay accommodates for the user typing a word and then stops typing to start searching
private delayer = new ThrottledDelayer<ISymbolsQuickPickItem[]>(SymbolsQuickAccessProvider.TYPING_SEARCH_DELAY);
constructor(
@ILabelService private readonly labelService: ILabelService,
@IOpenerService private readonly openerService: IOpenerService,
@IEditorService private readonly editorService: IEditorService,
@IConfigurationService private readonly configurationService: IConfigurationService
) {
super(SymbolsQuickAccessProvider.PREFIX);
}
private get configuration() {
const editorConfig = this.configurationService.getValue<IWorkbenchEditorConfiguration>().workbench.editor;
return {
openEditorPinned: !editorConfig.enablePreviewFromQuickOpen,
openSideBySideDirection: editorConfig.openSideBySideDirection
};
}
protected getPicks(filter: string, disposables: DisposableStore, token: CancellationToken): Promise<Array<ISymbolsQuickPickItem>> {
return this.delayer.trigger(async () => {
if (token.isCancellationRequested) {
return [];
}
return this.doGetSymbolPicks(filter, token);
});
}
private async doGetSymbolPicks(filter: string, token: CancellationToken): Promise<Array<ISymbolsQuickPickItem>> {
const workspaceSymbols = await getWorkspaceSymbols(filter, token);
if (token.isCancellationRequested) {
return [];
}
const symbolPicks: Array<ISymbolsQuickPickItem> = [];
// Normalize filter
const [symbolFilter, containerFilter] = stripWildcards(filter).split(' ') as [string, string | undefined];
const symbolFilterLow = symbolFilter.toLowerCase();
const containerFilterLow = containerFilter?.toLowerCase();
// Convert to symbol picks and apply filtering
const openSideBySideDirection = this.configuration.openSideBySideDirection;
for (const [provider, symbols] of workspaceSymbols) {
for (const symbol of symbols) {
const symbolLabel = symbol.name;
const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`;
let containerLabel: string | undefined = undefined;
if (symbol.location.uri) {
if (symbol.containerName) {
containerLabel = `${symbol.containerName}${basename(symbol.location.uri)}`;
} else {
containerLabel = this.labelService.getUriLabel(symbol.location.uri, { relative: true });
}
}
// Score by symbol
const symbolScore = fuzzyScore(symbolFilter, symbolFilterLow, 0, symbolLabel, symbolLabel.toLowerCase(), 0, true);
let containerScore: FuzzyScore | undefined = undefined;
if (!symbolScore) {
continue;
}
// Score by container if specified
if (containerFilter && containerFilterLow) {
if (containerLabel) {
containerScore = fuzzyScore(containerFilter, containerFilterLow, 0, containerLabel, containerLabel.toLowerCase(), 0, true);
}
if (!containerScore) {
continue;
}
}
const deprecated = symbol.tags ? symbol.tags.indexOf(SymbolTag.Deprecated) >= 0 : false;
symbolPicks.push({
symbol,
score: symbolScore,
label: symbolLabelWithIcon,
ariaLabel: localize('symbolAriaLabel', "{0}, symbols picker", symbolLabel),
highlights: deprecated ? undefined : {
label: createMatches(symbolScore, symbolLabelWithIcon.length - symbolLabel.length /* Readjust matches to account for codicons in label */),
description: createMatches(containerScore)
},
description: containerLabel,
strikethrough: deprecated,
buttons: [
{
iconClass: openSideBySideDirection === 'right' ? 'codicon-split-horizontal' : 'codicon-split-vertical',
tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom")
}
],
accept: async keyMods => this.openSymbol(provider, symbol, token, keyMods),
trigger: async (buttonIndex, keyMods) => {
this.openSymbol(provider, symbol, token, keyMods, true);
return TriggerAction.CLOSE_PICKER;
}
});
}
}
// Sort picks
symbolPicks.sort((symbolA, symbolB) => this.compareSymbols(symbolA, symbolB));
return symbolPicks;
}
private async openSymbol(provider: IWorkspaceSymbolProvider, symbol: IWorkspaceSymbol, token: CancellationToken, keyMods: IKeyMods, forceOpenSideBySide = false): Promise<void> {
// Resolve actual symbol to open for providers that can resolve
let symbolToOpen = symbol;
if (typeof provider.resolveWorkspaceSymbol === 'function' && !symbol.location.range) {
symbolToOpen = await provider.resolveWorkspaceSymbol(symbol, token) || symbol;
if (token.isCancellationRequested) {
return;
}
}
// Open HTTP(s) links with opener service
if (symbolToOpen.location.uri.scheme === Schemas.http || symbolToOpen.location.uri.scheme === Schemas.https) {
this.openerService.open(symbolToOpen.location.uri, { fromUserGesture: true });
}
// Otherwise open as editor
else {
this.editorService.openEditor({
resource: symbolToOpen.location.uri,
options: {
pinned: keyMods.alt || forceOpenSideBySide || this.configuration.openEditorPinned,
selection: symbolToOpen.location.range ? Range.collapseToStart(symbolToOpen.location.range) : undefined
}
}, keyMods.ctrlCmd || forceOpenSideBySide ? SIDE_GROUP : ACTIVE_GROUP);
}
}
private compareSymbols(symbolA: ISymbolsQuickPickItem, symbolB: ISymbolsQuickPickItem): number {
// By score
if (symbolA.score && symbolB.score) {
if (symbolA.score[0] > symbolB.score[0]) {
return -1;
} else if (symbolA.score[0] < symbolB.score[0]) {
return 1;
}
}
// By name
const symbolAName = symbolA.symbol.name.toLowerCase();
const symbolBName = symbolB.symbol.name.toLowerCase();
const res = symbolAName.localeCompare(symbolBName);
if (res !== 0) {
return res;
}
// By kind
const symbolAKind = SymbolKinds.toCssClassName(symbolA.symbol.kind);
const symbolBKind = SymbolKinds.toCssClassName(symbolB.symbol.kind);
return symbolAKind.localeCompare(symbolBKind);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册