Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/intents/node/reviewIntent.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 l10n from '@vscode/l10n';
7
import { RenderPromptResult } from '@vscode/prompt-tsx';
8
import type * as vscode from 'vscode';
9
import { IResponsePart } from '../../../platform/chat/common/chatMLFetcher';
10
import { ChatLocation } from '../../../platform/chat/common/commonTypes';
11
import { TextDocumentSnapshot } from '../../../platform/editing/common/textDocumentSnapshot';
12
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
13
import { IGitExtensionService } from '../../../platform/git/common/gitExtensionService';
14
import { ILogService } from '../../../platform/log/common/logService';
15
import { IChatEndpoint } from '../../../platform/networking/common/networking';
16
import { IReviewService, ReviewComment, ReviewRequest } from '../../../platform/review/common/reviewService';
17
import { ITabsAndEditorsService } from '../../../platform/tabs/common/tabsAndEditorsService';
18
import { IWorkspaceService } from '../../../platform/workspace/common/workspaceService';
19
import { CancellationToken } from '../../../util/vs/base/common/cancellation';
20
import * as path from '../../../util/vs/base/common/path';
21
import { generateUuid } from '../../../util/vs/base/common/uuid';
22
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
23
import { MarkdownString } from '../../../vscodeTypes';
24
import { Intent } from '../../common/constants';
25
import { LinkifiedPart, LinkifiedText, LinkifyLocationAnchor } from '../../linkify/common/linkifiedText';
26
import { IContributedLinkifier, LinkifierContext } from '../../linkify/common/linkifyService';
27
import { IBuildPromptContext } from '../../prompt/common/intents';
28
import { IDocumentContext } from '../../prompt/node/documentContext';
29
import { parseFeedbackResponse, parseReviewComments } from '../../prompt/node/feedbackGenerator';
30
import { IIntent, IIntentInvocation, IIntentInvocationContext, IIntentSlashCommandInfo, IResponseProcessorContext, ReplyInterpreter } from '../../prompt/node/intents';
31
import { PromptRenderer, RendererIntentInvocation } from '../../prompts/node/base/promptRenderer';
32
import { CurrentChange, CurrentChangeInput } from '../../prompts/node/feedback/currentChange';
33
import { ProvideFeedbackPrompt } from '../../prompts/node/feedback/provideFeedback';
34
35
export const reviewIntentPromptSnippet = 'Review the currently selected code.';
36
37
export const reviewLocalChangesMessage = l10n.t('local changes');
38
39
40
class ReviewIntentInvocation extends RendererIntentInvocation implements IIntentInvocation {
41
42
readonly linkification = {
43
additionaLinkifiers: [{ create: () => new LineLinkifier(this.documentContext.document.uri) }]
44
};
45
46
constructor(
47
intent: IIntent,
48
location: ChatLocation,
49
endpoint: IChatEndpoint,
50
protected readonly documentContext: IDocumentContext,
51
@IInstantiationService protected readonly instantiationService: IInstantiationService,
52
@IWorkspaceService private readonly workspaceService: IWorkspaceService,
53
@ITabsAndEditorsService private readonly tabsAndEditorsService: ITabsAndEditorsService,
54
@ILogService private readonly logService: ILogService,
55
@IGitExtensionService private readonly gitExtensionService: IGitExtensionService,
56
) {
57
super(intent, location, endpoint);
58
}
59
60
async createRenderer({ history, query, chatVariables }: IBuildPromptContext, endpoint: IChatEndpoint, progress: vscode.Progress<vscode.ChatResponseProgressPart | vscode.ChatResponseReferencePart>, token: vscode.CancellationToken) {
61
62
const input: CurrentChangeInput[] = [];
63
if (query === reviewLocalChangesMessage) {
64
const changes = await CurrentChange.getCurrentChanges(this.gitExtensionService, 'workingTree');
65
const documentsAndChanges = await Promise.all<CurrentChangeInput>(changes.map(async change => {
66
const document = await this.workspaceService.openTextDocumentAndSnapshot(change.uri);
67
return {
68
document,
69
relativeDocumentPath: path.relative(change.repository.rootUri.fsPath, change.uri.fsPath),
70
change,
71
};
72
}));
73
documentsAndChanges.map(i => input.push(i));
74
} else {
75
const editor = this.tabsAndEditorsService.activeTextEditor;
76
if (editor) {
77
input.push({
78
document: TextDocumentSnapshot.create(editor.document),
79
relativeDocumentPath: path.basename(editor.document.uri.fsPath),
80
selection: editor.selection,
81
});
82
}
83
}
84
85
return PromptRenderer.create(this.instantiationService, endpoint, ProvideFeedbackPrompt, {
86
query,
87
history,
88
chatVariables,
89
input,
90
logService: this.logService,
91
});
92
}
93
94
override async buildPrompt(context: IBuildPromptContext, progress: vscode.Progress<vscode.ChatResponseProgressPart | vscode.ChatResponseReferencePart>, token: vscode.CancellationToken): Promise<RenderPromptResult> {
95
if (context.query === '') {
96
context = { ...context, query: reviewIntentPromptSnippet };
97
}
98
return super.buildPrompt(context, progress, token);
99
}
100
}
101
102
class InlineReviewIntentInvocation extends ReviewIntentInvocation implements IIntentInvocation {
103
104
processResponse(context: IResponseProcessorContext, inputStream: AsyncIterable<IResponsePart>, outputStream: vscode.ChatResponseStream, token: CancellationToken): Promise<void> {
105
const replyInterpreter = this.instantiationService.createInstance(ReviewReplyInterpreter, this.documentContext);
106
return replyInterpreter.processResponse(context, inputStream, outputStream, token);
107
}
108
}
109
110
export class ReviewIntent implements IIntent {
111
112
static readonly ID = Intent.Review;
113
readonly id = Intent.Review;
114
readonly locations = [ChatLocation.Panel, ChatLocation.Editor];
115
readonly description = l10n.t('Review the selected code in your active editor');
116
117
readonly commandInfo: IIntentSlashCommandInfo | undefined;
118
119
constructor(
120
@IInstantiationService private readonly instantiationService: IInstantiationService,
121
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
122
) { }
123
124
async invoke(invocationContext: IIntentInvocationContext): Promise<IIntentInvocation> {
125
126
const documentContext = invocationContext.documentContext;
127
const location = invocationContext.location;
128
const endpoint = await this.endpointProvider.getChatEndpoint(invocationContext.request);
129
if (location === ChatLocation.Editor) {
130
return this.instantiationService.createInstance(InlineReviewIntentInvocation, this, location, endpoint, documentContext!);
131
}
132
return this.instantiationService.createInstance(ReviewIntentInvocation, this, location, endpoint, documentContext!);
133
}
134
}
135
136
class LineLinkifier implements IContributedLinkifier {
137
138
constructor(private readonly file: vscode.Uri) { }
139
140
async linkify(newText: string, context: LinkifierContext, token?: vscode.CancellationToken): Promise<LinkifiedText | undefined> {
141
const parsedResponse = parseFeedbackResponse(newText);
142
if (!parsedResponse.length) {
143
return;
144
}
145
146
let remaining = 0;
147
const parts: LinkifiedPart[] = [];
148
for (const match of parsedResponse) {
149
parts.push(newText.substring(remaining, match.linkOffset));
150
parts.push(new LinkifyLocationAnchor(this.file.with({ fragment: String(match.from + 1) }), newText.substring(match.linkOffset, match.linkOffset + match.linkLength)));
151
remaining = match.linkOffset + match.linkLength;
152
}
153
parts.push(newText.substring(remaining));
154
return { parts };
155
}
156
}
157
158
class ReviewReplyInterpreter implements ReplyInterpreter {
159
160
private updating = false;
161
private text = '';
162
private comments: ReviewComment[] = [];
163
164
constructor(
165
private readonly documentContext: IDocumentContext,
166
@IReviewService private readonly reviewService: IReviewService
167
) {
168
}
169
170
async processResponse(context: IResponseProcessorContext, inputStream: AsyncIterable<IResponsePart>, outputStream: vscode.ChatResponseStream, token: CancellationToken): Promise<void> {
171
const request: ReviewRequest = {
172
source: 'vscodeCopilotChat',
173
promptCount: 1,
174
messageId: generateUuid(), // TODO: Use from request?
175
inputType: 'selection',
176
inputRanges: [
177
{
178
uri: this.documentContext.document.uri,
179
ranges: [this.documentContext.selection]
180
}
181
],
182
};
183
184
for await (const part of inputStream) {
185
this.text += part.delta.text;
186
if (!this.updating) {
187
this.updating = true;
188
const content = new MarkdownString(l10n.t({
189
message: 'Reviewing your code...\n',
190
comment: `{Locked='](command:workbench.panel.markers.view.focus)'}`,
191
}));
192
content.isTrusted = {
193
enabledCommands: ['workbench.panel.markers.view.focus']
194
};
195
outputStream.markdown(content);
196
}
197
const comments = parseReviewComments(request, [
198
{
199
document: this.documentContext.document,
200
relativeDocumentPath: path.basename(this.documentContext.document.uri.fsPath),
201
selection: this.documentContext.selection
202
}
203
], this.text, true);
204
if (comments.length > this.comments.length) {
205
this.reviewService.addReviewComments(comments.slice(this.comments.length));
206
this.comments = comments;
207
}
208
}
209
210
const comments = parseReviewComments(request, [
211
{
212
document: this.documentContext.document,
213
relativeDocumentPath: path.basename(this.documentContext.document.uri.fsPath),
214
selection: this.documentContext.selection
215
}
216
], this.text, false); // parse all
217
if (comments.length > this.comments.length) {
218
this.reviewService.addReviewComments(comments.slice(this.comments.length));
219
this.comments = comments;
220
}
221
outputStream.markdown(l10n.t('Reviewed your code and generated {0} suggestions.', comments.length));
222
}
223
}
224
225