"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}`) { 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 -> 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 -> which is noise if it gets suggested for every word typed return (expandedText.toLowerCase() === `<${abbreviation.toLowerCase()}>\${1}`); } /** * 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', '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