Merge branch 'neoclide:master' into master

This commit is contained in:
サポミク 2021-08-20 03:02:11 +09:00 committed by GitHub
commit 860c97aaf0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 360 additions and 213 deletions

View file

@ -1,6 +1,6 @@
{ {
"name": "coc-tsserver", "name": "coc-tsserver",
"version": "1.8.1", "version": "1.8.5",
"description": "tsserver extension for coc.nvim", "description": "tsserver extension for coc.nvim",
"main": "lib/index.js", "main": "lib/index.js",
"publisher": "chemzqm", "publisher": "chemzqm",
@ -265,6 +265,11 @@
"default": true, "default": true,
"description": "Show unused variable hint." "description": "Show unused variable hint."
}, },
"typescript.showDeprecated": {
"type": "boolean",
"default": true,
"description": "Show deprecated variable hint."
},
"typescript.updateImportsOnFileMove.enable": { "typescript.updateImportsOnFileMove.enable": {
"type": "boolean", "type": "boolean",
"default": true, "default": true,
@ -473,6 +478,11 @@
"default": true, "default": true,
"description": "Show unused variable hint." "description": "Show unused variable hint."
}, },
"javascript.showDeprecated": {
"type": "boolean",
"default": true,
"description": "Show deprecated variable hint."
},
"javascript.updateImportsOnFileMove.enable": { "javascript.updateImportsOnFileMove.enable": {
"type": "boolean", "type": "boolean",
"default": true "default": true
@ -675,13 +685,13 @@
"license": "MIT", "license": "MIT",
"devDependencies": { "devDependencies": {
"@types/node": "^10.12.0", "@types/node": "^10.12.0",
"coc.nvim": "^0.0.80", "coc.nvim": "^0.0.81-next.5",
"esbuild": "^0.8.29", "esbuild": "^0.8.29",
"semver": "^7.3.2", "semver": "^7.3.2",
"vscode-languageserver-protocol": "^3.16.0", "vscode-languageserver-protocol": "^3.16.0",
"which": "^2.0.2" "which": "^2.0.2"
}, },
"dependencies": { "dependencies": {
"typescript": "^4.3.2" "typescript": "^4.3.5"
} }
} }

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { CallHierarchyProvider, TextDocument, Uri } from 'coc.nvim' import { CallHierarchyProvider, TextDocument, Uri, workspace } from 'coc.nvim'
import path from "path" import path from "path"
import { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CancellationToken, Position, SymbolTag } from 'vscode-languageserver-protocol' import { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CancellationToken, Position, SymbolTag } from 'vscode-languageserver-protocol'
import type * as Proto from '../protocol' import type * as Proto from '../protocol'
@ -80,9 +80,7 @@ function parseKindModifier(kindModifiers: string): Set<string> {
function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): CallHierarchyItem { function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): CallHierarchyItem {
const useFileName = isSourceFileItem(item) const useFileName = isSourceFileItem(item)
const name = useFileName ? path.basename(item.file) : item.name const name = useFileName ? path.basename(item.file) : item.name
// TODO const detail = useFileName ? path.relative(workspace.cwd, path.dirname(item.file)) : item.containerName ?? ''
// const detail = useFileName ? workspace.asRelativePath(path.dirname(item.file)) : item.containerName ?? ''
const detail = item.containerName || ''
const result: CallHierarchyItem = { const result: CallHierarchyItem = {
name, name,
detail, detail,
@ -93,7 +91,7 @@ function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): CallHiera
} }
const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined
if (kindModifiers?.has(PConst.KindModifiers.depreacted)) { if (kindModifiers?.has(PConst.KindModifiers.deprecated)) {
result.tags = [SymbolTag.Deprecated] result.tags = [SymbolTag.Deprecated]
} }
return result return result

View file

@ -1,9 +1,9 @@
import { commands, CompletionItemProvider, TextDocument, window, workspace } from 'coc.nvim' import { commands, CompletionItemProvider, TextDocument, CompletionList, CompletionItem, window, workspace } from 'coc.nvim'
/*--------------------------------------------------------------------------------------------- /*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { CancellationToken, Command, CompletionContext, CompletionItem, CompletionList, InsertTextFormat, MarkupContent, MarkupKind, Position, Range, TextEdit } from 'vscode-languageserver-protocol' import { CancellationToken, Command, CompletionContext, InsertTextFormat, MarkupContent, MarkupKind, Position, Range, TextEdit } from 'vscode-languageserver-protocol'
import Proto from '../protocol' import Proto from '../protocol'
import * as PConst from '../protocol.const' import * as PConst from '../protocol.const'
import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService' import { ITypeScriptServiceClient, ServerResponse } from '../typescriptService'
@ -344,7 +344,7 @@ export default class TypeScriptCompletionItemProvider implements CompletionItemP
if (!this.completeOption.importStatementSuggestions || !this.client.apiVersion.lt(API.v430)) { if (!this.completeOption.importStatementSuggestions || !this.client.apiVersion.lt(API.v430)) {
return false return false
} }
return pre === 'import '; return pre === 'import '
} }
return true return true

View file

@ -3,10 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'coc.nvim' import { TextDocument } from 'coc.nvim'
import { DefinitionProvider, ImplementationProvider, TypeDefinitionProvider } from 'coc.nvim' import { DefinitionProvider, CancellationToken, Definition, Location, Position, DefinitionLink, ImplementationProvider, TypeDefinitionProvider } from 'coc.nvim'
import { CancellationToken, Definition, Location, Position } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
import * as typeConverters from '../utils/typeConverters' import * as typeConverters from '../utils/typeConverters'
export default class TypeScriptDefinitionProvider implements DefinitionProvider, TypeDefinitionProvider, ImplementationProvider { export default class TypeScriptDefinitionProvider implements DefinitionProvider, TypeDefinitionProvider, ImplementationProvider {
@ -41,12 +41,44 @@ export default class TypeScriptDefinitionProvider implements DefinitionProvider,
} }
} }
public provideDefinition( public async provideDefinition(
document: TextDocument, document: TextDocument,
position: Position, position: Position,
token: CancellationToken token: CancellationToken
): Promise<Definition | undefined> { ): Promise<Definition | DefinitionLink[] | undefined> {
return this.getSymbolLocations('definition', document, position, token) if (this.client.apiVersion.gte(API.v270)) {
const filepath = this.client.toOpenedFilePath(document.uri)
if (!filepath) {
return undefined
}
const args = typeConverters.Position.toFileLocationRequestArgs(filepath, position)
const response = await this.client.execute('definitionAndBoundSpan', args, token)
if (response.type !== 'response' || !response.body) {
return undefined
}
const span = response.body.textSpan ? typeConverters.Range.fromTextSpan(response.body.textSpan) : undefined
return response.body.definitions
.map((location): DefinitionLink => {
const target = typeConverters.Location.fromTextSpan(this.client.toResource(location.file), location)
if (location.contextStart && location.contextEnd) {
return {
originSelectionRange: span,
targetRange: typeConverters.Range.fromLocations(location.contextStart, location.contextEnd),
targetUri: target.uri,
targetSelectionRange: target.range,
}
}
return {
originSelectionRange: span,
targetRange: target.range,
targetUri: target.uri,
targetSelectionRange: target.range,
}
})
}
return await this.getSymbolLocations('definition', document, position, token)
} }
public provideTypeDefinition( public provideTypeDefinition(

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { DiagnosticCollection, languages, workspace } from 'coc.nvim' import { DiagnosticCollection, languages, workspace } from 'coc.nvim'
import { Diagnostic } from 'vscode-languageserver-protocol' import { Diagnostic, DiagnosticTag } from 'vscode-languageserver-protocol'
import { ResourceMap } from './resourceMap' import { ResourceMap } from './resourceMap'
export class DiagnosticSet { export class DiagnosticSet {
@ -146,8 +146,7 @@ export class DiagnosticsManager {
.get(uri) .get(uri)
.filter(x => { .filter(x => {
if (!enabled) { if (!enabled) {
// Still show unused return x.tags && (x.tags.includes(DiagnosticTag.Unnecessary) || x.tags.includes(DiagnosticTag.Deprecated))
return x.code == 6133
} }
return x.code !== 80001 // disable annoying CommonJS module warning return x.code !== 80001 // disable annoying CommonJS module warning
}) })

View file

@ -2,8 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, CompletionList, Position, Range } from 'vscode-languageserver-protocol' import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, CompletionList, Position, Range, TextDocument } from 'coc.nvim'
import { TextDocument } from 'coc.nvim'
import { workspace } from 'coc.nvim' import { workspace } from 'coc.nvim'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api' import API from '../utils/api'
@ -37,7 +36,7 @@ const tsDirectives390: Directive[] = [
] ]
export default class DirectiveCommentCompletionProvider { export default class DirectiveCommentCompletionProvider {
constructor(private readonly client: ITypeScriptServiceClient) { } constructor(private readonly client: ITypeScriptServiceClient) {}
public provideCompletionItems( public provideCompletionItems(
document: TextDocument, document: TextDocument,
@ -62,7 +61,7 @@ export default class DirectiveCommentCompletionProvider {
? tsDirectives390 ? tsDirectives390
: tsDirectives : tsDirectives
let items = directives.map(directive => { let items = directives.map(directive => {
const item = CompletionItem.create(directive.value) const item: CompletionItem = { label: directive.value }
item.kind = CompletionItemKind.Snippet item.kind = CompletionItemKind.Snippet
item.detail = directive.description item.detail = directive.description
item.textEdit = { item.textEdit = {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'coc.nvim' import { TextDocument } from 'coc.nvim'
import { DocumentSymbolProvider } from 'coc.nvim' import { DocumentSymbolProvider } from 'coc.nvim'
import { CancellationToken, DocumentSymbol, Range, SymbolKind } from 'vscode-languageserver-protocol' import { CancellationToken, DocumentSymbol, Range, SymbolKind, SymbolTag } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import * as PConst from '../protocol.const' import * as PConst from '../protocol.const'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
@ -82,20 +82,15 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP
} }
private static convertNavTree( private static convertNavTree(
bucket: DocumentSymbol[], output: DocumentSymbol[],
item: Proto.NavigationTree, item: Proto.NavigationTree,
): boolean { ): boolean {
let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item) let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item)
const children = new Set(item.childItems || []) const children = new Set(item.childItems || [])
for (const span of item.spans) { for (const span of item.spans) {
const range = typeConverters.Range.fromTextSpan(span) const range = typeConverters.Range.fromTextSpan(span)
const symbolInfo = DocumentSymbol.create( const symbolInfo = TypeScriptDocumentSymbolProvider.convertSymbol(item, range)
item.text, if (children.size) symbolInfo.children = []
'',
getSymbolKind(item.kind),
range,
range)
symbolInfo.children = children.size > 0 ? [] : null
for (const child of children) { for (const child of children) {
if (child.spans.some(span => !!containsRange(range, typeConverters.Range.fromTextSpan(span)))) { if (child.spans.some(span => !!containsRange(range, typeConverters.Range.fromTextSpan(span)))) {
@ -106,13 +101,33 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP
} }
if (shouldInclude) { if (shouldInclude) {
bucket.push(symbolInfo) output.push(symbolInfo)
} }
} }
return shouldInclude return shouldInclude
} }
private static convertSymbol(item: Proto.NavigationTree, range: Range): DocumentSymbol {
const selectionRange = item.nameSpan ? typeConverters.Range.fromTextSpan(item.nameSpan) : range
let label = item.text
switch (item.kind) {
case PConst.Kind.memberGetAccessor: label = `(get) ${label}`; break
case PConst.Kind.memberSetAccessor: label = `(set) ${label}`; break
}
const symbolInfo = DocumentSymbol.create(
label,
'',
getSymbolKind(item.kind),
range,
containsRange(range, selectionRange) ? selectionRange : range)
const kindModifiers = parseKindModifier(item.kindModifiers)
if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
symbolInfo.tags = [SymbolTag.Deprecated]
}
return symbolInfo
}
private static shouldInclueEntry( private static shouldInclueEntry(
item: Proto.NavigationTree | Proto.NavigationBarItem item: Proto.NavigationTree | Proto.NavigationBarItem
): boolean { ): boolean {
@ -142,3 +157,7 @@ function containsRange(range: Range, otherRange: Range): boolean {
} }
return true return true
} }
function parseKindModifier(kindModifiers: string): Set<string> {
return new Set(kindModifiers.split(/,|\s+/g))
}

View file

@ -2,8 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * 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 { CancellationTokenSource, Disposable, disposeAll, Position, Range, snippetManager, events, workspace, InsertChange } from 'coc.nvim'
import { TextDocumentContentChangeEvent } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api' import API from '../utils/api'
@ -19,49 +18,82 @@ export default class TagClosing implements Disposable {
} }
private _disposables: Disposable[] = [] private _disposables: Disposable[] = []
private _enabled: boolean = false
private _disposed = false private _disposed = false
private _timeout: NodeJS.Timer | undefined = undefined private _timeout: NodeJS.Timer | undefined = undefined
private _cancel: CancellationTokenSource | undefined = undefined private _cancel: CancellationTokenSource | undefined = undefined
private lastInsert: string
constructor( constructor(
private readonly client: ITypeScriptServiceClient, private readonly client: ITypeScriptServiceClient,
private readonly descriptionLanguageId: string private readonly descriptionLanguageId: string
) { ) {
workspace.onDidChangeTextDocument( events.on('InsertCharPre', character => {
(event) => this.lastInsert = character
this.onDidChangeTextDocument( }, null, this._disposables)
event.textDocument, events.on('TextChangedI', this.onChange, this, this._disposables)
event.contentChanges events.on('TextChangedP', this.onChange, this, this._disposables)
),
null,
this._disposables
)
this.updateEnabledState()
workspace.registerAutocmd({
event: ['BufEnter'],
request: false,
callback: () => this.updateEnabledState(),
})
} }
async updateEnabledState(): Promise<void> { private async onChange(bufnr: number, change: InsertChange): Promise<void> {
this._enabled = false let doc = workspace.getDocument((bufnr))
const doc = await workspace.document if (!doc || !doc.attached) return
if (!doc) { let enabled = this.isEnabled(doc.filetype, doc.uri)
return if (!enabled) return
let { pre, changedtick, lnum } = change
if (!pre.endsWith('/') && !pre.endsWith('>')) return
if (!pre.endsWith(this.lastInsert)) return
if (pre.length > 1 && pre[pre.length - 2] == '>') return
const filepath = this.client.toOpenedFilePath(doc.uri)
if (!filepath) return
if (this._timeout) {
clearTimeout(this._timeout)
} }
const document = doc.textDocument if (this._cancel) {
const configLang = TagClosing._configurationLanguages[document.languageId] this._cancel.cancel()
this._cancel.dispose()
this._cancel = undefined
}
await (doc as any).patchChange()
this._timeout = setTimeout(async () => {
this._timeout = undefined
if (this._disposed) return
if (doc.changedtick > changedtick) return
const position = Position.create(lnum - 1, pre.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 (doc.changedtick === changedtick) {
snippetManager.insertSnippet(
this.getTagSnippet(insertion).value,
false,
Range.create(position, position)
)
}
}, 50)
}
private isEnabled(languageId: string, uri: string): boolean {
const configLang = TagClosing._configurationLanguages[languageId]
if (!configLang || configLang !== this.descriptionLanguageId) { if (!configLang || configLang !== this.descriptionLanguageId) {
return return false
} }
if (!workspace.getConfiguration(undefined, document.uri).get<boolean>(`${configLang}.autoClosingTags`)) { if (!workspace.getConfiguration(undefined, uri).get<boolean>(`${configLang}.autoClosingTags`)) {
return return false
} }
this._enabled = true return true
} }
public dispose() { public dispose() {
@ -82,120 +114,10 @@ export default class TagClosing implements Disposable {
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 { private getTagSnippet(closingTag: Proto.TextInsertion): SnippetString {
const snippet = new SnippetString(); const snippet = new SnippetString()
snippet.appendPlaceholder('', 0); snippet.appendPlaceholder('', 0)
snippet.appendText(closingTag.newText); snippet.appendText(closingTag.newText)
return snippet; return snippet
} }
} }

View file

@ -4,12 +4,17 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { workspace } from 'coc.nvim' import { workspace } from 'coc.nvim'
import { WorkspaceSymbolProvider } from 'coc.nvim' import { WorkspaceSymbolProvider } from 'coc.nvim'
import { CancellationToken, Range, SymbolInformation, SymbolKind } from 'vscode-languageserver-protocol' import { CancellationToken, Range, SymbolInformation, SymbolKind, SymbolTag } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import * as PConst from '../protocol.const'
import { ITypeScriptServiceClient } from '../typescriptService' import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api' import API from '../utils/api'
import * as typeConverters from '../utils/typeConverters' import * as typeConverters from '../utils/typeConverters'
function parseKindModifier(kindModifiers: string): Set<string> {
return new Set(kindModifiers.split(/,|\s+/g))
}
function getSymbolKind(item: Proto.NavtoItem): SymbolKind { function getSymbolKind(item: Proto.NavtoItem): SymbolKind {
switch (item.kind) { switch (item.kind) {
case 'method': case 'method':
@ -75,7 +80,10 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo
getSymbolKind(item), getSymbolKind(item),
range, range,
this.client.toResource(item.file)) this.client.toResource(item.file))
const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined
if (kindModifiers?.has(PConst.KindModifiers.deprecated)) {
symbolInfo.tags = [SymbolTag.Deprecated]
}
result.push(symbolInfo) result.push(symbolInfo)
} }
return result return result

View file

@ -44,7 +44,7 @@ export default class LanguageProvider {
public client: TypeScriptServiceClient, public client: TypeScriptServiceClient,
private readonly fileConfigurationManager: FileConfigurationManager, private readonly fileConfigurationManager: FileConfigurationManager,
private description: LanguageDescription, private description: LanguageDescription,
private typingsStatus: TypingsStatus typingsStatus: TypingsStatus
) { ) {
workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables) workspace.onDidChangeConfiguration(this.configurationChanged, this, this.disposables)
this.configurationChanged() this.configurationChanged()
@ -86,7 +86,7 @@ export default class LanguageProvider {
if (this.client.apiVersion.gte(API.v230)) { if (this.client.apiVersion.gte(API.v230)) {
this._register(languages.registerCompletionItemProvider( this._register(languages.registerCompletionItemProvider(
`${this.description.id}-directive`, `${this.description.id}-directive`,
'TSC', languageIds, new DirectiveCommentCompletionProvider(client,), ['@'] 'TSC', languageIds, new DirectiveCommentCompletionProvider(client), ['@']
)) ))
} }
@ -179,17 +179,22 @@ export default class LanguageProvider {
public diagnosticsReceived( public diagnosticsReceived(
diagnosticsKind: DiagnosticKind, diagnosticsKind: DiagnosticKind,
file: Uri, file: Uri,
diagnostics: (Diagnostic & { reportUnnecessary: any })[] diagnostics: (Diagnostic & { reportUnnecessary: any, reportDeprecated: any })[]
): void { ): void {
const config = workspace.getConfiguration(this.id, file.toString()) const config = workspace.getConfiguration(this.id, file.toString())
const reportUnnecessary = config.get<boolean>('showUnused', true) const reportUnnecessary = config.get<boolean>('showUnused', true)
const reportDeprecated = config.get<boolean>('showDeprecated', true)
this.client.diagnosticsManager.diagnosticsReceived(diagnosticsKind, file.toString(), diagnostics.filter(diag => { this.client.diagnosticsManager.diagnosticsReceived(diagnosticsKind, file.toString(), diagnostics.filter(diag => {
if (!reportUnnecessary) { if (!reportUnnecessary) {
diag.tags = undefined
if (diag.reportUnnecessary && diag.severity === DiagnosticSeverity.Information) { if (diag.reportUnnecessary && diag.severity === DiagnosticSeverity.Information) {
return false return false
} }
} }
if (!reportDeprecated) {
if (diag.reportDeprecated && diag.severity === DiagnosticSeverity.Hint) {
return false
}
}
return true return true
})) }))
} }

View file

@ -44,7 +44,7 @@ export class DiagnosticCategory {
export class KindModifiers { export class KindModifiers {
public static readonly optional = 'optional' public static readonly optional = 'optional'
public static readonly depreacted = 'deprecated' public static readonly deprecated = 'deprecated'
public static readonly color = 'color' public static readonly color = 'color'
public static readonly dtsFile = '.d.ts' public static readonly dtsFile = '.d.ts'
@ -60,7 +60,7 @@ export class KindModifiers {
KindModifiers.tsxFile, KindModifiers.tsxFile,
KindModifiers.jsFile, KindModifiers.jsFile,
KindModifiers.jsxFile, KindModifiers.jsxFile,
KindModifiers.jsonFile, KindModifiers.jsonFile
] ]
} }
@ -72,3 +72,19 @@ export class DisplayPartKind {
public static readonly punctuation = 'punctuation' public static readonly punctuation = 'punctuation'
public static readonly text = 'text' public static readonly text = 'text'
} }
export enum EventName {
syntaxDiag = 'syntaxDiag',
semanticDiag = 'semanticDiag',
suggestionDiag = 'suggestionDiag',
configFileDiag = 'configFileDiag',
telemetry = 'telemetry',
projectLanguageServiceState = 'projectLanguageServiceState',
projectsUpdatedInBackground = 'projectsUpdatedInBackground',
beginInstallTypes = 'beginInstallTypes',
endInstallTypes = 'endInstallTypes',
typesInstallerInitializationFailed = 'typesInstallerInitializationFailed',
surveyReady = 'surveyReady',
projectLoadingStart = 'projectLoadingStart',
projectLoadingFinish = 'projectLoadingFinish'
}

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { disposeAll, languages, TextDocument, Uri, workspace } from 'coc.nvim' import { disposeAll, languages, TextDocument, Uri, workspace } from 'coc.nvim'
import { CancellationToken, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, Disposable, Position, Range } from 'vscode-languageserver-protocol' import { CancellationToken, Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, Disposable, Position, Range } from 'vscode-languageserver-protocol'
import { flatten } from '../utils/arrays' import { flatten } from '../utils/arrays'
import { PluginManager } from '../utils/plugins' import { PluginManager } from '../utils/plugins'
import { DiagnosticKind } from './features/diagnostics' import { DiagnosticKind } from './features/diagnostics'
@ -217,11 +217,11 @@ export default class TypeScriptServiceClientHost implements Disposable {
} }
} }
private createMarkerDatas(diagnostics: Proto.Diagnostic[]): (Diagnostic & { reportUnnecessary: any })[] { private createMarkerDatas(diagnostics: Proto.Diagnostic[]): (Diagnostic & { reportUnnecessary: any, reportDeprecated: any })[] {
return diagnostics.map(tsDiag => this.tsDiagnosticToLspDiagnostic(tsDiag)) return diagnostics.map(tsDiag => this.tsDiagnosticToLspDiagnostic(tsDiag))
} }
private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): (Diagnostic & { reportUnnecessary: any }) { private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): (Diagnostic & { reportUnnecessary: any, reportDeprecated: any }) {
const { start, end, text } = diagnostic const { start, end, text } = diagnostic
const range = { const range = {
start: typeConverters.Position.fromLocation(start), start: typeConverters.Position.fromLocation(start),
@ -237,11 +237,22 @@ export default class TypeScriptServiceClientHost implements Disposable {
} }
}) })
} }
let tags: DiagnosticTag[] | undefined = []
if (diagnostic.reportsUnnecessary) {
tags.push(DiagnosticTag.Unnecessary)
}
if (diagnostic.reportsDeprecated) {
tags.push(DiagnosticTag.Deprecated)
}
tags = tags.length ? tags : undefined
return { return {
range, range,
tags,
message: text, message: text,
code: diagnostic.code ? diagnostic.code : null, code: diagnostic.code ? diagnostic.code : null,
severity: this.getDiagnosticSeverity(diagnostic), severity: this.getDiagnosticSeverity(diagnostic),
reportDeprecated: diagnostic.reportsDeprecated,
reportUnnecessary: diagnostic.reportsUnnecessary, reportUnnecessary: diagnostic.reportsUnnecessary,
source: diagnostic.source || 'tsserver', source: diagnostic.source || 'tsserver',
relatedInformation relatedInformation

View file

@ -2,7 +2,8 @@
* Copyright (c) Microsoft Corporation. All rights reserved. * Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information. * Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Range, CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'vscode-languageserver-protocol' import { Range, CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'coc.nvim'
import { CompletionItemTag } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import * as PConst from '../protocol.const' import * as PConst from '../protocol.const'
@ -52,6 +53,7 @@ export function convertCompletionEntry(
let insertText = tsEntry.insertText let insertText = tsEntry.insertText
let commitCharacters = getCommitCharacters(tsEntry, context) let commitCharacters = getCommitCharacters(tsEntry, context)
let tags: CompletionItemTag[]
if (tsEntry.isImportStatementCompletion) { if (tsEntry.isImportStatementCompletion) {
insertText = label insertText = label
@ -75,6 +77,10 @@ export function convertCompletionEntry(
label += '?' label += '?'
} }
if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
tags = [CompletionItemTag.Deprecated]
}
if (kindModifiers.has(PConst.KindModifiers.color)) { if (kindModifiers.has(PConst.KindModifiers.color)) {
kind = CompletionItemKind.Color kind = CompletionItemKind.Color
} }
@ -97,6 +103,7 @@ export function convertCompletionEntry(
insertText, insertText,
textEdit, textEdit,
kind, kind,
tags,
preselect, preselect,
insertTextFormat, insertTextFormat,
sortText, sortText,

View file

@ -5,37 +5,83 @@
import { MarkupContent, MarkupKind } from 'vscode-languageserver-protocol' import { MarkupContent, MarkupKind } from 'vscode-languageserver-protocol'
import * as Proto from '../protocol' import * as Proto from '../protocol'
import { Uri } from 'coc.nvim'
function toResource(filepath: string): Uri {
return Uri.file(filepath)
}
function replaceLinks(text: string): string {
return text
// Http(s) links
.replace(/\{@(link|linkplain|linkcode) (https?:\/\/[^ |}]+?)(?:[| ]([^{}\n]+?))?\}/gi, (_, tag: string, link: string, text?: string) => {
switch (tag) {
case 'linkcode':
return `[\`${text ? text.trim() : link}\`](${link})`
default:
return `[${text ? text.trim() : link}](${link})`
}
})
}
function processInlineTags(text: string): string {
return replaceLinks(text)
}
function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined { function getTagBodyText(tag: Proto.JSDocTagInfo): string | undefined {
if (!tag.text) { if (!tag.text) {
return undefined return undefined
} }
// Convert to markdown code block if it is not already one
switch (tag.name) { function makeCodeblock(text: string): string {
case 'example': if (text.match(/^\s*[~`]{3}/g)) {
case 'default': return text
// Convert to markdown code block if it not already one }
if (tag.text.match(/^\s*[~`]{3}/g)) { return '```\n' + text + '\n```'
return tag.text
}
return '```\n' + tag.text + '\n```'
} }
return tag.text const text = convertLinkTags(tag.text)
switch (tag.name) {
case 'example':
// check for caption tags, fix for #79704
const captionTagMatches = text.match(/<caption>(.*?)<\/caption>\s*(\r\n|\n)/)
if (captionTagMatches && captionTagMatches.index === 0) {
return captionTagMatches[1] + '\n\n' + makeCodeblock(text.substr(captionTagMatches[0].length))
} else {
return makeCodeblock(text)
}
case 'author':
// fix obsucated email address, #80898
const emailMatch = text.match(/(.+)\s<([-.\w]+@[-.\w]+)>/)
if (emailMatch === null) {
return text
} else {
return `${emailMatch[1]} ${emailMatch[2]}`
}
case 'default':
return makeCodeblock(text)
}
return processInlineTags(text)
} }
function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined { function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
switch (tag.name) { switch (tag.name) {
case 'augments':
case 'extends':
case 'param': case 'param':
const body = (tag.text || '').split(/^([\w\.]+)\s*/) case 'template':
if (body && body.length === 3) { const body = (convertLinkTags(tag.text)).split(/^(\S+)\s*-?\s*/)
if (body?.length === 3) {
const param = body[1] const param = body[1]
const doc = body[2] const doc = body[2]
const label = `*@${tag.name}* \`${param}\`` const label = `*@${tag.name}* \`${param}\``
if (!doc) { if (!doc) {
return label return label
} }
return label + (doc.match(/\r\n|\n/g) ? '\n' + doc : `${doc}`) return label + (doc.match(/\r\n|\n/g) ? ' \n' + processInlineTags(doc) : `${processInlineTags(doc)}`)
} }
} }
@ -45,7 +91,7 @@ function getTagDocumentation(tag: Proto.JSDocTagInfo): string | undefined {
if (!text) { if (!text) {
return label return label
} }
return label + (text.match(/\r\n|\n/g) ? '\n' + text : `${text}`) return label + (text.match(/\r\n|\n/g) ? ' \n' + text : `${text}`)
} }
export function plain(parts: Proto.SymbolDisplayPart[]): string { export function plain(parts: Proto.SymbolDisplayPart[]): string {
@ -71,3 +117,73 @@ export function markdownDocumentation(
value: out value: out
} }
} }
/**
* Convert `@link` inline tags to markdown links
*/
function convertLinkTags(
parts: readonly Proto.SymbolDisplayPart[] | string | undefined
): string {
if (!parts) {
return ''
}
if (typeof parts === 'string') {
return parts
}
const out: string[] = []
let currentLink: { name?: string, target?: Proto.FileSpan, text?: string } | undefined
for (const part of parts) {
switch (part.kind) {
case 'link':
if (currentLink) {
const text = currentLink.text ?? currentLink.name
if (currentLink.target) {
const link = toResource(currentLink.target.file)
.with({
fragment: `L${currentLink.target.start.line},${currentLink.target.start.offset}`
})
out.push(`[${text}](${link.toString()})`)
} else {
if (text) {
if (/^https?:/.test(text)) {
const parts = text.split(' ')
if (parts.length === 1) {
out.push(parts[0])
} else if (parts.length > 1) {
out.push(`[${parts.slice(1).join(' ')}](${parts[0]})`)
}
} else {
out.push(text)
}
}
}
currentLink = undefined
} else {
currentLink = {}
}
break
case 'linkName':
if (currentLink) {
currentLink.name = part.text
// TODO: remove cast once we pick up TS 4.3
currentLink.target = (part as any as Proto.JSDocLinkDisplayPart).target
}
break
case 'linkText':
if (currentLink) {
currentLink.text = part.text
}
break
default:
out.push(part.text)
break
}
}
return processInlineTags(out.join(''))
}

View file

@ -23,6 +23,11 @@ export namespace Range {
} }
} }
} }
export const fromLocations = (start: Proto.Location, end: Proto.Location): language.Range =>
language.Range.create(
Math.max(0, start.line - 1), Math.max(start.offset - 1, 0),
Math.max(0, end.line - 1), Math.max(0, end.offset - 1))
export const toFormattingRequestArgs = (file: string, range: language.Range): Proto.FormatRequestArgs => ({ export const toFormattingRequestArgs = (file: string, range: language.Range): Proto.FormatRequestArgs => ({
file, file,

View file

@ -7,10 +7,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.44.tgz#3945e6b702cb6403f22b779c8ea9e5c3f44ead40" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.44.tgz#3945e6b702cb6403f22b779c8ea9e5c3f44ead40"
integrity sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw== integrity sha512-vHPAyBX1ffLcy4fQHmDyIUMUb42gHZjPHU66nhvbMzAWJqHnySGZ6STwN3rwrnSd1FHB0DI/RWgGELgKSYRDmw==
coc.nvim@^0.0.80: coc.nvim@^0.0.81-next.5:
version "0.0.80" version "0.0.81-next.5"
resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.80.tgz#785145c382660db03f517f9b497900d95cbd0e4f" resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.5.tgz#b09bda5a2d527f7cd7bd786d4dee958160285e2b"
integrity sha512-/3vTcnofoAYMrdENrlQmADTzfXX4+PZ0fiM10a39UA37dTR2dpIGi9O469kcIksuunLjToqWG8S45AGx/9wV7g== integrity sha512-NUTEWmjm9uKGBD8FNsj55PH1Xn4hD5uDCs7EdMoBpXWehq6/P1UoDZQfWXQv9HTq4zF/jH3g7KtZtl94VTlW4A==
esbuild@^0.8.29: esbuild@^0.8.29:
version "0.8.29" version "0.8.29"
@ -27,10 +27,10 @@ semver@^7.3.2:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ== integrity sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==
typescript@^4.3.2: typescript@^4.3.5:
version "4.3.2" version "4.3.5"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.2.tgz#399ab18aac45802d6f2498de5054fcbbe716a805" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.3.5.tgz#4d1c37cc16e893973c45a06886b7113234f119f4"
integrity sha512-zZ4hShnmnoVnAHpVHWpTcxdv7dWP60S2FsydQLV8V5PbS3FifjWFFRiHSWpDJahly88PRyV5teTSLoq4eG7mKw== integrity sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==
vscode-jsonrpc@6.0.0: vscode-jsonrpc@6.0.0:
version "6.0.0" version "6.0.0"