From 26bb4b48727deff875faeca558498f33301ad188 Mon Sep 17 00:00:00 2001 From: chemzqm Date: Wed, 5 Jun 2019 23:11:56 +0800 Subject: [PATCH] add organizeImports codeAction --- package.json | 5 -- src/index.ts | 4 +- src/server/features/completionItemProvider.ts | 2 +- src/server/languageProvider.ts | 6 ++ src/server/organizeImports.ts | 75 ++++++++++++------- 5 files changed, 58 insertions(+), 34 deletions(-) diff --git a/package.json b/package.json index 1932ce0..074b11e 100644 --- a/package.json +++ b/package.json @@ -115,11 +115,6 @@ "default": "", "description": "Folder path for cache typings" }, - "tsserver.orgnizeImportOnSave": { - "type": "boolean", - "default": false, - "description": "Orgnize import on buffer will save" - }, "tsserver.formatOnType": { "type": "boolean", "default": true, diff --git a/src/index.ts b/src/index.ts index 9b4e937..f08cac3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,7 +1,7 @@ import { commands, ExtensionContext, services, workspace } from 'coc.nvim' import TsserverService from './server' import { AutoFixCommand, Command, ConfigurePluginCommand, OpenTsServerLogCommand, ReloadProjectsCommand, TypeScriptGoToProjectConfigCommand } from './server/commands' -import OrganizeImportsCommand from './server/organizeImports' +import { OrganizeImportsCommand } from './server/organizeImports' import { PluginManager } from './utils/plugins' interface API { @@ -30,7 +30,7 @@ export async function activate(context: ExtensionContext): Promise { registCommand(new ReloadProjectsCommand(service.clientHost)) registCommand(new OpenTsServerLogCommand(service.clientHost)) registCommand(new TypeScriptGoToProjectConfigCommand(service.clientHost)) - registCommand(new OrganizeImportsCommand(service.clientHost)) + registCommand(new OrganizeImportsCommand(service.clientHost.serviceClient)) registCommand(new ConfigurePluginCommand(pluginManager)) registCommand(commands.register({ id: 'tsserver.restart', diff --git a/src/server/features/completionItemProvider.ts b/src/server/features/completionItemProvider.ts index 3c75a23..fc35ccc 100644 --- a/src/server/features/completionItemProvider.ts +++ b/src/server/features/completionItemProvider.ts @@ -300,7 +300,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP if (additionalTextEdits.length && this.noSemicolons) { // remove comma additionalTextEdits.forEach(o => { - o.newText = o.newText.replace(/;/g, '') + o.newText = o.newText.replace(/;(?:(\n|$))/g, '') }) } return { diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index c0e5eb5..ade5926 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -34,6 +34,7 @@ import InstallModuleProvider from './features/moduleInstall' import API from './utils/api' import { LanguageDescription } from './utils/languageDescription' import TypingsStatus from './utils/typingsStatus' +import { OrganizeImportsCodeActionProvider } from './organizeImports' const validateSetting = 'validate.enable' const suggestionSetting = 'suggestionActions.enabled' @@ -220,6 +221,11 @@ export default class LanguageProvider { this.disposables.push( languages.registerFoldingRangeProvider(languageIds, new Folding(this.client)) ) + this.disposables.push( + languages.registerCodeActionProvider(languageIds, + new OrganizeImportsCodeActionProvider(this.client, this.fileConfigurationManager), + `tsserver-${this.description.id}`, [CodeActionKind.SourceOrganizeImports]) + ) } let { fileConfigurationManager } = this diff --git a/src/server/organizeImports.ts b/src/server/organizeImports.ts index 5f6def1..6675bd6 100644 --- a/src/server/organizeImports.ts +++ b/src/server/organizeImports.ts @@ -2,40 +2,26 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextDocumentWillSaveEvent, workspace } from 'coc.nvim' -import { TextDocument, TextEdit, WorkspaceEdit, CancellationToken } from 'vscode-languageserver-protocol' +import { workspace, CodeActionProvider, CodeActionProviderMetadata } from 'coc.nvim' +import { CancellationToken, Range, TextDocument, CodeActionContext, WorkspaceEdit, CodeActionKind, CodeAction } from 'vscode-languageserver-protocol' import { Command } from './commands' import Proto from './protocol' -import TypeScriptServiceClientHost from './typescriptServiceClientHost' import { standardLanguageDescriptions } from './utils/languageDescription' import { languageIds } from './utils/languageModeIds' import * as typeconverts from './utils/typeConverters' +import FileConfigurationManager from './features/fileConfigurationManager' +import TypeScriptServiceClient from './typescriptServiceClient' -export default class OrganizeImportsCommand implements Command { +export class OrganizeImportsCommand implements Command { public readonly id: string = 'tsserver.organizeImports' constructor( - private readonly client: TypeScriptServiceClientHost + private readonly client: TypeScriptServiceClient ) { - workspace.onWillSaveUntil(this.onWillSaveUntil, this, 'tsserver') - } - - private onWillSaveUntil(event: TextDocumentWillSaveEvent): void { - let config = workspace.getConfiguration('tsserver') - let format = config.get('orgnizeImportOnSave', false) - if (!format) return - let { document } = event - if (languageIds.indexOf(document.languageId) == -1) return - let willSaveWaitUntil = async (): Promise => { - let edit = await this.getTextEdits(document) - if (!edit) return [] - return edit.changes ? edit.changes[document.uri] : [] - } - event.waitUntil(willSaveWaitUntil()) } private async getTextEdits(document: TextDocument): Promise { - let client = this.client.serviceClient + let client = this.client let file = client.toPath(document.uri) const args: Proto.OrganizeImportsRequestArgs = { scope: { @@ -64,7 +50,7 @@ export default class OrganizeImportsCommand implements Command { if (changes) { for (let c of Object.keys(changes)) { for (let textEdit of changes[c]) { - textEdit.newText = textEdit.newText.replace(/;/g, '') + textEdit.newText = textEdit.newText.replace(/;(?:(\n|$))/g, '') } } } @@ -72,11 +58,48 @@ export default class OrganizeImportsCommand implements Command { return edit } - public async execute(): Promise { - let document = await workspace.document - if (languageIds.indexOf(document.filetype) == -1) return - let edit = await this.getTextEdits(document.textDocument) + public async execute(document?: TextDocument): Promise { + if (!document) { + let doc = await workspace.document + if (languageIds.indexOf(doc.filetype) == -1) return + document = doc.textDocument + } + let edit = await this.getTextEdits(document) if (edit) await workspace.applyEdit(edit) return } } + +export class OrganizeImportsCodeActionProvider implements CodeActionProvider { + // public static readonly minVersion = API.v280 + + public constructor( + private readonly client: TypeScriptServiceClient, + private readonly fileConfigManager: FileConfigurationManager, + ) { + } + + public readonly metadata: CodeActionProviderMetadata = { + providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports] + } + + public provideCodeActions( + document: TextDocument, + _range: Range, + context: CodeActionContext, + _token: CancellationToken + ): CodeAction[] { + if (languageIds.indexOf(document.languageId) == -1) return + + if (!context.only || !context.only.includes(CodeActionKind.SourceOrganizeImports)) { + return [] + } + + const action = CodeAction.create('Organize Imports', { + title: '', + command: 'tsserver.organizeImports', + arguments: [document] + }, CodeActionKind.SourceOrganizeImports) + return [action] + } +}