Path: blob/main/extensions/copilot/src/extension/linkify/vscode-node/findSymbol.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/4import type * as vscode from 'vscode';56type FoundSymbol = {7symbol: vscode.SymbolInformation | vscode.DocumentSymbol;8matchCount: number;9};1011function findBestSymbol(12symbols: ReadonlyArray<vscode.SymbolInformation | vscode.DocumentSymbol>,13symbolParts: readonly string[]14): FoundSymbol | undefined {15if (!symbolParts.length) {16return;17}1819let bestMatch: FoundSymbol | undefined;20for (const symbol of symbols) {21// TODO: vscode.executeDocumentSymbolProvider doesn't return a real instance of22// vscode.DocumentSymbol so use cast to check for children23if ((symbol as vscode.DocumentSymbol).children) {24let partMatch = symbol.name === symbolParts[0] ? { symbol, matchCount: 1 } : undefined;25if (partMatch) {26const remainingPartMatch = findBestSymbol((symbol as vscode.DocumentSymbol).children, symbolParts.slice(1));27if (remainingPartMatch) {28partMatch = { symbol: remainingPartMatch.symbol, matchCount: partMatch.matchCount + remainingPartMatch.matchCount };29}30}3132const restMatch = findBestSymbol((symbol as vscode.DocumentSymbol).children, symbolParts);33let match: FoundSymbol | undefined;34if (partMatch && restMatch) {35match = partMatch.matchCount >= restMatch.matchCount ? partMatch : restMatch;36} else {37match = partMatch ?? restMatch;38}3940if (match && (!bestMatch || match.matchCount > bestMatch?.matchCount)) {41bestMatch = match;42}43} else { // Is a vscode.SymbolInformation44// For flat symbol information, try to match against symbol parts45// Prefer symbols that appear more to the right (higher index) in the qualified name46// This prioritizes members over classes (e.g., in `TextModel.undo()`, prefer `undo`)47const matchIndex = symbolParts.indexOf(symbol.name);48if (matchIndex !== -1) {49// Higher index = more to the right = higher priority50const match = { symbol, matchCount: matchIndex + 1 };51if (!bestMatch || match.matchCount > bestMatch.matchCount) {52bestMatch = match;53}54}55}56}5758return bestMatch;59}6061/**62* Try to find a symbol in a symbol tree.63*64* This does a fuzzy search of the symbol tree. This means that the symbol parts must appear in order,65* but there can be separated by layers. For example: `a, c` could match on a symbol tree `a -> b -> c`.66* We also always make a best effort to find the symbol even if not all parts match.67* For example with `a, c`, this means we would match on `a -> x -> z` because `a` matched.68*/69export function findBestSymbolByPath(70symbols: ReadonlyArray<vscode.SymbolInformation | vscode.DocumentSymbol>,71symbolPath: string72): vscode.SymbolInformation | vscode.DocumentSymbol | undefined {7374// Prefer an exact match but fallback to breaking up the symbol into parts75return (76findBestSymbol(symbols, [symbolPath]) ?? findBestSymbol(symbols, extractSymbolNamesInCode(symbolPath))77)?.symbol;78}7980/**81* The symbol path may be take a few different forms:82* - Exact name: `foo`, `some symbol name`83* - Name plus signature: `foo()`84* - Qualified name: `foo.bar`85*86* We want just the names without any of the extra punctuation because `symbols` does not include these87*/88function extractSymbolNamesInCode(inlineCode: string): string[] {89// TODO: this assumes the language is JS like.90// It won't handle symbol parts that include spaces or special characters91return Array.from(inlineCode.matchAll(/[#\w$][\w\d$]*/g), x => x[0]);92}939495