123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
- return new (P || (P = Promise))(function (resolve, reject) {
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
- step((generator = generator.apply(thisArg, _arguments || [])).next());
- });
- };
- import { DeferredPromise } from '../../../base/common/async.js';
- import { CancellationTokenSource } from '../../../base/common/cancellation.js';
- import { Codicon } from '../../../base/common/codicons.js';
- import { pieceToQuery, prepareQuery, scoreFuzzy2 } from '../../../base/common/fuzzyScorer.js';
- import { Disposable, DisposableStore, toDisposable } from '../../../base/common/lifecycle.js';
- import { format, trim } from '../../../base/common/strings.js';
- import { Range } from '../../common/core/range.js';
- import { DocumentSymbolProviderRegistry, SymbolKinds } from '../../common/modes.js';
- import { OutlineModel } from '../documentSymbols/outlineModel.js';
- import { AbstractEditorNavigationQuickAccessProvider } from './editorNavigationQuickAccess.js';
- import { localize } from '../../../nls.js';
- export class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
- constructor(options = Object.create(null)) {
- super(options);
- this.options = options;
- this.options.canAcceptInBackground = true;
- }
- provideWithoutTextEditor(picker) {
- this.provideLabelPick(picker, localize('cannotRunGotoSymbolWithoutEditor', "To go to a symbol, first open a text editor with symbol information."));
- return Disposable.None;
- }
- provideWithTextEditor(context, picker, token) {
- const editor = context.editor;
- const model = this.getModel(editor);
- if (!model) {
- return Disposable.None;
- }
- // Provide symbols from model if available in registry
- if (DocumentSymbolProviderRegistry.has(model)) {
- return this.doProvideWithEditorSymbols(context, model, picker, token);
- }
- // Otherwise show an entry for a model without registry
- // But give a chance to resolve the symbols at a later
- // point if possible
- return this.doProvideWithoutEditorSymbols(context, model, picker, token);
- }
- doProvideWithoutEditorSymbols(context, model, picker, token) {
- const disposables = new DisposableStore();
- // Generic pick for not having any symbol information
- this.provideLabelPick(picker, localize('cannotRunGotoSymbolWithoutSymbolProvider', "The active text editor does not provide symbol information."));
- // Wait for changes to the registry and see if eventually
- // we do get symbols. This can happen if the picker is opened
- // very early after the model has loaded but before the
- // language registry is ready.
- // https://github.com/microsoft/vscode/issues/70607
- (() => __awaiter(this, void 0, void 0, function* () {
- const result = yield this.waitForLanguageSymbolRegistry(model, disposables);
- if (!result || token.isCancellationRequested) {
- return;
- }
- disposables.add(this.doProvideWithEditorSymbols(context, model, picker, token));
- }))();
- return disposables;
- }
- provideLabelPick(picker, label) {
- picker.items = [{ label, index: 0, kind: 14 /* String */ }];
- picker.ariaLabel = label;
- }
- waitForLanguageSymbolRegistry(model, disposables) {
- return __awaiter(this, void 0, void 0, function* () {
- if (DocumentSymbolProviderRegistry.has(model)) {
- return true;
- }
- const symbolProviderRegistryPromise = new DeferredPromise();
- // Resolve promise when registry knows model
- const symbolProviderListener = disposables.add(DocumentSymbolProviderRegistry.onDidChange(() => {
- if (DocumentSymbolProviderRegistry.has(model)) {
- symbolProviderListener.dispose();
- symbolProviderRegistryPromise.complete(true);
- }
- }));
- // Resolve promise when we get disposed too
- disposables.add(toDisposable(() => symbolProviderRegistryPromise.complete(false)));
- return symbolProviderRegistryPromise.p;
- });
- }
- doProvideWithEditorSymbols(context, model, picker, token) {
- const editor = context.editor;
- const disposables = new DisposableStore();
- // Goto symbol once picked
- disposables.add(picker.onDidAccept(event => {
- const [item] = picker.selectedItems;
- if (item && item.range) {
- this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground });
- if (!event.inBackground) {
- picker.hide();
- }
- }
- }));
- // Goto symbol side by side if enabled
- disposables.add(picker.onDidTriggerItemButton(({ item }) => {
- if (item && item.range) {
- this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, forceSideBySide: true });
- picker.hide();
- }
- }));
- // Resolve symbols from document once and reuse this
- // request for all filtering and typing then on
- const symbolsPromise = this.getDocumentSymbols(model, token);
- // Set initial picks and update on type
- let picksCts = undefined;
- const updatePickerItems = () => __awaiter(this, void 0, void 0, function* () {
- // Cancel any previous ask for picks and busy
- picksCts === null || picksCts === void 0 ? void 0 : picksCts.dispose(true);
- picker.busy = false;
- // Create new cancellation source for this run
- picksCts = new CancellationTokenSource(token);
- // Collect symbol picks
- picker.busy = true;
- try {
- const query = prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim());
- const items = yield this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.token);
- if (token.isCancellationRequested) {
- return;
- }
- if (items.length > 0) {
- picker.items = items;
- }
- else {
- if (query.original.length > 0) {
- this.provideLabelPick(picker, localize('noMatchingSymbolResults', "No matching editor symbols"));
- }
- else {
- this.provideLabelPick(picker, localize('noSymbolResults', "No editor symbols"));
- }
- }
- }
- finally {
- if (!token.isCancellationRequested) {
- picker.busy = false;
- }
- }
- });
- disposables.add(picker.onDidChangeValue(() => updatePickerItems()));
- updatePickerItems();
- // Reveal and decorate when active item changes
- // However, ignore the very first event so that
- // opening the picker is not immediately revealing
- // and decorating the first entry.
- let ignoreFirstActiveEvent = true;
- disposables.add(picker.onDidChangeActive(() => {
- const [item] = picker.activeItems;
- if (item && item.range) {
- if (ignoreFirstActiveEvent) {
- ignoreFirstActiveEvent = false;
- return;
- }
- // Reveal
- editor.revealRangeInCenter(item.range.selection, 0 /* Smooth */);
- // Decorate
- this.addDecorations(editor, item.range.decoration);
- }
- }));
- return disposables;
- }
- doGetSymbolPicks(symbolsPromise, query, options, token) {
- return __awaiter(this, void 0, void 0, function* () {
- const symbols = yield symbolsPromise;
- if (token.isCancellationRequested) {
- return [];
- }
- const filterBySymbolKind = query.original.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0;
- const filterPos = filterBySymbolKind ? 1 : 0;
- // Split between symbol and container query
- let symbolQuery;
- let containerQuery;
- if (query.values && query.values.length > 1) {
- symbolQuery = pieceToQuery(query.values[0]); // symbol: only match on first part
- containerQuery = pieceToQuery(query.values.slice(1)); // container: match on all but first parts
- }
- else {
- symbolQuery = query;
- }
- // Convert to symbol picks and apply filtering
- const filteredSymbolPicks = [];
- for (let index = 0; index < symbols.length; index++) {
- const symbol = symbols[index];
- const symbolLabel = trim(symbol.name);
- const symbolLabelWithIcon = `$(symbol-${SymbolKinds.toString(symbol.kind) || 'property'}) ${symbolLabel}`;
- const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length;
- let containerLabel = symbol.containerName;
- if (options === null || options === void 0 ? void 0 : options.extraContainerLabel) {
- if (containerLabel) {
- containerLabel = `${options.extraContainerLabel} • ${containerLabel}`;
- }
- else {
- containerLabel = options.extraContainerLabel;
- }
- }
- let symbolScore = undefined;
- let symbolMatches = undefined;
- let containerScore = undefined;
- let containerMatches = undefined;
- if (query.original.length > filterPos) {
- // First: try to score on the entire query, it is possible that
- // the symbol matches perfectly (e.g. searching for "change log"
- // can be a match on a markdown symbol "change log"). In that
- // case we want to skip the container query altogether.
- let skipContainerQuery = false;
- if (symbolQuery !== query) {
- [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, Object.assign(Object.assign({}, query), { values: undefined /* disable multi-query support */ }), filterPos, symbolLabelIconOffset);
- if (typeof symbolScore === 'number') {
- skipContainerQuery = true; // since we consumed the query, skip any container matching
- }
- }
- // Otherwise: score on the symbol query and match on the container later
- if (typeof symbolScore !== 'number') {
- [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, symbolQuery, filterPos, symbolLabelIconOffset);
- if (typeof symbolScore !== 'number') {
- continue;
- }
- }
- // Score by container if specified
- if (!skipContainerQuery && containerQuery) {
- if (containerLabel && containerQuery.original.length > 0) {
- [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery);
- }
- if (typeof containerScore !== 'number') {
- continue;
- }
- if (typeof symbolScore === 'number') {
- symbolScore += containerScore; // boost symbolScore by containerScore
- }
- }
- }
- const deprecated = symbol.tags && symbol.tags.indexOf(1 /* Deprecated */) >= 0;
- filteredSymbolPicks.push({
- index,
- kind: symbol.kind,
- score: symbolScore,
- label: symbolLabelWithIcon,
- ariaLabel: symbolLabel,
- description: containerLabel,
- highlights: deprecated ? undefined : {
- label: symbolMatches,
- description: containerMatches
- },
- range: {
- selection: Range.collapseToStart(symbol.selectionRange),
- decoration: symbol.range
- },
- strikethrough: deprecated,
- buttons: (() => {
- var _a, _b;
- const openSideBySideDirection = ((_a = this.options) === null || _a === void 0 ? void 0 : _a.openSideBySideDirection) ? (_b = this.options) === null || _b === void 0 ? void 0 : _b.openSideBySideDirection() : undefined;
- if (!openSideBySideDirection) {
- return undefined;
- }
- return [
- {
- iconClass: openSideBySideDirection === 'right' ? Codicon.splitHorizontal.classNames : Codicon.splitVertical.classNames,
- tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom")
- }
- ];
- })()
- });
- }
- // Sort by score
- const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ?
- this.compareByKindAndScore(symbolA, symbolB) :
- this.compareByScore(symbolA, symbolB));
- // Add separator for types
- // - @ only total number of symbols
- // - @: grouped by symbol kind
- let symbolPicks = [];
- if (filterBySymbolKind) {
- let lastSymbolKind = undefined;
- let lastSeparator = undefined;
- let lastSymbolKindCounter = 0;
- function updateLastSeparatorLabel() {
- if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) {
- lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter);
- }
- }
- for (const symbolPick of sortedFilteredSymbolPicks) {
- // Found new kind
- if (lastSymbolKind !== symbolPick.kind) {
- // Update last separator with number of symbols we found for kind
- updateLastSeparatorLabel();
- lastSymbolKind = symbolPick.kind;
- lastSymbolKindCounter = 1;
- // Add new separator for new kind
- lastSeparator = { type: 'separator' };
- symbolPicks.push(lastSeparator);
- }
- // Existing kind, keep counting
- else {
- lastSymbolKindCounter++;
- }
- // Add to final result
- symbolPicks.push(symbolPick);
- }
- // Update last separator with number of symbols we found for kind
- updateLastSeparatorLabel();
- }
- else if (sortedFilteredSymbolPicks.length > 0) {
- symbolPicks = [
- { label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' },
- ...sortedFilteredSymbolPicks
- ];
- }
- return symbolPicks;
- });
- }
- compareByScore(symbolA, symbolB) {
- if (typeof symbolA.score !== 'number' && typeof symbolB.score === 'number') {
- return 1;
- }
- else if (typeof symbolA.score === 'number' && typeof symbolB.score !== 'number') {
- return -1;
- }
- if (typeof symbolA.score === 'number' && typeof symbolB.score === 'number') {
- if (symbolA.score > symbolB.score) {
- return -1;
- }
- else if (symbolA.score < symbolB.score) {
- return 1;
- }
- }
- if (symbolA.index < symbolB.index) {
- return -1;
- }
- else if (symbolA.index > symbolB.index) {
- return 1;
- }
- return 0;
- }
- compareByKindAndScore(symbolA, symbolB) {
- const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND;
- const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND;
- // Sort by type first if scoped search
- const result = kindA.localeCompare(kindB);
- if (result === 0) {
- return this.compareByScore(symbolA, symbolB);
- }
- return result;
- }
- getDocumentSymbols(document, token) {
- return __awaiter(this, void 0, void 0, function* () {
- const model = yield OutlineModel.create(document, token);
- return token.isCancellationRequested ? [] : model.asListOfDocumentSymbols();
- });
- }
- }
- AbstractGotoSymbolQuickAccessProvider.PREFIX = '@';
- AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX = ':';
- AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`;
- // #region NLS Helpers
- const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})");
- const NLS_SYMBOL_KIND_CACHE = {
- [5 /* Method */]: localize('method', "methods ({0})"),
- [11 /* Function */]: localize('function', "functions ({0})"),
- [8 /* Constructor */]: localize('_constructor', "constructors ({0})"),
- [12 /* Variable */]: localize('variable', "variables ({0})"),
- [4 /* Class */]: localize('class', "classes ({0})"),
- [22 /* Struct */]: localize('struct', "structs ({0})"),
- [23 /* Event */]: localize('event', "events ({0})"),
- [24 /* Operator */]: localize('operator', "operators ({0})"),
- [10 /* Interface */]: localize('interface', "interfaces ({0})"),
- [2 /* Namespace */]: localize('namespace', "namespaces ({0})"),
- [3 /* Package */]: localize('package', "packages ({0})"),
- [25 /* TypeParameter */]: localize('typeParameter', "type parameters ({0})"),
- [1 /* Module */]: localize('modules', "modules ({0})"),
- [6 /* Property */]: localize('property', "properties ({0})"),
- [9 /* Enum */]: localize('enum', "enumerations ({0})"),
- [21 /* EnumMember */]: localize('enumMember', "enumeration members ({0})"),
- [14 /* String */]: localize('string', "strings ({0})"),
- [0 /* File */]: localize('file', "files ({0})"),
- [17 /* Array */]: localize('array', "arrays ({0})"),
- [15 /* Number */]: localize('number', "numbers ({0})"),
- [16 /* Boolean */]: localize('boolean', "booleans ({0})"),
- [18 /* Object */]: localize('object', "objects ({0})"),
- [19 /* Key */]: localize('key', "keys ({0})"),
- [7 /* Field */]: localize('field', "fields ({0})"),
- [13 /* Constant */]: localize('constant', "constants ({0})")
- };
- //#endregion
|