Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/linkify/vscode-node/symbolLinkifier.ts
13399 views
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
6
import * as vscode from 'vscode';
7
import { IFileSystemService } from '../../../platform/filesystem/common/fileSystemService';
8
import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
9
import { collapseRangeToStart } from '../../../util/common/range';
10
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
11
import { SymbolInformation, Uri } from '../../../vscodeTypes';
12
import { LinkifiedPart, LinkifiedText, LinkifySymbolAnchor } from '../common/linkifiedText';
13
import { IContributedLinkifier, LinkifierContext } from '../common/linkifyService';
14
import { findBestSymbolByPath } from './findSymbol';
15
16
/**
17
* Linkifies symbol paths in responses. For example:
18
*
19
* ```
20
* [`symbol`](file.md)
21
* ```
22
*/
23
export class SymbolLinkifier implements IContributedLinkifier {
24
25
constructor(
26
@IFileSystemService private readonly fileSystem: IFileSystemService,
27
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
28
) { }
29
30
async linkify(
31
text: string,
32
context: LinkifierContext,
33
token: CancellationToken,
34
): Promise<LinkifiedText | undefined> {
35
const workspaceFolders = this.workspaceService.getWorkspaceFolders();
36
if (!workspaceFolders.length) {
37
return;
38
}
39
40
const out: LinkifiedPart[] = [];
41
42
let endLastMatch = 0;
43
for (const match of text.matchAll(/\[`([^`\[\]]+?)`]\((\S+?\.\w+)\)/g)) {
44
const prefix = text.slice(endLastMatch, match.index);
45
if (prefix) {
46
out.push(prefix);
47
}
48
49
const symbolText = match[1];
50
let symbolPath = match[2];
51
try {
52
symbolPath = decodeURIComponent(symbolPath);
53
} catch {
54
// noop
55
}
56
57
const resolvedUri = await this.resolveInWorkspace(symbolPath, workspaceFolders);
58
59
if (resolvedUri) {
60
const info: SymbolInformation = {
61
name: symbolText,
62
containerName: '',
63
kind: vscode.SymbolKind.Variable,
64
location: new vscode.Location(resolvedUri, new vscode.Position(0, 0))
65
};
66
67
out.push(new LinkifySymbolAnchor(info, async (token) => {
68
let symbols: Array<vscode.SymbolInformation | vscode.DocumentSymbol> | undefined;
69
try {
70
symbols = await vscode.commands.executeCommand<Array<vscode.SymbolInformation | vscode.DocumentSymbol> | undefined>('vscode.executeDocumentSymbolProvider', resolvedUri);
71
} catch (e) {
72
// Noop
73
}
74
75
if (symbols?.length) {
76
const matchingSymbol = findBestSymbolByPath(symbols, symbolText);
77
if (matchingSymbol) {
78
info.kind = matchingSymbol.kind;
79
80
// Not a real instance of 'vscode.DocumentSymbol' so use cast to check
81
if ((matchingSymbol as vscode.DocumentSymbol).children) {
82
const symbol = matchingSymbol as vscode.DocumentSymbol;
83
info.location = new vscode.Location(resolvedUri, collapseRangeToStart(symbol.selectionRange));
84
} else {
85
const symbol = matchingSymbol as vscode.SymbolInformation;
86
info.location = new vscode.Location(symbol.location.uri, collapseRangeToStart(symbol.location.range));
87
}
88
}
89
}
90
return info;
91
}));
92
} else {
93
out.push('`' + symbolText + '`');
94
}
95
96
endLastMatch = match.index + match[0].length;
97
}
98
99
const suffix = text.slice(endLastMatch);
100
if (suffix) {
101
out.push(suffix);
102
}
103
104
return { parts: out };
105
}
106
107
private async resolveInWorkspace(symbolPath: string, workspaceFolders: readonly Uri[]): Promise<Uri | undefined> {
108
const candidates = workspaceFolders.map(folder => Uri.joinPath(folder, symbolPath));
109
const results = await Promise.all(candidates.map(uri => this.exists(uri).then(exists => exists ? uri : undefined)));
110
return results.find((uri): uri is Uri => uri !== undefined);
111
}
112
113
private async exists(uri: Uri) {
114
try {
115
await this.fileSystem.stat(uri);
116
return true;
117
} catch {
118
return false;
119
}
120
}
121
}
122
123