Revamping to matrix style

This commit is contained in:
2026-02-16 16:37:35 -05:00
parent 71852ec99a
commit 9d0e3938e4
14958 changed files with 2089572 additions and 114 deletions

View File

@@ -0,0 +1,17 @@
import type * as vscode from '@volar/language-service';
import type { Node } from 'EmmetFlatNode';
/**
* Checks if given position is a valid location to expand emmet abbreviation.
* Works only on html and css/less/scss syntax
* @param document current Text Document
* @param rootNode parsed document
* @param currentNode current node in the parsed document
* @param syntax syntax of the abbreviation
* @param position position to validate
* @param abbreviationRange The range of the abbreviation for which given position is being validated
*/
export declare function isValidLocationForEmmetAbbreviation(context: vscode.LanguageServiceContext, document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | undefined, syntax: string, offset: number, abbreviationRange: vscode.Range): Promise<boolean>;
export declare function getSyntaxFromArgs(context: vscode.LanguageServiceContext, args: {
[x: string]: string;
}): Promise<string | undefined>;
//# sourceMappingURL=abbreviationActions.d.ts.map

View File

@@ -0,0 +1,199 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidLocationForEmmetAbbreviation = isValidLocationForEmmetAbbreviation;
exports.getSyntaxFromArgs = getSyntaxFromArgs;
const util_1 = require("./util");
const hexColorRegex = /^#[\da-fA-F]{0,6}$/;
/**
* Checks if given position is a valid location to expand emmet abbreviation.
* Works only on html and css/less/scss syntax
* @param document current Text Document
* @param rootNode parsed document
* @param currentNode current node in the parsed document
* @param syntax syntax of the abbreviation
* @param position position to validate
* @param abbreviationRange The range of the abbreviation for which given position is being validated
*/
async function isValidLocationForEmmetAbbreviation(context, document, rootNode, currentNode, syntax, offset, abbreviationRange) {
if ((0, util_1.isStyleSheet)(syntax)) {
const stylesheet = rootNode;
if (stylesheet && (stylesheet.comments || []).some(x => offset >= x.start && offset <= x.end)) {
return false;
}
// Continue validation only if the file was parse-able and the currentNode has been found
if (!currentNode) {
return true;
}
// Get the abbreviation right now
// Fixes https://github.com/microsoft/vscode/issues/74505
// Stylesheet abbreviations starting with @ should bring up suggestions
// even at outer-most level
const abbreviation = document.getText(abbreviationRange);
if (abbreviation.startsWith('@')) {
return true;
}
// Fix for https://github.com/microsoft/vscode/issues/34162
// Other than sass, stylus, we can make use of the terminator tokens to validate position
if (syntax !== 'sass' && syntax !== 'stylus' && currentNode.type === 'property') {
// Fix for upstream issue https://github.com/emmetio/css-parser/issues/3
if (currentNode.parent
&& currentNode.parent.type !== 'rule'
&& currentNode.parent.type !== 'at-rule') {
return false;
}
const propertyNode = currentNode;
if (propertyNode.terminatorToken
&& propertyNode.separator
&& offset >= propertyNode.separatorToken.end
&& offset <= propertyNode.terminatorToken.start
&& !abbreviation.includes(':')) {
return hexColorRegex.test(abbreviation) || abbreviation === '!';
}
if (!propertyNode.terminatorToken
&& propertyNode.separator
&& offset >= propertyNode.separatorToken.end
&& !abbreviation.includes(':')) {
return hexColorRegex.test(abbreviation) || abbreviation === '!';
}
if (hexColorRegex.test(abbreviation) || abbreviation === '!') {
return false;
}
}
// If current node is a rule or at-rule, then perform additional checks to ensure
// emmet suggestions are not provided in the rule selector
if (currentNode.type !== 'rule' && currentNode.type !== 'at-rule') {
return true;
}
const currentCssNode = currentNode;
// Position is valid if it occurs after the `{` that marks beginning of rule contents
if (offset > currentCssNode.contentStartToken.end) {
return true;
}
// Workaround for https://github.com/microsoft/vscode/30188
// The line above the rule selector is considered as part of the selector by the css-parser
// But we should assume it is a valid location for css properties under the parent rule
if (currentCssNode.parent
&& (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule')
&& currentCssNode.selectorToken) {
const position = document.positionAt(offset);
const tokenStartPos = document.positionAt(currentCssNode.selectorToken.start);
const tokenEndPos = document.positionAt(currentCssNode.selectorToken.end);
if (position.line !== tokenEndPos.line
&& tokenStartPos.character === abbreviationRange.start.character
&& tokenStartPos.line === abbreviationRange.start.line) {
return true;
}
}
return false;
}
const startAngle = '<';
const endAngle = '>';
const escape = '\\';
const question = '?';
const currentHtmlNode = currentNode;
let start = 0;
if (currentHtmlNode) {
if (currentHtmlNode.name === 'script') {
const typeAttribute = (currentHtmlNode.attributes || []).filter(x => x.name.toString() === 'type')[0];
const typeValue = typeAttribute ? typeAttribute.value.toString() : '';
if (util_1.allowedMimeTypesInScriptTag.includes(typeValue)) {
return true;
}
const isScriptJavascriptType = !typeValue || typeValue === 'application/javascript' || typeValue === 'text/javascript';
if (isScriptJavascriptType) {
return !!await getSyntaxFromArgs(context, { language: 'javascript' });
}
return false;
}
// Fix for https://github.com/microsoft/vscode/issues/28829
if (!currentHtmlNode.open || !currentHtmlNode.close ||
!(currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start)) {
return false;
}
// Fix for https://github.com/microsoft/vscode/issues/35128
// Find the position up till where we will backtrack looking for unescaped < or >
// to decide if current position is valid for emmet expansion
start = currentHtmlNode.open.end;
let lastChildBeforePosition = currentHtmlNode.firstChild;
while (lastChildBeforePosition) {
if (lastChildBeforePosition.end > offset) {
break;
}
start = lastChildBeforePosition.end;
lastChildBeforePosition = lastChildBeforePosition.nextSibling;
}
}
const startPos = document.positionAt(start);
let textToBackTrack = document.getText({ start: startPos, end: abbreviationRange.start });
// Worse case scenario is when cursor is inside a big chunk of text which needs to backtracked
// Backtrack only 500 offsets to ensure we dont waste time doing this
if (textToBackTrack.length > 500) {
textToBackTrack = textToBackTrack.substr(textToBackTrack.length - 500);
}
if (!textToBackTrack.trim()) {
return true;
}
let valid = true;
let foundSpace = false; // If < is found before finding whitespace, then its valid abbreviation. E.g.: <div|
let i = textToBackTrack.length - 1;
if (textToBackTrack[i] === startAngle) {
return false;
}
while (i >= 0) {
const char = textToBackTrack[i];
i--;
if (!foundSpace && /\s/.test(char)) {
foundSpace = true;
continue;
}
if (char === question && textToBackTrack[i] === startAngle) {
i--;
continue;
}
// Fix for https://github.com/microsoft/vscode/issues/55411
// A space is not a valid character right after < in a tag name.
if (/\s/.test(char) && textToBackTrack[i] === startAngle) {
i--;
continue;
}
if (char !== startAngle && char !== endAngle) {
continue;
}
if (i >= 0 && textToBackTrack[i] === escape) {
i--;
continue;
}
if (char === endAngle) {
if (i >= 0 && textToBackTrack[i] === '=') {
continue; // False alarm of cases like =>
}
else {
break;
}
}
if (char === startAngle) {
valid = !foundSpace;
break;
}
}
return valid;
}
async function getSyntaxFromArgs(context, args) {
const mappedModes = await (0, util_1.getMappingForIncludedLanguages)(context);
const language = args['language'];
const parentMode = args['parentMode'];
const excludedLanguages = await context.env.getConfiguration?.('emmet.excludeLanguages') ?? [];
if (excludedLanguages.includes(language)) {
return;
}
let syntax = (0, util_1.getEmmetMode)(mappedModes[language] ?? language, mappedModes, excludedLanguages);
if (!syntax) {
syntax = (0, util_1.getEmmetMode)(mappedModes[parentMode] ?? parentMode, mappedModes, excludedLanguages);
}
return syntax;
}
//# sourceMappingURL=abbreviationActions.js.map

66
node_modules/volar-service-emmet/lib/bufferStream.d.ts generated vendored Normal file
View File

@@ -0,0 +1,66 @@
import type { TextDocument } from '@volar/language-service';
/**
* A stream reader for VSCode's `TextDocument`
* Based on @emmetio/stream-reader and @emmetio/atom-plugin
*/
export declare class DocumentStreamReader {
private document;
private start;
private _eof;
private _sof;
pos: number;
constructor(document: TextDocument, pos?: number, limit?: [number, number]);
/**
* Returns true only if the stream is at the start of the file.
*/
sof(): boolean;
/**
* Returns true only if the stream is at the end of the file.
*/
eof(): boolean;
/**
* Creates a new stream instance which is limited to given range for given document
*/
limit(start: number, end: number): DocumentStreamReader;
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
*/
peek(): number;
/**
* Returns the next character in the stream and advances it.
* Also returns NaN when no more characters are available.
*/
next(): number;
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n: number): number;
/**
* Get the string between the start of the current token and the
* current stream position.
*/
current(): string;
/**
* Returns contents for given range
*/
substring(from: number, to: number): string;
/**
* Creates error object with current stream state
*/
error(message: string): Error;
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
*/
eat(match: number | Function): boolean;
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
*/
eatWhile(match: number | Function): boolean;
}
//# sourceMappingURL=bufferStream.d.ts.map

119
node_modules/volar-service-emmet/lib/bufferStream.js generated vendored Normal file
View File

@@ -0,0 +1,119 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.DocumentStreamReader = void 0;
/**
* A stream reader for VSCode's `TextDocument`
* Based on @emmetio/stream-reader and @emmetio/atom-plugin
*/
class DocumentStreamReader {
constructor(document, pos, limit) {
this.document = document;
this.start = this.pos = pos ? pos : 0;
this._sof = limit ? limit[0] : 0;
this._eof = limit ? limit[1] : document.getText().length;
}
/**
* Returns true only if the stream is at the start of the file.
*/
sof() {
return this.pos <= this._sof;
}
/**
* Returns true only if the stream is at the end of the file.
*/
eof() {
return this.pos >= this._eof;
}
/**
* Creates a new stream instance which is limited to given range for given document
*/
limit(start, end) {
return new DocumentStreamReader(this.document, start, [start, end]);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
*/
peek() {
if (this.eof()) {
return NaN;
}
return this.document.getText().charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns NaN when no more characters are available.
*/
next() {
if (this.eof()) {
return NaN;
}
const code = this.document.getText().charCodeAt(this.pos);
this.pos++;
if (this.eof()) {
// restrict pos to eof, if in case it got moved beyond eof
this.pos = this._eof;
}
return code;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n) {
this.pos -= n;
if (this.pos < 0) {
this.pos = 0;
}
return this.peek();
}
/**
* Get the string between the start of the current token and the
* current stream position.
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns contents for given range
*/
substring(from, to) {
return this.document.getText().substring(from, to);
}
/**
* Creates error object with current stream state
*/
error(message) {
const err = new Error(`${message} at offset ${this.pos}`);
return err;
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) { }
return this.pos !== start;
}
}
exports.DocumentStreamReader = DocumentStreamReader;
//# sourceMappingURL=bufferStream.js.map

View File

@@ -0,0 +1,4 @@
import type { TextDocument } from '@volar/language-service';
import type { Node as FlatNode } from 'EmmetFlatNode';
export declare function getRootNode(document: TextDocument, useCache: boolean): FlatNode;
//# sourceMappingURL=parseDocument.d.ts.map

29
node_modules/volar-service-emmet/lib/parseDocument.js generated vendored Normal file
View File

@@ -0,0 +1,29 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.getRootNode = getRootNode;
const html_matcher_1 = require("@emmetio/html-matcher");
const css_parser_1 = require("@emmetio/css-parser");
const util_1 = require("./util");
// Map(filename, Pair(fileVersion, rootNodeOfParsedContent))
const _parseCache = new Map();
function getRootNode(document, useCache) {
const key = document.uri.toString();
const result = _parseCache.get(key);
const documentVersion = document.version;
if (useCache && result) {
if (documentVersion === result.key) {
return result.value;
}
}
const parseContent = (0, util_1.isStyleSheet)(document.languageId) ? css_parser_1.default : html_matcher_1.default;
const rootNode = parseContent(document.getText());
if (useCache) {
_parseCache.set(key, { key: documentVersion, value: rootNode });
}
return rootNode;
}
//# sourceMappingURL=parseDocument.js.map

View File

@@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'EmmetFlatNode' {
export interface Node {
start: number;
end: number;
type: string;
parent: Node | undefined;
firstChild: Node | undefined;
nextSibling: Node | undefined;
previousSibling: Node | undefined;
children: Node[];
}
export interface Token {
start: number;
end: number;
stream: BufferStream;
toString(): string;
}
export interface CssToken extends Token {
size: number;
item(number: number): any;
type: string;
}
export interface HtmlToken extends Token {
value: string;
}
export interface Attribute extends Token {
name: Token;
value: Token;
}
export interface HtmlNode extends Node {
name: string;
open: Token | undefined;
close: Token | undefined;
parent: HtmlNode | undefined;
firstChild: HtmlNode | undefined;
nextSibling: HtmlNode | undefined;
previousSibling: HtmlNode | undefined;
children: HtmlNode[];
attributes: Attribute[];
}
export interface CssNode extends Node {
name: string;
parent: CssNode | undefined;
firstChild: CssNode | undefined;
nextSibling: CssNode | undefined;
previousSibling: CssNode | undefined;
children: CssNode[];
}
export interface Rule extends CssNode {
selectorToken: Token;
contentStartToken: Token;
contentEndToken: Token;
}
export interface Property extends CssNode {
valueToken: Token;
separator: string;
parent: Rule;
terminatorToken: Token;
separatorToken: Token;
value: string;
}
export interface Stylesheet extends Node {
comments: Token[];
}
export interface BufferStream {
peek(): number;
next(): number;
backUp(n: number): number;
current(): string;
substring(from: number, to: number): string;
eat(match: any): boolean;
eatWhile(match: any): boolean;
}
}

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.
*--------------------------------------------------------------------------------------------*/
declare module 'EmmetNode' {
import { Position } from 'vscode';
export interface Node {
start: Position;
end: Position;
type: string;
parent: Node;
firstChild: Node;
nextSibling: Node;
previousSibling: Node;
children: Node[];
}
export interface Token {
start: Position;
end: Position;
stream: BufferStream;
toString(): string;
}
export interface CssToken extends Token {
size: number;
item(number: number): any;
type: string;
}
export interface HtmlToken extends Token {
value: string;
}
export interface Attribute extends Token {
name: Token;
value: Token;
}
export interface HtmlNode extends Node {
name: string;
open: Token;
close: Token;
parent: HtmlNode;
firstChild: HtmlNode;
nextSibling: HtmlNode;
previousSibling: HtmlNode;
children: HtmlNode[];
attributes: Attribute[];
}
export interface CssNode extends Node {
name: string;
parent: CssNode;
firstChild: CssNode;
nextSibling: CssNode;
previousSibling: CssNode;
children: CssNode[];
}
export interface Rule extends CssNode {
selectorToken: Token;
contentStartToken: Token;
contentEndToken: Token;
}
export interface Property extends CssNode {
valueToken: Token;
separator: string;
parent: Rule;
terminatorToken: Token;
separatorToken: Token;
value: string;
}
export interface Stylesheet extends Node {
comments: Token[];
}
export interface BufferStream {
peek(): number;
next(): number;
backUp(n: number): number;
current(): string;
substring(from: Position, to: Position): string;
eat(match: any): boolean;
eatWhile(match: any): boolean;
}
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module '@emmetio/css-parser' {
import { BufferStream, Stylesheet } from 'EmmetNode';
import { Stylesheet as FlatStylesheet } from 'EmmetFlatNode';
function parseStylesheet(stream: BufferStream): Stylesheet;
function parseStylesheet(stream: string): FlatStylesheet;
export default parseStylesheet;
}

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module '@emmetio/html-matcher' {
import { BufferStream, HtmlNode } from 'EmmetNode';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
function parse(stream: BufferStream): HtmlNode;
function parse(stream: string): HtmlFlatNode;
export default parse;
}

View File

@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference types='@types/node'/>

43
node_modules/volar-service-emmet/lib/util.d.ts generated vendored Normal file
View File

@@ -0,0 +1,43 @@
import type * as vscode from '@volar/language-service';
import type * as EmmetHelper from '@vscode/emmet-helper';
import type { Node as FlatNode, Stylesheet as FlatStylesheet, HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
export declare function getEmmetHelper(): typeof EmmetHelper;
export declare function isStyleSheet(syntax: string): boolean;
export declare function getMappingForIncludedLanguages(context: vscode.LanguageServiceContext): Promise<Record<string, string>>;
/**
* Get the corresponding emmet mode for given vscode language mode
* E.g.: jsx for typescriptreact/javascriptreact or pug for jade
* If the language is not supported by emmet or has been excluded via `excludeLanguages` setting,
* then nothing is returned
*
* @param excludedLanguages Array of language ids that user has chosen to exclude for emmet
*/
export declare function getEmmetMode(language: string, mappedModes: Record<string, string>, excludedLanguages: string[]): string | undefined;
/**
* Traverse the given document backward & forward from given position
* to find a complete ruleset, then parse just that to return a Stylesheet
* @param document TextDocument
* @param position vscode.Position
*/
export declare function parsePartialStylesheet(document: vscode.TextDocument, position: vscode.Position): FlatStylesheet | undefined;
/**
* Returns node corresponding to given position in the given root node
*/
export declare function getFlatNode(root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): FlatNode | undefined;
export declare const allowedMimeTypesInScriptTag: string[];
/**
* Finds the HTML node within an HTML document at a given position
* If position is inside a script tag of type template, then it will be parsed to find the inner HTML node as well
*/
export declare function getHtmlFlatNode(documentText: string, root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): HtmlFlatNode | undefined;
export declare function getEmmetConfiguration(context: vscode.LanguageServiceContext, syntax: string): Promise<{
preferences: any;
showExpandedAbbreviation: any;
showAbbreviationSuggestions: any;
syntaxProfiles: any;
variables: any;
excludeLanguages: any;
showSuggestionsAsSnippets: any;
}>;
export declare function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNode: FlatNode | undefined, position: vscode.Position): FlatNode | undefined;
//# sourceMappingURL=util.d.ts.map

403
node_modules/volar-service-emmet/lib/util.js generated vendored Normal file
View File

@@ -0,0 +1,403 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.allowedMimeTypesInScriptTag = void 0;
exports.getEmmetHelper = getEmmetHelper;
exports.isStyleSheet = isStyleSheet;
exports.getMappingForIncludedLanguages = getMappingForIncludedLanguages;
exports.getEmmetMode = getEmmetMode;
exports.parsePartialStylesheet = parsePartialStylesheet;
exports.getFlatNode = getFlatNode;
exports.getHtmlFlatNode = getHtmlFlatNode;
exports.getEmmetConfiguration = getEmmetConfiguration;
exports.getEmbeddedCssNodeIfAny = getEmbeddedCssNodeIfAny;
const css_parser_1 = require("@emmetio/css-parser");
const html_matcher_1 = require("@emmetio/html-matcher");
const bufferStream_1 = require("./bufferStream");
let _emmetHelper;
function getEmmetHelper() {
// Lazy load vscode-emmet-helper instead of importing it
// directly to reduce the start-up time of the extension
if (!_emmetHelper) {
_emmetHelper = require('@vscode/emmet-helper');
}
return _emmetHelper;
}
/**
* Mapping between languages that support Emmet and completion trigger characters
*/
const LANGUAGE_MODES = {
'html': ['!', '.', '}', ':', '*', '$', ']', '/', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'jade': ['!', '.', '}', ':', '*', '$', ']', '/', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'slim': ['!', '.', '}', ':', '*', '$', ']', '/', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'haml': ['!', '.', '}', ':', '*', '$', ']', '/', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'xml': ['.', '}', '*', '$', ']', '/', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'xsl': ['!', '.', '}', '*', '$', '/', ']', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'css': [':', '!', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'scss': [':', '!', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'sass': [':', '!', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'less': [':', '!', '-', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'stylus': [':', '!', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'javascriptreact': ['!', '.', '}', '*', '$', ']', '/', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'],
'typescriptreact': ['!', '.', '}', '*', '$', ']', '/', '>', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
};
function isStyleSheet(syntax) {
const stylesheetSyntaxes = ['css', 'scss', 'sass', 'less', 'stylus'];
return stylesheetSyntaxes.includes(syntax);
}
async function getMappingForIncludedLanguages(context) {
// Explicitly map languages that have built-in grammar in VS Code to their parent language
// to get emmet completion support
// For other languages, users will have to use `emmet.includeLanguages` or
// language specific extensions can provide emmet completion support
const MAPPED_MODES = {
'handlebars': 'html',
'php': 'html'
};
const finalMappedModes = {};
const includeLanguagesConfig = await context.env.getConfiguration?.('emmet.includeLanguages');
const includeLanguages = Object.assign({}, MAPPED_MODES, includeLanguagesConfig ?? {});
Object.keys(includeLanguages).forEach(syntax => {
if (typeof includeLanguages[syntax] === 'string' && LANGUAGE_MODES[includeLanguages[syntax]]) {
finalMappedModes[syntax] = includeLanguages[syntax];
}
});
return finalMappedModes;
}
/**
* Get the corresponding emmet mode for given vscode language mode
* E.g.: jsx for typescriptreact/javascriptreact or pug for jade
* If the language is not supported by emmet or has been excluded via `excludeLanguages` setting,
* then nothing is returned
*
* @param excludedLanguages Array of language ids that user has chosen to exclude for emmet
*/
function getEmmetMode(language, mappedModes, excludedLanguages) {
if (!language || excludedLanguages.includes(language)) {
return;
}
if (language === 'jsx-tags') {
language = 'javascriptreact';
}
if (mappedModes[language]) {
language = mappedModes[language];
}
if (/\b(typescriptreact|javascriptreact|jsx-tags)\b/.test(language)) { // treat tsx like jsx
language = 'jsx';
}
else if (language === 'sass-indented') { // map sass-indented to sass
language = 'sass';
}
else if (language === 'jade' || language === 'pug') {
language = 'pug';
}
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(language) || syntaxes.stylesheet.includes(language)) {
return language;
}
return;
}
const closeBrace = 125;
const openBrace = 123;
const slash = 47;
const star = 42;
/**
* Traverse the given document backward & forward from given position
* to find a complete ruleset, then parse just that to return a Stylesheet
* @param document TextDocument
* @param position vscode.Position
*/
function parsePartialStylesheet(document, position) {
const isCSS = document.languageId === 'css';
const positionOffset = document.offsetAt(position);
let startOffset = 0;
let endOffset = document.getText().length;
const limitCharacter = positionOffset - 5000;
const limitOffset = limitCharacter > 0 ? limitCharacter : startOffset;
const stream = new bufferStream_1.DocumentStreamReader(document, positionOffset);
function findOpeningCommentBeforePosition(pos) {
const text = document.getText().substring(0, pos);
const offset = text.lastIndexOf('/*');
if (offset === -1) {
return;
}
return offset;
}
function findClosingCommentAfterPosition(pos) {
const text = document.getText().substring(pos);
let offset = text.indexOf('*/');
if (offset === -1) {
return;
}
offset += 2 + pos;
return offset;
}
function consumeLineCommentBackwards() {
const posLineNumber = document.positionAt(stream.pos).line;
if (!isCSS && currentLine !== posLineNumber) {
currentLine = posLineNumber;
const startLineComment = document.getText({
start: { line: currentLine, character: 0 },
end: { line: currentLine + 1, character: 0 },
}).indexOf('//');
if (startLineComment > -1) {
stream.pos = document.offsetAt({ line: currentLine, character: startLineComment });
}
}
}
function consumeBlockCommentBackwards() {
if (!stream.sof() && stream.peek() === slash) {
if (stream.backUp(1) === star) {
stream.pos = findOpeningCommentBeforePosition(stream.pos) ?? startOffset;
}
else {
stream.next();
}
}
}
function consumeCommentForwards() {
if (stream.eat(slash)) {
if (stream.eat(slash) && !isCSS) {
const posLineNumber = document.positionAt(stream.pos).line;
stream.pos = document.offsetAt({ line: posLineNumber + 1, character: 0 });
}
else if (stream.eat(star)) {
stream.pos = findClosingCommentAfterPosition(stream.pos) ?? endOffset;
}
}
}
// Go forward until we find a closing brace.
while (!stream.eof() && !stream.eat(closeBrace)) {
if (stream.peek() === slash) {
consumeCommentForwards();
}
else {
stream.next();
}
}
if (!stream.eof()) {
endOffset = stream.pos;
}
stream.pos = positionOffset;
let openBracesToFind = 1;
let currentLine = position.line;
let exit = false;
// Go back until we found an opening brace. If we find a closing one, consume its pair and continue.
while (!exit && openBracesToFind > 0 && !stream.sof()) {
consumeLineCommentBackwards();
switch (stream.backUp(1)) {
case openBrace:
openBracesToFind--;
break;
case closeBrace:
if (isCSS) {
stream.next();
startOffset = stream.pos;
exit = true;
}
else {
openBracesToFind++;
}
break;
case slash:
consumeBlockCommentBackwards();
break;
default:
break;
}
if (position.line - document.positionAt(stream.pos).line > 100
|| stream.pos <= limitOffset) {
exit = true;
}
}
// We are at an opening brace. We need to include its selector.
currentLine = document.positionAt(stream.pos).line;
openBracesToFind = 0;
let foundSelector = false;
while (!exit && !stream.sof() && !foundSelector && openBracesToFind >= 0) {
consumeLineCommentBackwards();
const ch = stream.backUp(1);
if (/\s/.test(String.fromCharCode(ch))) {
continue;
}
switch (ch) {
case slash:
consumeBlockCommentBackwards();
break;
case closeBrace:
openBracesToFind++;
break;
case openBrace:
openBracesToFind--;
break;
default:
if (!openBracesToFind) {
foundSelector = true;
}
break;
}
if (!stream.sof() && foundSelector) {
startOffset = stream.pos;
}
}
try {
const buffer = ' '.repeat(startOffset) + document.getText().substring(startOffset, endOffset);
return (0, css_parser_1.default)(buffer);
}
catch (e) {
return;
}
}
/**
* Returns node corresponding to given position in the given root node
*/
function getFlatNode(root, offset, includeNodeBoundary) {
if (!root) {
return;
}
function getFlatNodeChild(child) {
if (!child) {
return;
}
const nodeStart = child.start;
const nodeEnd = child.end;
if ((nodeStart < offset && nodeEnd > offset)
|| (includeNodeBoundary && nodeStart <= offset && nodeEnd >= offset)) {
return getFlatNodeChildren(child.children) ?? child;
}
else if ('close' in child) {
// We have an HTML node in this case.
// In case this node is an invalid unpaired HTML node,
// we still want to search its children
const htmlChild = child;
if (htmlChild.open && !htmlChild.close) {
return getFlatNodeChildren(htmlChild.children);
}
}
return;
}
function getFlatNodeChildren(children) {
for (let i = 0; i < children.length; i++) {
const foundChild = getFlatNodeChild(children[i]);
if (foundChild) {
return foundChild;
}
}
return;
}
return getFlatNodeChildren(root.children);
}
exports.allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template', 'text/template', 'text/ng-template'];
/**
* Finds the HTML node within an HTML document at a given position
* If position is inside a script tag of type template, then it will be parsed to find the inner HTML node as well
*/
function getHtmlFlatNode(documentText, root, offset, includeNodeBoundary) {
let currentNode = getFlatNode(root, offset, includeNodeBoundary);
if (!currentNode) {
return;
}
// If the currentNode is a script one, first set up its subtree and then find HTML node.
if (currentNode.name === 'script' && currentNode.children.length === 0) {
const scriptNodeBody = setupScriptNodeSubtree(documentText, currentNode);
if (scriptNodeBody) {
currentNode = getHtmlFlatNode(scriptNodeBody, currentNode, offset, includeNodeBoundary) ?? currentNode;
}
}
else if (currentNode.type === 'cdata') {
const cdataBody = setupCdataNodeSubtree(documentText, currentNode);
currentNode = getHtmlFlatNode(cdataBody, currentNode, offset, includeNodeBoundary) ?? currentNode;
}
return currentNode;
}
function setupScriptNodeSubtree(documentText, scriptNode) {
const isTemplateScript = scriptNode.name === 'script' &&
(scriptNode.attributes &&
scriptNode.attributes.some(x => x.name.toString() === 'type'
&& exports.allowedMimeTypesInScriptTag.includes(x.value.toString())));
if (isTemplateScript
&& scriptNode.open) {
// blank out the rest of the document and generate the subtree.
const beforePadding = ' '.repeat(scriptNode.open.end);
const endToUse = scriptNode.close ? scriptNode.close.start : scriptNode.end;
const scriptBodyText = beforePadding + documentText.substring(scriptNode.open.end, endToUse);
const innerRoot = (0, html_matcher_1.default)(scriptBodyText);
innerRoot.children.forEach(child => {
scriptNode.children.push(child);
child.parent = scriptNode;
});
return scriptBodyText;
}
return '';
}
function setupCdataNodeSubtree(documentText, cdataNode) {
// blank out the rest of the document and generate the subtree.
const cdataStart = '<![CDATA[';
const cdataEnd = ']]>';
const startToUse = cdataNode.start + cdataStart.length;
const endToUse = cdataNode.end - cdataEnd.length;
const beforePadding = ' '.repeat(startToUse);
const cdataBody = beforePadding + documentText.substring(startToUse, endToUse);
const innerRoot = (0, html_matcher_1.default)(cdataBody);
innerRoot.children.forEach(child => {
cdataNode.children.push(child);
child.parent = cdataNode;
});
return cdataBody;
}
async function getEmmetConfiguration(context, syntax) {
const emmetConfig = await context.env.getConfiguration?.('emmet') ?? {};
const syntaxProfiles = Object.assign({}, emmetConfig['syntaxProfiles'] || {});
const preferences = Object.assign({}, emmetConfig['preferences'] || {});
// jsx, xml and xsl syntaxes need to have self closing tags unless otherwise configured by user
if (syntax === 'jsx' || syntax === 'xml' || syntax === 'xsl') {
syntaxProfiles[syntax] = syntaxProfiles[syntax] || {};
if (typeof syntaxProfiles[syntax] === 'object'
&& !syntaxProfiles[syntax].hasOwnProperty('self_closing_tag') // Old Emmet format
&& !syntaxProfiles[syntax].hasOwnProperty('selfClosingStyle') // Emmet 2.0 format
) {
syntaxProfiles[syntax] = {
...syntaxProfiles[syntax],
selfClosingStyle: syntax === 'jsx' ? 'xhtml' : 'xml'
};
}
}
return {
preferences,
showExpandedAbbreviation: emmetConfig['showExpandedAbbreviation'],
showAbbreviationSuggestions: emmetConfig['showAbbreviationSuggestions'],
syntaxProfiles,
variables: emmetConfig['variables'],
excludeLanguages: emmetConfig['excludeLanguages'],
showSuggestionsAsSnippets: emmetConfig['showSuggestionsAsSnippets']
};
}
function getEmbeddedCssNodeIfAny(document, currentNode, position) {
if (!currentNode) {
return;
}
const currentHtmlNode = currentNode;
if (currentHtmlNode && currentHtmlNode.open && currentHtmlNode.close) {
const offset = document.offsetAt(position);
if (currentHtmlNode.open.end < offset && offset <= currentHtmlNode.close.start) {
if (currentHtmlNode.name === 'style') {
const buffer = ' '.repeat(currentHtmlNode.open.end) + document.getText().substring(currentHtmlNode.open.end, currentHtmlNode.close.start);
return (0, css_parser_1.default)(buffer);
}
}
}
return;
}
function getSyntaxes() {
/**
* List of all known syntaxes, from emmetio/emmet
*/
return {
markup: ['html', 'xml', 'xsl', 'jsx', 'js', 'pug', 'slim', 'haml'],
stylesheet: ['css', 'sass', 'scss', 'less', 'sss', 'stylus']
};
}
//# sourceMappingURL=util.js.map