Merge branch 'master' of git://github.com/neoclide/coc-tsserver

This commit is contained in:
sup39[サポミク] 2021-04-30 14:43:29 +09:00
commit 49444b93c6
7 changed files with 232 additions and 75 deletions

View file

@ -76,7 +76,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.
@ -144,6 +144,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`
@ -179,6 +180,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`

View file

@ -1,6 +1,6 @@
{
"name": "coc-tsserver",
"version": "1.6.7",
"version": "1.7.0",
"description": "tsserver extension for coc.nvim",
"main": "lib/index.js",
"publisher": "chemzqm",
@ -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",

View file

@ -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<string, string> = {
'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<void> {
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<boolean>(`${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;
}
}

View file

@ -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<CompletionItem[] | undefined> {
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)
}
}

View file

@ -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 {

View file

@ -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)

View file

@ -181,7 +181,7 @@ export default class TypeScriptServiceClientHost implements Disposable {
public async findLanguage(uri: string): Promise<LanguageProvider> {
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))