From 0f3e462afd2bb700a25f77b3daec78648f6ba248 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Mon, 22 Feb 2021 18:42:35 +0800 Subject: [PATCH 1/5] fix untitled buffer not work Closes #261 --- src/server/typescriptServiceClient.ts | 16 ++++++++++++---- src/server/typescriptServiceClientHost.ts | 2 +- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/server/typescriptServiceClient.ts b/src/server/typescriptServiceClient.ts index 97eaf48..301d7f4 100644 --- a/src/server/typescriptServiceClient.ts +++ b/src/server/typescriptServiceClient.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import cp from 'child_process' -import { ServiceStat, Uri, window, workspace } from 'coc.nvim' +import { Document, ServiceStat, Uri, window, workspace } from 'coc.nvim' import fs from 'fs' import os from 'os' import path from 'path' @@ -491,7 +491,7 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient public toResource(filepath: string): string { if (this._apiVersion.gte(API.v213)) { - if (filepath.startsWith('untitled:')) { + if (filepath.startsWith(this.inMemoryResourcePrefix + 'untitled:')) { let resource = Uri.parse(filepath) if (this.inMemoryResourcePrefix) { const dirName = path.dirname(resource.path) @@ -524,14 +524,22 @@ export default class TypeScriptServiceClient implements ITypeScriptServiceClient } } + public getDocument(resource: string): Document | undefined { + if (resource.startsWith('untitled:')) { + let bufnr = parseInt(resource.split(':', 2)[1], 10) + return workspace.getDocument(bufnr) + } + return workspace.getDocument(resource) + } + private get inMemoryResourcePrefix(): string { return this._apiVersion.gte(API.v270) ? '^' : '' } public asUrl(filepath: string): Uri { if (this._apiVersion.gte(API.v213)) { - if (filepath.startsWith('untitled:')) { - let resource = Uri.parse(filepath) + if (filepath.startsWith(this.inMemoryResourcePrefix + 'untitled:')) { + let resource = Uri.parse(filepath.slice(this.inMemoryResourcePrefix.length)) if (this.inMemoryResourcePrefix) { const dirName = path.dirname(resource.path) const fileName = path.basename(resource.path) diff --git a/src/server/typescriptServiceClientHost.ts b/src/server/typescriptServiceClientHost.ts index 2f62757..5f1479a 100644 --- a/src/server/typescriptServiceClientHost.ts +++ b/src/server/typescriptServiceClientHost.ts @@ -181,7 +181,7 @@ export default class TypeScriptServiceClientHost implements Disposable { public async findLanguage(uri: string): Promise { try { - let doc = await workspace.loadFile(uri) + let doc = this.client.getDocument(uri) if (!doc) return undefined let languages = Array.from(this.languagePerId.values()) return languages.find(language => language.handles(uri, doc.textDocument)) From 17dc0c235c7be2f29211c1696edb2c82017e8e43 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Mon, 22 Feb 2021 18:42:51 +0800 Subject: [PATCH 2/5] Release 1.6.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 10f462c..b683dbb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.6.7", + "version": "1.6.8", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm", From f0a9f46dad4c9c21ce65fb41ad0d35662a5af3fd Mon Sep 17 00:00:00 2001 From: fabriziobertoglio1987 Date: Wed, 7 Apr 2021 12:28:07 +0200 Subject: [PATCH 3/5] README - Add info about typescript issue 37777 (#272) * adding info to readme * give basic info about typescript issue with node_modules * adding link * just include the link --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 77ce2b6..29ec4f2 100644 --- a/Readme.md +++ b/Readme.md @@ -73,7 +73,7 @@ Almost the same as VSCode. - `tsserver.organizeImports` - `tsserver.watchBuild` - Code completion support. -- Go to definition. +- Go to definition (more info in [microsoft/TypeScript#37777](https://github.com/microsoft/TypeScript/issues/37777)) - Code validation. - Document highlight. - Document symbols of current buffer. From 579e8920a7a059ebf3513fea2fa4f5f11e795b02 Mon Sep 17 00:00:00 2001 From: Raidou Date: Wed, 7 Apr 2021 18:31:04 +0800 Subject: [PATCH 4/5] feat: support tagClosing for JSX (#277) --- Readme.md | 2 + package.json | 8 ++ src/server/features/tagClosing.ts | 201 +++++++++++++++++++++++++++ src/server/features/tagCompletion.ts | 54 ------- src/server/languageProvider.ts | 20 +-- 5 files changed, 217 insertions(+), 68 deletions(-) create mode 100644 src/server/features/tagClosing.ts delete mode 100644 src/server/features/tagCompletion.ts diff --git a/Readme.md b/Readme.md index 29ec4f2..7e41cc0 100644 --- a/Readme.md +++ b/Readme.md @@ -141,6 +141,7 @@ for guide of coc.nvim's configuration. - `typescript.validate.enable`:Enable/disable TypeScript validation., default: `true` - `typescript.showUnused`: show unused variable hint, default: `true`. +- `typescript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false`. - `typescript.suggest.enabled` default: `true` - `typescript.suggest.paths`:Enable/disable suggest paths in import statement and require calls, default: `true` @@ -176,6 +177,7 @@ for guide of coc.nvim's configuration. - `typescript.suggest.includeAutomaticOptionalChainCompletions`: default: `true` - `javascript.format.enabled`: Enable/disable format for javascript files. - `javascript.showUnused`: show unused variable hint. +- `javascript.autoClosingTags`: Enable/disable autoClosing of JSX tags, default: `false`. - `javascript.updateImportsOnFileMove.enable` default: `true` - `javascript.implementationsCodeLens.enable` default: `true` - `javascript.referencesCodeLens.enable` default: `true` diff --git a/package.json b/package.json index b683dbb..b82f396 100644 --- a/package.json +++ b/package.json @@ -431,6 +431,10 @@ "default": "allOpenProjects", "scope": "window" }, + "typescript.autoClosingTags": { + "type": "boolean", + "default": false + }, "javascript.showUnused": { "type": "boolean", "default": true, @@ -593,6 +597,10 @@ "description": "%configuration.suggest.includeAutomaticOptionalChainCompletions%", "scope": "resource" }, + "javascript.autoClosingTags": { + "type": "boolean", + "default": false + }, "javascript.format.semicolons": { "type": "string", "default": "ignore", diff --git a/src/server/features/tagClosing.ts b/src/server/features/tagClosing.ts new file mode 100644 index 0000000..b55c127 --- /dev/null +++ b/src/server/features/tagClosing.ts @@ -0,0 +1,201 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { CancellationTokenSource, Disposable, disposeAll, Position, Range, snippetManager, window, workspace } from 'coc.nvim' +import { TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol' +import * as Proto from '../protocol' +import { ITypeScriptServiceClient } from '../typescriptService' +import API from '../utils/api' +import SnippetString from '../utils/SnippetString' +import * as typeConverters from '../utils/typeConverters' + +export default class TagClosing implements Disposable { + public static readonly minVersion = API.v300 + + private static _configurationLanguages: Record = { + 'javascriptreact': 'javascript', + 'typescriptreact': 'typescript', + } + + private _disposables: Disposable[] = [] + private _enabled: boolean = false + private _disposed = false + private _timeout: NodeJS.Timer | undefined = undefined + private _cancel: CancellationTokenSource | undefined = undefined + + constructor( + private readonly client: ITypeScriptServiceClient, + private readonly descriptionLanguageId: string + ) { + workspace.onDidChangeTextDocument( + (event) => + this.onDidChangeTextDocument( + event.textDocument, + event.contentChanges + ), + null, + this._disposables + ) + + this.updateEnabledState() + + workspace.registerAutocmd({ + event: ['BufEnter'], + request: false, + callback: () => this.updateEnabledState(), + }) + } + + async updateEnabledState(): Promise { + this._enabled = false + const doc = await workspace.document + if (!doc) { + return + } + const document = doc.textDocument + const configLang = TagClosing._configurationLanguages[document.languageId] + if (!configLang || configLang !== this.descriptionLanguageId) { + return + } + if (!workspace.getConfiguration(undefined, document.uri).get(`${configLang}.autoClosingTags`)) { + return + } + this._enabled = true + } + + public dispose() { + this._disposed = true + + if (this._timeout) { + clearTimeout(this._timeout) + this._timeout = undefined + } + + if (this._cancel) { + this._cancel.cancel() + this._cancel.dispose() + this._cancel = undefined + } + + disposeAll(this._disposables) + this._disposables = [] + } + + private async onDidChangeTextDocument( + documentEvent: { + uri: string, + version: number, + }, + changes: readonly TextDocumentContentChangeEvent[] + ) { + if (!this._enabled) { + return + } + const document = await workspace.document + if (!document) { + return + } + const activeDocument = document.textDocument + if (activeDocument.uri !== documentEvent.uri || changes.length === 0) { + return + } + const filepath = this.client.toOpenedFilePath(documentEvent.uri) + if (!filepath) { + return + } + + if (typeof this._timeout !== 'undefined') { + clearTimeout(this._timeout) + } + + if (this._cancel) { + this._cancel.cancel() + this._cancel.dispose() + this._cancel = undefined + } + + const lastChange = changes[changes.length - 1] + if (!Range.is(lastChange['range']) || !lastChange.text) { + return + } + + const lastCharacter = lastChange.text[lastChange.text.length - 1] + if (lastCharacter !== '>' && lastCharacter !== '/') { + return + } + + const version = documentEvent.version + + const rangeStart = lastChange['range'].start + const priorCharacter = + lastChange['range'].start.character > 0 + ? activeDocument.getText( + Range.create( + Position.create(rangeStart.line, rangeStart.character - 1), + rangeStart + ) + ) + : '' + if (priorCharacter === '>') { + return + } + + this._timeout = setTimeout(async () => { + this._timeout = undefined + + if (this._disposed) { + return + } + + const addedLines = lastChange.text.split(/\r\n|\n/g) + const position = + addedLines.length <= 1 + ? Position.create( + rangeStart.line, + rangeStart.character + lastChange.text.length + ) + : Position.create( + rangeStart.line + addedLines.length - 1, + addedLines[addedLines.length - 1].length + ) + + const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs( + filepath, + position + ) + this._cancel = new CancellationTokenSource() + const response = await this.client.execute( + 'jsxClosingTag', + args, + this._cancel.token + ) + if (response.type !== 'response' || !response.body) { + return + } + + if (this._disposed) { + return + } + + const insertion = response.body; + if ( + documentEvent.uri === activeDocument.uri && + activeDocument.version === version + ) { + snippetManager.insertSnippet( + this.getTagSnippet(insertion).value, + false, + Range.create(position, position) + ) + } + }, 100); + } + + private getTagSnippet(closingTag: Proto.TextInsertion): SnippetString { + const snippet = new SnippetString(); + snippet.appendPlaceholder('', 0); + snippet.appendText(closingTag.newText); + return snippet; + } +} diff --git a/src/server/features/tagCompletion.ts b/src/server/features/tagCompletion.ts deleted file mode 100644 index 174e1ef..0000000 --- a/src/server/features/tagCompletion.ts +++ /dev/null @@ -1,54 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ -import { TextDocument } from 'coc.nvim' -import { CompletionItemProvider } from 'coc.nvim' -import { CancellationToken, CompletionContext, CompletionItem, Position } from 'vscode-languageserver-protocol' -import * as Proto from '../protocol' -import { ITypeScriptServiceClient } from '../typescriptService' -import * as typeConverters from '../utils/typeConverters' - -export default class TypeScriptTagCompletion implements CompletionItemProvider { - constructor( - private readonly client: ITypeScriptServiceClient - ) {} - - public async provideCompletionItems( - document: TextDocument, - position: Position, - token: CancellationToken, - context: CompletionContext - ): Promise { - const filepath = this.client.toPath(document.uri) - if (!filepath) return undefined - if (context.triggerCharacter != '>') { - return undefined - } - - const args: Proto.JsxClosingTagRequestArgs = typeConverters.Position.toFileLocationRequestArgs(filepath, position) - let body: Proto.TextInsertion | undefined - try { - const response = await this.client.execute('jsxClosingTag', args, token) - body = response && (response as any).body - if (!body) { - return undefined - } - } catch { - return undefined - } - - return [this.getCompletion(body)] - } - - private getCompletion(body: Proto.TextInsertion): CompletionItem { - const completion = CompletionItem.create(body.newText) - completion.insertText = this.getTagSnippet(body) // tslint:disable-line - return completion - } - - private getTagSnippet(closingTag: Proto.TextInsertion): string { - let { newText, caretOffset } = closingTag - return newText.slice(0, caretOffset) + '$0' + newText.slice(caretOffset) - } -} diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 58a75b9..e867d91 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -2,9 +2,9 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { disposeAll, languages, Uri, workspace } from 'coc.nvim' +import { disposeAll, languages, TextDocument, Uri, workspace } from 'coc.nvim' import path from 'path' -import { CodeActionKind, Diagnostic, DiagnosticSeverity, Disposable, TextDocument } from 'vscode-languageserver-protocol' +import { CodeActionKind, Diagnostic, DiagnosticSeverity, Disposable } from 'vscode-languageserver-protocol' import { CachedNavTreeResponse } from './features/baseCodeLensProvider' import CompletionItemProvider from './features/completionItemProvider' import DefinitionProvider from './features/definitionProvider' @@ -19,7 +19,6 @@ import HoverProvider from './features/hover' import ImplementationsCodeLensProvider from './features/implementationsCodeLens' import ImportfixProvider from './features/importFix' import InstallModuleProvider from './features/moduleInstall' -// import TagCompletionProvider from './features/tagCompletion' import QuickfixProvider from './features/quickfix' import RefactorProvider from './features/refactor' import ReferenceProvider from './features/references' @@ -27,6 +26,7 @@ import ReferencesCodeLensProvider from './features/referencesCodeLens' import RenameProvider from './features/rename' import SignatureHelpProvider from './features/signatureHelp' import SmartSelection from './features/smartSelect' +import TagClosing from './features/tagClosing' import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename' import { OrganizeImportsCodeActionProvider } from './organizeImports' import TypeScriptServiceClient from './typescriptServiceClient' @@ -147,17 +147,9 @@ export default class LanguageProvider { if (this.client.apiVersion.gte(API.v350)) { this._register(languages.registerSelectionRangeProvider(languageIds, new SmartSelection(this.client))) } - // if (this.client.apiVersion.gte(API.v300)) { - // this._register( - // languages.registerCompletionItemProvider( - // `tsserver-${this.description.id}-tag`, - // 'TSC', - // languageIds, - // new TagCompletionProvider(client), - // ['>'] - // ) - // ) - // } + if (this.client.apiVersion.gte(API.v300)) { + this._register(new TagClosing(this.client, this.description.id)) + } } public handles(resource: string, doc: TextDocument): boolean { From ed3886f4c8c767e78f25d95117c26f22e84724b6 Mon Sep 17 00:00:00 2001 From: Qiming Zhao Date: Mon, 12 Apr 2021 15:22:56 +0800 Subject: [PATCH 5/5] Release 1.7.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b82f396..9a84a59 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "coc-tsserver", - "version": "1.6.8", + "version": "1.7.0", "description": "tsserver extension for coc.nvim", "main": "lib/index.js", "publisher": "chemzqm",