Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompt/node/definitionAroundCursor.tsx
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 { PromptElement, PromptElementProps, PromptPiece, PromptSizing, UserMessage } from '@vscode/prompt-tsx';
7
import type * as vscode from 'vscode';
8
import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';
9
import { IChatEndpoint } from '../../../platform/networking/common/networking';
10
import { TreeSitterOffsetRange } from '../../../platform/parser/node/nodes';
11
import { NodeToDocumentContext } from '../../../platform/parser/node/parserImpl';
12
import { IParserService, treeSitterOffsetRangeToVSCodeRange as toRange, vscodeToTreeSitterOffsetRange as toTSOffsetRange } from '../../../platform/parser/node/parserService';
13
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
14
import { CodeContextRegion, CodeContextTracker } from '../../inlineChat/node/codeContextRegion';
15
import { IDocumentContext } from './documentContext';
16
17
export type Props = PromptElementProps<{
18
documentContext: IDocumentContext;
19
endpointInfo: IChatEndpoint;
20
/** if not provided, the component will compute based on `documentContext` */
21
nodeToDocument?: NodeToDocument;
22
}>;
23
24
export type State =
25
| {
26
k: 'found';
27
nodeToDocument: NodeToDocument;
28
codeExcerptToDocument: CodeContextRegion;
29
}
30
| {
31
k: 'ignored';
32
}
33
;
34
35
export class DefinitionAroundCursor extends PromptElement<Props, State> {
36
37
constructor(
38
props: Props,
39
@ITelemetryService private readonly _telemetryService: ITelemetryService,
40
@IParserService private readonly _parserService: IParserService,
41
@IIgnoreService private readonly _ignoreService: IIgnoreService,
42
) {
43
super(props);
44
}
45
46
override async prepare(sizing: PromptSizing, progress?: vscode.Progress<vscode.ChatResponseProgressPart | vscode.ChatResponseReferencePart> | undefined, token?: vscode.CancellationToken | undefined): Promise<State> {
47
if (await this._ignoreService.isCopilotIgnored(this.props.documentContext.document.uri)) {
48
return { k: 'ignored' };
49
}
50
const nodeToDocument = this.props.nodeToDocument ?? await determineNodeToDocument(this._parserService, this._telemetryService, this.props.documentContext);
51
const contextInfo = generateDocContext(this.props.endpointInfo, this.props.documentContext, nodeToDocument.range);
52
return {
53
k: 'found',
54
nodeToDocument,
55
codeExcerptToDocument: contextInfo.range,
56
};
57
}
58
59
override render(state: State, sizing: PromptSizing): PromptPiece<any, any> | undefined {
60
if (state.k === 'ignored') {
61
return <ignoredFiles value={[this.props.documentContext.document.uri]} />;
62
}
63
const codeExcerpt = state.codeExcerptToDocument.generatePrompt().join('\n');
64
return (
65
<UserMessage>
66
I have the following code in the selection:{codeExcerpt !== '' ? <br /> : ''}
67
{state.codeExcerptToDocument.generatePrompt().join('\n')}
68
</UserMessage>
69
);
70
}
71
}
72
73
export type NodeToDocument = {
74
readonly range: vscode.Range;
75
readonly identifier?: string;
76
};
77
export async function determineNodeToDocument(parserService: IParserService, telemetryService: ITelemetryService, ctx: IDocumentContext): Promise<{ range: vscode.Range; identifier?: string }> {
78
79
const selectionRange = toTSOffsetRange(ctx.selection, ctx.document);
80
81
const treeSitterAST = parserService.getTreeSitterAST(ctx.document);
82
83
if (treeSitterAST === undefined) {
84
return {
85
range: ctx.wholeRange,
86
};
87
}
88
89
const startTime = Date.now();
90
const nodeToDocContext = await treeSitterAST.getNodeToDocument(selectionRange);
91
const timeSpentMs = Date.now() - startTime;
92
93
const wholeOffsetRange = toTSOffsetRange(ctx.wholeRange, ctx.document);
94
sendNodeToDocumentTelemetry(telemetryService, selectionRange, wholeOffsetRange, nodeToDocContext, ctx.document.languageId, timeSpentMs);
95
96
const rangeOfNodeToDocument = toRange(ctx.document, nodeToDocContext.nodeToDocument);
97
return {
98
identifier: nodeToDocContext.nodeIdentifier,
99
range: rangeOfNodeToDocument,
100
};
101
}
102
function generateDocContext(endpoint: IChatEndpoint, ctx: IDocumentContext, range: vscode.Range) {
103
104
const tracker = new CodeContextTracker((endpoint.modelMaxPromptTokens * 4) / 3);
105
const rangeInfo = new CodeContextRegion(tracker, ctx.document, ctx.language);
106
107
// we only want to include the code that's being documented but we need `above` and `below` for the return value's type
108
// so we just make these code regions empty
109
const above = new CodeContextRegion(new CodeContextTracker(0), ctx.document, ctx.language);
110
const below = new CodeContextRegion(new CodeContextTracker(0), ctx.document, ctx.language);
111
112
for (let i = range.start.line, len = range.end.line; i <= len; ++i) {
113
if ((i === len && range.end.character === 0) // we don't want to include the end line if it's (end.line, 0)
114
|| !rangeInfo.appendLine(i) // didn't fit
115
) {
116
break;
117
}
118
}
119
120
rangeInfo.trim(ctx.selection);
121
122
return {
123
language: ctx.language,
124
above,
125
range: rangeInfo,
126
below,
127
};
128
}
129
function sendNodeToDocumentTelemetry(
130
telemetryService: ITelemetryService,
131
selectionRange: TreeSitterOffsetRange,
132
wholeOffsetRange: TreeSitterOffsetRange,
133
nodeToDocContext: NodeToDocumentContext,
134
languageId: string,
135
timeSpentMs: number
136
) {
137
138
/* __GDPR__
139
"getNodeToDocument" : {
140
"owner": "ulugbekna",
141
"comment": "Info on success and properties of detecting AST node to document",
142
"languageId": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The language ID of the document" },
143
"typeOfNodeToDocument": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Type of the AST node offered to be documented (type defined by tree-sitter grammar for that language)" },
144
"nodeToDocumentStart": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Start offset of the AST node offered to be documented (type defined by tree-sitter grammar for that language)" },
145
"nodeToDocumentEnd": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "End offset of the AST node offered to be documented (type defined by tree-sitter grammar for that language)" },
146
"selectionOffsetRangeStart": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The start offset range of the selection in the document" },
147
"selectionOffsetRangeEnd": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The end offset range of the selection in the document" },
148
"wholeRangeOffsetRangeStart": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The start offset range of the inline-chat wholeRange" },
149
"wholeRangeOffsetRangeEnd": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The end offset range of the inline-chat wholeRange" },
150
"timeSpentMs": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Time (in milliseconds) spent on finding the AST node to document (approximate as it's an async call)" }
151
}
152
*/
153
telemetryService.sendMSFTTelemetryEvent('getNodeToDocument',
154
{
155
languageId,
156
typeOfNodeToDocument: nodeToDocContext.nodeToDocument.type,
157
nodeToDocumentStart: nodeToDocContext.nodeToDocument.startIndex.toString(),
158
nodeToDocumentEnd: nodeToDocContext.nodeToDocument.endIndex.toString(),
159
selectionOffsetRangeStart: selectionRange.startIndex.toString(),
160
selectionOffsetRangeEnd: selectionRange.endIndex.toString(),
161
wholeRangeOffsetRangeStart: wholeOffsetRange.startIndex.toString(),
162
wholeRangeOffsetRangeEnd: wholeOffsetRange.endIndex.toString(),
163
},
164
{
165
timeSpentMs,
166
}
167
);
168
}
169
170