HTML code completion

This commit is contained in:
Martin Aeschlimann 2016-08-25 15:46:59 +02:00
parent be7e03f74f
commit 4e181e8fd2
3 changed files with 203 additions and 29 deletions

View file

@ -35,7 +35,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: true, triggerCharacters: ['"', ':'] },
completionProvider: { resolveProvider: false, triggerCharacters: ['"', ':'] },
hoverProvider: true,
documentSymbolProvider: true,
documentRangeFormattingProvider: true,
@ -122,31 +122,31 @@ connection.onCompletion(textDocumentPosition => {
return languageService.doComplete(document, textDocumentPosition.position, htmlDocument);
});
connection.onCompletionResolve(completionItem => {
return languageService.doResolve(completionItem);
});
// connection.onCompletionResolve(completionItem => {
// return languageService.doResolve(completionItem);
// });
connection.onHover(textDocumentPositionParams => {
let document = documents.get(textDocumentPositionParams.textDocument.uri);
let htmlDocument = getHTMLDocument(document);
return languageService.doHover(document, textDocumentPositionParams.position, htmlDocument);
});
// connection.onHover(textDocumentPositionParams => {
// let document = documents.get(textDocumentPositionParams.textDocument.uri);
// let htmlDocument = getHTMLDocument(document);
// return languageService.doHover(document, textDocumentPositionParams.position, htmlDocument);
// });
connection.onDocumentSymbol(documentSymbolParams => {
let document = documents.get(documentSymbolParams.textDocument.uri);
let htmlDocument = getHTMLDocument(document);
return languageService.findDocumentSymbols(document, htmlDocument);
});
// connection.onDocumentSymbol(documentSymbolParams => {
// let document = documents.get(documentSymbolParams.textDocument.uri);
// let htmlDocument = getHTMLDocument(document);
// return languageService.findDocumentSymbols(document, htmlDocument);
// });
connection.onDocumentFormatting(formatParams => {
let document = documents.get(formatParams.textDocument.uri);
return languageService.format(document, null, formatParams.options);
});
// connection.onDocumentFormatting(formatParams => {
// let document = documents.get(formatParams.textDocument.uri);
// return languageService.format(document, null, formatParams.options);
// });
connection.onDocumentRangeFormatting(formatParams => {
let document = documents.get(formatParams.textDocument.uri);
return languageService.format(document, formatParams.range, formatParams.options);
});
// connection.onDocumentRangeFormatting(formatParams => {
// let document = documents.get(formatParams.textDocument.uri);
// return languageService.format(document, formatParams.range, formatParams.options);
// });
// Listen on the connection
connection.listen();

View file

@ -3,7 +3,8 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { parse} from './parser/htmlParser';
import { doComplete } from './services/htmlCompletion';
import { TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, MarkedString } from 'vscode-languageserver-types';
export { TextDocument, Position, CompletionItem, CompletionList, Hover, Range, SymbolInformation, Diagnostic, TextEdit, FormattingOptions, MarkedString };
@ -29,15 +30,22 @@ export declare type HTMLDocument = {};
export interface LanguageService {
configure(settings: LanguageSettings): void;
doValidation(document: TextDocument, htmlDocument: HTMLDocument): Diagnostic[];
parseHTMLDocument(document: TextDocument): HTMLDocument;
doResolve(item: CompletionItem): CompletionItem;
doValidation(document: TextDocument, htmlDocument: HTMLDocument): Diagnostic[];
// doResolve(item: CompletionItem): CompletionItem;
doComplete(document: TextDocument, position: Position, doc: HTMLDocument): CompletionList;
findDocumentSymbols(document: TextDocument, doc: HTMLDocument): SymbolInformation[];
doHover(document: TextDocument, position: Position, doc: HTMLDocument): Hover;
format(document: TextDocument, range: Range, options: FormattingOptions): TextEdit[];
// findDocumentSymbols(document: TextDocument, doc: HTMLDocument): SymbolInformation[];
// doHover(document: TextDocument, position: Position, doc: HTMLDocument): Hover;
// format(document: TextDocument, range: Range, options: FormattingOptions): TextEdit[];
}
export function getLanguageService() : LanguageService {
return null;
return {
doValidation: (document, htmlDocument) => { return []; },
configure: (settings) => {},
parseHTMLDocument: (document) => parse(document.getText()),
doComplete
};
}

View file

@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* 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 { TextDocument, Position, CompletionList, CompletionItemKind, Range } from 'vscode-languageserver-types';
import { HTMLDocument } from '../parser/htmlParser';
import { TokenType, createScanner, ScannerState } from '../parser/htmlScanner';
import { IHTMLTagProvider, getHTML5TagProvider, getAngularTagProvider, getIonicTagProvider } from '../parser/htmlTags';
import { startsWith } from '../utils/strings';
let tagProviders: IHTMLTagProvider[];
tagProviders.push(getHTML5TagProvider());
tagProviders.push(getAngularTagProvider());
tagProviders.push(getIonicTagProvider());
export function doComplete(document: TextDocument, position: Position, doc: HTMLDocument): CompletionList {
let result: CompletionList = {
isIncomplete: false,
items: []
};
let offset = document.offsetAt(position);
let node = doc.findNodeBefore(offset);
if (!node) {
return result;
}
let scanner = createScanner(document.getText(), node.start);
let currentTag: string;
let currentAttributeName: string;
function collectOpenTagSuggestions(afterOpenBracket: number) : CompletionList {
let range : Range = { start: document.positionAt(afterOpenBracket), end: document.positionAt(offset)};
tagProviders.forEach((provider) => {
provider.collectTags((tag, label) => {
result.items.push({
label: tag,
kind: CompletionItemKind.Property,
documentation: label,
textEdit: { newText: tag, range: range }
});
});
});
return result;
}
function collectCloseTagSuggestions(afterOpenBracket: number) : CompletionList {
let range : Range = { start: document.positionAt(afterOpenBracket), end: document.positionAt(offset)};
let contentAfter = document.getText().substr(offset);
let closeTag = isWhiteSpace(contentAfter) || startsWith(contentAfter, '<') ? '>' : '';
tagProviders.forEach((provider) => {
provider.collectTags((tag, label) => {
result.items.push({
label: '/' + tag,
kind: CompletionItemKind.Property,
documentation: label,
filterText: '/' + tag + closeTag,
textEdit: { newText: '/' + tag + closeTag, range: range }
});
});
});
return result;
}
function collectTagSuggestions(tagStart: number) : CompletionList {
collectOpenTagSuggestions(tagStart);
collectCloseTagSuggestions(tagStart);
return result;
}
function collectAttributeNameSuggestions(nameStart: number) : CompletionList {
let range : Range = { start: document.positionAt(nameStart), end: document.positionAt(offset)};
tagProviders.forEach((provider) => {
provider.collectAttributes(currentTag, (attribute, type) => {
let codeSnippet = attribute;
if (type !== 'v') {
codeSnippet = codeSnippet + '="{{}}"';
}
result.items.push({
label: attribute,
kind: type === 'handler' ? CompletionItemKind.Function : CompletionItemKind.Value,
textEdit: { newText: codeSnippet, range: range }
});
});
});
return result;
}
function collectAttributeValueSuggestions(valueStart: number) : CompletionList {
let range : Range = { start: document.positionAt(valueStart), end: document.positionAt(offset)};
tagProviders.forEach((provider) => {
provider.collectValues(currentTag, currentAttributeName, (value) => {
let codeSnippet = '"' + value + '"';
result.items.push({
label: value,
filterText: codeSnippet,
kind: CompletionItemKind.Unit,
textEdit: { newText: codeSnippet, range: range }
});
});
});
return result;
}
let token = scanner.scan();
while (token !== TokenType.EOS && scanner.getTokenOffset() <= offset) {
switch (token) {
case TokenType.StartTagOpen:
if (scanner.getTokenEnd() === offset) {
return collectTagSuggestions(offset);
}
break;
case TokenType.StartTag:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectOpenTagSuggestions(scanner.getTokenOffset());
}
currentTag = scanner.getTokenText();
break;
case TokenType.AttributeName:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectAttributeNameSuggestions(scanner.getTokenOffset());
}
currentAttributeName = scanner.getTokenText();
break;
case TokenType.DelimiterAssign:
if (scanner.getTokenEnd() === offset) {
return collectAttributeValueSuggestions(scanner.getTokenEnd());
}
break;
case TokenType.AttributeValue:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectAttributeValueSuggestions(scanner.getTokenEnd());
}
break;
case TokenType.Whitespace:
case TokenType.StartTagClose:
case TokenType.StartTagSelfClose:
if (offset <= scanner.getTokenOffset()) {
switch (scanner.getScannerState()) {
case ScannerState.WithinTag:
case ScannerState.AfterAttributeName:
return collectAttributeNameSuggestions(scanner.getTokenOffset());
case ScannerState.BeforeAttributeValue:
return collectAttributeValueSuggestions(scanner.getTokenOffset());
}
}
break;
case TokenType.EndTagOpen:
if (offset <= scanner.getTokenEnd()) {
return collectCloseTagSuggestions(scanner.getTokenOffset() + 1);
}
break;
}
}
return null;
}
function isWhiteSpace(s:string) : boolean {
return /^\s*$/.test(s);
}