jsDocCompletion feature

This commit is contained in:
Qiming Zhao 2022-04-10 15:07:17 +08:00
parent 2fc8fb014a
commit 148a55d63c
No known key found for this signature in database
GPG key ID: 9722CD0E8D4DCB8C
5 changed files with 149 additions and 1 deletions

View file

@ -269,6 +269,11 @@ for guide of coc.nvim's configuration.
- `javascript.format.placeOpenBraceOnNewLineForControlBlocks` default: `false`
- `javascript.inlayHints`: inlayHints related options.
### Added on 1.10.0
- `javascript.suggest.completeJSDocs` `typescript.suggest.completeJSDocs`:
Enable/disable suggestion to complete JSDoc comments. default: `true`
Configurations are the same as with VSCode. Install
[coc-json](https://github.com/neoclide/coc-json) and try completion with
`tsserver`, `typescript` or `javascript` in your

View file

@ -1,3 +1,8 @@
# 1.10.0
- Support jsdoc completion.
- Add configurations `javascript.suggest.completeJSDocs` and `typescript.suggest.completeJSDocs`.
# 1.9.15
- Fix uri for `zipfile`.

View file

@ -865,6 +865,16 @@
"insert",
"remove"
]
},
"javascript.suggest.completeJSDocs": {
"type": "boolean",
"default": true,
"description": "Enable/disable suggestion to complete JSDoc comments."
},
"typescript.suggest.completeJSDocs": {
"type": "boolean",
"default": true,
"description": "Enable/disable suggestion to complete JSDoc comments."
}
}
},

View file

@ -0,0 +1,121 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CancellationToken, CompletionItem, CompletionItemKind, CompletionItemProvider, InsertTextFormat, Position, Range, SnippetString, TextDocument, workspace } from 'coc.nvim'
import { ITypeScriptServiceClient } from '../typescriptService'
import { LanguageDescription } from '../utils/languageDescription'
import * as typeConverters from '../utils/typeConverters'
import FileConfigurationManager from './fileConfigurationManager'
const defaultJsDoc = new SnippetString(`/**\n * $0\n */`)
function createCompleteItem(document: TextDocument, position: Position): CompletionItem {
const line = document.lineAt(position.line).text
const prefix = line.slice(0, position.character).match(/\/\**\s*$/)
const suffix = line.slice(position.character).match(/^\s*\**\//)
const start = Position.create(position.line, prefix ? position.character - prefix[0].length : position.character)
const range = Range.create(start, Position.create(start.line, start.character + (suffix ? suffix[0].length : 0)))
let insert = `/** */`
return {
label: insert,
kind: CompletionItemKind.Text,
insertTextFormat: InsertTextFormat.Snippet,
detail: 'JSDoc comment',
sortText: `\0`,
textEdit: {
newText: insert,
range
}
}
}
export class JsDocCompletionProvider implements CompletionItemProvider {
constructor(
private readonly client: ITypeScriptServiceClient,
private readonly language: LanguageDescription,
private readonly fileConfigurationManager: FileConfigurationManager,
) {}
public async provideCompletionItems(
document: TextDocument,
position: Position,
token: CancellationToken
): Promise<CompletionItem[] | undefined> {
if (!workspace.getConfiguration(this.language.id, document.uri).get('suggest.completeJSDocs')) {
return undefined
}
const file = this.client.toOpenedFilePath(document.uri)
if (!file) {
return undefined
}
if (!this.isPotentiallyValidDocCompletionPosition(document, position)) {
return undefined
}
const response = await this.client.interruptGetErr(async () => {
await this.fileConfigurationManager.ensureConfigurationForDocument(document, token)
const args = typeConverters.Position.toFileLocationRequestArgs(file, position)
return this.client.execute('docCommentTemplate', args, token)
})
if (response.type !== 'response' || !response.body) {
return undefined
}
const item = createCompleteItem(document, position)
// Workaround for #43619
// docCommentTemplate previously returned undefined for empty jsdoc templates.
// TS 2.7 now returns a single line doc comment, which breaks indentation.
if (response.body.newText === '/** */') {
item.textEdit.newText = defaultJsDoc.value
} else {
item.textEdit.newText = templateToSnippet(response.body.newText).value
}
return [item]
}
private isPotentiallyValidDocCompletionPosition(
document: TextDocument,
position: Position
): boolean {
// Only show the JSdoc completion when the everything before the cursor is whitespace
// or could be the opening of a comment
const line = document.lineAt(position.line).text
const prefix = line.slice(0, position.character)
if (!/^\s*$|\/\*\s*$|^\s*\/\*+\s*$/.test(prefix)) {
return false
}
// And everything after is possibly a closing comment or more whitespace
const suffix = line.slice(position.character)
return /^\s*(\*+\/)?\s*$/.test(suffix)
}
}
export function templateToSnippet(template: string): SnippetString {
// TODO: use append placeholder
let snippetIndex = 1
template = template.replace(/\*\s$/gm, '*')
template = template.replace(/\$/g, '\\$')
template = template.replace(/^[ \t]*(?=(\/|[ ]\*))/gm, '')
template = template.replace(/^(\/\*\*\s*\*[ ]*)$/m, (x) => x + `\$0`)
template = template.replace(/\* @param([ ]\{\S+\})?\s+(\S+)[ \t]*$/gm, (_param, type, post) => {
let out = '* @param '
if (type === ' {any}' || type === ' {*}') {
out += `{\$\{${snippetIndex++}:*\}} `
} else if (type) {
out += type + ' '
}
out += post + ` \${${snippetIndex++}}`
return out
})
template = template.replace(/\* @returns[ \t]*$/gm, `* @returns \${${snippetIndex++}}`)
return new SnippetString(template)
}

View file

@ -31,6 +31,7 @@ import SignatureHelpProvider from './features/signatureHelp'
import SmartSelection from './features/smartSelect'
import TagClosing from './features/tagClosing'
import UpdateImportsOnFileRenameHandler from './features/updatePathOnRename'
import { JsDocCompletionProvider } from './features/jsDocCompletion'
import { OrganizeImportsCodeActionProvider } from './organizeImports'
import TypeScriptServiceClient from './typescriptServiceClient'
import API from './utils/api'
@ -73,13 +74,19 @@ export default class LanguageProvider {
typingsStatus: TypingsStatus
): void {
let languageIds = this.description.modeIds
let clientId = `tsserver-${this.description.id}`
let clientId = `tsc-${this.description.id}`
this._register(
languages.registerCompletionItemProvider(clientId, 'TSC', languageIds,
new CompletionItemProvider(client, typingsStatus, this.fileConfigurationManager, this.description.id),
CompletionItemProvider.triggerCharacters
)
)
this._register(
languages.registerCompletionItemProvider(`tsc-${this.description.id}-jsdoc`, 'TSC', languageIds,
new JsDocCompletionProvider(client, this.description, this.fileConfigurationManager),
['*', ' ']
)
)
if (this.client.apiVersion.gte(API.v230)) {
this._register(languages.registerCompletionItemProvider(
`${this.description.id}-directive`,