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",
"version": "1.8.1",
"version": "1.8.5",
"description": "tsserver extension for coc.nvim",
"main": "lib/index.js",
"publisher": "chemzqm",
@ -265,6 +265,11 @@
"default": true,
"description": "Show unused variable hint."
},
"typescript.showDeprecated": {
"type": "boolean",
"default": true,
"description": "Show deprecated variable hint."
},
"typescript.updateImportsOnFileMove.enable": {
"type": "boolean",
"default": true,
@ -473,6 +478,11 @@
"default": true,
"description": "Show unused variable hint."
},
"javascript.showDeprecated": {
"type": "boolean",
"default": true,
"description": "Show deprecated variable hint."
},
"javascript.updateImportsOnFileMove.enable": {
"type": "boolean",
"default": true
@ -675,13 +685,13 @@
"license": "MIT",
"devDependencies": {
"@types/node": "^10.12.0",
"coc.nvim": "^0.0.80",
"coc.nvim": "^0.0.81-next.5",
"esbuild": "^0.8.29",
"semver": "^7.3.2",
"vscode-languageserver-protocol": "^3.16.0",
"which": "^2.0.2"
},
"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.
*--------------------------------------------------------------------------------------------*/
import { CallHierarchyProvider, TextDocument, Uri } from 'coc.nvim'
import { CallHierarchyProvider, TextDocument, Uri, workspace } from 'coc.nvim'
import path from "path"
import { CallHierarchyIncomingCall, CallHierarchyItem, CallHierarchyOutgoingCall, CancellationToken, Position, SymbolTag } from 'vscode-languageserver-protocol'
import type * as Proto from '../protocol'
@ -80,9 +80,7 @@ function parseKindModifier(kindModifiers: string): Set<string> {
function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): CallHierarchyItem {
const useFileName = isSourceFileItem(item)
const name = useFileName ? path.basename(item.file) : item.name
// TODO
// const detail = useFileName ? workspace.asRelativePath(path.dirname(item.file)) : item.containerName ?? ''
const detail = item.containerName || ''
const detail = useFileName ? path.relative(workspace.cwd, path.dirname(item.file)) : item.containerName ?? ''
const result: CallHierarchyItem = {
name,
detail,
@ -93,7 +91,7 @@ function fromProtocolCallHierarchyItem(item: Proto.CallHierarchyItem): CallHiera
}
const kindModifiers = item.kindModifiers ? parseKindModifier(item.kindModifiers) : undefined
if (kindModifiers?.has(PConst.KindModifiers.depreacted)) {
if (kindModifiers?.has(PConst.KindModifiers.deprecated)) {
result.tags = [SymbolTag.Deprecated]
}
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.
* 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 * as PConst from '../protocol.const'
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)) {
return false
}
return pre === 'import ';
return pre === 'import '
}
return true

View file

@ -3,10 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'coc.nvim'
import { DefinitionProvider, ImplementationProvider, TypeDefinitionProvider } from 'coc.nvim'
import { CancellationToken, Definition, Location, Position } from 'vscode-languageserver-protocol'
import { DefinitionProvider, CancellationToken, Definition, Location, Position, DefinitionLink, ImplementationProvider, TypeDefinitionProvider } from 'coc.nvim'
import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
import * as typeConverters from '../utils/typeConverters'
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,
position: Position,
token: CancellationToken
): Promise<Definition | undefined> {
return this.getSymbolLocations('definition', document, position, token)
): Promise<Definition | DefinitionLink[] | undefined> {
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(

View file

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

View file

@ -2,8 +2,7 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 { TextDocument } from 'coc.nvim'
import { CancellationToken, CompletionContext, CompletionItem, CompletionItemKind, CompletionList, Position, Range, TextDocument } from 'coc.nvim'
import { workspace } from 'coc.nvim'
import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
@ -37,7 +36,7 @@ const tsDirectives390: Directive[] = [
]
export default class DirectiveCommentCompletionProvider {
constructor(private readonly client: ITypeScriptServiceClient) { }
constructor(private readonly client: ITypeScriptServiceClient) {}
public provideCompletionItems(
document: TextDocument,
@ -62,7 +61,7 @@ export default class DirectiveCommentCompletionProvider {
? tsDirectives390
: tsDirectives
let items = directives.map(directive => {
const item = CompletionItem.create(directive.value)
const item: CompletionItem = { label: directive.value }
item.kind = CompletionItemKind.Snippet
item.detail = directive.description
item.textEdit = {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { TextDocument } 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 PConst from '../protocol.const'
import { ITypeScriptServiceClient } from '../typescriptService'
@ -82,20 +82,15 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP
}
private static convertNavTree(
bucket: DocumentSymbol[],
output: DocumentSymbol[],
item: Proto.NavigationTree,
): boolean {
let shouldInclude = TypeScriptDocumentSymbolProvider.shouldInclueEntry(item)
const children = new Set(item.childItems || [])
for (const span of item.spans) {
const range = typeConverters.Range.fromTextSpan(span)
const symbolInfo = DocumentSymbol.create(
item.text,
'',
getSymbolKind(item.kind),
range,
range)
symbolInfo.children = children.size > 0 ? [] : null
const symbolInfo = TypeScriptDocumentSymbolProvider.convertSymbol(item, range)
if (children.size) symbolInfo.children = []
for (const child of children) {
if (child.spans.some(span => !!containsRange(range, typeConverters.Range.fromTextSpan(span)))) {
@ -106,13 +101,33 @@ export default class TypeScriptDocumentSymbolProvider implements DocumentSymbolP
}
if (shouldInclude) {
bucket.push(symbolInfo)
output.push(symbolInfo)
}
}
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(
item: Proto.NavigationTree | Proto.NavigationBarItem
): boolean {
@ -142,3 +157,7 @@ function containsRange(range: Range, otherRange: Range): boolean {
}
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.
* 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 { CancellationTokenSource, Disposable, disposeAll, Position, Range, snippetManager, events, workspace, InsertChange } from 'coc.nvim'
import * as Proto from '../protocol'
import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
@ -19,49 +18,82 @@ export default class TagClosing implements Disposable {
}
private _disposables: Disposable[] = []
private _enabled: boolean = false
private _disposed = false
private _timeout: NodeJS.Timer | undefined = undefined
private _cancel: CancellationTokenSource | undefined = undefined
private lastInsert: string
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(),
})
events.on('InsertCharPre', character => {
this.lastInsert = character
}, null, this._disposables)
events.on('TextChangedI', this.onChange, this, this._disposables)
events.on('TextChangedP', this.onChange, this, this._disposables)
}
async updateEnabledState(): Promise<void> {
this._enabled = false
const doc = await workspace.document
if (!doc) {
return
private async onChange(bufnr: number, change: InsertChange): Promise<void> {
let doc = workspace.getDocument((bufnr))
if (!doc || !doc.attached) return
let enabled = this.isEnabled(doc.filetype, doc.uri)
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
const configLang = TagClosing._configurationLanguages[document.languageId]
if (this._cancel) {
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) {
return
return false
}
if (!workspace.getConfiguration(undefined, document.uri).get<boolean>(`${configLang}.autoClosingTags`)) {
return
if (!workspace.getConfiguration(undefined, uri).get<boolean>(`${configLang}.autoClosingTags`)) {
return false
}
this._enabled = true
return true
}
public dispose() {
@ -82,120 +114,10 @@ export default class TagClosing implements Disposable {
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;
const snippet = new SnippetString()
snippet.appendPlaceholder('', 0)
snippet.appendText(closingTag.newText)
return snippet
}
}

View file

@ -4,12 +4,17 @@
*--------------------------------------------------------------------------------------------*/
import { workspace } 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 PConst from '../protocol.const'
import { ITypeScriptServiceClient } from '../typescriptService'
import API from '../utils/api'
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 {
switch (item.kind) {
case 'method':
@ -75,7 +80,10 @@ export default class TypeScriptWorkspaceSymbolProvider implements WorkspaceSymbo
getSymbolKind(item),
range,
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)
}
return result

View file

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

View file

@ -44,7 +44,7 @@ export class DiagnosticCategory {
export class KindModifiers {
public static readonly optional = 'optional'
public static readonly depreacted = 'deprecated'
public static readonly deprecated = 'deprecated'
public static readonly color = 'color'
public static readonly dtsFile = '.d.ts'
@ -60,7 +60,7 @@ export class KindModifiers {
KindModifiers.tsxFile,
KindModifiers.jsFile,
KindModifiers.jsxFile,
KindModifiers.jsonFile,
KindModifiers.jsonFile
]
}
@ -72,3 +72,19 @@ export class DisplayPartKind {
public static readonly punctuation = 'punctuation'
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.
*--------------------------------------------------------------------------------------------*/
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 { PluginManager } from '../utils/plugins'
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))
}
private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): (Diagnostic & { reportUnnecessary: any }) {
private tsDiagnosticToLspDiagnostic(diagnostic: Proto.Diagnostic): (Diagnostic & { reportUnnecessary: any, reportDeprecated: any }) {
const { start, end, text } = diagnostic
const range = {
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 {
range,
tags,
message: text,
code: diagnostic.code ? diagnostic.code : null,
severity: this.getDiagnosticSeverity(diagnostic),
reportDeprecated: diagnostic.reportsDeprecated,
reportUnnecessary: diagnostic.reportsUnnecessary,
source: diagnostic.source || 'tsserver',
relatedInformation

View file

@ -2,7 +2,8 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* 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 PConst from '../protocol.const'
@ -52,6 +53,7 @@ export function convertCompletionEntry(
let insertText = tsEntry.insertText
let commitCharacters = getCommitCharacters(tsEntry, context)
let tags: CompletionItemTag[]
if (tsEntry.isImportStatementCompletion) {
insertText = label
@ -75,6 +77,10 @@ export function convertCompletionEntry(
label += '?'
}
if (kindModifiers.has(PConst.KindModifiers.deprecated)) {
tags = [CompletionItemTag.Deprecated]
}
if (kindModifiers.has(PConst.KindModifiers.color)) {
kind = CompletionItemKind.Color
}
@ -97,6 +103,7 @@ export function convertCompletionEntry(
insertText,
textEdit,
kind,
tags,
preselect,
insertTextFormat,
sortText,

View file

@ -5,37 +5,83 @@
import { MarkupContent, MarkupKind } from 'vscode-languageserver-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 {
if (!tag.text) {
return undefined
}
switch (tag.name) {
case 'example':
case 'default':
// Convert to markdown code block if it not already one
if (tag.text.match(/^\s*[~`]{3}/g)) {
return tag.text
}
return '```\n' + tag.text + '\n```'
// Convert to markdown code block if it is not already one
function makeCodeblock(text: string): string {
if (text.match(/^\s*[~`]{3}/g)) {
return text
}
return '```\n' + 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 {
switch (tag.name) {
case 'augments':
case 'extends':
case 'param':
const body = (tag.text || '').split(/^([\w\.]+)\s*/)
if (body && body.length === 3) {
case 'template':
const body = (convertLinkTags(tag.text)).split(/^(\S+)\s*-?\s*/)
if (body?.length === 3) {
const param = body[1]
const doc = body[2]
const label = `*@${tag.name}* \`${param}\``
if (!doc) {
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) {
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 {
@ -71,3 +117,73 @@ export function markdownDocumentation(
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 => ({
file,

View file

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