add new features:

- Loading status
- Batched buffer synchronize
- Configuration for showUnused variable
- Smart selection support
- Support 'auto' as quoteStyle
- Support validateDefaultNpmLocation
This commit is contained in:
chemzqm 2019-06-10 02:24:30 +08:00
parent a3d81e2cb8
commit f1aa930569
23 changed files with 439 additions and 208 deletions

View file

@ -80,6 +80,8 @@ module will be used.
- `typescript.preferences.quoteStyle` default: `"single"`
- `typescript.suggestionActions.enabled`:Enable/disable suggestion diagnostics for TypeScript files in the editor. Requires using TypeScript 2.8 or newer in the workspace., default: `true`
- `typescript.validate.enable`:Enable/disable TypeScript validation., default: `true`
- `typescript.useBatchedBufferSync`: use batched buffer synchronize support.
- `typescript.showUnused`: show unused variable hint.
- `typescript.suggest.enabled` default: `true`
- `typescript.suggest.paths`:Enable/disable suggest paths in import statement and require calls, default: `true`
- `typescript.suggest.autoImports`:Enable/disable auto import suggests., default: `true`
@ -99,6 +101,7 @@ module will be used.
- `typescript.format.insertSpaceAfterTypeAssertion` default: `false`
- `typescript.format.placeOpenBraceOnNewLineForFunctions` default: `false`
- `typescript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false`
- `javascript.showUnused`: show unused variable hint.
- `javascript.updateImportsOnFileMove.enable` default: `true`
- `javascript.implementationsCodeLens.enable` default: `true`
- `javascript.referencesCodeLens.enable` default: `true`

View file

@ -187,6 +187,16 @@
"default": false,
"description": "Disable download of typings"
},
"typescript.useBatchedBufferSync": {
"type": "boolean",
"default": true,
"description": "Use batched buffer sync support."
},
"typescript.showUnused": {
"type": "boolean",
"default": true,
"description": "Show unused variable hint."
},
"typescript.updateImportsOnFileMove.enable": {
"type": "boolean",
"default": true,
@ -311,6 +321,11 @@
"type": "boolean",
"default": false
},
"javascript.showUnused": {
"type": "boolean",
"default": true,
"description": "Show unused variable hint."
},
"javascript.updateImportsOnFileMove.enable": {
"type": "boolean",
"default": true
@ -467,7 +482,7 @@
"semver": "^6.1.1",
"tslib": "^1.9.3",
"typescript": "3.5.1",
"vscode-languageserver-protocol": "^3.15.0-next.1",
"vscode-languageserver-protocol": "^3.15.0-next.5",
"which": "^1.3.1"
}
}

View file

@ -2,12 +2,13 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { disposeAll, workspace } from 'coc.nvim'
import { CancellationTokenSource, DidChangeTextDocumentParams, Disposable, TextDocument } from 'vscode-languageserver-protocol'
import { Uri, disposeAll, workspace } from 'coc.nvim'
import { CancellationTokenSource, Emitter, Event, DidChangeTextDocumentParams, Disposable, TextDocument, TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol'
import Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
import { Delayer } from '../utils/async'
import * as typeConverters from '../utils/typeConverters'
import * as languageModeIds from '../utils/languageModeIds'
function mode2ScriptKind(
@ -30,10 +31,117 @@ function mode2ScriptKind(
return undefined
}
/**
* Manages synchronization of buffers with the TS server.
*
* If supported, batches together file changes. This allows the TS server to more efficiently process changes.
*/
class BufferSynchronizer {
private _pending: Proto.UpdateOpenRequestArgs = {}
private _pendingFiles = new Set<string>()
constructor(
private readonly client: ITypeScriptServiceClient
) { }
public open(args: Proto.OpenRequestArgs): void {
if (this.supportsBatching) {
this.updatePending(args.file, pending => {
if (!pending.openFiles) {
pending.openFiles = []
}
pending.openFiles.push(args)
})
} else {
this.client.executeWithoutWaitingForResponse('open', args)
}
}
public close(filepath: string): void {
if (this.supportsBatching) {
this.updatePending(filepath, pending => {
if (!pending.closedFiles) {
pending.closedFiles = []
}
pending.closedFiles.push(filepath)
})
} else {
const args: Proto.FileRequestArgs = { file: filepath }
this.client.executeWithoutWaitingForResponse('close', args)
}
}
public change(filepath: string, events: TextDocumentContentChangeEvent[]): void {
if (!events.length) {
return
}
if (this.supportsBatching) {
this.updatePending(filepath, pending => {
if (!pending.changedFiles) {
pending.changedFiles = []
}
pending.changedFiles.push({
fileName: filepath,
textChanges: events.map((change): Proto.CodeEdit => ({
newText: change.text,
start: typeConverters.Position.toLocation(change.range.start),
end: typeConverters.Position.toLocation(change.range.end),
})).reverse(), // Send the edits end-of-document to start-of-document order
})
})
} else {
for (const { range, text } of events) {
const args: Proto.ChangeRequestArgs = {
insertString: text,
...typeConverters.Range.toFormattingRequestArgs(filepath, range)
}
this.client.executeWithoutWaitingForResponse('change', args)
}
}
}
public beforeCommand(command: string): void {
if (command === 'updateOpen') {
return
}
this.flush()
}
private flush(): void {
if (!this.supportsBatching) {
// We've already eagerly synchronized
return
}
if (this._pending.changedFiles || this._pending.closedFiles || this._pending.openFiles) {
this.client.executeWithoutWaitingForResponse('updateOpen', this._pending)
this._pending = {}
this._pendingFiles.clear()
}
}
private get supportsBatching(): boolean {
return this.client.apiVersion.gte(API.v340) && workspace.getConfiguration('typescript').get<boolean>('useBatchedBufferSync', true)
}
private updatePending(filepath: string, f: (pending: Proto.UpdateOpenRequestArgs) => void): void {
if (this.supportsBatching && this._pendingFiles.has(filepath)) {
this.flush()
this._pendingFiles.clear()
f(this._pending)
this._pendingFiles.add(filepath)
} else {
f(this._pending)
}
}
}
export default class BufferSyncSupport {
private readonly client: ITypeScriptServiceClient
private _validate: boolean
private readonly modeIds: Set<string>
private readonly uris: Set<string> = new Set()
private readonly disposables: Disposable[] = []
@ -41,19 +149,28 @@ export default class BufferSyncSupport {
private readonly pendingDiagnostics = new Map<string, number>()
private readonly diagnosticDelayer: Delayer<any>
private pendingGetErr: GetErrRequest | undefined
private readonly synchronizer: BufferSynchronizer
private _validateJavaScript = true
private _validateTypeScript = true
private listening = false
private readonly _onDelete = new Emitter<string>()
public readonly onDelete: Event<string> = this._onDelete.event
constructor(
client: ITypeScriptServiceClient,
modeIds: string[],
validate: boolean
) {
this.client = client
this.modeIds = new Set<string>(modeIds)
this._validate = validate || false
this.synchronizer = new BufferSynchronizer(client)
this.modeIds = new Set<string>(languageModeIds.languageIds)
this.diagnosticDelayer = new Delayer<any>(300)
}
public listen(): void {
if (this.listening) {
return
}
this.listening = true
workspace.onDidOpenTextDocument(
this.onDidOpenTextDocument,
this,
@ -70,14 +187,8 @@ export default class BufferSyncSupport {
this.disposables
)
workspace.textDocuments.forEach(this.onDidOpenTextDocument, this)
}
public reInitialize(): void {
workspace.textDocuments.forEach(this.onDidOpenTextDocument, this)
}
public set validate(value: boolean) {
this._validate = value
this.updateConfiguration()
workspace.onDidChangeConfiguration(this.updateConfiguration, this, this.disposables)
}
public dispose(): void {
@ -105,8 +216,8 @@ export default class BufferSyncSupport {
let root = this.client.getProjectRootPath(document.uri)
if (root) args.projectRootPath = root
}
this.client.executeWithoutWaitingForResponse('open', args) // tslint:disable-line
this.synchronizer.open(args)
// this.client.executeWithoutWaitingForResponse('open', args)
this.requestDiagnostic(uri)
}
@ -114,10 +225,12 @@ export default class BufferSyncSupport {
let { uri } = document
if (!this.uris.has(uri)) return
let filepath = this.client.toPath(uri)
const args: Proto.FileRequestArgs = {
file: filepath
}
this.client.executeWithoutWaitingForResponse('close', args) // tslint:disable-line
this.uris.delete(uri)
this.pendingDiagnostics.delete(uri)
this.synchronizer.close(filepath)
this._onDelete.fire(uri)
this.requestAllDiagnostics()
// this.client.executeWithoutWaitingForResponse('close', args)
}
private onDidChangeTextDocument(e: DidChangeTextDocumentParams): void {
@ -125,17 +238,7 @@ export default class BufferSyncSupport {
let { uri } = textDocument
if (!this.uris.has(uri)) return
let filepath = this.client.toPath(uri)
for (const { range, text } of contentChanges) {
const args: Proto.ChangeRequestArgs = {
file: filepath,
line: range ? range.start.line + 1 : 1,
offset: range ? range.start.character + 1 : 1,
endLine: range ? range.end.line + 1 : 2 ** 24,
endOffset: range ? range.end.character + 1 : 1,
insertString: text
}
this.client.executeWithoutWaitingForResponse('change', args) // tslint:disable-line
}
this.synchronizer.change(filepath, contentChanges)
const didTrigger = this.requestDiagnostic(uri)
if (!didTrigger && this.pendingGetErr) {
// In this case we always want to re-trigger all diagnostics
@ -145,6 +248,10 @@ export default class BufferSyncSupport {
}
}
public beforeCommand(command: string): void {
this.synchronizer.beforeCommand(command)
}
public interuptGetErr<R>(f: () => R): R {
if (!this.pendingGetErr) {
return f()
@ -157,6 +264,19 @@ export default class BufferSyncSupport {
return result
}
public getErr(resources: Uri[]): any {
const handledResources = resources.filter(resource => this.uris.has(resource.toString()))
if (!handledResources.length) {
return
}
for (const resource of handledResources) {
this.pendingDiagnostics.set(resource.toString(), Date.now())
}
this.triggerDiagnostics()
}
private triggerDiagnostics(delay = 200): void {
this.diagnosticDelayer.trigger(() => {
this.sendPendingDiagnostics()
@ -164,11 +284,11 @@ export default class BufferSyncSupport {
}
public requestAllDiagnostics(): void {
if (!this._validate) {
return
}
for (const uri of this.uris) {
this.pendingDiagnostics.set(uri, Date.now())
let doc = workspace.getDocument(uri)
if (doc && this.shouldValidate(doc.filetype)) {
this.pendingDiagnostics.set(uri, Date.now())
}
}
this.diagnosticDelayer.trigger(() => { // tslint:disable-line
this.sendPendingDiagnostics()
@ -176,11 +296,8 @@ export default class BufferSyncSupport {
}
public requestDiagnostic(uri: string): boolean {
if (!this._validate) {
return false
}
let document = workspace.getDocument(uri)
if (!document) return false
if (!document || !this.shouldValidate(document.filetype)) return false
this.pendingDiagnostics.set(uri, Date.now())
const lineCount = document.lineCount
const delay = Math.min(Math.max(Math.ceil(lineCount / 20), 300), 800)
@ -193,9 +310,6 @@ export default class BufferSyncSupport {
}
private sendPendingDiagnostics(): void {
if (!this._validate) {
return
}
const uris = Array.from(this.pendingDiagnostics.entries())
.sort((a, b) => a[1] - b[1])
.map(entry => entry[0])
@ -217,6 +331,23 @@ export default class BufferSyncSupport {
}
this.pendingDiagnostics.clear()
}
private updateConfiguration(): void {
const jsConfig = workspace.getConfiguration('javascript', null)
const tsConfig = workspace.getConfiguration('typescript', null)
this._validateJavaScript = jsConfig.get<boolean>('validate.enable', true)
this._validateTypeScript = tsConfig.get<boolean>('validate.enable', true)
}
private shouldValidate(filetype: string): boolean {
if (languageModeIds.languageIds.indexOf(filetype) == -1) {
return false
}
if (filetype.startsWith('javascript')) {
return this._validateJavaScript
}
return this._validateTypeScript
}
}
class GetErrRequest {

View file

@ -16,7 +16,6 @@ import * as typeConverters from '../utils/typeConverters'
import TypingsStatus from '../utils/typingsStatus'
import FileConfigurationManager, { SuggestOptions } from './fileConfigurationManager'
import SnippetString from '../utils/SnippetString'
import BufferSyncSupport from './bufferSyncSupport'
// command center
export interface CommandItem {
@ -59,7 +58,6 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
private readonly client: ITypeScriptServiceClient,
private readonly typingsStatus: TypingsStatus,
private readonly fileConfigurationManager: FileConfigurationManager,
private readonly bufferSyncSupport: BufferSyncSupport,
languageId: string
) {
@ -128,7 +126,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
let isNewIdentifierLocation = true
if (this.client.apiVersion.gte(API.v300)) {
try {
const response = await this.bufferSyncSupport.interuptGetErr(() => this.client.execute('completionInfo', args, token))
const response = await this.client.interruptGetErr(() => this.client.execute('completionInfo', args, token))
if (response.type !== 'response' || !response.body) {
return null
}
@ -141,7 +139,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
throw e
}
} else {
const response = await this.bufferSyncSupport.interuptGetErr(() => this.client.execute('completions', args, token))
const response = await this.client.interruptGetErr(() => this.client.execute('completions', args, token))
if (response.type !== 'response' || !response.body) {
return null
}
@ -218,11 +216,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
let response: ServerResponse.Response<Proto.CompletionDetailsResponse>
try {
response = await this.client.execute(
'completionEntryDetails',
args,
token
)
response = await this.client.interruptGetErr(() => this.client.execute('completionEntryDetails', args, token))
} catch {
return item
}

View file

@ -93,9 +93,7 @@ export class DiagnosticsManager {
diagnostics: Diagnostic[]
): void {
const collection = this._diagnostics.get(kind)
if (!collection) {
return
}
if (!collection) return
if (diagnostics.length === 0) {
const existing = collection.get(uri)

View file

@ -144,10 +144,11 @@ export default class FileConfigurationManager {
return {}
}
const config = workspace.getConfiguration(`${language}`)
const defaultQuote = this.client.apiVersion.gte(API.v333) ? 'auto' : undefined
return {
disableSuggestions: !config.get<boolean>('suggest.enabled', true),
importModuleSpecifierPreference: getImportModuleSpecifier(config) as any,
quotePreference: config.get<'single' | 'double'>('preferences.quoteStyle', 'single'),
quotePreference: config.get<'single' | 'double' | 'auto'>('preferences.quoteStyle', defaultQuote),
allowRenameOfImportPath: true,
allowTextChangesInNewFiles: true,
}

View file

@ -26,7 +26,7 @@ export default class TypeScriptHoverProvider implements HoverProvider {
position
)
try {
const response = await this.client.execute('quickinfo', args, token)
const response = await this.client.interruptGetErr(() => this.client.execute('quickinfo', args, token))
if (response && response.type == 'response' && response.body) {
const data = response.body
return {

View file

@ -142,8 +142,6 @@ export default class TypeScriptQuickFixProvider implements CodeActionProvider {
constructor(
private readonly client: ITypeScriptServiceClient,
private readonly diagnosticsManager: DiagnosticsManager,
private readonly bufferSyncSupport: BufferSyncSupport,
) {
commands.register(
new ApplyCodeActionCommand(client)
@ -177,7 +175,7 @@ export default class TypeScriptQuickFixProvider implements CodeActionProvider {
return []
}
if (this.bufferSyncSupport.hasPendingDiagnostics(document.uri)) {
if (this.client.bufferSyncSupport.hasPendingDiagnostics(document.uri)) {
return []
}
@ -273,7 +271,7 @@ export default class TypeScriptQuickFixProvider implements CodeActionProvider {
}
// Make sure there are multiple diagnostics of the same type in the file
if (!this.diagnosticsManager
if (!this.client.diagnosticsManager
.getDiagnostics(document.uri)
.some(x => x.code === diagnostic.code && x !== diagnostic)) {
return

View file

@ -127,7 +127,9 @@ export default class TypeScriptRefactorProvider implements CodeActionProvider {
)
let response
try {
response = await this.client.execute('getApplicableRefactors', args, token)
response = await this.client.interruptGetErr(() => {
return this.client.execute('getApplicableRefactors', args, token)
})
if (!response || !response.body) {
return undefined
}

View file

@ -81,7 +81,9 @@ export default class TypeScriptRenameProvider implements RenameProvider {
findInComments: false
}
return this.client.execute('rename', args, token)
return this.client.interruptGetErr(() => {
return this.client.execute('rename', args, token)
})
}
private toWorkspaceEdit(

View file

@ -28,14 +28,11 @@ export default class TypeScriptSignatureHelpProvider implements SignatureHelpPro
position
)
let info: Proto.SignatureHelpItems | undefined
try {
const response = await this.client.execute('signatureHelp', args, token)
info = (response as any).body
if (!info) return undefined
} catch {
const response = await this.client.interruptGetErr(() => this.client.execute('signatureHelp', args, token))
if (response.type !== 'response' || !response.body) {
return undefined
}
let info = response.body
const result: SignatureHelp = {
activeSignature: info.selectedItemIndex,

View file

@ -0,0 +1,47 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
import { TextDocument, Position, CancellationToken } from 'vscode-languageserver-protocol'
import { SelectionRange } from 'vscode-languageserver-protocol/lib/protocol.selectionRange.proposed'
import * as typeConverters from '../utils/typeConverters'
import { SelectionRangeProvider } from 'coc.nvim'
export default class SmartSelection implements SelectionRangeProvider {
public constructor(
private readonly client: ITypeScriptServiceClient
) { }
public async provideSelectionRanges(
document: TextDocument,
positions: Position[],
token: CancellationToken,
): Promise<SelectionRange[] | undefined> {
const file = this.client.toPath(document.uri)
if (!file) {
return undefined
}
const args: Proto.SelectionRangeRequestArgs = {
file,
locations: positions.map(typeConverters.Position.toLocation)
}
const response = await this.client.execute('selectionRange', args, token)
if (response.type !== 'response' || !response.body) {
return undefined
}
return response.body.map(SmartSelection.convertSelectionRange)
}
private static convertSelectionRange(
selectionRange: Proto.SelectionRange
): SelectionRange {
return SelectionRange.create(
typeConverters.Range.fromTextSpan(selectionRange.textSpan),
selectionRange.parent ? SmartSelection.convertSelectionRange(selectionRange.parent) : undefined,
)
}
}

View file

@ -74,11 +74,13 @@ export default class UpdateImportsOnFileRenameHandler {
private async getEditsForFileRename(document: TextDocument, oldFile: string, newFile: string): Promise<WorkspaceEdit> {
await this.fileConfigurationManager.ensureConfigurationForDocument(document)
const args: Proto.GetEditsForFileRenameRequestArgs = {
oldFilePath: oldFile,
newFilePath: newFile
}
const response = await this.client.execute('getEditsForFileRename', args, CancellationToken.None)
const response = await this.client.interruptGetErr(() => {
const args: Proto.GetEditsForFileRenameRequestArgs = {
oldFilePath: oldFile,
newFilePath: newFile,
}
return this.client.execute('getEditsForFileRename', args, CancellationToken.None)
})
if (!response || response.type != 'response' || !response.body) {
return
}

View file

@ -2,13 +2,11 @@
* 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 } from 'vscode-languageserver-protocol'
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 BufferSyncSupport from './features/bufferSyncSupport'
import CompletionItemProvider from './features/completionItemProvider'
import DefinitionProvider from './features/definitionProvider'
import { DiagnosticsManager } from './features/diagnostics'
import DirectiveCommentCompletionProvider from './features/directiveCommentCompletions'
import DocumentHighlight from './features/documentHighlight'
import DocumentSymbolProvider from './features/documentSymbol'
@ -28,6 +26,7 @@ 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'
@ -35,15 +34,8 @@ import { LanguageDescription } from './utils/languageDescription'
import TypingsStatus from './utils/typingsStatus'
import { OrganizeImportsCodeActionProvider } from './organizeImports'
const validateSetting = 'validate.enable'
const suggestionSetting = 'suggestionActions.enabled'
export default class LanguageProvider {
private readonly diagnosticsManager: DiagnosticsManager
private readonly bufferSyncSupport: BufferSyncSupport
public readonly fileConfigurationManager: FileConfigurationManager // tslint:disable-line
private _validate = true
private _enableSuggestionDiagnostics = true
private readonly disposables: Disposable[] = []
constructor(
@ -52,54 +44,33 @@ export default class LanguageProvider {
typingsStatus: TypingsStatus
) {
this.fileConfigurationManager = new FileConfigurationManager(client)
this.bufferSyncSupport = new BufferSyncSupport(
client,
description.modeIds,
this._validate
)
this.diagnosticsManager = new DiagnosticsManager()
this.disposables.push(this.diagnosticsManager)
client.onTsServerStarted(async () => {
let document = await workspace.document
if (description.modeIds.indexOf(document.filetype) !== -1) {
this.fileConfigurationManager.ensureConfigurationForDocument(document.textDocument) // tslint:disable-line
}
})
events.on('BufEnter', bufnr => {
let doc = workspace.getDocument(bufnr)
if (!doc) return
if (!doc || client.state !== ServiceStat.Running) return
if (description.modeIds.indexOf(doc.filetype) == -1) return
if (client.state !== ServiceStat.Running) return
this.fileConfigurationManager.ensureConfigurationForDocument(doc.textDocument) // tslint:disable-line
}, this, this.disposables)
this.configurationChanged()
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
let initialized = false
client.onTsServerStarted(() => { // tslint:disable-line
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)
this.bufferSyncSupport.listen()
} else {
this.reInitialize()
this.client.diagnosticsManager.reInitialize()
}
})
}
public dispose(): void {
disposeAll(this.disposables)
this.bufferSyncSupport.dispose()
}
private configurationChanged(): void {
const config = workspace.getConfiguration(this.id)
this.updateValidate(config.get(validateSetting, true))
this.updateSuggestionDiagnostics(config.get(suggestionSetting, true))
}
private registerProviders(
@ -117,7 +88,6 @@ export default class LanguageProvider {
client,
typingsStatus,
this.fileConfigurationManager,
this.bufferSyncSupport,
this.description.id
),
CompletionItemProvider.triggerCharacters
@ -256,14 +226,14 @@ export default class LanguageProvider {
this.disposables.push(
languages.registerCodeActionProvider(
languageIds,
new QuickfixProvider(client, this.diagnosticsManager, this.bufferSyncSupport),
new QuickfixProvider(client),
'tsserver',
[CodeActionKind.QuickFix]))
this.disposables.push(
languages.registerCodeActionProvider(
languageIds,
new ImportfixProvider(this.bufferSyncSupport),
new ImportfixProvider(this.client.bufferSyncSupport),
'tsserver',
[CodeActionKind.QuickFix]))
let cachedResponse = new CachedNavTreeResponse()
@ -282,6 +252,11 @@ export default class LanguageProvider {
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(
@ -329,51 +304,31 @@ export default class LanguageProvider {
return this.description.diagnosticSource
}
private updateValidate(value: boolean): void {
if (this._validate === value) {
return
}
this._validate = value
this.bufferSyncSupport.validate = value
this.diagnosticsManager.validate = value
if (value) {
this.triggerAllDiagnostics()
}
}
private updateSuggestionDiagnostics(value: boolean): void {
if (this._enableSuggestionDiagnostics === value) {
return
}
this._enableSuggestionDiagnostics = value
this.diagnosticsManager.enableSuggestions = value
if (value) {
this.triggerAllDiagnostics()
}
}
public reInitialize(): void {
this.diagnosticsManager.reInitialize()
this.bufferSyncSupport.reInitialize()
}
public triggerAllDiagnostics(): void {
this.bufferSyncSupport.requestAllDiagnostics()
this.client.bufferSyncSupport.requestAllDiagnostics()
}
public diagnosticsReceived(
diagnosticsKind: DiagnosticKind,
file: Uri,
diagnostics: Diagnostic[]
diagnostics: (Diagnostic & { reportUnnecessary: any })[]
): void {
this.diagnosticsManager.diagnosticsReceived(
this.client.diagnosticsManager.diagnosticsReceived(
diagnosticsKind,
file.toString(),
diagnostics
)
}
public configFileDiagnosticsReceived(uri: Uri, diagnostics: Diagnostic[]): void {
this.diagnosticsManager.configFileDiagnosticsReceived(uri.toString(), diagnostics)
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
}))
}
}

View file

@ -31,7 +31,7 @@ export class OrganizeImportsCommand implements Command {
}
}
}
const response = await client.execute('organizeImports', args, CancellationToken.None)
const response = await this.client.interruptGetErr(() => this.client.execute('organizeImports', args, CancellationToken.None))
if (!response || response.type != 'response' || !response.success) {
return
}

View file

@ -8,6 +8,8 @@ import * as Proto from './protocol'
import API from './utils/api'
import { TypeScriptServiceConfiguration } from './utils/configuration'
import Logger from './utils/logger'
import BufferSyncSupport from './features/bufferSyncSupport'
import { DiagnosticsManager } from './features/diagnostics'
export namespace ServerResponse {
@ -74,6 +76,8 @@ export interface ITypeScriptServiceClient {
onDidEndInstallTypings: Event<Proto.EndInstallTypesEventBody>
onTypesInstallerInitializationFailed: Event<Proto.TypesInstallerInitializationFailedEventBody>
readonly logger: Logger
readonly bufferSyncSupport: BufferSyncSupport
readonly diagnosticsManager: DiagnosticsManager
getProjectRootPath(uri: string): string | null
normalizePath(resource: Uri): string | null
@ -91,10 +95,15 @@ export interface ITypeScriptServiceClient {
executeWithoutWaitingForResponse(command: 'open', args: Proto.OpenRequestArgs): void
executeWithoutWaitingForResponse(command: 'close', args: Proto.FileRequestArgs): void
executeWithoutWaitingForResponse(command: 'change', args: Proto.ChangeRequestArgs): void
// executeWithoutWaitingForResponse(command: 'updateOpen', args: Proto.UpdateOpenRequestArgs): void
executeWithoutWaitingForResponse(command: 'updateOpen', args: Proto.UpdateOpenRequestArgs): void
executeWithoutWaitingForResponse(command: 'compilerOptionsForInferredProjects', args: Proto.SetCompilerOptionsForInferredProjectsArgs): void
executeWithoutWaitingForResponse(command: 'reloadProjects', args: null): void
executeWithoutWaitingForResponse(command: 'configurePlugin', args: Proto.ConfigurePluginRequestArguments): void
executeAsync(command: 'geterr', args: Proto.GeterrRequestArgs, token: CancellationToken): Promise<ServerResponse.Response<Proto.Response>>
/**
* Cancel on going geterr requests and re-queue them after `f` has been evaluated.
*/
interruptGetErr<R>(f: () => R): R
}

View file

@ -8,7 +8,7 @@ import os from 'os'
import path from 'path'
import { CancellationToken, Disposable, Emitter, Event } from 'vscode-languageserver-protocol'
import which from 'which'
import { Uri, DiagnosticKind, ServiceStat, workspace, disposeAll } from 'coc.nvim'
import { Uri, ServiceStat, workspace, disposeAll } from 'coc.nvim'
import FileConfigurationManager from './features/fileConfigurationManager'
import * as Proto from './protocol'
import { ITypeScriptServiceClient, ServerResponse } from './typescriptService'
@ -25,6 +25,8 @@ import { PluginManager } from '../utils/plugins'
import { ICallback, Reader } from './utils/wireProtocol'
import { CallbackMap } from './callbackMap'
import { RequestItem, RequestQueue, RequestQueueingType } from './requestQueue'
import BufferSyncSupport from './features/bufferSyncSupport'
import { DiagnosticKind, DiagnosticsManager } from './features/diagnostics'
class ForkedTsServerProcess {
constructor(private childProcess: cp.ChildProcess) { }
@ -66,6 +68,9 @@ export interface TsDiagnostics {
export default class TypeScriptServiceClient implements ITypeScriptServiceClient {
public state = ServiceStat.Initial
public readonly logger: Logger = new Logger()
public readonly bufferSyncSupport: BufferSyncSupport
public readonly diagnosticsManager: DiagnosticsManager
private fileConfigurationManager: FileConfigurationManager
private pathSeparator: string
private tracer: Tracer
@ -111,6 +116,16 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
pluginManager.onDidChangePlugins(() => {
this.restartTsServer()
}, null, this.disposables)
this.bufferSyncSupport = new BufferSyncSupport(this)
this.onTsServerStarted(() => {
this.bufferSyncSupport.listen()
})
this.diagnosticsManager = new DiagnosticsManager()
this.bufferSyncSupport.onDelete(resource => {
this.diagnosticsManager.delete(resource)
}, null, this.disposables)
}
private _onDiagnosticsReceived = new Emitter<TsDiagnostics>()
@ -140,7 +155,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
})
.then(undefined, () => void 0)
}
this.bufferSyncSupport.dispose()
disposeAll(this.disposables)
this.logger.dispose()
this._onTsServerStarted.dispose()
@ -516,6 +531,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
if (this.servicePromise == null) {
return Promise.resolve(undefined)
}
this.bufferSyncSupport.beforeCommand(command)
const request = this._requestQueue.createRequest(command, args)
const requestInfo: RequestItem = {
@ -700,7 +716,11 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
)
}
break
case 'projectsUpdatedInBackground':
const body = (event as Proto.ProjectsUpdatedInBackgroundEvent).body
const resources = body.openFiles.map(Uri.file)
this.bufferSyncSupport.getErr(resources)
break
case 'typesInstallerInitializationFailed':
if (event.body) {
this._onTypesInstallerInitializationFailed.fire(
@ -708,6 +728,13 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
)
}
break
case 'projectLoadingStart':
this.versionStatus.loading = true
break
case 'projectLoadingFinish':
this.versionStatus.loading = false
break
}
}
@ -786,6 +813,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
if (this.apiVersion.gte(API.v291)) {
args.push('--noGetErrOnBackgroundUpdate')
}
if (this.apiVersion.gte(API.v345)) {
args.push('--validateDefaultNpmLocation')
}
return args
}
@ -816,6 +847,10 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient
})
}
}
public interruptGetErr<R>(f: () => R): R {
return this.bufferSyncSupport.interuptGetErr(f)
}
}
function getDiagnosticsKind(event: Proto.Event): DiagnosticKind {

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Uri, DiagnosticKind, disposeAll, workspace } from 'coc.nvim'
import { Range, Diagnostic, DiagnosticSeverity, Disposable, Position, CancellationToken } from 'vscode-languageserver-protocol'
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'
@ -64,13 +64,13 @@ export default class TypeScriptServiceClientHost implements Disposable {
let language = this.findLanguage(uri)
if (!language) return
if (diagnostics.length == 0) {
language.configFileDiagnosticsReceived(uri, [])
this.client.diagnosticsManager.configFileDiagnosticsReceived(uri.toString(), [])
} else {
let range = Range.create(Position.create(0, 0), Position.create(0, 1))
let { text, code, category } = diagnostics[0]
let severity = category == 'error' ? DiagnosticSeverity.Error : DiagnosticSeverity.Warning
let diagnostic = Diagnostic.create(range, text, severity, code)
language.configFileDiagnosticsReceived(uri, [diagnostic])
this.client.diagnosticsManager.configFileDiagnosticsReceived(uri.toString(), [diagnostic])
}
}
}, null, this.disposables)
@ -158,22 +158,34 @@ export default class TypeScriptServiceClientHost implements Disposable {
}
}
private createMarkerDatas(diagnostics: Proto.Diagnostic[]): Diagnostic[] {
private createMarkerDatas(diagnostics: Proto.Diagnostic[]): (Diagnostic & { reportUnnecessary: any })[] {
return diagnostics.map(tsDiag => this.tsDiagnosticToLspDiagnostic(tsDiag))
}
private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): Diagnostic {
private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): (Diagnostic & { reportUnnecessary: any }) {
const { start, end, text } = diagnostic
const range = {
start: typeConverters.Position.fromLocation(start),
end: typeConverters.Position.fromLocation(end)
}
let relatedInformation: DiagnosticRelatedInformation[]
if (diagnostic.relatedInformation) {
relatedInformation = diagnostic.relatedInformation.map(o => {
let { span, message } = o
return {
location: typeConverters.Location.fromTextSpan(this.client.toResource(span.file), span),
message
}
})
}
return {
range,
message: text,
code: diagnostic.code ? diagnostic.code : null,
severity: this.getDiagnosticSeverity(diagnostic),
reportUnnecessary: diagnostic.reportsUnnecessary,
source: diagnostic.source || 'tsserver',
relatedInformation
}
}

View file

@ -5,56 +5,61 @@
import * as semver from 'semver'
export default class API {
private static fromSimpleString(value: string): API {
return new API(value, value)
}
private static fromSimpleString(value: string): API {
return new API(value, value)
}
public static readonly defaultVersion = API.fromSimpleString('1.0.0')
public static readonly v203 = API.fromSimpleString('2.0.3')
public static readonly v206 = API.fromSimpleString('2.0.6')
public static readonly v208 = API.fromSimpleString('2.0.8')
public static readonly v213 = API.fromSimpleString('2.1.3')
public static readonly v220 = API.fromSimpleString('2.2.0')
public static readonly v222 = API.fromSimpleString('2.2.2')
public static readonly v230 = API.fromSimpleString('2.3.0')
public static readonly v234 = API.fromSimpleString('2.3.4')
public static readonly v240 = API.fromSimpleString('2.4.0')
public static readonly v250 = API.fromSimpleString('2.5.0')
public static readonly v260 = API.fromSimpleString('2.6.0')
public static readonly v270 = API.fromSimpleString('2.7.0')
public static readonly v280 = API.fromSimpleString('2.8.0')
public static readonly v290 = API.fromSimpleString('2.9.0')
public static readonly v291 = API.fromSimpleString('2.9.1')
public static readonly v292 = API.fromSimpleString('2.9.2')
public static readonly v300 = API.fromSimpleString('3.0.0')
public static readonly v310 = API.fromSimpleString('3.1.0')
public static readonly v314 = API.fromSimpleString('3.1.4')
public static readonly v320 = API.fromSimpleString('3.2.0')
public static readonly defaultVersion = API.fromSimpleString('1.0.0')
public static readonly v203 = API.fromSimpleString('2.0.3')
public static readonly v206 = API.fromSimpleString('2.0.6')
public static readonly v208 = API.fromSimpleString('2.0.8')
public static readonly v213 = API.fromSimpleString('2.1.3')
public static readonly v220 = API.fromSimpleString('2.2.0')
public static readonly v222 = API.fromSimpleString('2.2.2')
public static readonly v230 = API.fromSimpleString('2.3.0')
public static readonly v234 = API.fromSimpleString('2.3.4')
public static readonly v240 = API.fromSimpleString('2.4.0')
public static readonly v250 = API.fromSimpleString('2.5.0')
public static readonly v260 = API.fromSimpleString('2.6.0')
public static readonly v270 = API.fromSimpleString('2.7.0')
public static readonly v280 = API.fromSimpleString('2.8.0')
public static readonly v290 = API.fromSimpleString('2.9.0')
public static readonly v291 = API.fromSimpleString('2.9.1')
public static readonly v292 = API.fromSimpleString('2.9.2')
public static readonly v300 = API.fromSimpleString('3.0.0')
public static readonly v310 = API.fromSimpleString('3.1.0')
public static readonly v314 = API.fromSimpleString('3.1.4')
public static readonly v320 = API.fromSimpleString('3.2.0')
public static readonly v330 = API.fromSimpleString('3.3.0')
public static readonly v333 = API.fromSimpleString('3.3.3')
public static readonly v340 = API.fromSimpleString('3.4.0')
public static readonly v345 = API.fromSimpleString('3.4.5')
public static readonly v350 = API.fromSimpleString('3.5.0')
public static fromVersionString(versionString: string): API {
let version = semver.valid(versionString)
if (!version) {
return new API('invalid version', '1.0.0')
}
public static fromVersionString(versionString: string): API {
let version = semver.valid(versionString)
if (!version) {
return new API('invalid version', '1.0.0')
}
// Cut off any prerelease tag since we sometimes consume those on purpose.
const index = versionString.indexOf('-')
if (index >= 0) {
version = version.substr(0, index)
}
return new API(versionString, version)
}
// Cut off any prerelease tag since we sometimes consume those on purpose.
const index = versionString.indexOf('-')
if (index >= 0) {
version = version.substr(0, index)
}
return new API(versionString, version)
}
private constructor(
public readonly versionString: string,
private readonly version: string
) { }
private constructor(
public readonly versionString: string,
private readonly version: string
) { }
public gte(other: API): boolean {
return semver.gte(this.version, other.version)
}
public gte(other: API): boolean {
return semver.gte(this.version, other.version)
}
public lt(other: API): boolean {
return !this.gte(other)
}
public lt(other: API): boolean {
return !this.gte(other)
}
}

View file

@ -7,12 +7,18 @@ import * as languageModeIds from './languageModeIds'
export interface LanguageDescription {
readonly id: string
readonly diagnosticSource: string
readonly diagnosticLanguage: DiagnosticLanguage
readonly modeIds: string[]
readonly configFile?: string
readonly isExternal?: boolean
readonly diagnosticOwner: string
}
export const enum DiagnosticLanguage {
JavaScript,
TypeScript
}
export const standardLanguageDescriptions: LanguageDescription[] = [
{
id: 'typescript',
@ -20,6 +26,7 @@ export const standardLanguageDescriptions: LanguageDescription[] = [
diagnosticOwner: 'typescript',
modeIds: [languageModeIds.typescript, languageModeIds.typescriptreact,
languageModeIds.typescripttsx, languageModeIds.typescriptjsx],
diagnosticLanguage: DiagnosticLanguage.TypeScript,
configFile: 'tsconfig.json'
},
{
@ -27,6 +34,7 @@ export const standardLanguageDescriptions: LanguageDescription[] = [
diagnosticSource: 'ts',
diagnosticOwner: 'typescript',
modeIds: [languageModeIds.javascript, languageModeIds.javascriptreact],
diagnosticLanguage: DiagnosticLanguage.JavaScript,
configFile: 'jsconfig.json'
}
]

View file

@ -23,6 +23,14 @@ export namespace Range {
}
}
export const toFormattingRequestArgs = (file: string, range: language.Range): Proto.FormatRequestArgs => ({
file,
line: range.start.line + 1,
offset: range.start.character + 1,
endLine: range.end.line + 1,
endOffset: range.end.character + 1
})
export const toFileRangeRequestArgs = (
file: string,
range: language.Range
@ -43,6 +51,11 @@ export namespace Position {
}
}
export const toLocation = (position: language.Position): Proto.Location => ({
line: position.line + 1,
offset: position.character + 1,
})
export const toFileLocationRequestArgs = (
file: string,
position: language.Position

View file

@ -27,6 +27,10 @@ export default class VersionStatus {
})
}
public set loading(isLoading: boolean) {
this._versionBarEntry.isProgress = isLoading
}
private async showHideStatus(): Promise<void> {
let document = await workspace.document
if (!document) {

View file

@ -553,7 +553,7 @@ vscode-jsonrpc@^4.1.0-next.2:
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.1.0-next.2.tgz#3bd318910a48e631742b290975386e3dae685be3"
integrity sha512-GsBLjP9DxQ42yl1mW9GEIlnSc0+R8mfzhaebwmmTPEJjezD5SPoAo3DFrIAFZha9yvQ1nzZfZlhtVpGQmgxtXg==
vscode-languageserver-protocol@^3.15.0-next.1, vscode-languageserver-protocol@^3.15.0-next.5:
vscode-languageserver-protocol@^3.15.0-next.5:
version "3.15.0-next.5"
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.15.0-next.5.tgz#23afad3d28795f2235eda7a167e2fe0825b7c151"
integrity sha512-rR7Zo5WZTGSsE9lq7pPSgO+VMhVV8UVq6emrDoQ3x5dUyhLKB2/gbMkGKucQpsKGLtF/NuccCa+3jMsO788HjQ==