mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-26 03:29:00 +01:00
HTML code completion
This commit is contained in:
@@ -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
|
||||
};
|
||||
}
|
||||
166
extensions/html/server/src/service/services/htmlCompletion.ts
Normal file
166
extensions/html/server/src/service/services/htmlCompletion.ts
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user