Skip to content

Commit

Permalink
Upgrade to latest Emmet (#113848)
Browse files Browse the repository at this point in the history
  • Loading branch information
rzhao271 authored Jan 5, 2021
1 parent 6f56b47 commit dc5a3da
Show file tree
Hide file tree
Showing 4 changed files with 205 additions and 145 deletions.
6 changes: 4 additions & 2 deletions extensions/emmet/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -430,14 +430,16 @@
"deps": "yarn add vscode-emmet-helper"
},
"devDependencies": {
"@types/node": "^12.19.9"
"@types/node": "^12.19.9",
"emmet": "https://github.com/rzhao271/emmet.git#1b2df677d8925ef5ea6da9df8845968403979a0a"
},
"dependencies": {
"@emmetio/abbreviation": "^2.2.0",
"@emmetio/css-parser": "ramya-rao-a/css-parser#vscode",
"@emmetio/html-matcher": "^0.3.3",
"@emmetio/math-expression": "^1.0.4",
"image-size": "^0.5.2",
"vscode-emmet-helper": "~2.0.0",
"vscode-emmet-helper": "2.2.4-2",
"vscode-languageserver-textdocument": "^1.0.1"
}
}
180 changes: 95 additions & 85 deletions extensions/emmet/src/abbreviationActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,16 @@ import * as vscode from 'vscode';
import { Node, HtmlNode, Rule, Property, Stylesheet } from 'EmmetFlatNode';
import { getEmmetHelper, getFlatNode, getMappingForIncludedLanguages, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, allowedMimeTypesInScriptTag, toLSTextDocument } from './util';
import { getRootNode as parseDocument } from './parseDocument';
import { MarkupAbbreviation } from 'emmet';
// import { AbbreviationNode } from '@emmetio/abbreviation';

const trimRegex = /[\u00a0]*[\d#\-\*\u2022]+\.?/;
const hexColorRegex = /^#[\da-fA-F]{0,6}$/;
const inlineElements = ['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
'textarea', 'tt', 'u', 'var'];
// const inlineElements = ['a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
// 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
// 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
// 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
// 'textarea', 'tt', 'u', 'var'];

interface ExpandAbbreviationInput {
syntax: string;
Expand All @@ -32,29 +34,29 @@ interface PreviewRangesWithContent {
}

export function wrapWithAbbreviation(args: any) {
return doWrapping(false, args);
return doWrapping(true, args);
}

export function wrapIndividualLinesWithAbbreviation(args: any) {
return doWrapping(true, args);
}

function doWrapping(individualLines: boolean, args: any) {
function doWrapping(_: boolean, args: any) {
if (!validate(false) || !vscode.window.activeTextEditor) {
return;
}

const editor = vscode.window.activeTextEditor;
if (individualLines) {
if (editor.selections.length === 1 && editor.selection.isEmpty) {
vscode.window.showInformationMessage('Select more than 1 line and try again.');
return;
}
if (editor.selections.find(x => x.isEmpty)) {
vscode.window.showInformationMessage('Select more than 1 line in each selection and try again.');
return;
}
}
// if (individualLines) {
// if (editor.selections.length === 1 && editor.selection.isEmpty) {
// vscode.window.showInformationMessage('Select more than 1 line and try again.');
// return;
// }
// if (editor.selections.find(x => x.isEmpty)) {
// vscode.window.showInformationMessage('Select more than 1 line in each selection and try again.');
// return;
// }
// }
args = args || {};
if (!args['language']) {
args['language'] = editor.document.languageId;
Expand Down Expand Up @@ -99,14 +101,15 @@ function doWrapping(individualLines: boolean, args: any) {

let textToWrapInPreview: string[];
const textToReplace = document.getText(rangeToReplace);
if (individualLines) {
textToWrapInPreview = textToReplace.split('\n').map(x => x.trim());
} else {
const wholeFirstLine = document.lineAt(rangeToReplace.start).text;
const otherMatches = wholeFirstLine.match(/^(\s*)/);
const precedingWhitespace = otherMatches ? otherMatches[1] : '';
textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : ['\n\t' + textToReplace.split('\n' + precedingWhitespace).join('\n\t') + '\n'];
}
// if (individualLines) {
// textToWrapInPreview = textToReplace.split('\n').map(x => x.trim());
// } else {
// the following assumes the lines are indented the same way
const wholeFirstLine = document.lineAt(rangeToReplace.start).text;
const otherMatches = wholeFirstLine.match(/^(\s*)/);
const precedingWhitespace = otherMatches ? otherMatches[1] : '';
textToWrapInPreview = rangeToReplace.isSingleLine ? [textToReplace] : textToReplace.split('\n' + precedingWhitespace).map(x => x.trimEnd());
// }
textToWrapInPreview = textToWrapInPreview.map(e => e.replace(/(\$\d)/g, '\\$1'));

return {
Expand All @@ -117,7 +120,7 @@ function doWrapping(individualLines: boolean, args: any) {
};
});

function revertPreview(): Thenable<any> {
function revertPreview(): Thenable<boolean> {
return editor.edit(builder => {
for (const rangeToReplace of rangesToReplace) {
builder.replace(rangeToReplace.previewRange, rangeToReplace.originalContent);
Expand All @@ -143,7 +146,8 @@ function doWrapping(individualLines: boolean, args: any) {
const preceedingText = editor.document.getText(new vscode.Range(oldPreviewRange.start.line, 0, oldPreviewRange.start.line, oldPreviewRange.start.character));
const indentPrefix = (preceedingText.match(/^(\s*)/) || ['', ''])[1];

let newText = expandedText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text
let newText = expandedText;
newText = newText.replace(/\n/g, '\n' + indentPrefix); // Adding indentation on each line of expanded text
newText = newText.replace(/\$\{[\d]*\}/g, '|'); // Removing Tabstops
newText = newText.replace(/\$\{[\d]*(:[^}]*)?\}/g, (match) => { // Replacing Placeholders
return match.replace(/^\$\{[\d]*:/, '').replace('}', '');
Expand Down Expand Up @@ -197,19 +201,21 @@ function doWrapping(individualLines: boolean, args: any) {

const { abbreviation, filter } = extractedResults;
if (definitive) {
const revertPromise = inPreview ? revertPreview() : Promise.resolve();
const revertPromise = inPreview ? revertPreview() : Promise.resolve(true);
return revertPromise.then(() => {
const expandAbbrList: ExpandAbbreviationInput[] = rangesToReplace.map(rangesAndContent => {
const rangeToReplace = rangesAndContent.originalRange;
let textToWrap: string[];
if (individualLines) {
textToWrap = rangesAndContent.textToWrapInPreview;
} else {
textToWrap = rangeToReplace.isSingleLine ? ['$TM_SELECTED_TEXT'] : ['\n\t$TM_SELECTED_TEXT\n'];
}
// if (individualLines) {
textToWrap = rangesAndContent.textToWrapInPreview;
// } else {
// // use the p tag as a dummy element to get Emmet to wrap the expression properly
// textToWrap = rangeToReplace.isSingleLine ?
// ['$TM_SELECTED_TEXT'] : ['<p>$TM_SELECTED_TEXT</p>'];
// }
return { syntax: syntax || '', abbreviation, rangeToReplace, textToWrap, filter };
});
return expandAbbreviationInRange(editor, expandAbbrList, !individualLines).then(() => { return true; });
return expandAbbreviationInRange(editor, expandAbbrList, false).then(() => { return true; });
});
}

Expand All @@ -224,14 +230,15 @@ function doWrapping(individualLines: boolean, args: any) {
if (value !== currentValue) {
currentValue = value;
makeChanges(value, false).then((out) => {
if (typeof out === 'boolean') {
inPreview = out;
}
inPreview = out;
});
}
return '';
}
const abbreviationPromise: Thenable<string | undefined> = (args && args['abbreviation']) ? Promise.resolve(args['abbreviation']) : vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });

const abbreviationPromise: Thenable<string | undefined> = (args && args['abbreviation']) ?
Promise.resolve(args['abbreviation']) :
vscode.window.showInputBox({ prompt: 'Enter Abbreviation', validateInput: inputChanged });
return abbreviationPromise.then(inputAbbreviation => {
return makeChanges(inputAbbreviation, true);
});
Expand Down Expand Up @@ -597,7 +604,7 @@ function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: Ex
const insertPromises: Thenable<boolean>[] = [];
if (!insertSameSnippet) {
expandAbbrList.sort((a: ExpandAbbreviationInput, b: ExpandAbbreviationInput) => { return b.rangeToReplace.start.compareTo(a.rangeToReplace.start); }).forEach((expandAbbrInput: ExpandAbbreviationInput) => {
let expandedText = expandAbbr(expandAbbrInput);
const expandedText = expandAbbr(expandAbbrInput);
if (expandedText) {
insertPromises.push(editor.insertSnippet(new vscode.SnippetString(expandedText), expandAbbrInput.rangeToReplace, { undoStopBefore: false, undoStopAfter: false }));
}
Expand All @@ -622,24 +629,26 @@ function expandAbbreviationInRange(editor: vscode.TextEditor, expandAbbrList: Ex
return Promise.resolve(false);
}

/*
* Walks the tree rooted at root and apply function fn on each node.
* if fn return false at any node, the further processing of tree is stopped.
*/
function walk(root: any, fn: ((node: any) => boolean)): boolean {
let ctx = root;
while (ctx) {

const next = ctx.next;
if (fn(ctx) === false || walk(ctx.firstChild, fn) === false) {
return false;
}

ctx = next;
}

return true;
}
// /*
// * Walks the tree rooted at root and apply function fn on each node.
// * if fn return false at any node, the further processing of tree is stopped.
// */
// function walk(root: AbbreviationNode, fn: ((node: AbbreviationNode) => boolean)): boolean {
// if (fn(root) === false || walkChildren(root.children, fn) === false) {
// return false;
// }
// return true;
// }

// function walkChildren(children: AbbreviationNode[], fn: ((node: AbbreviationNode) => boolean)): boolean {
// for (let i = 0; i < children.length; i++) {
// const child = children[i];
// if (walk(child, fn) === false) {
// return false;
// }
// }
// return true;
// }

/**
* Expands abbreviation as detailed in given input.
Expand All @@ -649,7 +658,7 @@ function expandAbbr(input: ExpandAbbreviationInput): string | undefined {
const expandOptions = helper.getExpandOptions(input.syntax, getEmmetConfiguration(input.syntax), input.filter);

if (input.textToWrap) {
if (input.filter && input.filter.indexOf('t') > -1) {
if (input.filter && input.filter.includes('t')) {
input.textToWrap = input.textToWrap.map(line => {
return line.replace(trimRegex, '').trim();
});
Expand All @@ -659,46 +668,47 @@ function expandAbbr(input: ExpandAbbreviationInput): string | undefined {
// Below fixes https://github.com/microsoft/vscode/issues/29898
// With this, Emmet formats inline elements as block elements
// ensuring the wrapped multi line text does not get merged to a single line
if (!input.rangeToReplace.isSingleLine) {
expandOptions.profile['inlineBreak'] = 1;
if (!input.rangeToReplace.isSingleLine && expandOptions.options) {
expandOptions.options['output.inlineBreak'] = 1;
}
}

let expandedText;
try {
// Expand the abbreviation

if (input.textToWrap) {
const parsedAbbr = helper.parseAbbreviation(input.abbreviation, expandOptions);
if (input.rangeToReplace.isSingleLine && input.textToWrap.length === 1) {

// Fetch rightmost element in the parsed abbreviation (i.e the element that will contain the wrapped text).
let wrappingNode = parsedAbbr;
while (wrappingNode && wrappingNode.children && wrappingNode.children.length > 0) {
wrappingNode = wrappingNode.children[wrappingNode.children.length - 1];
}

// If wrapping with a block element, insert newline in the text to wrap.
if (wrappingNode && inlineElements.indexOf(wrappingNode.name) === -1 && (expandOptions['profile'].hasOwnProperty('format') ? expandOptions['profile'].format : true)) {
wrappingNode.value = '\n\t' + wrappingNode.value + '\n';
}
}
if (input.textToWrap && !isStyleSheet(input.syntax)) {
const parsedAbbr = <MarkupAbbreviation>helper.parseAbbreviation(input.abbreviation, expandOptions);
// if (input.rangeToReplace.isSingleLine && input.textToWrap.length === 1) {
// // Fetch rightmost element in the parsed abbreviation (i.e the element that will contain the wrapped text).
// const wrappingNodeChildren = parsedAbbr.children;
// let wrappingNode = wrappingNodeChildren[wrappingNodeChildren.length - 1];
// while (wrappingNode && wrappingNode.children && wrappingNode.children.length > 0) {
// wrappingNode = wrappingNode.children[wrappingNode.children.length - 1];
// }

// // If wrapping with a block element, insert newline in the text to wrap.
// // const format = expandOptions.options ? (expandOptions.options['output.format'] ?? true) : true;
// // if (wrappingNode && wrappingNode.name && wrappingNode.value
// // && inlineElements.indexOf(wrappingNode.name) === -1
// // && format) {
// // wrappingNode.value[0] = '\n\t' + wrappingNode.value[0] + '\n';
// // }
// }

// Below fixes https://github.com/microsoft/vscode/issues/78219
// walk the tree and remove tags for empty values
walk(parsedAbbr, node => {
if (node.name !== null && node.value === '' && !node.isSelfClosing && node.children.length === 0) {
node.name = '';
node.value = '\n';
}

return true;
});
// walkChildren(parsedAbbr.children, node => {
// if (node.name !== null && node.value && node.value[0] === '' && !node.selfClosing && node.children.length === 0) {
// node.name = '';
// node.value[0] = '\n';
// }
// return true;
// });

expandedText = helper.expandAbbreviation(parsedAbbr, expandOptions);
// All $anyword would have been escaped by the emmet helper.
// Remove the escaping backslash from $TM_SELECTED_TEXT so that VS Code Snippet controller can treat it as a variable
expandedText = expandedText.replace('\\$TM_SELECTED_TEXT', '$TM_SELECTED_TEXT');
expandedText = expandedText.replace('<p>\\$TM_SELECTED_TEXT</p>', '$TM_SELECTED_TEXT');
} else {
expandedText = helper.expandAbbreviation(input.abbreviation, expandOptions);
}
Expand Down
Loading

0 comments on commit dc5a3da

Please sign in to comment.