Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/referencesAtPosition.tsx
13405 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 { PromptElement, PromptElementProps, PromptPiece, PromptReference, PromptSizing } from '@vscode/prompt-tsx';
7
import type * as vscode from 'vscode';
8
import { TextDocumentSnapshot } from '../../../../platform/editing/common/textDocumentSnapshot';
9
import { isScenarioAutomation } from '../../../../platform/env/common/envService';
10
import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';
11
import { IIgnoreService } from '../../../../platform/ignore/common/ignoreService';
12
import { ILanguageFeaturesService } from '../../../../platform/languages/common/languageFeaturesService';
13
import { ILogService } from '../../../../platform/log/common/logService';
14
import { TreeSitterOffsetRange } from '../../../../platform/parser/node/nodes';
15
import { IParserService, treeSitterOffsetRangeToVSCodeRange, vscodeToTreeSitterOffsetRange } from '../../../../platform/parser/node/parserService';
16
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
17
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
18
import { getLanguage } from '../../../../util/common/languages';
19
import { ExtensionMode, Location, Selection, Uri } from '../../../../vscodeTypes';
20
import { asyncComputeWithTimeBudget } from '../../../context/node/resolvers/selectionContextHelpers';
21
import { determineNodeToDocument } from '../../../prompt/node/definitionAroundCursor';
22
import { CodeBlock } from './safeElements';
23
24
type Props = PromptElementProps<{
25
document: TextDocumentSnapshot;
26
/**
27
* Range of interest for which definitions are to be found.
28
* @remark if not provided, will use active selection in currently active editor
29
*/
30
position: vscode.Position;
31
/**
32
* Timeout for finding implementations in milliseconds. Defaults to 200ms.
33
*/
34
timeoutMs?: number;
35
}>;
36
37
type CodeExcerpt = {
38
languageId: string;
39
uri: Uri;
40
code: string;
41
excerptRange: vscode.Range;
42
};
43
44
/**
45
* @remark Excludes references that are in copilot-ignored files.
46
*/
47
export class ReferencesAtPosition extends PromptElement<Props> {
48
private static DEFAULT_TIMEOUT_MS = 200;
49
50
constructor(
51
props: Props,
52
@IIgnoreService private readonly ignoreService: IIgnoreService,
53
@IVSCodeExtensionContext private readonly extensionContext: IVSCodeExtensionContext,
54
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
55
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
56
@ILogService private readonly logService: ILogService,
57
@IParserService private readonly parserService: IParserService,
58
@ITelemetryService private readonly telemetryService: ITelemetryService,
59
) {
60
super(props);
61
}
62
63
async render(state: void, sizing: PromptSizing) {
64
if (await this.ignoreService.isCopilotIgnored(this.props.document.uri)) {
65
return <ignoredFiles value={[this.props.document.uri]} />;
66
}
67
68
const timeout = this.extensionContext.extensionMode === ExtensionMode.Test && !isScenarioAutomation
69
? 0
70
: (this.props.timeoutMs === undefined ? ReferencesAtPosition.DEFAULT_TIMEOUT_MS : this.props.timeoutMs);
71
72
const [definitions, usages] = await this.findReferences(timeout);
73
74
this.logService.debug(`Found ${definitions.length} implementation(s)/definition(s), ${usages.length} usages`);
75
if (definitions.length > 0) {
76
this.logService.debug(`Implementation(s)/definition(s) found:` + JSON.stringify(definitions, null, '\t'));
77
}
78
if (usages.length > 0) {
79
this.logService.debug(`Usages found:` + JSON.stringify(usages, null, '\t'));
80
}
81
82
return (
83
<>
84
{this.renderCodeExcerpts(`Relevant definition${definitions.length > 1 ? 's' : ''}:`, definitions)}
85
{this.renderCodeExcerpts(`Other usages:`, usages)}
86
</>
87
);
88
}
89
90
private renderCodeExcerpts(title: string, excerts: CodeExcerpt[]): PromptPiece<any, any> | undefined {
91
if (excerts.length > 0) {
92
return (
93
<>
94
{title}<br /><br />
95
{excerts.map(excerpt => {
96
const lineCommentStart = getLanguage(excerpt.languageId).lineComment.start;
97
const filePath = `${lineCommentStart} FILEPATH: ${excerpt.uri.path}`;
98
const code = `${filePath}\n${excerpt.code}`;
99
return (
100
<CodeBlock uri={excerpt.uri} languageId={excerpt.languageId} code={code} references={[new PromptReference(new Location(excerpt.uri, excerpt.excerptRange))]} />
101
);
102
})}
103
<br /><br />
104
</>
105
);
106
}
107
return undefined;
108
}
109
110
private async findReferences(timeoutMs: number): Promise<[CodeExcerpt[], CodeExcerpt[]]> {
111
const { document, position } = this.props;
112
113
const findReference = async () => {
114
try {
115
const refs = await this.languageFeaturesService.getReferences(document.uri, position);
116
this.logService.debug(`Found ${refs.length} references: ` + JSON.stringify(refs, null, '\t'));
117
return refs;
118
} catch (e) {
119
return [];
120
}
121
};
122
123
const foundRefs = await asyncComputeWithTimeBudget(this.logService, this.telemetryService, document, timeoutMs * 3, findReference, []);
124
125
const nonIgnoredDefs = [];
126
const nonIgnoredRefs = [];
127
for (const ref of foundRefs) {
128
if (await this.ignoreService.isCopilotIgnored(ref.uri)) {
129
continue;
130
}
131
132
const docContainingRef = await this.workspaceService.openTextDocumentAndSnapshot(ref.uri);
133
const treeSitterAST = this.parserService.getTreeSitterAST(docContainingRef);
134
if (!treeSitterAST) {
135
continue;
136
}
137
138
const range = vscodeToTreeSitterOffsetRange(ref.range, docContainingRef);
139
const calls = await treeSitterAST.getCallExpressions(range);
140
const functions = await treeSitterAST.getFunctionDefinitions();
141
if (calls.length > 0) {
142
nonIgnoredRefs.push({
143
languageId: docContainingRef.languageId,
144
uri: docContainingRef.uri,
145
code: calls[0].text,
146
excerptRange: treeSitterOffsetRangeToVSCodeRange(docContainingRef, calls[0]),
147
} as CodeExcerpt);
148
} else if (functions.some(f => TreeSitterOffsetRange.doIntersect(f, range))) {
149
// since language service gives us only links to identifiers, expand to whole implementation/definition using tree-sitter
150
const nodeToDocument = await determineNodeToDocument(this.parserService, this.telemetryService, {
151
document: docContainingRef,
152
language: getLanguage(document.languageId),
153
wholeRange: ref.range,
154
selection: new Selection(ref.range.start, ref.range.end),
155
fileIndentInfo: undefined,
156
});
157
const excerptRange = nodeToDocument.range;
158
nonIgnoredDefs.push({
159
languageId: docContainingRef.languageId,
160
uri: docContainingRef.uri,
161
code: docContainingRef.getText(excerptRange),
162
excerptRange,
163
} satisfies CodeExcerpt);
164
}
165
}
166
return [nonIgnoredDefs, nonIgnoredRefs];
167
}
168
}
169
170