Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/inlineChat/vscode-node/inlineChatCodeActions.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 vscode from 'vscode';
7
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
8
import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';
9
import { IReviewService } from '../../../platform/review/common/reviewService';
10
import { extractImageAttributes } from '../../../util/common/imageUtils';
11
import * as path from '../../../util/vs/base/common/path';
12
import { Intent } from '../../common/constants';
13
14
class AICodeAction extends vscode.CodeAction {
15
override readonly isAI = true;
16
}
17
18
export interface ImageCodeAction extends AICodeAction {
19
resolvedImagePath: string;
20
type: 'generate' | 'refine';
21
isUrl: boolean;
22
}
23
24
export class QuickFixesProvider implements vscode.CodeActionProvider {
25
26
constructor(
27
@IConfigurationService private readonly configurationService: IConfigurationService,
28
@IIgnoreService private readonly ignoreService: IIgnoreService,
29
@IReviewService private readonly reviewService: IReviewService,
30
) {
31
}
32
33
private static readonly fixKind = vscode.CodeActionKind.QuickFix.append('copilot');
34
private static readonly explainKind = vscode.CodeActionKind.QuickFix.append('explain').append('copilot');
35
private static readonly reviewKind = vscode.CodeActionKind.RefactorRewrite.append('review').append('copilot');
36
37
static readonly providedCodeActionKinds = [
38
this.fixKind,
39
this.explainKind,
40
this.reviewKind,
41
];
42
43
static getWarningOrErrorDiagnostics(diagnostics: ReadonlyArray<vscode.Diagnostic>): vscode.Diagnostic[] {
44
return diagnostics.filter(d => d.severity <= vscode.DiagnosticSeverity.Warning);
45
}
46
47
static getDiagnosticsAsText(diagnostics: ReadonlyArray<vscode.Diagnostic>): string {
48
return diagnostics.map(d => d.message).join(', ');
49
}
50
51
async provideCodeActions(doc: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, cancellationToken: vscode.CancellationToken): Promise<vscode.CodeAction[] | undefined> {
52
53
const copilotCodeActionsEnabled = this.configurationService.getConfig(ConfigKey.EnableCodeActions);
54
if (!copilotCodeActionsEnabled) {
55
return;
56
}
57
58
if (await this.ignoreService.isCopilotIgnored(doc.uri)) {
59
return;
60
}
61
if (cancellationToken.isCancellationRequested) {
62
return;
63
}
64
65
const codeActions: vscode.CodeAction[] = [];
66
const activeTextEditor = vscode.window.activeTextEditor;
67
if (!activeTextEditor) {
68
return codeActions;
69
}
70
71
const altTextQuickFixes = this.provideAltTextQuickFix(doc, range);
72
if (altTextQuickFixes) {
73
altTextQuickFixes.command = {
74
title: altTextQuickFixes.title,
75
command: 'github.copilot.chat.generateAltText',
76
arguments: [
77
{
78
type: altTextQuickFixes.type,
79
resolvedImagePath: altTextQuickFixes.resolvedImagePath,
80
isUrl: altTextQuickFixes.isUrl,
81
}
82
],
83
};
84
codeActions.push(altTextQuickFixes);
85
}
86
87
if (vscode.workspace.getConfiguration('inlineChat').get('affordance') !== 'off') {
88
return codeActions;
89
}
90
91
if (this.reviewService.isCodeFeedbackEnabled() && !activeTextEditor.selection.isEmpty) {
92
const reviewAction = new AICodeAction(vscode.l10n.t('Review'), QuickFixesProvider.reviewKind);
93
reviewAction.command = {
94
title: reviewAction.title,
95
command: 'github.copilot.chat.review',
96
};
97
codeActions.push(reviewAction);
98
}
99
100
const severeDiagnostics = QuickFixesProvider.getWarningOrErrorDiagnostics(context.diagnostics);
101
if (severeDiagnostics.length === 0) {
102
return codeActions;
103
}
104
105
const initialRange = severeDiagnostics.map(d => d.range).reduce((a, b) => a.union(b));
106
const initialSelection = new vscode.Selection(initialRange.start, initialRange.end);
107
const diagnostics = QuickFixesProvider.getDiagnosticsAsText(severeDiagnostics);
108
109
const fixAction = new AICodeAction(vscode.l10n.t('Fix'), QuickFixesProvider.fixKind);
110
fixAction.diagnostics = severeDiagnostics;
111
fixAction.command = {
112
title: fixAction.title,
113
command: 'vscode.editorChat.start',
114
arguments: [
115
{
116
autoSend: true,
117
message: `/fix ${diagnostics}`,
118
position: initialRange.start,
119
initialSelection: initialSelection,
120
initialRange: initialRange
121
},
122
],
123
};
124
125
const explainAction = new AICodeAction(vscode.l10n.t('Explain'), QuickFixesProvider.explainKind);
126
explainAction.diagnostics = severeDiagnostics;
127
const query = `/${Intent.Explain} ${diagnostics}`;
128
explainAction.command = {
129
title: explainAction.title,
130
command: 'github.copilot.chat.explain',
131
arguments: [query],
132
};
133
134
codeActions.push(fixAction, explainAction);
135
return codeActions;
136
}
137
138
private provideAltTextQuickFix(document: vscode.TextDocument, range: vscode.Range): ImageCodeAction | undefined {
139
const currentLine = document.lineAt(range.start.line).text;
140
const generateImagePath = extractImageAttributes(currentLine);
141
const refineImagePath = extractImageAttributes(currentLine, true);
142
if (!generateImagePath && !refineImagePath) {
143
return;
144
}
145
146
if (generateImagePath) {
147
const isUrl = this.isValidUrl(generateImagePath);
148
const resolvedImagePath = isUrl ? generateImagePath : path.resolve(path.dirname(document.uri.fsPath), generateImagePath);
149
return {
150
title: vscode.l10n.t('Generate alt text'),
151
kind: vscode.CodeActionKind.QuickFix,
152
resolvedImagePath,
153
type: 'generate',
154
isUrl,
155
isAI: true,
156
};
157
} else if (refineImagePath) {
158
const isUrl = this.isValidUrl(refineImagePath);
159
const resolvedImagePath = isUrl ? refineImagePath : path.resolve(path.dirname(document.uri.fsPath), refineImagePath);
160
return {
161
title: vscode.l10n.t('Refine alt text'),
162
kind: vscode.CodeActionKind.QuickFix,
163
resolvedImagePath,
164
type: 'refine',
165
isUrl,
166
isAI: true,
167
};
168
}
169
170
}
171
172
private isValidUrl(imagePath: string): boolean {
173
try {
174
new URL(imagePath);
175
return true;
176
} catch (e) {
177
return false;
178
}
179
}
180
181
182
}
183
184
export class RefactorsProvider implements vscode.CodeActionProvider {
185
186
187
private static readonly generateOrModifyKind = vscode.CodeActionKind.RefactorRewrite.append('copilot');
188
189
static readonly providedCodeActionKinds = [
190
this.generateOrModifyKind,
191
];
192
193
constructor(
194
@IConfigurationService private readonly configurationService: IConfigurationService,
195
@IIgnoreService private readonly ignoreService: IIgnoreService,
196
) { }
197
198
async provideCodeActions(
199
doc: vscode.TextDocument,
200
range: vscode.Range,
201
_ctx: vscode.CodeActionContext,
202
cancellationToken: vscode.CancellationToken
203
): Promise<vscode.CodeAction[] | undefined> {
204
205
const copilotCodeActionsEnabled = this.configurationService.getConfig(ConfigKey.EnableCodeActions);
206
if (!copilotCodeActionsEnabled) {
207
return;
208
}
209
210
if (await this.ignoreService.isCopilotIgnored(doc.uri)) {
211
return;
212
}
213
214
if (cancellationToken.isCancellationRequested) {
215
return;
216
}
217
218
return this.provideGenerateUsingCopilotCodeAction(doc, range);
219
}
220
221
/**
222
* Provides code action `Generate using Copilot` or `Modify using Copilot`.
223
* - `Generate using Copilot` is shown when the selection is empty and the line of the selection contains only white-space characters or tabs.
224
* - `Modify using Copilot` is shown when the selection is not empty and the selection does not contain only white-space characters or tabs.
225
*/
226
private provideGenerateUsingCopilotCodeAction(doc: vscode.TextDocument, range: vscode.Range): vscode.CodeAction[] | undefined {
227
228
if (vscode.workspace.getConfiguration('inlineChat').get('affordance') !== 'off') {
229
return undefined;
230
}
231
232
let codeActionTitle: string | undefined;
233
234
if (range.isEmpty) {
235
const textAtLine = doc.lineAt(range.start.line).text;
236
if (range.end.character === textAtLine.length && /^\s*$/g.test(textAtLine)) {
237
codeActionTitle = vscode.l10n.t('Generate');
238
}
239
} else {
240
const textInSelection = doc.getText(range);
241
if (!/^\s*$/g.test(textInSelection)) {
242
codeActionTitle = vscode.l10n.t('Modify');
243
}
244
}
245
246
if (codeActionTitle === undefined) {
247
return undefined;
248
}
249
250
const codeAction = new AICodeAction(codeActionTitle, RefactorsProvider.generateOrModifyKind);
251
252
codeAction.command = {
253
title: codeAction.title,
254
command: 'vscode.editorChat.start',
255
arguments: [
256
{
257
position: range.start,
258
initialSelection: new vscode.Selection(range.start, range.end),
259
initialRange: range
260
},
261
],
262
};
263
264
return [codeAction];
265
}
266
}
267
268