Merge branch 'master' of git://github.com/neoclide/coc-tsserver
This commit is contained in:
commit
49444b93c6
7 changed files with 232 additions and 75 deletions
|
@ -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`
|
||||
|
|
10
package.json
10
package.json
|
@ -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",
|
||||
|
|
201
src/server/features/tagClosing.ts
Normal file
201
src/server/features/tagClosing.ts
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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))
|
||||
|
|
Loading…
Reference in a new issue