diff --git a/extensions/typescript-language-features/package.json b/extensions/typescript-language-features/package.json index 0393fbb0ea4..26fd5e64d96 100644 --- a/extensions/typescript-language-features/package.json +++ b/extensions/typescript-language-features/package.json @@ -143,6 +143,12 @@ "title": "%configuration.typescript%", "order": 20, "properties": { + "typescript.experimental.aiQuickFix": { + "type": "boolean", + "default": false, + "description": "%typescript.experimental.aiQuickFix%", + "scope": "resource" + }, "typescript.tsdk": { "type": "string", "markdownDescription": "%typescript.tsdk.desc%", diff --git a/extensions/typescript-language-features/package.nls.json b/extensions/typescript-language-features/package.nls.json index 2d5aa3e7eb7..c46952747fd 100644 --- a/extensions/typescript-language-features/package.nls.json +++ b/extensions/typescript-language-features/package.nls.json @@ -8,6 +8,7 @@ "configuration.suggest.completeFunctionCalls": "Complete functions with their parameter signature.", "configuration.suggest.includeAutomaticOptionalChainCompletions": "Enable/disable showing completions on potentially undefined values that insert an optional chain call. Requires strict null checks to be enabled.", "configuration.suggest.includeCompletionsForImportStatements": "Enable/disable auto-import-style completions on partially-typed import statements.", + "typescript.experimental.aiQuickFix": "Enable/disable AI-assisted quick fixes.", "typescript.tsdk.desc": "Specifies the folder path to the tsserver and `lib*.d.ts` files under a TypeScript install to use for IntelliSense, for example: `./node_modules/typescript/lib`.\n\n- When specified as a user setting, the TypeScript version from `typescript.tsdk` automatically replaces the built-in TypeScript version.\n- When specified as a workspace setting, `typescript.tsdk` allows you to switch to use that workspace version of TypeScript for IntelliSense with the `TypeScript: Select TypeScript version` command.\n\nSee the [TypeScript documentation](https://code.visualstudio.com/docs/typescript/typescript-compiling#_using-newer-typescript-versions) for more detail about managing TypeScript versions.", "typescript.disableAutomaticTypeAcquisition": "Disables [automatic type acquisition](https://code.visualstudio.com/docs/nodejs/working-with-javascript#_typings-and-automatic-type-acquisition). Automatic type acquisition fetches `@types` packages from npm to improve IntelliSense for external libraries.", "typescript.enablePromptUseWorkspaceTsdk": "Enables prompting of users to use the TypeScript version configured in the workspace for Intellisense.", diff --git a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts index 515402f6e1e..a6553902cbe 100644 --- a/extensions/typescript-language-features/src/languageFeatures/quickFix.ts +++ b/extensions/typescript-language-features/src/languageFeatures/quickFix.ts @@ -20,11 +20,49 @@ import { applyCodeActionCommands, getEditForCodeAction } from './util/codeAction import { conditionalRegistration, requireSomeCapability } from './util/dependentRegistration'; type ApplyCodeActionCommand_args = { - readonly resource: vscode.Uri; + readonly document: vscode.TextDocument; readonly diagnostic: vscode.Diagnostic; readonly action: Proto.CodeFixAction; + readonly followupAction?: Command; }; +class EditorChatFollowUp implements Command { + + id: string = 'needsBetterName.editorChateFollowUp'; + + constructor(private readonly prompt: string, private readonly document: vscode.TextDocument, private readonly range: vscode.Range, private readonly client: ITypeScriptServiceClient) { + + } + + async execute() { + const findScopeEndLineFromNavTree = (startLine: number, navigationTree: Proto.NavigationTree[]): vscode.Range | undefined => { + for (const node of navigationTree) { + const range = typeConverters.Range.fromTextSpan(node.spans[0]); + if (startLine === range.start.line) { + return range; + } else if (startLine > range.start.line && startLine <= range.end.line && node.childItems) { + return findScopeEndLineFromNavTree(startLine, node.childItems); + } + } + return undefined; + }; + const filepath = this.client.toOpenTsFilePath(this.document); + if (!filepath) { + return; + } + const response = await this.client.execute('navtree', { file: filepath }, (new vscode.CancellationTokenSource()).token); + if (response.type !== 'response' || !response.body?.childItems) { + return; + } + const startLine = this.range.start.line; + const enclosingRange = findScopeEndLineFromNavTree(startLine, response.body.childItems); + if (!enclosingRange) { + return; + } + await vscode.commands.executeCommand('vscode.editorChat.start', { initialRange: enclosingRange, message: this.prompt, autoSend: true }); + } +} + class ApplyCodeActionCommand implements Command { public static readonly ID = '_typescript.applyCodeActionCommand'; public readonly id = ApplyCodeActionCommand.ID; @@ -35,7 +73,7 @@ class ApplyCodeActionCommand implements Command { private readonly telemetryReporter: TelemetryReporter, ) { } - public async execute({ resource, action, diagnostic }: ApplyCodeActionCommand_args): Promise { + public async execute({ document, action, diagnostic, followupAction }: ApplyCodeActionCommand_args): Promise { /* __GDPR__ "quickFix.execute" : { "owner": "mjbvz", @@ -49,8 +87,10 @@ class ApplyCodeActionCommand implements Command { fixName: action.fixName }); - this.diagnosticManager.deleteDiagnostic(resource, diagnostic); - return applyCodeActionCommands(this.client, action.commands, nulToken); + this.diagnosticManager.deleteDiagnostic(document.uri, diagnostic); + const codeActionResult = await applyCodeActionCommands(this.client, action.commands, nulToken); + await followupAction?.execute(); + return codeActionResult; } } @@ -313,22 +353,27 @@ class TypeScriptQuickFixProvider implements vscode.CodeActionProvider{ action: tsAction, diagnostic, resource }], + arguments: [{ action: tsAction, diagnostic, document, followupAction }], title: '' }; return codeAction; diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 00ff9e2cff7..1f67e2a66dd 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -202,7 +202,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I const extHostUriOpeners = rpcProtocol.set(ExtHostContext.ExtHostUriOpeners, new ExtHostUriOpeners(rpcProtocol)); const extHostProfileContentHandlers = rpcProtocol.set(ExtHostContext.ExtHostProfileContentHandlers, new ExtHostProfileContentHandlers(rpcProtocol)); rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); - const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostDocuments, extHostLogService)); + const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInteractiveEditor, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); const extHostSemanticSimilarity = rpcProtocol.set(ExtHostContext.ExtHostSemanticSimilarity, new ExtHostSemanticSimilarity(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); diff --git a/src/vs/workbench/api/common/extHostInteractiveEditor.ts b/src/vs/workbench/api/common/extHostInteractiveEditor.ts index 94ba77eeb02..90164a44aab 100644 --- a/src/vs/workbench/api/common/extHostInteractiveEditor.ts +++ b/src/vs/workbench/api/common/extHostInteractiveEditor.ts @@ -15,6 +15,8 @@ import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; import type * as vscode from 'vscode'; +import { ApiCommand, ApiCommandArgument, ApiCommandResult, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; +import { IRange } from 'vs/editor/common/core/range'; class ProviderWrapper { @@ -47,10 +49,40 @@ export class ExtHostInteractiveEditor implements ExtHostInteractiveEditorShape { constructor( mainContext: IMainContext, + extHostCommands: ExtHostCommands, private readonly _documents: ExtHostDocuments, private readonly _logService: ILogService, ) { this._proxy = mainContext.getProxy(MainContext.MainThreadInteractiveEditor); + + type EditorChatApiArg = { + initialRange?: vscode.Range; + message?: string; + autoSend?: boolean; + }; + + type InteractiveEditorRunOptions = { + initialRange?: IRange; + message?: string; + autoSend?: boolean; + }; + + extHostCommands.registerApiCommand(new ApiCommand( + 'vscode.editorChat.start', 'interactiveEditor.start', 'Invoke a new editor chat session', + [new ApiCommandArgument('Run arguments', '', _v => true, v => { + + if (!v) { + return undefined; + } + + return { + initialRange: v.initialRange ? typeConvert.Range.from(v.initialRange) : undefined, + message: v.message, + autoSend: v.autoSend + }; + })], + ApiCommandResult.Void + )); } registerProvider(extension: Readonly, provider: vscode.InteractiveEditorSessionProvider): vscode.Disposable {