提交 5b1a6f41 编写于 作者: M Matt Bierner 提交者: GitHub

Use CompletionItem for JsDoc Comment auto fill (#21025)

Fixes #20990

Switches back to using a completion item provider for jsdoc auto complete.

The completion list will be automatically shown when you are in a potentially valid jsdoc completion context
上级 729400d7
......@@ -36,7 +36,6 @@
"onCommand:typescript.selectTypeScriptVersion",
"onCommand:javascript.goToProjectConfig",
"onCommand:typescript.goToProjectConfig",
"onCommand:_typescript.tryCompleteJsDoc",
"workspaceContains:jsconfig.json",
"workspaceContains:tsconfig.json"
],
......@@ -84,28 +83,6 @@
}
}
],
"keybindings": [
{
"key": "enter",
"command": "_typescript.tryCompleteJsDoc",
"when": "editorTextFocus && !suggestWidgetVisible && editorLangId == 'typescript'"
},
{
"key": "enter",
"command": "_typescript.tryCompleteJsDoc",
"when": "editorTextFocus && !suggestWidgetVisible && editorLangId == 'typescriptreact'"
},
{
"key": "enter",
"command": "_typescript.tryCompleteJsDoc",
"when": "editorTextFocus && !suggestWidgetVisible && editorLangId == 'javascript'"
},
{
"key": "enter",
"command": "_typescript.tryCompleteJsDoc",
"when": "editorTextFocus && !suggestWidgetVisible && editorLangId == 'javascriptreact'"
}
],
"configuration": {
"type": "object",
"title": "%configuration.typescript%",
......
{
"jsdoc snippet": {
"prefix": "jsdoc comment",
"body": [
"/**",
" * $0",
" */"
],
"description": "jsdoc snippet"
},
"Constructor": {
"prefix": "ctor",
"body": [
......
{
"jsdoc snippet": {
"prefix": "jsdoc comment",
"body": [
"/**",
" * $0",
" */"
],
"description": "jsdoc snippet"
},
"Constructor": {
"prefix": "ctor",
"body": [
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Position, Selection, Range, CompletionItemProvider, CompletionItemKind, TextDocument, CancellationToken, CompletionItem, window, commands, Uri, ProviderResult, TextEditor } from 'vscode';
import { ITypescriptServiceClient } from '../typescriptService';
import { FileLocationRequestArgs, DocCommandTemplateResponse } from '../protocol';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
const tryCompleteJsDocCommand = '_typeScript.tryCompleteJsDoc';
class JsDocCompletionItem extends CompletionItem {
constructor(file: Uri, position: Position) {
super('/** @param */', CompletionItemKind.Snippet);
this.detail = localize('typescript.jsDocCompletionItem.detail', 'Complete JSDoc comment');
this.insertText = '';
this.command = {
title: 'Try Complete Js Doc',
command: tryCompleteJsDocCommand,
arguments: [file, position]
};
}
}
export default class JsDocCompletionHelper implements CompletionItemProvider {
constructor(
private client: ITypescriptServiceClient,
) {
window.onDidChangeTextEditorSelection(e => {
if (e.textEditor.document.languageId !== 'typescript'
&& e.textEditor.document.languageId !== 'typescriptreact'
&& e.textEditor.document.languageId !== 'javascript'
&& e.textEditor.document.languageId !== 'javascriptreact'
) {
return;
}
const selection = e.selections[0];
if (!selection.start.isEqual(selection.end)) {
return;
}
if (this.shouldAutoShowJsDocSuggestion(e.textEditor.document, selection.start)) {
return commands.executeCommand('editor.action.triggerSuggest');
}
return;
});
commands.registerCommand(
tryCompleteJsDocCommand,
(file: Uri, position: Position) => this.tryCompleteJsDoc(file, position));
}
public provideCompletionItems(document: TextDocument, position: Position, _token: CancellationToken): ProviderResult<CompletionItem[]> {
const file = this.client.normalizePath(document.uri);
if (file) {
return [new JsDocCompletionItem(document.uri, position)];
}
return [];
}
public resolveCompletionItem(item: CompletionItem, _token: CancellationToken) {
return item;
}
private shouldAutoShowJsDocSuggestion(document: TextDocument, position: Position): boolean {
const line = document.lineAt(position.line).text;
// Ensure line starts with '/**' then cursor
const prefix = line.slice(0, position.character).match(/^\s*(\/\*\*+)\s*$/);
if (prefix === null) {
return false;
}
// Ensure there is no content after the cursor besides possibly the end of the comment
const suffix = line.slice(position.character).match(/^\s*\**\/?$/);
return suffix !== null;
}
/**
* Try to insert a jsdoc comment, using a template provide by typescript
* if possible, otherwise falling back to a default comment format.
*/
private tryCompleteJsDoc(resource: Uri, position: Position): Thenable<boolean> {
const file = this.client.normalizePath(resource);
if (!file) {
return Promise.resolve(false);
}
const editor = window.activeTextEditor;
if (!editor || editor.document.uri.fsPath !== resource.fsPath) {
return Promise.resolve(false);
}
return this.prepForDocCompletion(editor, position)
.then((start: Position) => {
return this.tryInsertJsDocFromTemplate(editor, file, start);
})
.then((didInsertFromTemplate: boolean) => {
if (didInsertFromTemplate) {
return true;
}
return this.tryInsertDefaultDoc(editor, position);
});
}
/**
* Prepare the area around the position for insertion of the jsdoc.
*
* Removes any the prefix and suffix of a possible jsdoc
*/
private prepForDocCompletion(editor: TextEditor, position: Position): Thenable<Position> {
const line = editor.document.lineAt(position.line).text;
const prefix = line.slice(0, position.character).match(/\/\**\s*$/);
const suffix = line.slice(position.character).match(/^\s*\**\//);
if (!prefix && !suffix) {
// Nothing to remove
return Promise.resolve(position);
}
const start = position.translate(0, prefix ? -prefix[0].length : 0);
return editor.edit(
edits => {
edits.delete(new Range(start, position.translate(0, suffix ? suffix[0].length : 0)));
}, {
undoStopBefore: true,
undoStopAfter: false
}).then(() => start);
}
private tryInsertJsDocFromTemplate(editor: TextEditor, file: string, position: Position): Promise<boolean> {
const args: FileLocationRequestArgs = {
file: file,
line: position.line + 1,
offset: position.character + 1
};
return this.client.execute('docCommentTemplate', args)
.then((res: DocCommandTemplateResponse) => {
if (!res || !res.body) {
return false;
}
const commentText = res.body.newText;
return editor.edit(
edits => edits.insert(position, commentText),
{ undoStopBefore: false, undoStopAfter: true });
}, () => false)
.then((didInsertComment: boolean) => {
if (didInsertComment) {
const newCursorPosition = new Position(position.line + 1, editor.document.lineAt(position.line + 1).text.length);
editor.selection = new Selection(newCursorPosition, newCursorPosition);
}
return didInsertComment;
});
}
/**
* Insert the default JSDoc
*/
private tryInsertDefaultDoc(editor: TextEditor, position: Position): Thenable<boolean> {
const line = editor.document.lineAt(position.line).text;
const spaceBefore = line.slice(0, position.character).match(/^\s*$/);
const indent = spaceBefore ? spaceBefore[0] : '';
return editor.edit(
edits => edits.insert(position, `/**\n${indent} * \n${indent} */`),
{ undoStopBefore: false, undoStopAfter: true })
.then((didInsert: boolean) => {
if (didInsert) {
const newCursorPosition = new Position(position.line + 1, editor.document.lineAt(position.line + 1).text.length);
editor.selection = new Selection(newCursorPosition, newCursorPosition);
}
return didInsert;
});
}
}
\ No newline at end of file
......@@ -39,8 +39,8 @@ import CompletionItemProvider from './features/completionItemProvider';
import WorkspaceSymbolProvider from './features/workspaceSymbolProvider';
import CodeActionProvider from './features/codeActionProvider';
import ReferenceCodeLensProvider from './features/referencesCodeLensProvider';
import JsDocCompletionHelper from './features/jsDocCompletionProvider';
import JsDocCompletionHelper from './utils/JsDocCompletionHelper';
import * as BuildStatus from './utils/buildStatus';
import * as ProjectStatus from './utils/projectStatus';
import TypingsStatus, { AtaProgressReporter } from './utils/typingsStatus';
......@@ -69,6 +69,7 @@ export function activate(context: ExtensionContext): void {
const MODE_ID_TSX = 'typescriptreact';
const MODE_ID_JS = 'javascript';
const MODE_ID_JSX = 'javascriptreact';
const selector = [MODE_ID_TS, MODE_ID_TSX, MODE_ID_JS, MODE_ID_JSX];
const clientHost = new TypeScriptServiceClientHost([
{
......@@ -102,19 +103,8 @@ export function activate(context: ExtensionContext): void {
client.onVersionStatusClicked();
}));
const jsDocCompletionHelper = new JsDocCompletionHelper(client);
context.subscriptions.push(commands.registerCommand('_typescript.tryCompleteJsDoc', () => {
const editor = window.activeTextEditor;
if (!editor || !editor.selection.isEmpty) {
return commands.executeCommand('type', { text: '\n' });
}
return jsDocCompletionHelper.tryCompleteJsDoc(editor, editor.selection.active).then(didCompleteComment => {
if (didCompleteComment) {
return;
}
return commands.executeCommand('type', { text: '\n' });
});
}));
context.subscriptions.push(
languages.registerCompletionItemProvider(selector, new JsDocCompletionHelper(client)));
const goToProjectConfig = (isTypeScript: boolean) => {
const editor = window.activeTextEditor;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TextEditor, Position, Range, Selection } from 'vscode';
import { ITypescriptServiceClient } from '../typescriptService';
import { FileLocationRequestArgs, DocCommandTemplateResponse } from '../protocol';
export default class JsDocCompletionHelper {
constructor(
private client: ITypescriptServiceClient,
) { }
public tryCompleteJsDoc(editor: TextEditor, position: Position): Thenable<boolean> {
const file = this.client.normalizePath(editor.document.uri);
if (!file) {
return Promise.resolve(false);
}
const line = editor.document.lineAt(position.line).text;
// Ensure line starts with '/**' then cursor
const prefix = line.slice(0, position.character).match(/^\s*(\/\*\*+)$/);
if (!prefix) {
return Promise.resolve(false);
}
// Ensure there is no content after the cursor besides possibly the end of the comment
const suffix = line.slice(position.character).match(/^\s*\**\/?$/);
if (!suffix) {
return Promise.resolve(false);
}
const start = position.translate(0, -prefix[1].length);
return editor.edit(
edits => {
edits.delete(new Range(start, new Position(start.line, line.length)));
}, {
undoStopBefore: true,
undoStopAfter: false
}
).then(removedComment => {
if (!removedComment) {
// Edit failed, nothing to revert.
return false;
}
const args: FileLocationRequestArgs = {
file: file,
line: start.line + 1,
offset: start.character + 1
};
return Promise.race([
this.client.execute('docCommentTemplate', args),
new Promise((_, reject) => {
setTimeout(reject, 250);
})
]).then((res: DocCommandTemplateResponse) => {
if (!res || !res.body) {
return false;
}
const commentText = res.body.newText;
return editor.edit(
edits => edits.insert(start, commentText),
{ undoStopBefore: false, undoStopAfter: true });
}, () => {
return false;
}).then(didInsertComment => {
if (didInsertComment) {
const newCursorPosition = new Position(start.line + 1, editor.document.lineAt(start.line + 1).text.length);
editor.selection = new Selection(newCursorPosition, newCursorPosition);
return true;
}
// Revert to the original line content and restore position
return editor.edit(
edits => {
edits.insert(start, prefix[1] + suffix[0]);
}, {
undoStopBefore: false,
undoStopAfter: true
}
).then(() => {
editor.selection = new Selection(position, position);
return false;
});
});
});
}
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册