diff --git a/package.json b/package.json index 5cb415a..ffe8f09 100644 --- a/package.json +++ b/package.json @@ -675,7 +675,7 @@ "license": "MIT", "devDependencies": { "@types/node": "^10.12.0", - "coc.nvim": "^0.0.80", + "coc.nvim": "^0.0.81-next.4", "esbuild": "^0.8.29", "semver": "^7.3.2", "vscode-languageserver-protocol": "^3.16.0", diff --git a/src/server/features/callHierarchy.ts b/src/server/features/callHierarchy.ts index 90f449e..e8bb791 100644 --- a/src/server/features/callHierarchy.ts +++ b/src/server/features/callHierarchy.ts @@ -93,7 +93,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 diff --git a/src/server/features/completionItemProvider.ts b/src/server/features/completionItemProvider.ts index 66d8392..7f2ad21 100644 --- a/src/server/features/completionItemProvider.ts +++ b/src/server/features/completionItemProvider.ts @@ -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' diff --git a/src/server/features/directiveCommentCompletions.ts b/src/server/features/directiveCommentCompletions.ts index d03fe80..bc49cc0 100644 --- a/src/server/features/directiveCommentCompletions.ts +++ b/src/server/features/directiveCommentCompletions.ts @@ -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 = { diff --git a/src/server/languageProvider.ts b/src/server/languageProvider.ts index 293775d..3e940ca 100644 --- a/src/server/languageProvider.ts +++ b/src/server/languageProvider.ts @@ -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), ['@'] )) } diff --git a/src/server/protocol.const.ts b/src/server/protocol.const.ts index 418fbf4..47da389 100644 --- a/src/server/protocol.const.ts +++ b/src/server/protocol.const.ts @@ -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' diff --git a/src/server/utils/completionItem.ts b/src/server/utils/completionItem.ts index d6b4cbb..7fcbe4f 100644 --- a/src/server/utils/completionItem.ts +++ b/src/server/utils/completionItem.ts @@ -2,7 +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 { Range, CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'vscode-languageserver-protocol' +import { Range, CompletionItem, CompletionItemKind, InsertTextFormat, Position, TextEdit } from 'coc.nvim' import * as Proto from '../protocol' import * as PConst from '../protocol.const' diff --git a/src/server/utils/previewer.ts b/src/server/utils/previewer.ts index 07d53b7..ccae4f5 100644 --- a/src/server/utils/previewer.ts +++ b/src/server/utils/previewer.ts @@ -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>\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('')) +} diff --git a/yarn.lock b/yarn.lock index 6553c0b..413b2b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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.4: + version "0.0.81-next.4" + resolved "https://registry.yarnpkg.com/coc.nvim/-/coc.nvim-0.0.81-next.4.tgz#06d808ceca76a250041f3f3dbf33ed9c221eb19b" + integrity sha512-zAgDe8o0mMvmsz7lM3Y/TiDc/+ZQIp05Z21tp1+WnLpNlNjbrqp6xpdcgDutjJWS04nzPNRRJA/btCGpMg7+/Q== esbuild@^0.8.29: version "0.8.29"