Update several Emmet commands (#112597)

For match tag, update tag, and balance in/out, we now have the following:

- vscode-html-languageservice parse is being used to speed up file parse times
- Balance balance-in after balance-out behaviour is now more consistent
- These commands now work in markup files with unclosed tags
- Implemented a cache for these commands to prevent multiple document parses on the same version of the document
This commit is contained in:
Raymond Zhao 2020-12-16 10:20:57 -08:00 committed by GitHub
parent 2156931d38
commit 483bd40550
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 273 additions and 108 deletions

View file

@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate } from './util';
import { getHtmlNodeLS, offsetRangeToSelection, toLSTextDocument, validate } from './util';
import { parseMarkupDocument } from './parseMarkupDocument';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
let balanceOutStack: Array<vscode.Selection[]> = [];
let lastOut = false;
let lastBalancedSelections: vscode.Selection[] = [];
export function balanceOut() {
@ -24,53 +24,50 @@ function balance(out: boolean) {
return;
}
const editor = vscode.window.activeTextEditor;
let rootNode = <HtmlNode>parseDocument(editor.document);
if (!rootNode) {
const document = toLSTextDocument(editor.document);
const htmlDocument = parseMarkupDocument(document);
if (!htmlDocument) {
return;
}
let getRangeFunction = out ? getRangeToBalanceOut : getRangeToBalanceIn;
const rangeFn = out ? getRangeToBalanceOut : getRangeToBalanceIn;
let newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let range = getRangeFunction(editor.document, selection, rootNode);
const range = rangeFn(document, selection);
newSelections.push(range);
});
if (areSameSelections(newSelections, editor.selections)) {
return;
}
// check whether we are starting a balance elsewhere
if (areSameSelections(lastBalancedSelections, editor.selections)) {
// we are not starting elsewhere, so use the stack as-is
if (out) {
if (!balanceOutStack.length) {
// make sure we are able to expand outwards
if (!areSameSelections(editor.selections, newSelections)) {
balanceOutStack.push(editor.selections);
}
balanceOutStack.push(newSelections);
} else {
if (lastOut) {
balanceOutStack.pop();
}
newSelections = balanceOutStack.pop() || newSelections;
} else if (balanceOutStack.length) {
newSelections = balanceOutStack.pop()!;
}
} else {
balanceOutStack = out ? [editor.selections, newSelections] : [];
// we are starting elsewhere, so reset the stack
balanceOutStack = out ? [editor.selections] : [];
}
lastOut = out;
lastBalancedSelections = editor.selections = newSelections;
editor.selections = newSelections;
lastBalancedSelections = editor.selections;
}
function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlNode): vscode.Selection {
let nodeToBalance = getHtmlNode(document, rootNode, selection.start, false);
function getRangeToBalanceOut(document: LSTextDocument, selection: vscode.Selection): vscode.Selection {
const nodeToBalance = getHtmlNodeLS(document, selection.start, false);
if (!nodeToBalance) {
return selection;
}
if (!nodeToBalance.close) {
return new vscode.Selection(nodeToBalance.start, nodeToBalance.end);
if (!nodeToBalance.endTagStart || !nodeToBalance.startTagEnd) {
return offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end);
}
let innerSelection = new vscode.Selection(nodeToBalance.open.end, nodeToBalance.close.start);
let outerSelection = new vscode.Selection(nodeToBalance.start, nodeToBalance.end);
const innerSelection = offsetRangeToSelection(document, nodeToBalance.startTagEnd, nodeToBalance.endTagStart);
const outerSelection = offsetRangeToSelection(document, nodeToBalance.start, nodeToBalance.end);
if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) {
return innerSelection;
@ -81,34 +78,37 @@ function getRangeToBalanceOut(document: vscode.TextDocument, selection: vscode.S
return selection;
}
function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlNode): vscode.Selection {
let nodeToBalance = getHtmlNode(document, rootNode, selection.start, true);
function getRangeToBalanceIn(document: LSTextDocument, selection: vscode.Selection): vscode.Selection {
const nodeToBalance = getHtmlNodeLS(document, selection.start, true);
if (!nodeToBalance) {
return selection;
}
if (nodeToBalance.close) {
const entireNodeSelected = selection.start.isEqual(nodeToBalance.start) && selection.end.isEqual(nodeToBalance.end);
const startInOpenTag = selection.start.isAfter(nodeToBalance.open.start) && selection.start.isBefore(nodeToBalance.open.end);
const startInCloseTag = selection.start.isAfter(nodeToBalance.close.start) && selection.start.isBefore(nodeToBalance.close.end);
const selectionStart = document.offsetAt(selection.start);
const selectionEnd = document.offsetAt(selection.end);
if (nodeToBalance.endTagStart !== undefined && nodeToBalance.startTagEnd !== undefined) {
const entireNodeSelected = selectionStart === nodeToBalance.start && selectionEnd === nodeToBalance.end;
const startInOpenTag = selectionStart > nodeToBalance.start && selectionStart < nodeToBalance.startTagEnd;
const startInCloseTag = selectionStart > nodeToBalance.endTagStart && selectionStart < nodeToBalance.end;
if (entireNodeSelected || startInOpenTag || startInCloseTag) {
return new vscode.Selection(nodeToBalance.open.end, nodeToBalance.close.start);
return offsetRangeToSelection(document, nodeToBalance.startTagEnd, nodeToBalance.endTagStart);
}
}
if (!nodeToBalance.firstChild) {
if (!nodeToBalance.children.length) {
return selection;
}
if (selection.start.isEqual(nodeToBalance.firstChild.start)
&& selection.end.isEqual(nodeToBalance.firstChild.end)
&& nodeToBalance.firstChild.close) {
return new vscode.Selection(nodeToBalance.firstChild.open.end, nodeToBalance.firstChild.close.start);
const firstChild = nodeToBalance.children[0];
if (selectionStart === firstChild.start
&& selectionEnd === firstChild.end
&& firstChild.endTagStart !== undefined
&& firstChild.startTagEnd !== undefined) {
return offsetRangeToSelection(document, firstChild.startTagEnd, firstChild.endTagStart);
}
return new vscode.Selection(nodeToBalance.firstChild.start, nodeToBalance.firstChild.end);
return offsetRangeToSelection(document, firstChild.start, firstChild.end);
}
function areSameSelections(a: vscode.Selection[], b: vscode.Selection[]): boolean {
@ -121,4 +121,4 @@ function areSameSelections(a: vscode.Selection[], b: vscode.Selection[]): boolea
}
}
return true;
}
}

View file

@ -17,8 +17,9 @@ import { fetchEditPoint } from './editPoint';
import { fetchSelectItem } from './selectItem';
import { evaluateMathExpression } from './evaluateMathExpression';
import { incrementDecrement } from './incrementDecrement';
import { LANGUAGE_MODES, getMappingForIncludedLanguages, updateEmmetExtensionsPath, getPathBaseName } from './util';
import { LANGUAGE_MODES, getMappingForIncludedLanguages, updateEmmetExtensionsPath, getPathBaseName, toLSTextDocument, getSyntaxes, getEmmetMode } from './util';
import { reflectCssValue } from './reflectCssValue';
import { addFileToMarkupParseCache, removeFileFromMarkupParseCache } from './parseMarkupDocument';
export function activateEmmetExtension(context: vscode.ExtensionContext) {
registerCompletionProviders(context);
@ -145,6 +146,20 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) {
updateEmmetExtensionsPath(true);
}
}));
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
if (getSyntaxes().markup.includes(emmetMode)) {
addFileToMarkupParseCache(toLSTextDocument(e));
}
}));
context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
if (getSyntaxes().markup.includes(emmetMode)) {
removeFileFromMarkupParseCache(toLSTextDocument(e));
}
}));
}
/**

View file

@ -4,9 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate } from './util';
import { toLSTextDocument, validate, getHtmlNodeLS, offsetRangeToSelection } from './util';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
export function matchTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -14,32 +13,37 @@ export function matchTag() {
}
const editor = vscode.window.activeTextEditor;
let rootNode: HtmlNode = <HtmlNode>parseDocument(editor.document);
if (!rootNode) { return; }
const document = toLSTextDocument(editor.document);
let updatedSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let updatedSelection = getUpdatedSelections(editor, selection.start, rootNode);
const updatedSelection = getUpdatedSelections(document, selection.start);
if (updatedSelection) {
updatedSelections.push(updatedSelection);
}
});
if (updatedSelections.length > 0) {
if (updatedSelections.length) {
editor.selections = updatedSelections;
editor.revealRange(editor.selections[updatedSelections.length - 1]);
}
}
function getUpdatedSelections(editor: vscode.TextEditor, position: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = getHtmlNode(editor.document, rootNode, position, true);
if (!currentNode) { return; }
function getUpdatedSelections(document: LSTextDocument, position: vscode.Position): vscode.Selection | undefined {
const currentNode = getHtmlNodeLS(document, position, true);
if (!currentNode) {
return;
}
const offset = document.offsetAt(position);
// If no closing tag or cursor is between open and close tag, then no-op
if (!currentNode.close || (position.isAfter(currentNode.open.end) && position.isBefore(currentNode.close.start))) {
if (currentNode.endTagStart === undefined
|| currentNode.startTagEnd === undefined
|| (offset > currentNode.startTagEnd && offset < currentNode.endTagStart)) {
return;
}
// Place cursor inside the close tag if cursor is inside the open tag, else place it inside the open tag
let finalPosition = position.isBeforeOrEqual(currentNode.open.end) ? currentNode.close.start.translate(0, 2) : currentNode.open.start.translate(0, 1);
return new vscode.Selection(finalPosition, finalPosition);
}
const finalOffset = (offset <= currentNode.startTagEnd) ? currentNode.endTagStart + 2 : currentNode.start + 1;
return offsetRangeToSelection(document, finalOffset, finalOffset);
}

View file

@ -0,0 +1,43 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { HTMLDocument, TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { getLanguageService } from './util';
type Pair<K, V> = {
key: K;
value: V;
};
// Map(filename, Pair(fileVersion, parsedContent))
const _parseCache = new Map<string, Pair<number, HTMLDocument> | undefined>();
export function parseMarkupDocument(document: LSTextDocument, useCache: boolean = true): HTMLDocument {
const languageService = getLanguageService();
const key = document.uri;
const result = _parseCache.get(key);
const documentVersion = document.version;
if (useCache && result) {
if (documentVersion === result.key) {
return result.value;
}
}
const parsedDocument = languageService.parseHTMLDocument(document);
if (useCache) {
_parseCache.set(key, { key: documentVersion, value: parsedDocument });
}
return parsedDocument;
}
export function addFileToMarkupParseCache(document: LSTextDocument) {
const filename = document.uri;
_parseCache.set(filename, undefined);
}
export function removeFileFromMarkupParseCache(document: LSTextDocument) {
const filename = document.uri;
_parseCache.delete(filename);
}

View file

@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate, getEmmetMode, getEmmetConfiguration } from './util';
import { validate, getEmmetMode, getEmmetConfiguration, toLSTextDocument, getHtmlNodeLS, offsetRangeToVsRange } from './util';
import { Node as LSNode, TextDocument as LSTextDocument } from 'vscode-html-languageservice';
export function splitJoinTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -13,40 +13,42 @@ export function splitJoinTag() {
}
const editor = vscode.window.activeTextEditor;
let rootNode = <HtmlNode>parseDocument(editor.document);
if (!rootNode) {
return;
}
const document = toLSTextDocument(editor.document);
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let nodeToUpdate = getHtmlNode(editor.document, rootNode, selection.start, true);
const nodeToUpdate = getHtmlNodeLS(document, selection.start, true);
if (nodeToUpdate) {
let textEdit = getRangesToReplace(editor.document, nodeToUpdate);
editBuilder.replace(textEdit.range, textEdit.newText);
const textEdit = getRangesToReplace(document, nodeToUpdate);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);
}
}
});
});
}
function getRangesToReplace(document: vscode.TextDocument, nodeToUpdate: HtmlNode): vscode.TextEdit {
function getRangesToReplace(document: LSTextDocument, nodeToUpdate: LSNode): vscode.TextEdit | undefined {
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate.close) {
// Split Tag
let nodeText = document.getText(new vscode.Range(nodeToUpdate.start, nodeToUpdate.end));
let m = nodeText.match(/(\s*\/)?>$/);
let end = <vscode.Position>nodeToUpdate.end;
let start = m ? end.translate(0, -m[0].length) : end;
if (!nodeToUpdate?.tag) {
return;
}
rangeToReplace = new vscode.Range(start, end);
textToReplaceWith = `></${nodeToUpdate.name}>`;
if (nodeToUpdate.endTagStart === undefined || nodeToUpdate.startTagEnd === undefined) {
// Split Tag
const nodeText = document.getText().substring(nodeToUpdate.start, nodeToUpdate.end);
const m = nodeText.match(/(\s*\/)?>$/);
const end = nodeToUpdate.end;
const start = m ? end - m[0].length : end;
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = `></${nodeToUpdate.tag}>`;
} else {
// Join Tag
let start = (<vscode.Position>nodeToUpdate.open.end).translate(0, -1);
let end = <vscode.Position>nodeToUpdate.end;
rangeToReplace = new vscode.Range(start, end);
const start = nodeToUpdate.startTagEnd - 1;
const end = nodeToUpdate.end;
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = '/>';
const emmetMode = getEmmetMode(document.languageId, []) || '';
@ -55,8 +57,7 @@ function getRangesToReplace(document: vscode.TextDocument, nodeToUpdate: HtmlNod
(emmetConfig.syntaxProfiles[emmetMode]['selfClosingStyle'] === 'xhtml' || emmetConfig.syntaxProfiles[emmetMode]['self_closing_tag'] === 'xhtml')) {
textToReplaceWith = ' ' + textToReplaceWith;
}
}
return new vscode.TextEdit(rangeToReplace, textToReplaceWith);
}
}

View file

@ -4,23 +4,18 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate } from './util';
import { getHtmlNodeLS, toLSTextDocument, validate } from './util';
import { TextDocument as LSTextDocument, Node as LSNode } from 'vscode-html-languageservice';
export function updateTag(tagName: string): Thenable<boolean> | undefined {
if (!validate(false) || !vscode.window.activeTextEditor) {
return;
}
let editor = vscode.window.activeTextEditor;
let rootNode = <HtmlNode>parseDocument(editor.document);
if (!rootNode) {
return;
}
let rangesToUpdate: vscode.Range[] = [];
editor.selections.reverse().forEach(selection => {
rangesToUpdate = rangesToUpdate.concat(getRangesToUpdate(editor, selection, rootNode));
});
const editor = vscode.window.activeTextEditor;
const rangesToUpdate = editor.selections.reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToUpdate(editor, selection)), []);
return editor.edit(editBuilder => {
rangesToUpdate.forEach(range => {
@ -29,22 +24,36 @@ export function updateTag(tagName: string): Thenable<boolean> | undefined {
});
}
function getRangesToUpdate(editor: vscode.TextEditor, selection: vscode.Selection, rootNode: HtmlNode): vscode.Range[] {
let nodeToUpdate = getHtmlNode(editor.document, rootNode, selection.start, true);
if (!nodeToUpdate) {
return [];
function getPositionFromOffset(offset: number | undefined, document: LSTextDocument): vscode.Position | undefined {
if (offset === undefined) {
return undefined;
}
const pos = document.positionAt(offset);
return new vscode.Position(pos.line, pos.character);
}
let openStart = nodeToUpdate.open.start.translate(0, 1);
let openEnd = openStart.translate(0, nodeToUpdate.name.length);
function getRangesFromNode(node: LSNode, document: LSTextDocument): vscode.Range[] {
const start = getPositionFromOffset(node.start, document)!;
const startTagEnd = getPositionFromOffset(node.startTagEnd, document);
const end = getPositionFromOffset(node.end, document)!;
const endTagStart = getPositionFromOffset(node.endTagStart, document);
let ranges = [new vscode.Range(openStart, openEnd)];
if (nodeToUpdate.close) {
let closeStart = nodeToUpdate.close.start.translate(0, 2);
let closeEnd = nodeToUpdate.close.end.translate(0, -1);
ranges.push(new vscode.Range(closeStart, closeEnd));
let ranges: vscode.Range[] = [];
if (startTagEnd) {
ranges.push(new vscode.Range(start.translate(0, 1),
start.translate(0, 1).translate(0, node.tag!.length ?? 0)));
}
if (endTagStart) {
ranges.push(new vscode.Range(endTagStart.translate(0, 2), end.translate(0, -1)));
}
return ranges;
}
function getRangesToUpdate(editor: vscode.TextEditor, selection: vscode.Selection): vscode.Range[] {
const document = toLSTextDocument(editor.document);
const nodeToUpdate = getHtmlNodeLS(document, selection.start, true);
if (!nodeToUpdate) {
return [];
}
return getRangesFromNode(nodeToUpdate, document);
}

View file

@ -9,9 +9,11 @@ import parseStylesheet from '@emmetio/css-parser';
import { Node, HtmlNode, CssToken, Property, Rule, Stylesheet } from 'EmmetNode';
import { DocumentStreamReader } from './bufferStream';
import * as EmmetHelper from 'vscode-emmet-helper';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { Position as LSPosition, getLanguageService as getLanguageServiceInternal, LanguageService, LanguageServiceOptions, TextDocument as LSTextDocument, Node as LSNode } from 'vscode-html-languageservice';
import { parseMarkupDocument } from './parseMarkupDocument';
let _emmetHelper: typeof EmmetHelper;
let _languageService: LanguageService;
let _currentExtensionsPath: string | undefined = undefined;
let _homeDir: vscode.Uri | undefined;
@ -21,7 +23,6 @@ export function setHomeDir(homeDir: vscode.Uri) {
_homeDir = homeDir;
}
export function getEmmetHelper() {
// Lazy load vscode-emmet-helper instead of importing it
// directly to reduce the start-up time of the extension
@ -32,6 +33,16 @@ export function getEmmetHelper() {
return _emmetHelper;
}
export function getLanguageService(options?: LanguageServiceOptions): LanguageService {
if (!options) {
if (!_languageService) {
_languageService = getLanguageServiceInternal();
}
return _languageService;
}
return getLanguageServiceInternal(options);
}
/**
* Update Emmet Helper to use user snippets from the extensionsPath setting
*/
@ -130,8 +141,8 @@ export function getEmmetMode(language: string, excludedLanguages: string[]): str
if (language === 'jade') {
return 'pug';
}
const emmetModes = ['html', 'pug', 'slim', 'haml', 'xml', 'xsl', 'jsx', 'css', 'scss', 'sass', 'less', 'stylus'];
if (emmetModes.indexOf(language) > -1) {
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(language) || syntaxes.stylesheet.includes(language)) {
return language;
}
return;
@ -366,6 +377,78 @@ export function getHtmlNode(document: vscode.TextDocument, root: Node | undefine
return currentNode;
}
/**
* Finds the HTML node within an HTML document at a given position
*/
export function getHtmlNodeLS(document: LSTextDocument, position: vscode.Position, includeNodeBoundary: boolean): LSNode | undefined {
const documentText = document.getText();
const offset = document.offsetAt(position);
let selectionStartOffset = offset;
if (includeNodeBoundary && documentText.charAt(offset) === '<') {
selectionStartOffset++;
}
else if (includeNodeBoundary && documentText.charAt(offset) === '>') {
selectionStartOffset--;
}
return getHtmlNodeLSInternal(document, selectionStartOffset);
}
function getHtmlNodeLSInternal(document: LSTextDocument, offset: number, isInTemplateNode: boolean = false): LSNode | undefined {
const useCache = !isInTemplateNode;
const parsedDocument = parseMarkupDocument(document, useCache);
const currentNode: LSNode = parsedDocument.findNodeAt(offset);
if (!currentNode.tag) { return; }
const isTemplateScript = isNodeTemplateScriptLS(currentNode);
if (isTemplateScript
&& currentNode.startTagEnd
&& offset > currentNode.startTagEnd
&& (!currentNode.endTagStart || offset < currentNode.endTagStart)) {
// blank out the rest of the document and search for the node within
const documentText = document.getText();
const beforePadding = ' '.repeat(currentNode.startTagEnd);
const scriptBodyText = beforePadding + documentText.substring(currentNode.startTagEnd, currentNode.endTagStart ?? currentNode.end);
const scriptBodyDocument = LSTextDocument.create(document.uri, document.languageId, document.version, scriptBodyText);
const scriptBodyNode = getHtmlNodeLSInternal(scriptBodyDocument, offset, true);
if (scriptBodyNode) {
scriptBodyNode.parent = currentNode;
currentNode.children.push(scriptBodyNode);
return scriptBodyNode;
}
}
return currentNode;
}
/**
* Returns whether the node is a <script> node
* that we want to search through and parse for more potential HTML nodes
*/
function isNodeTemplateScriptLS(node: LSNode): boolean {
if (node.tag === 'script' && node.attributes && node.attributes['type']) {
let scriptType = node.attributes['type'];
scriptType = scriptType.substring(1, scriptType.length - 1);
return allowedMimeTypesInScriptTag.includes(scriptType);
}
return false;
}
function toVsPosition(position: LSPosition): vscode.Position {
return new vscode.Position(position.line, position.character);
}
export function offsetRangeToSelection(document: LSTextDocument, start: number, end: number): vscode.Selection {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Selection(toVsPosition(startPos), toVsPosition(endPos));
}
export function offsetRangeToVsRange(document: LSTextDocument, start: number, end: number): vscode.Range {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Range(toVsPosition(startPos), toVsPosition(endPos));
}
/**
* Returns inner range of an html node.
*/
@ -653,3 +736,13 @@ export function getPathBaseName(path: string): string {
const pathAfterBackslashSplit = pathAfterSlashSplit ? pathAfterSlashSplit.split('\\').pop() : '';
return pathAfterBackslashSplit ?? '';
}
export function getSyntaxes() {
/**
* List of all known syntaxes, from emmetio/emmet
*/
return {
markup: ['html', 'xml', 'xsl', 'jsx', 'js', 'pug', 'slim', 'haml'],
stylesheet: ['css', 'sass', 'scss', 'less', 'sss', 'stylus']
};
}