astro-ghostcms/.pnpm-store/v3/files/a4/2e20f630a58d04aab71fc644494...

1075 lines
48 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
"use strict";
/*---------------------------------------------------------------------------------------------
* 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;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getEmmetMode = exports.updateExtensionsPath = exports.expandAbbreviation = exports.parseAbbreviation = exports.getExpandOptions = exports.isAbbreviationValid = exports.extractAbbreviationFromText = exports.extractAbbreviation = exports.getDefaultSnippets = exports.getDefaultSyntax = exports.getSyntaxType = exports.isStyleSheet = exports.emmetSnippetField = exports.doComplete = exports.FileType = void 0;
const JSONC = __importStar(require("jsonc-parser"));
const util_1 = require("util");
const vscode_languageserver_types_1 = require("vscode-languageserver-types");
const vscode_uri_1 = require("vscode-uri");
const data_1 = require("./data");
const fileService_1 = require("./fileService");
Object.defineProperty(exports, "FileType", { enumerable: true, get: function () { return fileService_1.FileType; } });
const emmet_1 = __importStar(require("emmet"));
const configCompat_1 = require("./configCompat");
let l10n;
try {
l10n = require('vscode').l10n;
}
catch (_a) {
// Fallback to the identity function.
l10n = {
t: (message) => message
};
}
const snippetKeyCache = new Map();
let markupSnippetKeys;
const stylesheetCustomSnippetsKeyCache = new Map();
const htmlAbbreviationStartRegex = /^[a-z,A-Z,!,(,[,#,\.\{]/;
// take off { for jsx because it interferes with the language
const jsxAbbreviationStartRegex = /^[a-z,A-Z,!,(,[,#,\.]/;
const cssAbbreviationRegex = /^-?[a-z,A-Z,!,@,#]/;
const htmlAbbreviationRegex = /[a-z,A-Z\.]/;
const commonlyUsedTags = [...data_1.htmlData.tags, 'lorem'];
const bemFilterSuffix = 'bem';
const filterDelimitor = '|';
const trimFilterSuffix = 't';
const commentFilterSuffix = 'c';
const maxFilters = 3;
/**
* Returns all applicable emmet expansions for abbreviation at given position in a CompletionList
* @param document TextDocument in which completions are requested
* @param position Position in the document at which completions are requested
* @param syntax Emmet supported language
* @param emmetConfig Emmet Configurations as derived from VS Code
*/
function doComplete(document, position, syntax, emmetConfig) {
var _a, _b;
if (emmetConfig.showExpandedAbbreviation === 'never' || !getEmmetMode(syntax, emmetConfig.excludeLanguages)) {
return;
}
const isStyleSheetRes = isStyleSheet(syntax);
// Fetch markupSnippets so that we can provide possible abbreviation completions
// For example, when text at position is `a`, completions should return `a:blank`, `a:link`, `acr` etc.
if (!isStyleSheetRes) {
if (!snippetKeyCache.has(syntax)) {
const registry = Object.assign(Object.assign({}, getDefaultSnippets(syntax)), customSnippetsRegistry[syntax]);
snippetKeyCache.set(syntax, Object.keys(registry));
}
markupSnippetKeys = (_a = snippetKeyCache.get(syntax)) !== null && _a !== void 0 ? _a : [];
}
const extractOptions = { lookAhead: !isStyleSheetRes, type: isStyleSheetRes ? 'stylesheet' : 'markup' };
const extractedValue = extractAbbreviation(document, position, extractOptions);
if (!extractedValue) {
return;
}
const { abbreviationRange, abbreviation, filter } = extractedValue;
const currentLineTillPosition = getCurrentLine(document, position).substr(0, position.character);
const currentWord = getCurrentWord(currentLineTillPosition);
// Don't attempt to expand open tags
if (currentWord === abbreviation
&& currentLineTillPosition.endsWith(`<${abbreviation}`)
&& configCompat_1.syntaxes.markup.includes(syntax)) {
return;
}
const expandOptions = getExpandOptions(syntax, emmetConfig, filter);
let expandedText = "";
let expandedAbbr;
let completionItems = [];
// Create completion item after expanding given abbreviation
// if abbreviation is valid and expanded value is not noise
const createExpandedAbbr = (syntax, abbr) => {
if (!isAbbreviationValid(syntax, abbreviation)) {
return;
}
try {
expandedText = (0, emmet_1.default)(abbr, expandOptions);
// manually patch https://github.com/microsoft/vscode/issues/120245 for now
if (isStyleSheetRes && '!important'.startsWith(abbr)) {
expandedText = '!important';
}
}
catch (e) {
}
if (!expandedText || isExpandedTextNoise(syntax, abbr, expandedText, expandOptions.options)) {
return;
}
expandedAbbr = vscode_languageserver_types_1.CompletionItem.create(abbr);
expandedAbbr.textEdit = vscode_languageserver_types_1.TextEdit.replace(abbreviationRange, escapeNonTabStopDollar(addFinalTabStop(expandedText)));
expandedAbbr.documentation = replaceTabStopsWithCursors(expandedText);
expandedAbbr.insertTextFormat = vscode_languageserver_types_1.InsertTextFormat.Snippet;
expandedAbbr.detail = l10n.t('Emmet Abbreviation');
expandedAbbr.label = abbreviation;
expandedAbbr.label += filter ? '|' + filter.replace(',', '|') : "";
completionItems = [expandedAbbr];
};
if (isStyleSheet(syntax)) {
createExpandedAbbr(syntax, abbreviation);
// When abbr is longer than usual emmet snippets and matches better with existing css property, then no emmet
if (abbreviation.length > 4
&& data_1.cssData.properties.find(x => x.startsWith(abbreviation))) {
return vscode_languageserver_types_1.CompletionList.create([], true);
}
if (expandedAbbr && expandedText.length) {
expandedAbbr.textEdit = vscode_languageserver_types_1.TextEdit.replace(abbreviationRange, escapeNonTabStopDollar(addFinalTabStop(expandedText)));
expandedAbbr.documentation = replaceTabStopsWithCursors(expandedText);
expandedAbbr.label = removeTabStops(expandedText);
expandedAbbr.filterText = abbreviation;
// Custom snippets should show up in completions if abbreviation is a prefix
const stylesheetCustomSnippetsKeys = stylesheetCustomSnippetsKeyCache.has(syntax) ?
stylesheetCustomSnippetsKeyCache.get(syntax) : stylesheetCustomSnippetsKeyCache.get('css');
completionItems = makeSnippetSuggestion(stylesheetCustomSnippetsKeys !== null && stylesheetCustomSnippetsKeys !== void 0 ? stylesheetCustomSnippetsKeys : [], abbreviation, abbreviation, abbreviationRange, expandOptions, 'Emmet Custom Snippet', false);
if (!completionItems.find(x => { var _a, _b, _c; return ((_a = x.textEdit) === null || _a === void 0 ? void 0 : _a.newText) && ((_b = x.textEdit) === null || _b === void 0 ? void 0 : _b.newText) === ((_c = expandedAbbr === null || expandedAbbr === void 0 ? void 0 : expandedAbbr.textEdit) === null || _c === void 0 ? void 0 : _c.newText); })) {
// Fix for https://github.com/Microsoft/vscode/issues/28933#issuecomment-309236902
// When user types in propertyname, emmet uses it to match with snippet names, resulting in width -> widows or font-family -> font: family
// Filter out those cases here.
const abbrRegex = new RegExp('.*' + abbreviation.split('').map(x => (x === '$' || x === '+') ? '\\' + x : x).join('.*') + '.*', 'i');
if (/\d/.test(abbreviation) || abbrRegex.test(expandedAbbr.label)) {
completionItems.push(expandedAbbr);
}
}
}
}
else {
createExpandedAbbr(syntax, abbreviation);
let tagToFindMoreSuggestionsFor = abbreviation;
const newTagMatches = abbreviation.match(/(>|\+)([\w:-]+)$/);
if (newTagMatches && newTagMatches.length === 3) {
tagToFindMoreSuggestionsFor = newTagMatches[2];
}
if (syntax !== 'xml') {
const commonlyUsedTagSuggestions = makeSnippetSuggestion(commonlyUsedTags, tagToFindMoreSuggestionsFor, abbreviation, abbreviationRange, expandOptions, 'Emmet Abbreviation');
completionItems = completionItems.concat(commonlyUsedTagSuggestions);
}
if (emmetConfig.showAbbreviationSuggestions === true) {
const abbreviationSuggestions = makeSnippetSuggestion(markupSnippetKeys.filter(x => !commonlyUsedTags.includes(x)), tagToFindMoreSuggestionsFor, abbreviation, abbreviationRange, expandOptions, 'Emmet Abbreviation');
// Workaround for the main expanded abbr not appearing before the snippet suggestions
if (expandedAbbr && abbreviationSuggestions.length > 0 && tagToFindMoreSuggestionsFor !== abbreviation) {
expandedAbbr.sortText = '0' + expandedAbbr.label;
abbreviationSuggestions.forEach(item => {
// Workaround for snippet suggestions items getting filtered out as the complete abbr does not start with snippetKey
item.filterText = abbreviation;
// Workaround for the main expanded abbr not appearing before the snippet suggestions
item.sortText = '9' + abbreviation;
});
}
completionItems = completionItems.concat(abbreviationSuggestions);
}
// https://github.com/microsoft/vscode/issues/66680
if (syntax === 'html' && completionItems.length >= 2 && abbreviation.includes(":")
&& ((_b = expandedAbbr === null || expandedAbbr === void 0 ? void 0 : expandedAbbr.textEdit) === null || _b === void 0 ? void 0 : _b.newText) === `<${abbreviation}>\${0}</${abbreviation}>`) {
completionItems = completionItems.filter(item => item.label !== abbreviation);
}
}
if (emmetConfig.showSuggestionsAsSnippets === true) {
completionItems.forEach(x => x.kind = vscode_languageserver_types_1.CompletionItemKind.Snippet);
}
return completionItems.length ? vscode_languageserver_types_1.CompletionList.create(completionItems, true) : undefined;
}
exports.doComplete = doComplete;
/**
* Create & return snippets for snippet keys that start with given prefix
*/
function makeSnippetSuggestion(snippetKeys, prefix, abbreviation, abbreviationRange, expandOptions, snippetDetail, skipFullMatch = true) {
if (!prefix || !snippetKeys) {
return [];
}
const snippetCompletions = [];
snippetKeys.forEach(snippetKey => {
if (!snippetKey.startsWith(prefix.toLowerCase()) || (skipFullMatch && snippetKey === prefix.toLowerCase())) {
return;
}
const currentAbbr = abbreviation + snippetKey.substr(prefix.length);
let expandedAbbr;
try {
expandedAbbr = (0, emmet_1.default)(currentAbbr, expandOptions);
}
catch (e) {
}
if (!expandedAbbr) {
return;
}
const item = vscode_languageserver_types_1.CompletionItem.create(prefix + snippetKey.substr(prefix.length));
item.documentation = replaceTabStopsWithCursors(expandedAbbr);
item.detail = snippetDetail;
item.textEdit = vscode_languageserver_types_1.TextEdit.replace(abbreviationRange, escapeNonTabStopDollar(addFinalTabStop(expandedAbbr)));
item.insertTextFormat = vscode_languageserver_types_1.InsertTextFormat.Snippet;
snippetCompletions.push(item);
});
return snippetCompletions;
}
function getCurrentWord(currentLineTillPosition) {
if (currentLineTillPosition) {
const matches = currentLineTillPosition.match(/[\w,:,-,\.]*$/);
if (matches) {
return matches[0];
}
}
}
function replaceTabStopsWithCursors(expandedWord) {
return expandedWord.replace(/([^\\])\$\{\d+\}/g, '$1|').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
}
function removeTabStops(expandedWord) {
return expandedWord.replace(/([^\\])\$\{\d+\}/g, '$1').replace(/\$\{\d+:([^\}]+)\}/g, '$1');
}
function escapeNonTabStopDollar(text) {
return text ? text.replace(/([^\\])(\$)([^\{])/g, '$1\\$2$3') : text;
}
function addFinalTabStop(text) {
if (!text || !text.trim()) {
return text;
}
let maxTabStop = -1;
let maxTabStopRanges = [];
let foundLastStop = false;
let replaceWithLastStop = false;
let i = 0;
const n = text.length;
try {
while (i < n && !foundLastStop) {
// Look for ${
if (text[i++] != '$' || text[i++] != '{') {
continue;
}
// Find tabstop
let numberStart = -1;
let numberEnd = -1;
while (i < n && /\d/.test(text[i])) {
numberStart = numberStart < 0 ? i : numberStart;
numberEnd = i + 1;
i++;
}
// If ${ was not followed by a number and either } or :, then its not a tabstop
if (numberStart === -1 || numberEnd === -1 || i >= n || (text[i] != '}' && text[i] != ':')) {
continue;
}
// If ${0} was found, then break
const currentTabStop = text.substring(numberStart, numberEnd);
foundLastStop = currentTabStop === '0';
if (foundLastStop) {
break;
}
let foundPlaceholder = false;
if (text[i++] == ':') {
// TODO: Nested placeholders may break here
while (i < n) {
if (text[i] == '}') {
foundPlaceholder = true;
break;
}
i++;
}
}
// Decide to replace currentTabStop with ${0} only if its the max among all tabstops and is not a placeholder
if (Number(currentTabStop) > Number(maxTabStop)) {
maxTabStop = Number(currentTabStop);
maxTabStopRanges = [{ numberStart, numberEnd }];
replaceWithLastStop = !foundPlaceholder;
}
else if (Number(currentTabStop) === maxTabStop) {
maxTabStopRanges.push({ numberStart, numberEnd });
}
}
}
catch (e) {
}
if (replaceWithLastStop && !foundLastStop) {
for (let i = 0; i < maxTabStopRanges.length; i++) {
const rangeStart = maxTabStopRanges[i].numberStart;
const rangeEnd = maxTabStopRanges[i].numberEnd;
text = text.substr(0, rangeStart) + '0' + text.substr(rangeEnd);
}
}
return text;
}
function getCurrentLine(document, position) {
const offset = document.offsetAt(position);
const text = document.getText();
let start = 0;
let end = text.length;
for (let i = offset - 1; i >= 0; i--) {
if (text[i] === '\n') {
start = i + 1;
break;
}
}
for (let i = offset; i < text.length; i++) {
if (text[i] === '\n') {
end = i;
break;
}
}
return text.substring(start, end);
}
let customSnippetsRegistry = {};
let variablesFromFile = {};
let profilesFromFile = {};
const emmetSnippetField = (index, placeholder) => `\${${index}${placeholder ? ':' + placeholder : ''}}`;
exports.emmetSnippetField = emmetSnippetField;
/** Returns whether or not syntax is a supported stylesheet syntax, like CSS */
function isStyleSheet(syntax) {
return configCompat_1.syntaxes.stylesheet.includes(syntax);
}
exports.isStyleSheet = isStyleSheet;
/** Returns the syntax type, either markup (e.g. for HTML) or stylesheet (e.g. for CSS) */
function getSyntaxType(syntax) {
return isStyleSheet(syntax) ? 'stylesheet' : 'markup';
}
exports.getSyntaxType = getSyntaxType;
/** Returns the default syntax (html or css) to use for the snippets registry */
function getDefaultSyntax(syntax) {
return isStyleSheet(syntax) ? 'css' : 'html';
}
exports.getDefaultSyntax = getDefaultSyntax;
/** Returns the default snippets that Emmet suggests */
function getDefaultSnippets(syntax) {
const syntaxType = getSyntaxType(syntax);
const emptyUserConfig = { type: syntaxType, syntax };
const resolvedConfig = (0, emmet_1.resolveConfig)(emptyUserConfig);
// https://github.com/microsoft/vscode/issues/97632
// don't return markup (HTML) snippets for XML
return syntax === 'xml' ? {} : resolvedConfig.snippets;
}
exports.getDefaultSnippets = getDefaultSnippets;
function getFilters(text, pos) {
let filter;
for (let i = 0; i < maxFilters; i++) {
if (text.endsWith(`${filterDelimitor}${bemFilterSuffix}`, pos)) {
pos -= bemFilterSuffix.length + 1;
filter = filter ? bemFilterSuffix + ',' + filter : bemFilterSuffix;
}
else if (text.endsWith(`${filterDelimitor}${commentFilterSuffix}`, pos)) {
pos -= commentFilterSuffix.length + 1;
filter = filter ? commentFilterSuffix + ',' + filter : commentFilterSuffix;
}
else if (text.endsWith(`${filterDelimitor}${trimFilterSuffix}`, pos)) {
pos -= trimFilterSuffix.length + 1;
filter = filter ? trimFilterSuffix + ',' + filter : trimFilterSuffix;
}
else {
break;
}
}
return {
pos: pos,
filter: filter
};
}
/**
* Extracts abbreviation from the given position in the given document
* @param document The TextDocument from which abbreviation needs to be extracted
* @param position The Position in the given document from where abbreviation needs to be extracted
* @param options The options to pass to the @emmetio/extract-abbreviation module
*/
function extractAbbreviation(document, position, options) {
const currentLine = getCurrentLine(document, position);
const currentLineTillPosition = currentLine.substr(0, position.character);
const { pos, filter } = getFilters(currentLineTillPosition, position.character);
const lengthOccupiedByFilter = filter ? filter.length + 1 : 0;
const result = (0, emmet_1.extract)(currentLine, pos, options);
if (!result) {
return;
}
const rangeToReplace = vscode_languageserver_types_1.Range.create(position.line, result.location, position.line, result.location + result.abbreviation.length + lengthOccupiedByFilter);
return {
abbreviationRange: rangeToReplace,
abbreviation: result.abbreviation,
filter
};
}
exports.extractAbbreviation = extractAbbreviation;
/**
* Extracts abbreviation from the given text
* @param text Text from which abbreviation needs to be extracted
* @param syntax Syntax used to extract the abbreviation from the given text
*/
function extractAbbreviationFromText(text, syntax) {
if (!text) {
return;
}
const { pos, filter } = getFilters(text, text.length);
const extractOptions = (isStyleSheet(syntax) || syntax === 'stylesheet') ?
{ syntax: 'stylesheet', lookAhead: false } :
{ lookAhead: true };
const result = (0, emmet_1.extract)(text, pos, extractOptions);
if (!result) {
return;
}
return {
abbreviation: result.abbreviation,
filter
};
}
exports.extractAbbreviationFromText = extractAbbreviationFromText;
/**
* Returns a boolean denoting validity of given abbreviation in the context of given syntax
* Not needed once https://github.com/emmetio/atom-plugin/issues/22 is fixed
* @param syntax string
* @param abbreviation string
*/
function isAbbreviationValid(syntax, abbreviation) {
if (!abbreviation) {
return false;
}
if (isStyleSheet(syntax)) {
if (abbreviation.includes('#')) {
if (abbreviation.startsWith('#')) {
const hexColorRegex = /^#[\d,a-f,A-F]{1,6}$/;
return hexColorRegex.test(abbreviation);
}
else if (commonlyUsedTags.includes(abbreviation.substring(0, abbreviation.indexOf('#')))) {
return false;
}
}
return cssAbbreviationRegex.test(abbreviation);
}
if (abbreviation.startsWith('!')) {
return !/[^!]/.test(abbreviation);
}
// Its common for users to type (sometextinsidebrackets), this should not be treated as an abbreviation
// Grouping in abbreviation is valid only if it's inside a text node or preceeded/succeeded with one of the symbols for nesting, sibling, repeater or climb up
// Also, cases such as `span[onclick="alert();"]` are valid
if ((/\(/.test(abbreviation) || /\)/.test(abbreviation))
&& !/\{[^\}\{]*[\(\)]+[^\}\{]*\}(?:[>\+\*\^]|$)/.test(abbreviation)
&& !/\(.*\)[>\+\*\^]/.test(abbreviation)
&& !/\[[^\[\]\(\)]+=".*"\]/.test(abbreviation)
&& !/[>\+\*\^]\(.*\)/.test(abbreviation)) {
return false;
}
if (syntax === 'jsx') {
return (jsxAbbreviationStartRegex.test(abbreviation) && htmlAbbreviationRegex.test(abbreviation));
}
// Fix for jinja syntax https://github.com/microsoft/vscode/issues/179422
if (/^{%|{#|{{/.test(abbreviation)) {
return false;
}
return (htmlAbbreviationStartRegex.test(abbreviation) && htmlAbbreviationRegex.test(abbreviation));
}
exports.isAbbreviationValid = isAbbreviationValid;
function isExpandedTextNoise(syntax, abbreviation, expandedText, options) {
var _a, _b;
// Unresolved css abbreviations get expanded to a blank property value
// Eg: abc -> abc: ; or abc:d -> abc: d; which is noise if it gets suggested for every word typed
if (isStyleSheet(syntax) && options) {
const between = (_a = options['stylesheet.between']) !== null && _a !== void 0 ? _a : ': ';
const after = (_b = options['stylesheet.after']) !== null && _b !== void 0 ? _b : ';';
// Remove overlapping between `abbreviation` and `between`, if any
let endPrefixIndex = abbreviation.indexOf(between[0], Math.max(abbreviation.length - between.length, 0));
endPrefixIndex = endPrefixIndex >= 0 ? endPrefixIndex : abbreviation.length;
const abbr = abbreviation.substring(0, endPrefixIndex);
return expandedText === `${abbr}${between}\${0}${after}` ||
expandedText.replace(/\s/g, '') === abbreviation.replace(/\s/g, '') + after;
}
// we don't want common html tags suggested for xml
if (syntax === 'xml' &&
commonlyUsedTags.some(tag => tag.startsWith(abbreviation.toLowerCase()))) {
return true;
}
if (commonlyUsedTags.includes(abbreviation.toLowerCase()) ||
markupSnippetKeys.includes(abbreviation)) {
return false;
}
// Custom tags can have - or :
if (/[-,:]/.test(abbreviation) && !/--|::/.test(abbreviation) &&
!abbreviation.endsWith(':')) {
return false;
}
// users might write successive dots '..', '...' which shouldn't be treated as an abbreviation
if (/^\.{2,}$/.test(abbreviation)) {
return true;
}
// Its common for users to type some text and end it with period, this should not be treated as an abbreviation
// Else it becomes noise.
// When user just types '.', return the expansion
// Otherwise emmet loses change to participate later
// For example in `.foo`. See https://github.com/Microsoft/vscode/issues/66013
if (abbreviation === '.') {
return false;
}
const dotMatches = abbreviation.match(/^([a-z,A-Z,\d]*)\.$/);
if (dotMatches) {
// Valid html tags such as `div.`
if (dotMatches[1] && data_1.htmlData.tags.includes(dotMatches[1])) {
return false;
}
return true;
}
// Fix for https://github.com/microsoft/vscode/issues/89746
// PascalCase tags are common in jsx code, which should not be treated as noise.
// Eg: MyAwesomComponent -> <MyAwesomComponent></MyAwesomComponent>
if (syntax === 'jsx' && /^([A-Z][A-Za-z0-9]*)+$/.test(abbreviation)) {
return false;
}
// Unresolved html abbreviations get expanded as if it were a tag
// Eg: abc -> <abc></abc> which is noise if it gets suggested for every word typed
return (expandedText.toLowerCase() === `<${abbreviation.toLowerCase()}>\${1}</${abbreviation.toLowerCase()}>`);
}
/**
* Returns options to be used by emmet
*/
function getExpandOptions(syntax, emmetConfig, filter) {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
emmetConfig = emmetConfig !== null && emmetConfig !== void 0 ? emmetConfig : {};
emmetConfig['preferences'] = (_a = emmetConfig['preferences']) !== null && _a !== void 0 ? _a : {};
const preferences = emmetConfig['preferences'];
const stylesheetSyntax = isStyleSheet(syntax) ? syntax : 'css';
// Fetch Profile
const profile = getProfile(syntax, (_b = emmetConfig['syntaxProfiles']) !== null && _b !== void 0 ? _b : {});
const filtersFromProfile = (profile && profile['filters']) ? profile['filters'].split(',') : [];
const trimmedFilters = filtersFromProfile.map(filterFromProfile => filterFromProfile.trim());
const bemEnabled = (filter && filter.split(',').some(x => x.trim() === 'bem')) || trimmedFilters.includes('bem');
const commentEnabled = (filter && filter.split(',').some(x => x.trim() === 'c')) || trimmedFilters.includes('c');
// Fetch formatters
const formatters = getFormatters(syntax, emmetConfig['preferences']);
const unitAliases = ((formatters === null || formatters === void 0 ? void 0 : formatters.stylesheet) && formatters.stylesheet['unitAliases']) || {};
// These options are the default values provided by vscode for
// extension preferences
const defaultVSCodeOptions = {
// inlineElements: string[],
// 'output.indent': string,
// 'output.baseIndent': string,
// 'output.newline': string,
// 'output.tagCase': profile['tagCase'],
// 'output.attributeCase': profile['attributeCase'],
// 'output.attributeQuotes': profile['attributeQuotes'],
// 'output.format': profile['format'] ?? true,
// 'output.formatLeafNode': boolean,
'output.formatSkip': ['html'],
'output.formatForce': ['body'],
'output.inlineBreak': 0,
'output.compactBoolean': false,
// 'output.booleanAttributes': string[],
'output.reverseAttributes': false,
// 'output.selfClosingStyle': profile['selfClosingStyle'],
'output.field': exports.emmetSnippetField,
// 'output.text': TextOutput,
'markup.href': true,
'comment.enabled': false,
'comment.trigger': ['id', 'class'],
'comment.before': '',
'comment.after': '\n<!-- /[#ID][.CLASS] -->',
'bem.enabled': false,
'bem.element': '__',
'bem.modifier': '_',
'jsx.enabled': syntax === 'jsx',
// 'stylesheet.keywords': string[],
// 'stylesheet.unitless': string[],
'stylesheet.shortHex': true,
'stylesheet.between': syntax === 'stylus' ? ' ' : ': ',
'stylesheet.after': (syntax === 'sass' || syntax === 'stylus') ? '' : ';',
'stylesheet.intUnit': 'px',
'stylesheet.floatUnit': 'em',
'stylesheet.unitAliases': { e: 'em', p: '%', x: 'ex', r: 'rem' },
// 'stylesheet.json': boolean,
// 'stylesheet.jsonDoubleQuotes': boolean,
'stylesheet.fuzzySearchMinScore': 0.3,
};
// These options come from user prefs in the vscode repo
let userPreferenceOptions = {
// inlineElements: string[],
// 'output.indent': string,
// 'output.baseIndent': string,
// 'output.newline': string,
'output.tagCase': profile['tagCase'],
'output.attributeCase': profile['attributeCase'],
'output.attributeQuotes': profile['attributeQuotes'],
'output.format': (_c = profile['format']) !== null && _c !== void 0 ? _c : true,
// 'output.formatLeafNode': boolean,
'output.formatSkip': preferences['format.noIndentTags'],
'output.formatForce': preferences['format.forceIndentationForTags'],
'output.inlineBreak': (_d = profile['inlineBreak']) !== null && _d !== void 0 ? _d : preferences['output.inlineBreak'],
'output.compactBoolean': (_e = profile['compactBooleanAttributes']) !== null && _e !== void 0 ? _e : preferences['profile.allowCompactBoolean'],
// 'output.booleanAttributes': string[],
'output.reverseAttributes': preferences['output.reverseAttributes'],
'output.selfClosingStyle': (_g = (_f = profile['selfClosingStyle']) !== null && _f !== void 0 ? _f : preferences['output.selfClosingStyle']) !== null && _g !== void 0 ? _g : getClosingStyle(syntax),
'output.field': exports.emmetSnippetField,
// 'output.text': TextOutput,
// 'markup.href': boolean,
'comment.enabled': commentEnabled,
'comment.trigger': preferences['filter.commentTrigger'],
'comment.before': preferences['filter.commentBefore'],
'comment.after': preferences['filter.commentAfter'],
'bem.enabled': bemEnabled,
'bem.element': (_h = preferences['bem.elementSeparator']) !== null && _h !== void 0 ? _h : '__',
'bem.modifier': (_j = preferences['bem.modifierSeparator']) !== null && _j !== void 0 ? _j : '_',
'jsx.enabled': syntax === 'jsx',
// 'stylesheet.keywords': string[],
// 'stylesheet.unitless': string[],
'stylesheet.shortHex': preferences['css.color.short'],
'stylesheet.between': preferences[`${stylesheetSyntax}.valueSeparator`],
'stylesheet.after': preferences[`${stylesheetSyntax}.propertyEnd`],
'stylesheet.intUnit': preferences['css.intUnit'],
'stylesheet.floatUnit': preferences['css.floatUnit'],
'stylesheet.unitAliases': unitAliases,
// 'stylesheet.json': boolean,
// 'stylesheet.jsonDoubleQuotes': boolean,
'stylesheet.fuzzySearchMinScore': preferences['css.fuzzySearchMinScore']
};
if (syntax === 'jsx') {
// Ref https://github.com/emmetio/emmet/blob/master/src/config.ts#L391
const defaultMarkupAttributeOptions = {
'class': 'className',
'class*': 'styleName',
'for': 'htmlFor'
};
const defaultMarkupValuePrefixOptions = {
'class*': 'styles'
};
// Rather than trying to merge these specific options upstream,
// we can merge them here before passing them upstream.
if (profile['markup.attributes']) {
userPreferenceOptions['markup.attributes'] = Object.assign(Object.assign({}, defaultMarkupAttributeOptions), profile['markup.attributes']);
}
if (profile['markup.valuePrefix']) {
userPreferenceOptions['markup.valuePrefix'] = Object.assign(Object.assign({}, defaultMarkupValuePrefixOptions), profile['markup.valuePrefix']);
}
}
const combinedOptions = {};
[...Object.keys(defaultVSCodeOptions), ...Object.keys(userPreferenceOptions)].forEach(key => {
var _a;
const castKey = key;
combinedOptions[castKey] = (_a = userPreferenceOptions[castKey]) !== null && _a !== void 0 ? _a : defaultVSCodeOptions[castKey];
});
const mergedAliases = Object.assign(Object.assign({}, defaultVSCodeOptions['stylesheet.unitAliases']), userPreferenceOptions['stylesheet.unitAliases']);
combinedOptions['stylesheet.unitAliases'] = mergedAliases;
const type = getSyntaxType(syntax);
const variables = getVariables(emmetConfig['variables']);
const baseSyntax = getDefaultSyntax(syntax);
const snippets = (type === 'stylesheet') ?
((_k = customSnippetsRegistry[syntax]) !== null && _k !== void 0 ? _k : customSnippetsRegistry[baseSyntax]) :
customSnippetsRegistry[syntax];
return {
type,
options: combinedOptions,
variables,
snippets,
syntax,
// context: null,
text: undefined,
maxRepeat: 1000,
// cache: null
};
}
exports.getExpandOptions = getExpandOptions;
function getClosingStyle(syntax) {
switch (syntax) {
case 'xhtml': return 'xhtml';
case 'xml': return 'xml';
case 'xsl': return 'xml';
case 'jsx': return 'xhtml';
default: return 'html';
}
}
/**
* Parses given abbreviation using given options and returns a tree
* @param abbreviation string
* @param options options used by the emmet module to parse given abbreviation
*/
function parseAbbreviation(abbreviation, options) {
const resolvedOptions = (0, emmet_1.resolveConfig)(options);
return (options.type === 'stylesheet') ?
(0, emmet_1.parseStylesheet)(abbreviation, resolvedOptions) :
(0, emmet_1.parseMarkup)(abbreviation, resolvedOptions);
}
exports.parseAbbreviation = parseAbbreviation;
/**
* Expands given abbreviation using given options
* @param abbreviation string or parsed abbreviation
* @param config options used by the @emmetio/expand-abbreviation module to expand given abbreviation
*/
function expandAbbreviation(abbreviation, config) {
let expandedText;
const resolvedConfig = (0, emmet_1.resolveConfig)(config);
if (config.type === 'stylesheet') {
if (typeof abbreviation === 'string') {
expandedText = (0, emmet_1.default)(abbreviation, resolvedConfig);
}
else {
expandedText = (0, emmet_1.stringifyStylesheet)(abbreviation, resolvedConfig);
}
}
else {
if (typeof abbreviation === 'string') {
expandedText = (0, emmet_1.default)(abbreviation, resolvedConfig);
}
else {
expandedText = (0, emmet_1.stringifyMarkup)(abbreviation, resolvedConfig);
}
}
return escapeNonTabStopDollar(addFinalTabStop(expandedText));
}
exports.expandAbbreviation = expandAbbreviation;
/**
* Maps and returns syntaxProfiles of previous format to ones compatible with new emmet modules
* @param syntax
*/
function getProfile(syntax, profilesFromSettings) {
if (!profilesFromSettings) {
profilesFromSettings = {};
}
const profilesConfig = Object.assign({}, profilesFromFile, profilesFromSettings);
const options = profilesConfig[syntax];
if (!options || typeof options === 'string') {
if (options === 'xhtml') {
return {
selfClosingStyle: 'xhtml'
};
}
return {};
}
const newOptions = {};
for (const key in options) {
switch (key) {
case 'tag_case':
newOptions['tagCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : '';
break;
case 'attr_case':
newOptions['attributeCase'] = (options[key] === 'lower' || options[key] === 'upper') ? options[key] : '';
break;
case 'attr_quotes':
newOptions['attributeQuotes'] = options[key];
break;
case 'tag_nl':
newOptions['format'] = (options[key] === true || options[key] === false) ? options[key] : true;
break;
case 'inline_break':
newOptions['inlineBreak'] = options[key];
break;
case 'self_closing_tag':
if (options[key] === true) {
newOptions['selfClosingStyle'] = 'xml';
break;
}
if (options[key] === false) {
newOptions['selfClosingStyle'] = 'html';
break;
}
newOptions['selfClosingStyle'] = options[key];
break;
case 'compact_bool':
newOptions['compactBooleanAttributes'] = options[key];
break;
default:
newOptions[key] = options[key];
break;
}
}
return newOptions;
}
/**
* Returns variables to be used while expanding snippets
*/
function getVariables(variablesFromSettings) {
if (!variablesFromSettings) {
return variablesFromFile;
}
return Object.assign({}, variablesFromFile, variablesFromSettings);
}
function getFormatters(syntax, preferences) {
if (!preferences || typeof preferences !== 'object') {
return {};
}
if (!isStyleSheet(syntax)) {
const commentFormatter = {};
for (const key in preferences) {
switch (key) {
case 'filter.commentAfter':
commentFormatter['after'] = preferences[key];
break;
case 'filter.commentBefore':
commentFormatter['before'] = preferences[key];
break;
case 'filter.commentTrigger':
commentFormatter['trigger'] = preferences[key];
break;
default:
break;
}
}
return {
comment: commentFormatter
};
}
let fuzzySearchMinScore = typeof (preferences === null || preferences === void 0 ? void 0 : preferences['css.fuzzySearchMinScore']) === 'number' ? preferences['css.fuzzySearchMinScore'] : 0.3;
if (fuzzySearchMinScore > 1) {
fuzzySearchMinScore = 1;
}
else if (fuzzySearchMinScore < 0) {
fuzzySearchMinScore = 0;
}
const stylesheetFormatter = {
'fuzzySearchMinScore': fuzzySearchMinScore
};
for (const key in preferences) {
switch (key) {
case 'css.floatUnit':
stylesheetFormatter['floatUnit'] = preferences[key];
break;
case 'css.intUnit':
stylesheetFormatter['intUnit'] = preferences[key];
break;
case 'css.unitAliases':
const unitAliases = {};
preferences[key].split(',').forEach((alias) => {
if (!alias || !alias.trim() || !alias.includes(':')) {
return;
}
const aliasName = alias.substr(0, alias.indexOf(':'));
const aliasValue = alias.substr(aliasName.length + 1);
if (!aliasName.trim() || !aliasValue) {
return;
}
unitAliases[aliasName.trim()] = aliasValue;
});
stylesheetFormatter['unitAliases'] = unitAliases;
break;
case `${syntax}.valueSeparator`:
stylesheetFormatter['between'] = preferences[key];
break;
case `${syntax}.propertyEnd`:
stylesheetFormatter['after'] = preferences[key];
break;
default:
break;
}
}
return {
stylesheet: stylesheetFormatter
};
}
/**
* Updates customizations from snippets.json and syntaxProfiles.json files in the directory configured in emmet.extensionsPath setting
* @param emmetExtensionsPathSetting setting passed from emmet.extensionsPath. Supports multiple paths
*/
function updateExtensionsPath(emmetExtensionsPathSetting, fs, workspaceFolderPaths, homeDir) {
return __awaiter(this, void 0, void 0, function* () {
resetSettingsFromFile();
if (!emmetExtensionsPathSetting.length) {
return;
}
// Extract URIs from the given setting
const emmetExtensionsPathUri = [];
for (let emmetExtensionsPath of emmetExtensionsPathSetting) {
if (typeof emmetExtensionsPath !== 'string') {
console.warn("The following emmetExtensionsPath isn't a string: " + JSON.stringify(emmetExtensionsPath));
continue;
}
emmetExtensionsPath = emmetExtensionsPath.trim();
if (emmetExtensionsPath.length && emmetExtensionsPath[0] === '~') {
if (homeDir) {
emmetExtensionsPathUri.push((0, fileService_1.joinPath)(homeDir, emmetExtensionsPath.substring(1)));
}
}
else if (!(0, fileService_1.isAbsolutePath)(emmetExtensionsPath)) {
if (workspaceFolderPaths) {
// Try pushing the path for each workspace root
for (const workspacePath of workspaceFolderPaths) {
emmetExtensionsPathUri.push((0, fileService_1.joinPath)(workspacePath, emmetExtensionsPath));
}
}
}
else {
emmetExtensionsPathUri.push(vscode_uri_1.URI.file(emmetExtensionsPath));
}
}
// For each URI, grab the files
for (const uri of emmetExtensionsPathUri) {
try {
if ((yield fs.stat(uri)).type !== fileService_1.FileType.Directory) {
// Invalid directory, or path is not a directory
continue;
}
}
catch (e) {
// stat threw an error
continue;
}
const snippetsPath = (0, fileService_1.joinPath)(uri, 'snippets.json');
const profilesPath = (0, fileService_1.joinPath)(uri, 'syntaxProfiles.json');
let decoder;
if (typeof globalThis.TextDecoder === 'function') {
decoder = new globalThis.TextDecoder();
}
else {
decoder = new util_1.TextDecoder();
}
// the only errors we want to throw here are JSON parse errors
let snippetsDataStr = "";
try {
const snippetsData = yield fs.readFile(snippetsPath);
snippetsDataStr = decoder.decode(snippetsData);
}
catch (e) {
}
if (snippetsDataStr.length) {
try {
const snippetsJson = tryParseFile(snippetsPath, snippetsDataStr);
if (snippetsJson['variables']) {
updateVariables(snippetsJson['variables']);
}
updateSnippets(snippetsJson);
}
catch (e) {
resetSettingsFromFile();
throw e;
}
}
let profilesDataStr = "";
try {
const profilesData = yield fs.readFile(profilesPath);
profilesDataStr = decoder.decode(profilesData);
}
catch (e) {
}
if (profilesDataStr.length) {
try {
const profilesJson = tryParseFile(profilesPath, profilesDataStr);
updateProfiles(profilesJson);
}
catch (e) {
resetSettingsFromFile();
throw e;
}
}
}
});
}
exports.updateExtensionsPath = updateExtensionsPath;
function tryParseFile(strPath, dataStr) {
let errors = [];
const json = JSONC.parse(dataStr, errors);
if (errors.length) {
throw new Error(`Found error ${JSONC.printParseErrorCode(errors[0].error)} while parsing the file ${strPath} at offset ${errors[0].offset}`);
}
return json;
}
/**
* Assigns variables from one snippet file under emmet.extensionsPath to
* variablesFromFile
*/
function updateVariables(varsJson) {
if (typeof varsJson === 'object' && varsJson) {
variablesFromFile = Object.assign({}, variablesFromFile, varsJson);
}
else {
throw new Error(l10n.t('Invalid emmet.variables field. See https://code.visualstudio.com/docs/editor/emmet#_emmet-configuration for a valid example.'));
}
}
/**
* Assigns profiles from one profile file under emmet.extensionsPath to
* profilesFromFile
*/
function updateProfiles(profileJson) {
if (typeof profileJson === 'object' && profileJson) {
profilesFromFile = Object.assign({}, profilesFromFile, profileJson);
}
else {
throw new Error(l10n.t('Invalid syntax profile. See https://code.visualstudio.com/docs/editor/emmet#_emmet-configuration for a valid example.'));
}
}
/**
* Assigns snippets from one snippet file under emmet.extensionsPath to
* customSnippetsRegistry, snippetKeyCache, and stylesheetCustomSnippetsKeyCache
*/
function updateSnippets(snippetsJson) {
if (typeof snippetsJson === 'object' && snippetsJson) {
Object.keys(snippetsJson).forEach(syntax => {
if (!snippetsJson[syntax]['snippets']) {
return;
}
const baseSyntax = getDefaultSyntax(syntax);
let customSnippets = snippetsJson[syntax]['snippets'];
if (snippetsJson[baseSyntax] && snippetsJson[baseSyntax]['snippets'] && baseSyntax !== syntax) {
customSnippets = Object.assign({}, snippetsJson[baseSyntax]['snippets'], snippetsJson[syntax]['snippets']);
}
if (!isStyleSheet(syntax)) {
// In Emmet 2.0 all snippets should be valid abbreviations
// Convert old snippets that do not follow this format to new format
for (const snippetKey in customSnippets) {
if (customSnippets.hasOwnProperty(snippetKey)
&& customSnippets[snippetKey].startsWith('<')
&& customSnippets[snippetKey].endsWith('>')) {
customSnippets[snippetKey] = `{${customSnippets[snippetKey]}}`;
}
}
}
else {
const prevSnippetKeys = stylesheetCustomSnippetsKeyCache.get(syntax);
const mergedSnippetKeys = Object.assign([], prevSnippetKeys, Object.keys(customSnippets));
stylesheetCustomSnippetsKeyCache.set(syntax, mergedSnippetKeys);
}
const prevSnippetsRegistry = customSnippetsRegistry[syntax];
const newSnippets = (0, configCompat_1.parseSnippets)(customSnippets);
const mergedSnippets = Object.assign({}, prevSnippetsRegistry, newSnippets);
customSnippetsRegistry[syntax] = mergedSnippets;
});
}
else {
throw new Error(l10n.t('Invalid snippets file. See https://code.visualstudio.com/docs/editor/emmet#_using-custom-emmet-snippets for a valid example.'));
}
}
function resetSettingsFromFile() {
customSnippetsRegistry = {};
snippetKeyCache.clear();
stylesheetCustomSnippetsKeyCache.clear();
profilesFromFile = {};
variablesFromFile = {};
}
/**
* Get the corresponding emmet mode for given vscode language mode
* Eg: jsx for typescriptreact/javascriptreact or pug for jade
* If the language is not supported by emmet or has been exlcuded via `exlcudeLanguages` setting,
* then nothing is returned
*
* @param language
* @param exlcudedLanguages Array of language ids that user has chosen to exlcude for emmet
*/
function getEmmetMode(language, excludedLanguages = []) {
if (!language || excludedLanguages.includes(language)) {
return;
}
if (/\b(typescriptreact|javascriptreact|jsx-tags)\b/.test(language)) { // treat tsx like jsx
return 'jsx';
}
if (language === 'sass-indented') { // map sass-indented to sass
return 'sass';
}
if (language === 'jade') {
return 'pug';
}
if (configCompat_1.syntaxes.markup.includes(language) || configCompat_1.syntaxes.stylesheet.includes(language)) {
return language;
}
}
exports.getEmmetMode = getEmmetMode;
//# sourceMappingURL=emmetHelper.js.map