mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
Split TS' AI-backed code actions into separate entries (#201140)
* Split TS' AI-backed code actions into separate entries Lets the user decide whether to add AI to their code action, which shows intent, which is good for us to learn whether people actually want this. Related: this should be unflagged for insiders. To do this, do I just delete the flags? * Stop appending a duplicate message in missingFunctionDeclaration * Fix: quickfix was still showing Copilot-only It's a workaround--I'm not sure of the right way to do this. * Update to use `isAI` * Put AI code actions after others. * Add isAI to rest of code actions * Remove flags for TS AI code actions * Check for copilot-chat instead of copilot It's possible to have copilot installed without copilot-chat. * Fix file casing --------- Co-authored-by: Matt Bierner <matb@microsoft.com>
This commit is contained in:
committed by
GitHub
parent
3246d63177
commit
5e6ec068b2
@@ -147,12 +147,19 @@ class VsCodeFixAllCodeAction extends VsCodeCodeAction {
|
||||
class CodeActionSet {
|
||||
private readonly _actions = new Set<VsCodeCodeAction>();
|
||||
private readonly _fixAllActions = new Map<{}, VsCodeCodeAction>();
|
||||
private readonly _aiActions = new Set<VsCodeCodeAction>();
|
||||
|
||||
public get values(): Iterable<VsCodeCodeAction> {
|
||||
return this._actions;
|
||||
public *values(): Iterable<VsCodeCodeAction> {
|
||||
yield* this._actions;
|
||||
yield* this._aiActions;
|
||||
}
|
||||
|
||||
public addAction(action: VsCodeCodeAction) {
|
||||
if (action.isAI) {
|
||||
// there are no separate fixAllActions for AI, and no duplicates, so return immediately
|
||||
this._aiActions.add(action);
|
||||
return;
|
||||
}
|
||||
for (const existing of this._actions) {
|
||||
if (action.tsAction.fixName === existing.tsAction.fixName && equals(action.edit, existing.edit)) {
|
||||
this._actions.delete(existing);
|
||||
@@ -261,7 +268,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
|
||||
}
|
||||
}
|
||||
|
||||
const allActions = Array.from(results.values);
|
||||
const allActions = Array.from(results.values());
|
||||
for (const action of allActions) {
|
||||
action.isPreferred = isPreferredFix(action, allActions);
|
||||
}
|
||||
@@ -321,29 +328,41 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
|
||||
action: Proto.CodeFixAction
|
||||
): VsCodeCodeAction[] {
|
||||
const actions: VsCodeCodeAction[] = [];
|
||||
let message: string | undefined;
|
||||
let expand: Expand | undefined;
|
||||
let title = action.description;
|
||||
if (vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions')) {
|
||||
if (action.fixName === fixNames.classIncorrectlyImplementsInterface && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.classIncorrectlyImplementsInterface')) {
|
||||
const codeAction = new VsCodeCodeAction(action, action.description, vscode.CodeActionKind.QuickFix);
|
||||
codeAction.edit = getEditForCodeAction(this.client, action);
|
||||
codeAction.diagnostics = [diagnostic];
|
||||
codeAction.command = {
|
||||
command: ApplyCodeActionCommand.ID,
|
||||
arguments: [{ action, diagnostic, document } satisfies ApplyCodeActionCommand_args],
|
||||
title: ''
|
||||
};
|
||||
actions.push(codeAction);
|
||||
|
||||
const copilot = vscode.extensions.getExtension('github.copilot-chat');
|
||||
if (copilot?.isActive) {
|
||||
let message: string | undefined;
|
||||
let expand: Expand | undefined;
|
||||
let title = action.description;
|
||||
if (action.fixName === fixNames.classIncorrectlyImplementsInterface) {
|
||||
title += ' with Copilot';
|
||||
message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
|
||||
expand = { kind: 'code-action', action };
|
||||
}
|
||||
else if (action.fixName === fixNames.fixClassDoesntImplementInheritedAbstractMember && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.classDoesntImplementInheritedAbstractMember')) {
|
||||
else if (action.fixName === fixNames.fixClassDoesntImplementInheritedAbstractMember) {
|
||||
title += ' with Copilot';
|
||||
message = `Implement the stubbed-out class members for ${document.getText(diagnostic.range)} with a useful implementation.`;
|
||||
expand = { kind: 'code-action', action };
|
||||
}
|
||||
else if (action.fixName === fixNames.fixMissingFunctionDeclaration && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.missingFunctionDeclaration')) {
|
||||
title += `Implement missing function declaration '${document.getText(diagnostic.range)}' using Copilot`;
|
||||
else if (action.fixName === fixNames.fixMissingFunctionDeclaration) {
|
||||
title = `Implement missing function declaration '${document.getText(diagnostic.range)}' using Copilot`;
|
||||
message = `Provide a reasonable implementation of the function ${document.getText(diagnostic.range)} given its type and the context it's called in.`;
|
||||
expand = { kind: 'code-action', action };
|
||||
}
|
||||
else if (action.fixName === fixNames.inferFromUsage && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.inferAndAddTypes')) {
|
||||
else if (action.fixName === fixNames.inferFromUsage) {
|
||||
const inferFromBody = new VsCodeCodeAction(action, 'Infer types using Copilot', vscode.CodeActionKind.QuickFix);
|
||||
inferFromBody.edit = new vscode.WorkspaceEdit();
|
||||
inferFromBody.diagnostics = [diagnostic];
|
||||
inferFromBody.isAI = true;
|
||||
inferFromBody.command = {
|
||||
command: EditorChatFollowUp.ID,
|
||||
arguments: [{
|
||||
@@ -356,7 +375,7 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
|
||||
};
|
||||
actions.push(inferFromBody);
|
||||
}
|
||||
else if (action.fixName === fixNames.addNameToNamelessParameter && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.addNameToNamelessParameter')) {
|
||||
else if (action.fixName === fixNames.addNameToNamelessParameter) {
|
||||
const newText = action.changes.map(change => change.textChanges.map(textChange => textChange.newText).join('')).join('');
|
||||
title = 'Add meaningful parameter name with Copilot';
|
||||
message = `Rename the parameter ${newText} with a more meaningful name.`;
|
||||
@@ -365,32 +384,33 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider<VsCodeCode
|
||||
pos: diagnostic.range.start
|
||||
};
|
||||
}
|
||||
}
|
||||
const codeAction = new VsCodeCodeAction(action, title, vscode.CodeActionKind.QuickFix);
|
||||
codeAction.edit = getEditForCodeAction(this.client, action);
|
||||
codeAction.diagnostics = [diagnostic];
|
||||
codeAction.command = {
|
||||
command: ApplyCodeActionCommand.ID,
|
||||
arguments: [{ action: action, diagnostic, document } satisfies ApplyCodeActionCommand_args],
|
||||
title: ''
|
||||
};
|
||||
if (expand && message !== undefined) {
|
||||
codeAction.command = {
|
||||
command: CompositeCommand.ID,
|
||||
title: '',
|
||||
arguments: [codeAction.command, {
|
||||
command: EditorChatFollowUp.ID,
|
||||
if (expand && message !== undefined) {
|
||||
const aiCodeAction = new VsCodeCodeAction(action, title, vscode.CodeActionKind.QuickFix);
|
||||
aiCodeAction.edit = getEditForCodeAction(this.client, action);
|
||||
aiCodeAction.edit?.insert(document.uri, diagnostic.range.start, '');
|
||||
aiCodeAction.diagnostics = [diagnostic];
|
||||
aiCodeAction.isAI = true;
|
||||
aiCodeAction.command = {
|
||||
command: CompositeCommand.ID,
|
||||
title: '',
|
||||
arguments: [{
|
||||
message,
|
||||
expand,
|
||||
document,
|
||||
action: { type: 'quickfix', quickfix: action }
|
||||
} satisfies EditorChatFollowUp_Args],
|
||||
}],
|
||||
};
|
||||
command: ApplyCodeActionCommand.ID,
|
||||
arguments: [{ action, diagnostic, document } satisfies ApplyCodeActionCommand_args],
|
||||
title: ''
|
||||
}, {
|
||||
command: EditorChatFollowUp.ID,
|
||||
title: '',
|
||||
arguments: [{
|
||||
message,
|
||||
expand,
|
||||
document,
|
||||
action: { type: 'quickfix', quickfix: action }
|
||||
} satisfies EditorChatFollowUp_Args],
|
||||
}],
|
||||
};
|
||||
actions.push(aiCodeAction);
|
||||
}
|
||||
}
|
||||
actions.push(codeAction);
|
||||
return actions;
|
||||
}
|
||||
|
||||
|
||||
@@ -352,6 +352,9 @@ class InlinedCodeAction extends vscode.CodeAction {
|
||||
const title = copilotRename ? action.description + ' and suggest a name with Copilot.' : action.description;
|
||||
super(title, InlinedCodeAction.getKind(action));
|
||||
|
||||
if (copilotRename) {
|
||||
this.isAI = true;
|
||||
}
|
||||
if (action.notApplicableReason) {
|
||||
this.disabled = { reason: action.notApplicableReason };
|
||||
}
|
||||
@@ -613,36 +616,39 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
|
||||
yield new SelectCodeAction(refactor, document, rangeOrSelection);
|
||||
} else {
|
||||
for (const action of refactor.actions) {
|
||||
yield this.refactorActionToCodeAction(document, refactor, action, rangeOrSelection, refactor.actions);
|
||||
for (const codeAction of this.refactorActionToCodeActions(document, refactor, action, rangeOrSelection, refactor.actions)) {
|
||||
yield codeAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private refactorActionToCodeAction(
|
||||
private refactorActionToCodeActions(
|
||||
document: vscode.TextDocument,
|
||||
refactor: Proto.ApplicableRefactorInfo,
|
||||
action: Proto.RefactorActionInfo,
|
||||
rangeOrSelection: vscode.Range | vscode.Selection,
|
||||
allActions: readonly Proto.RefactorActionInfo[],
|
||||
): TsCodeAction {
|
||||
let codeAction: TsCodeAction;
|
||||
): TsCodeAction[] {
|
||||
const codeActions: TsCodeAction[] = [];
|
||||
if (action.name === 'Move to file') {
|
||||
codeAction = new MoveToFileCodeAction(document, action, rangeOrSelection);
|
||||
codeActions.push(new MoveToFileCodeAction(document, action, rangeOrSelection));
|
||||
} else {
|
||||
let copilotRename: ((info: Proto.RefactorEditInfo) => vscode.Command) | undefined;
|
||||
if (vscode.workspace.getConfiguration('typescript', null).get('experimental.aiCodeActions')) {
|
||||
if (Extract_Constant.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractConstant')
|
||||
|| Extract_Function.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractFunction')
|
||||
|| Extract_Type.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractType')
|
||||
|| Extract_Interface.matches(action) && vscode.workspace.getConfiguration('typescript').get('experimental.aiCodeActions.extractInterface')
|
||||
codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, undefined));
|
||||
const copilot = vscode.extensions.getExtension('github.copilot-chat');
|
||||
if (copilot?.isActive) {
|
||||
if (Extract_Constant.matches(action)
|
||||
|| Extract_Function.matches(action)
|
||||
|| Extract_Type.matches(action)
|
||||
|| Extract_Interface.matches(action)
|
||||
) {
|
||||
const newName = Extract_Constant.matches(action) ? 'newLocal'
|
||||
: Extract_Function.matches(action) ? 'newFunction'
|
||||
: Extract_Type.matches(action) ? 'NewType'
|
||||
: Extract_Interface.matches(action) ? 'NewInterface'
|
||||
: '';
|
||||
copilotRename = info => ({
|
||||
const copilotRename: ((info: Proto.RefactorEditInfo) => vscode.Command) = info => ({
|
||||
title: '',
|
||||
command: EditorChatFollowUp.ID,
|
||||
arguments: [{
|
||||
@@ -658,14 +664,14 @@ class TypeScriptRefactorProvider implements vscode.CodeActionProvider<TsCodeActi
|
||||
document,
|
||||
} satisfies EditorChatFollowUp_Args]
|
||||
});
|
||||
codeActions.push(new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, copilotRename));
|
||||
}
|
||||
|
||||
}
|
||||
codeAction = new InlinedCodeAction(this.client, document, refactor, action, rangeOrSelection, copilotRename);
|
||||
}
|
||||
|
||||
codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions);
|
||||
return codeAction;
|
||||
for (const codeAction of codeActions) {
|
||||
codeAction.isPreferred = TypeScriptRefactorProvider.isPreferred(action, allActions);
|
||||
}
|
||||
return codeActions;
|
||||
}
|
||||
|
||||
private shouldTrigger(context: vscode.CodeActionContext, rangeOrSelection: vscode.Range | vscode.Selection) {
|
||||
|
||||
Reference in New Issue
Block a user