Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/panel/symbolDefinitions.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, UserMessage } 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 { TreeSitterExpressionInfo, TreeSitterExpressionLocationInfo } from '../../../../platform/parser/node/nodes';
15
import { IParserService, treeSitterOffsetRangeToVSCodeRange } from '../../../../platform/parser/node/parserService';
16
import { ITabsAndEditorsService } from '../../../../platform/tabs/common/tabsAndEditorsService';
17
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
18
import { IWorkspaceService } from '../../../../platform/workspace/common/workspaceService';
19
import { ExtensionMode, Location, Uri } from '../../../../vscodeTypes';
20
import { findAllReferencedClassDeclarationsInSelection, findAllReferencedFunctionImplementationsInSelection, findAllReferencedTypeDeclarationsInSelection } from '../../../context/node/resolvers/selectionContextHelpers';
21
import { PromptReference } from '../../../prompt/common/conversation';
22
import { EmbeddedInsideUserMessage, embeddedInsideUserMessageDefault } from '../base/promptElement';
23
import { Tag } from '../base/tag';
24
import { CodeBlock } from './safeElements';
25
26
type Props = PromptElementProps<EmbeddedInsideUserMessage & {
27
// We want this upfront if possible because these could change during async prompt rendering
28
document?: TextDocumentSnapshot;
29
/**
30
* Range of interest for which definitions are to be found.
31
* @remark if not provided, will use active selection in currently active editor
32
*/
33
range?: vscode.Range;
34
/**
35
* Timeout for finding implementations in milliseconds. Defaults to 200ms.
36
*/
37
timeoutMs?: number;
38
}>;
39
40
interface State {
41
activeDocument: TextDocumentSnapshot | undefined;
42
isIgnored: boolean;
43
implementations: [/* header/description */ string, TreeSitterExpressionLocationInfo[]][];
44
}
45
46
export class SymbolDefinitions extends PromptElement<Props, State> {
47
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
@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,
55
@IParserService private readonly parserService: IParserService,
56
@ILogService private readonly logService: ILogService,
57
@ITelemetryService private readonly telemetryService: ITelemetryService,
58
@ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService,
59
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
60
) {
61
super(props);
62
}
63
64
override async prepare(): Promise<State> {
65
const emptyState: State = { implementations: [], activeDocument: undefined, isIgnored: false };
66
67
let { document: activeDocument, range: selection } = this.props;
68
69
if (!activeDocument) {
70
const activeEditor = this.tabsAndEditorsService.activeTextEditor;
71
if (!activeEditor) {
72
return emptyState;
73
}
74
activeDocument ??= TextDocumentSnapshot.create(activeEditor.document);
75
selection ??= activeEditor.selection;
76
}
77
78
if (!selection || selection.isEmpty) {
79
return emptyState;
80
}
81
82
if (await this.ignoreService.isCopilotIgnored(activeDocument.uri)) {
83
return { ...emptyState, isIgnored: true };
84
}
85
86
const timeout = this.extensionContext.extensionMode === ExtensionMode.Test && !isScenarioAutomation
87
? 0
88
: (this.props.timeoutMs === undefined ? SymbolDefinitions.DEFAULT_TIMEOUT_MS : this.props.timeoutMs);
89
90
const refFinders = [
91
{ header: 'Relevant function implementations', findImpls: findAllReferencedFunctionImplementationsInSelection },
92
{ header: 'Relevant class declarations', findImpls: findAllReferencedClassDeclarationsInSelection },
93
{ header: 'Relevant type declarations', findImpls: findAllReferencedTypeDeclarationsInSelection }
94
];
95
96
const implementations: [string, TreeSitterExpressionInfo[]][] = [];
97
for (const { header, findImpls } of refFinders) {
98
const impls = await findImpls(this.parserService, this.logService, this.telemetryService, this.languageFeaturesService, this.workspaceService, activeDocument, selection, timeout);
99
implementations.push([header, impls]);
100
}
101
return { implementations, activeDocument, isIgnored: false };
102
}
103
104
override render(state: State, sizing: PromptSizing): PromptPiece<any, any> | undefined {
105
if (!state.implementations.length || !state.activeDocument) {
106
return;
107
}
108
109
const activeDocumentUri = state.activeDocument.uri;
110
if (state.isIgnored) {
111
return <ignoredFiles value={[activeDocumentUri]} />;
112
}
113
114
const combinedElements: UserMessage[] = [];
115
116
for (const [header, implementations] of state.implementations) {
117
const { references, text, uris } = treeSitterInfoToContext(state.activeDocument, implementations);
118
if (text.length === 0) {
119
continue;
120
}
121
122
const elements = (
123
<>
124
{header}:<br />
125
<br />
126
{text.map((t, i) => <>
127
<CodeBlock code={t} languageId={state.activeDocument?.languageId} uri={uris[i]} references={[references[i]]} />
128
<br />
129
</>)}
130
</>
131
);
132
const msg = this.props.embeddedInsideUserMessage ?? embeddedInsideUserMessageDefault ? (
133
<Tag name='symbolDefinitions' priority={this.props.priority}>
134
{elements}
135
</Tag>
136
) : (
137
<UserMessage priority={this.props.priority}>
138
{elements}
139
</UserMessage>
140
);
141
142
combinedElements.push(msg);
143
}
144
145
return (<>
146
{...combinedElements}
147
</>);
148
}
149
}
150
151
export function treeSitterInfoToContext(activeDocument: TextDocumentSnapshot, info: TreeSitterExpressionLocationInfo[]) {
152
const references = [];
153
const seenReferences = new Set<string>();
154
const text: string[] = [];
155
const uris: Uri[] = [];
156
for (const impl of info) {
157
const uri = impl.uri ?? activeDocument.uri;
158
const range = impl.range ?? treeSitterOffsetRangeToVSCodeRange(activeDocument, impl);
159
const key = `${uri.toString()}-${range.start.line}-${range.start.character}-${range.end.line}-${range.end.character}`;
160
if (seenReferences.has(key)) {
161
continue;
162
}
163
seenReferences.add(key);
164
165
references.push(new PromptReference(new Location(uri, range)));
166
text.push(impl.text);
167
uris.push(uri);
168
}
169
return { references, text, uris };
170
}
171
172