Path: blob/main/extensions/emmet/src/abbreviationActions.ts
4772 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import * as vscode from 'vscode';6import { Node, HtmlNode, Rule, Property, Stylesheet } from 'EmmetFlatNode';7import { getEmmetHelper, getFlatNode, getHtmlFlatNode, getMappingForIncludedLanguages, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, allowedMimeTypesInScriptTag, toLSTextDocument, isOffsetInsideOpenOrCloseTag } from './util';8import { getRootNode as parseDocument } from './parseDocument';910const trimRegex = /[\u00a0]*[\d#\-\*\u2022]+\.?/;11const hexColorRegex = /^#[\da-fA-F]{0,6}$/;1213interface ExpandAbbreviationInput {14syntax: string;15abbreviation: string;16rangeToReplace: vscode.Range;17textToWrap?: string[];18filter?: string;19indent?: string;20baseIndent?: string;21}2223interface PreviewRangesWithContent {24previewRange: vscode.Range;25originalRange: vscode.Range;26originalContent: string;27textToWrapInPreview: string[];28baseIndent: string;29}3031export async function wrapWithAbbreviation(args: any): Promise<boolean> {32if (!validate(false)) {33return false;34}3536const editor = vscode.window.activeTextEditor!;37const document = editor.document;3839args = args || {};40if (!args['language']) {41args['language'] = document.languageId;42}43// we know it's not stylesheet due to the validate(false) call above44const syntax = getSyntaxFromArgs(args) || 'html';45const rootNode = parseDocument(document, true);4647const helper = getEmmetHelper();4849const operationRanges = Array.from(editor.selections).sort((a, b) => a.start.compareTo(b.start)).map(selection => {50let rangeToReplace: vscode.Range = selection;51// wrap around the node if the selection falls inside its open or close tag52{53let { start, end } = rangeToReplace;5455const startOffset = document.offsetAt(start);56const documentText = document.getText();57const startNode = getHtmlFlatNode(documentText, rootNode, startOffset, true);58if (startNode && isOffsetInsideOpenOrCloseTag(startNode, startOffset)) {59start = document.positionAt(startNode.start);60const nodeEndPosition = document.positionAt(startNode.end);61end = nodeEndPosition.isAfter(end) ? nodeEndPosition : end;62}6364const endOffset = document.offsetAt(end);65const endNode = getHtmlFlatNode(documentText, rootNode, endOffset, true);66if (endNode && isOffsetInsideOpenOrCloseTag(endNode, endOffset)) {67const nodeStartPosition = document.positionAt(endNode.start);68start = nodeStartPosition.isBefore(start) ? nodeStartPosition : start;69const nodeEndPosition = document.positionAt(endNode.end);70end = nodeEndPosition.isAfter(end) ? nodeEndPosition : end;71}7273rangeToReplace = new vscode.Range(start, end);74}75// in case of multi-line, exclude last empty line from rangeToReplace76if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) {77const previousLine = rangeToReplace.end.line - 1;78rangeToReplace = new vscode.Range(rangeToReplace.start, document.lineAt(previousLine).range.end);79}80// wrap line the cursor is on81if (rangeToReplace.isEmpty) {82rangeToReplace = document.lineAt(rangeToReplace.start).range;83}8485// ignore whitespace on the first line86const firstLineOfRange = document.lineAt(rangeToReplace.start);87if (!firstLineOfRange.isEmptyOrWhitespace && firstLineOfRange.firstNonWhitespaceCharacterIndex > rangeToReplace.start.character) {88rangeToReplace = rangeToReplace.with(new vscode.Position(rangeToReplace.start.line, firstLineOfRange.firstNonWhitespaceCharacterIndex));89}9091return rangeToReplace;92}).reduce((mergedRanges, range) => {93// Merge overlapping ranges94if (mergedRanges.length > 0 && range.intersection(mergedRanges[mergedRanges.length - 1])) {95mergedRanges.push(range.union(mergedRanges.pop()!));96} else {97mergedRanges.push(range);98}99return mergedRanges;100}, [] as vscode.Range[]);101102// Backup orginal selections and update selections103// Also helps with https://github.com/microsoft/vscode/issues/113930 by avoiding `editor.linkedEditing`104// execution if selection is inside an open or close tag105const oldSelections = editor.selections;106editor.selections = operationRanges.map(range => new vscode.Selection(range.start, range.end));107108// Fetch general information for the succesive expansions. i.e. the ranges to replace and its contents109const rangesToReplace: PreviewRangesWithContent[] = operationRanges.map(rangeToReplace => {110let textToWrapInPreview: string[];111const textToReplace = document.getText(rangeToReplace);112113// the following assumes all the lines are indented the same way as the first114// this assumption helps with applyPreview later115const wholeFirstLine = document.lineAt(rangeToReplace.start).text;116const otherMatches = wholeFirstLine.match(/^(\s*)/);117const baseIndent = otherMatches ? otherMatches[1] : '';118textToWrapInPreview = rangeToReplace.isSingleLine ?119[textToReplace] :120textToReplace.split('\n' + baseIndent).map(x => x.trimEnd());121122// escape $ characters, fixes #52640123textToWrapInPreview = textToWrapInPreview.map(e => e.replace(/(\$\d)/g, '\\$1'));124125return {126previewRange: rangeToReplace,127originalRange: rangeToReplace,128originalContent: textToReplace,129textToWrapInPreview,130baseIndent131};132});133134const { tabSize, insertSpaces } = editor.options;135const indent = insertSpaces ? ' '.repeat(tabSize as number) : '\t';136137function revertPreview(): Thenable<boolean> {138return editor.edit(builder => {139for (const rangeToReplace of rangesToReplace) {140builder.replace(rangeToReplace.previewRange, rangeToReplace.originalContent);141rangeToReplace.previewRange = rangeToReplace.originalRange;142}143}, { undoStopBefore: false, undoStopAfter: false });144}145146function applyPreview(expandAbbrList: ExpandAbbreviationInput[]): Thenable<boolean> {147let lastOldPreviewRange = new vscode.Range(0, 0, 0, 0);148let lastNewPreviewRange = new vscode.Range(0, 0, 0, 0);149let totalNewLinesInserted = 0;150151return editor.edit(builder => {152// the edits are applied in order top-down153for (let i = 0; i < rangesToReplace.length; i++) {154const expandedText = expandAbbr(expandAbbrList[i]) || '';155if (!expandedText) {156// Failed to expand text. We already showed an error inside expandAbbr.157break;158}159160// get the current preview range, format the new wrapped text, and then replace161// the text in the preview range with that new text162const oldPreviewRange = rangesToReplace[i].previewRange;163const newText = expandedText164.replace(/\$\{[\d]*\}/g, '|') // Removing Tabstops165.replace(/\$\{[\d]*:([^}]*)\}/g, (_, placeholder) => placeholder) // Replacing Placeholders166.replace(/\\\$/g, '$'); // Remove backslashes before $167builder.replace(oldPreviewRange, newText);168169// calculate the new preview range to use for future previews170// we also have to take into account that the previous expansions could:171// - cause new lines to appear172// - be on the same line as other expansions173const expandedTextLines = newText.split('\n');174const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1;175const newLinesInserted = expandedTextLines.length - oldPreviewLines;176177const newPreviewLineStart = oldPreviewRange.start.line + totalNewLinesInserted;178let newPreviewStart = oldPreviewRange.start.character;179const newPreviewLineEnd = oldPreviewRange.end.line + totalNewLinesInserted + newLinesInserted;180let newPreviewEnd = expandedTextLines[expandedTextLines.length - 1].length;181if (i > 0 && newPreviewLineEnd === lastNewPreviewRange.end.line) {182// If newPreviewLineEnd is equal to the previous expandedText lineEnd,183// set newPreviewStart to the length of the previous expandedText in that line184// plus the number of characters between both selections.185newPreviewStart = lastNewPreviewRange.end.character + (oldPreviewRange.start.character - lastOldPreviewRange.end.character);186newPreviewEnd += newPreviewStart;187} else if (i > 0 && newPreviewLineStart === lastNewPreviewRange.end.line) {188// Same as above but expandedTextLines.length > 1 so newPreviewEnd keeps its value.189newPreviewStart = lastNewPreviewRange.end.character + (oldPreviewRange.start.character - lastOldPreviewRange.end.character);190} else if (expandedTextLines.length === 1) {191// If the expandedText is single line, add the length of preceeding text as it will not be included in line length.192newPreviewEnd += oldPreviewRange.start.character;193}194195lastOldPreviewRange = rangesToReplace[i].previewRange;196lastNewPreviewRange = new vscode.Range(newPreviewLineStart, newPreviewStart, newPreviewLineEnd, newPreviewEnd);197rangesToReplace[i].previewRange = lastNewPreviewRange;198totalNewLinesInserted += newLinesInserted;199}200}, { undoStopBefore: false, undoStopAfter: false });201}202203let inPreviewMode = false;204async function makeChanges(inputAbbreviation: string | undefined, previewChanges: boolean): Promise<boolean> {205const isAbbreviationValid = !!inputAbbreviation && !!inputAbbreviation.trim() && helper.isAbbreviationValid(syntax, inputAbbreviation);206const extractedResults = isAbbreviationValid ? helper.extractAbbreviationFromText(inputAbbreviation!, syntax) : undefined;207if (!extractedResults) {208if (inPreviewMode) {209inPreviewMode = false;210await revertPreview();211}212return false;213}214215const { abbreviation, filter } = extractedResults;216if (abbreviation !== inputAbbreviation) {217// Not clear what should we do in this case. Warn the user? How?218}219220if (previewChanges) {221const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent =>222({ syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter, indent, baseIndent: rangesAndContent.baseIndent })223);224225inPreviewMode = true;226return applyPreview(expandAbbrList);227}228229const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent =>230({ syntax, abbreviation, rangeToReplace: rangesAndContent.originalRange, textToWrap: rangesAndContent.textToWrapInPreview, filter, indent })231);232233if (inPreviewMode) {234inPreviewMode = false;235await revertPreview();236}237238return expandAbbreviationInRange(editor, expandAbbrList, false);239}240241let currentValue = '';242async function inputChanged(value: string): Promise<string> {243if (value !== currentValue) {244currentValue = value;245await makeChanges(value, true);246}247return '';248}249250const prompt = vscode.l10n.t("Enter Abbreviation");251const inputAbbreviation = (args && args['abbreviation'])252? (args['abbreviation'] as string)253: await vscode.window.showInputBox({ prompt, validateInput: inputChanged });254255const changesWereMade = await makeChanges(inputAbbreviation, false);256if (!changesWereMade) {257editor.selections = oldSelections;258}259260return changesWereMade;261}262263export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined> {264if (!validate() || !vscode.window.activeTextEditor) {265return fallbackTab();266}267268/**269* Short circuit the parsing. If previous character is space, do not expand.270*/271if (vscode.window.activeTextEditor.selections.length === 1 &&272vscode.window.activeTextEditor.selection.isEmpty273) {274const anchor = vscode.window.activeTextEditor.selection.anchor;275if (anchor.character === 0) {276return fallbackTab();277}278279const prevPositionAnchor = anchor.translate(0, -1);280const prevText = vscode.window.activeTextEditor.document.getText(new vscode.Range(prevPositionAnchor, anchor));281if (prevText === ' ' || prevText === '\t') {282return fallbackTab();283}284}285286args = args || {};287if (!args['language']) {288args['language'] = vscode.window.activeTextEditor.document.languageId;289} else {290const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : [];291if (excludedLanguages.includes(vscode.window.activeTextEditor.document.languageId)) {292return fallbackTab();293}294}295const syntax = getSyntaxFromArgs(args);296if (!syntax) {297return fallbackTab();298}299300const editor = vscode.window.activeTextEditor;301302// When tabbed on a non empty selection, do not treat it as an emmet abbreviation, and fallback to tab instead303if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true && editor.selections.find(x => !x.isEmpty)) {304return fallbackTab();305}306307const abbreviationList: ExpandAbbreviationInput[] = [];308let firstAbbreviation: string;309let allAbbreviationsSame: boolean = true;310const helper = getEmmetHelper();311312const getAbbreviation = (document: vscode.TextDocument, selection: vscode.Selection, position: vscode.Position, syntax: string): [vscode.Range | null, string, string | undefined] => {313position = document.validatePosition(position);314let rangeToReplace: vscode.Range = selection;315let abbr = document.getText(rangeToReplace);316if (!rangeToReplace.isEmpty) {317const extractedResults = helper.extractAbbreviationFromText(abbr, syntax);318if (extractedResults) {319return [rangeToReplace, extractedResults.abbreviation, extractedResults.filter];320}321return [null, '', ''];322}323324const currentLine = editor.document.lineAt(position.line).text;325const textTillPosition = currentLine.substr(0, position.character);326327// Expand cases like <div to <div></div> explicitly328// else we will end up with <<div></div>329if (syntax === 'html') {330const matches = textTillPosition.match(/<(\w+)$/);331if (matches) {332abbr = matches[1];333rangeToReplace = new vscode.Range(position.translate(0, -(abbr.length + 1)), position);334return [rangeToReplace, abbr, ''];335}336}337const extractedResults = helper.extractAbbreviation(toLSTextDocument(editor.document), position, { lookAhead: false });338if (!extractedResults) {339return [null, '', ''];340}341342const { abbreviationRange, abbreviation, filter } = extractedResults;343return [new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character), abbreviation, filter];344};345346const selectionsInReverseOrder = editor.selections.slice(0);347selectionsInReverseOrder.sort((a, b) => {348const posA = a.isReversed ? a.anchor : a.active;349const posB = b.isReversed ? b.anchor : b.active;350return posA.compareTo(posB) * -1;351});352353let rootNode: Node | undefined;354function getRootNode() {355if (rootNode) {356return rootNode;357}358359const usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true;360if (editor.selections.length === 1 && isStyleSheet(editor.document.languageId) && usePartialParsing && editor.document.lineCount > 1000) {361rootNode = parsePartialStylesheet(editor.document, editor.selection.isReversed ? editor.selection.anchor : editor.selection.active);362} else {363rootNode = parseDocument(editor.document, true);364}365366return rootNode;367}368369selectionsInReverseOrder.forEach(selection => {370const position = selection.isReversed ? selection.anchor : selection.active;371const [rangeToReplace, abbreviation, filter] = getAbbreviation(editor.document, selection, position, syntax);372if (!rangeToReplace) {373return;374}375if (!helper.isAbbreviationValid(syntax, abbreviation)) {376return;377}378if (isStyleSheet(syntax) && abbreviation.endsWith(':')) {379// Fix for https://github.com/Microsoft/vscode/issues/1623380return;381}382383const offset = editor.document.offsetAt(position);384let currentNode = getFlatNode(getRootNode(), offset, true);385let validateLocation = true;386let syntaxToUse = syntax;387388if (editor.document.languageId === 'html') {389if (isStyleAttribute(currentNode, offset)) {390syntaxToUse = 'css';391validateLocation = false;392} else {393const embeddedCssNode = getEmbeddedCssNodeIfAny(editor.document, currentNode, position);394if (embeddedCssNode) {395currentNode = getFlatNode(embeddedCssNode, offset, true);396syntaxToUse = 'css';397}398}399}400401if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, getRootNode(), currentNode, syntaxToUse, offset, rangeToReplace)) {402return;403}404405if (!firstAbbreviation) {406firstAbbreviation = abbreviation;407} else if (allAbbreviationsSame && firstAbbreviation !== abbreviation) {408allAbbreviationsSame = false;409}410411abbreviationList.push({ syntax: syntaxToUse, abbreviation, rangeToReplace, filter });412});413414return expandAbbreviationInRange(editor, abbreviationList, allAbbreviationsSame).then(success => {415return success ? Promise.resolve(undefined) : fallbackTab();416});417}418419function fallbackTab(): Thenable<boolean | undefined> {420if (vscode.workspace.getConfiguration('emmet')['triggerExpansionOnTab'] === true) {421return vscode.commands.executeCommand('tab');422}423return Promise.resolve(true);424}425/**426* Checks if given position is a valid location to expand emmet abbreviation.427* Works only on html and css/less/scss syntax428* @param document current Text Document429* @param rootNode parsed document430* @param currentNode current node in the parsed document431* @param syntax syntax of the abbreviation432* @param position position to validate433* @param abbreviationRange The range of the abbreviation for which given position is being validated434*/435export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | undefined, syntax: string, offset: number, abbreviationRange: vscode.Range): boolean {436if (isStyleSheet(syntax)) {437const stylesheet = <Stylesheet>rootNode;438if (stylesheet && (stylesheet.comments || []).some(x => offset >= x.start && offset <= x.end)) {439return false;440}441// Continue validation only if the file was parse-able and the currentNode has been found442if (!currentNode) {443return true;444}445446// Get the abbreviation right now447// Fixes https://github.com/microsoft/vscode/issues/74505448// Stylesheet abbreviations starting with @ should bring up suggestions449// even at outer-most level450const abbreviation = document.getText(new vscode.Range(abbreviationRange.start.line, abbreviationRange.start.character, abbreviationRange.end.line, abbreviationRange.end.character));451if (abbreviation.startsWith('@')) {452return true;453}454455// Fix for https://github.com/microsoft/vscode/issues/34162456// Other than sass, stylus, we can make use of the terminator tokens to validate position457if (syntax !== 'sass' && syntax !== 'stylus' && currentNode.type === 'property') {458// Fix for upstream issue https://github.com/emmetio/css-parser/issues/3459if (currentNode.parent460&& currentNode.parent.type !== 'rule'461&& currentNode.parent.type !== 'at-rule') {462return false;463}464465const propertyNode = <Property>currentNode;466if (propertyNode.terminatorToken467&& propertyNode.separator468&& offset >= propertyNode.separatorToken.end469&& offset <= propertyNode.terminatorToken.start470&& !abbreviation.includes(':')) {471return hexColorRegex.test(abbreviation) || abbreviation === '!';472}473if (!propertyNode.terminatorToken474&& propertyNode.separator475&& offset >= propertyNode.separatorToken.end476&& !abbreviation.includes(':')) {477return hexColorRegex.test(abbreviation) || abbreviation === '!';478}479if (hexColorRegex.test(abbreviation) || abbreviation === '!') {480return false;481}482}483484// If current node is a rule or at-rule, then perform additional checks to ensure485// emmet suggestions are not provided in the rule selector486if (currentNode.type !== 'rule' && currentNode.type !== 'at-rule') {487return true;488}489490const currentCssNode = <Rule>currentNode;491492// Position is valid if it occurs after the `{` that marks beginning of rule contents493if (offset > currentCssNode.contentStartToken.end) {494return true;495}496497// Workaround for https://github.com/microsoft/vscode/30188498// The line above the rule selector is considered as part of the selector by the css-parser499// But we should assume it is a valid location for css properties under the parent rule500if (currentCssNode.parent501&& (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule')502&& currentCssNode.selectorToken) {503const position = document.positionAt(offset);504const tokenStartPos = document.positionAt(currentCssNode.selectorToken.start);505const tokenEndPos = document.positionAt(currentCssNode.selectorToken.end);506if (position.line !== tokenEndPos.line507&& tokenStartPos.character === abbreviationRange.start.character508&& tokenStartPos.line === abbreviationRange.start.line509) {510return true;511}512}513514return false;515}516517const startAngle = '<';518const endAngle = '>';519const escape = '\\';520const question = '?';521const currentHtmlNode = <HtmlNode>currentNode;522let start = 0;523524if (currentHtmlNode) {525if (currentHtmlNode.name === 'script') {526const typeAttribute = (currentHtmlNode.attributes || []).filter(x => x.name.toString() === 'type')[0];527const typeValue = typeAttribute ? typeAttribute.value.toString() : '';528529if (allowedMimeTypesInScriptTag.includes(typeValue)) {530return true;531}532533const isScriptJavascriptType = !typeValue || typeValue === 'application/javascript' || typeValue === 'text/javascript';534if (isScriptJavascriptType) {535return !!getSyntaxFromArgs({ language: 'javascript' });536}537return false;538}539540// Fix for https://github.com/microsoft/vscode/issues/28829541if (!currentHtmlNode.open || !currentHtmlNode.close ||542!(currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start)) {543return false;544}545546// Fix for https://github.com/microsoft/vscode/issues/35128547// Find the position up till where we will backtrack looking for unescaped < or >548// to decide if current position is valid for emmet expansion549start = currentHtmlNode.open.end;550let lastChildBeforePosition = currentHtmlNode.firstChild;551while (lastChildBeforePosition) {552if (lastChildBeforePosition.end > offset) {553break;554}555start = lastChildBeforePosition.end;556lastChildBeforePosition = lastChildBeforePosition.nextSibling;557}558}559const startPos = document.positionAt(start);560let textToBackTrack = document.getText(new vscode.Range(startPos.line, startPos.character, abbreviationRange.start.line, abbreviationRange.start.character));561562// Worse case scenario is when cursor is inside a big chunk of text which needs to backtracked563// Backtrack only 500 offsets to ensure we dont waste time doing this564if (textToBackTrack.length > 500) {565textToBackTrack = textToBackTrack.substr(textToBackTrack.length - 500);566}567568if (!textToBackTrack.trim()) {569return true;570}571572let valid = true;573let foundSpace = false; // If < is found before finding whitespace, then its valid abbreviation. E.g.: <div|574let i = textToBackTrack.length - 1;575if (textToBackTrack[i] === startAngle) {576return false;577}578579while (i >= 0) {580const char = textToBackTrack[i];581i--;582if (!foundSpace && /\s/.test(char)) {583foundSpace = true;584continue;585}586if (char === question && textToBackTrack[i] === startAngle) {587i--;588continue;589}590// Fix for https://github.com/microsoft/vscode/issues/55411591// A space is not a valid character right after < in a tag name.592if (/\s/.test(char) && textToBackTrack[i] === startAngle) {593i--;594continue;595}596if (char !== startAngle && char !== endAngle) {597continue;598}599if (i >= 0 && textToBackTrack[i] === escape) {600i--;601continue;602}603if (char === endAngle) {604if (i >= 0 && textToBackTrack[i] === '=') {605continue; // False alarm of cases like =>606} else {607break;608}609}610if (char === startAngle) {611valid = !foundSpace;612break;613}614}615616return valid;617}618619/**620* Expands abbreviations as detailed in expandAbbrList in the editor621*622* @returns false if no snippet can be inserted.623*/624async function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: ExpandAbbreviationInput[], insertSameSnippet: boolean): Promise<boolean> {625if (!expandAbbrList || expandAbbrList.length === 0) {626return false;627}628629// Snippet to replace at multiple cursors are not the same630// `editor.insertSnippet` will have to be called for each instance separately631// We will not be able to maintain multiple cursors after snippet insertion632let insertedSnippetsCount = 0;633if (!insertSameSnippet) {634expandAbbrList.sort((a: ExpandAbbreviationInput, b: ExpandAbbreviationInput) => { return b.rangeToReplace.start.compareTo(a.rangeToReplace.start); });635for (const expandAbbrInput of expandAbbrList) {636const expandedText = expandAbbr(expandAbbrInput);637if (expandedText) {638await editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false });639insertedSnippetsCount++;640}641}642return insertedSnippetsCount > 0;643}644645// Snippet to replace at all cursors are the same646// We can pass all ranges to `editor.insertSnippet` in a single call so that647// all cursors are maintained after snippet insertion648const anyExpandAbbrInput = expandAbbrList[0];649const expandedText = expandAbbr(anyExpandAbbrInput);650const allRanges = expandAbbrList.map(value => value.rangeToReplace);651if (expandedText) {652return editor.insertSnippet(new vscode.SnippetString(expandedText), allRanges);653}654return false;655}656657/**658* Expands abbreviation as detailed in given input.659*/660function expandAbbr(input: ExpandAbbreviationInput): string | undefined {661const helper = getEmmetHelper();662const expandOptions = helper.getExpandOptions(input.syntax, getEmmetConfiguration(input.syntax), input.filter);663664if (input.textToWrap) {665// escape ${ sections, fixes #122231666input.textToWrap = input.textToWrap.map(e => e.replace(/\$\{/g, '\\\$\{'));667if (input.filter && input.filter.includes('t')) {668input.textToWrap = input.textToWrap.map(line => {669return line.replace(trimRegex, '').trim();670});671}672expandOptions['text'] = input.textToWrap;673674if (expandOptions.options) {675// Below fixes https://github.com/microsoft/vscode/issues/29898676// With this, Emmet formats inline elements as block elements677// ensuring the wrapped multi line text does not get merged to a single line678if (!input.rangeToReplace.isSingleLine) {679expandOptions.options['output.inlineBreak'] = 1;680}681682if (input.indent) {683expandOptions.options['output.indent'] = input.indent;684}685if (input.baseIndent) {686expandOptions.options['output.baseIndent'] = input.baseIndent;687}688}689}690691let expandedText: string | undefined;692try {693expandedText = helper.expandAbbreviation(input.abbreviation, expandOptions);694} catch (e) {695void vscode.window.showErrorMessage('Failed to expand abbreviation');696}697698return expandedText;699}700701export function getSyntaxFromArgs(args: { [x: string]: string }): string | undefined {702const mappedModes = getMappingForIncludedLanguages();703const language: string = args['language'];704const parentMode: string = args['parentMode'];705const excludedLanguages = vscode.workspace.getConfiguration('emmet')['excludeLanguages'] ? vscode.workspace.getConfiguration('emmet')['excludeLanguages'] : [];706if (excludedLanguages.includes(language)) {707return;708}709710let syntax = getEmmetMode(mappedModes[language] ?? language, mappedModes, excludedLanguages);711if (!syntax) {712syntax = getEmmetMode(mappedModes[parentMode] ?? parentMode, mappedModes, excludedLanguages);713}714715return syntax;716}717718719