Path: blob/main/extensions/copilot/src/extension/inlineChat/vscode-node/inlineChatCodeActions.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as vscode from 'vscode';6import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';7import { IIgnoreService } from '../../../platform/ignore/common/ignoreService';8import { IReviewService } from '../../../platform/review/common/reviewService';9import { extractImageAttributes } from '../../../util/common/imageUtils';10import * as path from '../../../util/vs/base/common/path';11import { Intent } from '../../common/constants';1213class AICodeAction extends vscode.CodeAction {14override readonly isAI = true;15}1617export interface ImageCodeAction extends AICodeAction {18resolvedImagePath: string;19type: 'generate' | 'refine';20isUrl: boolean;21}2223export class QuickFixesProvider implements vscode.CodeActionProvider {2425constructor(26@IConfigurationService private readonly configurationService: IConfigurationService,27@IIgnoreService private readonly ignoreService: IIgnoreService,28@IReviewService private readonly reviewService: IReviewService,29) {30}3132private static readonly fixKind = vscode.CodeActionKind.QuickFix.append('copilot');33private static readonly explainKind = vscode.CodeActionKind.QuickFix.append('explain').append('copilot');34private static readonly reviewKind = vscode.CodeActionKind.RefactorRewrite.append('review').append('copilot');3536static readonly providedCodeActionKinds = [37this.fixKind,38this.explainKind,39this.reviewKind,40];4142static getWarningOrErrorDiagnostics(diagnostics: ReadonlyArray<vscode.Diagnostic>): vscode.Diagnostic[] {43return diagnostics.filter(d => d.severity <= vscode.DiagnosticSeverity.Warning);44}4546static getDiagnosticsAsText(diagnostics: ReadonlyArray<vscode.Diagnostic>): string {47return diagnostics.map(d => d.message).join(', ');48}4950async provideCodeActions(doc: vscode.TextDocument, range: vscode.Range, context: vscode.CodeActionContext, cancellationToken: vscode.CancellationToken): Promise<vscode.CodeAction[] | undefined> {5152const copilotCodeActionsEnabled = this.configurationService.getConfig(ConfigKey.EnableCodeActions);53if (!copilotCodeActionsEnabled) {54return;55}5657if (await this.ignoreService.isCopilotIgnored(doc.uri)) {58return;59}60if (cancellationToken.isCancellationRequested) {61return;62}6364const codeActions: vscode.CodeAction[] = [];65const activeTextEditor = vscode.window.activeTextEditor;66if (!activeTextEditor) {67return codeActions;68}6970const altTextQuickFixes = this.provideAltTextQuickFix(doc, range);71if (altTextQuickFixes) {72altTextQuickFixes.command = {73title: altTextQuickFixes.title,74command: 'github.copilot.chat.generateAltText',75arguments: [76{77type: altTextQuickFixes.type,78resolvedImagePath: altTextQuickFixes.resolvedImagePath,79isUrl: altTextQuickFixes.isUrl,80}81],82};83codeActions.push(altTextQuickFixes);84}8586if (vscode.workspace.getConfiguration('inlineChat').get('affordance') !== 'off') {87return codeActions;88}8990if (this.reviewService.isCodeFeedbackEnabled() && !activeTextEditor.selection.isEmpty) {91const reviewAction = new AICodeAction(vscode.l10n.t('Review'), QuickFixesProvider.reviewKind);92reviewAction.command = {93title: reviewAction.title,94command: 'github.copilot.chat.review',95};96codeActions.push(reviewAction);97}9899const severeDiagnostics = QuickFixesProvider.getWarningOrErrorDiagnostics(context.diagnostics);100if (severeDiagnostics.length === 0) {101return codeActions;102}103104const initialRange = severeDiagnostics.map(d => d.range).reduce((a, b) => a.union(b));105const initialSelection = new vscode.Selection(initialRange.start, initialRange.end);106const diagnostics = QuickFixesProvider.getDiagnosticsAsText(severeDiagnostics);107108const fixAction = new AICodeAction(vscode.l10n.t('Fix'), QuickFixesProvider.fixKind);109fixAction.diagnostics = severeDiagnostics;110fixAction.command = {111title: fixAction.title,112command: 'vscode.editorChat.start',113arguments: [114{115autoSend: true,116message: `/fix ${diagnostics}`,117position: initialRange.start,118initialSelection: initialSelection,119initialRange: initialRange120},121],122};123124const explainAction = new AICodeAction(vscode.l10n.t('Explain'), QuickFixesProvider.explainKind);125explainAction.diagnostics = severeDiagnostics;126const query = `/${Intent.Explain} ${diagnostics}`;127explainAction.command = {128title: explainAction.title,129command: 'github.copilot.chat.explain',130arguments: [query],131};132133codeActions.push(fixAction, explainAction);134return codeActions;135}136137private provideAltTextQuickFix(document: vscode.TextDocument, range: vscode.Range): ImageCodeAction | undefined {138const currentLine = document.lineAt(range.start.line).text;139const generateImagePath = extractImageAttributes(currentLine);140const refineImagePath = extractImageAttributes(currentLine, true);141if (!generateImagePath && !refineImagePath) {142return;143}144145if (generateImagePath) {146const isUrl = this.isValidUrl(generateImagePath);147const resolvedImagePath = isUrl ? generateImagePath : path.resolve(path.dirname(document.uri.fsPath), generateImagePath);148return {149title: vscode.l10n.t('Generate alt text'),150kind: vscode.CodeActionKind.QuickFix,151resolvedImagePath,152type: 'generate',153isUrl,154isAI: true,155};156} else if (refineImagePath) {157const isUrl = this.isValidUrl(refineImagePath);158const resolvedImagePath = isUrl ? refineImagePath : path.resolve(path.dirname(document.uri.fsPath), refineImagePath);159return {160title: vscode.l10n.t('Refine alt text'),161kind: vscode.CodeActionKind.QuickFix,162resolvedImagePath,163type: 'refine',164isUrl,165isAI: true,166};167}168169}170171private isValidUrl(imagePath: string): boolean {172try {173new URL(imagePath);174return true;175} catch (e) {176return false;177}178}179180181}182183export class RefactorsProvider implements vscode.CodeActionProvider {184185186private static readonly generateOrModifyKind = vscode.CodeActionKind.RefactorRewrite.append('copilot');187188static readonly providedCodeActionKinds = [189this.generateOrModifyKind,190];191192constructor(193@IConfigurationService private readonly configurationService: IConfigurationService,194@IIgnoreService private readonly ignoreService: IIgnoreService,195) { }196197async provideCodeActions(198doc: vscode.TextDocument,199range: vscode.Range,200_ctx: vscode.CodeActionContext,201cancellationToken: vscode.CancellationToken202): Promise<vscode.CodeAction[] | undefined> {203204const copilotCodeActionsEnabled = this.configurationService.getConfig(ConfigKey.EnableCodeActions);205if (!copilotCodeActionsEnabled) {206return;207}208209if (await this.ignoreService.isCopilotIgnored(doc.uri)) {210return;211}212213if (cancellationToken.isCancellationRequested) {214return;215}216217return this.provideGenerateUsingCopilotCodeAction(doc, range);218}219220/**221* Provides code action `Generate using Copilot` or `Modify using Copilot`.222* - `Generate using Copilot` is shown when the selection is empty and the line of the selection contains only white-space characters or tabs.223* - `Modify using Copilot` is shown when the selection is not empty and the selection does not contain only white-space characters or tabs.224*/225private provideGenerateUsingCopilotCodeAction(doc: vscode.TextDocument, range: vscode.Range): vscode.CodeAction[] | undefined {226227if (vscode.workspace.getConfiguration('inlineChat').get('affordance') !== 'off') {228return undefined;229}230231let codeActionTitle: string | undefined;232233if (range.isEmpty) {234const textAtLine = doc.lineAt(range.start.line).text;235if (range.end.character === textAtLine.length && /^\s*$/g.test(textAtLine)) {236codeActionTitle = vscode.l10n.t('Generate');237}238} else {239const textInSelection = doc.getText(range);240if (!/^\s*$/g.test(textInSelection)) {241codeActionTitle = vscode.l10n.t('Modify');242}243}244245if (codeActionTitle === undefined) {246return undefined;247}248249const codeAction = new AICodeAction(codeActionTitle, RefactorsProvider.generateOrModifyKind);250251codeAction.command = {252title: codeAction.title,253command: 'vscode.editorChat.start',254arguments: [255{256position: range.start,257initialSelection: new vscode.Selection(range.start, range.end),258initialRange: range259},260],261};262263return [codeAction];264}265}266267268