🎉 initiate project *astro_rewrite*

This commit is contained in:
sindrekjelsrud 2023-07-19 21:31:30 +02:00
parent ffd4d5e86c
commit 2ba37bfbe3
8658 changed files with 2268794 additions and 2538 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,11 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
* Mock for the JS formatter. Ignore formatting of JS content in HTML.
*/
export function js_beautify(js_source_text, options) {
// no formatting
return js_source_text;
}

View file

@ -0,0 +1,29 @@
import { Scanner, HTMLDocument, CompletionConfiguration, ICompletionParticipant, HTMLFormatConfiguration, DocumentContext, IHTMLDataProvider, HTMLDataV1, LanguageServiceOptions, TextDocument, SelectionRange, WorkspaceEdit, Position, CompletionList, Hover, Range, SymbolInformation, TextEdit, DocumentHighlight, DocumentLink, FoldingRange, HoverSettings } from './htmlLanguageTypes';
export * from './htmlLanguageTypes';
export interface LanguageService {
setDataProviders(useDefaultDataProvider: boolean, customDataProviders: IHTMLDataProvider[]): void;
createScanner(input: string, initialOffset?: number): Scanner;
parseHTMLDocument(document: TextDocument): HTMLDocument;
findDocumentHighlights(document: TextDocument, position: Position, htmlDocument: HTMLDocument): DocumentHighlight[];
doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): CompletionList;
doComplete2(document: TextDocument, position: Position, htmlDocument: HTMLDocument, documentContext: DocumentContext, options?: CompletionConfiguration): Promise<CompletionList>;
setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]): void;
doHover(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: HoverSettings): Hover | null;
format(document: TextDocument, range: Range | undefined, options: HTMLFormatConfiguration): TextEdit[];
findDocumentLinks(document: TextDocument, documentContext: DocumentContext): DocumentLink[];
findDocumentSymbols(document: TextDocument, htmlDocument: HTMLDocument): SymbolInformation[];
doQuoteComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): string | null;
doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): string | null;
getFoldingRanges(document: TextDocument, context?: {
rangeLimit?: number;
}): FoldingRange[];
getSelectionRanges(document: TextDocument, positions: Position[]): SelectionRange[];
doRename(document: TextDocument, position: Position, newName: string, htmlDocument: HTMLDocument): WorkspaceEdit | null;
findMatchingTagPosition(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Position | null;
/** Deprecated, Use findLinkedEditingRanges instead */
findOnTypeRenameRanges(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Range[] | null;
findLinkedEditingRanges(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Range[] | null;
}
export declare function getLanguageService(options?: LanguageServiceOptions): LanguageService;
export declare function newHTMLDataProvider(id: string, customData: HTMLDataV1): IHTMLDataProvider;
export declare function getDefaultHTMLDataProvider(): IHTMLDataProvider;

View file

@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner } from './parser/htmlScanner';
import { HTMLParser } from './parser/htmlParser';
import { HTMLCompletion } from './services/htmlCompletion';
import { HTMLHover } from './services/htmlHover';
import { format } from './services/htmlFormatter';
import { HTMLDocumentLinks } from './services/htmlLinks';
import { findDocumentHighlights } from './services/htmlHighlighting';
import { findDocumentSymbols } from './services/htmlSymbolsProvider';
import { doRename } from './services/htmlRename';
import { findMatchingTagPosition } from './services/htmlMatchingTagPosition';
import { findLinkedEditingRanges } from './services/htmlLinkedEditing';
import { HTMLFolding } from './services/htmlFolding';
import { HTMLSelectionRange } from './services/htmlSelectionRange';
import { HTMLDataProvider } from './languageFacts/dataProvider';
import { HTMLDataManager } from './languageFacts/dataManager';
import { htmlData } from './languageFacts/data/webCustomData';
export * from './htmlLanguageTypes';
const defaultLanguageServiceOptions = {};
export function getLanguageService(options = defaultLanguageServiceOptions) {
const dataManager = new HTMLDataManager(options);
const htmlHover = new HTMLHover(options, dataManager);
const htmlCompletion = new HTMLCompletion(options, dataManager);
const htmlParser = new HTMLParser(dataManager);
const htmlSelectionRange = new HTMLSelectionRange(htmlParser);
const htmlFolding = new HTMLFolding(dataManager);
const htmlDocumentLinks = new HTMLDocumentLinks(dataManager);
return {
setDataProviders: dataManager.setDataProviders.bind(dataManager),
createScanner,
parseHTMLDocument: htmlParser.parseDocument.bind(htmlParser),
doComplete: htmlCompletion.doComplete.bind(htmlCompletion),
doComplete2: htmlCompletion.doComplete2.bind(htmlCompletion),
setCompletionParticipants: htmlCompletion.setCompletionParticipants.bind(htmlCompletion),
doHover: htmlHover.doHover.bind(htmlHover),
format,
findDocumentHighlights,
findDocumentLinks: htmlDocumentLinks.findDocumentLinks.bind(htmlDocumentLinks),
findDocumentSymbols,
getFoldingRanges: htmlFolding.getFoldingRanges.bind(htmlFolding),
getSelectionRanges: htmlSelectionRange.getSelectionRanges.bind(htmlSelectionRange),
doQuoteComplete: htmlCompletion.doQuoteComplete.bind(htmlCompletion),
doTagComplete: htmlCompletion.doTagComplete.bind(htmlCompletion),
doRename,
findMatchingTagPosition,
findOnTypeRenameRanges: findLinkedEditingRanges,
findLinkedEditingRanges
};
}
export function newHTMLDataProvider(id, customData) {
return new HTMLDataProvider(id, customData);
}
export function getDefaultHTMLDataProvider() {
return newHTMLDataProvider('default', htmlData);
}

View file

@ -0,0 +1,256 @@
import { Position, Range, Location, MarkupContent, MarkupKind, MarkedString, DocumentUri, SelectionRange, WorkspaceEdit, CompletionList, CompletionItemKind, CompletionItem, CompletionItemTag, InsertTextMode, Command, SymbolInformation, SymbolKind, Hover, TextEdit, InsertReplaceEdit, InsertTextFormat, DocumentHighlight, DocumentHighlightKind, DocumentLink, FoldingRange, FoldingRangeKind, SignatureHelp, Definition, Diagnostic, FormattingOptions, Color, ColorInformation, ColorPresentation } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
export { TextDocument, Position, Range, Location, MarkupContent, MarkupKind, MarkedString, DocumentUri, SelectionRange, WorkspaceEdit, CompletionList, CompletionItemKind, CompletionItem, CompletionItemTag, InsertTextMode, Command, SymbolInformation, SymbolKind, Hover, TextEdit, InsertReplaceEdit, InsertTextFormat, DocumentHighlight, DocumentHighlightKind, DocumentLink, FoldingRange, FoldingRangeKind, SignatureHelp, Definition, Diagnostic, FormattingOptions, Color, ColorInformation, ColorPresentation };
export interface HTMLFormatConfiguration {
tabSize?: number;
insertSpaces?: boolean;
indentEmptyLines?: boolean;
wrapLineLength?: number;
unformatted?: string;
contentUnformatted?: string;
indentInnerHtml?: boolean;
wrapAttributes?: 'auto' | 'force' | 'force-aligned' | 'force-expand-multiline' | 'aligned-multiple' | 'preserve' | 'preserve-aligned';
wrapAttributesIndentSize?: number;
preserveNewLines?: boolean;
maxPreserveNewLines?: number;
indentHandlebars?: boolean;
endWithNewline?: boolean;
extraLiners?: string;
indentScripts?: 'keep' | 'separate' | 'normal';
templating?: boolean;
unformattedContentDelimiter?: string;
}
export interface HoverSettings {
documentation?: boolean;
references?: boolean;
}
export interface CompletionConfiguration {
[provider: string]: boolean | undefined | string;
hideAutoCompleteProposals?: boolean;
attributeDefaultValue?: 'empty' | 'singlequotes' | 'doublequotes';
}
export interface Node {
tag: string | undefined;
start: number;
startTagEnd: number | undefined;
end: number;
endTagStart: number | undefined;
children: Node[];
parent?: Node;
attributes?: {
[name: string]: string | null;
} | undefined;
}
export declare enum TokenType {
StartCommentTag = 0,
Comment = 1,
EndCommentTag = 2,
StartTagOpen = 3,
StartTagClose = 4,
StartTagSelfClose = 5,
StartTag = 6,
EndTagOpen = 7,
EndTagClose = 8,
EndTag = 9,
DelimiterAssign = 10,
AttributeName = 11,
AttributeValue = 12,
StartDoctypeTag = 13,
Doctype = 14,
EndDoctypeTag = 15,
Content = 16,
Whitespace = 17,
Unknown = 18,
Script = 19,
Styles = 20,
EOS = 21
}
export declare enum ScannerState {
WithinContent = 0,
AfterOpeningStartTag = 1,
AfterOpeningEndTag = 2,
WithinDoctype = 3,
WithinTag = 4,
WithinEndTag = 5,
WithinComment = 6,
WithinScriptContent = 7,
WithinStyleContent = 8,
AfterAttributeName = 9,
BeforeAttributeValue = 10
}
export interface Scanner {
scan(): TokenType;
getTokenType(): TokenType;
getTokenOffset(): number;
getTokenLength(): number;
getTokenEnd(): number;
getTokenText(): string;
getTokenError(): string | undefined;
getScannerState(): ScannerState;
}
export declare type HTMLDocument = {
roots: Node[];
findNodeBefore(offset: number): Node;
findNodeAt(offset: number): Node;
};
export interface DocumentContext {
resolveReference(ref: string, base: string): string | undefined;
}
export interface HtmlAttributeValueContext {
document: TextDocument;
position: Position;
tag: string;
attribute: string;
value: string;
range: Range;
}
export interface HtmlContentContext {
document: TextDocument;
position: Position;
}
export interface ICompletionParticipant {
onHtmlAttributeValue?: (context: HtmlAttributeValueContext) => void;
onHtmlContent?: (context: HtmlContentContext) => void;
}
export interface IReference {
name: string;
url: string;
}
export interface ITagData {
name: string;
description?: string | MarkupContent;
attributes: IAttributeData[];
references?: IReference[];
void?: boolean;
}
export interface IAttributeData {
name: string;
description?: string | MarkupContent;
valueSet?: string;
values?: IValueData[];
references?: IReference[];
}
export interface IValueData {
name: string;
description?: string | MarkupContent;
references?: IReference[];
}
export interface IValueSet {
name: string;
values: IValueData[];
}
export interface HTMLDataV1 {
version: 1 | 1.1;
tags?: ITagData[];
globalAttributes?: IAttributeData[];
valueSets?: IValueSet[];
}
export interface IHTMLDataProvider {
getId(): string;
isApplicable(languageId: string): boolean;
provideTags(): ITagData[];
provideAttributes(tag: string): IAttributeData[];
provideValues(tag: string, attribute: string): IValueData[];
}
/**
* Describes what LSP capabilities the client supports
*/
export interface ClientCapabilities {
/**
* The text document client capabilities
*/
textDocument?: {
/**
* Capabilities specific to completions.
*/
completion?: {
/**
* The client supports the following `CompletionItem` specific
* capabilities.
*/
completionItem?: {
/**
* Client supports the follow content formats for the documentation
* property. The order describes the preferred format of the client.
*/
documentationFormat?: MarkupKind[];
};
};
/**
* Capabilities specific to hovers.
*/
hover?: {
/**
* Client supports the follow content formats for the content
* property. The order describes the preferred format of the client.
*/
contentFormat?: MarkupKind[];
};
};
}
export declare namespace ClientCapabilities {
const LATEST: ClientCapabilities;
}
export interface LanguageServiceOptions {
/**
* Unless set to false, the default HTML data provider will be used
* along with the providers from customDataProviders.
* Defaults to true.
*/
useDefaultDataProvider?: boolean;
/**
* Provide data that could enhance the service's understanding of
* HTML tag / attribute / attribute-value
*/
customDataProviders?: IHTMLDataProvider[];
/**
* Abstract file system access away from the service.
* Used for path completion, etc.
*/
fileSystemProvider?: FileSystemProvider;
/**
* Describes the LSP capabilities the client supports.
*/
clientCapabilities?: ClientCapabilities;
}
export declare enum FileType {
/**
* The file type is unknown.
*/
Unknown = 0,
/**
* A regular file.
*/
File = 1,
/**
* A directory.
*/
Directory = 2,
/**
* A symbolic link to a file.
*/
SymbolicLink = 64
}
export interface FileStat {
/**
* The type of the file, e.g. is a regular file, a directory, or symbolic link
* to a file.
*/
type: FileType;
/**
* The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
ctime: number;
/**
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
mtime: number;
/**
* The size in bytes.
*/
size: number;
}
export interface FileSystemProvider {
stat(uri: DocumentUri): Promise<FileStat>;
readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>;
}

View file

@ -0,0 +1,80 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position, Range, Location, MarkupContent, MarkupKind, MarkedString, DocumentUri, SelectionRange, WorkspaceEdit, CompletionList, CompletionItemKind, CompletionItem, CompletionItemTag, InsertTextMode, Command, SymbolInformation, SymbolKind, Hover, TextEdit, InsertReplaceEdit, InsertTextFormat, DocumentHighlight, DocumentHighlightKind, DocumentLink, FoldingRange, FoldingRangeKind, Diagnostic, FormattingOptions, Color, ColorInformation, ColorPresentation } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
export { TextDocument, Position, Range, Location, MarkupContent, MarkupKind, MarkedString, DocumentUri, SelectionRange, WorkspaceEdit, CompletionList, CompletionItemKind, CompletionItem, CompletionItemTag, InsertTextMode, Command, SymbolInformation, SymbolKind, Hover, TextEdit, InsertReplaceEdit, InsertTextFormat, DocumentHighlight, DocumentHighlightKind, DocumentLink, FoldingRange, FoldingRangeKind, Diagnostic, FormattingOptions, Color, ColorInformation, ColorPresentation };
export var TokenType;
(function (TokenType) {
TokenType[TokenType["StartCommentTag"] = 0] = "StartCommentTag";
TokenType[TokenType["Comment"] = 1] = "Comment";
TokenType[TokenType["EndCommentTag"] = 2] = "EndCommentTag";
TokenType[TokenType["StartTagOpen"] = 3] = "StartTagOpen";
TokenType[TokenType["StartTagClose"] = 4] = "StartTagClose";
TokenType[TokenType["StartTagSelfClose"] = 5] = "StartTagSelfClose";
TokenType[TokenType["StartTag"] = 6] = "StartTag";
TokenType[TokenType["EndTagOpen"] = 7] = "EndTagOpen";
TokenType[TokenType["EndTagClose"] = 8] = "EndTagClose";
TokenType[TokenType["EndTag"] = 9] = "EndTag";
TokenType[TokenType["DelimiterAssign"] = 10] = "DelimiterAssign";
TokenType[TokenType["AttributeName"] = 11] = "AttributeName";
TokenType[TokenType["AttributeValue"] = 12] = "AttributeValue";
TokenType[TokenType["StartDoctypeTag"] = 13] = "StartDoctypeTag";
TokenType[TokenType["Doctype"] = 14] = "Doctype";
TokenType[TokenType["EndDoctypeTag"] = 15] = "EndDoctypeTag";
TokenType[TokenType["Content"] = 16] = "Content";
TokenType[TokenType["Whitespace"] = 17] = "Whitespace";
TokenType[TokenType["Unknown"] = 18] = "Unknown";
TokenType[TokenType["Script"] = 19] = "Script";
TokenType[TokenType["Styles"] = 20] = "Styles";
TokenType[TokenType["EOS"] = 21] = "EOS";
})(TokenType || (TokenType = {}));
export var ScannerState;
(function (ScannerState) {
ScannerState[ScannerState["WithinContent"] = 0] = "WithinContent";
ScannerState[ScannerState["AfterOpeningStartTag"] = 1] = "AfterOpeningStartTag";
ScannerState[ScannerState["AfterOpeningEndTag"] = 2] = "AfterOpeningEndTag";
ScannerState[ScannerState["WithinDoctype"] = 3] = "WithinDoctype";
ScannerState[ScannerState["WithinTag"] = 4] = "WithinTag";
ScannerState[ScannerState["WithinEndTag"] = 5] = "WithinEndTag";
ScannerState[ScannerState["WithinComment"] = 6] = "WithinComment";
ScannerState[ScannerState["WithinScriptContent"] = 7] = "WithinScriptContent";
ScannerState[ScannerState["WithinStyleContent"] = 8] = "WithinStyleContent";
ScannerState[ScannerState["AfterAttributeName"] = 9] = "AfterAttributeName";
ScannerState[ScannerState["BeforeAttributeValue"] = 10] = "BeforeAttributeValue";
})(ScannerState || (ScannerState = {}));
export var ClientCapabilities;
(function (ClientCapabilities) {
ClientCapabilities.LATEST = {
textDocument: {
completion: {
completionItem: {
documentationFormat: [MarkupKind.Markdown, MarkupKind.PlainText]
}
},
hover: {
contentFormat: [MarkupKind.Markdown, MarkupKind.PlainText]
}
}
};
})(ClientCapabilities || (ClientCapabilities = {}));
export var FileType;
(function (FileType) {
/**
* The file type is unknown.
*/
FileType[FileType["Unknown"] = 0] = "Unknown";
/**
* A regular file.
*/
FileType[FileType["File"] = 1] = "File";
/**
* A directory.
*/
FileType[FileType["Directory"] = 2] = "Directory";
/**
* A symbolic link to a file.
*/
FileType[FileType["SymbolicLink"] = 64] = "SymbolicLink";
})(FileType || (FileType = {}));

View file

@ -0,0 +1,2 @@
import { HTMLDataV1 } from '../../htmlLanguageTypes';
export declare const htmlData: HTMLDataV1;

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { HTMLDataProvider } from './dataProvider';
import { htmlData } from './data/webCustomData';
import * as arrays from '../utils/arrays';
export class HTMLDataManager {
constructor(options) {
this.dataProviders = [];
this.setDataProviders(options.useDefaultDataProvider !== false, options.customDataProviders || []);
}
setDataProviders(builtIn, providers) {
this.dataProviders = [];
if (builtIn) {
this.dataProviders.push(new HTMLDataProvider('html5', htmlData));
}
this.dataProviders.push(...providers);
}
getDataProviders() {
return this.dataProviders;
}
isVoidElement(e, voidElements) {
return !!e && arrays.binarySearch(voidElements, e.toLowerCase(), (s1, s2) => s1.localeCompare(s2)) >= 0;
}
getVoidElements(languageOrProviders) {
const dataProviders = Array.isArray(languageOrProviders) ? languageOrProviders : this.getDataProviders().filter(p => p.isApplicable(languageOrProviders));
const voidTags = [];
dataProviders.forEach((provider) => {
provider.provideTags().filter(tag => tag.void).forEach(tag => voidTags.push(tag.name));
});
return voidTags.sort();
}
isPathAttribute(tag, attr) {
// should eventually come from custom data
if (attr === 'src' || attr === 'href') {
return true;
}
const a = PATH_TAG_AND_ATTR[tag];
if (a) {
if (typeof a === 'string') {
return a === attr;
}
else {
return a.indexOf(attr) !== -1;
}
}
return false;
}
}
// Selected from https://stackoverflow.com/a/2725168/1780148
const PATH_TAG_AND_ATTR = {
// HTML 4
a: 'href',
area: 'href',
body: 'background',
blockquote: 'cite',
del: 'cite',
form: 'action',
frame: ['src', 'longdesc'],
img: ['src', 'longdesc'],
ins: 'cite',
link: 'href',
object: 'data',
q: 'cite',
script: 'src',
// HTML 5
audio: 'src',
button: 'formaction',
command: 'icon',
embed: 'src',
html: 'manifest',
input: ['src', 'formaction'],
source: 'src',
track: 'src',
video: ['src', 'poster']
};

View file

@ -0,0 +1,112 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { normalizeMarkupContent } from '../utils/markup';
export class HTMLDataProvider {
isApplicable() {
return true;
}
/**
* Currently, unversioned data uses the V1 implementation
* In the future when the provider handles multiple versions of HTML custom data,
* use the latest implementation for unversioned data
*/
constructor(id, customData) {
this.id = id;
this._tags = [];
this._tagMap = {};
this._valueSetMap = {};
this._tags = customData.tags || [];
this._globalAttributes = customData.globalAttributes || [];
this._tags.forEach(t => {
this._tagMap[t.name.toLowerCase()] = t;
});
if (customData.valueSets) {
customData.valueSets.forEach(vs => {
this._valueSetMap[vs.name] = vs.values;
});
}
}
getId() {
return this.id;
}
provideTags() {
return this._tags;
}
provideAttributes(tag) {
const attributes = [];
const processAttribute = (a) => {
attributes.push(a);
};
const tagEntry = this._tagMap[tag.toLowerCase()];
if (tagEntry) {
tagEntry.attributes.forEach(processAttribute);
}
this._globalAttributes.forEach(processAttribute);
return attributes;
}
provideValues(tag, attribute) {
const values = [];
attribute = attribute.toLowerCase();
const processAttributes = (attributes) => {
attributes.forEach(a => {
if (a.name.toLowerCase() === attribute) {
if (a.values) {
a.values.forEach(v => {
values.push(v);
});
}
if (a.valueSet) {
if (this._valueSetMap[a.valueSet]) {
this._valueSetMap[a.valueSet].forEach(v => {
values.push(v);
});
}
}
}
});
};
const tagEntry = this._tagMap[tag.toLowerCase()];
if (tagEntry) {
processAttributes(tagEntry.attributes);
}
processAttributes(this._globalAttributes);
return values;
}
}
/**
* Generate Documentation used in hover/complete
* From `documentation` and `references`
*/
export function generateDocumentation(item, settings = {}, doesSupportMarkdown) {
const result = {
kind: doesSupportMarkdown ? 'markdown' : 'plaintext',
value: ''
};
if (item.description && settings.documentation !== false) {
const normalizedDescription = normalizeMarkupContent(item.description);
if (normalizedDescription) {
result.value += normalizedDescription.value;
}
}
if (item.references && item.references.length > 0 && settings.references !== false) {
if (result.value.length) {
result.value += `\n\n`;
}
if (doesSupportMarkdown) {
result.value += item.references.map(r => {
return `[${r.name}](${r.url})`;
}).join(' | ');
}
else {
result.value += item.references.map(r => {
return `${r.name}: ${r.url}`;
}).join('\n');
}
}
if (result.value === '') {
return undefined;
}
return result;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,161 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner } from './htmlScanner';
import { findFirst } from '../utils/arrays';
import { TokenType } from '../htmlLanguageTypes';
export class Node {
get attributeNames() { return this.attributes ? Object.keys(this.attributes) : []; }
constructor(start, end, children, parent) {
this.start = start;
this.end = end;
this.children = children;
this.parent = parent;
this.closed = false;
}
isSameTag(tagInLowerCase) {
if (this.tag === undefined) {
return tagInLowerCase === undefined;
}
else {
return tagInLowerCase !== undefined && this.tag.length === tagInLowerCase.length && this.tag.toLowerCase() === tagInLowerCase;
}
}
get firstChild() { return this.children[0]; }
get lastChild() { return this.children.length ? this.children[this.children.length - 1] : void 0; }
findNodeBefore(offset) {
const idx = findFirst(this.children, c => offset <= c.start) - 1;
if (idx >= 0) {
const child = this.children[idx];
if (offset > child.start) {
if (offset < child.end) {
return child.findNodeBefore(offset);
}
const lastChild = child.lastChild;
if (lastChild && lastChild.end === child.end) {
return child.findNodeBefore(offset);
}
return child;
}
}
return this;
}
findNodeAt(offset) {
const idx = findFirst(this.children, c => offset <= c.start) - 1;
if (idx >= 0) {
const child = this.children[idx];
if (offset > child.start && offset <= child.end) {
return child.findNodeAt(offset);
}
}
return this;
}
}
export class HTMLParser {
constructor(dataManager) {
this.dataManager = dataManager;
}
parseDocument(document) {
return this.parse(document.getText(), this.dataManager.getVoidElements(document.languageId));
}
parse(text, voidElements) {
const scanner = createScanner(text, undefined, undefined, true);
const htmlDocument = new Node(0, text.length, [], void 0);
let curr = htmlDocument;
let endTagStart = -1;
let endTagName = undefined;
let pendingAttribute = null;
let token = scanner.scan();
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTagOpen:
const child = new Node(scanner.getTokenOffset(), text.length, [], curr);
curr.children.push(child);
curr = child;
break;
case TokenType.StartTag:
curr.tag = scanner.getTokenText();
break;
case TokenType.StartTagClose:
if (curr.parent) {
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
if (scanner.getTokenLength()) {
curr.startTagEnd = scanner.getTokenEnd();
if (curr.tag && this.dataManager.isVoidElement(curr.tag, voidElements)) {
curr.closed = true;
curr = curr.parent;
}
}
else {
// pseudo close token from an incomplete start tag
curr = curr.parent;
}
}
break;
case TokenType.StartTagSelfClose:
if (curr.parent) {
curr.closed = true;
curr.end = scanner.getTokenEnd();
curr.startTagEnd = scanner.getTokenEnd();
curr = curr.parent;
}
break;
case TokenType.EndTagOpen:
endTagStart = scanner.getTokenOffset();
endTagName = undefined;
break;
case TokenType.EndTag:
endTagName = scanner.getTokenText().toLowerCase();
break;
case TokenType.EndTagClose:
let node = curr;
// see if we can find a matching tag
while (!node.isSameTag(endTagName) && node.parent) {
node = node.parent;
}
if (node.parent) {
while (curr !== node) {
curr.end = endTagStart;
curr.closed = false;
curr = curr.parent;
}
curr.closed = true;
curr.endTagStart = endTagStart;
curr.end = scanner.getTokenEnd();
curr = curr.parent;
}
break;
case TokenType.AttributeName: {
pendingAttribute = scanner.getTokenText();
let attributes = curr.attributes;
if (!attributes) {
curr.attributes = attributes = {};
}
attributes[pendingAttribute] = null; // Support valueless attributes such as 'checked'
break;
}
case TokenType.AttributeValue: {
const value = scanner.getTokenText();
const attributes = curr.attributes;
if (attributes && pendingAttribute) {
attributes[pendingAttribute] = value;
pendingAttribute = null;
}
break;
}
}
token = scanner.scan();
}
while (curr.parent) {
curr.end = text.length;
curr.closed = false;
curr = curr.parent;
}
return {
roots: htmlDocument.children,
findNodeBefore: htmlDocument.findNodeBefore.bind(htmlDocument),
findNodeAt: htmlDocument.findNodeAt.bind(htmlDocument)
};
}
}

View file

@ -0,0 +1,2 @@
import { ScannerState, Scanner } from '../htmlLanguageTypes';
export declare function createScanner(input: string, initialOffset?: number, initialState?: ScannerState, emitPseudoCloseTags?: boolean): Scanner;

View file

@ -0,0 +1,403 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as l10n from '@vscode/l10n';
import { TokenType, ScannerState } from '../htmlLanguageTypes';
class MultiLineStream {
constructor(source, position) {
this.source = source;
this.len = source.length;
this.position = position;
}
eos() {
return this.len <= this.position;
}
getSource() {
return this.source;
}
pos() {
return this.position;
}
goBackTo(pos) {
this.position = pos;
}
goBack(n) {
this.position -= n;
}
advance(n) {
this.position += n;
}
goToEnd() {
this.position = this.source.length;
}
nextChar() {
return this.source.charCodeAt(this.position++) || 0;
}
peekChar(n = 0) {
return this.source.charCodeAt(this.position + n) || 0;
}
advanceIfChar(ch) {
if (ch === this.source.charCodeAt(this.position)) {
this.position++;
return true;
}
return false;
}
advanceIfChars(ch) {
let i;
if (this.position + ch.length > this.source.length) {
return false;
}
for (i = 0; i < ch.length; i++) {
if (this.source.charCodeAt(this.position + i) !== ch[i]) {
return false;
}
}
this.advance(i);
return true;
}
advanceIfRegExp(regex) {
const str = this.source.substr(this.position);
const match = str.match(regex);
if (match) {
this.position = this.position + match.index + match[0].length;
return match[0];
}
return '';
}
advanceUntilRegExp(regex) {
const str = this.source.substr(this.position);
const match = str.match(regex);
if (match) {
this.position = this.position + match.index;
return match[0];
}
else {
this.goToEnd();
}
return '';
}
advanceUntilChar(ch) {
while (this.position < this.source.length) {
if (this.source.charCodeAt(this.position) === ch) {
return true;
}
this.advance(1);
}
return false;
}
advanceUntilChars(ch) {
while (this.position + ch.length <= this.source.length) {
let i = 0;
for (; i < ch.length && this.source.charCodeAt(this.position + i) === ch[i]; i++) {
}
if (i === ch.length) {
return true;
}
this.advance(1);
}
this.goToEnd();
return false;
}
skipWhitespace() {
const n = this.advanceWhileChar(ch => {
return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR;
});
return n > 0;
}
advanceWhileChar(condition) {
const posNow = this.position;
while (this.position < this.len && condition(this.source.charCodeAt(this.position))) {
this.position++;
}
return this.position - posNow;
}
}
const _BNG = '!'.charCodeAt(0);
const _MIN = '-'.charCodeAt(0);
const _LAN = '<'.charCodeAt(0);
const _RAN = '>'.charCodeAt(0);
const _FSL = '/'.charCodeAt(0);
const _EQS = '='.charCodeAt(0);
const _DQO = '"'.charCodeAt(0);
const _SQO = '\''.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _WSP = ' '.charCodeAt(0);
const _TAB = '\t'.charCodeAt(0);
const htmlScriptContents = {
'text/x-handlebars-template': true,
// Fix for https://github.com/microsoft/vscode/issues/77977
'text/html': true,
};
export function createScanner(input, initialOffset = 0, initialState = ScannerState.WithinContent, emitPseudoCloseTags = false) {
const stream = new MultiLineStream(input, initialOffset);
let state = initialState;
let tokenOffset = 0;
let tokenType = TokenType.Unknown;
let tokenError;
let hasSpaceAfterTag;
let lastTag;
let lastAttributeName;
let lastTypeValue;
function nextElementName() {
return stream.advanceIfRegExp(/^[_:\w][_:\w-.\d]*/).toLowerCase();
}
function nextAttributeName() {
return stream.advanceIfRegExp(/^[^\s"'></=\x00-\x0F\x7F\x80-\x9F]*/).toLowerCase();
}
function finishToken(offset, type, errorMessage) {
tokenType = type;
tokenOffset = offset;
tokenError = errorMessage;
return type;
}
function scan() {
const offset = stream.pos();
const oldState = state;
const token = internalScan();
if (token !== TokenType.EOS && offset === stream.pos() && !(emitPseudoCloseTags && (token === TokenType.StartTagClose || token === TokenType.EndTagClose))) {
console.warn('Scanner.scan has not advanced at offset ' + offset + ', state before: ' + oldState + ' after: ' + state);
stream.advance(1);
return finishToken(offset, TokenType.Unknown);
}
return token;
}
function internalScan() {
const offset = stream.pos();
if (stream.eos()) {
return finishToken(offset, TokenType.EOS);
}
let errorMessage;
switch (state) {
case ScannerState.WithinComment:
if (stream.advanceIfChars([_MIN, _MIN, _RAN])) { // -->
state = ScannerState.WithinContent;
return finishToken(offset, TokenType.EndCommentTag);
}
stream.advanceUntilChars([_MIN, _MIN, _RAN]); // -->
return finishToken(offset, TokenType.Comment);
case ScannerState.WithinDoctype:
if (stream.advanceIfChar(_RAN)) {
state = ScannerState.WithinContent;
return finishToken(offset, TokenType.EndDoctypeTag);
}
stream.advanceUntilChar(_RAN); // >
return finishToken(offset, TokenType.Doctype);
case ScannerState.WithinContent:
if (stream.advanceIfChar(_LAN)) { // <
if (!stream.eos() && stream.peekChar() === _BNG) { // !
if (stream.advanceIfChars([_BNG, _MIN, _MIN])) { // <!--
state = ScannerState.WithinComment;
return finishToken(offset, TokenType.StartCommentTag);
}
if (stream.advanceIfRegExp(/^!doctype/i)) {
state = ScannerState.WithinDoctype;
return finishToken(offset, TokenType.StartDoctypeTag);
}
}
if (stream.advanceIfChar(_FSL)) { // /
state = ScannerState.AfterOpeningEndTag;
return finishToken(offset, TokenType.EndTagOpen);
}
state = ScannerState.AfterOpeningStartTag;
return finishToken(offset, TokenType.StartTagOpen);
}
stream.advanceUntilChar(_LAN);
return finishToken(offset, TokenType.Content);
case ScannerState.AfterOpeningEndTag:
const tagName = nextElementName();
if (tagName.length > 0) {
state = ScannerState.WithinEndTag;
return finishToken(offset, TokenType.EndTag);
}
if (stream.skipWhitespace()) { // white space is not valid here
return finishToken(offset, TokenType.Whitespace, l10n.t('Tag name must directly follow the open bracket.'));
}
state = ScannerState.WithinEndTag;
stream.advanceUntilChar(_RAN);
if (offset < stream.pos()) {
return finishToken(offset, TokenType.Unknown, l10n.t('End tag name expected.'));
}
return internalScan();
case ScannerState.WithinEndTag:
if (stream.skipWhitespace()) { // white space is valid here
return finishToken(offset, TokenType.Whitespace);
}
if (stream.advanceIfChar(_RAN)) { // >
state = ScannerState.WithinContent;
return finishToken(offset, TokenType.EndTagClose);
}
if (emitPseudoCloseTags && stream.peekChar() === _LAN) { // <
state = ScannerState.WithinContent;
return finishToken(offset, TokenType.EndTagClose, l10n.t('Closing bracket missing.'));
}
errorMessage = l10n.t('Closing bracket expected.');
break;
case ScannerState.AfterOpeningStartTag:
lastTag = nextElementName();
lastTypeValue = void 0;
lastAttributeName = void 0;
if (lastTag.length > 0) {
hasSpaceAfterTag = false;
state = ScannerState.WithinTag;
return finishToken(offset, TokenType.StartTag);
}
if (stream.skipWhitespace()) { // white space is not valid here
return finishToken(offset, TokenType.Whitespace, l10n.t('Tag name must directly follow the open bracket.'));
}
state = ScannerState.WithinTag;
stream.advanceUntilChar(_RAN);
if (offset < stream.pos()) {
return finishToken(offset, TokenType.Unknown, l10n.t('Start tag name expected.'));
}
return internalScan();
case ScannerState.WithinTag:
if (stream.skipWhitespace()) {
hasSpaceAfterTag = true; // remember that we have seen a whitespace
return finishToken(offset, TokenType.Whitespace);
}
if (hasSpaceAfterTag) {
lastAttributeName = nextAttributeName();
if (lastAttributeName.length > 0) {
state = ScannerState.AfterAttributeName;
hasSpaceAfterTag = false;
return finishToken(offset, TokenType.AttributeName);
}
}
if (stream.advanceIfChars([_FSL, _RAN])) { // />
state = ScannerState.WithinContent;
return finishToken(offset, TokenType.StartTagSelfClose);
}
if (stream.advanceIfChar(_RAN)) { // >
if (lastTag === 'script') {
if (lastTypeValue && htmlScriptContents[lastTypeValue]) {
// stay in html
state = ScannerState.WithinContent;
}
else {
state = ScannerState.WithinScriptContent;
}
}
else if (lastTag === 'style') {
state = ScannerState.WithinStyleContent;
}
else {
state = ScannerState.WithinContent;
}
return finishToken(offset, TokenType.StartTagClose);
}
if (emitPseudoCloseTags && stream.peekChar() === _LAN) { // <
state = ScannerState.WithinContent;
return finishToken(offset, TokenType.StartTagClose, l10n.t('Closing bracket missing.'));
}
stream.advance(1);
return finishToken(offset, TokenType.Unknown, l10n.t('Unexpected character in tag.'));
case ScannerState.AfterAttributeName:
if (stream.skipWhitespace()) {
hasSpaceAfterTag = true;
return finishToken(offset, TokenType.Whitespace);
}
if (stream.advanceIfChar(_EQS)) {
state = ScannerState.BeforeAttributeValue;
return finishToken(offset, TokenType.DelimiterAssign);
}
state = ScannerState.WithinTag;
return internalScan(); // no advance yet - jump to WithinTag
case ScannerState.BeforeAttributeValue:
if (stream.skipWhitespace()) {
return finishToken(offset, TokenType.Whitespace);
}
let attributeValue = stream.advanceIfRegExp(/^[^\s"'`=<>]+/);
if (attributeValue.length > 0) {
if (stream.peekChar() === _RAN && stream.peekChar(-1) === _FSL) { // <foo bar=http://foo/>
stream.goBack(1);
attributeValue = attributeValue.substring(0, attributeValue.length - 1);
}
if (lastAttributeName === 'type') {
lastTypeValue = attributeValue;
}
if (attributeValue.length > 0) {
state = ScannerState.WithinTag;
hasSpaceAfterTag = false;
return finishToken(offset, TokenType.AttributeValue);
}
}
const ch = stream.peekChar();
if (ch === _SQO || ch === _DQO) {
stream.advance(1); // consume quote
if (stream.advanceUntilChar(ch)) {
stream.advance(1); // consume quote
}
if (lastAttributeName === 'type') {
lastTypeValue = stream.getSource().substring(offset + 1, stream.pos() - 1);
}
state = ScannerState.WithinTag;
hasSpaceAfterTag = false;
return finishToken(offset, TokenType.AttributeValue);
}
state = ScannerState.WithinTag;
hasSpaceAfterTag = false;
return internalScan(); // no advance yet - jump to WithinTag
case ScannerState.WithinScriptContent:
// see http://stackoverflow.com/questions/14574471/how-do-browsers-parse-a-script-tag-exactly
let sciptState = 1;
while (!stream.eos()) {
const match = stream.advanceIfRegExp(/<!--|-->|<\/?script\s*\/?>?/i);
if (match.length === 0) {
stream.goToEnd();
return finishToken(offset, TokenType.Script);
}
else if (match === '<!--') {
if (sciptState === 1) {
sciptState = 2;
}
}
else if (match === '-->') {
sciptState = 1;
}
else if (match[1] !== '/') { // <script
if (sciptState === 2) {
sciptState = 3;
}
}
else { // </script
if (sciptState === 3) {
sciptState = 2;
}
else {
stream.goBack(match.length); // to the beginning of the closing tag
break;
}
}
}
state = ScannerState.WithinContent;
if (offset < stream.pos()) {
return finishToken(offset, TokenType.Script);
}
return internalScan(); // no advance yet - jump to content
case ScannerState.WithinStyleContent:
stream.advanceUntilRegExp(/<\/style/i);
state = ScannerState.WithinContent;
if (offset < stream.pos()) {
return finishToken(offset, TokenType.Styles);
}
return internalScan(); // no advance yet - jump to content
}
stream.advance(1);
state = ScannerState.WithinContent;
return finishToken(offset, TokenType.Unknown, errorMessage);
}
return {
scan,
getTokenType: () => tokenType,
getTokenOffset: () => tokenOffset,
getTokenLength: () => stream.pos() - tokenOffset,
getTokenEnd: () => stream.pos(),
getTokenText: () => stream.getSource().substring(tokenOffset, stream.pos()),
getScannerState: () => state,
getTokenError: () => tokenError
};
}

View file

@ -0,0 +1,571 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner } from '../parser/htmlScanner';
import { ScannerState, TokenType, Position, CompletionItemKind, Range, TextEdit, InsertTextFormat, MarkupKind } from '../htmlLanguageTypes';
import { entities } from '../parser/htmlEntities';
import * as l10n from '@vscode/l10n';
import { isLetterOrDigit, endsWith, startsWith } from '../utils/strings';
import { isDefined } from '../utils/object';
import { generateDocumentation } from '../languageFacts/dataProvider';
import { PathCompletionParticipant } from './pathCompletion';
export class HTMLCompletion {
constructor(lsOptions, dataManager) {
this.lsOptions = lsOptions;
this.dataManager = dataManager;
this.completionParticipants = [];
}
setCompletionParticipants(registeredCompletionParticipants) {
this.completionParticipants = registeredCompletionParticipants || [];
}
async doComplete2(document, position, htmlDocument, documentContext, settings) {
if (!this.lsOptions.fileSystemProvider || !this.lsOptions.fileSystemProvider.readDirectory) {
return this.doComplete(document, position, htmlDocument, settings);
}
const participant = new PathCompletionParticipant(this.dataManager, this.lsOptions.fileSystemProvider.readDirectory);
const contributedParticipants = this.completionParticipants;
this.completionParticipants = [participant].concat(contributedParticipants);
const result = this.doComplete(document, position, htmlDocument, settings);
try {
const pathCompletionResult = await participant.computeCompletions(document, documentContext);
return {
isIncomplete: result.isIncomplete || pathCompletionResult.isIncomplete,
items: pathCompletionResult.items.concat(result.items)
};
}
finally {
this.completionParticipants = contributedParticipants;
}
}
doComplete(document, position, htmlDocument, settings) {
const result = this._doComplete(document, position, htmlDocument, settings);
return this.convertCompletionList(result);
}
_doComplete(document, position, htmlDocument, settings) {
const result = {
isIncomplete: false,
items: []
};
const completionParticipants = this.completionParticipants;
const dataProviders = this.dataManager.getDataProviders().filter(p => p.isApplicable(document.languageId) && (!settings || settings[p.getId()] !== false));
const voidElements = this.dataManager.getVoidElements(dataProviders);
const doesSupportMarkdown = this.doesSupportMarkdown();
const text = document.getText();
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeBefore(offset);
if (!node) {
return result;
}
const scanner = createScanner(text, node.start);
let currentTag = '';
let currentAttributeName;
function getReplaceRange(replaceStart, replaceEnd = offset) {
if (replaceStart > offset) {
replaceStart = offset;
}
return { start: document.positionAt(replaceStart), end: document.positionAt(replaceEnd) };
}
function collectOpenTagSuggestions(afterOpenBracket, tagNameEnd) {
const range = getReplaceRange(afterOpenBracket, tagNameEnd);
dataProviders.forEach((provider) => {
provider.provideTags().forEach(tag => {
result.items.push({
label: tag.name,
kind: CompletionItemKind.Property,
documentation: generateDocumentation(tag, undefined, doesSupportMarkdown),
textEdit: TextEdit.replace(range, tag.name),
insertTextFormat: InsertTextFormat.PlainText
});
});
});
return result;
}
function getLineIndent(offset) {
let start = offset;
while (start > 0) {
const ch = text.charAt(start - 1);
if ("\n\r".indexOf(ch) >= 0) {
return text.substring(start, offset);
}
if (!isWhiteSpace(ch)) {
return null;
}
start--;
}
return text.substring(0, offset);
}
function collectCloseTagSuggestions(afterOpenBracket, inOpenTag, tagNameEnd = offset) {
const range = getReplaceRange(afterOpenBracket, tagNameEnd);
const closeTag = isFollowedBy(text, tagNameEnd, ScannerState.WithinEndTag, TokenType.EndTagClose) ? '' : '>';
let curr = node;
if (inOpenTag) {
curr = curr.parent; // don't suggest the own tag, it's not yet open
}
while (curr) {
const tag = curr.tag;
if (tag && (!curr.closed || curr.endTagStart && (curr.endTagStart > offset))) {
const item = {
label: '/' + tag,
kind: CompletionItemKind.Property,
filterText: '/' + tag,
textEdit: TextEdit.replace(range, '/' + tag + closeTag),
insertTextFormat: InsertTextFormat.PlainText
};
const startIndent = getLineIndent(curr.start);
const endIndent = getLineIndent(afterOpenBracket - 1);
if (startIndent !== null && endIndent !== null && startIndent !== endIndent) {
const insertText = startIndent + '</' + tag + closeTag;
item.textEdit = TextEdit.replace(getReplaceRange(afterOpenBracket - 1 - endIndent.length), insertText);
item.filterText = endIndent + '</' + tag;
}
result.items.push(item);
return result;
}
curr = curr.parent;
}
if (inOpenTag) {
return result;
}
dataProviders.forEach(provider => {
provider.provideTags().forEach(tag => {
result.items.push({
label: '/' + tag.name,
kind: CompletionItemKind.Property,
documentation: generateDocumentation(tag, undefined, doesSupportMarkdown),
filterText: '/' + tag.name + closeTag,
textEdit: TextEdit.replace(range, '/' + tag.name + closeTag),
insertTextFormat: InsertTextFormat.PlainText
});
});
});
return result;
}
const collectAutoCloseTagSuggestion = (tagCloseEnd, tag) => {
if (settings && settings.hideAutoCompleteProposals) {
return result;
}
if (!this.dataManager.isVoidElement(tag, voidElements)) {
const pos = document.positionAt(tagCloseEnd);
result.items.push({
label: '</' + tag + '>',
kind: CompletionItemKind.Property,
filterText: '</' + tag + '>',
textEdit: TextEdit.insert(pos, '$0</' + tag + '>'),
insertTextFormat: InsertTextFormat.Snippet
});
}
return result;
};
function collectTagSuggestions(tagStart, tagEnd) {
collectOpenTagSuggestions(tagStart, tagEnd);
collectCloseTagSuggestions(tagStart, true, tagEnd);
return result;
}
function getExistingAttributes() {
const existingAttributes = Object.create(null);
node.attributeNames.forEach(attribute => {
existingAttributes[attribute] = true;
});
return existingAttributes;
}
function collectAttributeNameSuggestions(nameStart, nameEnd = offset) {
let replaceEnd = offset;
while (replaceEnd < nameEnd && text[replaceEnd] !== '<') { // < is a valid attribute name character, but we rather assume the attribute name ends. See #23236.
replaceEnd++;
}
const currentAttribute = text.substring(nameStart, nameEnd);
const range = getReplaceRange(nameStart, replaceEnd);
let value = '';
if (!isFollowedBy(text, nameEnd, ScannerState.AfterAttributeName, TokenType.DelimiterAssign)) {
const defaultValue = settings?.attributeDefaultValue ?? 'doublequotes';
if (defaultValue === 'empty') {
value = '=$1';
}
else if (defaultValue === 'singlequotes') {
value = '=\'$1\'';
}
else {
value = '="$1"';
}
}
const seenAttributes = getExistingAttributes();
// include current typing attribute
seenAttributes[currentAttribute] = false;
dataProviders.forEach(provider => {
provider.provideAttributes(currentTag).forEach(attr => {
if (seenAttributes[attr.name]) {
return;
}
seenAttributes[attr.name] = true;
let codeSnippet = attr.name;
let command;
if (attr.valueSet !== 'v' && value.length) {
codeSnippet = codeSnippet + value;
if (attr.valueSet || attr.name === 'style') {
command = {
title: 'Suggest',
command: 'editor.action.triggerSuggest'
};
}
}
result.items.push({
label: attr.name,
kind: attr.valueSet === 'handler' ? CompletionItemKind.Function : CompletionItemKind.Value,
documentation: generateDocumentation(attr, undefined, doesSupportMarkdown),
textEdit: TextEdit.replace(range, codeSnippet),
insertTextFormat: InsertTextFormat.Snippet,
command
});
});
});
collectDataAttributesSuggestions(range, seenAttributes);
return result;
}
function collectDataAttributesSuggestions(range, seenAttributes) {
const dataAttr = 'data-';
const dataAttributes = {};
dataAttributes[dataAttr] = `${dataAttr}$1="$2"`;
function addNodeDataAttributes(node) {
node.attributeNames.forEach(attr => {
if (startsWith(attr, dataAttr) && !dataAttributes[attr] && !seenAttributes[attr]) {
dataAttributes[attr] = attr + '="$1"';
}
});
node.children.forEach(child => addNodeDataAttributes(child));
}
if (htmlDocument) {
htmlDocument.roots.forEach(root => addNodeDataAttributes(root));
}
Object.keys(dataAttributes).forEach(attr => result.items.push({
label: attr,
kind: CompletionItemKind.Value,
textEdit: TextEdit.replace(range, dataAttributes[attr]),
insertTextFormat: InsertTextFormat.Snippet
}));
}
function collectAttributeValueSuggestions(valueStart, valueEnd = offset) {
let range;
let addQuotes;
let valuePrefix;
if (offset > valueStart && offset <= valueEnd && isQuote(text[valueStart])) {
// inside quoted attribute
const valueContentStart = valueStart + 1;
let valueContentEnd = valueEnd;
// valueEnd points to the char after quote, which encloses the replace range
if (valueEnd > valueStart && text[valueEnd - 1] === text[valueStart]) {
valueContentEnd--;
}
const wsBefore = getWordStart(text, offset, valueContentStart);
const wsAfter = getWordEnd(text, offset, valueContentEnd);
range = getReplaceRange(wsBefore, wsAfter);
valuePrefix = offset >= valueContentStart && offset <= valueContentEnd ? text.substring(valueContentStart, offset) : '';
addQuotes = false;
}
else {
range = getReplaceRange(valueStart, valueEnd);
valuePrefix = text.substring(valueStart, offset);
addQuotes = true;
}
if (completionParticipants.length > 0) {
const tag = currentTag.toLowerCase();
const attribute = currentAttributeName.toLowerCase();
const fullRange = getReplaceRange(valueStart, valueEnd);
for (const participant of completionParticipants) {
if (participant.onHtmlAttributeValue) {
participant.onHtmlAttributeValue({ document, position, tag, attribute, value: valuePrefix, range: fullRange });
}
}
}
dataProviders.forEach(provider => {
provider.provideValues(currentTag, currentAttributeName).forEach(value => {
const insertText = addQuotes ? '"' + value.name + '"' : value.name;
result.items.push({
label: value.name,
filterText: insertText,
kind: CompletionItemKind.Unit,
documentation: generateDocumentation(value, undefined, doesSupportMarkdown),
textEdit: TextEdit.replace(range, insertText),
insertTextFormat: InsertTextFormat.PlainText
});
});
});
collectCharacterEntityProposals();
return result;
}
function scanNextForEndPos(nextToken) {
if (offset === scanner.getTokenEnd()) {
token = scanner.scan();
if (token === nextToken && scanner.getTokenOffset() === offset) {
return scanner.getTokenEnd();
}
}
return offset;
}
function collectInsideContent() {
for (const participant of completionParticipants) {
if (participant.onHtmlContent) {
participant.onHtmlContent({ document, position });
}
}
return collectCharacterEntityProposals();
}
function collectCharacterEntityProposals() {
// character entities
let k = offset - 1;
let characterStart = position.character;
while (k >= 0 && isLetterOrDigit(text, k)) {
k--;
characterStart--;
}
if (k >= 0 && text[k] === '&') {
const range = Range.create(Position.create(position.line, characterStart - 1), position);
for (const entity in entities) {
if (endsWith(entity, ';')) {
const label = '&' + entity;
result.items.push({
label,
kind: CompletionItemKind.Keyword,
documentation: l10n.t('Character entity representing \'{0}\'', entities[entity]),
textEdit: TextEdit.replace(range, label),
insertTextFormat: InsertTextFormat.PlainText
});
}
}
}
return result;
}
function suggestDoctype(replaceStart, replaceEnd) {
const range = getReplaceRange(replaceStart, replaceEnd);
result.items.push({
label: '!DOCTYPE',
kind: CompletionItemKind.Property,
documentation: 'A preamble for an HTML document.',
textEdit: TextEdit.replace(range, '!DOCTYPE html>'),
insertTextFormat: InsertTextFormat.PlainText
});
}
let token = scanner.scan();
while (token !== TokenType.EOS && scanner.getTokenOffset() <= offset) {
switch (token) {
case TokenType.StartTagOpen:
if (scanner.getTokenEnd() === offset) {
const endPos = scanNextForEndPos(TokenType.StartTag);
if (position.line === 0) {
suggestDoctype(offset, endPos);
}
return collectTagSuggestions(offset, endPos);
}
break;
case TokenType.StartTag:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectOpenTagSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
}
currentTag = scanner.getTokenText();
break;
case TokenType.AttributeName:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
}
currentAttributeName = scanner.getTokenText();
break;
case TokenType.DelimiterAssign:
if (scanner.getTokenEnd() === offset) {
const endPos = scanNextForEndPos(TokenType.AttributeValue);
return collectAttributeValueSuggestions(offset, endPos);
}
break;
case TokenType.AttributeValue:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectAttributeValueSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
}
break;
case TokenType.Whitespace:
if (offset <= scanner.getTokenEnd()) {
switch (scanner.getScannerState()) {
case ScannerState.AfterOpeningStartTag:
const startPos = scanner.getTokenOffset();
const endTagPos = scanNextForEndPos(TokenType.StartTag);
return collectTagSuggestions(startPos, endTagPos);
case ScannerState.WithinTag:
case ScannerState.AfterAttributeName:
return collectAttributeNameSuggestions(scanner.getTokenEnd());
case ScannerState.BeforeAttributeValue:
return collectAttributeValueSuggestions(scanner.getTokenEnd());
case ScannerState.AfterOpeningEndTag:
return collectCloseTagSuggestions(scanner.getTokenOffset() - 1, false);
case ScannerState.WithinContent:
return collectInsideContent();
}
}
break;
case TokenType.EndTagOpen:
if (offset <= scanner.getTokenEnd()) {
const afterOpenBracket = scanner.getTokenOffset() + 1;
const endOffset = scanNextForEndPos(TokenType.EndTag);
return collectCloseTagSuggestions(afterOpenBracket, false, endOffset);
}
break;
case TokenType.EndTag:
if (offset <= scanner.getTokenEnd()) {
let start = scanner.getTokenOffset() - 1;
while (start >= 0) {
const ch = text.charAt(start);
if (ch === '/') {
return collectCloseTagSuggestions(start, false, scanner.getTokenEnd());
}
else if (!isWhiteSpace(ch)) {
break;
}
start--;
}
}
break;
case TokenType.StartTagClose:
if (offset <= scanner.getTokenEnd()) {
if (currentTag) {
return collectAutoCloseTagSuggestion(scanner.getTokenEnd(), currentTag);
}
}
break;
case TokenType.Content:
if (offset <= scanner.getTokenEnd()) {
return collectInsideContent();
}
break;
default:
if (offset <= scanner.getTokenEnd()) {
return result;
}
break;
}
token = scanner.scan();
}
return result;
}
doQuoteComplete(document, position, htmlDocument, settings) {
const offset = document.offsetAt(position);
if (offset <= 0) {
return null;
}
const defaultValue = settings?.attributeDefaultValue ?? 'doublequotes';
if (defaultValue === 'empty') {
return null;
}
const char = document.getText().charAt(offset - 1);
if (char !== '=') {
return null;
}
const value = defaultValue === 'doublequotes' ? '"$1"' : '\'$1\'';
const node = htmlDocument.findNodeBefore(offset);
if (node && node.attributes && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
const scanner = createScanner(document.getText(), node.start);
let token = scanner.scan();
while (token !== TokenType.EOS && scanner.getTokenEnd() <= offset) {
if (token === TokenType.AttributeName && scanner.getTokenEnd() === offset - 1) {
// Ensure the token is a valid standalone attribute name
token = scanner.scan(); // this should be the = just written
if (token !== TokenType.DelimiterAssign) {
return null;
}
token = scanner.scan();
// Any non-attribute valid tag
if (token === TokenType.Unknown || token === TokenType.AttributeValue) {
return null;
}
return value;
}
token = scanner.scan();
}
}
return null;
}
doTagComplete(document, position, htmlDocument) {
const offset = document.offsetAt(position);
if (offset <= 0) {
return null;
}
const char = document.getText().charAt(offset - 1);
if (char === '>') {
const voidElements = this.dataManager.getVoidElements(document.languageId);
const node = htmlDocument.findNodeBefore(offset);
if (node && node.tag && !this.dataManager.isVoidElement(node.tag, voidElements) && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
const scanner = createScanner(document.getText(), node.start);
let token = scanner.scan();
while (token !== TokenType.EOS && scanner.getTokenEnd() <= offset) {
if (token === TokenType.StartTagClose && scanner.getTokenEnd() === offset) {
return `$0</${node.tag}>`;
}
token = scanner.scan();
}
}
}
else if (char === '/') {
let node = htmlDocument.findNodeBefore(offset);
while (node && node.closed && !(node.endTagStart && (node.endTagStart > offset))) {
node = node.parent;
}
if (node && node.tag) {
const scanner = createScanner(document.getText(), node.start);
let token = scanner.scan();
while (token !== TokenType.EOS && scanner.getTokenEnd() <= offset) {
if (token === TokenType.EndTagOpen && scanner.getTokenEnd() === offset) {
return `${node.tag}>`;
}
token = scanner.scan();
}
}
}
return null;
}
convertCompletionList(list) {
if (!this.doesSupportMarkdown()) {
list.items.forEach(item => {
if (item.documentation && typeof item.documentation !== 'string') {
item.documentation = {
kind: 'plaintext',
value: item.documentation.value
};
}
});
}
return list;
}
doesSupportMarkdown() {
if (!isDefined(this.supportsMarkdown)) {
if (!isDefined(this.lsOptions.clientCapabilities)) {
this.supportsMarkdown = true;
return this.supportsMarkdown;
}
const documentationFormat = this.lsOptions.clientCapabilities.textDocument?.completion?.completionItem?.documentationFormat;
this.supportsMarkdown = Array.isArray(documentationFormat) && documentationFormat.indexOf(MarkupKind.Markdown) !== -1;
}
return this.supportsMarkdown;
}
}
function isQuote(s) {
return /^["']*$/.test(s);
}
function isWhiteSpace(s) {
return /^\s*$/.test(s);
}
function isFollowedBy(s, offset, intialState, expectedToken) {
const scanner = createScanner(s, offset, intialState);
let token = scanner.scan();
while (token === TokenType.Whitespace) {
token = scanner.scan();
}
return token === expectedToken;
}
function getWordStart(s, offset, limit) {
while (offset > limit && !isWhiteSpace(s[offset - 1])) {
offset--;
}
return offset;
}
function getWordEnd(s, offset, limit) {
while (offset < limit && !isWhiteSpace(s[offset])) {
offset++;
}
return offset;
}

View file

@ -0,0 +1,170 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TokenType, FoldingRangeKind } from '../htmlLanguageTypes';
import { createScanner } from '../parser/htmlScanner';
export class HTMLFolding {
constructor(dataManager) {
this.dataManager = dataManager;
}
limitRanges(ranges, rangeLimit) {
ranges = ranges.sort((r1, r2) => {
let diff = r1.startLine - r2.startLine;
if (diff === 0) {
diff = r1.endLine - r2.endLine;
}
return diff;
});
// compute each range's nesting level in 'nestingLevels'.
// count the number of ranges for each level in 'nestingLevelCounts'
let top = void 0;
const previous = [];
const nestingLevels = [];
const nestingLevelCounts = [];
const setNestingLevel = (index, level) => {
nestingLevels[index] = level;
if (level < 30) {
nestingLevelCounts[level] = (nestingLevelCounts[level] || 0) + 1;
}
};
// compute nesting levels and sanitize
for (let i = 0; i < ranges.length; i++) {
const entry = ranges[i];
if (!top) {
top = entry;
setNestingLevel(i, 0);
}
else {
if (entry.startLine > top.startLine) {
if (entry.endLine <= top.endLine) {
previous.push(top);
top = entry;
setNestingLevel(i, previous.length);
}
else if (entry.startLine > top.endLine) {
do {
top = previous.pop();
} while (top && entry.startLine > top.endLine);
if (top) {
previous.push(top);
}
top = entry;
setNestingLevel(i, previous.length);
}
}
}
}
let entries = 0;
let maxLevel = 0;
for (let i = 0; i < nestingLevelCounts.length; i++) {
const n = nestingLevelCounts[i];
if (n) {
if (n + entries > rangeLimit) {
maxLevel = i;
break;
}
entries += n;
}
}
const result = [];
for (let i = 0; i < ranges.length; i++) {
const level = nestingLevels[i];
if (typeof level === 'number') {
if (level < maxLevel || (level === maxLevel && entries++ < rangeLimit)) {
result.push(ranges[i]);
}
}
}
return result;
}
getFoldingRanges(document, context) {
const voidElements = this.dataManager.getVoidElements(document.languageId);
const scanner = createScanner(document.getText());
let token = scanner.scan();
const ranges = [];
const stack = [];
let lastTagName = null;
let prevStart = -1;
function addRange(range) {
ranges.push(range);
prevStart = range.startLine;
}
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTag: {
const tagName = scanner.getTokenText();
const startLine = document.positionAt(scanner.getTokenOffset()).line;
stack.push({ startLine, tagName });
lastTagName = tagName;
break;
}
case TokenType.EndTag: {
lastTagName = scanner.getTokenText();
break;
}
case TokenType.StartTagClose:
if (!lastTagName || !this.dataManager.isVoidElement(lastTagName, voidElements)) {
break;
}
// fallthrough
case TokenType.EndTagClose:
case TokenType.StartTagSelfClose: {
let i = stack.length - 1;
while (i >= 0 && stack[i].tagName !== lastTagName) {
i--;
}
if (i >= 0) {
const stackElement = stack[i];
stack.length = i;
const line = document.positionAt(scanner.getTokenOffset()).line;
const startLine = stackElement.startLine;
const endLine = line - 1;
if (endLine > startLine && prevStart !== startLine) {
addRange({ startLine, endLine });
}
}
break;
}
case TokenType.Comment: {
let startLine = document.positionAt(scanner.getTokenOffset()).line;
const text = scanner.getTokenText();
const m = text.match(/^\s*#(region\b)|(endregion\b)/);
if (m) {
if (m[1]) { // start pattern match
stack.push({ startLine, tagName: '' }); // empty tagName marks region
}
else {
let i = stack.length - 1;
while (i >= 0 && stack[i].tagName.length) {
i--;
}
if (i >= 0) {
const stackElement = stack[i];
stack.length = i;
const endLine = startLine;
startLine = stackElement.startLine;
if (endLine > startLine && prevStart !== startLine) {
addRange({ startLine, endLine, kind: FoldingRangeKind.Region });
}
}
}
}
else {
const endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line;
if (startLine < endLine) {
addRange({ startLine, endLine, kind: FoldingRangeKind.Comment });
}
}
break;
}
}
token = scanner.scan();
}
const rangeLimit = context && context.rangeLimit || Number.MAX_VALUE;
if (ranges.length > rangeLimit) {
return this.limitRanges(ranges, rangeLimit);
}
return ranges;
}
}

View file

@ -0,0 +1,146 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Range, Position } from '../htmlLanguageTypes';
import { html_beautify } from '../beautify/beautify-html';
import { repeat } from '../utils/strings';
export function format(document, range, options) {
let value = document.getText();
let includesEnd = true;
let initialIndentLevel = 0;
const tabSize = options.tabSize || 4;
if (range) {
let startOffset = document.offsetAt(range.start);
// include all leading whitespace iff at the beginning of the line
let extendedStart = startOffset;
while (extendedStart > 0 && isWhitespace(value, extendedStart - 1)) {
extendedStart--;
}
if (extendedStart === 0 || isEOL(value, extendedStart - 1)) {
startOffset = extendedStart;
}
else {
// else keep at least one whitespace
if (extendedStart < startOffset) {
startOffset = extendedStart + 1;
}
}
// include all following whitespace until the end of the line
let endOffset = document.offsetAt(range.end);
let extendedEnd = endOffset;
while (extendedEnd < value.length && isWhitespace(value, extendedEnd)) {
extendedEnd++;
}
if (extendedEnd === value.length || isEOL(value, extendedEnd)) {
endOffset = extendedEnd;
}
range = Range.create(document.positionAt(startOffset), document.positionAt(endOffset));
// Do not modify if substring starts in inside an element
// Ending inside an element is fine as it doesn't cause formatting errors
const firstHalf = value.substring(0, startOffset);
if (new RegExp(/.*[<][^>]*$/).test(firstHalf)) {
//return without modification
value = value.substring(startOffset, endOffset);
return [{
range: range,
newText: value
}];
}
includesEnd = endOffset === value.length;
value = value.substring(startOffset, endOffset);
if (startOffset !== 0) {
const startOfLineOffset = document.offsetAt(Position.create(range.start.line, 0));
initialIndentLevel = computeIndentLevel(document.getText(), startOfLineOffset, options);
}
}
else {
range = Range.create(Position.create(0, 0), document.positionAt(value.length));
}
const htmlOptions = {
indent_size: tabSize,
indent_char: options.insertSpaces ? ' ' : '\t',
indent_empty_lines: getFormatOption(options, 'indentEmptyLines', false),
wrap_line_length: getFormatOption(options, 'wrapLineLength', 120),
unformatted: getTagsFormatOption(options, 'unformatted', void 0),
content_unformatted: getTagsFormatOption(options, 'contentUnformatted', void 0),
indent_inner_html: getFormatOption(options, 'indentInnerHtml', false),
preserve_newlines: getFormatOption(options, 'preserveNewLines', true),
max_preserve_newlines: getFormatOption(options, 'maxPreserveNewLines', 32786),
indent_handlebars: getFormatOption(options, 'indentHandlebars', false),
end_with_newline: includesEnd && getFormatOption(options, 'endWithNewline', false),
extra_liners: getTagsFormatOption(options, 'extraLiners', void 0),
wrap_attributes: getFormatOption(options, 'wrapAttributes', 'auto'),
wrap_attributes_indent_size: getFormatOption(options, 'wrapAttributesIndentSize', void 0),
eol: '\n',
indent_scripts: getFormatOption(options, 'indentScripts', 'normal'),
templating: getTemplatingFormatOption(options, 'all'),
unformatted_content_delimiter: getFormatOption(options, 'unformattedContentDelimiter', ''),
};
let result = html_beautify(trimLeft(value), htmlOptions);
if (initialIndentLevel > 0) {
const indent = options.insertSpaces ? repeat(' ', tabSize * initialIndentLevel) : repeat('\t', initialIndentLevel);
result = result.split('\n').join('\n' + indent);
if (range.start.character === 0) {
result = indent + result; // keep the indent
}
}
return [{
range: range,
newText: result
}];
}
function trimLeft(str) {
return str.replace(/^\s+/, '');
}
function getFormatOption(options, key, dflt) {
if (options && options.hasOwnProperty(key)) {
const value = options[key];
if (value !== null) {
return value;
}
}
return dflt;
}
function getTagsFormatOption(options, key, dflt) {
const list = getFormatOption(options, key, null);
if (typeof list === 'string') {
if (list.length > 0) {
return list.split(',').map(t => t.trim().toLowerCase());
}
return [];
}
return dflt;
}
function getTemplatingFormatOption(options, dflt) {
const value = getFormatOption(options, 'templating', dflt);
if (value === true) {
return ['auto'];
}
return ['none'];
}
function computeIndentLevel(content, offset, options) {
let i = offset;
let nChars = 0;
const tabSize = options.tabSize || 4;
while (i < content.length) {
const ch = content.charAt(i);
if (ch === ' ') {
nChars++;
}
else if (ch === '\t') {
nChars += tabSize;
}
else {
break;
}
i++;
}
return Math.floor(nChars / tabSize);
}
function isEOL(text, offset) {
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
}
function isWhitespace(text, offset) {
return ' \t'.indexOf(text.charAt(offset)) !== -1;
}

View file

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner } from '../parser/htmlScanner';
import { TokenType, DocumentHighlightKind } from '../htmlLanguageTypes';
export function findDocumentHighlights(document, position, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
if (!node.tag) {
return [];
}
const result = [];
const startTagRange = getTagNameRange(TokenType.StartTag, document, node.start);
const endTagRange = typeof node.endTagStart === 'number' && getTagNameRange(TokenType.EndTag, document, node.endTagStart);
if (startTagRange && covers(startTagRange, position) || endTagRange && covers(endTagRange, position)) {
if (startTagRange) {
result.push({ kind: DocumentHighlightKind.Read, range: startTagRange });
}
if (endTagRange) {
result.push({ kind: DocumentHighlightKind.Read, range: endTagRange });
}
}
return result;
}
function isBeforeOrEqual(pos1, pos2) {
return pos1.line < pos2.line || (pos1.line === pos2.line && pos1.character <= pos2.character);
}
function covers(range, position) {
return isBeforeOrEqual(range.start, position) && isBeforeOrEqual(position, range.end);
}
function getTagNameRange(tokenType, document, startOffset) {
const scanner = createScanner(document.getText(), startOffset);
let token = scanner.scan();
while (token !== TokenType.EOS && token !== tokenType) {
token = scanner.scan();
}
if (token !== TokenType.EOS) {
return { start: document.positionAt(scanner.getTokenOffset()), end: document.positionAt(scanner.getTokenEnd()) };
}
return null;
}

View file

@ -0,0 +1,265 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner } from '../parser/htmlScanner';
import { TokenType, Range, Position, MarkupKind } from '../htmlLanguageTypes';
import { isDefined } from '../utils/object';
import { generateDocumentation } from '../languageFacts/dataProvider';
import { entities } from '../parser/htmlEntities';
import { isLetterOrDigit } from '../utils/strings';
import * as l10n from '@vscode/l10n';
export class HTMLHover {
constructor(lsOptions, dataManager) {
this.lsOptions = lsOptions;
this.dataManager = dataManager;
}
doHover(document, position, htmlDocument, options) {
const convertContents = this.convertContents.bind(this);
const doesSupportMarkdown = this.doesSupportMarkdown();
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
const text = document.getText();
if (!node || !node.tag) {
return null;
}
const dataProviders = this.dataManager.getDataProviders().filter(p => p.isApplicable(document.languageId));
function getTagHover(currTag, range, open) {
for (const provider of dataProviders) {
let hover = null;
provider.provideTags().forEach(tag => {
if (tag.name.toLowerCase() === currTag.toLowerCase()) {
let markupContent = generateDocumentation(tag, options, doesSupportMarkdown);
if (!markupContent) {
markupContent = {
kind: doesSupportMarkdown ? 'markdown' : 'plaintext',
value: ''
};
}
hover = { contents: markupContent, range };
}
});
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getAttrHover(currTag, currAttr, range) {
for (const provider of dataProviders) {
let hover = null;
provider.provideAttributes(currTag).forEach(attr => {
if (currAttr === attr.name && attr.description) {
const contentsDoc = generateDocumentation(attr, options, doesSupportMarkdown);
if (contentsDoc) {
hover = { contents: contentsDoc, range };
}
else {
hover = null;
}
}
});
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getAttrValueHover(currTag, currAttr, currAttrValue, range) {
for (const provider of dataProviders) {
let hover = null;
provider.provideValues(currTag, currAttr).forEach(attrValue => {
if (currAttrValue === attrValue.name && attrValue.description) {
const contentsDoc = generateDocumentation(attrValue, options, doesSupportMarkdown);
if (contentsDoc) {
hover = { contents: contentsDoc, range };
}
else {
hover = null;
}
}
});
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getEntityHover(text, range) {
let currEntity = filterEntity(text);
for (const entity in entities) {
let hover = null;
const label = '&' + entity;
if (currEntity === label) {
let code = entities[entity].charCodeAt(0).toString(16).toUpperCase();
let hex = 'U+';
if (code.length < 4) {
const zeroes = 4 - code.length;
let k = 0;
while (k < zeroes) {
hex += '0';
k += 1;
}
}
hex += code;
const contentsDoc = l10n.t('Character entity representing \'{0}\', unicode equivalent \'{1}\'', entities[entity], hex);
if (contentsDoc) {
hover = { contents: contentsDoc, range };
}
else {
hover = null;
}
}
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getTagNameRange(tokenType, startOffset) {
const scanner = createScanner(document.getText(), startOffset);
let token = scanner.scan();
while (token !== TokenType.EOS && (scanner.getTokenEnd() < offset || scanner.getTokenEnd() === offset && token !== tokenType)) {
token = scanner.scan();
}
if (token === tokenType && offset <= scanner.getTokenEnd()) {
return { start: document.positionAt(scanner.getTokenOffset()), end: document.positionAt(scanner.getTokenEnd()) };
}
return null;
}
function getEntityRange() {
let k = offset - 1;
let characterStart = position.character;
while (k >= 0 && isLetterOrDigit(text, k)) {
k--;
characterStart--;
}
let n = k + 1;
let characterEnd = characterStart;
while (isLetterOrDigit(text, n)) {
n++;
characterEnd++;
}
if (k >= 0 && text[k] === '&') {
let range = null;
if (text[n] === ';') {
range = Range.create(Position.create(position.line, characterStart), Position.create(position.line, characterEnd + 1));
}
else {
range = Range.create(Position.create(position.line, characterStart), Position.create(position.line, characterEnd));
}
return range;
}
return null;
}
function filterEntity(text) {
let k = offset - 1;
let newText = '&';
while (k >= 0 && isLetterOrDigit(text, k)) {
k--;
}
k = k + 1;
while (isLetterOrDigit(text, k)) {
newText += text[k];
k += 1;
}
newText += ';';
return newText;
}
if (node.endTagStart && offset >= node.endTagStart) {
const tagRange = getTagNameRange(TokenType.EndTag, node.endTagStart);
if (tagRange) {
return getTagHover(node.tag, tagRange, false);
}
return null;
}
const tagRange = getTagNameRange(TokenType.StartTag, node.start);
if (tagRange) {
return getTagHover(node.tag, tagRange, true);
}
const attrRange = getTagNameRange(TokenType.AttributeName, node.start);
if (attrRange) {
const tag = node.tag;
const attr = document.getText(attrRange);
return getAttrHover(tag, attr, attrRange);
}
const entityRange = getEntityRange();
if (entityRange) {
return getEntityHover(text, entityRange);
}
function scanAttrAndAttrValue(nodeStart, attrValueStart) {
const scanner = createScanner(document.getText(), nodeStart);
let token = scanner.scan();
let prevAttr = undefined;
while (token !== TokenType.EOS && (scanner.getTokenEnd() <= attrValueStart)) {
token = scanner.scan();
if (token === TokenType.AttributeName) {
prevAttr = scanner.getTokenText();
}
}
return prevAttr;
}
const attrValueRange = getTagNameRange(TokenType.AttributeValue, node.start);
if (attrValueRange) {
const tag = node.tag;
const attrValue = trimQuotes(document.getText(attrValueRange));
const matchAttr = scanAttrAndAttrValue(node.start, document.offsetAt(attrValueRange.start));
if (matchAttr) {
return getAttrValueHover(tag, matchAttr, attrValue, attrValueRange);
}
}
return null;
}
convertContents(contents) {
if (!this.doesSupportMarkdown()) {
if (typeof contents === 'string') {
return contents;
}
// MarkupContent
else if ('kind' in contents) {
return {
kind: 'plaintext',
value: contents.value
};
}
// MarkedString[]
else if (Array.isArray(contents)) {
contents.map(c => {
return typeof c === 'string' ? c : c.value;
});
}
// MarkedString
else {
return contents.value;
}
}
return contents;
}
doesSupportMarkdown() {
if (!isDefined(this.supportsMarkdown)) {
if (!isDefined(this.lsOptions.clientCapabilities)) {
this.supportsMarkdown = true;
return this.supportsMarkdown;
}
const contentFormat = this.lsOptions.clientCapabilities?.textDocument?.hover?.contentFormat;
this.supportsMarkdown = Array.isArray(contentFormat) && contentFormat.indexOf(MarkupKind.Markdown) !== -1;
}
return this.supportsMarkdown;
}
}
function trimQuotes(s) {
if (s.length <= 1) {
return s.replace(/['"]/, '');
}
if (s[0] === `'` || s[0] === `"`) {
s = s.slice(1);
}
if (s[s.length - 1] === `'` || s[s.length - 1] === `"`) {
s = s.slice(0, -1);
}
return s;
}

View file

@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Range } from '../htmlLanguageTypes';
export function findLinkedEditingRanges(document, position, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
const tagLength = node.tag ? node.tag.length : 0;
if (!node.endTagStart) {
return null;
}
if (
// Within open tag, compute close tag
(node.start + '<'.length <= offset && offset <= node.start + '<'.length + tagLength) ||
// Within closing tag, compute open tag
node.endTagStart + '</'.length <= offset && offset <= node.endTagStart + '</'.length + tagLength) {
return [
Range.create(document.positionAt(node.start + '<'.length), document.positionAt(node.start + '<'.length + tagLength)),
Range.create(document.positionAt(node.endTagStart + '</'.length), document.positionAt(node.endTagStart + '</'.length + tagLength))
];
}
return null;
}

View file

@ -0,0 +1,144 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner } from '../parser/htmlScanner';
import * as strings from '../utils/strings';
import { URI as Uri } from 'vscode-uri';
import { TokenType, Range } from '../htmlLanguageTypes';
function normalizeRef(url) {
const first = url[0];
const last = url[url.length - 1];
if (first === last && (first === '\'' || first === '\"')) {
url = url.substring(1, url.length - 1);
}
return url;
}
function validateRef(url, languageId) {
if (!url.length) {
return false;
}
if (languageId === 'handlebars' && /{{|}}/.test(url)) {
return false;
}
return /\b(w[\w\d+.-]*:\/\/)?[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/?))/.test(url);
}
function getWorkspaceUrl(documentUri, tokenContent, documentContext, base) {
if (/^\s*javascript\:/i.test(tokenContent) || /[\n\r]/.test(tokenContent)) {
return undefined;
}
tokenContent = tokenContent.replace(/^\s*/g, '');
const match = tokenContent.match(/^(\w[\w\d+.-]*):/);
if (match) {
// Absolute link that needs no treatment
const schema = match[1].toLowerCase();
if (schema === 'http' || schema === 'https' || schema === 'file') {
return tokenContent;
}
return undefined;
}
if (/^\#/i.test(tokenContent)) {
return documentUri + tokenContent;
}
if (/^\/\//i.test(tokenContent)) {
// Absolute link (that does not name the protocol)
const pickedScheme = strings.startsWith(documentUri, 'https://') ? 'https' : 'http';
return pickedScheme + ':' + tokenContent.replace(/^\s*/g, '');
}
if (documentContext) {
return documentContext.resolveReference(tokenContent, base || documentUri);
}
return tokenContent;
}
function createLink(document, documentContext, attributeValue, startOffset, endOffset, base) {
const tokenContent = normalizeRef(attributeValue);
if (!validateRef(tokenContent, document.languageId)) {
return undefined;
}
if (tokenContent.length < attributeValue.length) {
startOffset++;
endOffset--;
}
const workspaceUrl = getWorkspaceUrl(document.uri, tokenContent, documentContext, base);
if (!workspaceUrl || !isValidURI(workspaceUrl)) {
return undefined;
}
return {
range: Range.create(document.positionAt(startOffset), document.positionAt(endOffset)),
target: workspaceUrl
};
}
function isValidURI(uri) {
try {
Uri.parse(uri);
return true;
}
catch (e) {
return false;
}
}
export class HTMLDocumentLinks {
constructor(dataManager) {
this.dataManager = dataManager;
}
findDocumentLinks(document, documentContext) {
const newLinks = [];
const scanner = createScanner(document.getText(), 0);
let token = scanner.scan();
let lastAttributeName = undefined;
let lastTagName = undefined;
let afterBase = false;
let base = void 0;
const idLocations = {};
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.StartTag:
lastTagName = scanner.getTokenText().toLowerCase();
if (!base) {
afterBase = lastTagName === 'base';
}
break;
case TokenType.AttributeName:
lastAttributeName = scanner.getTokenText().toLowerCase();
break;
case TokenType.AttributeValue:
if (lastTagName && lastAttributeName && this.dataManager.isPathAttribute(lastTagName, lastAttributeName)) {
const attributeValue = scanner.getTokenText();
if (!afterBase) { // don't highlight the base link itself
const link = createLink(document, documentContext, attributeValue, scanner.getTokenOffset(), scanner.getTokenEnd(), base);
if (link) {
newLinks.push(link);
}
}
if (afterBase && typeof base === 'undefined') {
base = normalizeRef(attributeValue);
if (base && documentContext) {
base = documentContext.resolveReference(base, document.uri);
}
}
afterBase = false;
lastAttributeName = undefined;
}
else if (lastAttributeName === 'id') {
const id = normalizeRef(scanner.getTokenText());
idLocations[id] = scanner.getTokenOffset();
}
break;
}
token = scanner.scan();
}
// change local links with ids to actual positions
for (const link of newLinks) {
const localWithHash = document.uri + '#';
if (link.target && strings.startsWith(link.target, localWithHash)) {
const target = link.target.substring(localWithHash.length);
const offset = idLocations[target];
if (offset !== undefined) {
const pos = document.positionAt(offset);
link.target = `${localWithHash}${pos.line + 1},${pos.character + 1}`;
}
}
}
return newLinks;
}
}

View file

@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function findMatchingTagPosition(document, position, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
if (!node.tag) {
return null;
}
if (!node.endTagStart) {
return null;
}
// Within open tag, compute close tag
if (node.start + '<'.length <= offset && offset <= node.start + '<'.length + node.tag.length) {
const mirrorOffset = (offset - '<'.length - node.start) + node.endTagStart + '</'.length;
return document.positionAt(mirrorOffset);
}
// Within closing tag, compute open tag
if (node.endTagStart + '</'.length <= offset && offset <= node.endTagStart + '</'.length + node.tag.length) {
const mirrorOffset = (offset - '</'.length - node.endTagStart) + node.start + '<'.length;
return document.positionAt(mirrorOffset);
}
return null;
}

View file

@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function doRename(document, position, newName, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
if (!node.tag) {
return null;
}
if (!isWithinTagRange(node, offset, node.tag)) {
return null;
}
const edits = [];
const startTagRange = {
start: document.positionAt(node.start + '<'.length),
end: document.positionAt(node.start + '<'.length + node.tag.length)
};
edits.push({
range: startTagRange,
newText: newName
});
if (node.endTagStart) {
const endTagRange = {
start: document.positionAt(node.endTagStart + '</'.length),
end: document.positionAt(node.endTagStart + '</'.length + node.tag.length)
};
edits.push({
range: endTagRange,
newText: newName
});
}
const changes = {
[document.uri.toString()]: edits
};
return {
changes
};
}
function toLocString(p) {
return `(${p.line}, ${p.character})`;
}
function isWithinTagRange(node, offset, nodeTag) {
// Self-closing tag
if (node.endTagStart) {
if (node.endTagStart + '</'.length <= offset && offset <= node.endTagStart + '</'.length + nodeTag.length) {
return true;
}
}
return node.start + '<'.length <= offset && offset <= node.start + '<'.length + nodeTag.length;
}

View file

@ -0,0 +1,174 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createScanner } from '../parser/htmlScanner';
import { TokenType, Range, SelectionRange } from '../htmlLanguageTypes';
export class HTMLSelectionRange {
constructor(htmlParser) {
this.htmlParser = htmlParser;
}
getSelectionRanges(document, positions) {
const htmlDocument = this.htmlParser.parseDocument(document);
return positions.map(p => this.getSelectionRange(p, document, htmlDocument));
}
getSelectionRange(position, document, htmlDocument) {
const applicableRanges = this.getApplicableRanges(document, position, htmlDocument);
let prev = undefined;
let current = undefined;
for (let index = applicableRanges.length - 1; index >= 0; index--) {
const range = applicableRanges[index];
if (!prev || range[0] !== prev[0] || range[1] !== prev[1]) {
current = SelectionRange.create(Range.create(document.positionAt(applicableRanges[index][0]), document.positionAt(applicableRanges[index][1])), current);
}
prev = range;
}
if (!current) {
current = SelectionRange.create(Range.create(position, position));
}
return current;
}
getApplicableRanges(document, position, htmlDoc) {
const currOffset = document.offsetAt(position);
const currNode = htmlDoc.findNodeAt(currOffset);
let result = this.getAllParentTagRanges(currNode);
// Self-closing or void elements
if (currNode.startTagEnd && !currNode.endTagStart) {
// THe rare case of unmatching tag pairs like <div></div1>
if (currNode.startTagEnd !== currNode.end) {
return [[currNode.start, currNode.end]];
}
const closeRange = Range.create(document.positionAt(currNode.startTagEnd - 2), document.positionAt(currNode.startTagEnd));
const closeText = document.getText(closeRange);
// Self-closing element
if (closeText === '/>') {
result.unshift([currNode.start + 1, currNode.startTagEnd - 2]);
}
// Void element
else {
result.unshift([currNode.start + 1, currNode.startTagEnd - 1]);
}
const attributeLevelRanges = this.getAttributeLevelRanges(document, currNode, currOffset);
result = attributeLevelRanges.concat(result);
return result;
}
if (!currNode.startTagEnd || !currNode.endTagStart) {
return result;
}
/**
* For html like
* `<div class="foo">bar</div>`
*/
result.unshift([currNode.start, currNode.end]);
/**
* Cursor inside `<div class="foo">`
*/
if (currNode.start < currOffset && currOffset < currNode.startTagEnd) {
result.unshift([currNode.start + 1, currNode.startTagEnd - 1]);
const attributeLevelRanges = this.getAttributeLevelRanges(document, currNode, currOffset);
result = attributeLevelRanges.concat(result);
return result;
}
/**
* Cursor inside `bar`
*/
else if (currNode.startTagEnd <= currOffset && currOffset <= currNode.endTagStart) {
result.unshift([currNode.startTagEnd, currNode.endTagStart]);
return result;
}
/**
* Cursor inside `</div>`
*/
else {
// `div` inside `</div>`
if (currOffset >= currNode.endTagStart + 2) {
result.unshift([currNode.endTagStart + 2, currNode.end - 1]);
}
return result;
}
}
getAllParentTagRanges(initialNode) {
let currNode = initialNode;
const result = [];
while (currNode.parent) {
currNode = currNode.parent;
this.getNodeRanges(currNode).forEach(r => result.push(r));
}
return result;
}
getNodeRanges(n) {
if (n.startTagEnd && n.endTagStart && n.startTagEnd < n.endTagStart) {
return [
[n.startTagEnd, n.endTagStart],
[n.start, n.end]
];
}
return [
[n.start, n.end]
];
}
;
getAttributeLevelRanges(document, currNode, currOffset) {
const currNodeRange = Range.create(document.positionAt(currNode.start), document.positionAt(currNode.end));
const currNodeText = document.getText(currNodeRange);
const relativeOffset = currOffset - currNode.start;
/**
* Tag level semantic selection
*/
const scanner = createScanner(currNodeText);
let token = scanner.scan();
/**
* For text like
* <div class="foo">bar</div>
*/
const positionOffset = currNode.start;
const result = [];
let isInsideAttribute = false;
let attrStart = -1;
while (token !== TokenType.EOS) {
switch (token) {
case TokenType.AttributeName: {
if (relativeOffset < scanner.getTokenOffset()) {
isInsideAttribute = false;
break;
}
if (relativeOffset <= scanner.getTokenEnd()) {
// `class`
result.unshift([scanner.getTokenOffset(), scanner.getTokenEnd()]);
}
isInsideAttribute = true;
attrStart = scanner.getTokenOffset();
break;
}
case TokenType.AttributeValue: {
if (!isInsideAttribute) {
break;
}
const valueText = scanner.getTokenText();
if (relativeOffset < scanner.getTokenOffset()) {
// `class="foo"`
result.push([attrStart, scanner.getTokenEnd()]);
break;
}
if (relativeOffset >= scanner.getTokenOffset() && relativeOffset <= scanner.getTokenEnd()) {
// `"foo"`
result.unshift([scanner.getTokenOffset(), scanner.getTokenEnd()]);
// `foo`
if ((valueText[0] === `"` && valueText[valueText.length - 1] === `"`) || (valueText[0] === `'` && valueText[valueText.length - 1] === `'`)) {
if (relativeOffset >= scanner.getTokenOffset() + 1 && relativeOffset <= scanner.getTokenEnd() - 1) {
result.unshift([scanner.getTokenOffset() + 1, scanner.getTokenEnd() - 1]);
}
}
// `class="foo"`
result.push([attrStart, scanner.getTokenEnd()]);
}
break;
}
}
token = scanner.scan();
}
return result.map(pair => {
return [pair[0] + positionOffset, pair[1] + positionOffset];
});
}
}

View file

@ -0,0 +1,40 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Location, Range, SymbolKind } from '../htmlLanguageTypes';
export function findDocumentSymbols(document, htmlDocument) {
const symbols = [];
htmlDocument.roots.forEach(node => {
provideFileSymbolsInternal(document, node, '', symbols);
});
return symbols;
}
function provideFileSymbolsInternal(document, node, container, symbols) {
const name = nodeToName(node);
const location = Location.create(document.uri, Range.create(document.positionAt(node.start), document.positionAt(node.end)));
const symbol = {
name: name,
location: location,
containerName: container,
kind: SymbolKind.Field
};
symbols.push(symbol);
node.children.forEach(child => {
provideFileSymbolsInternal(document, child, name, symbols);
});
}
function nodeToName(node) {
let name = node.tag;
if (node.attributes) {
const id = node.attributes['id'];
const classes = node.attributes['class'];
if (id) {
name += `#${id.replace(/[\"\']/g, '')}`;
}
if (classes) {
name += classes.replace(/[\"\']/g, '').split(/\s+/).map(className => `.${className}`).join('');
}
}
return name || '?';
}

View file

@ -0,0 +1,126 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { CompletionItemKind, TextEdit, Range, Position, FileType } from '../htmlLanguageTypes';
import { startsWith } from '../utils/strings';
export class PathCompletionParticipant {
constructor(dataManager, readDirectory) {
this.dataManager = dataManager;
this.readDirectory = readDirectory;
this.atributeCompletions = [];
}
onHtmlAttributeValue(context) {
if (this.dataManager.isPathAttribute(context.tag, context.attribute)) {
this.atributeCompletions.push(context);
}
}
async computeCompletions(document, documentContext) {
const result = { items: [], isIncomplete: false };
for (const attributeCompletion of this.atributeCompletions) {
const fullValue = stripQuotes(document.getText(attributeCompletion.range));
if (isCompletablePath(fullValue)) {
if (fullValue === '.' || fullValue === '..') {
result.isIncomplete = true;
}
else {
const replaceRange = pathToReplaceRange(attributeCompletion.value, fullValue, attributeCompletion.range);
const suggestions = await this.providePathSuggestions(attributeCompletion.value, replaceRange, document, documentContext);
for (const item of suggestions) {
result.items.push(item);
}
}
}
}
return result;
}
async providePathSuggestions(valueBeforeCursor, replaceRange, document, documentContext) {
const valueBeforeLastSlash = valueBeforeCursor.substring(0, valueBeforeCursor.lastIndexOf('/') + 1); // keep the last slash
let parentDir = documentContext.resolveReference(valueBeforeLastSlash || '.', document.uri);
if (parentDir) {
try {
const result = [];
const infos = await this.readDirectory(parentDir);
for (const [name, type] of infos) {
// Exclude paths that start with `.`
if (name.charCodeAt(0) !== CharCode_dot) {
result.push(createCompletionItem(name, type === FileType.Directory, replaceRange));
}
}
return result;
}
catch (e) {
// ignore
}
}
return [];
}
}
const CharCode_dot = '.'.charCodeAt(0);
function stripQuotes(fullValue) {
if (startsWith(fullValue, `'`) || startsWith(fullValue, `"`)) {
return fullValue.slice(1, -1);
}
else {
return fullValue;
}
}
function isCompletablePath(value) {
if (startsWith(value, 'http') || startsWith(value, 'https') || startsWith(value, '//')) {
return false;
}
return true;
}
function pathToReplaceRange(valueBeforeCursor, fullValue, range) {
let replaceRange;
const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
if (lastIndexOfSlash === -1) {
replaceRange = shiftRange(range, 1, -1);
}
else {
// For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
// Find the last slash before cursor, and calculate the start of replace range from there
const valueAfterLastSlash = fullValue.slice(lastIndexOfSlash + 1);
const startPos = shiftPosition(range.end, -1 - valueAfterLastSlash.length);
// If whitespace exists, replace until there is no more
const whitespaceIndex = valueAfterLastSlash.indexOf(' ');
let endPos;
if (whitespaceIndex !== -1) {
endPos = shiftPosition(startPos, whitespaceIndex);
}
else {
endPos = shiftPosition(range.end, -1);
}
replaceRange = Range.create(startPos, endPos);
}
return replaceRange;
}
function createCompletionItem(p, isDir, replaceRange) {
if (isDir) {
p = p + '/';
return {
label: p,
kind: CompletionItemKind.Folder,
textEdit: TextEdit.replace(replaceRange, p),
command: {
title: 'Suggest',
command: 'editor.action.triggerSuggest'
}
};
}
else {
return {
label: p,
kind: CompletionItemKind.File,
textEdit: TextEdit.replace(replaceRange, p)
};
}
}
function shiftPosition(pos, offset) {
return Position.create(pos.line, pos.character + offset);
}
function shiftRange(range, startOffset, endOffset) {
const start = shiftPosition(range.start, startOffset);
const end = shiftPosition(range.end, endOffset);
return Range.create(start, end);
}

View file

@ -0,0 +1,42 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false
* are located before all elements where p(x) is true.
* @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
*/
export function findFirst(array, p) {
let low = 0, high = array.length;
if (high === 0) {
return 0; // no children
}
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (p(array[mid])) {
high = mid;
}
else {
low = mid + 1;
}
}
return low;
}
export function binarySearch(array, key, comparator) {
let low = 0, high = array.length - 1;
while (low <= high) {
const mid = ((low + high) / 2) | 0;
const comp = comparator(array[mid], key);
if (comp < 0) {
low = mid + 1;
}
else if (comp > 0) {
high = mid - 1;
}
else {
return mid;
}
}
return -(low + 1);
}

View file

@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function normalizeMarkupContent(input) {
if (!input) {
return undefined;
}
if (typeof input === 'string') {
return {
kind: 'markdown',
value: input
};
}
return {
kind: 'markdown',
value: input.value
};
}

View file

@ -0,0 +1,7 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function isDefined(obj) {
return typeof obj !== 'undefined';
}

View file

@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* @returns the directory name of a path.
*/
export function dirname(path) {
const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
if (idx === 0) {
return '.';
}
else if (~idx === 0) {
return path[0];
}
else {
return path.substring(0, ~idx);
}
}
/**
* @returns the base name of a path.
*/
export function basename(path) {
const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
if (idx === 0) {
return path;
}
else if (~idx === path.length - 1) {
return basename(path.substring(0, path.length - 1));
}
else {
return path.substr(~idx + 1);
}
}
/**
* @returns {{.far}} from boo.far or the empty string.
*/
export function extname(path) {
path = basename(path);
const idx = ~path.lastIndexOf('.');
return idx ? path.substring(~idx) : '';
}
export const join = function () {
// Not using a function with var-args because of how TS compiles
// them to JS - it would result in 2*n runtime cost instead
// of 1*n, where n is parts.length.
let value = '';
for (let i = 0; i < arguments.length; i++) {
const part = arguments[i];
if (i > 0) {
// add the separater between two parts unless
// there already is one
const last = value.charCodeAt(value.length - 1);
if (last !== 47 /* CharCode.Slash */ && last !== 92 /* CharCode.Backslash */) {
const next = part.charCodeAt(0);
if (next !== 47 /* CharCode.Slash */ && next !== 92 /* CharCode.Backslash */) {
value += '/';
}
}
}
value += part;
}
return value;
};

View file

@ -0,0 +1,73 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { URI } from 'vscode-uri';
const Slash = '/'.charCodeAt(0);
const Dot = '.'.charCodeAt(0);
export function isAbsolutePath(path) {
return path.charCodeAt(0) === Slash;
}
export function dirname(uri) {
const lastIndexOfSlash = uri.lastIndexOf('/');
return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
}
export function basename(uri) {
const lastIndexOfSlash = uri.lastIndexOf('/');
return uri.substr(lastIndexOfSlash + 1);
}
export function extname(uri) {
for (let i = uri.length - 1; i >= 0; i--) {
const ch = uri.charCodeAt(i);
if (ch === Dot) {
if (i > 0 && uri.charCodeAt(i - 1) !== Slash) {
return uri.substr(i);
}
else {
break;
}
}
else if (ch === Slash) {
break;
}
}
return '';
}
export function resolvePath(uriString, path) {
if (isAbsolutePath(path)) {
const uri = URI.parse(uriString);
const parts = path.split('/');
return uri.with({ path: normalizePath(parts) }).toString();
}
return joinPath(uriString, path);
}
export function normalizePath(parts) {
const newParts = [];
for (const part of parts) {
if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
// ignore
}
else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
newParts.pop();
}
else {
newParts.push(part);
}
}
if (parts.length > 1 && parts[parts.length - 1].length === 0) {
newParts.push('');
}
let res = newParts.join('/');
if (parts[0].length === 0) {
res = '/' + res;
}
return res;
}
export function joinPath(uriString, ...paths) {
const uri = URI.parse(uriString);
const parts = uri.path.split('/');
for (let path of paths) {
parts.push(...path.split('/'));
}
return uri.with({ path: normalizePath(parts) }).toString();
}

View file

@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export function startsWith(haystack, needle) {
if (haystack.length < needle.length) {
return false;
}
for (let i = 0; i < needle.length; i++) {
if (haystack[i] !== needle[i]) {
return false;
}
}
return true;
}
/**
* Determines if haystack ends with needle.
*/
export function endsWith(haystack, needle) {
const diff = haystack.length - needle.length;
if (diff > 0) {
return haystack.lastIndexOf(needle) === diff;
}
else if (diff === 0) {
return haystack === needle;
}
else {
return false;
}
}
/**
* @returns the length of the common prefix of the two strings.
*/
export function commonPrefixLength(a, b) {
let i;
const len = Math.min(a.length, b.length);
for (i = 0; i < len; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) {
return i;
}
}
return len;
}
export function repeat(value, count) {
let s = '';
while (count > 0) {
if ((count & 1) === 1) {
s += value;
}
value += value;
count = count >>> 1;
}
return s;
}
const _a = 'a'.charCodeAt(0);
const _z = 'z'.charCodeAt(0);
const _A = 'A'.charCodeAt(0);
const _Z = 'Z'.charCodeAt(0);
const _0 = '0'.charCodeAt(0);
const _9 = '9'.charCodeAt(0);
export function isLetterOrDigit(text, index) {
const c = text.charCodeAt(index);
return (_a <= c && c <= _z) || (_A <= c && c <= _Z) || (_0 <= c && c <= _9);
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,25 @@
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.js_beautify = void 0;
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/*
* Mock for the JS formatter. Ignore formatting of JS content in HTML.
*/
function js_beautify(js_source_text, options) {
// no formatting
return js_source_text;
}
exports.js_beautify = js_beautify;
});

View file

@ -0,0 +1,29 @@
import { Scanner, HTMLDocument, CompletionConfiguration, ICompletionParticipant, HTMLFormatConfiguration, DocumentContext, IHTMLDataProvider, HTMLDataV1, LanguageServiceOptions, TextDocument, SelectionRange, WorkspaceEdit, Position, CompletionList, Hover, Range, SymbolInformation, TextEdit, DocumentHighlight, DocumentLink, FoldingRange, HoverSettings } from './htmlLanguageTypes';
export * from './htmlLanguageTypes';
export interface LanguageService {
setDataProviders(useDefaultDataProvider: boolean, customDataProviders: IHTMLDataProvider[]): void;
createScanner(input: string, initialOffset?: number): Scanner;
parseHTMLDocument(document: TextDocument): HTMLDocument;
findDocumentHighlights(document: TextDocument, position: Position, htmlDocument: HTMLDocument): DocumentHighlight[];
doComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): CompletionList;
doComplete2(document: TextDocument, position: Position, htmlDocument: HTMLDocument, documentContext: DocumentContext, options?: CompletionConfiguration): Promise<CompletionList>;
setCompletionParticipants(registeredCompletionParticipants: ICompletionParticipant[]): void;
doHover(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: HoverSettings): Hover | null;
format(document: TextDocument, range: Range | undefined, options: HTMLFormatConfiguration): TextEdit[];
findDocumentLinks(document: TextDocument, documentContext: DocumentContext): DocumentLink[];
findDocumentSymbols(document: TextDocument, htmlDocument: HTMLDocument): SymbolInformation[];
doQuoteComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument, options?: CompletionConfiguration): string | null;
doTagComplete(document: TextDocument, position: Position, htmlDocument: HTMLDocument): string | null;
getFoldingRanges(document: TextDocument, context?: {
rangeLimit?: number;
}): FoldingRange[];
getSelectionRanges(document: TextDocument, positions: Position[]): SelectionRange[];
doRename(document: TextDocument, position: Position, newName: string, htmlDocument: HTMLDocument): WorkspaceEdit | null;
findMatchingTagPosition(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Position | null;
/** Deprecated, Use findLinkedEditingRanges instead */
findOnTypeRenameRanges(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Range[] | null;
findLinkedEditingRanges(document: TextDocument, position: Position, htmlDocument: HTMLDocument): Range[] | null;
}
export declare function getLanguageService(options?: LanguageServiceOptions): LanguageService;
export declare function newHTMLDataProvider(id: string, customData: HTMLDataV1): IHTMLDataProvider;
export declare function getDefaultHTMLDataProvider(): IHTMLDataProvider;

View file

@ -0,0 +1,88 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./parser/htmlScanner", "./parser/htmlParser", "./services/htmlCompletion", "./services/htmlHover", "./services/htmlFormatter", "./services/htmlLinks", "./services/htmlHighlighting", "./services/htmlSymbolsProvider", "./services/htmlRename", "./services/htmlMatchingTagPosition", "./services/htmlLinkedEditing", "./services/htmlFolding", "./services/htmlSelectionRange", "./languageFacts/dataProvider", "./languageFacts/dataManager", "./languageFacts/data/webCustomData", "./htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDefaultHTMLDataProvider = exports.newHTMLDataProvider = exports.getLanguageService = void 0;
const htmlScanner_1 = require("./parser/htmlScanner");
const htmlParser_1 = require("./parser/htmlParser");
const htmlCompletion_1 = require("./services/htmlCompletion");
const htmlHover_1 = require("./services/htmlHover");
const htmlFormatter_1 = require("./services/htmlFormatter");
const htmlLinks_1 = require("./services/htmlLinks");
const htmlHighlighting_1 = require("./services/htmlHighlighting");
const htmlSymbolsProvider_1 = require("./services/htmlSymbolsProvider");
const htmlRename_1 = require("./services/htmlRename");
const htmlMatchingTagPosition_1 = require("./services/htmlMatchingTagPosition");
const htmlLinkedEditing_1 = require("./services/htmlLinkedEditing");
const htmlFolding_1 = require("./services/htmlFolding");
const htmlSelectionRange_1 = require("./services/htmlSelectionRange");
const dataProvider_1 = require("./languageFacts/dataProvider");
const dataManager_1 = require("./languageFacts/dataManager");
const webCustomData_1 = require("./languageFacts/data/webCustomData");
__exportStar(require("./htmlLanguageTypes"), exports);
const defaultLanguageServiceOptions = {};
function getLanguageService(options = defaultLanguageServiceOptions) {
const dataManager = new dataManager_1.HTMLDataManager(options);
const htmlHover = new htmlHover_1.HTMLHover(options, dataManager);
const htmlCompletion = new htmlCompletion_1.HTMLCompletion(options, dataManager);
const htmlParser = new htmlParser_1.HTMLParser(dataManager);
const htmlSelectionRange = new htmlSelectionRange_1.HTMLSelectionRange(htmlParser);
const htmlFolding = new htmlFolding_1.HTMLFolding(dataManager);
const htmlDocumentLinks = new htmlLinks_1.HTMLDocumentLinks(dataManager);
return {
setDataProviders: dataManager.setDataProviders.bind(dataManager),
createScanner: htmlScanner_1.createScanner,
parseHTMLDocument: htmlParser.parseDocument.bind(htmlParser),
doComplete: htmlCompletion.doComplete.bind(htmlCompletion),
doComplete2: htmlCompletion.doComplete2.bind(htmlCompletion),
setCompletionParticipants: htmlCompletion.setCompletionParticipants.bind(htmlCompletion),
doHover: htmlHover.doHover.bind(htmlHover),
format: htmlFormatter_1.format,
findDocumentHighlights: htmlHighlighting_1.findDocumentHighlights,
findDocumentLinks: htmlDocumentLinks.findDocumentLinks.bind(htmlDocumentLinks),
findDocumentSymbols: htmlSymbolsProvider_1.findDocumentSymbols,
getFoldingRanges: htmlFolding.getFoldingRanges.bind(htmlFolding),
getSelectionRanges: htmlSelectionRange.getSelectionRanges.bind(htmlSelectionRange),
doQuoteComplete: htmlCompletion.doQuoteComplete.bind(htmlCompletion),
doTagComplete: htmlCompletion.doTagComplete.bind(htmlCompletion),
doRename: htmlRename_1.doRename,
findMatchingTagPosition: htmlMatchingTagPosition_1.findMatchingTagPosition,
findOnTypeRenameRanges: htmlLinkedEditing_1.findLinkedEditingRanges,
findLinkedEditingRanges: htmlLinkedEditing_1.findLinkedEditingRanges
};
}
exports.getLanguageService = getLanguageService;
function newHTMLDataProvider(id, customData) {
return new dataProvider_1.HTMLDataProvider(id, customData);
}
exports.newHTMLDataProvider = newHTMLDataProvider;
function getDefaultHTMLDataProvider() {
return newHTMLDataProvider('default', webCustomData_1.htmlData);
}
exports.getDefaultHTMLDataProvider = getDefaultHTMLDataProvider;
});

View file

@ -0,0 +1,256 @@
import { Position, Range, Location, MarkupContent, MarkupKind, MarkedString, DocumentUri, SelectionRange, WorkspaceEdit, CompletionList, CompletionItemKind, CompletionItem, CompletionItemTag, InsertTextMode, Command, SymbolInformation, SymbolKind, Hover, TextEdit, InsertReplaceEdit, InsertTextFormat, DocumentHighlight, DocumentHighlightKind, DocumentLink, FoldingRange, FoldingRangeKind, SignatureHelp, Definition, Diagnostic, FormattingOptions, Color, ColorInformation, ColorPresentation } from 'vscode-languageserver-types';
import { TextDocument } from 'vscode-languageserver-textdocument';
export { TextDocument, Position, Range, Location, MarkupContent, MarkupKind, MarkedString, DocumentUri, SelectionRange, WorkspaceEdit, CompletionList, CompletionItemKind, CompletionItem, CompletionItemTag, InsertTextMode, Command, SymbolInformation, SymbolKind, Hover, TextEdit, InsertReplaceEdit, InsertTextFormat, DocumentHighlight, DocumentHighlightKind, DocumentLink, FoldingRange, FoldingRangeKind, SignatureHelp, Definition, Diagnostic, FormattingOptions, Color, ColorInformation, ColorPresentation };
export interface HTMLFormatConfiguration {
tabSize?: number;
insertSpaces?: boolean;
indentEmptyLines?: boolean;
wrapLineLength?: number;
unformatted?: string;
contentUnformatted?: string;
indentInnerHtml?: boolean;
wrapAttributes?: 'auto' | 'force' | 'force-aligned' | 'force-expand-multiline' | 'aligned-multiple' | 'preserve' | 'preserve-aligned';
wrapAttributesIndentSize?: number;
preserveNewLines?: boolean;
maxPreserveNewLines?: number;
indentHandlebars?: boolean;
endWithNewline?: boolean;
extraLiners?: string;
indentScripts?: 'keep' | 'separate' | 'normal';
templating?: boolean;
unformattedContentDelimiter?: string;
}
export interface HoverSettings {
documentation?: boolean;
references?: boolean;
}
export interface CompletionConfiguration {
[provider: string]: boolean | undefined | string;
hideAutoCompleteProposals?: boolean;
attributeDefaultValue?: 'empty' | 'singlequotes' | 'doublequotes';
}
export interface Node {
tag: string | undefined;
start: number;
startTagEnd: number | undefined;
end: number;
endTagStart: number | undefined;
children: Node[];
parent?: Node;
attributes?: {
[name: string]: string | null;
} | undefined;
}
export declare enum TokenType {
StartCommentTag = 0,
Comment = 1,
EndCommentTag = 2,
StartTagOpen = 3,
StartTagClose = 4,
StartTagSelfClose = 5,
StartTag = 6,
EndTagOpen = 7,
EndTagClose = 8,
EndTag = 9,
DelimiterAssign = 10,
AttributeName = 11,
AttributeValue = 12,
StartDoctypeTag = 13,
Doctype = 14,
EndDoctypeTag = 15,
Content = 16,
Whitespace = 17,
Unknown = 18,
Script = 19,
Styles = 20,
EOS = 21
}
export declare enum ScannerState {
WithinContent = 0,
AfterOpeningStartTag = 1,
AfterOpeningEndTag = 2,
WithinDoctype = 3,
WithinTag = 4,
WithinEndTag = 5,
WithinComment = 6,
WithinScriptContent = 7,
WithinStyleContent = 8,
AfterAttributeName = 9,
BeforeAttributeValue = 10
}
export interface Scanner {
scan(): TokenType;
getTokenType(): TokenType;
getTokenOffset(): number;
getTokenLength(): number;
getTokenEnd(): number;
getTokenText(): string;
getTokenError(): string | undefined;
getScannerState(): ScannerState;
}
export declare type HTMLDocument = {
roots: Node[];
findNodeBefore(offset: number): Node;
findNodeAt(offset: number): Node;
};
export interface DocumentContext {
resolveReference(ref: string, base: string): string | undefined;
}
export interface HtmlAttributeValueContext {
document: TextDocument;
position: Position;
tag: string;
attribute: string;
value: string;
range: Range;
}
export interface HtmlContentContext {
document: TextDocument;
position: Position;
}
export interface ICompletionParticipant {
onHtmlAttributeValue?: (context: HtmlAttributeValueContext) => void;
onHtmlContent?: (context: HtmlContentContext) => void;
}
export interface IReference {
name: string;
url: string;
}
export interface ITagData {
name: string;
description?: string | MarkupContent;
attributes: IAttributeData[];
references?: IReference[];
void?: boolean;
}
export interface IAttributeData {
name: string;
description?: string | MarkupContent;
valueSet?: string;
values?: IValueData[];
references?: IReference[];
}
export interface IValueData {
name: string;
description?: string | MarkupContent;
references?: IReference[];
}
export interface IValueSet {
name: string;
values: IValueData[];
}
export interface HTMLDataV1 {
version: 1 | 1.1;
tags?: ITagData[];
globalAttributes?: IAttributeData[];
valueSets?: IValueSet[];
}
export interface IHTMLDataProvider {
getId(): string;
isApplicable(languageId: string): boolean;
provideTags(): ITagData[];
provideAttributes(tag: string): IAttributeData[];
provideValues(tag: string, attribute: string): IValueData[];
}
/**
* Describes what LSP capabilities the client supports
*/
export interface ClientCapabilities {
/**
* The text document client capabilities
*/
textDocument?: {
/**
* Capabilities specific to completions.
*/
completion?: {
/**
* The client supports the following `CompletionItem` specific
* capabilities.
*/
completionItem?: {
/**
* Client supports the follow content formats for the documentation
* property. The order describes the preferred format of the client.
*/
documentationFormat?: MarkupKind[];
};
};
/**
* Capabilities specific to hovers.
*/
hover?: {
/**
* Client supports the follow content formats for the content
* property. The order describes the preferred format of the client.
*/
contentFormat?: MarkupKind[];
};
};
}
export declare namespace ClientCapabilities {
const LATEST: ClientCapabilities;
}
export interface LanguageServiceOptions {
/**
* Unless set to false, the default HTML data provider will be used
* along with the providers from customDataProviders.
* Defaults to true.
*/
useDefaultDataProvider?: boolean;
/**
* Provide data that could enhance the service's understanding of
* HTML tag / attribute / attribute-value
*/
customDataProviders?: IHTMLDataProvider[];
/**
* Abstract file system access away from the service.
* Used for path completion, etc.
*/
fileSystemProvider?: FileSystemProvider;
/**
* Describes the LSP capabilities the client supports.
*/
clientCapabilities?: ClientCapabilities;
}
export declare enum FileType {
/**
* The file type is unknown.
*/
Unknown = 0,
/**
* A regular file.
*/
File = 1,
/**
* A directory.
*/
Directory = 2,
/**
* A symbolic link to a file.
*/
SymbolicLink = 64
}
export interface FileStat {
/**
* The type of the file, e.g. is a regular file, a directory, or symbolic link
* to a file.
*/
type: FileType;
/**
* The creation timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
ctime: number;
/**
* The modification timestamp in milliseconds elapsed since January 1, 1970 00:00:00 UTC.
*/
mtime: number;
/**
* The size in bytes.
*/
size: number;
}
export interface FileSystemProvider {
stat(uri: DocumentUri): Promise<FileStat>;
readDirectory?(uri: DocumentUri): Promise<[string, FileType][]>;
}

View file

@ -0,0 +1,124 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "vscode-languageserver-types", "vscode-languageserver-textdocument"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.FileType = exports.ClientCapabilities = exports.ScannerState = exports.TokenType = exports.ColorPresentation = exports.ColorInformation = exports.Color = exports.FormattingOptions = exports.Diagnostic = exports.FoldingRangeKind = exports.FoldingRange = exports.DocumentLink = exports.DocumentHighlightKind = exports.DocumentHighlight = exports.InsertTextFormat = exports.InsertReplaceEdit = exports.TextEdit = exports.Hover = exports.SymbolKind = exports.SymbolInformation = exports.Command = exports.InsertTextMode = exports.CompletionItemTag = exports.CompletionItem = exports.CompletionItemKind = exports.CompletionList = exports.WorkspaceEdit = exports.SelectionRange = exports.DocumentUri = exports.MarkedString = exports.MarkupKind = exports.MarkupContent = exports.Location = exports.Range = exports.Position = exports.TextDocument = void 0;
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
Object.defineProperty(exports, "Position", { enumerable: true, get: function () { return vscode_languageserver_types_1.Position; } });
Object.defineProperty(exports, "Range", { enumerable: true, get: function () { return vscode_languageserver_types_1.Range; } });
Object.defineProperty(exports, "Location", { enumerable: true, get: function () { return vscode_languageserver_types_1.Location; } });
Object.defineProperty(exports, "MarkupContent", { enumerable: true, get: function () { return vscode_languageserver_types_1.MarkupContent; } });
Object.defineProperty(exports, "MarkupKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.MarkupKind; } });
Object.defineProperty(exports, "MarkedString", { enumerable: true, get: function () { return vscode_languageserver_types_1.MarkedString; } });
Object.defineProperty(exports, "DocumentUri", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentUri; } });
Object.defineProperty(exports, "SelectionRange", { enumerable: true, get: function () { return vscode_languageserver_types_1.SelectionRange; } });
Object.defineProperty(exports, "WorkspaceEdit", { enumerable: true, get: function () { return vscode_languageserver_types_1.WorkspaceEdit; } });
Object.defineProperty(exports, "CompletionList", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionList; } });
Object.defineProperty(exports, "CompletionItemKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionItemKind; } });
Object.defineProperty(exports, "CompletionItem", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionItem; } });
Object.defineProperty(exports, "CompletionItemTag", { enumerable: true, get: function () { return vscode_languageserver_types_1.CompletionItemTag; } });
Object.defineProperty(exports, "InsertTextMode", { enumerable: true, get: function () { return vscode_languageserver_types_1.InsertTextMode; } });
Object.defineProperty(exports, "Command", { enumerable: true, get: function () { return vscode_languageserver_types_1.Command; } });
Object.defineProperty(exports, "SymbolInformation", { enumerable: true, get: function () { return vscode_languageserver_types_1.SymbolInformation; } });
Object.defineProperty(exports, "SymbolKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.SymbolKind; } });
Object.defineProperty(exports, "Hover", { enumerable: true, get: function () { return vscode_languageserver_types_1.Hover; } });
Object.defineProperty(exports, "TextEdit", { enumerable: true, get: function () { return vscode_languageserver_types_1.TextEdit; } });
Object.defineProperty(exports, "InsertReplaceEdit", { enumerable: true, get: function () { return vscode_languageserver_types_1.InsertReplaceEdit; } });
Object.defineProperty(exports, "InsertTextFormat", { enumerable: true, get: function () { return vscode_languageserver_types_1.InsertTextFormat; } });
Object.defineProperty(exports, "DocumentHighlight", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentHighlight; } });
Object.defineProperty(exports, "DocumentHighlightKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentHighlightKind; } });
Object.defineProperty(exports, "DocumentLink", { enumerable: true, get: function () { return vscode_languageserver_types_1.DocumentLink; } });
Object.defineProperty(exports, "FoldingRange", { enumerable: true, get: function () { return vscode_languageserver_types_1.FoldingRange; } });
Object.defineProperty(exports, "FoldingRangeKind", { enumerable: true, get: function () { return vscode_languageserver_types_1.FoldingRangeKind; } });
Object.defineProperty(exports, "Diagnostic", { enumerable: true, get: function () { return vscode_languageserver_types_1.Diagnostic; } });
Object.defineProperty(exports, "FormattingOptions", { enumerable: true, get: function () { return vscode_languageserver_types_1.FormattingOptions; } });
Object.defineProperty(exports, "Color", { enumerable: true, get: function () { return vscode_languageserver_types_1.Color; } });
Object.defineProperty(exports, "ColorInformation", { enumerable: true, get: function () { return vscode_languageserver_types_1.ColorInformation; } });
Object.defineProperty(exports, "ColorPresentation", { enumerable: true, get: function () { return vscode_languageserver_types_1.ColorPresentation; } });
const vscode_languageserver_textdocument_1 = require("vscode-languageserver-textdocument");
Object.defineProperty(exports, "TextDocument", { enumerable: true, get: function () { return vscode_languageserver_textdocument_1.TextDocument; } });
var TokenType;
(function (TokenType) {
TokenType[TokenType["StartCommentTag"] = 0] = "StartCommentTag";
TokenType[TokenType["Comment"] = 1] = "Comment";
TokenType[TokenType["EndCommentTag"] = 2] = "EndCommentTag";
TokenType[TokenType["StartTagOpen"] = 3] = "StartTagOpen";
TokenType[TokenType["StartTagClose"] = 4] = "StartTagClose";
TokenType[TokenType["StartTagSelfClose"] = 5] = "StartTagSelfClose";
TokenType[TokenType["StartTag"] = 6] = "StartTag";
TokenType[TokenType["EndTagOpen"] = 7] = "EndTagOpen";
TokenType[TokenType["EndTagClose"] = 8] = "EndTagClose";
TokenType[TokenType["EndTag"] = 9] = "EndTag";
TokenType[TokenType["DelimiterAssign"] = 10] = "DelimiterAssign";
TokenType[TokenType["AttributeName"] = 11] = "AttributeName";
TokenType[TokenType["AttributeValue"] = 12] = "AttributeValue";
TokenType[TokenType["StartDoctypeTag"] = 13] = "StartDoctypeTag";
TokenType[TokenType["Doctype"] = 14] = "Doctype";
TokenType[TokenType["EndDoctypeTag"] = 15] = "EndDoctypeTag";
TokenType[TokenType["Content"] = 16] = "Content";
TokenType[TokenType["Whitespace"] = 17] = "Whitespace";
TokenType[TokenType["Unknown"] = 18] = "Unknown";
TokenType[TokenType["Script"] = 19] = "Script";
TokenType[TokenType["Styles"] = 20] = "Styles";
TokenType[TokenType["EOS"] = 21] = "EOS";
})(TokenType = exports.TokenType || (exports.TokenType = {}));
var ScannerState;
(function (ScannerState) {
ScannerState[ScannerState["WithinContent"] = 0] = "WithinContent";
ScannerState[ScannerState["AfterOpeningStartTag"] = 1] = "AfterOpeningStartTag";
ScannerState[ScannerState["AfterOpeningEndTag"] = 2] = "AfterOpeningEndTag";
ScannerState[ScannerState["WithinDoctype"] = 3] = "WithinDoctype";
ScannerState[ScannerState["WithinTag"] = 4] = "WithinTag";
ScannerState[ScannerState["WithinEndTag"] = 5] = "WithinEndTag";
ScannerState[ScannerState["WithinComment"] = 6] = "WithinComment";
ScannerState[ScannerState["WithinScriptContent"] = 7] = "WithinScriptContent";
ScannerState[ScannerState["WithinStyleContent"] = 8] = "WithinStyleContent";
ScannerState[ScannerState["AfterAttributeName"] = 9] = "AfterAttributeName";
ScannerState[ScannerState["BeforeAttributeValue"] = 10] = "BeforeAttributeValue";
})(ScannerState = exports.ScannerState || (exports.ScannerState = {}));
var ClientCapabilities;
(function (ClientCapabilities) {
ClientCapabilities.LATEST = {
textDocument: {
completion: {
completionItem: {
documentationFormat: [vscode_languageserver_types_1.MarkupKind.Markdown, vscode_languageserver_types_1.MarkupKind.PlainText]
}
},
hover: {
contentFormat: [vscode_languageserver_types_1.MarkupKind.Markdown, vscode_languageserver_types_1.MarkupKind.PlainText]
}
}
};
})(ClientCapabilities = exports.ClientCapabilities || (exports.ClientCapabilities = {}));
var FileType;
(function (FileType) {
/**
* The file type is unknown.
*/
FileType[FileType["Unknown"] = 0] = "Unknown";
/**
* A regular file.
*/
FileType[FileType["File"] = 1] = "File";
/**
* A directory.
*/
FileType[FileType["Directory"] = 2] = "Directory";
/**
* A symbolic link to a file.
*/
FileType[FileType["SymbolicLink"] = 64] = "SymbolicLink";
})(FileType = exports.FileType || (exports.FileType = {}));
});

View file

@ -0,0 +1,2 @@
import { HTMLDataV1 } from '../../htmlLanguageTypes';
export declare const htmlData: HTMLDataV1;

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./dataProvider", "./data/webCustomData", "../utils/arrays"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTMLDataManager = void 0;
const dataProvider_1 = require("./dataProvider");
const webCustomData_1 = require("./data/webCustomData");
const arrays = require("../utils/arrays");
class HTMLDataManager {
constructor(options) {
this.dataProviders = [];
this.setDataProviders(options.useDefaultDataProvider !== false, options.customDataProviders || []);
}
setDataProviders(builtIn, providers) {
this.dataProviders = [];
if (builtIn) {
this.dataProviders.push(new dataProvider_1.HTMLDataProvider('html5', webCustomData_1.htmlData));
}
this.dataProviders.push(...providers);
}
getDataProviders() {
return this.dataProviders;
}
isVoidElement(e, voidElements) {
return !!e && arrays.binarySearch(voidElements, e.toLowerCase(), (s1, s2) => s1.localeCompare(s2)) >= 0;
}
getVoidElements(languageOrProviders) {
const dataProviders = Array.isArray(languageOrProviders) ? languageOrProviders : this.getDataProviders().filter(p => p.isApplicable(languageOrProviders));
const voidTags = [];
dataProviders.forEach((provider) => {
provider.provideTags().filter(tag => tag.void).forEach(tag => voidTags.push(tag.name));
});
return voidTags.sort();
}
isPathAttribute(tag, attr) {
// should eventually come from custom data
if (attr === 'src' || attr === 'href') {
return true;
}
const a = PATH_TAG_AND_ATTR[tag];
if (a) {
if (typeof a === 'string') {
return a === attr;
}
else {
return a.indexOf(attr) !== -1;
}
}
return false;
}
}
exports.HTMLDataManager = HTMLDataManager;
// Selected from https://stackoverflow.com/a/2725168/1780148
const PATH_TAG_AND_ATTR = {
// HTML 4
a: 'href',
area: 'href',
body: 'background',
blockquote: 'cite',
del: 'cite',
form: 'action',
frame: ['src', 'longdesc'],
img: ['src', 'longdesc'],
ins: 'cite',
link: 'href',
object: 'data',
q: 'cite',
script: 'src',
// HTML 5
audio: 'src',
button: 'formaction',
command: 'icon',
embed: 'src',
html: 'manifest',
input: ['src', 'formaction'],
source: 'src',
track: 'src',
video: ['src', 'poster']
};
});

View file

@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../utils/markup"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateDocumentation = exports.HTMLDataProvider = void 0;
const markup_1 = require("../utils/markup");
class HTMLDataProvider {
isApplicable() {
return true;
}
/**
* Currently, unversioned data uses the V1 implementation
* In the future when the provider handles multiple versions of HTML custom data,
* use the latest implementation for unversioned data
*/
constructor(id, customData) {
this.id = id;
this._tags = [];
this._tagMap = {};
this._valueSetMap = {};
this._tags = customData.tags || [];
this._globalAttributes = customData.globalAttributes || [];
this._tags.forEach(t => {
this._tagMap[t.name.toLowerCase()] = t;
});
if (customData.valueSets) {
customData.valueSets.forEach(vs => {
this._valueSetMap[vs.name] = vs.values;
});
}
}
getId() {
return this.id;
}
provideTags() {
return this._tags;
}
provideAttributes(tag) {
const attributes = [];
const processAttribute = (a) => {
attributes.push(a);
};
const tagEntry = this._tagMap[tag.toLowerCase()];
if (tagEntry) {
tagEntry.attributes.forEach(processAttribute);
}
this._globalAttributes.forEach(processAttribute);
return attributes;
}
provideValues(tag, attribute) {
const values = [];
attribute = attribute.toLowerCase();
const processAttributes = (attributes) => {
attributes.forEach(a => {
if (a.name.toLowerCase() === attribute) {
if (a.values) {
a.values.forEach(v => {
values.push(v);
});
}
if (a.valueSet) {
if (this._valueSetMap[a.valueSet]) {
this._valueSetMap[a.valueSet].forEach(v => {
values.push(v);
});
}
}
}
});
};
const tagEntry = this._tagMap[tag.toLowerCase()];
if (tagEntry) {
processAttributes(tagEntry.attributes);
}
processAttributes(this._globalAttributes);
return values;
}
}
exports.HTMLDataProvider = HTMLDataProvider;
/**
* Generate Documentation used in hover/complete
* From `documentation` and `references`
*/
function generateDocumentation(item, settings = {}, doesSupportMarkdown) {
const result = {
kind: doesSupportMarkdown ? 'markdown' : 'plaintext',
value: ''
};
if (item.description && settings.documentation !== false) {
const normalizedDescription = (0, markup_1.normalizeMarkupContent)(item.description);
if (normalizedDescription) {
result.value += normalizedDescription.value;
}
}
if (item.references && item.references.length > 0 && settings.references !== false) {
if (result.value.length) {
result.value += `\n\n`;
}
if (doesSupportMarkdown) {
result.value += item.references.map(r => {
return `[${r.name}](${r.url})`;
}).join(' | ');
}
else {
result.value += item.references.map(r => {
return `${r.name}: ${r.url}`;
}).join('\n');
}
}
if (result.value === '') {
return undefined;
}
return result;
}
exports.generateDocumentation = generateDocumentation;
});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,176 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./htmlScanner", "../utils/arrays", "../htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTMLParser = exports.Node = void 0;
const htmlScanner_1 = require("./htmlScanner");
const arrays_1 = require("../utils/arrays");
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
class Node {
get attributeNames() { return this.attributes ? Object.keys(this.attributes) : []; }
constructor(start, end, children, parent) {
this.start = start;
this.end = end;
this.children = children;
this.parent = parent;
this.closed = false;
}
isSameTag(tagInLowerCase) {
if (this.tag === undefined) {
return tagInLowerCase === undefined;
}
else {
return tagInLowerCase !== undefined && this.tag.length === tagInLowerCase.length && this.tag.toLowerCase() === tagInLowerCase;
}
}
get firstChild() { return this.children[0]; }
get lastChild() { return this.children.length ? this.children[this.children.length - 1] : void 0; }
findNodeBefore(offset) {
const idx = (0, arrays_1.findFirst)(this.children, c => offset <= c.start) - 1;
if (idx >= 0) {
const child = this.children[idx];
if (offset > child.start) {
if (offset < child.end) {
return child.findNodeBefore(offset);
}
const lastChild = child.lastChild;
if (lastChild && lastChild.end === child.end) {
return child.findNodeBefore(offset);
}
return child;
}
}
return this;
}
findNodeAt(offset) {
const idx = (0, arrays_1.findFirst)(this.children, c => offset <= c.start) - 1;
if (idx >= 0) {
const child = this.children[idx];
if (offset > child.start && offset <= child.end) {
return child.findNodeAt(offset);
}
}
return this;
}
}
exports.Node = Node;
class HTMLParser {
constructor(dataManager) {
this.dataManager = dataManager;
}
parseDocument(document) {
return this.parse(document.getText(), this.dataManager.getVoidElements(document.languageId));
}
parse(text, voidElements) {
const scanner = (0, htmlScanner_1.createScanner)(text, undefined, undefined, true);
const htmlDocument = new Node(0, text.length, [], void 0);
let curr = htmlDocument;
let endTagStart = -1;
let endTagName = undefined;
let pendingAttribute = null;
let token = scanner.scan();
while (token !== htmlLanguageTypes_1.TokenType.EOS) {
switch (token) {
case htmlLanguageTypes_1.TokenType.StartTagOpen:
const child = new Node(scanner.getTokenOffset(), text.length, [], curr);
curr.children.push(child);
curr = child;
break;
case htmlLanguageTypes_1.TokenType.StartTag:
curr.tag = scanner.getTokenText();
break;
case htmlLanguageTypes_1.TokenType.StartTagClose:
if (curr.parent) {
curr.end = scanner.getTokenEnd(); // might be later set to end tag position
if (scanner.getTokenLength()) {
curr.startTagEnd = scanner.getTokenEnd();
if (curr.tag && this.dataManager.isVoidElement(curr.tag, voidElements)) {
curr.closed = true;
curr = curr.parent;
}
}
else {
// pseudo close token from an incomplete start tag
curr = curr.parent;
}
}
break;
case htmlLanguageTypes_1.TokenType.StartTagSelfClose:
if (curr.parent) {
curr.closed = true;
curr.end = scanner.getTokenEnd();
curr.startTagEnd = scanner.getTokenEnd();
curr = curr.parent;
}
break;
case htmlLanguageTypes_1.TokenType.EndTagOpen:
endTagStart = scanner.getTokenOffset();
endTagName = undefined;
break;
case htmlLanguageTypes_1.TokenType.EndTag:
endTagName = scanner.getTokenText().toLowerCase();
break;
case htmlLanguageTypes_1.TokenType.EndTagClose:
let node = curr;
// see if we can find a matching tag
while (!node.isSameTag(endTagName) && node.parent) {
node = node.parent;
}
if (node.parent) {
while (curr !== node) {
curr.end = endTagStart;
curr.closed = false;
curr = curr.parent;
}
curr.closed = true;
curr.endTagStart = endTagStart;
curr.end = scanner.getTokenEnd();
curr = curr.parent;
}
break;
case htmlLanguageTypes_1.TokenType.AttributeName: {
pendingAttribute = scanner.getTokenText();
let attributes = curr.attributes;
if (!attributes) {
curr.attributes = attributes = {};
}
attributes[pendingAttribute] = null; // Support valueless attributes such as 'checked'
break;
}
case htmlLanguageTypes_1.TokenType.AttributeValue: {
const value = scanner.getTokenText();
const attributes = curr.attributes;
if (attributes && pendingAttribute) {
attributes[pendingAttribute] = value;
pendingAttribute = null;
}
break;
}
}
token = scanner.scan();
}
while (curr.parent) {
curr.end = text.length;
curr.closed = false;
curr = curr.parent;
}
return {
roots: htmlDocument.children,
findNodeBefore: htmlDocument.findNodeBefore.bind(htmlDocument),
findNodeAt: htmlDocument.findNodeAt.bind(htmlDocument)
};
}
}
exports.HTMLParser = HTMLParser;
});

View file

@ -0,0 +1,2 @@
import { ScannerState, Scanner } from '../htmlLanguageTypes';
export declare function createScanner(input: string, initialOffset?: number, initialState?: ScannerState, emitPseudoCloseTags?: boolean): Scanner;

View file

@ -0,0 +1,417 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "@vscode/l10n", "../htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createScanner = void 0;
const l10n = require("@vscode/l10n");
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
class MultiLineStream {
constructor(source, position) {
this.source = source;
this.len = source.length;
this.position = position;
}
eos() {
return this.len <= this.position;
}
getSource() {
return this.source;
}
pos() {
return this.position;
}
goBackTo(pos) {
this.position = pos;
}
goBack(n) {
this.position -= n;
}
advance(n) {
this.position += n;
}
goToEnd() {
this.position = this.source.length;
}
nextChar() {
return this.source.charCodeAt(this.position++) || 0;
}
peekChar(n = 0) {
return this.source.charCodeAt(this.position + n) || 0;
}
advanceIfChar(ch) {
if (ch === this.source.charCodeAt(this.position)) {
this.position++;
return true;
}
return false;
}
advanceIfChars(ch) {
let i;
if (this.position + ch.length > this.source.length) {
return false;
}
for (i = 0; i < ch.length; i++) {
if (this.source.charCodeAt(this.position + i) !== ch[i]) {
return false;
}
}
this.advance(i);
return true;
}
advanceIfRegExp(regex) {
const str = this.source.substr(this.position);
const match = str.match(regex);
if (match) {
this.position = this.position + match.index + match[0].length;
return match[0];
}
return '';
}
advanceUntilRegExp(regex) {
const str = this.source.substr(this.position);
const match = str.match(regex);
if (match) {
this.position = this.position + match.index;
return match[0];
}
else {
this.goToEnd();
}
return '';
}
advanceUntilChar(ch) {
while (this.position < this.source.length) {
if (this.source.charCodeAt(this.position) === ch) {
return true;
}
this.advance(1);
}
return false;
}
advanceUntilChars(ch) {
while (this.position + ch.length <= this.source.length) {
let i = 0;
for (; i < ch.length && this.source.charCodeAt(this.position + i) === ch[i]; i++) {
}
if (i === ch.length) {
return true;
}
this.advance(1);
}
this.goToEnd();
return false;
}
skipWhitespace() {
const n = this.advanceWhileChar(ch => {
return ch === _WSP || ch === _TAB || ch === _NWL || ch === _LFD || ch === _CAR;
});
return n > 0;
}
advanceWhileChar(condition) {
const posNow = this.position;
while (this.position < this.len && condition(this.source.charCodeAt(this.position))) {
this.position++;
}
return this.position - posNow;
}
}
const _BNG = '!'.charCodeAt(0);
const _MIN = '-'.charCodeAt(0);
const _LAN = '<'.charCodeAt(0);
const _RAN = '>'.charCodeAt(0);
const _FSL = '/'.charCodeAt(0);
const _EQS = '='.charCodeAt(0);
const _DQO = '"'.charCodeAt(0);
const _SQO = '\''.charCodeAt(0);
const _NWL = '\n'.charCodeAt(0);
const _CAR = '\r'.charCodeAt(0);
const _LFD = '\f'.charCodeAt(0);
const _WSP = ' '.charCodeAt(0);
const _TAB = '\t'.charCodeAt(0);
const htmlScriptContents = {
'text/x-handlebars-template': true,
// Fix for https://github.com/microsoft/vscode/issues/77977
'text/html': true,
};
function createScanner(input, initialOffset = 0, initialState = htmlLanguageTypes_1.ScannerState.WithinContent, emitPseudoCloseTags = false) {
const stream = new MultiLineStream(input, initialOffset);
let state = initialState;
let tokenOffset = 0;
let tokenType = htmlLanguageTypes_1.TokenType.Unknown;
let tokenError;
let hasSpaceAfterTag;
let lastTag;
let lastAttributeName;
let lastTypeValue;
function nextElementName() {
return stream.advanceIfRegExp(/^[_:\w][_:\w-.\d]*/).toLowerCase();
}
function nextAttributeName() {
return stream.advanceIfRegExp(/^[^\s"'></=\x00-\x0F\x7F\x80-\x9F]*/).toLowerCase();
}
function finishToken(offset, type, errorMessage) {
tokenType = type;
tokenOffset = offset;
tokenError = errorMessage;
return type;
}
function scan() {
const offset = stream.pos();
const oldState = state;
const token = internalScan();
if (token !== htmlLanguageTypes_1.TokenType.EOS && offset === stream.pos() && !(emitPseudoCloseTags && (token === htmlLanguageTypes_1.TokenType.StartTagClose || token === htmlLanguageTypes_1.TokenType.EndTagClose))) {
console.warn('Scanner.scan has not advanced at offset ' + offset + ', state before: ' + oldState + ' after: ' + state);
stream.advance(1);
return finishToken(offset, htmlLanguageTypes_1.TokenType.Unknown);
}
return token;
}
function internalScan() {
const offset = stream.pos();
if (stream.eos()) {
return finishToken(offset, htmlLanguageTypes_1.TokenType.EOS);
}
let errorMessage;
switch (state) {
case htmlLanguageTypes_1.ScannerState.WithinComment:
if (stream.advanceIfChars([_MIN, _MIN, _RAN])) { // -->
state = htmlLanguageTypes_1.ScannerState.WithinContent;
return finishToken(offset, htmlLanguageTypes_1.TokenType.EndCommentTag);
}
stream.advanceUntilChars([_MIN, _MIN, _RAN]); // -->
return finishToken(offset, htmlLanguageTypes_1.TokenType.Comment);
case htmlLanguageTypes_1.ScannerState.WithinDoctype:
if (stream.advanceIfChar(_RAN)) {
state = htmlLanguageTypes_1.ScannerState.WithinContent;
return finishToken(offset, htmlLanguageTypes_1.TokenType.EndDoctypeTag);
}
stream.advanceUntilChar(_RAN); // >
return finishToken(offset, htmlLanguageTypes_1.TokenType.Doctype);
case htmlLanguageTypes_1.ScannerState.WithinContent:
if (stream.advanceIfChar(_LAN)) { // <
if (!stream.eos() && stream.peekChar() === _BNG) { // !
if (stream.advanceIfChars([_BNG, _MIN, _MIN])) { // <!--
state = htmlLanguageTypes_1.ScannerState.WithinComment;
return finishToken(offset, htmlLanguageTypes_1.TokenType.StartCommentTag);
}
if (stream.advanceIfRegExp(/^!doctype/i)) {
state = htmlLanguageTypes_1.ScannerState.WithinDoctype;
return finishToken(offset, htmlLanguageTypes_1.TokenType.StartDoctypeTag);
}
}
if (stream.advanceIfChar(_FSL)) { // /
state = htmlLanguageTypes_1.ScannerState.AfterOpeningEndTag;
return finishToken(offset, htmlLanguageTypes_1.TokenType.EndTagOpen);
}
state = htmlLanguageTypes_1.ScannerState.AfterOpeningStartTag;
return finishToken(offset, htmlLanguageTypes_1.TokenType.StartTagOpen);
}
stream.advanceUntilChar(_LAN);
return finishToken(offset, htmlLanguageTypes_1.TokenType.Content);
case htmlLanguageTypes_1.ScannerState.AfterOpeningEndTag:
const tagName = nextElementName();
if (tagName.length > 0) {
state = htmlLanguageTypes_1.ScannerState.WithinEndTag;
return finishToken(offset, htmlLanguageTypes_1.TokenType.EndTag);
}
if (stream.skipWhitespace()) { // white space is not valid here
return finishToken(offset, htmlLanguageTypes_1.TokenType.Whitespace, l10n.t('Tag name must directly follow the open bracket.'));
}
state = htmlLanguageTypes_1.ScannerState.WithinEndTag;
stream.advanceUntilChar(_RAN);
if (offset < stream.pos()) {
return finishToken(offset, htmlLanguageTypes_1.TokenType.Unknown, l10n.t('End tag name expected.'));
}
return internalScan();
case htmlLanguageTypes_1.ScannerState.WithinEndTag:
if (stream.skipWhitespace()) { // white space is valid here
return finishToken(offset, htmlLanguageTypes_1.TokenType.Whitespace);
}
if (stream.advanceIfChar(_RAN)) { // >
state = htmlLanguageTypes_1.ScannerState.WithinContent;
return finishToken(offset, htmlLanguageTypes_1.TokenType.EndTagClose);
}
if (emitPseudoCloseTags && stream.peekChar() === _LAN) { // <
state = htmlLanguageTypes_1.ScannerState.WithinContent;
return finishToken(offset, htmlLanguageTypes_1.TokenType.EndTagClose, l10n.t('Closing bracket missing.'));
}
errorMessage = l10n.t('Closing bracket expected.');
break;
case htmlLanguageTypes_1.ScannerState.AfterOpeningStartTag:
lastTag = nextElementName();
lastTypeValue = void 0;
lastAttributeName = void 0;
if (lastTag.length > 0) {
hasSpaceAfterTag = false;
state = htmlLanguageTypes_1.ScannerState.WithinTag;
return finishToken(offset, htmlLanguageTypes_1.TokenType.StartTag);
}
if (stream.skipWhitespace()) { // white space is not valid here
return finishToken(offset, htmlLanguageTypes_1.TokenType.Whitespace, l10n.t('Tag name must directly follow the open bracket.'));
}
state = htmlLanguageTypes_1.ScannerState.WithinTag;
stream.advanceUntilChar(_RAN);
if (offset < stream.pos()) {
return finishToken(offset, htmlLanguageTypes_1.TokenType.Unknown, l10n.t('Start tag name expected.'));
}
return internalScan();
case htmlLanguageTypes_1.ScannerState.WithinTag:
if (stream.skipWhitespace()) {
hasSpaceAfterTag = true; // remember that we have seen a whitespace
return finishToken(offset, htmlLanguageTypes_1.TokenType.Whitespace);
}
if (hasSpaceAfterTag) {
lastAttributeName = nextAttributeName();
if (lastAttributeName.length > 0) {
state = htmlLanguageTypes_1.ScannerState.AfterAttributeName;
hasSpaceAfterTag = false;
return finishToken(offset, htmlLanguageTypes_1.TokenType.AttributeName);
}
}
if (stream.advanceIfChars([_FSL, _RAN])) { // />
state = htmlLanguageTypes_1.ScannerState.WithinContent;
return finishToken(offset, htmlLanguageTypes_1.TokenType.StartTagSelfClose);
}
if (stream.advanceIfChar(_RAN)) { // >
if (lastTag === 'script') {
if (lastTypeValue && htmlScriptContents[lastTypeValue]) {
// stay in html
state = htmlLanguageTypes_1.ScannerState.WithinContent;
}
else {
state = htmlLanguageTypes_1.ScannerState.WithinScriptContent;
}
}
else if (lastTag === 'style') {
state = htmlLanguageTypes_1.ScannerState.WithinStyleContent;
}
else {
state = htmlLanguageTypes_1.ScannerState.WithinContent;
}
return finishToken(offset, htmlLanguageTypes_1.TokenType.StartTagClose);
}
if (emitPseudoCloseTags && stream.peekChar() === _LAN) { // <
state = htmlLanguageTypes_1.ScannerState.WithinContent;
return finishToken(offset, htmlLanguageTypes_1.TokenType.StartTagClose, l10n.t('Closing bracket missing.'));
}
stream.advance(1);
return finishToken(offset, htmlLanguageTypes_1.TokenType.Unknown, l10n.t('Unexpected character in tag.'));
case htmlLanguageTypes_1.ScannerState.AfterAttributeName:
if (stream.skipWhitespace()) {
hasSpaceAfterTag = true;
return finishToken(offset, htmlLanguageTypes_1.TokenType.Whitespace);
}
if (stream.advanceIfChar(_EQS)) {
state = htmlLanguageTypes_1.ScannerState.BeforeAttributeValue;
return finishToken(offset, htmlLanguageTypes_1.TokenType.DelimiterAssign);
}
state = htmlLanguageTypes_1.ScannerState.WithinTag;
return internalScan(); // no advance yet - jump to WithinTag
case htmlLanguageTypes_1.ScannerState.BeforeAttributeValue:
if (stream.skipWhitespace()) {
return finishToken(offset, htmlLanguageTypes_1.TokenType.Whitespace);
}
let attributeValue = stream.advanceIfRegExp(/^[^\s"'`=<>]+/);
if (attributeValue.length > 0) {
if (stream.peekChar() === _RAN && stream.peekChar(-1) === _FSL) { // <foo bar=http://foo/>
stream.goBack(1);
attributeValue = attributeValue.substring(0, attributeValue.length - 1);
}
if (lastAttributeName === 'type') {
lastTypeValue = attributeValue;
}
if (attributeValue.length > 0) {
state = htmlLanguageTypes_1.ScannerState.WithinTag;
hasSpaceAfterTag = false;
return finishToken(offset, htmlLanguageTypes_1.TokenType.AttributeValue);
}
}
const ch = stream.peekChar();
if (ch === _SQO || ch === _DQO) {
stream.advance(1); // consume quote
if (stream.advanceUntilChar(ch)) {
stream.advance(1); // consume quote
}
if (lastAttributeName === 'type') {
lastTypeValue = stream.getSource().substring(offset + 1, stream.pos() - 1);
}
state = htmlLanguageTypes_1.ScannerState.WithinTag;
hasSpaceAfterTag = false;
return finishToken(offset, htmlLanguageTypes_1.TokenType.AttributeValue);
}
state = htmlLanguageTypes_1.ScannerState.WithinTag;
hasSpaceAfterTag = false;
return internalScan(); // no advance yet - jump to WithinTag
case htmlLanguageTypes_1.ScannerState.WithinScriptContent:
// see http://stackoverflow.com/questions/14574471/how-do-browsers-parse-a-script-tag-exactly
let sciptState = 1;
while (!stream.eos()) {
const match = stream.advanceIfRegExp(/<!--|-->|<\/?script\s*\/?>?/i);
if (match.length === 0) {
stream.goToEnd();
return finishToken(offset, htmlLanguageTypes_1.TokenType.Script);
}
else if (match === '<!--') {
if (sciptState === 1) {
sciptState = 2;
}
}
else if (match === '-->') {
sciptState = 1;
}
else if (match[1] !== '/') { // <script
if (sciptState === 2) {
sciptState = 3;
}
}
else { // </script
if (sciptState === 3) {
sciptState = 2;
}
else {
stream.goBack(match.length); // to the beginning of the closing tag
break;
}
}
}
state = htmlLanguageTypes_1.ScannerState.WithinContent;
if (offset < stream.pos()) {
return finishToken(offset, htmlLanguageTypes_1.TokenType.Script);
}
return internalScan(); // no advance yet - jump to content
case htmlLanguageTypes_1.ScannerState.WithinStyleContent:
stream.advanceUntilRegExp(/<\/style/i);
state = htmlLanguageTypes_1.ScannerState.WithinContent;
if (offset < stream.pos()) {
return finishToken(offset, htmlLanguageTypes_1.TokenType.Styles);
}
return internalScan(); // no advance yet - jump to content
}
stream.advance(1);
state = htmlLanguageTypes_1.ScannerState.WithinContent;
return finishToken(offset, htmlLanguageTypes_1.TokenType.Unknown, errorMessage);
}
return {
scan,
getTokenType: () => tokenType,
getTokenOffset: () => tokenOffset,
getTokenLength: () => stream.pos() - tokenOffset,
getTokenEnd: () => stream.pos(),
getTokenText: () => stream.getSource().substring(tokenOffset, stream.pos()),
getScannerState: () => state,
getTokenError: () => tokenError
};
}
exports.createScanner = createScanner;
});

View file

@ -0,0 +1,585 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/htmlScanner", "../htmlLanguageTypes", "../parser/htmlEntities", "@vscode/l10n", "../utils/strings", "../utils/object", "../languageFacts/dataProvider", "./pathCompletion"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTMLCompletion = void 0;
const htmlScanner_1 = require("../parser/htmlScanner");
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
const htmlEntities_1 = require("../parser/htmlEntities");
const l10n = require("@vscode/l10n");
const strings_1 = require("../utils/strings");
const object_1 = require("../utils/object");
const dataProvider_1 = require("../languageFacts/dataProvider");
const pathCompletion_1 = require("./pathCompletion");
class HTMLCompletion {
constructor(lsOptions, dataManager) {
this.lsOptions = lsOptions;
this.dataManager = dataManager;
this.completionParticipants = [];
}
setCompletionParticipants(registeredCompletionParticipants) {
this.completionParticipants = registeredCompletionParticipants || [];
}
async doComplete2(document, position, htmlDocument, documentContext, settings) {
if (!this.lsOptions.fileSystemProvider || !this.lsOptions.fileSystemProvider.readDirectory) {
return this.doComplete(document, position, htmlDocument, settings);
}
const participant = new pathCompletion_1.PathCompletionParticipant(this.dataManager, this.lsOptions.fileSystemProvider.readDirectory);
const contributedParticipants = this.completionParticipants;
this.completionParticipants = [participant].concat(contributedParticipants);
const result = this.doComplete(document, position, htmlDocument, settings);
try {
const pathCompletionResult = await participant.computeCompletions(document, documentContext);
return {
isIncomplete: result.isIncomplete || pathCompletionResult.isIncomplete,
items: pathCompletionResult.items.concat(result.items)
};
}
finally {
this.completionParticipants = contributedParticipants;
}
}
doComplete(document, position, htmlDocument, settings) {
const result = this._doComplete(document, position, htmlDocument, settings);
return this.convertCompletionList(result);
}
_doComplete(document, position, htmlDocument, settings) {
const result = {
isIncomplete: false,
items: []
};
const completionParticipants = this.completionParticipants;
const dataProviders = this.dataManager.getDataProviders().filter(p => p.isApplicable(document.languageId) && (!settings || settings[p.getId()] !== false));
const voidElements = this.dataManager.getVoidElements(dataProviders);
const doesSupportMarkdown = this.doesSupportMarkdown();
const text = document.getText();
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeBefore(offset);
if (!node) {
return result;
}
const scanner = (0, htmlScanner_1.createScanner)(text, node.start);
let currentTag = '';
let currentAttributeName;
function getReplaceRange(replaceStart, replaceEnd = offset) {
if (replaceStart > offset) {
replaceStart = offset;
}
return { start: document.positionAt(replaceStart), end: document.positionAt(replaceEnd) };
}
function collectOpenTagSuggestions(afterOpenBracket, tagNameEnd) {
const range = getReplaceRange(afterOpenBracket, tagNameEnd);
dataProviders.forEach((provider) => {
provider.provideTags().forEach(tag => {
result.items.push({
label: tag.name,
kind: htmlLanguageTypes_1.CompletionItemKind.Property,
documentation: (0, dataProvider_1.generateDocumentation)(tag, undefined, doesSupportMarkdown),
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, tag.name),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.PlainText
});
});
});
return result;
}
function getLineIndent(offset) {
let start = offset;
while (start > 0) {
const ch = text.charAt(start - 1);
if ("\n\r".indexOf(ch) >= 0) {
return text.substring(start, offset);
}
if (!isWhiteSpace(ch)) {
return null;
}
start--;
}
return text.substring(0, offset);
}
function collectCloseTagSuggestions(afterOpenBracket, inOpenTag, tagNameEnd = offset) {
const range = getReplaceRange(afterOpenBracket, tagNameEnd);
const closeTag = isFollowedBy(text, tagNameEnd, htmlLanguageTypes_1.ScannerState.WithinEndTag, htmlLanguageTypes_1.TokenType.EndTagClose) ? '' : '>';
let curr = node;
if (inOpenTag) {
curr = curr.parent; // don't suggest the own tag, it's not yet open
}
while (curr) {
const tag = curr.tag;
if (tag && (!curr.closed || curr.endTagStart && (curr.endTagStart > offset))) {
const item = {
label: '/' + tag,
kind: htmlLanguageTypes_1.CompletionItemKind.Property,
filterText: '/' + tag,
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, '/' + tag + closeTag),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.PlainText
};
const startIndent = getLineIndent(curr.start);
const endIndent = getLineIndent(afterOpenBracket - 1);
if (startIndent !== null && endIndent !== null && startIndent !== endIndent) {
const insertText = startIndent + '</' + tag + closeTag;
item.textEdit = htmlLanguageTypes_1.TextEdit.replace(getReplaceRange(afterOpenBracket - 1 - endIndent.length), insertText);
item.filterText = endIndent + '</' + tag;
}
result.items.push(item);
return result;
}
curr = curr.parent;
}
if (inOpenTag) {
return result;
}
dataProviders.forEach(provider => {
provider.provideTags().forEach(tag => {
result.items.push({
label: '/' + tag.name,
kind: htmlLanguageTypes_1.CompletionItemKind.Property,
documentation: (0, dataProvider_1.generateDocumentation)(tag, undefined, doesSupportMarkdown),
filterText: '/' + tag.name + closeTag,
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, '/' + tag.name + closeTag),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.PlainText
});
});
});
return result;
}
const collectAutoCloseTagSuggestion = (tagCloseEnd, tag) => {
if (settings && settings.hideAutoCompleteProposals) {
return result;
}
if (!this.dataManager.isVoidElement(tag, voidElements)) {
const pos = document.positionAt(tagCloseEnd);
result.items.push({
label: '</' + tag + '>',
kind: htmlLanguageTypes_1.CompletionItemKind.Property,
filterText: '</' + tag + '>',
textEdit: htmlLanguageTypes_1.TextEdit.insert(pos, '$0</' + tag + '>'),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.Snippet
});
}
return result;
};
function collectTagSuggestions(tagStart, tagEnd) {
collectOpenTagSuggestions(tagStart, tagEnd);
collectCloseTagSuggestions(tagStart, true, tagEnd);
return result;
}
function getExistingAttributes() {
const existingAttributes = Object.create(null);
node.attributeNames.forEach(attribute => {
existingAttributes[attribute] = true;
});
return existingAttributes;
}
function collectAttributeNameSuggestions(nameStart, nameEnd = offset) {
let replaceEnd = offset;
while (replaceEnd < nameEnd && text[replaceEnd] !== '<') { // < is a valid attribute name character, but we rather assume the attribute name ends. See #23236.
replaceEnd++;
}
const currentAttribute = text.substring(nameStart, nameEnd);
const range = getReplaceRange(nameStart, replaceEnd);
let value = '';
if (!isFollowedBy(text, nameEnd, htmlLanguageTypes_1.ScannerState.AfterAttributeName, htmlLanguageTypes_1.TokenType.DelimiterAssign)) {
const defaultValue = settings?.attributeDefaultValue ?? 'doublequotes';
if (defaultValue === 'empty') {
value = '=$1';
}
else if (defaultValue === 'singlequotes') {
value = '=\'$1\'';
}
else {
value = '="$1"';
}
}
const seenAttributes = getExistingAttributes();
// include current typing attribute
seenAttributes[currentAttribute] = false;
dataProviders.forEach(provider => {
provider.provideAttributes(currentTag).forEach(attr => {
if (seenAttributes[attr.name]) {
return;
}
seenAttributes[attr.name] = true;
let codeSnippet = attr.name;
let command;
if (attr.valueSet !== 'v' && value.length) {
codeSnippet = codeSnippet + value;
if (attr.valueSet || attr.name === 'style') {
command = {
title: 'Suggest',
command: 'editor.action.triggerSuggest'
};
}
}
result.items.push({
label: attr.name,
kind: attr.valueSet === 'handler' ? htmlLanguageTypes_1.CompletionItemKind.Function : htmlLanguageTypes_1.CompletionItemKind.Value,
documentation: (0, dataProvider_1.generateDocumentation)(attr, undefined, doesSupportMarkdown),
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, codeSnippet),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.Snippet,
command
});
});
});
collectDataAttributesSuggestions(range, seenAttributes);
return result;
}
function collectDataAttributesSuggestions(range, seenAttributes) {
const dataAttr = 'data-';
const dataAttributes = {};
dataAttributes[dataAttr] = `${dataAttr}$1="$2"`;
function addNodeDataAttributes(node) {
node.attributeNames.forEach(attr => {
if ((0, strings_1.startsWith)(attr, dataAttr) && !dataAttributes[attr] && !seenAttributes[attr]) {
dataAttributes[attr] = attr + '="$1"';
}
});
node.children.forEach(child => addNodeDataAttributes(child));
}
if (htmlDocument) {
htmlDocument.roots.forEach(root => addNodeDataAttributes(root));
}
Object.keys(dataAttributes).forEach(attr => result.items.push({
label: attr,
kind: htmlLanguageTypes_1.CompletionItemKind.Value,
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, dataAttributes[attr]),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.Snippet
}));
}
function collectAttributeValueSuggestions(valueStart, valueEnd = offset) {
let range;
let addQuotes;
let valuePrefix;
if (offset > valueStart && offset <= valueEnd && isQuote(text[valueStart])) {
// inside quoted attribute
const valueContentStart = valueStart + 1;
let valueContentEnd = valueEnd;
// valueEnd points to the char after quote, which encloses the replace range
if (valueEnd > valueStart && text[valueEnd - 1] === text[valueStart]) {
valueContentEnd--;
}
const wsBefore = getWordStart(text, offset, valueContentStart);
const wsAfter = getWordEnd(text, offset, valueContentEnd);
range = getReplaceRange(wsBefore, wsAfter);
valuePrefix = offset >= valueContentStart && offset <= valueContentEnd ? text.substring(valueContentStart, offset) : '';
addQuotes = false;
}
else {
range = getReplaceRange(valueStart, valueEnd);
valuePrefix = text.substring(valueStart, offset);
addQuotes = true;
}
if (completionParticipants.length > 0) {
const tag = currentTag.toLowerCase();
const attribute = currentAttributeName.toLowerCase();
const fullRange = getReplaceRange(valueStart, valueEnd);
for (const participant of completionParticipants) {
if (participant.onHtmlAttributeValue) {
participant.onHtmlAttributeValue({ document, position, tag, attribute, value: valuePrefix, range: fullRange });
}
}
}
dataProviders.forEach(provider => {
provider.provideValues(currentTag, currentAttributeName).forEach(value => {
const insertText = addQuotes ? '"' + value.name + '"' : value.name;
result.items.push({
label: value.name,
filterText: insertText,
kind: htmlLanguageTypes_1.CompletionItemKind.Unit,
documentation: (0, dataProvider_1.generateDocumentation)(value, undefined, doesSupportMarkdown),
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, insertText),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.PlainText
});
});
});
collectCharacterEntityProposals();
return result;
}
function scanNextForEndPos(nextToken) {
if (offset === scanner.getTokenEnd()) {
token = scanner.scan();
if (token === nextToken && scanner.getTokenOffset() === offset) {
return scanner.getTokenEnd();
}
}
return offset;
}
function collectInsideContent() {
for (const participant of completionParticipants) {
if (participant.onHtmlContent) {
participant.onHtmlContent({ document, position });
}
}
return collectCharacterEntityProposals();
}
function collectCharacterEntityProposals() {
// character entities
let k = offset - 1;
let characterStart = position.character;
while (k >= 0 && (0, strings_1.isLetterOrDigit)(text, k)) {
k--;
characterStart--;
}
if (k >= 0 && text[k] === '&') {
const range = htmlLanguageTypes_1.Range.create(htmlLanguageTypes_1.Position.create(position.line, characterStart - 1), position);
for (const entity in htmlEntities_1.entities) {
if ((0, strings_1.endsWith)(entity, ';')) {
const label = '&' + entity;
result.items.push({
label,
kind: htmlLanguageTypes_1.CompletionItemKind.Keyword,
documentation: l10n.t('Character entity representing \'{0}\'', htmlEntities_1.entities[entity]),
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, label),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.PlainText
});
}
}
}
return result;
}
function suggestDoctype(replaceStart, replaceEnd) {
const range = getReplaceRange(replaceStart, replaceEnd);
result.items.push({
label: '!DOCTYPE',
kind: htmlLanguageTypes_1.CompletionItemKind.Property,
documentation: 'A preamble for an HTML document.',
textEdit: htmlLanguageTypes_1.TextEdit.replace(range, '!DOCTYPE html>'),
insertTextFormat: htmlLanguageTypes_1.InsertTextFormat.PlainText
});
}
let token = scanner.scan();
while (token !== htmlLanguageTypes_1.TokenType.EOS && scanner.getTokenOffset() <= offset) {
switch (token) {
case htmlLanguageTypes_1.TokenType.StartTagOpen:
if (scanner.getTokenEnd() === offset) {
const endPos = scanNextForEndPos(htmlLanguageTypes_1.TokenType.StartTag);
if (position.line === 0) {
suggestDoctype(offset, endPos);
}
return collectTagSuggestions(offset, endPos);
}
break;
case htmlLanguageTypes_1.TokenType.StartTag:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectOpenTagSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
}
currentTag = scanner.getTokenText();
break;
case htmlLanguageTypes_1.TokenType.AttributeName:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
}
currentAttributeName = scanner.getTokenText();
break;
case htmlLanguageTypes_1.TokenType.DelimiterAssign:
if (scanner.getTokenEnd() === offset) {
const endPos = scanNextForEndPos(htmlLanguageTypes_1.TokenType.AttributeValue);
return collectAttributeValueSuggestions(offset, endPos);
}
break;
case htmlLanguageTypes_1.TokenType.AttributeValue:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
return collectAttributeValueSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd());
}
break;
case htmlLanguageTypes_1.TokenType.Whitespace:
if (offset <= scanner.getTokenEnd()) {
switch (scanner.getScannerState()) {
case htmlLanguageTypes_1.ScannerState.AfterOpeningStartTag:
const startPos = scanner.getTokenOffset();
const endTagPos = scanNextForEndPos(htmlLanguageTypes_1.TokenType.StartTag);
return collectTagSuggestions(startPos, endTagPos);
case htmlLanguageTypes_1.ScannerState.WithinTag:
case htmlLanguageTypes_1.ScannerState.AfterAttributeName:
return collectAttributeNameSuggestions(scanner.getTokenEnd());
case htmlLanguageTypes_1.ScannerState.BeforeAttributeValue:
return collectAttributeValueSuggestions(scanner.getTokenEnd());
case htmlLanguageTypes_1.ScannerState.AfterOpeningEndTag:
return collectCloseTagSuggestions(scanner.getTokenOffset() - 1, false);
case htmlLanguageTypes_1.ScannerState.WithinContent:
return collectInsideContent();
}
}
break;
case htmlLanguageTypes_1.TokenType.EndTagOpen:
if (offset <= scanner.getTokenEnd()) {
const afterOpenBracket = scanner.getTokenOffset() + 1;
const endOffset = scanNextForEndPos(htmlLanguageTypes_1.TokenType.EndTag);
return collectCloseTagSuggestions(afterOpenBracket, false, endOffset);
}
break;
case htmlLanguageTypes_1.TokenType.EndTag:
if (offset <= scanner.getTokenEnd()) {
let start = scanner.getTokenOffset() - 1;
while (start >= 0) {
const ch = text.charAt(start);
if (ch === '/') {
return collectCloseTagSuggestions(start, false, scanner.getTokenEnd());
}
else if (!isWhiteSpace(ch)) {
break;
}
start--;
}
}
break;
case htmlLanguageTypes_1.TokenType.StartTagClose:
if (offset <= scanner.getTokenEnd()) {
if (currentTag) {
return collectAutoCloseTagSuggestion(scanner.getTokenEnd(), currentTag);
}
}
break;
case htmlLanguageTypes_1.TokenType.Content:
if (offset <= scanner.getTokenEnd()) {
return collectInsideContent();
}
break;
default:
if (offset <= scanner.getTokenEnd()) {
return result;
}
break;
}
token = scanner.scan();
}
return result;
}
doQuoteComplete(document, position, htmlDocument, settings) {
const offset = document.offsetAt(position);
if (offset <= 0) {
return null;
}
const defaultValue = settings?.attributeDefaultValue ?? 'doublequotes';
if (defaultValue === 'empty') {
return null;
}
const char = document.getText().charAt(offset - 1);
if (char !== '=') {
return null;
}
const value = defaultValue === 'doublequotes' ? '"$1"' : '\'$1\'';
const node = htmlDocument.findNodeBefore(offset);
if (node && node.attributes && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
const scanner = (0, htmlScanner_1.createScanner)(document.getText(), node.start);
let token = scanner.scan();
while (token !== htmlLanguageTypes_1.TokenType.EOS && scanner.getTokenEnd() <= offset) {
if (token === htmlLanguageTypes_1.TokenType.AttributeName && scanner.getTokenEnd() === offset - 1) {
// Ensure the token is a valid standalone attribute name
token = scanner.scan(); // this should be the = just written
if (token !== htmlLanguageTypes_1.TokenType.DelimiterAssign) {
return null;
}
token = scanner.scan();
// Any non-attribute valid tag
if (token === htmlLanguageTypes_1.TokenType.Unknown || token === htmlLanguageTypes_1.TokenType.AttributeValue) {
return null;
}
return value;
}
token = scanner.scan();
}
}
return null;
}
doTagComplete(document, position, htmlDocument) {
const offset = document.offsetAt(position);
if (offset <= 0) {
return null;
}
const char = document.getText().charAt(offset - 1);
if (char === '>') {
const voidElements = this.dataManager.getVoidElements(document.languageId);
const node = htmlDocument.findNodeBefore(offset);
if (node && node.tag && !this.dataManager.isVoidElement(node.tag, voidElements) && node.start < offset && (!node.endTagStart || node.endTagStart > offset)) {
const scanner = (0, htmlScanner_1.createScanner)(document.getText(), node.start);
let token = scanner.scan();
while (token !== htmlLanguageTypes_1.TokenType.EOS && scanner.getTokenEnd() <= offset) {
if (token === htmlLanguageTypes_1.TokenType.StartTagClose && scanner.getTokenEnd() === offset) {
return `$0</${node.tag}>`;
}
token = scanner.scan();
}
}
}
else if (char === '/') {
let node = htmlDocument.findNodeBefore(offset);
while (node && node.closed && !(node.endTagStart && (node.endTagStart > offset))) {
node = node.parent;
}
if (node && node.tag) {
const scanner = (0, htmlScanner_1.createScanner)(document.getText(), node.start);
let token = scanner.scan();
while (token !== htmlLanguageTypes_1.TokenType.EOS && scanner.getTokenEnd() <= offset) {
if (token === htmlLanguageTypes_1.TokenType.EndTagOpen && scanner.getTokenEnd() === offset) {
return `${node.tag}>`;
}
token = scanner.scan();
}
}
}
return null;
}
convertCompletionList(list) {
if (!this.doesSupportMarkdown()) {
list.items.forEach(item => {
if (item.documentation && typeof item.documentation !== 'string') {
item.documentation = {
kind: 'plaintext',
value: item.documentation.value
};
}
});
}
return list;
}
doesSupportMarkdown() {
if (!(0, object_1.isDefined)(this.supportsMarkdown)) {
if (!(0, object_1.isDefined)(this.lsOptions.clientCapabilities)) {
this.supportsMarkdown = true;
return this.supportsMarkdown;
}
const documentationFormat = this.lsOptions.clientCapabilities.textDocument?.completion?.completionItem?.documentationFormat;
this.supportsMarkdown = Array.isArray(documentationFormat) && documentationFormat.indexOf(htmlLanguageTypes_1.MarkupKind.Markdown) !== -1;
}
return this.supportsMarkdown;
}
}
exports.HTMLCompletion = HTMLCompletion;
function isQuote(s) {
return /^["']*$/.test(s);
}
function isWhiteSpace(s) {
return /^\s*$/.test(s);
}
function isFollowedBy(s, offset, intialState, expectedToken) {
const scanner = (0, htmlScanner_1.createScanner)(s, offset, intialState);
let token = scanner.scan();
while (token === htmlLanguageTypes_1.TokenType.Whitespace) {
token = scanner.scan();
}
return token === expectedToken;
}
function getWordStart(s, offset, limit) {
while (offset > limit && !isWhiteSpace(s[offset - 1])) {
offset--;
}
return offset;
}
function getWordEnd(s, offset, limit) {
while (offset < limit && !isWhiteSpace(s[offset])) {
offset++;
}
return offset;
}
});

View file

@ -0,0 +1,184 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../htmlLanguageTypes", "../parser/htmlScanner"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTMLFolding = void 0;
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
const htmlScanner_1 = require("../parser/htmlScanner");
class HTMLFolding {
constructor(dataManager) {
this.dataManager = dataManager;
}
limitRanges(ranges, rangeLimit) {
ranges = ranges.sort((r1, r2) => {
let diff = r1.startLine - r2.startLine;
if (diff === 0) {
diff = r1.endLine - r2.endLine;
}
return diff;
});
// compute each range's nesting level in 'nestingLevels'.
// count the number of ranges for each level in 'nestingLevelCounts'
let top = void 0;
const previous = [];
const nestingLevels = [];
const nestingLevelCounts = [];
const setNestingLevel = (index, level) => {
nestingLevels[index] = level;
if (level < 30) {
nestingLevelCounts[level] = (nestingLevelCounts[level] || 0) + 1;
}
};
// compute nesting levels and sanitize
for (let i = 0; i < ranges.length; i++) {
const entry = ranges[i];
if (!top) {
top = entry;
setNestingLevel(i, 0);
}
else {
if (entry.startLine > top.startLine) {
if (entry.endLine <= top.endLine) {
previous.push(top);
top = entry;
setNestingLevel(i, previous.length);
}
else if (entry.startLine > top.endLine) {
do {
top = previous.pop();
} while (top && entry.startLine > top.endLine);
if (top) {
previous.push(top);
}
top = entry;
setNestingLevel(i, previous.length);
}
}
}
}
let entries = 0;
let maxLevel = 0;
for (let i = 0; i < nestingLevelCounts.length; i++) {
const n = nestingLevelCounts[i];
if (n) {
if (n + entries > rangeLimit) {
maxLevel = i;
break;
}
entries += n;
}
}
const result = [];
for (let i = 0; i < ranges.length; i++) {
const level = nestingLevels[i];
if (typeof level === 'number') {
if (level < maxLevel || (level === maxLevel && entries++ < rangeLimit)) {
result.push(ranges[i]);
}
}
}
return result;
}
getFoldingRanges(document, context) {
const voidElements = this.dataManager.getVoidElements(document.languageId);
const scanner = (0, htmlScanner_1.createScanner)(document.getText());
let token = scanner.scan();
const ranges = [];
const stack = [];
let lastTagName = null;
let prevStart = -1;
function addRange(range) {
ranges.push(range);
prevStart = range.startLine;
}
while (token !== htmlLanguageTypes_1.TokenType.EOS) {
switch (token) {
case htmlLanguageTypes_1.TokenType.StartTag: {
const tagName = scanner.getTokenText();
const startLine = document.positionAt(scanner.getTokenOffset()).line;
stack.push({ startLine, tagName });
lastTagName = tagName;
break;
}
case htmlLanguageTypes_1.TokenType.EndTag: {
lastTagName = scanner.getTokenText();
break;
}
case htmlLanguageTypes_1.TokenType.StartTagClose:
if (!lastTagName || !this.dataManager.isVoidElement(lastTagName, voidElements)) {
break;
}
// fallthrough
case htmlLanguageTypes_1.TokenType.EndTagClose:
case htmlLanguageTypes_1.TokenType.StartTagSelfClose: {
let i = stack.length - 1;
while (i >= 0 && stack[i].tagName !== lastTagName) {
i--;
}
if (i >= 0) {
const stackElement = stack[i];
stack.length = i;
const line = document.positionAt(scanner.getTokenOffset()).line;
const startLine = stackElement.startLine;
const endLine = line - 1;
if (endLine > startLine && prevStart !== startLine) {
addRange({ startLine, endLine });
}
}
break;
}
case htmlLanguageTypes_1.TokenType.Comment: {
let startLine = document.positionAt(scanner.getTokenOffset()).line;
const text = scanner.getTokenText();
const m = text.match(/^\s*#(region\b)|(endregion\b)/);
if (m) {
if (m[1]) { // start pattern match
stack.push({ startLine, tagName: '' }); // empty tagName marks region
}
else {
let i = stack.length - 1;
while (i >= 0 && stack[i].tagName.length) {
i--;
}
if (i >= 0) {
const stackElement = stack[i];
stack.length = i;
const endLine = startLine;
startLine = stackElement.startLine;
if (endLine > startLine && prevStart !== startLine) {
addRange({ startLine, endLine, kind: htmlLanguageTypes_1.FoldingRangeKind.Region });
}
}
}
}
else {
const endLine = document.positionAt(scanner.getTokenOffset() + scanner.getTokenLength()).line;
if (startLine < endLine) {
addRange({ startLine, endLine, kind: htmlLanguageTypes_1.FoldingRangeKind.Comment });
}
}
break;
}
}
token = scanner.scan();
}
const rangeLimit = context && context.rangeLimit || Number.MAX_VALUE;
if (ranges.length > rangeLimit) {
return this.limitRanges(ranges, rangeLimit);
}
return ranges;
}
}
exports.HTMLFolding = HTMLFolding;
});

View file

@ -0,0 +1,160 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../htmlLanguageTypes", "../beautify/beautify-html", "../utils/strings"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.format = void 0;
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
const beautify_html_1 = require("../beautify/beautify-html");
const strings_1 = require("../utils/strings");
function format(document, range, options) {
let value = document.getText();
let includesEnd = true;
let initialIndentLevel = 0;
const tabSize = options.tabSize || 4;
if (range) {
let startOffset = document.offsetAt(range.start);
// include all leading whitespace iff at the beginning of the line
let extendedStart = startOffset;
while (extendedStart > 0 && isWhitespace(value, extendedStart - 1)) {
extendedStart--;
}
if (extendedStart === 0 || isEOL(value, extendedStart - 1)) {
startOffset = extendedStart;
}
else {
// else keep at least one whitespace
if (extendedStart < startOffset) {
startOffset = extendedStart + 1;
}
}
// include all following whitespace until the end of the line
let endOffset = document.offsetAt(range.end);
let extendedEnd = endOffset;
while (extendedEnd < value.length && isWhitespace(value, extendedEnd)) {
extendedEnd++;
}
if (extendedEnd === value.length || isEOL(value, extendedEnd)) {
endOffset = extendedEnd;
}
range = htmlLanguageTypes_1.Range.create(document.positionAt(startOffset), document.positionAt(endOffset));
// Do not modify if substring starts in inside an element
// Ending inside an element is fine as it doesn't cause formatting errors
const firstHalf = value.substring(0, startOffset);
if (new RegExp(/.*[<][^>]*$/).test(firstHalf)) {
//return without modification
value = value.substring(startOffset, endOffset);
return [{
range: range,
newText: value
}];
}
includesEnd = endOffset === value.length;
value = value.substring(startOffset, endOffset);
if (startOffset !== 0) {
const startOfLineOffset = document.offsetAt(htmlLanguageTypes_1.Position.create(range.start.line, 0));
initialIndentLevel = computeIndentLevel(document.getText(), startOfLineOffset, options);
}
}
else {
range = htmlLanguageTypes_1.Range.create(htmlLanguageTypes_1.Position.create(0, 0), document.positionAt(value.length));
}
const htmlOptions = {
indent_size: tabSize,
indent_char: options.insertSpaces ? ' ' : '\t',
indent_empty_lines: getFormatOption(options, 'indentEmptyLines', false),
wrap_line_length: getFormatOption(options, 'wrapLineLength', 120),
unformatted: getTagsFormatOption(options, 'unformatted', void 0),
content_unformatted: getTagsFormatOption(options, 'contentUnformatted', void 0),
indent_inner_html: getFormatOption(options, 'indentInnerHtml', false),
preserve_newlines: getFormatOption(options, 'preserveNewLines', true),
max_preserve_newlines: getFormatOption(options, 'maxPreserveNewLines', 32786),
indent_handlebars: getFormatOption(options, 'indentHandlebars', false),
end_with_newline: includesEnd && getFormatOption(options, 'endWithNewline', false),
extra_liners: getTagsFormatOption(options, 'extraLiners', void 0),
wrap_attributes: getFormatOption(options, 'wrapAttributes', 'auto'),
wrap_attributes_indent_size: getFormatOption(options, 'wrapAttributesIndentSize', void 0),
eol: '\n',
indent_scripts: getFormatOption(options, 'indentScripts', 'normal'),
templating: getTemplatingFormatOption(options, 'all'),
unformatted_content_delimiter: getFormatOption(options, 'unformattedContentDelimiter', ''),
};
let result = (0, beautify_html_1.html_beautify)(trimLeft(value), htmlOptions);
if (initialIndentLevel > 0) {
const indent = options.insertSpaces ? (0, strings_1.repeat)(' ', tabSize * initialIndentLevel) : (0, strings_1.repeat)('\t', initialIndentLevel);
result = result.split('\n').join('\n' + indent);
if (range.start.character === 0) {
result = indent + result; // keep the indent
}
}
return [{
range: range,
newText: result
}];
}
exports.format = format;
function trimLeft(str) {
return str.replace(/^\s+/, '');
}
function getFormatOption(options, key, dflt) {
if (options && options.hasOwnProperty(key)) {
const value = options[key];
if (value !== null) {
return value;
}
}
return dflt;
}
function getTagsFormatOption(options, key, dflt) {
const list = getFormatOption(options, key, null);
if (typeof list === 'string') {
if (list.length > 0) {
return list.split(',').map(t => t.trim().toLowerCase());
}
return [];
}
return dflt;
}
function getTemplatingFormatOption(options, dflt) {
const value = getFormatOption(options, 'templating', dflt);
if (value === true) {
return ['auto'];
}
return ['none'];
}
function computeIndentLevel(content, offset, options) {
let i = offset;
let nChars = 0;
const tabSize = options.tabSize || 4;
while (i < content.length) {
const ch = content.charAt(i);
if (ch === ' ') {
nChars++;
}
else if (ch === '\t') {
nChars += tabSize;
}
else {
break;
}
i++;
}
return Math.floor(nChars / tabSize);
}
function isEOL(text, offset) {
return '\r\n'.indexOf(text.charAt(offset)) !== -1;
}
function isWhitespace(text, offset) {
return ' \t'.indexOf(text.charAt(offset)) !== -1;
}
});

View file

@ -0,0 +1,56 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/htmlScanner", "../htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findDocumentHighlights = void 0;
const htmlScanner_1 = require("../parser/htmlScanner");
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
function findDocumentHighlights(document, position, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
if (!node.tag) {
return [];
}
const result = [];
const startTagRange = getTagNameRange(htmlLanguageTypes_1.TokenType.StartTag, document, node.start);
const endTagRange = typeof node.endTagStart === 'number' && getTagNameRange(htmlLanguageTypes_1.TokenType.EndTag, document, node.endTagStart);
if (startTagRange && covers(startTagRange, position) || endTagRange && covers(endTagRange, position)) {
if (startTagRange) {
result.push({ kind: htmlLanguageTypes_1.DocumentHighlightKind.Read, range: startTagRange });
}
if (endTagRange) {
result.push({ kind: htmlLanguageTypes_1.DocumentHighlightKind.Read, range: endTagRange });
}
}
return result;
}
exports.findDocumentHighlights = findDocumentHighlights;
function isBeforeOrEqual(pos1, pos2) {
return pos1.line < pos2.line || (pos1.line === pos2.line && pos1.character <= pos2.character);
}
function covers(range, position) {
return isBeforeOrEqual(range.start, position) && isBeforeOrEqual(position, range.end);
}
function getTagNameRange(tokenType, document, startOffset) {
const scanner = (0, htmlScanner_1.createScanner)(document.getText(), startOffset);
let token = scanner.scan();
while (token !== htmlLanguageTypes_1.TokenType.EOS && token !== tokenType) {
token = scanner.scan();
}
if (token !== htmlLanguageTypes_1.TokenType.EOS) {
return { start: document.positionAt(scanner.getTokenOffset()), end: document.positionAt(scanner.getTokenEnd()) };
}
return null;
}
});

View file

@ -0,0 +1,279 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/htmlScanner", "../htmlLanguageTypes", "../utils/object", "../languageFacts/dataProvider", "../parser/htmlEntities", "../utils/strings", "@vscode/l10n"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTMLHover = void 0;
const htmlScanner_1 = require("../parser/htmlScanner");
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
const object_1 = require("../utils/object");
const dataProvider_1 = require("../languageFacts/dataProvider");
const htmlEntities_1 = require("../parser/htmlEntities");
const strings_1 = require("../utils/strings");
const l10n = require("@vscode/l10n");
class HTMLHover {
constructor(lsOptions, dataManager) {
this.lsOptions = lsOptions;
this.dataManager = dataManager;
}
doHover(document, position, htmlDocument, options) {
const convertContents = this.convertContents.bind(this);
const doesSupportMarkdown = this.doesSupportMarkdown();
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
const text = document.getText();
if (!node || !node.tag) {
return null;
}
const dataProviders = this.dataManager.getDataProviders().filter(p => p.isApplicable(document.languageId));
function getTagHover(currTag, range, open) {
for (const provider of dataProviders) {
let hover = null;
provider.provideTags().forEach(tag => {
if (tag.name.toLowerCase() === currTag.toLowerCase()) {
let markupContent = (0, dataProvider_1.generateDocumentation)(tag, options, doesSupportMarkdown);
if (!markupContent) {
markupContent = {
kind: doesSupportMarkdown ? 'markdown' : 'plaintext',
value: ''
};
}
hover = { contents: markupContent, range };
}
});
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getAttrHover(currTag, currAttr, range) {
for (const provider of dataProviders) {
let hover = null;
provider.provideAttributes(currTag).forEach(attr => {
if (currAttr === attr.name && attr.description) {
const contentsDoc = (0, dataProvider_1.generateDocumentation)(attr, options, doesSupportMarkdown);
if (contentsDoc) {
hover = { contents: contentsDoc, range };
}
else {
hover = null;
}
}
});
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getAttrValueHover(currTag, currAttr, currAttrValue, range) {
for (const provider of dataProviders) {
let hover = null;
provider.provideValues(currTag, currAttr).forEach(attrValue => {
if (currAttrValue === attrValue.name && attrValue.description) {
const contentsDoc = (0, dataProvider_1.generateDocumentation)(attrValue, options, doesSupportMarkdown);
if (contentsDoc) {
hover = { contents: contentsDoc, range };
}
else {
hover = null;
}
}
});
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getEntityHover(text, range) {
let currEntity = filterEntity(text);
for (const entity in htmlEntities_1.entities) {
let hover = null;
const label = '&' + entity;
if (currEntity === label) {
let code = htmlEntities_1.entities[entity].charCodeAt(0).toString(16).toUpperCase();
let hex = 'U+';
if (code.length < 4) {
const zeroes = 4 - code.length;
let k = 0;
while (k < zeroes) {
hex += '0';
k += 1;
}
}
hex += code;
const contentsDoc = l10n.t('Character entity representing \'{0}\', unicode equivalent \'{1}\'', htmlEntities_1.entities[entity], hex);
if (contentsDoc) {
hover = { contents: contentsDoc, range };
}
else {
hover = null;
}
}
if (hover) {
hover.contents = convertContents(hover.contents);
return hover;
}
}
return null;
}
function getTagNameRange(tokenType, startOffset) {
const scanner = (0, htmlScanner_1.createScanner)(document.getText(), startOffset);
let token = scanner.scan();
while (token !== htmlLanguageTypes_1.TokenType.EOS && (scanner.getTokenEnd() < offset || scanner.getTokenEnd() === offset && token !== tokenType)) {
token = scanner.scan();
}
if (token === tokenType && offset <= scanner.getTokenEnd()) {
return { start: document.positionAt(scanner.getTokenOffset()), end: document.positionAt(scanner.getTokenEnd()) };
}
return null;
}
function getEntityRange() {
let k = offset - 1;
let characterStart = position.character;
while (k >= 0 && (0, strings_1.isLetterOrDigit)(text, k)) {
k--;
characterStart--;
}
let n = k + 1;
let characterEnd = characterStart;
while ((0, strings_1.isLetterOrDigit)(text, n)) {
n++;
characterEnd++;
}
if (k >= 0 && text[k] === '&') {
let range = null;
if (text[n] === ';') {
range = htmlLanguageTypes_1.Range.create(htmlLanguageTypes_1.Position.create(position.line, characterStart), htmlLanguageTypes_1.Position.create(position.line, characterEnd + 1));
}
else {
range = htmlLanguageTypes_1.Range.create(htmlLanguageTypes_1.Position.create(position.line, characterStart), htmlLanguageTypes_1.Position.create(position.line, characterEnd));
}
return range;
}
return null;
}
function filterEntity(text) {
let k = offset - 1;
let newText = '&';
while (k >= 0 && (0, strings_1.isLetterOrDigit)(text, k)) {
k--;
}
k = k + 1;
while ((0, strings_1.isLetterOrDigit)(text, k)) {
newText += text[k];
k += 1;
}
newText += ';';
return newText;
}
if (node.endTagStart && offset >= node.endTagStart) {
const tagRange = getTagNameRange(htmlLanguageTypes_1.TokenType.EndTag, node.endTagStart);
if (tagRange) {
return getTagHover(node.tag, tagRange, false);
}
return null;
}
const tagRange = getTagNameRange(htmlLanguageTypes_1.TokenType.StartTag, node.start);
if (tagRange) {
return getTagHover(node.tag, tagRange, true);
}
const attrRange = getTagNameRange(htmlLanguageTypes_1.TokenType.AttributeName, node.start);
if (attrRange) {
const tag = node.tag;
const attr = document.getText(attrRange);
return getAttrHover(tag, attr, attrRange);
}
const entityRange = getEntityRange();
if (entityRange) {
return getEntityHover(text, entityRange);
}
function scanAttrAndAttrValue(nodeStart, attrValueStart) {
const scanner = (0, htmlScanner_1.createScanner)(document.getText(), nodeStart);
let token = scanner.scan();
let prevAttr = undefined;
while (token !== htmlLanguageTypes_1.TokenType.EOS && (scanner.getTokenEnd() <= attrValueStart)) {
token = scanner.scan();
if (token === htmlLanguageTypes_1.TokenType.AttributeName) {
prevAttr = scanner.getTokenText();
}
}
return prevAttr;
}
const attrValueRange = getTagNameRange(htmlLanguageTypes_1.TokenType.AttributeValue, node.start);
if (attrValueRange) {
const tag = node.tag;
const attrValue = trimQuotes(document.getText(attrValueRange));
const matchAttr = scanAttrAndAttrValue(node.start, document.offsetAt(attrValueRange.start));
if (matchAttr) {
return getAttrValueHover(tag, matchAttr, attrValue, attrValueRange);
}
}
return null;
}
convertContents(contents) {
if (!this.doesSupportMarkdown()) {
if (typeof contents === 'string') {
return contents;
}
// MarkupContent
else if ('kind' in contents) {
return {
kind: 'plaintext',
value: contents.value
};
}
// MarkedString[]
else if (Array.isArray(contents)) {
contents.map(c => {
return typeof c === 'string' ? c : c.value;
});
}
// MarkedString
else {
return contents.value;
}
}
return contents;
}
doesSupportMarkdown() {
if (!(0, object_1.isDefined)(this.supportsMarkdown)) {
if (!(0, object_1.isDefined)(this.lsOptions.clientCapabilities)) {
this.supportsMarkdown = true;
return this.supportsMarkdown;
}
const contentFormat = this.lsOptions.clientCapabilities?.textDocument?.hover?.contentFormat;
this.supportsMarkdown = Array.isArray(contentFormat) && contentFormat.indexOf(htmlLanguageTypes_1.MarkupKind.Markdown) !== -1;
}
return this.supportsMarkdown;
}
}
exports.HTMLHover = HTMLHover;
function trimQuotes(s) {
if (s.length <= 1) {
return s.replace(/['"]/, '');
}
if (s[0] === `'` || s[0] === `"`) {
s = s.slice(1);
}
if (s[s.length - 1] === `'` || s[s.length - 1] === `"`) {
s = s.slice(0, -1);
}
return s;
}
});

View file

@ -0,0 +1,38 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findLinkedEditingRanges = void 0;
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
function findLinkedEditingRanges(document, position, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
const tagLength = node.tag ? node.tag.length : 0;
if (!node.endTagStart) {
return null;
}
if (
// Within open tag, compute close tag
(node.start + '<'.length <= offset && offset <= node.start + '<'.length + tagLength) ||
// Within closing tag, compute open tag
node.endTagStart + '</'.length <= offset && offset <= node.endTagStart + '</'.length + tagLength) {
return [
htmlLanguageTypes_1.Range.create(document.positionAt(node.start + '<'.length), document.positionAt(node.start + '<'.length + tagLength)),
htmlLanguageTypes_1.Range.create(document.positionAt(node.endTagStart + '</'.length), document.positionAt(node.endTagStart + '</'.length + tagLength))
];
}
return null;
}
exports.findLinkedEditingRanges = findLinkedEditingRanges;
});

View file

@ -0,0 +1,158 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/htmlScanner", "../utils/strings", "vscode-uri", "../htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTMLDocumentLinks = void 0;
const htmlScanner_1 = require("../parser/htmlScanner");
const strings = require("../utils/strings");
const vscode_uri_1 = require("vscode-uri");
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
function normalizeRef(url) {
const first = url[0];
const last = url[url.length - 1];
if (first === last && (first === '\'' || first === '\"')) {
url = url.substring(1, url.length - 1);
}
return url;
}
function validateRef(url, languageId) {
if (!url.length) {
return false;
}
if (languageId === 'handlebars' && /{{|}}/.test(url)) {
return false;
}
return /\b(w[\w\d+.-]*:\/\/)?[^\s()<>]+(?:\([\w\d]+\)|([^[:punct:]\s]|\/?))/.test(url);
}
function getWorkspaceUrl(documentUri, tokenContent, documentContext, base) {
if (/^\s*javascript\:/i.test(tokenContent) || /[\n\r]/.test(tokenContent)) {
return undefined;
}
tokenContent = tokenContent.replace(/^\s*/g, '');
const match = tokenContent.match(/^(\w[\w\d+.-]*):/);
if (match) {
// Absolute link that needs no treatment
const schema = match[1].toLowerCase();
if (schema === 'http' || schema === 'https' || schema === 'file') {
return tokenContent;
}
return undefined;
}
if (/^\#/i.test(tokenContent)) {
return documentUri + tokenContent;
}
if (/^\/\//i.test(tokenContent)) {
// Absolute link (that does not name the protocol)
const pickedScheme = strings.startsWith(documentUri, 'https://') ? 'https' : 'http';
return pickedScheme + ':' + tokenContent.replace(/^\s*/g, '');
}
if (documentContext) {
return documentContext.resolveReference(tokenContent, base || documentUri);
}
return tokenContent;
}
function createLink(document, documentContext, attributeValue, startOffset, endOffset, base) {
const tokenContent = normalizeRef(attributeValue);
if (!validateRef(tokenContent, document.languageId)) {
return undefined;
}
if (tokenContent.length < attributeValue.length) {
startOffset++;
endOffset--;
}
const workspaceUrl = getWorkspaceUrl(document.uri, tokenContent, documentContext, base);
if (!workspaceUrl || !isValidURI(workspaceUrl)) {
return undefined;
}
return {
range: htmlLanguageTypes_1.Range.create(document.positionAt(startOffset), document.positionAt(endOffset)),
target: workspaceUrl
};
}
function isValidURI(uri) {
try {
vscode_uri_1.URI.parse(uri);
return true;
}
catch (e) {
return false;
}
}
class HTMLDocumentLinks {
constructor(dataManager) {
this.dataManager = dataManager;
}
findDocumentLinks(document, documentContext) {
const newLinks = [];
const scanner = (0, htmlScanner_1.createScanner)(document.getText(), 0);
let token = scanner.scan();
let lastAttributeName = undefined;
let lastTagName = undefined;
let afterBase = false;
let base = void 0;
const idLocations = {};
while (token !== htmlLanguageTypes_1.TokenType.EOS) {
switch (token) {
case htmlLanguageTypes_1.TokenType.StartTag:
lastTagName = scanner.getTokenText().toLowerCase();
if (!base) {
afterBase = lastTagName === 'base';
}
break;
case htmlLanguageTypes_1.TokenType.AttributeName:
lastAttributeName = scanner.getTokenText().toLowerCase();
break;
case htmlLanguageTypes_1.TokenType.AttributeValue:
if (lastTagName && lastAttributeName && this.dataManager.isPathAttribute(lastTagName, lastAttributeName)) {
const attributeValue = scanner.getTokenText();
if (!afterBase) { // don't highlight the base link itself
const link = createLink(document, documentContext, attributeValue, scanner.getTokenOffset(), scanner.getTokenEnd(), base);
if (link) {
newLinks.push(link);
}
}
if (afterBase && typeof base === 'undefined') {
base = normalizeRef(attributeValue);
if (base && documentContext) {
base = documentContext.resolveReference(base, document.uri);
}
}
afterBase = false;
lastAttributeName = undefined;
}
else if (lastAttributeName === 'id') {
const id = normalizeRef(scanner.getTokenText());
idLocations[id] = scanner.getTokenOffset();
}
break;
}
token = scanner.scan();
}
// change local links with ids to actual positions
for (const link of newLinks) {
const localWithHash = document.uri + '#';
if (link.target && strings.startsWith(link.target, localWithHash)) {
const target = link.target.substring(localWithHash.length);
const offset = idLocations[target];
if (offset !== undefined) {
const pos = document.positionAt(offset);
link.target = `${localWithHash}${pos.line + 1},${pos.character + 1}`;
}
}
}
return newLinks;
}
}
exports.HTMLDocumentLinks = HTMLDocumentLinks;
});

View file

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findMatchingTagPosition = void 0;
function findMatchingTagPosition(document, position, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
if (!node.tag) {
return null;
}
if (!node.endTagStart) {
return null;
}
// Within open tag, compute close tag
if (node.start + '<'.length <= offset && offset <= node.start + '<'.length + node.tag.length) {
const mirrorOffset = (offset - '<'.length - node.start) + node.endTagStart + '</'.length;
return document.positionAt(mirrorOffset);
}
// Within closing tag, compute open tag
if (node.endTagStart + '</'.length <= offset && offset <= node.endTagStart + '</'.length + node.tag.length) {
const mirrorOffset = (offset - '</'.length - node.endTagStart) + node.start + '<'.length;
return document.positionAt(mirrorOffset);
}
return null;
}
exports.findMatchingTagPosition = findMatchingTagPosition;
});

View file

@ -0,0 +1,65 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.doRename = void 0;
function doRename(document, position, newName, htmlDocument) {
const offset = document.offsetAt(position);
const node = htmlDocument.findNodeAt(offset);
if (!node.tag) {
return null;
}
if (!isWithinTagRange(node, offset, node.tag)) {
return null;
}
const edits = [];
const startTagRange = {
start: document.positionAt(node.start + '<'.length),
end: document.positionAt(node.start + '<'.length + node.tag.length)
};
edits.push({
range: startTagRange,
newText: newName
});
if (node.endTagStart) {
const endTagRange = {
start: document.positionAt(node.endTagStart + '</'.length),
end: document.positionAt(node.endTagStart + '</'.length + node.tag.length)
};
edits.push({
range: endTagRange,
newText: newName
});
}
const changes = {
[document.uri.toString()]: edits
};
return {
changes
};
}
exports.doRename = doRename;
function toLocString(p) {
return `(${p.line}, ${p.character})`;
}
function isWithinTagRange(node, offset, nodeTag) {
// Self-closing tag
if (node.endTagStart) {
if (node.endTagStart + '</'.length <= offset && offset <= node.endTagStart + '</'.length + nodeTag.length) {
return true;
}
}
return node.start + '<'.length <= offset && offset <= node.start + '<'.length + nodeTag.length;
}
});

View file

@ -0,0 +1,188 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../parser/htmlScanner", "../htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HTMLSelectionRange = void 0;
const htmlScanner_1 = require("../parser/htmlScanner");
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
class HTMLSelectionRange {
constructor(htmlParser) {
this.htmlParser = htmlParser;
}
getSelectionRanges(document, positions) {
const htmlDocument = this.htmlParser.parseDocument(document);
return positions.map(p => this.getSelectionRange(p, document, htmlDocument));
}
getSelectionRange(position, document, htmlDocument) {
const applicableRanges = this.getApplicableRanges(document, position, htmlDocument);
let prev = undefined;
let current = undefined;
for (let index = applicableRanges.length - 1; index >= 0; index--) {
const range = applicableRanges[index];
if (!prev || range[0] !== prev[0] || range[1] !== prev[1]) {
current = htmlLanguageTypes_1.SelectionRange.create(htmlLanguageTypes_1.Range.create(document.positionAt(applicableRanges[index][0]), document.positionAt(applicableRanges[index][1])), current);
}
prev = range;
}
if (!current) {
current = htmlLanguageTypes_1.SelectionRange.create(htmlLanguageTypes_1.Range.create(position, position));
}
return current;
}
getApplicableRanges(document, position, htmlDoc) {
const currOffset = document.offsetAt(position);
const currNode = htmlDoc.findNodeAt(currOffset);
let result = this.getAllParentTagRanges(currNode);
// Self-closing or void elements
if (currNode.startTagEnd && !currNode.endTagStart) {
// THe rare case of unmatching tag pairs like <div></div1>
if (currNode.startTagEnd !== currNode.end) {
return [[currNode.start, currNode.end]];
}
const closeRange = htmlLanguageTypes_1.Range.create(document.positionAt(currNode.startTagEnd - 2), document.positionAt(currNode.startTagEnd));
const closeText = document.getText(closeRange);
// Self-closing element
if (closeText === '/>') {
result.unshift([currNode.start + 1, currNode.startTagEnd - 2]);
}
// Void element
else {
result.unshift([currNode.start + 1, currNode.startTagEnd - 1]);
}
const attributeLevelRanges = this.getAttributeLevelRanges(document, currNode, currOffset);
result = attributeLevelRanges.concat(result);
return result;
}
if (!currNode.startTagEnd || !currNode.endTagStart) {
return result;
}
/**
* For html like
* `<div class="foo">bar</div>`
*/
result.unshift([currNode.start, currNode.end]);
/**
* Cursor inside `<div class="foo">`
*/
if (currNode.start < currOffset && currOffset < currNode.startTagEnd) {
result.unshift([currNode.start + 1, currNode.startTagEnd - 1]);
const attributeLevelRanges = this.getAttributeLevelRanges(document, currNode, currOffset);
result = attributeLevelRanges.concat(result);
return result;
}
/**
* Cursor inside `bar`
*/
else if (currNode.startTagEnd <= currOffset && currOffset <= currNode.endTagStart) {
result.unshift([currNode.startTagEnd, currNode.endTagStart]);
return result;
}
/**
* Cursor inside `</div>`
*/
else {
// `div` inside `</div>`
if (currOffset >= currNode.endTagStart + 2) {
result.unshift([currNode.endTagStart + 2, currNode.end - 1]);
}
return result;
}
}
getAllParentTagRanges(initialNode) {
let currNode = initialNode;
const result = [];
while (currNode.parent) {
currNode = currNode.parent;
this.getNodeRanges(currNode).forEach(r => result.push(r));
}
return result;
}
getNodeRanges(n) {
if (n.startTagEnd && n.endTagStart && n.startTagEnd < n.endTagStart) {
return [
[n.startTagEnd, n.endTagStart],
[n.start, n.end]
];
}
return [
[n.start, n.end]
];
}
;
getAttributeLevelRanges(document, currNode, currOffset) {
const currNodeRange = htmlLanguageTypes_1.Range.create(document.positionAt(currNode.start), document.positionAt(currNode.end));
const currNodeText = document.getText(currNodeRange);
const relativeOffset = currOffset - currNode.start;
/**
* Tag level semantic selection
*/
const scanner = (0, htmlScanner_1.createScanner)(currNodeText);
let token = scanner.scan();
/**
* For text like
* <div class="foo">bar</div>
*/
const positionOffset = currNode.start;
const result = [];
let isInsideAttribute = false;
let attrStart = -1;
while (token !== htmlLanguageTypes_1.TokenType.EOS) {
switch (token) {
case htmlLanguageTypes_1.TokenType.AttributeName: {
if (relativeOffset < scanner.getTokenOffset()) {
isInsideAttribute = false;
break;
}
if (relativeOffset <= scanner.getTokenEnd()) {
// `class`
result.unshift([scanner.getTokenOffset(), scanner.getTokenEnd()]);
}
isInsideAttribute = true;
attrStart = scanner.getTokenOffset();
break;
}
case htmlLanguageTypes_1.TokenType.AttributeValue: {
if (!isInsideAttribute) {
break;
}
const valueText = scanner.getTokenText();
if (relativeOffset < scanner.getTokenOffset()) {
// `class="foo"`
result.push([attrStart, scanner.getTokenEnd()]);
break;
}
if (relativeOffset >= scanner.getTokenOffset() && relativeOffset <= scanner.getTokenEnd()) {
// `"foo"`
result.unshift([scanner.getTokenOffset(), scanner.getTokenEnd()]);
// `foo`
if ((valueText[0] === `"` && valueText[valueText.length - 1] === `"`) || (valueText[0] === `'` && valueText[valueText.length - 1] === `'`)) {
if (relativeOffset >= scanner.getTokenOffset() + 1 && relativeOffset <= scanner.getTokenEnd() - 1) {
result.unshift([scanner.getTokenOffset() + 1, scanner.getTokenEnd() - 1]);
}
}
// `class="foo"`
result.push([attrStart, scanner.getTokenEnd()]);
}
break;
}
}
token = scanner.scan();
}
return result.map(pair => {
return [pair[0] + positionOffset, pair[1] + positionOffset];
});
}
}
exports.HTMLSelectionRange = HTMLSelectionRange;
});

View file

@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../htmlLanguageTypes"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findDocumentSymbols = void 0;
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
function findDocumentSymbols(document, htmlDocument) {
const symbols = [];
htmlDocument.roots.forEach(node => {
provideFileSymbolsInternal(document, node, '', symbols);
});
return symbols;
}
exports.findDocumentSymbols = findDocumentSymbols;
function provideFileSymbolsInternal(document, node, container, symbols) {
const name = nodeToName(node);
const location = htmlLanguageTypes_1.Location.create(document.uri, htmlLanguageTypes_1.Range.create(document.positionAt(node.start), document.positionAt(node.end)));
const symbol = {
name: name,
location: location,
containerName: container,
kind: htmlLanguageTypes_1.SymbolKind.Field
};
symbols.push(symbol);
node.children.forEach(child => {
provideFileSymbolsInternal(document, child, name, symbols);
});
}
function nodeToName(node) {
let name = node.tag;
if (node.attributes) {
const id = node.attributes['id'];
const classes = node.attributes['class'];
if (id) {
name += `#${id.replace(/[\"\']/g, '')}`;
}
if (classes) {
name += classes.replace(/[\"\']/g, '').split(/\s+/).map(className => `.${className}`).join('');
}
}
return name || '?';
}
});

View file

@ -0,0 +1,140 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "../htmlLanguageTypes", "../utils/strings"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PathCompletionParticipant = void 0;
const htmlLanguageTypes_1 = require("../htmlLanguageTypes");
const strings_1 = require("../utils/strings");
class PathCompletionParticipant {
constructor(dataManager, readDirectory) {
this.dataManager = dataManager;
this.readDirectory = readDirectory;
this.atributeCompletions = [];
}
onHtmlAttributeValue(context) {
if (this.dataManager.isPathAttribute(context.tag, context.attribute)) {
this.atributeCompletions.push(context);
}
}
async computeCompletions(document, documentContext) {
const result = { items: [], isIncomplete: false };
for (const attributeCompletion of this.atributeCompletions) {
const fullValue = stripQuotes(document.getText(attributeCompletion.range));
if (isCompletablePath(fullValue)) {
if (fullValue === '.' || fullValue === '..') {
result.isIncomplete = true;
}
else {
const replaceRange = pathToReplaceRange(attributeCompletion.value, fullValue, attributeCompletion.range);
const suggestions = await this.providePathSuggestions(attributeCompletion.value, replaceRange, document, documentContext);
for (const item of suggestions) {
result.items.push(item);
}
}
}
}
return result;
}
async providePathSuggestions(valueBeforeCursor, replaceRange, document, documentContext) {
const valueBeforeLastSlash = valueBeforeCursor.substring(0, valueBeforeCursor.lastIndexOf('/') + 1); // keep the last slash
let parentDir = documentContext.resolveReference(valueBeforeLastSlash || '.', document.uri);
if (parentDir) {
try {
const result = [];
const infos = await this.readDirectory(parentDir);
for (const [name, type] of infos) {
// Exclude paths that start with `.`
if (name.charCodeAt(0) !== CharCode_dot) {
result.push(createCompletionItem(name, type === htmlLanguageTypes_1.FileType.Directory, replaceRange));
}
}
return result;
}
catch (e) {
// ignore
}
}
return [];
}
}
exports.PathCompletionParticipant = PathCompletionParticipant;
const CharCode_dot = '.'.charCodeAt(0);
function stripQuotes(fullValue) {
if ((0, strings_1.startsWith)(fullValue, `'`) || (0, strings_1.startsWith)(fullValue, `"`)) {
return fullValue.slice(1, -1);
}
else {
return fullValue;
}
}
function isCompletablePath(value) {
if ((0, strings_1.startsWith)(value, 'http') || (0, strings_1.startsWith)(value, 'https') || (0, strings_1.startsWith)(value, '//')) {
return false;
}
return true;
}
function pathToReplaceRange(valueBeforeCursor, fullValue, range) {
let replaceRange;
const lastIndexOfSlash = valueBeforeCursor.lastIndexOf('/');
if (lastIndexOfSlash === -1) {
replaceRange = shiftRange(range, 1, -1);
}
else {
// For cases where cursor is in the middle of attribute value, like <script src="./s|rc/test.js">
// Find the last slash before cursor, and calculate the start of replace range from there
const valueAfterLastSlash = fullValue.slice(lastIndexOfSlash + 1);
const startPos = shiftPosition(range.end, -1 - valueAfterLastSlash.length);
// If whitespace exists, replace until there is no more
const whitespaceIndex = valueAfterLastSlash.indexOf(' ');
let endPos;
if (whitespaceIndex !== -1) {
endPos = shiftPosition(startPos, whitespaceIndex);
}
else {
endPos = shiftPosition(range.end, -1);
}
replaceRange = htmlLanguageTypes_1.Range.create(startPos, endPos);
}
return replaceRange;
}
function createCompletionItem(p, isDir, replaceRange) {
if (isDir) {
p = p + '/';
return {
label: p,
kind: htmlLanguageTypes_1.CompletionItemKind.Folder,
textEdit: htmlLanguageTypes_1.TextEdit.replace(replaceRange, p),
command: {
title: 'Suggest',
command: 'editor.action.triggerSuggest'
}
};
}
else {
return {
label: p,
kind: htmlLanguageTypes_1.CompletionItemKind.File,
textEdit: htmlLanguageTypes_1.TextEdit.replace(replaceRange, p)
};
}
}
function shiftPosition(pos, offset) {
return htmlLanguageTypes_1.Position.create(pos.line, pos.character + offset);
}
function shiftRange(range, startOffset, endOffset) {
const start = shiftPosition(range.start, startOffset);
const end = shiftPosition(range.end, endOffset);
return htmlLanguageTypes_1.Range.create(start, end);
}
});

View file

@ -0,0 +1,57 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.binarySearch = exports.findFirst = void 0;
/**
* Takes a sorted array and a function p. The array is sorted in such a way that all elements where p(x) is false
* are located before all elements where p(x) is true.
* @returns the least x for which p(x) is true or array.length if no element fullfills the given function.
*/
function findFirst(array, p) {
let low = 0, high = array.length;
if (high === 0) {
return 0; // no children
}
while (low < high) {
let mid = Math.floor((low + high) / 2);
if (p(array[mid])) {
high = mid;
}
else {
low = mid + 1;
}
}
return low;
}
exports.findFirst = findFirst;
function binarySearch(array, key, comparator) {
let low = 0, high = array.length - 1;
while (low <= high) {
const mid = ((low + high) / 2) | 0;
const comp = comparator(array[mid], key);
if (comp < 0) {
low = mid + 1;
}
else if (comp > 0) {
high = mid - 1;
}
else {
return mid;
}
}
return -(low + 1);
}
exports.binarySearch = binarySearch;
});

View file

@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeMarkupContent = void 0;
function normalizeMarkupContent(input) {
if (!input) {
return undefined;
}
if (typeof input === 'string') {
return {
kind: 'markdown',
value: input
};
}
return {
kind: 'markdown',
value: input.value
};
}
exports.normalizeMarkupContent = normalizeMarkupContent;
});

View file

@ -0,0 +1,21 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isDefined = void 0;
function isDefined(obj) {
return typeof obj !== 'undefined';
}
exports.isDefined = isDefined;
});

View file

@ -0,0 +1,81 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.join = exports.extname = exports.basename = exports.dirname = void 0;
/**
* @returns the directory name of a path.
*/
function dirname(path) {
const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
if (idx === 0) {
return '.';
}
else if (~idx === 0) {
return path[0];
}
else {
return path.substring(0, ~idx);
}
}
exports.dirname = dirname;
/**
* @returns the base name of a path.
*/
function basename(path) {
const idx = ~path.lastIndexOf('/') || ~path.lastIndexOf('\\');
if (idx === 0) {
return path;
}
else if (~idx === path.length - 1) {
return basename(path.substring(0, path.length - 1));
}
else {
return path.substr(~idx + 1);
}
}
exports.basename = basename;
/**
* @returns {{.far}} from boo.far or the empty string.
*/
function extname(path) {
path = basename(path);
const idx = ~path.lastIndexOf('.');
return idx ? path.substring(~idx) : '';
}
exports.extname = extname;
const join = function () {
// Not using a function with var-args because of how TS compiles
// them to JS - it would result in 2*n runtime cost instead
// of 1*n, where n is parts.length.
let value = '';
for (let i = 0; i < arguments.length; i++) {
const part = arguments[i];
if (i > 0) {
// add the separater between two parts unless
// there already is one
const last = value.charCodeAt(value.length - 1);
if (last !== 47 /* CharCode.Slash */ && last !== 92 /* CharCode.Backslash */) {
const next = part.charCodeAt(0);
if (next !== 47 /* CharCode.Slash */ && next !== 92 /* CharCode.Backslash */) {
value += '/';
}
}
}
value += part;
}
return value;
};
exports.join = join;
});

View file

@ -0,0 +1,93 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports", "vscode-uri"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.joinPath = exports.normalizePath = exports.resolvePath = exports.extname = exports.basename = exports.dirname = exports.isAbsolutePath = void 0;
const vscode_uri_1 = require("vscode-uri");
const Slash = '/'.charCodeAt(0);
const Dot = '.'.charCodeAt(0);
function isAbsolutePath(path) {
return path.charCodeAt(0) === Slash;
}
exports.isAbsolutePath = isAbsolutePath;
function dirname(uri) {
const lastIndexOfSlash = uri.lastIndexOf('/');
return lastIndexOfSlash !== -1 ? uri.substr(0, lastIndexOfSlash) : '';
}
exports.dirname = dirname;
function basename(uri) {
const lastIndexOfSlash = uri.lastIndexOf('/');
return uri.substr(lastIndexOfSlash + 1);
}
exports.basename = basename;
function extname(uri) {
for (let i = uri.length - 1; i >= 0; i--) {
const ch = uri.charCodeAt(i);
if (ch === Dot) {
if (i > 0 && uri.charCodeAt(i - 1) !== Slash) {
return uri.substr(i);
}
else {
break;
}
}
else if (ch === Slash) {
break;
}
}
return '';
}
exports.extname = extname;
function resolvePath(uriString, path) {
if (isAbsolutePath(path)) {
const uri = vscode_uri_1.URI.parse(uriString);
const parts = path.split('/');
return uri.with({ path: normalizePath(parts) }).toString();
}
return joinPath(uriString, path);
}
exports.resolvePath = resolvePath;
function normalizePath(parts) {
const newParts = [];
for (const part of parts) {
if (part.length === 0 || part.length === 1 && part.charCodeAt(0) === Dot) {
// ignore
}
else if (part.length === 2 && part.charCodeAt(0) === Dot && part.charCodeAt(1) === Dot) {
newParts.pop();
}
else {
newParts.push(part);
}
}
if (parts.length > 1 && parts[parts.length - 1].length === 0) {
newParts.push('');
}
let res = newParts.join('/');
if (parts[0].length === 0) {
res = '/' + res;
}
return res;
}
exports.normalizePath = normalizePath;
function joinPath(uriString, ...paths) {
const uri = vscode_uri_1.URI.parse(uriString);
const parts = uri.path.split('/');
for (let path of paths) {
parts.push(...path.split('/'));
}
return uri.with({ path: normalizePath(parts) }).toString();
}
exports.joinPath = joinPath;
});

View file

@ -0,0 +1,82 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
}
else if (typeof define === "function" && define.amd) {
define(["require", "exports"], factory);
}
})(function (require, exports) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isLetterOrDigit = exports.repeat = exports.commonPrefixLength = exports.endsWith = exports.startsWith = void 0;
function startsWith(haystack, needle) {
if (haystack.length < needle.length) {
return false;
}
for (let i = 0; i < needle.length; i++) {
if (haystack[i] !== needle[i]) {
return false;
}
}
return true;
}
exports.startsWith = startsWith;
/**
* Determines if haystack ends with needle.
*/
function endsWith(haystack, needle) {
const diff = haystack.length - needle.length;
if (diff > 0) {
return haystack.lastIndexOf(needle) === diff;
}
else if (diff === 0) {
return haystack === needle;
}
else {
return false;
}
}
exports.endsWith = endsWith;
/**
* @returns the length of the common prefix of the two strings.
*/
function commonPrefixLength(a, b) {
let i;
const len = Math.min(a.length, b.length);
for (i = 0; i < len; i++) {
if (a.charCodeAt(i) !== b.charCodeAt(i)) {
return i;
}
}
return len;
}
exports.commonPrefixLength = commonPrefixLength;
function repeat(value, count) {
let s = '';
while (count > 0) {
if ((count & 1) === 1) {
s += value;
}
value += value;
count = count >>> 1;
}
return s;
}
exports.repeat = repeat;
const _a = 'a'.charCodeAt(0);
const _z = 'z'.charCodeAt(0);
const _A = 'A'.charCodeAt(0);
const _Z = 'Z'.charCodeAt(0);
const _0 = '0'.charCodeAt(0);
const _9 = '9'.charCodeAt(0);
function isLetterOrDigit(text, index) {
const c = text.charCodeAt(index);
return (_a <= c && c <= _z) || (_A <= c && c <= _Z) || (_0 <= c && c <= _9);
}
exports.isLetterOrDigit = isLetterOrDigit;
});