🌐 AI搜索 & 代理 主页
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 0 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
- [Code actions on save](#code-actions-on-save)
- [Workspace commands \(`workspace/executeCommand`\)](#workspace-commands-workspaceexecutecommand)
- [Go to Source Definition](#go-to-source-definition)
- [Apply Workspace Edits](#apply-workspace-edits)
- [Apply Code Action](#apply-code-action)
- [Apply Refactoring](#apply-refactoring)
- [Organize Imports](#organize-imports)
- [Rename File](#rename-file)
Expand Down Expand Up @@ -127,36 +125,6 @@ Most of the time, you'll execute commands with arguments retrieved from another

(This command is supported from Typescript 4.7.)

#### Apply Workspace Edits

- Request:
```ts
{
command: `_typescript.applyWorkspaceEdit`
arguments: [lsp.WorkspaceEdit]
}
```
- Response:
```ts
lsp.ApplyWorkspaceEditResult
```

#### Apply Code Action

- Request:
```ts
{
command: `_typescript.applyCodeAction`
arguments: [
tsp.CodeAction, // TypeScript Code Action object
]
}
```
- Response:
```ts
void
```

#### Apply Refactoring

- Request:
Expand Down
2 changes: 0 additions & 2 deletions src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@ import { TypeScriptVersionSource } from './tsServer/versionProvider.js';
import { TSServerRequestCommand } from './commands/tsserverRequests.js';

export const Commands = {
APPLY_WORKSPACE_EDIT: '_typescript.applyWorkspaceEdit',
APPLY_CODE_ACTION: '_typescript.applyCodeAction',
APPLY_REFACTORING: '_typescript.applyRefactoring',
CONFIGURE_PLUGIN: '_typescript.configurePlugin',
ORGANIZE_IMPORTS: '_typescript.organizeImports',
Expand Down
41 changes: 41 additions & 0 deletions src/commands/commandManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// sync: file[extensions/typescript-language-features/src/commands/commandManager.ts] sha[f76ac124233270762d11ec3afaaaafcba53b3bbf]
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
* Copyright (C) 2024 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

export interface Command {
readonly id: string;

execute(...args: unknown[]): any;
}

export class CommandManager {
private readonly commands = new Map<string, Command>();

public dispose(): void {
this.commands.clear();
}

public register<T extends Command>(command: T): void {
const entry = this.commands.get(command.id);
if (!entry) {
this.commands.set(command.id, command);
}
}

public async handle(commandId: Command['id'], ...args: unknown[]): Promise<boolean> {
const entry = this.commands.get(commandId);
if (entry) {
await entry.execute(...args);
return true;
}
return false;
}
}
2 changes: 1 addition & 1 deletion src/diagnostic-queue.ts → src/diagnosticsManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ class FileDiagnostics {
}
}

export class DiagnosticEventQueue {
export class DiagnosticsManager {
protected readonly diagnostics = new Map<string, FileDiagnostics>();
private ignoredDiagnosticCodes: Set<number> = new Set();

Expand Down
130 changes: 130 additions & 0 deletions src/features/codeActions/codeActionManager.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/*
* Copyright (C) 2024 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import * as lsp from 'vscode-languageserver';
import { type ITypeScriptServiceClient } from '../../typescriptService.js';
import type FileConfigurationManager from '../fileConfigurationManager.js';
import { CommandManager } from '../../commands/commandManager.js';
import { LspDocument } from '../../document.js';
import { type CodeActionProvider, type TsCodeAction } from './codeActionProvider.js';
import { TypeScriptAutoFixProvider } from './fixAll.js';
import { TypeScriptQuickFixProvider } from './quickFix.js';
import { nulToken } from '../../utils/cancellation.js';
import { type SupportedFeatures } from '../../ts-protocol.js';
import { type DiagnosticsManager } from '../../diagnosticsManager.js';

interface ResolveData {
globalId?: number;
providerId?: number;
}

/**
* Requests code actions from registered providers and ensures that returned code actions have global IDs assigned
* and are cached for the purpose of codeAction/resolve request.
*/
export class CodeActionManager {
private providerMap = new Map<number, CodeActionProvider>;
private nextProviderId = 1;
private resolveCodeActionsMap = new Map<number, TsCodeAction>;
private nextGlobalCodeActionId = 1;

constructor(
client: ITypeScriptServiceClient,
fileConfigurationManager: FileConfigurationManager,
commandManager: CommandManager,
diagnosticsManager: DiagnosticsManager,
private readonly features: SupportedFeatures,
) {
this.addProvider(new TypeScriptAutoFixProvider(client, fileConfigurationManager, diagnosticsManager));
this.addProvider(new TypeScriptQuickFixProvider(client, fileConfigurationManager, commandManager, diagnosticsManager, features));
}

public get kinds(): lsp.CodeActionKind[] {
const allKinds: lsp.CodeActionKind[] = [];

for (const [_, provider] of this.providerMap) {
allKinds.push(...provider.getMetadata().providedCodeActionKinds || []);
}

return allKinds;
}

public async provideCodeActions(document: LspDocument, range: lsp.Range, context: lsp.CodeActionContext, token?: lsp.CancellationToken): Promise<(lsp.Command | lsp.CodeAction)[]> {
this.resolveCodeActionsMap.clear();

const allCodeActions: (lsp.Command | lsp.CodeAction)[] = [];

for (const [providerId, provider] of this.providerMap.entries()) {
const codeActions = await provider.provideCodeActions(document, range, context, token || nulToken);
if (!codeActions) {
continue;
}

for (const action of codeActions) {
if (lsp.Command.is(action)) {
allCodeActions.push(action);
continue;
}

const lspCodeAction = action.toLspCodeAction();

if (provider.isCodeActionResolvable(action)) {
const globalId = this.nextGlobalCodeActionId++;
this.resolveCodeActionsMap.set(globalId, action);
lspCodeAction.data = {
globalId,
providerId,
} satisfies ResolveData;
}

allCodeActions.push(lspCodeAction);
}
}

return allCodeActions;
}

public async resolveCodeAction(codeAction: lsp.CodeAction, token?: lsp.CancellationToken): Promise<lsp.CodeAction> {
if (!this.features.codeActionResolveSupport) {
return codeAction;
}

const { globalId, providerId } = codeAction.data as ResolveData || {};
if (globalId === undefined || providerId === undefined) {
return codeAction;
}

const provider = this.providerMap.get(providerId);
if (!provider || !provider.resolveCodeAction) {
return codeAction;
}

const tsCodeAction = this.resolveCodeActionsMap.get(globalId);
if (!tsCodeAction || !providerId) {
return codeAction;
}

const resolvedTsCodeAction = await provider.resolveCodeAction(tsCodeAction, token || nulToken);
if (!resolvedTsCodeAction) {
return codeAction;
}

const lspCodeAction = resolvedTsCodeAction.toLspCodeAction();
for (const property of this.features.codeActionResolveSupport.properties as Array<keyof lsp.CodeAction>) {
if (property in lspCodeAction) {
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
codeAction[property] = lspCodeAction[property];
}
}

return codeAction;
}

private addProvider(provider: CodeActionProvider): void {
this.providerMap.set(this.nextProviderId++, provider);
}
}
103 changes: 103 additions & 0 deletions src/features/codeActions/codeActionProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
* Copyright (C) 2022 TypeFox and others.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
*/

import * as lsp from 'vscode-languageserver';
import type { LspDocument } from '../../document.js';

export interface TsCodeActionProvider {
getQuickFixAllTsCodeActionByFixName(fixName: string): TsCodeAction | undefined;
}

export class TsCodeAction implements lsp.CodeAction {
public command: lsp.CodeAction['command'];
public diagnostics: lsp.CodeAction['diagnostics'];
public disabled: lsp.CodeAction['disabled'];
public edit: lsp.CodeAction['edit'];
public isPreferred: lsp.CodeAction['isPreferred'];

constructor(
public readonly title: string,
public readonly kind: lsp.CodeActionKind,
) {
}

toLspCodeAction(): lsp.CodeAction {
const codeAction = lsp.CodeAction.create(this.title, this.kind);

if (this.command !== undefined) {
codeAction.command = this.command;
}
if (this.diagnostics !== undefined) {
codeAction.diagnostics = this.diagnostics;
}
if (this.disabled !== undefined) {
codeAction.disabled = this.disabled;
}
if (this.edit !== undefined) {
codeAction.edit = this.edit;
}
if (this.isPreferred !== undefined) {
codeAction.isPreferred = this.isPreferred;
}

return codeAction;
}
}

type ProviderResult<T> = T | undefined | null | Thenable<T | undefined | null>;

/**
* Provides contextual actions for code. Code actions typically either fix problems or beautify/refactor code.
*/
export interface CodeActionProvider<T extends TsCodeAction = TsCodeAction> {
getMetadata(): CodeActionProviderMetadata;

/**
* Get code actions for a given range in a document.
*/
provideCodeActions(document: LspDocument, range: lsp.Range, context: lsp.CodeActionContext, token: lsp.CancellationToken): ProviderResult<(lsp.Command | T)[]>;

/**
* Whether given code action can be resolved with `resolveCodeAction`.
*/
isCodeActionResolvable(codeAction: T): boolean;

/**
* Given a code action fill in its {@linkcode lsp.CodeAction.edit edit}-property. Changes to
* all other properties, like title, are ignored. A code action that has an edit
* will not be resolved.
*
* *Note* that a code action provider that returns commands, not code actions, cannot successfully
* implement this function. Returning commands is deprecated and instead code actions should be
* returned.
*
* @param codeAction A code action.
* @param token A cancellation token.
* @returns The resolved code action or a thenable that resolves to such. It is OK to return the given
* `item`. When no result is returned, the given `item` will be used.
*/
resolveCodeAction?(codeAction: T, token: lsp.CancellationToken): ProviderResult<T>;
}

/**
* Metadata about the type of code actions that a {@link CodeActionProvider} provides.
*/
export interface CodeActionProviderMetadata {
/**
* List of {@link CodeActionKind CodeActionKinds} that a {@link CodeActionProvider} may return.
*
* This list is used to determine if a given `CodeActionProvider` should be invoked or not.
* To avoid unnecessary computation, every `CodeActionProvider` should list use `providedCodeActionKinds`. The
* list of kinds may either be generic, such as `[CodeActionKind.Refactor]`, or list out every kind provided,
* such as `[CodeActionKind.Refactor.Extract.append('function'), CodeActionKind.Refactor.Extract.append('constant'), ...]`.
*/
readonly providedCodeActionKinds?: readonly lsp.CodeActionKind[];
}
Loading