提交 d5bd9803 编写于 作者: M Matt Bierner

Change behavior of jt/ts jsdoc completions

Changes how jsdoc completion works to reduce typing delays and hopefully avoid showing it for fewer false positives. Main changes

- Only show the completion item if typescript's docCommentTemplate returns something. This means that you won't see the completion item in cases like: `/** |a */`  or `/**|` anymore. However in the case of `/**|`, pressing return will still complete to the empty comment as expected

- Get the snippet for the completion early so there is no flash when accepting it
上级 2d4a9884
......@@ -7,21 +7,21 @@ import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import * as Proto from '../protocol';
import { ITypeScriptServiceClient } from '../typescriptService';
import { Command, CommandManager } from '../utils/commandManager';
import { ConfigurationDependentRegistration } from '../utils/dependentRegistration';
import * as typeConverters from '../utils/typeConverters';
const localize = nls.loadMessageBundle();
const defaultJsDoc = new vscode.SnippetString(`/**\n * $0\n */`);
class JsDocCompletionItem extends vscode.CompletionItem {
constructor(
document: vscode.TextDocument,
position: vscode.Position
public readonly document: vscode.TextDocument,
public readonly position: vscode.Position
) {
super('/** */', vscode.CompletionItemKind.Snippet);
this.detail = localize('typescript.jsDocCompletionItem.documentation', 'JSDoc comment');
this.insertText = '';
this.sortText = '\0';
const line = document.lineAt(position.line).text;
......@@ -31,12 +31,6 @@ class JsDocCompletionItem extends vscode.CompletionItem {
this.range = new vscode.Range(
start,
position.translate(0, suffix ? suffix[0].length : 0));
this.command = {
title: 'Try Complete JSDoc',
command: TryCompleteJsDocCommand.COMMAND_NAME,
arguments: [document.uri, start]
};
}
}
......@@ -44,154 +38,66 @@ class JsDocCompletionProvider implements vscode.CompletionItemProvider {
constructor(
private readonly client: ITypeScriptServiceClient,
commandManager: CommandManager
) {
commandManager.register(new TryCompleteJsDocCommand(client));
}
) { }
public async provideCompletionItems(
document: vscode.TextDocument,
position: vscode.Position,
token: vscode.CancellationToken
): Promise<vscode.CompletionItem[]> {
): Promise<vscode.CompletionItem[] | undefined> {
const file = this.client.toPath(document.uri);
if (!file) {
return [];
return undefined;
}
if (!this.isValidCursorPosition(document, position)) {
return [];
if (!this.isPotentiallyValidDocCompletionPosition(document, position)) {
return undefined;
}
if (!await this.isCommentableLocation(file, position, token)) {
return [];
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
let res: Proto.DocCommandTemplateResponse | undefined;
try {
res = await this.client.execute('docCommentTemplate', args, token);
} catch {
return undefined;
}
return [new JsDocCompletionItem(document, position)];
}
private async isCommentableLocation(
file: string,
position: vscode.Position,
token: vscode.CancellationToken
): Promise<boolean> {
const args: Proto.FileRequestArgs = {
file
};
const response = await Promise.race([
this.client.execute('navtree', args, token),
new Promise<Proto.NavTreeResponse>((resolve) => setTimeout(resolve, 250))
]);
if (!response || !response.body) {
return false;
if (!res.body) {
return undefined;
}
const body = response.body;
function matchesPosition(tree: Proto.NavigationTree): boolean {
if (!tree.spans.length) {
return false;
}
const span = typeConverters.Range.fromTextSpan(tree.spans[0]);
if (position.line === span.start.line - 1 || position.line === span.start.line) {
return true;
}
const item = new JsDocCompletionItem(document, position);
return tree.childItems ? tree.childItems.some(matchesPosition) : false;
// 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 (res.body.newText === '/** */') {
item.insertText = defaultJsDoc;
} else {
item.insertText = templateToSnippet(res.body.newText);
}
return matchesPosition(body);
return [item];
}
private isValidCursorPosition(document: vscode.TextDocument, position: vscode.Position): boolean {
private isPotentiallyValidDocCompletionPosition(
document: vscode.TextDocument,
position: vscode.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);
return prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) !== null;
}
public resolveCompletionItem(item: vscode.CompletionItem, _token: vscode.CancellationToken) {
return item;
}
}
class TryCompleteJsDocCommand implements Command {
public static readonly COMMAND_NAME = '_typeScript.tryCompleteJsDoc';
public readonly id = TryCompleteJsDocCommand.COMMAND_NAME;
constructor(
private readonly client: ITypeScriptServiceClient
) { }
/**
* Try to insert a jsdoc comment, using a template provide by typescript
* if possible, otherwise falling back to a default comment format.
*/
public async execute(resource: vscode.Uri, start: vscode.Position): Promise<boolean> {
const file = this.client.toPath(resource);
if (!file) {
return false;
}
const editor = vscode.window.activeTextEditor;
if (!editor || editor.document.uri.fsPath !== resource.fsPath) {
return false;
}
const didInsertFromTemplate = await this.tryInsertJsDocFromTemplate(editor, file, start);
if (didInsertFromTemplate) {
return true;
}
return this.tryInsertDefaultDoc(editor, start);
}
private async tryInsertJsDocFromTemplate(editor: vscode.TextEditor, file: string, position: vscode.Position): Promise<boolean> {
const snippet = await TryCompleteJsDocCommand.getSnippetTemplate(this.client, file, position);
if (!snippet) {
if (prefix.match(/^\s*$|\/\*\*\s*$|^\s*\/\*\*+\s*$/) === null) {
return false;
}
return editor.insertSnippet(
snippet,
position,
{ undoStopBefore: false, undoStopAfter: true });
}
public static getSnippetTemplate(client: ITypeScriptServiceClient, file: string, position: vscode.Position): Promise<vscode.SnippetString | undefined> {
const args = typeConverters.Position.toFileLocationRequestArgs(file, position);
const tokenSource = new vscode.CancellationTokenSource();
return Promise.race([
client.execute('docCommentTemplate', args, tokenSource.token),
new Promise<Proto.DocCommandTemplateResponse>((_, reject) => setTimeout(() => {
tokenSource.cancel();
reject();
}, 250))
]).then((res: Proto.DocCommandTemplateResponse) => {
if (!res || !res.body) {
return undefined;
}
// 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 (res.body.newText === '/** */') {
return undefined;
}
return templateToSnippet(res.body.newText);
}, () => undefined);
}
/**
* Insert the default JSDoc
*/
private tryInsertDefaultDoc(editor: vscode.TextEditor, position: vscode.Position): Thenable<boolean> {
const snippet = new vscode.SnippetString(`/**\n * $0\n */`);
return editor.insertSnippet(snippet, position, { undoStopBefore: false, undoStopAfter: true });
// And everything after is possibly a closing comment or more whitespace
const suffix = line.slice(position.character);
return suffix.match(/^\s*\*+\//) !== null;
}
}
export function templateToSnippet(template: string): vscode.SnippetString {
// TODO: use append placeholder
let snippetIndex = 1;
......@@ -214,11 +120,10 @@ export function templateToSnippet(template: string): vscode.SnippetString {
export function register(
selector: vscode.DocumentSelector,
client: ITypeScriptServiceClient,
commandManager: CommandManager
): vscode.Disposable {
return new ConfigurationDependentRegistration('jsDocCompletion', 'enabled', () => {
return vscode.languages.registerCompletionItemProvider(selector,
new JsDocCompletionProvider(client, commandManager),
new JsDocCompletionProvider(client),
'*');
});
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册