support languages from plugins

Closes #235
This commit is contained in:
Qiming Zhao 2020-12-08 18:50:01 +08:00
parent c4ae2c2fbf
commit de0fa5c6a2
10 changed files with 207 additions and 255 deletions

View file

@ -30,6 +30,8 @@
"onLanguage:typescript.jsx",
"onLanguage:typescriptreact",
"onLanguage:jsx-tags",
"onLanguage:jsonc",
"onCommand:_typescript.configurePlugin",
"onCommand:typescript.reloadProjects",
"onCommand:javascript.reloadProjects",
"onCommand:javascript.goToProjectConfig",

View file

@ -9,40 +9,39 @@ interface API {
}
export async function activate(context: ExtensionContext): Promise<API> {
let { subscriptions } = context
let { subscriptions, logger } = context
const config = workspace.getConfiguration().get<any>('tsserver', {})
if (!config.enable) return
const pluginManager = new PluginManager()
const service = new TsserverService(pluginManager)
subscriptions.push(
(services as any).regist(service)
)
await service.start()
function registCommand(cmd: Command): void {
let { id, execute } = cmd
subscriptions.push(commands.registerCommand(id as string, execute, cmd))
}
registCommand(new AutoFixCommand(service.clientHost))
registCommand(new ReloadProjectsCommand(service.clientHost))
registCommand(new OpenTsServerLogCommand(service.clientHost))
registCommand(new TypeScriptGoToProjectConfigCommand(service.clientHost))
registCommand(new OrganizeImportsCommand(service.clientHost.serviceClient))
registCommand(new ConfigurePluginCommand(pluginManager))
registCommand(commands.register({
id: 'tsserver.restart',
execute: (): void => {
// tslint:disable-next-line:no-floating-promises
service.stop().then(() => {
setTimeout(() => {
service.restart()
}, 100)
})
}
}))
registCommand(new AutoFixCommand(service))
registCommand(new ReloadProjectsCommand(service))
registCommand(new OpenTsServerLogCommand(service))
registCommand(new TypeScriptGoToProjectConfigCommand(service))
registCommand(new OrganizeImportsCommand(service))
service.start().then(() => {
subscriptions.push(services.regist(service))
registCommand(commands.register({
id: 'tsserver.restart',
execute: (): void => {
// tslint:disable-next-line:no-floating-promises
service.stop().then(() => {
setTimeout(() => {
service.restart()
}, 100)
})
}
}))
}, e => {
logger.error(`Error on service start:`, e)
})
return {
configurePlugin: (pluginId: string, configuration: {}): void => {
pluginManager.setConfiguration(pluginId, configuration)

View file

@ -1,4 +1,4 @@
import { Uri as URI, diagnosticManager, workspace, commands } from 'coc.nvim'
import { Uri as URI, diagnosticManager, workspace, commands, ServiceStat } from 'coc.nvim'
import { CancellationToken, Diagnostic } from 'vscode-languageserver-protocol'
import * as Proto from './protocol'
import TypeScriptServiceClientHost from './typescriptServiceClientHost'
@ -7,6 +7,7 @@ import { TextEdit, Range } from 'vscode-languageserver-types'
import { installModules } from './utils/modules'
import { nodeModules } from './utils/helper'
import { PluginManager } from '../utils/plugins'
import TsserverService from '../server'
export interface Command {
readonly id: string | string[]
@ -17,11 +18,12 @@ export class ReloadProjectsCommand implements Command {
public readonly id = 'tsserver.reloadProjects'
public constructor(
private readonly client: TypeScriptServiceClientHost
private readonly service: TsserverService
) { }
public execute(): void {
this.client.reloadProjects()
public async execute(): Promise<void> {
let client = await this.service.getClientHost()
client.reloadProjects()
workspace.showMessage('projects reloaded')
}
}
@ -30,11 +32,12 @@ export class OpenTsServerLogCommand implements Command {
public readonly id = 'tsserver.openTsServerLog'
public constructor(
private readonly client: TypeScriptServiceClientHost
private readonly service: TsserverService
) { }
public execute(): void {
this.client.serviceClient.openTsServerLogFile() // tslint:disable-line
public async execute(): Promise<void> {
let client = await this.service.getClientHost()
client.serviceClient.openTsServerLogFile() // tslint:disable-line
}
}
@ -42,17 +45,18 @@ export class TypeScriptGoToProjectConfigCommand implements Command {
public readonly id = 'tsserver.goToProjectConfig'
public constructor(
private readonly client: TypeScriptServiceClientHost
private readonly service: TsserverService
) { }
public async execute(): Promise<void> {
let client = await this.service.getClientHost()
let doc = await workspace.document
let { languageId } = doc.textDocument
if (this.client.serviceClient.modeIds.indexOf(languageId) == -1) {
workspace.showMessage(`Could not determine TypeScript or JavaScript project. Unsupported file type: ${languageId}`, 'warning')
if (client.serviceClient.modeIds.indexOf(languageId) == -1) {
throw new Error(`Could not determine TypeScript or JavaScript project. Unsupported file type: ${languageId}`)
return
}
await goToProjectConfig(this.client, doc.uri)
await goToProjectConfig(client, doc.uri)
}
}
@ -69,13 +73,11 @@ async function goToProjectConfig(clientHost: TypeScriptServiceClientHost, uri: s
workspace.showMessage('Could not determine TypeScript or JavaScript project.', 'warning')
return
}
const { configFileName } = res.body
if (configFileName && !isImplicitProjectConfigFile(configFileName)) {
await workspace.openResource(URI.file(configFileName).toString())
return
}
workspace.showMessage('Config file not found', 'warning')
}
@ -92,18 +94,22 @@ const autoFixableDiagnosticCodes = new Set<number>([
export class AutoFixCommand implements Command {
public readonly id = 'tsserver.executeAutofix'
constructor(private client: TypeScriptServiceClientHost) {
constructor(private service: TsserverService) {
}
public async execute(): Promise<void> {
let document = await workspace.document
let { uri } = document
let handles = await this.client.handles(uri)
if (!handles) {
workspace.showMessage(`Document ${uri} is not handled by tsserver.`, 'warning')
if (this.service.state != ServiceStat.Running) {
throw new Error('service not running')
return
}
let file = this.client.serviceClient.toPath(document.uri)
let client = await this.service.getClientHost()
let document = await workspace.document
let handles = await client.handles(document.textDocument)
if (!handles) {
throw new Error(`Document ${document.uri} is not handled by tsserver.`)
return
}
let file = client.serviceClient.toPath(document.uri)
let diagnostics = diagnosticManager.getDiagnostics(document.uri)
let missingDiagnostics = diagnostics.filter(o => o.code == 2307)
if (missingDiagnostics.length) {
@ -125,7 +131,6 @@ export class AutoFixCommand implements Command {
arr.push(curr)
return arr
}, [] as Diagnostic[])
let client = this.client.serviceClient
let edits: TextEdit[] = []
let command: string
let names: string[] = []
@ -134,7 +139,7 @@ export class AutoFixCommand implements Command {
...typeConverters.Range.toFileRangeRequestArgs(file, diagnostic.range),
errorCodes: [+(diagnostic.code!)]
}
const response = await client.execute('getCodeFixes', args, CancellationToken.None)
const response = await client.serviceClient.execute('getCodeFixes', args, CancellationToken.None)
if (response.type !== 'response' || !response.body || response.body.length < 1) {
if (diagnostic.code == 2304) {
let { range } = diagnostic

View file

@ -1,7 +1,7 @@
import { Uri, commands } from 'coc.nvim'
import { Command } from 'coc.nvim/lib/commands'
import { CodeActionProvider } from 'coc.nvim/lib/provider'
import { CancellationToken, CodeAction, CodeActionContext, Range } from 'vscode-languageserver-protocol'
import { CancellationToken, CodeAction, CodeActionContext, CodeActionKind, Range } from 'vscode-languageserver-protocol'
import { TextDocument } from 'vscode-languageserver-textdocument'
import { ITypeScriptServiceClient } from '../typescriptService'
import { installModules } from '../utils/modules'
@ -35,7 +35,7 @@ export default class InstallModuleProvider implements CodeActionProvider {
let { diagnostics } = context
let diags = diagnostics.filter(s => s.code == 2307)
let names = diags.map(o => {
let ms = o.message.match(/module\s'(.+)'\./)
let ms = o.message.match(/module\s'(.+)'/)
return ms ? ms[1] : null
})
names = names.filter(s => s != null)
@ -48,7 +48,7 @@ export default class InstallModuleProvider implements CodeActionProvider {
command: InstallModuleCommand.ID,
arguments: [document.uri, name]
}
let codeAction = CodeAction.create(title, command)
let codeAction = CodeAction.create(title, command, CodeActionKind.QuickFix)
actions.push(codeAction)
}
return actions

View file

@ -1,6 +1,4 @@
import { disposeAll, StatusBarItem, TaskOptions, Uri, workspace } from 'coc.nvim'
import { CommandManager } from 'coc.nvim/lib/commands'
import Task from 'coc.nvim/lib/model/task'
import { disposeAll, commands, StatusBarItem, TaskOptions, Uri, workspace } from 'coc.nvim'
import path from 'path'
import { Disposable, Location } from 'vscode-languageserver-protocol'
import TypeScriptServiceClient from '../typescriptServiceClient'
@ -26,16 +24,15 @@ export default class WatchProject implements Disposable {
public static readonly id: string = 'tsserver.watchBuild'
public static readonly startTexts: string[] = ['Starting compilation in watch mode', 'Starting incremental compilation']
private statusItem: StatusBarItem
private task: Task
private task: any
private options: TaskOptions
public constructor(
commandManager: CommandManager,
private client: TypeScriptServiceClient
) {
this.statusItem = workspace.createStatusBarItem(1, { progress: true })
let task = this.task = workspace.createTask('TSC')
this.disposables.push(commandManager.registerCommand(WatchProject.id, async () => {
this.disposables.push(commands.registerCommand(WatchProject.id, async () => {
let opts = this.options = await this.getOptions()
await this.start(opts)
}))

View file

@ -33,6 +33,24 @@ export default class TsserverService implements IServiceProvider {
return workspace.getConfiguration('tsserver')
}
/**
* Get running client host.
*/
public getClientHost(): Promise<TypeScriptServiceClientHost> {
if (this.state == ServiceStat.Running) return Promise.resolve(this.clientHost)
this.start()
return new Promise((resolve, reject) => {
let timer = setTimeout(() => {
reject(new Error(`Server not started after 5s`))
}, 5000)
let disposable = this.onServiceReady(() => {
clearTimeout(timer)
disposable.dispose()
resolve(this.clientHost)
})
})
}
public start(): Promise<void> {
if (this.clientHost) return
this.state = ServiceStat.Starting

View file

@ -2,7 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, DiagnosticKind, disposeAll, languages, Uri, workspace } from 'coc.nvim'
import { DiagnosticKind, disposeAll, languages, Uri, workspace } from 'coc.nvim'
import path from 'path'
import { CodeActionKind, Diagnostic, DiagnosticSeverity, Disposable, TextDocument } from 'vscode-languageserver-protocol'
import { CachedNavTreeResponse } from './features/baseCodeLensProvider'
@ -27,8 +27,6 @@ import RenameProvider from './features/rename'
import SignatureHelpProvider from './features/signatureHelp'
import SmartSelection from './features/smartSelect'
import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename'
import WatchBuild from './features/watchBuild'
import WorkspaceSymbolProvider from './features/workspaceSymbols'
import { OrganizeImportsCodeActionProvider } from './organizeImports'
import TypeScriptServiceClient from './typescriptServiceClient'
import API from './utils/api'
@ -48,16 +46,14 @@ export default class LanguageProvider {
) {
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
this.configurationChanged()
let initialized = false
let initialized = false
client.onTsServerStarted(async () => { // tslint:disable-line
if (!initialized) {
initialized = true
this.registerProviders(client, typingsStatus)
} else {
this.client.diagnosticsManager.reInitialize()
}
})
}, null, this.disposables)
}
private configurationChanged(): void {
@ -69,201 +65,89 @@ export default class LanguageProvider {
disposeAll(this.disposables)
}
private _register(disposable: Disposable): void {
this.disposables.push(disposable)
}
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
),
let clientId = `tsserver-${this.description.id}`
this._register(
languages.registerCompletionItemProvider(clientId, '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,
),
['@']
)
)
this._register(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))
)
if (this.description.id == 'typescript') {
this.disposables.push(
languages.registerWorkspaceSymbolProvider(
new WorkspaceSymbolProvider(client, languageIds))
)
}
this.disposables.push(
languages.registerRenameProvider(
languageIds,
new RenameProvider(client, this.fileConfigurationManager))
)
this._register(languages.registerDefinitionProvider(languageIds, definitionProvider))
this._register(languages.registerTypeDefinitionProvider(languageIds, definitionProvider))
this._register(languages.registerImplementationProvider(languageIds, definitionProvider))
this._register(languages.registerReferencesProvider(languageIds, new ReferenceProvider(client)))
this._register(languages.registerHoverProvider(languageIds, new HoverProvider(client)))
this._register(languages.registerDocumentHighlightProvider(languageIds, new DocumentHighlight(this.client)))
this._register(languages.registerSignatureHelpProvider(languageIds, new SignatureHelpProvider(client), ['(', ',', '<', ')']))
this._register(languages.registerDocumentSymbolProvider(languageIds, new DocumentSymbolProvider(client)))
this._register(languages.registerRenameProvider(languageIds, new RenameProvider(client, this.fileConfigurationManager)))
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])
)
}
this._register(languages.registerDocumentFormatProvider(languageIds, formatProvider))
this._register(languages.registerDocumentRangeFormatProvider(languageIds, formatProvider))
this._register(languages.registerOnTypeFormattingEditProvider(languageIds, formatProvider, [';', '}', '\n', String.fromCharCode(27)]))
this._register(languages.registerCodeActionProvider(languageIds, new InstallModuleProvider(client), 'tsserver'))
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 (['javascript', 'typescript'].includes(this.id)) {
if (this.client.apiVersion.gte(API.v290) && conf.get<boolean>('updateImportsOnFileMove.enable')) {
this._register(new UpdateImportsOnFileRenameHandler(client, this.fileConfigurationManager, this.id))
}
}
if (this.client.apiVersion.gte(API.v280)) {
this._register(languages.registerFoldingRangeProvider(languageIds, new Folding(this.client)))
this._register(
languages.registerCodeActionProvider(languageIds,
new OrganizeImportsCodeActionProvider(this.client, this.fileConfigurationManager),
'tsserver', [CodeActionKind.SourceOrganizeImports])
)
}
if (this.client.apiVersion.gte(API.v240)) {
this.disposables.push(
this._register(
languages.registerCodeActionProvider(
languageIds,
new RefactorProvider(client, this.fileConfigurationManager),
'tsserver',
[CodeActionKind.Refactor]))
}
this.disposables.push(
this._register(
languages.registerCodeActionProvider(
languageIds,
new InstallModuleProvider(client),
'tsserver')
)
this.disposables.push(
languageIds, new QuickfixProvider(client, this.fileConfigurationManager),
'tsserver', [CodeActionKind.QuickFix]))
this._register(
languages.registerCodeActionProvider(
languageIds,
new QuickfixProvider(client, this.fileConfigurationManager),
'tsserver',
[CodeActionKind.QuickFix]))
this.disposables.push(
languages.registerCodeActionProvider(
languageIds,
new ImportfixProvider(this.client.bufferSyncSupport),
'tsserver',
[CodeActionKind.QuickFix]))
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.v206) && conf.get<boolean>('referencesCodeLens.enable')) {
this._register(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.v220) && conf.get<boolean>('implementationsCodeLens.enable')) {
this._register(languages.registerCodeLensProvider(languageIds, new ImplementationsCodeLensProvider(client, cachedResponse)))
}
if (this.client.apiVersion.gte(API.v350)) {
this.disposables.push(
languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client))
)
this._register(languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client)))
}
if (this.description.id == 'typescript') {
// this.client.apiVersion
this.disposables.push(
new WatchBuild(commands, this.client)
)
}
// if (this.client.apiVersion.gte(API.v300)) {
// this.disposables.push(
// this._register(
// languages.registerCompletionItemProvider(
// `tsserver-${this.description.id}-tag`,
// 'TSC',

View file

@ -11,17 +11,17 @@ import { standardLanguageDescriptions } from './utils/languageDescription'
import * as typeconverts from './utils/typeConverters'
import FileConfigurationManager from './features/fileConfigurationManager'
import TypeScriptServiceClient from './typescriptServiceClient'
import TsserverService from '../server'
export class OrganizeImportsCommand implements Command {
public readonly id: string = 'tsserver.organizeImports'
constructor(
private readonly client: TypeScriptServiceClient
private readonly service: TsserverService
) {
}
private async getTextEdits(document: TextDocument): Promise<WorkspaceEdit | null> {
let client = this.client
private async getTextEdits(client: TypeScriptServiceClient, document: TextDocument): Promise<WorkspaceEdit | null> {
let file = client.toPath(document.uri)
const args: Proto.OrganizeImportsRequestArgs = {
scope: {
@ -31,7 +31,7 @@ export class OrganizeImportsCommand implements Command {
}
}
}
const response = await this.client.interruptGetErr(() => this.client.execute('organizeImports', args, CancellationToken.None))
const response = await client.interruptGetErr(() => client.execute('organizeImports', args, CancellationToken.None))
if (!response || response.type != 'response' || !response.success) {
return
}
@ -46,13 +46,18 @@ export class OrganizeImportsCommand implements Command {
}
public async execute(document?: TextDocument): Promise<void> {
let client = await this.service.getClientHost()
if (!document) {
let doc = await workspace.document
if (!doc.attached) return
if (this.client.modeIds.indexOf(doc.textDocument.languageId) == -1) return
if (!doc.attached) {
throw new Error(`Document not attached.`)
}
if (client.serviceClient.modeIds.indexOf(doc.filetype) == -1) {
throw new Error(`filetype "${doc.filetype}" not supported by tsserver.`)
}
document = doc.textDocument
}
let edit = await this.getTextEdits(document)
let edit = await this.getTextEdits(client.serviceClient, document)
if (edit) await workspace.applyEdit(edit)
return
}

View file

@ -2,18 +2,21 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Uri, DiagnosticKind, disposeAll, workspace } from 'coc.nvim'
import { Uri, DiagnosticKind, disposeAll, workspace, languages } from 'coc.nvim'
import { Range, Diagnostic, DiagnosticSeverity, Disposable, Position, CancellationToken, DiagnosticRelatedInformation } from 'vscode-languageserver-protocol'
import LanguageProvider from './languageProvider'
import * as Proto from './protocol'
import * as PConst from './protocol.const'
import FileConfigurationManager from './features/fileConfigurationManager'
import TypeScriptServiceClient from './typescriptServiceClient'
import { LanguageDescription } from './utils/languageDescription'
import { DiagnosticLanguage, LanguageDescription } from './utils/languageDescription'
import * as typeConverters from './utils/typeConverters'
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus'
import { PluginManager } from '../utils/plugins'
import { flatten } from '../utils/arrays'
import WatchBuild from './features/watchBuild'
import WorkspaceSymbolProvider from './features/workspaceSymbols'
import { TextDocument } from 'vscode-languageserver-textdocument'
// Style check diagnostics that can be reported as warnings
const styleCheckDiagnostics = [
@ -29,7 +32,6 @@ export default class TypeScriptServiceClientHost implements Disposable {
private readonly ataProgressReporter: AtaProgressReporter
private readonly typingsStatus: TypingsStatus
private readonly client: TypeScriptServiceClient
private readonly languages: LanguageProvider[] = []
private readonly languagePerId = new Map<string, LanguageProvider>()
private readonly disposables: Disposable[] = []
private readonly fileConfigurationManager: FileConfigurationManager
@ -62,6 +64,9 @@ export default class TypeScriptServiceClientHost implements Disposable {
})
}, null, this.disposables)
// features
this.disposables.push(new WatchBuild(this.client))
this.disposables.push(languages.registerWorkspaceSymbolProvider(new WorkspaceSymbolProvider(this.client, allModeIds)))
this.client.onConfigDiagnosticsReceived(diag => {
let { body } = diag
if (body) {
@ -95,10 +100,36 @@ export default class TypeScriptServiceClientHost implements Disposable {
description,
this.typingsStatus
)
this.languages.push(manager)
this.disposables.push(manager)
this.languagePerId.set(description.id, manager)
}
const languageIds = new Set<string>()
for (const plugin of pluginManager.plugins) {
if (plugin.configNamespace && plugin.languages.length) {
this.registerExtensionLanguageProvider({
id: plugin.configNamespace,
modeIds: Array.from(plugin.languages),
diagnosticSource: 'ts-plugin',
diagnosticLanguage: DiagnosticLanguage.TypeScript,
diagnosticOwner: 'typescript',
isExternal: true
})
} else {
for (const language of plugin.languages) {
languageIds.add(language)
}
}
}
if (languageIds.size) {
this.registerExtensionLanguageProvider({
id: 'typescript-plugins',
modeIds: Array.from(languageIds.values()),
diagnosticSource: 'ts-plugin',
diagnosticLanguage: DiagnosticLanguage.TypeScript,
diagnosticOwner: 'typescript',
isExternal: true
})
}
this.client.ensureServiceStarted()
this.client.onTsServerStarted(() => {
@ -108,8 +139,17 @@ export default class TypeScriptServiceClientHost implements Disposable {
this.configurationChanged()
}
private registerExtensionLanguageProvider(description: LanguageDescription) {
const manager = new LanguageProvider(this.client, this.fileConfigurationManager, description, this.typingsStatus)
this.languagePerId.set(description.id, manager)
}
public dispose(): void {
disposeAll(this.disposables)
for (let language of this.languagePerId.values()) {
language.dispose()
}
this.languagePerId.clear()
this.fileConfigurationManager.dispose()
this.typingsStatus.dispose()
this.ataProgressReporter.dispose()
@ -124,6 +164,7 @@ export default class TypeScriptServiceClientHost implements Disposable {
}
public reloadProjects(): void {
this.client.diagnosticsManager.reInitialize()
this.client.execute('reloadProjects', null, CancellationToken.None)
this.triggerAllDiagnostics()
}
@ -142,18 +183,18 @@ export default class TypeScriptServiceClientHost implements Disposable {
try {
let doc = await workspace.loadFile(uri)
if (!doc) return undefined
return this.languages.find(language => language.handles(uri, doc.textDocument))
let languages = Array.from(this.languagePerId.values())
return languages.find(language => language.handles(uri, doc.textDocument))
} catch {
return undefined
}
}
public async handles(uri: string): Promise<boolean> {
const provider = await this.findLanguage(uri)
if (provider) {
return true
}
return this.client.bufferSyncSupport.handles(uri)
public async handles(doc: TextDocument): Promise<boolean> {
let languages = Array.from(this.languagePerId.values())
let idx = languages.findIndex(language => language.handles(doc.uri, doc))
if (idx != -1) return true
return this.client.bufferSyncSupport.handles(doc.uri)
}
private triggerAllDiagnostics(): void {
@ -235,7 +276,7 @@ export default class TypeScriptServiceClientHost implements Disposable {
return code ? styleCheckDiagnostics.indexOf(code) !== -1 : false
}
private getAllModeIds(descriptions: LanguageDescription[], pluginManager: PluginManager) {
private getAllModeIds(descriptions: LanguageDescription[], pluginManager: PluginManager): string[] {
const allModeIds = flatten([
...descriptions.map(x => x.modeIds),
...pluginManager.plugins.map(x => x.languages)

View file

@ -1,6 +1,7 @@
import { exec } from 'child_process'
import { workspace, Uri } from 'coc.nvim'
import fs from 'fs'
import path from 'path'
import { Uri, workspace } from 'coc.nvim'
export function runCommand(cmd: string, cwd: string, timeout?: number): Promise<string> {
return new Promise<string>((resolve, reject) => {
@ -72,9 +73,10 @@ export function distinct<T>(array: T[], keyFn?: (t: T) => string): T[] {
export async function installModules(uri: string, names: string[]): Promise<void> {
names = distinct(names)
let root = await getRoot()
if (!root) {
workspace.showMessage(`package.json not found from cwd: ${workspace.cwd}`, 'error')
let workspaceFolder = workspace.getWorkspaceFolder(uri)
let root = workspaceFolder ? Uri.parse(workspaceFolder.uri).fsPath : undefined
if (!root || !fs.existsSync(path.join(root, 'package.json'))) {
workspace.showMessage(`package.json not found from workspaceFolder: ${root}`, 'error')
return
}
let arr = names.concat(names.map(s => `@types/${s}`))
@ -93,11 +95,10 @@ export async function installModules(uri: string, names: string[]): Promise<void
let deps = exists.filter(s => devs.indexOf(s) == -1)
statusItem.text = `Installing ${exists.join(' ')}`
try {
await Promise.all([deps, devs].map((names, i) => {
let cmd = manager == 'npm' ? `npm i ${names.join(' ')}` : `yarn add ${names.join(' ')} --ignore-scripts --no-default-rc`
if (i == 1) cmd = cmd + ' --dev'
return runCommand(cmd, root)
}))
let cmd = manager == 'npm' ? `npm i ${deps.join(' ')}` : `yarn add ${deps.join(' ')}`
await runCommand(cmd, root)
cmd = manager == 'npm' ? `npm i ${deps.join(' ')} --save-dev` : `yarn add ${deps.join(' ')} --save-dev`
await runCommand(cmd, root)
} catch (e) {
statusItem.dispose()
workspace.showMessage(`Install error ${e.message}`, 'error')