/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Diagnostic, Disposable, CodeActionKind, DiagnosticSeverity } from 'vscode-languageserver-protocol' import { Uri, workspace, commands, events, languages, DiagnosticKind, ServiceStat, disposeAll } from 'coc.nvim' import { CachedNavTreeResponse } from './features/baseCodeLensProvider' import CompletionItemProvider from './features/completionItemProvider' import DefinitionProvider from './features/definitionProvider' import DirectiveCommentCompletionProvider from './features/directiveCommentCompletions' import DocumentHighlight from './features/documentHighlight' import DocumentSymbolProvider from './features/documentSymbol' import FileConfigurationManager from './features/fileConfigurationManager' import Folding from './features/folding' import FormattingProvider from './features/formatting' import HoverProvider from './features/hover' import ImplementationsCodeLensProvider from './features/implementationsCodeLens' // import TagCompletionProvider from './features/tagCompletion' import QuickfixProvider from './features/quickfix' import ImportfixProvider from './features/importFix' import RefactorProvider from './features/refactor' import ReferenceProvider from './features/references' import ReferencesCodeLensProvider from './features/referencesCodeLens' import RenameProvider from './features/rename' import SignatureHelpProvider from './features/signatureHelp' import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' import WatchBuild from './features/watchBuild' import WorkspaceSymbolProvider from './features/workspaceSymbols' import SmartSelection from './features/smartSelect' import TypeScriptServiceClient from './typescriptServiceClient' 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 suggestionSetting = 'suggestionActions.enabled' export default class LanguageProvider { public readonly fileConfigurationManager: FileConfigurationManager // tslint:disable-line private readonly disposables: Disposable[] = [] constructor( public client: TypeScriptServiceClient, private description: LanguageDescription, typingsStatus: TypingsStatus ) { this.fileConfigurationManager = new FileConfigurationManager(client) workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables) this.configurationChanged() events.on('BufEnter', bufnr => { let doc = workspace.getDocument(bufnr) if (!doc || client.state !== ServiceStat.Running) return if (description.modeIds.indexOf(doc.filetype) == -1) return this.fileConfigurationManager.ensureConfigurationForDocument(doc.textDocument) // tslint:disable-line }, this, this.disposables) let initialized = false client.onTsServerStarted(async () => { // tslint:disable-line if (!initialized) { for (let doc of workspace.documents) { if (description.modeIds.indexOf(doc.filetype) !== -1) { this.fileConfigurationManager.ensureConfigurationForDocument(doc.textDocument) // tslint:disable-line } } initialized = true this.registerProviders(client, typingsStatus) } else { this.client.diagnosticsManager.reInitialize() } }) } private configurationChanged(): void { const config = workspace.getConfiguration(this.id, null) this.client.diagnosticsManager.setEnableSuggestions(this.id, config.get(suggestionSetting, true)) } public dispose(): void { disposeAll(this.disposables) } private registerProviders( client: TypeScriptServiceClient, typingsStatus: TypingsStatus ): void { let languageIds = this.description.modeIds this.disposables.push( languages.registerCompletionItemProvider( `tsserver-${this.description.id}`, 'TSC', languageIds, new CompletionItemProvider( client, typingsStatus, this.fileConfigurationManager, this.description.id ), CompletionItemProvider.triggerCharacters ) ) if (this.client.apiVersion.gte(API.v230)) { this.disposables.push( languages.registerCompletionItemProvider( `${this.description.id}-directive`, 'TSC', languageIds, new DirectiveCommentCompletionProvider( client, ), ['@'] ) ) } let definitionProvider = new DefinitionProvider(client) this.disposables.push( languages.registerDefinitionProvider( languageIds, definitionProvider ) ) this.disposables.push( languages.registerTypeDefinitionProvider( languageIds, definitionProvider ) ) this.disposables.push( languages.registerImplementationProvider( languageIds, definitionProvider ) ) this.disposables.push( languages.registerReferencesProvider( languageIds, new ReferenceProvider(client) ) ) this.disposables.push( languages.registerHoverProvider( languageIds, new HoverProvider(client)) ) this.disposables.push( languages.registerDocumentHighlightProvider(languageIds, new DocumentHighlight(this.client)) ) this.disposables.push( languages.registerSignatureHelpProvider( languageIds, new SignatureHelpProvider(client), ['(', ',', '<', ')']) ) this.disposables.push( languages.registerDocumentSymbolProvider( languageIds, new DocumentSymbolProvider(client)) ) this.disposables.push( languages.registerWorkspaceSymbolProvider( languageIds, new WorkspaceSymbolProvider(client, languageIds)) ) this.disposables.push( languages.registerRenameProvider( languageIds, new RenameProvider(client)) ) let formatProvider = new FormattingProvider(client, this.fileConfigurationManager) this.disposables.push( languages.registerDocumentFormatProvider(languageIds, formatProvider) ) this.disposables.push( languages.registerDocumentRangeFormatProvider(languageIds, formatProvider) ) this.disposables.push( languages.registerOnTypeFormattingEditProvider(languageIds, formatProvider, [';', '}', '\n', String.fromCharCode(27)]) ) // this.disposables.push( // new ProjectError(client, commandManager) // ) if (this.client.apiVersion.gte(API.v280)) { 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 let conf = fileConfigurationManager.getLanguageConfiguration(this.id) if (this.client.apiVersion.gte(API.v290) && conf.get<boolean>('updateImportsOnFileMove.enable')) { this.disposables.push( new UpdateImportsOnFileRenameHandler(client, this.fileConfigurationManager, this.id) ) } if (this.client.apiVersion.gte(API.v240)) { this.disposables.push( languages.registerCodeActionProvider( languageIds, new RefactorProvider(client, this.fileConfigurationManager), 'tsserver', [CodeActionKind.Refactor])) } this.disposables.push( languages.registerCodeActionProvider( languageIds, new InstallModuleProvider(client), 'tsserver') ) this.disposables.push( languages.registerCodeActionProvider( languageIds, new QuickfixProvider(client), 'tsserver', [CodeActionKind.QuickFix])) this.disposables.push( languages.registerCodeActionProvider( languageIds, new ImportfixProvider(this.client.bufferSyncSupport), 'tsserver', [CodeActionKind.QuickFix])) let cachedResponse = new CachedNavTreeResponse() if (this.client.apiVersion.gte(API.v206) && conf.get<boolean>('referencesCodeLens.enable')) { this.disposables.push( languages.registerCodeLensProvider( languageIds, new ReferencesCodeLensProvider(client, cachedResponse))) } if (this.client.apiVersion.gte(API.v220) && conf.get<boolean>('implementationsCodeLens.enable')) { this.disposables.push( languages.registerCodeLensProvider( languageIds, new ImplementationsCodeLensProvider(client, cachedResponse))) } if (this.client.apiVersion.gte(API.v350)) { this.disposables.push( languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client)) ) } if (this.description.id == 'typescript') { this.disposables.push( new WatchBuild(commands) ) } // if (this.client.apiVersion.gte(API.v300)) { // this.disposables.push( // languages.registerCompletionItemProvider( // `tsserver-${this.description.id}-tag`, // 'TSC', // languageIds, // new TagCompletionProvider(client), // ['>'] // ) // ) // } } public handles(resource: Uri): boolean { let { modeIds, configFile } = this.description if (resource.toString().endsWith(configFile)) { return true } let doc = workspace.getDocument(resource.toString()) if (doc && modeIds.indexOf(doc.filetype) !== -1) { return true } let str = resource.toString() if (this.id === 'typescript' && /\.ts(x)?$/.test(str)) { return true } if (this.id === 'javascript' && /\.js(x)?$/.test(str)) { return true } return false } private get id(): string { // tslint:disable-line return this.description.id } public get diagnosticSource(): string { return this.description.diagnosticSource } public triggerAllDiagnostics(): void { this.client.bufferSyncSupport.requestAllDiagnostics() } public diagnosticsReceived( diagnosticsKind: DiagnosticKind, file: Uri, diagnostics: (Diagnostic & { reportUnnecessary: any })[] ): void { if (!this.client.bufferSyncSupport.shouldValidate(file.toString())) { return } const config = workspace.getConfiguration(this.id, file.toString()) const reportUnnecessary = config.get<boolean>('showUnused', true) this.client.diagnosticsManager.diagnosticsReceived(diagnosticsKind, file.toString(), diagnostics.filter(diag => { if (!reportUnnecessary) { diag.tags = undefined if (diag.reportUnnecessary && diag.severity === DiagnosticSeverity.Hint) { return false } } return true })) } }