70fffbc86bd543cf1e25eb04c4c3e092cf3a0de8889284191cf5df4de88e851edcb36db8f1061e33bcd1eabb4ac9334170504f72a5653a1a2c636c6d7c5f3e 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
  6. var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  7. if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
  8. 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;
  9. return c > 3 && r && Object.defineProperty(target, key, r), r;
  10. };
  11. var __param = (this && this.__param) || function (paramIndex, decorator) {
  12. return function (target, key) { decorator(target, key, paramIndex); }
  13. };
  14. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  15. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  16. return new (P || (P = Promise))(function (resolve, reject) {
  17. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  18. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  19. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  20. step((generator = generator.apply(thisArg, _arguments || [])).next());
  21. });
  22. };
  23. import { DeferredPromise } from '../../../../base/common/async.js';
  24. import { CancellationTokenSource } from '../../../../base/common/cancellation.js';
  25. import { Codicon } from '../../../../base/common/codicons.js';
  26. import { pieceToQuery, prepareQuery, scoreFuzzy2 } from '../../../../base/common/fuzzyScorer.js';
  27. import { Disposable, DisposableStore, toDisposable } from '../../../../base/common/lifecycle.js';
  28. import { format, trim } from '../../../../base/common/strings.js';
  29. import { Range } from '../../../common/core/range.js';
  30. import { SymbolKinds } from '../../../common/languages.js';
  31. import { IOutlineModelService } from '../../documentSymbols/browser/outlineModel.js';
  32. import { AbstractEditorNavigationQuickAccessProvider } from './editorNavigationQuickAccess.js';
  33. import { localize } from '../../../../nls.js';
  34. import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
  35. import { findLast } from '../../../../base/common/arrays.js';
  36. let AbstractGotoSymbolQuickAccessProvider = class AbstractGotoSymbolQuickAccessProvider extends AbstractEditorNavigationQuickAccessProvider {
  37. constructor(_languageFeaturesService, _outlineModelService, options = Object.create(null)) {
  38. super(options);
  39. this._languageFeaturesService = _languageFeaturesService;
  40. this._outlineModelService = _outlineModelService;
  41. this.options = options;
  42. this.options.canAcceptInBackground = true;
  43. }
  44. provideWithoutTextEditor(picker) {
  45. this.provideLabelPick(picker, localize('cannotRunGotoSymbolWithoutEditor', "To go to a symbol, first open a text editor with symbol information."));
  46. return Disposable.None;
  47. }
  48. provideWithTextEditor(context, picker, token) {
  49. const editor = context.editor;
  50. const model = this.getModel(editor);
  51. if (!model) {
  52. return Disposable.None;
  53. }
  54. // Provide symbols from model if available in registry
  55. if (this._languageFeaturesService.documentSymbolProvider.has(model)) {
  56. return this.doProvideWithEditorSymbols(context, model, picker, token);
  57. }
  58. // Otherwise show an entry for a model without registry
  59. // But give a chance to resolve the symbols at a later
  60. // point if possible
  61. return this.doProvideWithoutEditorSymbols(context, model, picker, token);
  62. }
  63. doProvideWithoutEditorSymbols(context, model, picker, token) {
  64. const disposables = new DisposableStore();
  65. // Generic pick for not having any symbol information
  66. this.provideLabelPick(picker, localize('cannotRunGotoSymbolWithoutSymbolProvider', "The active text editor does not provide symbol information."));
  67. // Wait for changes to the registry and see if eventually
  68. // we do get symbols. This can happen if the picker is opened
  69. // very early after the model has loaded but before the
  70. // language registry is ready.
  71. // https://github.com/microsoft/vscode/issues/70607
  72. (() => __awaiter(this, void 0, void 0, function* () {
  73. const result = yield this.waitForLanguageSymbolRegistry(model, disposables);
  74. if (!result || token.isCancellationRequested) {
  75. return;
  76. }
  77. disposables.add(this.doProvideWithEditorSymbols(context, model, picker, token));
  78. }))();
  79. return disposables;
  80. }
  81. provideLabelPick(picker, label) {
  82. picker.items = [{ label, index: 0, kind: 14 /* SymbolKind.String */ }];
  83. picker.ariaLabel = label;
  84. }
  85. waitForLanguageSymbolRegistry(model, disposables) {
  86. return __awaiter(this, void 0, void 0, function* () {
  87. if (this._languageFeaturesService.documentSymbolProvider.has(model)) {
  88. return true;
  89. }
  90. const symbolProviderRegistryPromise = new DeferredPromise();
  91. // Resolve promise when registry knows model
  92. const symbolProviderListener = disposables.add(this._languageFeaturesService.documentSymbolProvider.onDidChange(() => {
  93. if (this._languageFeaturesService.documentSymbolProvider.has(model)) {
  94. symbolProviderListener.dispose();
  95. symbolProviderRegistryPromise.complete(true);
  96. }
  97. }));
  98. // Resolve promise when we get disposed too
  99. disposables.add(toDisposable(() => symbolProviderRegistryPromise.complete(false)));
  100. return symbolProviderRegistryPromise.p;
  101. });
  102. }
  103. doProvideWithEditorSymbols(context, model, picker, token) {
  104. var _a;
  105. const editor = context.editor;
  106. const disposables = new DisposableStore();
  107. // Goto symbol once picked
  108. disposables.add(picker.onDidAccept(event => {
  109. const [item] = picker.selectedItems;
  110. if (item && item.range) {
  111. this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground });
  112. if (!event.inBackground) {
  113. picker.hide();
  114. }
  115. }
  116. }));
  117. // Goto symbol side by side if enabled
  118. disposables.add(picker.onDidTriggerItemButton(({ item }) => {
  119. if (item && item.range) {
  120. this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, forceSideBySide: true });
  121. picker.hide();
  122. }
  123. }));
  124. // Resolve symbols from document once and reuse this
  125. // request for all filtering and typing then on
  126. const symbolsPromise = this.getDocumentSymbols(model, token);
  127. // Set initial picks and update on type
  128. let picksCts = undefined;
  129. const updatePickerItems = (positionToEnclose) => __awaiter(this, void 0, void 0, function* () {
  130. // Cancel any previous ask for picks and busy
  131. picksCts === null || picksCts === void 0 ? void 0 : picksCts.dispose(true);
  132. picker.busy = false;
  133. // Create new cancellation source for this run
  134. picksCts = new CancellationTokenSource(token);
  135. // Collect symbol picks
  136. picker.busy = true;
  137. try {
  138. const query = prepareQuery(picker.value.substr(AbstractGotoSymbolQuickAccessProvider.PREFIX.length).trim());
  139. const items = yield this.doGetSymbolPicks(symbolsPromise, query, undefined, picksCts.token);
  140. if (token.isCancellationRequested) {
  141. return;
  142. }
  143. if (items.length > 0) {
  144. picker.items = items;
  145. if (positionToEnclose && query.original.length === 0) {
  146. const candidate = findLast(items, item => Boolean(item.type !== 'separator' && item.range && Range.containsPosition(item.range.decoration, positionToEnclose)));
  147. if (candidate) {
  148. picker.activeItems = [candidate];
  149. }
  150. }
  151. }
  152. else {
  153. if (query.original.length > 0) {
  154. this.provideLabelPick(picker, localize('noMatchingSymbolResults', "No matching editor symbols"));
  155. }
  156. else {
  157. this.provideLabelPick(picker, localize('noSymbolResults', "No editor symbols"));
  158. }
  159. }
  160. }
  161. finally {
  162. if (!token.isCancellationRequested) {
  163. picker.busy = false;
  164. }
  165. }
  166. });
  167. disposables.add(picker.onDidChangeValue(() => updatePickerItems(undefined)));
  168. updatePickerItems((_a = editor.getSelection()) === null || _a === void 0 ? void 0 : _a.getPosition());
  169. // Reveal and decorate when active item changes
  170. // However, ignore the very first two events so that
  171. // opening the picker is not immediately revealing
  172. // and decorating the first entry.
  173. let ignoreFirstActiveEvent = 2;
  174. disposables.add(picker.onDidChangeActive(() => {
  175. const [item] = picker.activeItems;
  176. if (item && item.range) {
  177. if (ignoreFirstActiveEvent-- > 0) {
  178. return;
  179. }
  180. // Reveal
  181. editor.revealRangeInCenter(item.range.selection, 0 /* ScrollType.Smooth */);
  182. // Decorate
  183. this.addDecorations(editor, item.range.decoration);
  184. }
  185. }));
  186. return disposables;
  187. }
  188. doGetSymbolPicks(symbolsPromise, query, options, token) {
  189. return __awaiter(this, void 0, void 0, function* () {
  190. const symbols = yield symbolsPromise;
  191. if (token.isCancellationRequested) {
  192. return [];
  193. }
  194. const filterBySymbolKind = query.original.indexOf(AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX) === 0;
  195. const filterPos = filterBySymbolKind ? 1 : 0;
  196. // Split between symbol and container query
  197. let symbolQuery;
  198. let containerQuery;
  199. if (query.values && query.values.length > 1) {
  200. symbolQuery = pieceToQuery(query.values[0]); // symbol: only match on first part
  201. containerQuery = pieceToQuery(query.values.slice(1)); // container: match on all but first parts
  202. }
  203. else {
  204. symbolQuery = query;
  205. }
  206. // Convert to symbol picks and apply filtering
  207. const filteredSymbolPicks = [];
  208. for (let index = 0; index < symbols.length; index++) {
  209. const symbol = symbols[index];
  210. const symbolLabel = trim(symbol.name);
  211. const symbolLabelWithIcon = `$(${SymbolKinds.toIcon(symbol.kind).id}) ${symbolLabel}`;
  212. const symbolLabelIconOffset = symbolLabelWithIcon.length - symbolLabel.length;
  213. let containerLabel = symbol.containerName;
  214. if (options === null || options === void 0 ? void 0 : options.extraContainerLabel) {
  215. if (containerLabel) {
  216. containerLabel = `${options.extraContainerLabel} • ${containerLabel}`;
  217. }
  218. else {
  219. containerLabel = options.extraContainerLabel;
  220. }
  221. }
  222. let symbolScore = undefined;
  223. let symbolMatches = undefined;
  224. let containerScore = undefined;
  225. let containerMatches = undefined;
  226. if (query.original.length > filterPos) {
  227. // First: try to score on the entire query, it is possible that
  228. // the symbol matches perfectly (e.g. searching for "change log"
  229. // can be a match on a markdown symbol "change log"). In that
  230. // case we want to skip the container query altogether.
  231. let skipContainerQuery = false;
  232. if (symbolQuery !== query) {
  233. [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, Object.assign(Object.assign({}, query), { values: undefined /* disable multi-query support */ }), filterPos, symbolLabelIconOffset);
  234. if (typeof symbolScore === 'number') {
  235. skipContainerQuery = true; // since we consumed the query, skip any container matching
  236. }
  237. }
  238. // Otherwise: score on the symbol query and match on the container later
  239. if (typeof symbolScore !== 'number') {
  240. [symbolScore, symbolMatches] = scoreFuzzy2(symbolLabelWithIcon, symbolQuery, filterPos, symbolLabelIconOffset);
  241. if (typeof symbolScore !== 'number') {
  242. continue;
  243. }
  244. }
  245. // Score by container if specified
  246. if (!skipContainerQuery && containerQuery) {
  247. if (containerLabel && containerQuery.original.length > 0) {
  248. [containerScore, containerMatches] = scoreFuzzy2(containerLabel, containerQuery);
  249. }
  250. if (typeof containerScore !== 'number') {
  251. continue;
  252. }
  253. if (typeof symbolScore === 'number') {
  254. symbolScore += containerScore; // boost symbolScore by containerScore
  255. }
  256. }
  257. }
  258. const deprecated = symbol.tags && symbol.tags.indexOf(1 /* SymbolTag.Deprecated */) >= 0;
  259. filteredSymbolPicks.push({
  260. index,
  261. kind: symbol.kind,
  262. score: symbolScore,
  263. label: symbolLabelWithIcon,
  264. ariaLabel: symbolLabel,
  265. description: containerLabel,
  266. highlights: deprecated ? undefined : {
  267. label: symbolMatches,
  268. description: containerMatches
  269. },
  270. range: {
  271. selection: Range.collapseToStart(symbol.selectionRange),
  272. decoration: symbol.range
  273. },
  274. strikethrough: deprecated,
  275. buttons: (() => {
  276. var _a, _b;
  277. 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;
  278. if (!openSideBySideDirection) {
  279. return undefined;
  280. }
  281. return [
  282. {
  283. iconClass: openSideBySideDirection === 'right' ? Codicon.splitHorizontal.classNames : Codicon.splitVertical.classNames,
  284. tooltip: openSideBySideDirection === 'right' ? localize('openToSide', "Open to the Side") : localize('openToBottom', "Open to the Bottom")
  285. }
  286. ];
  287. })()
  288. });
  289. }
  290. // Sort by score
  291. const sortedFilteredSymbolPicks = filteredSymbolPicks.sort((symbolA, symbolB) => filterBySymbolKind ?
  292. this.compareByKindAndScore(symbolA, symbolB) :
  293. this.compareByScore(symbolA, symbolB));
  294. // Add separator for types
  295. // - @ only total number of symbols
  296. // - @: grouped by symbol kind
  297. let symbolPicks = [];
  298. if (filterBySymbolKind) {
  299. let lastSymbolKind = undefined;
  300. let lastSeparator = undefined;
  301. let lastSymbolKindCounter = 0;
  302. function updateLastSeparatorLabel() {
  303. if (lastSeparator && typeof lastSymbolKind === 'number' && lastSymbolKindCounter > 0) {
  304. lastSeparator.label = format(NLS_SYMBOL_KIND_CACHE[lastSymbolKind] || FALLBACK_NLS_SYMBOL_KIND, lastSymbolKindCounter);
  305. }
  306. }
  307. for (const symbolPick of sortedFilteredSymbolPicks) {
  308. // Found new kind
  309. if (lastSymbolKind !== symbolPick.kind) {
  310. // Update last separator with number of symbols we found for kind
  311. updateLastSeparatorLabel();
  312. lastSymbolKind = symbolPick.kind;
  313. lastSymbolKindCounter = 1;
  314. // Add new separator for new kind
  315. lastSeparator = { type: 'separator' };
  316. symbolPicks.push(lastSeparator);
  317. }
  318. // Existing kind, keep counting
  319. else {
  320. lastSymbolKindCounter++;
  321. }
  322. // Add to final result
  323. symbolPicks.push(symbolPick);
  324. }
  325. // Update last separator with number of symbols we found for kind
  326. updateLastSeparatorLabel();
  327. }
  328. else if (sortedFilteredSymbolPicks.length > 0) {
  329. symbolPicks = [
  330. { label: localize('symbols', "symbols ({0})", filteredSymbolPicks.length), type: 'separator' },
  331. ...sortedFilteredSymbolPicks
  332. ];
  333. }
  334. return symbolPicks;
  335. });
  336. }
  337. compareByScore(symbolA, symbolB) {
  338. if (typeof symbolA.score !== 'number' && typeof symbolB.score === 'number') {
  339. return 1;
  340. }
  341. else if (typeof symbolA.score === 'number' && typeof symbolB.score !== 'number') {
  342. return -1;
  343. }
  344. if (typeof symbolA.score === 'number' && typeof symbolB.score === 'number') {
  345. if (symbolA.score > symbolB.score) {
  346. return -1;
  347. }
  348. else if (symbolA.score < symbolB.score) {
  349. return 1;
  350. }
  351. }
  352. if (symbolA.index < symbolB.index) {
  353. return -1;
  354. }
  355. else if (symbolA.index > symbolB.index) {
  356. return 1;
  357. }
  358. return 0;
  359. }
  360. compareByKindAndScore(symbolA, symbolB) {
  361. const kindA = NLS_SYMBOL_KIND_CACHE[symbolA.kind] || FALLBACK_NLS_SYMBOL_KIND;
  362. const kindB = NLS_SYMBOL_KIND_CACHE[symbolB.kind] || FALLBACK_NLS_SYMBOL_KIND;
  363. // Sort by type first if scoped search
  364. const result = kindA.localeCompare(kindB);
  365. if (result === 0) {
  366. return this.compareByScore(symbolA, symbolB);
  367. }
  368. return result;
  369. }
  370. getDocumentSymbols(document, token) {
  371. return __awaiter(this, void 0, void 0, function* () {
  372. const model = yield this._outlineModelService.getOrCreate(document, token);
  373. return token.isCancellationRequested ? [] : model.asListOfDocumentSymbols();
  374. });
  375. }
  376. };
  377. AbstractGotoSymbolQuickAccessProvider.PREFIX = '@';
  378. AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX = ':';
  379. AbstractGotoSymbolQuickAccessProvider.PREFIX_BY_CATEGORY = `${AbstractGotoSymbolQuickAccessProvider.PREFIX}${AbstractGotoSymbolQuickAccessProvider.SCOPE_PREFIX}`;
  380. AbstractGotoSymbolQuickAccessProvider = __decorate([
  381. __param(0, ILanguageFeaturesService),
  382. __param(1, IOutlineModelService)
  383. ], AbstractGotoSymbolQuickAccessProvider);
  384. export { AbstractGotoSymbolQuickAccessProvider };
  385. // #region NLS Helpers
  386. const FALLBACK_NLS_SYMBOL_KIND = localize('property', "properties ({0})");
  387. const NLS_SYMBOL_KIND_CACHE = {
  388. [5 /* SymbolKind.Method */]: localize('method', "methods ({0})"),
  389. [11 /* SymbolKind.Function */]: localize('function', "functions ({0})"),
  390. [8 /* SymbolKind.Constructor */]: localize('_constructor', "constructors ({0})"),
  391. [12 /* SymbolKind.Variable */]: localize('variable', "variables ({0})"),
  392. [4 /* SymbolKind.Class */]: localize('class', "classes ({0})"),
  393. [22 /* SymbolKind.Struct */]: localize('struct', "structs ({0})"),
  394. [23 /* SymbolKind.Event */]: localize('event', "events ({0})"),
  395. [24 /* SymbolKind.Operator */]: localize('operator', "operators ({0})"),
  396. [10 /* SymbolKind.Interface */]: localize('interface', "interfaces ({0})"),
  397. [2 /* SymbolKind.Namespace */]: localize('namespace', "namespaces ({0})"),
  398. [3 /* SymbolKind.Package */]: localize('package', "packages ({0})"),
  399. [25 /* SymbolKind.TypeParameter */]: localize('typeParameter', "type parameters ({0})"),
  400. [1 /* SymbolKind.Module */]: localize('modules', "modules ({0})"),
  401. [6 /* SymbolKind.Property */]: localize('property', "properties ({0})"),
  402. [9 /* SymbolKind.Enum */]: localize('enum', "enumerations ({0})"),
  403. [21 /* SymbolKind.EnumMember */]: localize('enumMember', "enumeration members ({0})"),
  404. [14 /* SymbolKind.String */]: localize('string', "strings ({0})"),
  405. [0 /* SymbolKind.File */]: localize('file', "files ({0})"),
  406. [17 /* SymbolKind.Array */]: localize('array', "arrays ({0})"),
  407. [15 /* SymbolKind.Number */]: localize('number', "numbers ({0})"),
  408. [16 /* SymbolKind.Boolean */]: localize('boolean', "booleans ({0})"),
  409. [18 /* SymbolKind.Object */]: localize('object', "objects ({0})"),
  410. [19 /* SymbolKind.Key */]: localize('key', "keys ({0})"),
  411. [7 /* SymbolKind.Field */]: localize('field', "fields ({0})"),
  412. [13 /* SymbolKind.Constant */]: localize('constant', "constants ({0})")
  413. };
  414. //#endregion