Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/definitionAtPosition.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, 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, isLocationLink } from '../../../../platform/languages/common/languageFeaturesService';
13
import { ILogService } from '../../../../platform/log/common/logService';
14
import { IParserService } from '../../../../platform/parser/node/parserService';
15
import { getWasmLanguage } from '../../../../platform/parser/node/treeSitterLanguages';
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 * as arrays from '../../../../util/vs/base/common/arrays';
20
import { ExtensionMode, Selection, Uri } from '../../../../vscodeTypes';
21
import { asyncComputeWithTimeBudget } from '../../../context/node/resolvers/selectionContextHelpers';
22
import { determineNodeToDocument } from '../../../prompt/node/definitionAroundCursor';
23
import { CodeBlock } from './safeElements';
24
25
type Props = PromptElementProps<{
26
document: TextDocumentSnapshot;
27
/**
28
* Range of interest for which definitions are to be found.
29
* @remark if not provided, will use active selection in currently active editor
30
*/
31
position: vscode.Position;
32
/**
33
* Timeout for finding implementations in milliseconds. Defaults to 200ms.
34
*/
35
timeoutMs?: number;
36
}>;
37
38
type CodeExcerpt = {
39
languageId: string;
40
uri: Uri;
41
code: string;
42
excerptRange: vscode.Range;
43
};
44
45
export type State =
46
| {
47
k: 'found';
48
definitions: CodeExcerpt[];
49
}
50
| {
51
/** Document should be ignored by Copilot */
52
k: 'ignored';
53
}
54
;
55
56
57
/**
58
* @remark Excludes definitions that are in copilot-ignored files.
59
*/
60
export class DefinitionAtPosition extends PromptElement<Props, State> {
61
62
private static DEFAULT_TIMEOUT_MS = 200;
63
64
constructor(
65
props: Props,
66
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
67
@IWorkspaceService private readonly _workspaceService: IWorkspaceService,
68
@IVSCodeExtensionContext private readonly _vscodeExtensionCtxService: IVSCodeExtensionContext,
69
@IIgnoreService private readonly _ignoreService: IIgnoreService,
70
@ILogService private readonly _logService: ILogService,
71
@IParserService private readonly _parserService: IParserService,
72
@ITelemetryService private readonly _telemetryService: ITelemetryService,
73
) {
74
super(props);
75
}
76
77
override async prepare(): Promise<State> {
78
if (await this._ignoreService.isCopilotIgnored(this.props.document.uri)) {
79
return { k: 'ignored' };
80
}
81
82
const timeout = this._vscodeExtensionCtxService.extensionMode === ExtensionMode.Test && !isScenarioAutomation
83
? 0
84
: (this.props.timeoutMs === undefined ? DefinitionAtPosition.DEFAULT_TIMEOUT_MS : this.props.timeoutMs);
85
86
const definitions = await this.findDefinition(timeout);
87
88
this._logService.debug(`Found ${definitions.length} implementation(s)/definition(s)`);
89
if (definitions.length > 0) {
90
this._logService.debug(`Implementation(s)/definition(s) found:` + JSON.stringify(definitions, null, '\t'));
91
}
92
93
return {
94
k: 'found',
95
definitions
96
};
97
}
98
99
override render(state: State, sizing: PromptSizing): PromptPiece<any, any> | undefined {
100
101
if (state.k === 'ignored') {
102
return <ignoredFiles value={[this.props.document.uri]} />;
103
}
104
105
const { document, position } = this.props;
106
107
const { definitions } = state;
108
109
if (definitions.length === 0) {
110
const line = document.lineAt(position.line);
111
definitions.push({
112
languageId: document.languageId,
113
uri: document.uri,
114
code: line.text,
115
excerptRange: line.range,
116
});
117
}
118
119
return (
120
<>
121
Relevant definition{definitions.length > 1 ? 's' : ''}: <br />
122
<br />
123
{definitions.map(codeBlock => {
124
const lineCommentStart = getLanguage(codeBlock.languageId).lineComment.start;
125
const filePath = `${lineCommentStart} FILEPATH: ${codeBlock.uri.path}`;
126
const code = `${filePath}\n${codeBlock.code}`;
127
return (
128
<CodeBlock uri={codeBlock.uri} languageId={codeBlock.languageId} code={code} />
129
);
130
})}
131
</>
132
);
133
}
134
135
136
private async findDefinition(timeoutMs: number): Promise<CodeExcerpt[]> {
137
const { document, position } = this.props;
138
139
// find implementation or, if not found, definition
140
const findImplOrDefinition = async (position: vscode.Position) => {
141
try {
142
const impls = await this._languageFeaturesService.getImplementations(document.uri, position);
143
144
this._logService.debug(`Found ${impls.length} implementations` + JSON.stringify(impls, null, '\t'));
145
146
if (impls.length > 0) {
147
return impls;
148
}
149
} catch { }
150
151
try {
152
const defs = await this._languageFeaturesService.getDefinitions(document.uri, position);
153
154
this._logService.debug(`Found ${defs.length} definitions` + JSON.stringify(defs, null, '\t'));
155
156
if (defs.length > 0) {
157
return defs;
158
}
159
} catch { }
160
161
this._logService.debug(`No definitions or implementations found`);
162
163
return [];
164
};
165
166
const foundDefs = await asyncComputeWithTimeBudget(this._logService, this._telemetryService, document, timeoutMs * 3, () => findImplOrDefinition(position), []);
167
168
const nonIgnoredDefs = arrays.coalesce(
169
await Promise.all(
170
foundDefs.map(async def => {
171
const uri = isLocationLink(def) ? def.targetUri : document.uri;
172
return await this._ignoreService.isCopilotIgnored(uri) ? undefined : def;
173
})
174
)
175
);
176
177
// since language service gives us only links to identifiers, expand to whole implementation/definition using tree-sitter
178
179
return Promise.all(
180
nonIgnoredDefs.map(async def => {
181
182
const { uri, range } = isLocationLink(def) ? { uri: def.targetUri, range: def.targetRange } : def;
183
184
const docContainingDef = await this._workspaceService.openTextDocumentAndSnapshot(uri);
185
186
const wasmLanguage = getWasmLanguage(docContainingDef.languageId);
187
188
let code: string;
189
let excerptRange: vscode.Range;
190
if (wasmLanguage === undefined) { // capture at least the line of the definition
191
const line = docContainingDef.lineAt(range.start.line);
192
code = line.text;
193
excerptRange = line.range;
194
} else {
195
const nodeToDocument = await determineNodeToDocument(this._parserService, this._telemetryService, {
196
document: docContainingDef,
197
language: getLanguage(document.languageId),
198
wholeRange: range,
199
selection: new Selection(range.start, range.end),
200
fileIndentInfo: undefined,
201
});
202
excerptRange = nodeToDocument.range;
203
code = docContainingDef.getText(excerptRange);
204
}
205
206
return {
207
languageId: docContainingDef.languageId,
208
uri: docContainingDef.uri,
209
code,
210
excerptRange,
211
} satisfies CodeExcerpt;
212
})
213
);
214
}
215
}
216
217