[html] resolve JS completion proposals

This commit is contained in:
Martin Aeschlimann 2016-11-16 16:04:22 +01:00
parent 0abf274748
commit 935027dc19
5 changed files with 128 additions and 7 deletions

View file

@ -54,7 +54,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => {
capabilities: {
// Tell the client that the server works in FULL text document sync mode
textDocumentSync: documents.syncKind,
completionProvider: { resolveProvider: false, triggerCharacters: ['.', ':', '<', '"', '=', '/'] },
completionProvider: { resolveProvider: true, triggerCharacters: ['.', ':', '<', '"', '=', '/'] },
hoverProvider: true,
documentHighlightProvider: true,
documentRangeFormattingProvider: initializationOptions && initializationOptions['format.enable'],
@ -127,6 +127,18 @@ connection.onCompletion(textDocumentPosition => {
return { isIncomplete: true, items: [] };
});
connection.onCompletionResolve(item => {
let data = item.data;
if (data && data.languageId && data.uri) {
let mode = languageModes.getMode(data.languageId);
let document = documents.get(data.uri);
if (mode && mode.doResolve && document) {
return mode.doResolve(document, item);
}
}
return item;
});
connection.onHover(textDocumentPosition => {
let document = documents.get(textDocumentPosition.textDocument.uri);
let mode = languageModes.getModeAtPosition(document, textDocumentPosition.position);

View file

@ -7,8 +7,9 @@
import { LanguageModelCache, getLanguageModelCache } from '../languageModelCache';
import { LanguageService as HTMLLanguageService, HTMLDocument } from 'vscode-html-languageservice';
import { getEmbeddedDocument } from './embeddedSupport';
import { Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types';
import { CompletionItem, Location, SignatureHelp, SignatureInformation, ParameterInformation, Definition, TextEdit, TextDocument, Diagnostic, DiagnosticSeverity, Range, CompletionItemKind, Hover, MarkedString, DocumentHighlight, DocumentHighlightKind, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types';
import { LanguageMode } from './languageModes';
import { getWordAtText } from '../utils/words';
import ts = require('./typescript/typescriptServices');
import { contents as libdts } from './typescript/lib-ts';
@ -19,6 +20,8 @@ const DEFAULT_LIB = {
};
const FILE_NAME = 'typescript://singlefile/1'; // the same 'file' is used for all contents
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
export function getJavascriptMode(htmlLanguageService: HTMLLanguageService, htmlDocuments: LanguageModelCache<HTMLDocument>): LanguageMode {
let compilerOptions = { allowNonTsExtensions: true, allowJs: true, target: ts.ScriptTarget.Latest };
let currentTextDocument: TextDocument;
@ -66,20 +69,41 @@ export function getJavascriptMode(htmlLanguageService: HTMLLanguageService, html
},
doComplete(document: TextDocument, position: Position): CompletionList {
currentTextDocument = jsDocuments.get(document);
let completions = jsLanguageService.getCompletionsAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
let offset = currentTextDocument.offsetAt(position);
let completions = jsLanguageService.getCompletionsAtPosition(FILE_NAME, offset);
if (!completions) {
return { isIncomplete: false, items: [] };
}
let replaceRange = convertRange(currentTextDocument, getWordAtText(currentTextDocument.getText(), offset, JS_WORD_REGEX));
return {
isIncomplete: false,
items: !completions ? [] : completions.entries.map(entry => {
items: completions.entries.map(entry => {
return {
uri: document.uri,
position: position,
label: entry.name,
sortText: entry.sortText,
kind: convertKind(entry.kind)
kind: convertKind(entry.kind),
textEdit: TextEdit.replace(replaceRange, entry.name),
data: { // data used for resolving item details (see 'doResolve')
languageId: 'javascript',
uri: document.uri,
offset: offset
}
};
})
};
},
doResolve(document: TextDocument, item: CompletionItem): CompletionItem {
currentTextDocument = jsDocuments.get(document);
let details = jsLanguageService.getCompletionEntryDetails(FILE_NAME, item.data.offset, item.label);
if (details) {
item.detail = ts.displayPartsToString(details.displayParts);
item.documentation = ts.displayPartsToString(details.documentation);
delete item.data;
}
return item;
},
doHover(document: TextDocument, position: Position): Hover {
currentTextDocument = jsDocuments.get(document);
let info = jsLanguageService.getQuickInfoAtPosition(FILE_NAME, currentTextDocument.offsetAt(position));
@ -238,7 +262,7 @@ function convertOptions(options: FormattingOptions, formatSettings?: any): ts.Fo
IndentSize: options.tabSize,
IndentStyle: ts.IndentStyle.Smart,
NewLineCharacter: '\n',
BaseIndentSize: 1, //
BaseIndentSize: 1, //
InsertSpaceAfterCommaDelimiter: !formatSettings || formatSettings.insertSpaceAfterCommaDelimiter,
InsertSpaceAfterSemicolonInForStatements: !formatSettings || formatSettings.insertSpaceAfterSemicolonInForStatements,
InsertSpaceBeforeAndAfterBinaryOperators: !formatSettings || formatSettings.insertSpaceBeforeAndAfterBinaryOperators,

View file

@ -5,7 +5,10 @@
'use strict';
import { HTMLDocument, getLanguageService as getHTMLLanguageService, DocumentContext } from 'vscode-html-languageservice';
import { Location, SignatureHelp, Definition, TextEdit, TextDocument, Diagnostic, DocumentLink, Range, Hover, DocumentHighlight, CompletionList, Position, FormattingOptions } from 'vscode-languageserver-types';
import {
CompletionItem, Location, SignatureHelp, Definition, TextEdit, TextDocument, Diagnostic, DocumentLink, Range,
Hover, DocumentHighlight, CompletionList, Position, FormattingOptions
} from 'vscode-languageserver-types';
import { getLanguageModelCache } from '../languageModelCache';
import { getLanguageAtPosition, getLanguagesInContent } from './embeddedSupport';
@ -17,6 +20,7 @@ export interface LanguageMode {
configure?: (options: any) => void;
doValidation?: (document: TextDocument) => Diagnostic[];
doComplete?: (document: TextDocument, position: Position) => CompletionList;
doResolve?: (document: TextDocument, item: CompletionItem) => CompletionItem;
doHover?: (document: TextDocument, position: Position) => Hover;
doSignatureHelp?: (document: TextDocument, position: Position) => SignatureHelp;
findDocumentHighlight?: (document: TextDocument, position: Position) => DocumentHighlight[];

View file

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* 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 * as assert from 'assert';
import * as words from '../utils/words';
suite('Words', () => {
let wordRegex = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
function assertWord(value: string, expected: string): void {
let offset = value.indexOf('|');
value = value.substr(0, offset) + value.substr(offset + 1);
let actualRange = words.getWordAtText(value, offset, wordRegex);
assert(actualRange.start <= offset);
assert(actualRange.start + actualRange.length >= offset);
assert.equal(value.substr(actualRange.start, actualRange.length), expected);
}
test('Basic', function (): any {
assertWord('|var x1 = new F<A>(a, b);', 'var');
assertWord('v|ar x1 = new F<A>(a, b);', 'var');
assertWord('var| x1 = new F<A>(a, b);', 'var');
assertWord('var |x1 = new F<A>(a, b);', 'x1');
assertWord('var x1| = new F<A>(a, b);', 'x1');
assertWord('var x1 = new |F<A>(a, b);', 'F');
assertWord('var x1 = new F<|A>(a, b);', 'A');
assertWord('var x1 = new F<A>(|a, b);', 'a');
assertWord('var x1 = new F<A>(a, b|);', 'b');
assertWord('var x1 = new F<A>(a, b)|;', '');
assertWord('var x1 = new F<A>(a, b)|;|', '');
assertWord('var x1 = | new F<A>(a, b)|;|', '');
});
test('Multiline', function (): any {
assertWord('console.log("hello");\n|var x1 = new F<A>(a, b);', 'var');
assertWord('console.log("hello");\n|\nvar x1 = new F<A>(a, b);', '');
assertWord('console.log("hello");\n\r |var x1 = new F<A>(a, b);', 'var');
});
});

View file

@ -0,0 +1,35 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export function getWordAtText(text: string, offset: number, wordDefinition: RegExp): { start: number, length: number } {
let lineStart = offset;
while (lineStart > 0 && !isNewlineCharacter(text.charCodeAt(lineStart - 1))) {
lineStart--;
}
let offsetInLine = offset - lineStart;
let lineText = text.substr(lineStart);
// make a copy of the regex as to not keep the state
let flags = wordDefinition.ignoreCase ? 'gi' : 'g';
wordDefinition = new RegExp(wordDefinition.source, flags);
let match = wordDefinition.exec(lineText);
while (match && match.index + match[0].length < offsetInLine) {
match = wordDefinition.exec(lineText);
}
if (match && match.index <= offsetInLine) {
return { start: match.index + lineStart, length: match[0].length };
}
return { start: offset, length: 0 };
}
const CR = '\r'.charCodeAt(0);
const NL = '\n'.charCodeAt(0);
function isNewlineCharacter(charCode: number) {
return charCode === CR || charCode === NL;
}