UNPKG

monaco-editor

Version:
392 lines (391 loc) 20.6 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var AbstractGotoSymbolQuickAccessProvider_1; import { DeferredPromise } from '../../../../base/common/async.js'; import { CancellationTokenSource } from '../../../../base/common/cancellation.js'; import { Codicon } from '../../../../base/common/codicons.js'; import { ThemeIcon } from '../../../../base/common/themables.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 { SymbolKinds, getAriaLabelForSymbol } from '../../../common/languages.js'; import { IOutlineModelService } from '../../documentSymbols/browser/outlineModel.js'; import { AbstractEditorNavigationQuickAccessProvider } from './editorNavigationQuickAccess.js'; import { localize } from '../../../../nls.js'; import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js'; import { findLast } from '../../../../base/common/arraysFind.js'; let AbstractGotoSymbolQuickAccessProvider = AbstractGotoSymbolQuickAccessProvider_1 = class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider { constructor(_languageFeaturesService, _outlineModelService, options = Object.create(null)) { super(options); this._languageFeaturesService = _languageFeaturesService; this._outlineModelService = _outlineModelService; 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 (this._languageFeaturesService.documentSymbolProvider.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 (async () => { const result = await 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 /* SymbolKind.String */ }]; picker.ariaLabel = label; } async waitForLanguageSymbolRegistry(model, disposables) { if (this._languageFeaturesService.documentSymbolProvider.has(model)) { return true; } const symbolProviderRegistryPromise = new DeferredPromise(); // Resolve promise when registry knows model const symbolProviderListener = disposables.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => { if (this._languageFeaturesService.documentSymbolProvider.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) { var _a; 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 = async (positionToEnclose) => { // 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_1.PREFIX.length).trim()); const items = await this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.token); if (token.isCancellationRequested) { return; } if (items.length > 0) { picker.items = items; if (positionToEnclose && query.original.length === 0) { const candidate = findLast(items, item => Boolean(item.type !== 'separator' && item.range && Range.containsPosition(item.range.decoration, positionToEnclose))); if (candidate) { picker.activeItems = [candidate]; } } } 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(undefined))); updatePickerItems((_a = editor.getSelection()) === null || _a === void 0 ? void 0 : _a.getPosition()); // Reveal and decorate when active item changes disposables.add(picker.onDidChangeActive(() => { const [item] = picker.activeItems; if (item && item.range) { // Reveal editor.revealRangeInCenter(item.range.selection, 0 /* ScrollType.Smooth */); // Decorate this.addDecorations(editor, item.range.decoration); } })); return disposables; } async doGetSymbolPicks(symbolsPromise, query, options, token) { var _a, _b; const symbols = await symbolsPromise; if (token.isCancellationRequested) { return []; } const filterBySymbolKind = query.original.indexOf(AbstractGotoSymbolQuickAccessProvider_1.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 let buttons; const openSideBySideDirection = (_b = (_a = this.options) === null || _a === void 0 ? void 0 : _a.openSideBySideDirection) === null || _b === void 0 ? void 0 : _b.call(_a); if (openSideBySideDirection) { buttons = [{ iconClass: openSideBySideDirection === 'right' ? ThemeIcon.asClassName(Codicon.splitHorizontal) : ThemeIcon.asClassName(Codicon.splitVertical), tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom") }]; } const filteredSymbolPicks = []; for (let index = 0; index < symbols.length; index++) { const symbol = symbols[index]; const symbolLabel = trim(symbol.name); const symbolLabelWithIcon = `$(${SymbolKinds.toIcon(symbol.kind).id}) ${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, { ...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 /* SymbolTag.Deprecated */) >= 0; filteredSymbolPicks.push({ index, kind: symbol.kind, score: symbolScore, label: symbolLabelWithIcon, ariaLabel: getAriaLabelForSymbol(symbol.name, symbol.kind), description: containerLabel, highlights: deprecated ? undefined : { label: symbolMatches, description: containerMatches }, range: { selection: Range.collapseToStart(symbol.selectionRange), decoration: symbol.range }, strikethrough: deprecated, buttons }); } // 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; } async getDocumentSymbols(document, token) { const model = await this._outlineModelService.getOrCreate(document, token); return token.isCancellationRequested ? [] : model.asListOfDocumentSymbols(); } }; AbstractGotoSymbolQuickAccessProvider.PREFIX = '@'; AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX = ':'; AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider_1.PREFIX}${AbstractGotoSymbolQuickAccessProvider_1.SCOPE_PREFIX}`; AbstractGotoSymbolQuickAccessProvider = AbstractGotoSymbolQuickAccessProvider_1 = __decorate([ __param(0, ILanguageFeaturesService), __param(1, IOutlineModelService) ], AbstractGotoSymbolQuickAccessProvider); export { AbstractGotoSymbolQuickAccessProvider }; // #region NLS Helpers const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})"); const NLS_SYMBOL_KIND_CACHE = { [5 /* SymbolKind.Method */]: localize('method', "methods ({0})"), [11 /* SymbolKind.Function */]: localize('function', "functions ({0})"), [8 /* SymbolKind.Constructor */]: localize('_constructor', "constructors ({0})"), [12 /* SymbolKind.Variable */]: localize('variable', "variables ({0})"), [4 /* SymbolKind.Class */]: localize('class', "classes ({0})"), [22 /* SymbolKind.Struct */]: localize('struct', "structs ({0})"), [23 /* SymbolKind.Event */]: localize('event', "events ({0})"), [24 /* SymbolKind.Operator */]: localize('operator', "operators ({0})"), [10 /* SymbolKind.Interface */]: localize('interface', "interfaces ({0})"), [2 /* SymbolKind.Namespace */]: localize('namespace', "namespaces ({0})"), [3 /* SymbolKind.Package */]: localize('package', "packages ({0})"), [25 /* SymbolKind.TypeParameter */]: localize('typeParameter', "type parameters ({0})"), [1 /* SymbolKind.Module */]: localize('modules', "modules ({0})"), [6 /* SymbolKind.Property */]: localize('property', "properties ({0})"), [9 /* SymbolKind.Enum */]: localize('enum', "enumerations ({0})"), [21 /* SymbolKind.EnumMember */]: localize('enumMember', "enumeration members ({0})"), [14 /* SymbolKind.String */]: localize('string', "strings ({0})"), [0 /* SymbolKind.File */]: localize('file', "files ({0})"), [17 /* SymbolKind.Array */]: localize('array', "arrays ({0})"), [15 /* SymbolKind.Number */]: localize('number', "numbers ({0})"), [16 /* SymbolKind.Boolean */]: localize('boolean', "booleans ({0})"), [18 /* SymbolKind.Object */]: localize('object', "objects ({0})"), [19 /* SymbolKind.Key */]: localize('key', "keys ({0})"), [7 /* SymbolKind.Field */]: localize('field', "fields ({0})"), [13 /* SymbolKind.Constant */]: localize('constant', "constants ({0})") }; //#endregion