Archived
1
0

chore(vscode): update to 1.53.2

These conflicts will be resolved in the following commits. We do it this way so
that PR review is possible.
This commit is contained in:
Joe Previte
2021-02-25 11:27:27 -07:00
1900 changed files with 83066 additions and 64589 deletions

View File

@ -4,16 +4,21 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Node, HtmlNode, Rule, Property, Stylesheet } from 'EmmetNode';
import { getEmmetHelper, getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, allowedMimeTypesInScriptTag, toLSTextDocument } from './util';
import * as nls from 'vscode-nls';
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 localize = nls.loadMessageBundle();
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;
@ -31,35 +36,28 @@ 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;
}
}
const document = editor.document;
args = args || {};
if (!args['language']) {
args['language'] = editor.document.languageId;
args['language'] = document.languageId;
}
// we know it's not stylesheet due to the validate(false) call above
const syntax = getSyntaxFromArgs(args) || 'html';
const rootNode = parseDocument(editor.document, false);
const rootNode = parseDocument(document, true);
let inPreview = false;
let currentValue = '';
@ -69,34 +67,49 @@ function doWrapping(individualLines: boolean, args: any) {
const rangesToReplace: PreviewRangesWithContent[] = editor.selections.sort((a: vscode.Selection, b: vscode.Selection) => { return a.start.compareTo(b.start); }).map(selection => {
let rangeToReplace: vscode.Range = selection.isReversed ? new vscode.Range(selection.active, selection.anchor) : selection;
if (!rangeToReplace.isSingleLine && rangeToReplace.end.character === 0) {
// in case of multi-line, exclude last empty line from rangeToReplace
const previousLine = rangeToReplace.end.line - 1;
const lastChar = editor.document.lineAt(previousLine).text.length;
const lastChar = document.lineAt(previousLine).text.length;
rangeToReplace = new vscode.Range(rangeToReplace.start, new vscode.Position(previousLine, lastChar));
} else if (rangeToReplace.isEmpty) {
const { active } = selection;
const currentNode = getNode(rootNode, active, true);
if (currentNode && (currentNode.start.line === active.line || currentNode.end.line === active.line)) {
rangeToReplace = new vscode.Range(currentNode.start, currentNode.end);
const activeOffset = document.offsetAt(active);
const currentNode = getFlatNode(rootNode, activeOffset, true);
if (currentNode) {
const currentNodeStart = document.positionAt(currentNode.start);
const currentNodeEnd = document.positionAt(currentNode.end);
if (currentNodeStart.line === active.line || currentNodeEnd.line === active.line) {
// wrap around entire node
rangeToReplace = new vscode.Range(currentNodeStart, currentNodeEnd);
}
else {
// wrap line that cursor is on
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, document.lineAt(rangeToReplace.start.line).text.length);
}
} else {
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, editor.document.lineAt(rangeToReplace.start.line).text.length);
// wrap line that cursor is on
rangeToReplace = new vscode.Range(rangeToReplace.start.line, 0, rangeToReplace.start.line, document.lineAt(rangeToReplace.start.line).text.length);
}
}
const firstLineOfSelection = editor.document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
const firstLineOfSelection = document.lineAt(rangeToReplace.start).text.substr(rangeToReplace.start.character);
const matches = firstLineOfSelection.match(/^(\s*)/);
const extraWhitespaceSelected = matches ? matches[1].length : 0;
rangeToReplace = new vscode.Range(rangeToReplace.start.line, rangeToReplace.start.character + extraWhitespaceSelected, rangeToReplace.end.line, rangeToReplace.end.character);
let textToWrapInPreview: string[];
const textToReplace = editor.document.getText(rangeToReplace);
if (individualLines) {
textToWrapInPreview = textToReplace.split('\n').map(x => x.trim());
} else {
const wholeFirstLine = editor.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'];
}
const textToReplace = document.getText(rangeToReplace);
// the following assumes all the lines are indented the same way as the first
// this assumption helps with applyPreview later
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());
// escape $ characters, fixes #52640
textToWrapInPreview = textToWrapInPreview.map(e => e.replace(/(\$\d)/g, '\\$1'));
return {
@ -107,7 +120,29 @@ function doWrapping(individualLines: boolean, args: any) {
};
});
function revertPreview(): Thenable<any> {
// if a selection falls on a node, it could interfere with linked editing,
// so back up the selections, and change selections to wrap around the node
const oldSelections = editor.selections;
const newSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let { start, end } = selection;
const startOffset = document.offsetAt(start);
const startNode = <HtmlNode>getFlatNode(rootNode, startOffset, true);
const endOffset = document.offsetAt(end);
const endNode = <HtmlNode>getFlatNode(rootNode, endOffset, true);
if (startNode) {
start = document.positionAt(startNode.start);
}
if (endNode) {
end = document.positionAt(endNode.end);
}
// don't need to preserve active/anchor order since the selection changes
// after wrapping anyway
newSelections.push(new vscode.Selection(start, end));
});
editor.selections = newSelections;
function revertPreview(): Thenable<boolean> {
return editor.edit(builder => {
for (const rangeToReplace of rangesToReplace) {
builder.replace(rangeToReplace.previewRange, rangeToReplace.originalContent);
@ -119,9 +154,10 @@ function doWrapping(individualLines: boolean, args: any) {
function applyPreview(expandAbbrList: ExpandAbbreviationInput[]): Thenable<boolean> {
let lastOldPreviewRange = new vscode.Range(0, 0, 0, 0);
let lastNewPreviewRange = new vscode.Range(0, 0, 0, 0);
let totalLinesInserted = 0;
let totalNewLinesInserted = 0;
return editor.edit(builder => {
// the edits are applied in order top-down
for (let i = 0; i < rangesToReplace.length; i++) {
const expandedText = expandAbbr(expandAbbrList[i]) || '';
if (!expandedText) {
@ -129,11 +165,14 @@ function doWrapping(individualLines: boolean, args: any) {
break;
}
// get the current preview range, format the new wrapped text, and then replace
// the text in the preview range with that new text
const oldPreviewRange = rangesToReplace[i].previewRange;
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('}', '');
@ -141,13 +180,17 @@ function doWrapping(individualLines: boolean, args: any) {
newText = newText.replace(/\\\$/g, '$'); // Remove backslashes before $
builder.replace(oldPreviewRange, newText);
// calculate the new preview range to use for future previews
// we also have to take into account that the previous expansions could:
// - cause new lines to appear
// - be on the same line as other expansions
const expandedTextLines = newText.split('\n');
const oldPreviewLines = oldPreviewRange.end.line - oldPreviewRange.start.line + 1;
const newLinesInserted = expandedTextLines.length - oldPreviewLines;
const newPreviewLineStart = oldPreviewRange.start.line + totalLinesInserted;
const newPreviewLineStart = oldPreviewRange.start.line + totalNewLinesInserted;
let newPreviewStart = oldPreviewRange.start.character;
const newPreviewLineEnd = oldPreviewRange.end.line + totalLinesInserted + newLinesInserted;
const newPreviewLineEnd = oldPreviewRange.end.line + totalNewLinesInserted + newLinesInserted;
let newPreviewEnd = expandedTextLines[expandedTextLines.length - 1].length;
if (i > 0 && newPreviewLineEnd === lastNewPreviewRange.end.line) {
// If newPreviewLineEnd is equal to the previous expandedText lineEnd,
@ -166,9 +209,9 @@ function doWrapping(individualLines: boolean, args: any) {
}
lastOldPreviewRange = rangesToReplace[i].previewRange;
rangesToReplace[i].previewRange = lastNewPreviewRange = new vscode.Range(newPreviewLineStart, newPreviewStart, newPreviewLineEnd, newPreviewEnd);
totalLinesInserted += newLinesInserted;
lastNewPreviewRange = new vscode.Range(newPreviewLineStart, newPreviewStart, newPreviewLineEnd, newPreviewEnd);
rangesToReplace[i].previewRange = lastNewPreviewRange;
totalNewLinesInserted += newLinesInserted;
}
}, { undoStopBefore: false, undoStopAfter: false });
}
@ -187,19 +230,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; });
});
}
@ -214,16 +259,22 @@ 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 });
return abbreviationPromise.then(inputAbbreviation => {
return makeChanges(inputAbbreviation, true);
const prompt = localize('wrapWithAbbreviationPrompt', "Enter Abbreviation");
const abbreviationPromise: Thenable<string | undefined> = (args && args['abbreviation']) ?
Promise.resolve(args['abbreviation']) :
vscode.window.showInputBox({ prompt, validateInput: inputChanged });
return abbreviationPromise.then(async (inputAbbreviation) => {
const changesWereMade = await makeChanges(inputAbbreviation, true);
if (!changesWereMade) {
editor.selections = oldSelections;
}
return changesWereMade;
});
}
@ -327,7 +378,7 @@ export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined
if (editor.selections.length === 1 && isStyleSheet(editor.document.languageId) && usePartialParsing && editor.document.lineCount > 1000) {
rootNode = parsePartialStylesheet(editor.document, editor.selection.isReversed ? editor.selection.anchor : editor.selection.active);
} else {
rootNode = parseDocument(editor.document, false);
rootNode = parseDocument(editor.document, true);
}
return rootNode;
@ -342,24 +393,25 @@ export function expandEmmetAbbreviation(args: any): Thenable<boolean | undefined
if (!helper.isAbbreviationValid(syntax, abbreviation)) {
return;
}
let currentNode = getNode(getRootNode(), position, true);
const offset = editor.document.offsetAt(position);
let currentNode = getFlatNode(getRootNode(), offset, true);
let validateLocation = true;
let syntaxToUse = syntax;
if (editor.document.languageId === 'html') {
if (isStyleAttribute(currentNode, position)) {
if (isStyleAttribute(currentNode, offset)) {
syntaxToUse = 'css';
validateLocation = false;
} else {
const embeddedCssNode = getEmbeddedCssNodeIfAny(editor.document, currentNode, position);
if (embeddedCssNode) {
currentNode = getNode(embeddedCssNode, position, true);
currentNode = getFlatNode(embeddedCssNode, offset, true);
syntaxToUse = 'css';
}
}
}
if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, getRootNode(), currentNode, syntaxToUse, position, rangeToReplace)) {
if (validateLocation && !isValidLocationForEmmetAbbreviation(editor.document, getRootNode(), currentNode, syntaxToUse, offset, rangeToReplace)) {
return;
}
@ -393,10 +445,10 @@ function fallbackTab(): Thenable<boolean | undefined> {
* @param position position to validate
* @param abbreviationRange The range of the abbreviation for which given position is being validated
*/
export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | null, syntax: string, position: vscode.Position, abbreviationRange: vscode.Range): boolean {
export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocument, rootNode: Node | undefined, currentNode: Node | undefined, syntax: string, offset: number, abbreviationRange: vscode.Range): boolean {
if (isStyleSheet(syntax)) {
const stylesheet = <Stylesheet>rootNode;
if (stylesheet && (stylesheet.comments || []).some(x => position.isAfterOrEqual(x.start) && position.isBeforeOrEqual(x.end))) {
if (stylesheet && (stylesheet.comments || []).some(x => offset >= x.start && offset <= x.end)) {
return false;
}
// Continue validation only if the file was parse-able and the currentNode has been found
@ -419,14 +471,14 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
const propertyNode = <Property>currentNode;
if (propertyNode.terminatorToken
&& propertyNode.separator
&& position.isAfterOrEqual(propertyNode.separatorToken.end)
&& position.isBeforeOrEqual(propertyNode.terminatorToken.start)
&& offset >= propertyNode.separatorToken.end
&& offset <= propertyNode.terminatorToken.start
&& abbreviation.indexOf(':') === -1) {
return hexColorRegex.test(abbreviation) || abbreviation === '!';
}
if (!propertyNode.terminatorToken
&& propertyNode.separator
&& position.isAfterOrEqual(propertyNode.separatorToken.end)
&& offset >= propertyNode.separatorToken.end
&& abbreviation.indexOf(':') === -1) {
return hexColorRegex.test(abbreviation) || abbreviation === '!';
}
@ -444,7 +496,7 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
const currentCssNode = <Rule>currentNode;
// Position is valid if it occurs after the `{` that marks beginning of rule contents
if (position.isAfter(currentCssNode.contentStartToken.end)) {
if (offset > currentCssNode.contentStartToken.end) {
return true;
}
@ -453,12 +505,16 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
// But we should assume it is a valid location for css properties under the parent rule
if (currentCssNode.parent
&& (currentCssNode.parent.type === 'rule' || currentCssNode.parent.type === 'at-rule')
&& currentCssNode.selectorToken
&& position.line !== currentCssNode.selectorToken.end.line
&& currentCssNode.selectorToken.start.character === abbreviationRange.start.character
&& currentCssNode.selectorToken.start.line === abbreviationRange.start.line
) {
return true;
&& currentCssNode.selectorToken) {
const position = document.positionAt(offset);
const tokenStartPos = document.positionAt(currentCssNode.selectorToken.start);
const tokenEndPos = document.positionAt(currentCssNode.selectorToken.end);
if (position.line !== tokenEndPos.line
&& tokenStartPos.character === abbreviationRange.start.character
&& tokenStartPos.line === abbreviationRange.start.line
) {
return true;
}
}
return false;
@ -469,7 +525,7 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
const escape = '\\';
const question = '?';
const currentHtmlNode = <HtmlNode>currentNode;
let start = new vscode.Position(0, 0);
let start = 0;
if (currentHtmlNode) {
if (currentHtmlNode.name === 'script') {
@ -487,27 +543,27 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
return false;
}
const innerRange = getInnerRange(currentHtmlNode);
// Fix for https://github.com/microsoft/vscode/issues/28829
if (!innerRange || !innerRange.contains(position)) {
if (!currentHtmlNode.open || !currentHtmlNode.close ||
!(currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start)) {
return false;
}
// Fix for https://github.com/microsoft/vscode/issues/35128
// Find the position up till where we will backtrack looking for unescaped < or >
// to decide if current position is valid for emmet expansion
start = innerRange.start;
start = currentHtmlNode.open.end;
let lastChildBeforePosition = currentHtmlNode.firstChild;
while (lastChildBeforePosition) {
if (lastChildBeforePosition.end.isAfter(position)) {
if (lastChildBeforePosition.end > offset) {
break;
}
start = lastChildBeforePosition.end;
lastChildBeforePosition = lastChildBeforePosition.nextSibling;
}
}
let textToBackTrack = document.getText(new vscode.Range(start.line, start.character, abbreviationRange.start.line, abbreviationRange.start.character));
const startPos = document.positionAt(start);
let textToBackTrack = document.getText(new vscode.Range(startPos.line, startPos.character, abbreviationRange.start.line, abbreviationRange.start.character));
// Worse case scenario is when cursor is inside a big chunk of text which needs to backtracked
// Backtrack only 500 offsets to ensure we dont waste time doing this
@ -582,7 +638,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 }));
}
@ -607,24 +663,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) {
// /*
// * 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;
// }
const next = ctx.next;
if (fn(ctx) === false || walk(ctx.firstChild, fn) === false) {
return false;
}
ctx = next;
}
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.
@ -634,7 +692,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();
});
@ -644,46 +702,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 && !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 (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 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);
}

View File

@ -4,11 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate } from './util';
import { getHtmlFlatNode, offsetRangeToSelection, validate } from './util';
import { getRootNode } from './parseDocument';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
let balanceOutStack: Array<vscode.Selection[]> = [];
let lastOut = false;
let lastBalancedSelections: vscode.Selection[] = [];
export function balanceOut() {
@ -24,53 +24,51 @@ function balance(out: boolean) {
return;
}
const editor = vscode.window.activeTextEditor;
let rootNode = <HtmlNode>parseDocument(editor.document);
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
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, rootNode, 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: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection {
const offset = document.offsetAt(selection.start);
const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, false);
if (!nodeToBalance) {
return selection;
}
if (!nodeToBalance.close) {
return new vscode.Selection(nodeToBalance.start, nodeToBalance.end);
if (!nodeToBalance.open || !nodeToBalance.close) {
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.open.end, nodeToBalance.close.start);
const outerSelection = offsetRangeToSelection(document, nodeToBalance.open.start, nodeToBalance.close.end);
if (innerSelection.contains(selection) && !innerSelection.isEqual(selection)) {
return innerSelection;
@ -81,19 +79,22 @@ 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: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Selection {
const offset = document.offsetAt(selection.start);
const nodeToBalance = getHtmlFlatNode(document.getText(), rootNode, offset, 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.open && nodeToBalance.close) {
const entireNodeSelected = selectionStart === nodeToBalance.start && selectionEnd === nodeToBalance.end;
const startInOpenTag = selectionStart > nodeToBalance.open.start && selectionStart < nodeToBalance.open.end;
const startInCloseTag = selectionStart > nodeToBalance.close.start && selectionStart < nodeToBalance.close.end;
if (entireNodeSelected || startInOpenTag || startInCloseTag) {
return new vscode.Selection(nodeToBalance.open.end, nodeToBalance.close.start);
return offsetRangeToSelection(document, nodeToBalance.open.end, nodeToBalance.close.start);
}
}
@ -101,14 +102,15 @@ function getRangeToBalanceIn(document: vscode.TextDocument, selection: vscode.Se
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.firstChild;
if (selectionStart === firstChild.start
&& selectionEnd === firstChild.end
&& firstChild.open
&& firstChild.close) {
return offsetRangeToSelection(document, firstChild.open.end, firstChild.close.start);
}
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 +123,4 @@ function areSameSelections(a: vscode.Selection[], b: vscode.Selection[]): boolea
}
}
return true;
}
}

View File

@ -5,7 +5,7 @@
/* Based on @sergeche's work in his emmet plugin */
import { TextDocument, Position, Range, EndOfLine } from 'vscode';
import { TextDocument } from 'vscode';
/**
* A stream reader for VSCode's `TextDocument`
@ -13,40 +13,37 @@ import { TextDocument, Position, Range, EndOfLine } from 'vscode';
*/
export class DocumentStreamReader {
private document: TextDocument;
private start: Position;
private _eof: Position;
private _sof: Position;
public pos: Position;
private _eol: string;
constructor(document: TextDocument, pos?: Position, limit?: Range) {
private start: number;
private _eof: number;
private _sof: number;
public pos: number;
constructor(document: TextDocument, pos?: number, limit?: [number, number]) {
this.document = document;
this.start = this.pos = pos ? pos : new Position(0, 0);
this._sof = limit ? limit.start : new Position(0, 0);
this._eof = limit ? limit.end : new Position(this.document.lineCount - 1, this._lineLength(this.document.lineCount - 1));
this._eol = this.document.eol === EndOfLine.LF ? '\n' : '\r\n';
this.start = this.pos = pos ? pos : 0;
this._sof = limit ? limit[0] : 0;
this._eof = limit ? limit[1] : document.getText().length;
}
/**
* Returns true only if the stream is at the start of the file.
*/
sof(): boolean {
return this.pos.isBeforeOrEqual(this._sof);
return this.pos <= this._sof;
}
/**
* Returns true only if the stream is at the end of the file.
*/
eof(): boolean {
return this.pos.isAfterOrEqual(this._eof);
return this.pos >= this._eof;
}
/**
* Creates a new stream instance which is limited to given range for given document
*/
limit(start: Position, end: Position): DocumentStreamReader {
return new DocumentStreamReader(this.document, start, new Range(start, end));
limit(start: number, end: number): DocumentStreamReader {
return new DocumentStreamReader(this.document, start, [start, end]);
}
/**
@ -57,8 +54,7 @@ export class DocumentStreamReader {
if (this.eof()) {
return NaN;
}
const line = this.document.lineAt(this.pos.line).text;
return this.pos.character < line.length ? line.charCodeAt(this.pos.character) : this._eol.charCodeAt(this.pos.character - line.length);
return this.document.getText().charCodeAt(this.pos);
}
/**
@ -70,19 +66,12 @@ export class DocumentStreamReader {
return NaN;
}
const line = this.document.lineAt(this.pos.line).text;
let code: number;
if (this.pos.character < line.length) {
code = line.charCodeAt(this.pos.character);
this.pos = this.pos.translate(0, 1);
} else {
code = this._eol.charCodeAt(this.pos.character - line.length);
this.pos = new Position(this.pos.line + 1, 0);
}
const code = this.document.getText().charCodeAt(this.pos);
this.pos++;
if (this.eof()) {
// restrict pos to eof, if in case it got moved beyond eof
this.pos = new Position(this._eof.line, this._eof.character);
this.pos = this._eof;
}
return code;
@ -92,20 +81,11 @@ export class DocumentStreamReader {
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n: number) {
let row = this.pos.line;
let column = this.pos.character;
column -= (n || 1);
while (row >= 0 && column < 0) {
row--;
column += this._lineLength(row);
backUp(n: number): number {
this.pos -= n;
if (this.pos < 0) {
this.pos = 0;
}
this.pos = row < 0 || column < 0
? new Position(0, 0)
: new Position(row, column);
return this.peek();
}
@ -120,29 +100,18 @@ export class DocumentStreamReader {
/**
* Returns contents for given range
*/
substring(from: Position, to: Position): string {
return this.document.getText(new Range(from, to));
substring(from: number, to: number): string {
return this.document.getText().substring(from, to);
}
/**
* Creates error object with current stream state
*/
error(message: string): Error {
const err = new Error(`${message} at row ${this.pos.line}, column ${this.pos.character}`);
const err = new Error(`${message} at offset ${this.pos}`);
return err;
}
/**
* Returns line length of given row, including line ending
*/
_lineLength(row: number): number {
if (row === this.document.lineCount - 1) {
return this.document.lineAt(row).text.length;
}
return this.document.lineAt(row).text.length + this._eol.length;
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
@ -167,6 +136,6 @@ export class DocumentStreamReader {
eatWhile(match: number | Function): boolean {
const start = this.pos;
while (!this.eof() && this.eat(match)) { }
return !this.pos.isEqual(start);
return this.pos !== start;
}
}

View File

@ -4,17 +4,16 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Node, Stylesheet } from 'EmmetNode';
import { Node, Stylesheet } from 'EmmetFlatNode';
import { isValidLocationForEmmetAbbreviation, getSyntaxFromArgs } from './abbreviationActions';
import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, parseDocument, getNode, allowedMimeTypesInScriptTag, trimQuotes, toLSTextDocument } from './util';
import { getLanguageService, TokenType, Range as LSRange } from 'vscode-html-languageservice';
import { getEmmetHelper, getMappingForIncludedLanguages, parsePartialStylesheet, getEmmetConfiguration, getEmmetMode, isStyleSheet, getFlatNode, allowedMimeTypesInScriptTag, toLSTextDocument, getHtmlFlatNode } from './util';
import { Range as LSRange } from 'vscode-languageserver-textdocument';
import { getRootNode } from './parseDocument';
export class DefaultCompletionItemProvider implements vscode.CompletionItemProvider {
private lastCompletionType: string | undefined;
private htmlLS = getLanguageService();
public provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, _: vscode.CancellationToken, context: vscode.CompletionContext): Thenable<vscode.CompletionList | undefined> | undefined {
const completionResult = this.provideCompletionItemsInternal(document, position, context);
if (!completionResult) {
@ -62,8 +61,8 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
const helper = getEmmetHelper();
let validateLocation = syntax === 'html' || syntax === 'jsx' || syntax === 'xml';
let rootNode: Node | undefined = undefined;
let currentNode: Node | null = null;
let rootNode: Node | undefined;
let currentNode: Node | undefined;
const lsDoc = toLSTextDocument(document);
position = document.validatePosition(position);
@ -81,19 +80,16 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
default:
break;
}
}
if (validateLocation) {
const parsedLsDoc = this.htmlLS.parseHTMLDocument(lsDoc);
const positionOffset = document.offsetAt(position);
const node = parsedLsDoc.findNodeAt(positionOffset);
if (node.tag === 'script') {
if (node.attributes && 'type' in node.attributes) {
const rawTypeAttrValue = node.attributes['type'];
if (rawTypeAttrValue) {
const typeAttrValue = trimQuotes(rawTypeAttrValue);
const emmetRootNode = getRootNode(document, true);
const foundNode = getHtmlFlatNode(document.getText(), emmetRootNode, positionOffset, false);
if (foundNode) {
if (foundNode.name === 'script') {
const typeNode = foundNode.attributes.find(attr => attr.name.toString() === 'type');
if (typeNode) {
const typeAttrValue = typeNode.value.toString();
if (typeAttrValue === 'application/javascript' || typeAttrValue === 'text/javascript') {
if (!getSyntaxFromArgs({ language: 'javascript' })) {
return;
@ -101,34 +97,19 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
validateLocation = false;
}
}
else if (allowedMimeTypesInScriptTag.indexOf(trimQuotes(rawTypeAttrValue)) > -1) {
else if (allowedMimeTypesInScriptTag.includes(typeAttrValue)) {
validateLocation = false;
}
} else {
return;
}
} else {
return;
}
}
else if (node.tag === 'style') {
syntax = 'css';
validateLocation = false;
} else {
if (node.attributes && node.attributes['style']) {
const scanner = this.htmlLS.createScanner(document.getText(), node.start);
let tokenType = scanner.scan();
let prevAttr = undefined;
let styleAttrValueRange: [number, number] | undefined = undefined;
while (tokenType !== TokenType.EOS && (scanner.getTokenEnd() <= positionOffset)) {
tokenType = scanner.scan();
if (tokenType === TokenType.AttributeName) {
prevAttr = scanner.getTokenText();
}
else if (tokenType === TokenType.AttributeValue && prevAttr === 'style') {
styleAttrValueRange = [scanner.getTokenOffset(), scanner.getTokenEnd()];
}
}
if (prevAttr === 'style' && styleAttrValueRange && positionOffset > styleAttrValueRange[0] && positionOffset < styleAttrValueRange[1]) {
else if (foundNode.name === 'style') {
syntax = 'css';
validateLocation = false;
} else {
const styleNode = foundNode.attributes.find(attr => attr.name.toString() === 'style');
if (styleNode && styleNode.value.start <= positionOffset && positionOffset <= styleNode.value.end) {
syntax = 'css';
validateLocation = false;
}
@ -145,19 +126,18 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
return;
}
const offset = document.offsetAt(position);
if (isStyleSheet(document.languageId) && context.triggerKind !== vscode.CompletionTriggerKind.TriggerForIncompleteCompletions) {
validateLocation = true;
let usePartialParsing = vscode.workspace.getConfiguration('emmet')['optimizeStylesheetParsing'] === true;
rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : <Stylesheet>parseDocument(document, false);
rootNode = usePartialParsing && document.lineCount > 1000 ? parsePartialStylesheet(document, position) : <Stylesheet>getRootNode(document, true);
if (!rootNode) {
return;
}
currentNode = getNode(rootNode, position, true);
currentNode = getFlatNode(rootNode, offset, true);
}
if (validateLocation && !isValidLocationForEmmetAbbreviation(document, rootNode, currentNode, syntax, position, toRange(extractAbbreviationResults.abbreviationRange))) {
if (validateLocation && !isValidLocationForEmmetAbbreviation(document, rootNode, currentNode, syntax, offset, toRange(extractAbbreviationResults.abbreviationRange))) {
return;
}

View File

@ -46,7 +46,7 @@ function findEditPoint(lineNum: number, editor: vscode.TextEditor, position: vsc
let line = editor.document.lineAt(lineNum);
let lineContent = line.text;
if (lineNum !== position.line && line.isEmptyOrWhitespace) {
if (lineNum !== position.line && line.isEmptyOrWhitespace && lineContent.length) {
return new vscode.Selection(lineNum, lineContent.length, lineNum, lineContent.length);
}

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, getSyntaxes, getEmmetMode } from './util';
import { reflectCssValue } from './reflectCssValue';
import { addFileToParseCache, removeFileFromParseCache } from './parseDocument';
export function activateEmmetExtension(context: vscode.ExtensionContext) {
registerCompletionProviders(context);
@ -145,6 +146,22 @@ export function activateEmmetExtension(context: vscode.ExtensionContext) {
updateEmmetExtensionsPath(true);
}
}));
context.subscriptions.push(vscode.workspace.onDidOpenTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(emmetMode) || syntaxes.stylesheet.includes(emmetMode)) {
addFileToParseCache(e);
}
}));
context.subscriptions.push(vscode.workspace.onDidCloseTextDocument((e) => {
const emmetMode = getEmmetMode(e.languageId, []) ?? '';
const syntaxes = getSyntaxes();
if (syntaxes.markup.includes(emmetMode) || syntaxes.stylesheet.includes(emmetMode)) {
removeFileFromParseCache(e);
}
}));
}
/**

View File

@ -7,7 +7,6 @@
import * as vscode from 'vscode';
import evaluate, { extract } from '@emmetio/math-expression';
import { DocumentStreamReader } from './bufferStream';
export function evaluateMathExpression(): Thenable<boolean> {
if (!vscode.window.activeTextEditor) {
@ -15,13 +14,12 @@ export function evaluateMathExpression(): Thenable<boolean> {
return Promise.resolve(false);
}
const editor = vscode.window.activeTextEditor;
const stream = new DocumentStreamReader(editor.document);
return editor.edit(editBuilder => {
editor.selections.forEach(selection => {
// startpos always comes before endpos
const startpos = selection.isReversed ? selection.active : selection.anchor;
const endpos = selection.isReversed ? selection.anchor : selection.active;
const selectionText = stream.substring(startpos, endpos);
const selectionText = editor.document.getText(new vscode.Range(startpos, endpos));
try {
if (selectionText) {
@ -30,7 +28,7 @@ export function evaluateMathExpression(): Thenable<boolean> {
editBuilder.replace(new vscode.Range(startpos, endpos), result);
} else {
// no selection made, extract expression from line
const lineToSelectionEnd = stream.substring(new vscode.Position(selection.end.line, 0), endpos);
const lineToSelectionEnd = editor.document.getText(new vscode.Range(new vscode.Position(selection.end.line, 0), endpos));
const extractedIndices = extract(lineToSelectionEnd);
if (!extractedIndices) {
throw new Error('Invalid extracted indices');

View File

@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
// Based on @sergeche's work on the emmet plugin for atom
// TODO: Move to https://github.com/emmetio/image-size
import * as path from 'path';
import * as http from 'http';

View File

@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate } from './util';
import { validate, getHtmlFlatNode, offsetRangeToSelection } from './util';
import { getRootNode } from './parseDocument';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
export function matchTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -14,32 +14,40 @@ export function matchTag() {
}
const editor = vscode.window.activeTextEditor;
let rootNode: HtmlNode = <HtmlNode>parseDocument(editor.document);
if (!rootNode) { return; }
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
let updatedSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
let updatedSelection = getUpdatedSelections(editor, selection.start, rootNode);
const updatedSelection = getUpdatedSelections(document, rootNode, 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: vscode.TextDocument, rootNode: HtmlFlatNode, position: vscode.Position): vscode.Selection | undefined {
const offset = document.offsetAt(position);
const currentNode = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!currentNode) {
return;
}
// 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 no opening/closing tag or cursor is between open and close tag, then no-op
if (!currentNode.open
|| !currentNode.close
|| (offset > currentNode.open.end && offset < currentNode.close.start)) {
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.open.end) ? currentNode.close.start + 2 : currentNode.start + 1;
return offsetRangeToSelection(document, finalOffset, finalOffset);
}

View File

@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { Node } from 'EmmetNode';
import { getNode, parseDocument, validate } from './util';
import { Node } from 'EmmetFlatNode';
import { getFlatNode, offsetRangeToVsRange, validate } from './util';
import { getRootNode } from './parseDocument';
export function mergeLines() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -14,14 +15,14 @@ export function mergeLines() {
const editor = vscode.window.activeTextEditor;
let rootNode = parseDocument(editor.document);
const rootNode = getRootNode(editor.document, true);
if (!rootNode) {
return;
}
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let textEdit = getRangesToReplace(editor.document, selection, rootNode!);
const textEdit = getRangesToReplace(editor.document, selection, rootNode);
if (textEdit) {
editBuilder.replace(textEdit.range, textEdit.newText);
}
@ -30,25 +31,36 @@ export function mergeLines() {
}
function getRangesToReplace(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit | undefined {
let startNodeToUpdate: Node | null;
let endNodeToUpdate: Node | null;
let startNodeToUpdate: Node | undefined;
let endNodeToUpdate: Node | undefined;
const selectionStart = document.offsetAt(selection.start);
const selectionEnd = document.offsetAt(selection.end);
if (selection.isEmpty) {
startNodeToUpdate = endNodeToUpdate = getNode(rootNode, selection.start, true);
startNodeToUpdate = endNodeToUpdate = getFlatNode(rootNode, selectionStart, true);
} else {
startNodeToUpdate = getNode(rootNode, selection.start, true);
endNodeToUpdate = getNode(rootNode, selection.end, true);
startNodeToUpdate = getFlatNode(rootNode, selectionStart, true);
endNodeToUpdate = getFlatNode(rootNode, selectionEnd, true);
}
if (!startNodeToUpdate || !endNodeToUpdate || startNodeToUpdate.start.line === endNodeToUpdate.end.line) {
if (!startNodeToUpdate || !endNodeToUpdate) {
return;
}
let rangeToReplace = new vscode.Range(startNodeToUpdate.start, endNodeToUpdate.end);
let textToReplaceWith = document.lineAt(startNodeToUpdate.start.line).text.substr(startNodeToUpdate.start.character);
for (let i = startNodeToUpdate.start.line + 1; i <= endNodeToUpdate.end.line; i++) {
const startPos = document.positionAt(startNodeToUpdate.start);
const startLine = startPos.line;
const startChar = startPos.character;
const endPos = document.positionAt(endNodeToUpdate.end);
const endLine = endPos.line;
if (startLine === endLine) {
return;
}
const rangeToReplace = offsetRangeToVsRange(document, startNodeToUpdate.start, endNodeToUpdate.end);
let textToReplaceWith = document.lineAt(startLine).text.substr(startChar);
for (let i = startLine + 1; i <= endLine; i++) {
textToReplaceWith += document.lineAt(i).text.trim();
}
return new vscode.TextEdit(rangeToReplace, textToReplaceWith);
}
}

View File

@ -0,0 +1,46 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TextDocument } from 'vscode';
import { Node as FlatNode } from 'EmmetFlatNode';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import { isStyleSheet } from './util';
type Pair<K, V> = {
key: K;
value: V;
};
// Map(filename, Pair(fileVersion, rootNodeOfParsedContent))
const _parseCache = new Map<string, Pair<number, FlatNode> | undefined>();
export function getRootNode(document: TextDocument, useCache: boolean): FlatNode {
const key = document.uri.toString();
const result = _parseCache.get(key);
const documentVersion = document.version;
if (useCache && result) {
if (documentVersion === result.key) {
return result.value;
}
}
const parseContent = isStyleSheet(document.languageId) ? parseStylesheet : parse;
const rootNode = parseContent(document.getText());
if (useCache) {
_parseCache.set(key, { key: documentVersion, value: rootNode });
}
return rootNode;
}
export function addFileToParseCache(document: TextDocument) {
const filename = document.uri.toString();
_parseCache.set(filename, undefined);
}
export function removeFileFromParseCache(document: TextDocument) {
const filename = document.uri.toString();
_parseCache.delete(filename);
}

View File

@ -3,20 +3,20 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Range, window, TextEditor } from 'vscode';
import { getCssPropertyFromRule, getCssPropertyFromDocument } from './util';
import { Property, Rule } from 'EmmetNode';
import { window, TextEditor } from 'vscode';
import { getCssPropertyFromRule, getCssPropertyFromDocument, offsetRangeToVsRange } from './util';
import { Property, Rule } from 'EmmetFlatNode';
const vendorPrefixes = ['-webkit-', '-moz-', '-ms-', '-o-', ''];
export function reflectCssValue(): Thenable<boolean> | undefined {
let editor = window.activeTextEditor;
const editor = window.activeTextEditor;
if (!editor) {
window.showInformationMessage('No editor is active.');
return;
}
let node = getCssPropertyFromDocument(editor, editor.selection.active);
const node = getCssPropertyFromDocument(editor, editor.selection.active);
if (!node) {
return;
}
@ -45,10 +45,11 @@ function updateCSSNode(editor: TextEditor, property: Property): Thenable<boolean
if (prefix === currentPrefix) {
return;
}
let vendorProperty = getCssPropertyFromRule(rule, prefix + propertyName);
const vendorProperty = getCssPropertyFromRule(rule, prefix + propertyName);
if (vendorProperty) {
builder.replace(new Range(vendorProperty.valueToken.start, vendorProperty.valueToken.end), propertyValue);
const rangeToReplace = offsetRangeToVsRange(editor.document, vendorProperty.valueToken.start, vendorProperty.valueToken.end);
builder.replace(rangeToReplace, propertyValue);
}
});
});
}
}

View File

@ -4,63 +4,91 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { parseDocument, validate, getHtmlNode } from './util';
import { HtmlNode } from 'EmmetNode';
import { getRootNode } from './parseDocument';
import { validate, getHtmlFlatNode, offsetRangeToVsRange } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
export function removeTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
return;
}
const editor = vscode.window.activeTextEditor;
let rootNode = <HtmlNode>parseDocument(editor.document);
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
let indentInSpaces = '';
const tabSize: number = editor.options.tabSize ? +editor.options.tabSize : 0;
for (let i = 0; i < tabSize || 0; i++) {
indentInSpaces += ' ';
}
let rangesToRemove: vscode.Range[] = [];
editor.selections.reverse().forEach(selection => {
rangesToRemove = rangesToRemove.concat(getRangeToRemove(editor, rootNode, selection, indentInSpaces));
});
let finalRangesToRemove = editor.selections.reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToRemove(editor.document, rootNode, selection)), []);
return editor.edit(editBuilder => {
rangesToRemove.forEach(range => {
finalRangesToRemove.forEach(range => {
editBuilder.replace(range, '');
});
});
}
function getRangeToRemove(editor: vscode.TextEditor, rootNode: HtmlNode, selection: vscode.Selection, indentInSpaces: string): vscode.Range[] {
let nodeToUpdate = getHtmlNode(editor.document, rootNode, selection.start, true);
/**
* Calculates the ranges to remove, along with what to replace those ranges with.
* It finds the node to remove based on the selection's start position
* and then removes that node, reindenting the content in between.
*/
function getRangesToRemove(document: vscode.TextDocument, rootNode: HtmlFlatNode, selection: vscode.Selection): vscode.Range[] {
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(document.getText(), rootNode, offset, true);
if (!nodeToUpdate) {
return [];
}
let openRange = new vscode.Range(nodeToUpdate.open.start, nodeToUpdate.open.end);
let closeRange: vscode.Range | null = null;
let openTagRange: vscode.Range | undefined;
if (nodeToUpdate.open) {
openTagRange = offsetRangeToVsRange(document, nodeToUpdate.open.start, nodeToUpdate.open.end);
}
let closeTagRange: vscode.Range | undefined;
if (nodeToUpdate.close) {
closeRange = new vscode.Range(nodeToUpdate.close.start, nodeToUpdate.close.end);
closeTagRange = offsetRangeToVsRange(document, nodeToUpdate.close.start, nodeToUpdate.close.end);
}
let ranges = [openRange];
if (closeRange) {
for (let i = openRange.start.line + 1; i <= closeRange.start.line; i++) {
let lineContent = editor.document.lineAt(i).text;
if (lineContent.startsWith('\t')) {
ranges.push(new vscode.Range(i, 0, i, 1));
} else if (lineContent.startsWith(indentInSpaces)) {
ranges.push(new vscode.Range(i, 0, i, indentInSpaces.length));
let rangesToRemove = [];
if (openTagRange) {
rangesToRemove.push(openTagRange);
if (closeTagRange) {
const indentAmountToRemove = calculateIndentAmountToRemove(document, openTagRange, closeTagRange);
for (let i = openTagRange.start.line + 1; i < closeTagRange.start.line; i++) {
rangesToRemove.push(new vscode.Range(i, 0, i, indentAmountToRemove));
}
rangesToRemove.push(closeTagRange);
}
ranges.push(closeRange);
}
return ranges;
return rangesToRemove;
}
/**
* Calculates the amount of indent to remove for getRangesToRemove.
*/
function calculateIndentAmountToRemove(document: vscode.TextDocument, openRange: vscode.Range, closeRange: vscode.Range): number {
const startLine = openRange.start.line;
const endLine = closeRange.start.line;
const startLineIndent = document.lineAt(startLine).firstNonWhitespaceCharacterIndex;
const endLineIndent = document.lineAt(endLine).firstNonWhitespaceCharacterIndex;
let contentIndent: number | undefined;
for (let i = startLine + 1; i < endLine; i++) {
const lineIndent = document.lineAt(i).firstNonWhitespaceCharacterIndex;
contentIndent = !contentIndent ? lineIndent : Math.min(contentIndent, lineIndent);
}
let indentAmount = 0;
if (contentIndent) {
if (contentIndent < startLineIndent || contentIndent < endLineIndent) {
indentAmount = 0;
}
else {
indentAmount = Math.min(contentIndent - startLineIndent, contentIndent - endLineIndent);
}
}
return indentAmount;
}

View File

@ -4,17 +4,19 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { validate, parseDocument, isStyleSheet } from './util';
import { validate, isStyleSheet } from './util';
import { nextItemHTML, prevItemHTML } from './selectItemHTML';
import { nextItemStylesheet, prevItemStylesheet } from './selectItemStylesheet';
import { HtmlNode, CssNode } from 'EmmetNode';
import { HtmlNode, CssNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
export function fetchSelectItem(direction: string): void {
if (!validate() || !vscode.window.activeTextEditor) {
return;
}
const editor = vscode.window.activeTextEditor;
let rootNode = parseDocument(editor.document);
const document = editor.document;
const rootNode = getRootNode(document, true);
if (!rootNode) {
return;
}
@ -26,12 +28,16 @@ export function fetchSelectItem(direction: string): void {
let updatedSelection;
if (isStyleSheet(editor.document.languageId)) {
updatedSelection = direction === 'next' ? nextItemStylesheet(selectionStart, selectionEnd, <CssNode>rootNode!) : prevItemStylesheet(selectionStart, selectionEnd, <CssNode>rootNode!);
updatedSelection = direction === 'next' ?
nextItemStylesheet(document, selectionStart, selectionEnd, <CssNode>rootNode) :
prevItemStylesheet(document, selectionStart, selectionEnd, <CssNode>rootNode);
} else {
updatedSelection = direction === 'next' ? nextItemHTML(selectionStart, selectionEnd, editor, <HtmlNode>rootNode!) : prevItemHTML(selectionStart, selectionEnd, editor, <HtmlNode>rootNode!);
updatedSelection = direction === 'next' ?
nextItemHTML(document, selectionStart, selectionEnd, <HtmlNode>rootNode) :
prevItemHTML(document, selectionStart, selectionEnd, <HtmlNode>rootNode);
}
newSelections.push(updatedSelection ? updatedSelection : selection);
});
editor.selections = newSelections;
editor.revealRange(editor.selections[editor.selections.length - 1]);
}
}

View File

@ -4,11 +4,12 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getDeepestNode, findNextWord, findPrevWord, getHtmlNode, isNumber } from './util';
import { HtmlNode } from 'EmmetNode';
import { getDeepestFlatNode, findNextWord, findPrevWord, getHtmlFlatNode, offsetRangeToSelection } from './util';
import { HtmlNode } from 'EmmetFlatNode';
export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = getHtmlNode(editor.document, rootNode, selectionEnd, false);
export function nextItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
const selectionEndOffset = document.offsetAt(selectionEnd);
let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionEndOffset, false);
let nextNode: HtmlNode | undefined = undefined;
if (!currentNode) {
@ -17,13 +18,16 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
if (currentNode.type !== 'comment') {
// If cursor is in the tag name, select tag
if (selectionEnd.isBefore(currentNode.open.start.translate(0, currentNode.name.length))) {
return getSelectionFromNode(currentNode);
if (currentNode.open &&
selectionEndOffset < currentNode.open.start + currentNode.name.length) {
return getSelectionFromNode(document, currentNode);
}
// If cursor is in the open tag, look for attributes
if (selectionEnd.isBefore(currentNode.open.end)) {
let attrSelection = getNextAttribute(selectionStart, selectionEnd, currentNode);
if (currentNode.open &&
selectionEndOffset < currentNode.open.end) {
const selectionStartOffset = document.offsetAt(selectionStart);
const attrSelection = getNextAttribute(document, selectionStartOffset, selectionEndOffset, currentNode);
if (attrSelection) {
return attrSelection;
}
@ -31,12 +35,11 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
// Get the first child of current node which is right after the cursor and is not a comment
nextNode = currentNode.firstChild;
while (nextNode && (selectionEnd.isAfterOrEqual(nextNode.end) || nextNode.type === 'comment')) {
while (nextNode && (selectionEndOffset >= nextNode.end || nextNode.type === 'comment')) {
nextNode = nextNode.nextSibling;
}
}
// Get next sibling of current node which is not a comment. If none is found try the same on the parent
while (!nextNode && currentNode) {
if (currentNode.nextSibling) {
@ -50,33 +53,36 @@ export function nextItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
}
}
return nextNode && getSelectionFromNode(nextNode);
return nextNode && getSelectionFromNode(document, nextNode);
}
export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vscode.Position, editor: vscode.TextEditor, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = getHtmlNode(editor.document, rootNode, selectionStart, false);
export function prevItemHTML(document: vscode.TextDocument, selectionStart: vscode.Position, selectionEnd: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
const selectionStartOffset = document.offsetAt(selectionStart);
let currentNode = getHtmlFlatNode(document.getText(), rootNode, selectionStartOffset, false);
let prevNode: HtmlNode | undefined = undefined;
if (!currentNode) {
return;
}
if (currentNode.type !== 'comment' && selectionStart.translate(0, -1).isAfter(currentNode.open.start)) {
if (selectionStart.isBefore(currentNode.open.end) || !currentNode.firstChild || selectionEnd.isBeforeOrEqual(currentNode.firstChild.start)) {
const selectionEndOffset = document.offsetAt(selectionEnd);
if (currentNode.open &&
currentNode.type !== 'comment' &&
selectionStartOffset - 1 > currentNode.open.start) {
if (selectionStartOffset < currentNode.open.end || !currentNode.firstChild || selectionEndOffset <= currentNode.firstChild.start) {
prevNode = currentNode;
} else {
// Select the child that appears just before the cursor and is not a comment
prevNode = currentNode.firstChild;
let oldOption: HtmlNode | undefined = undefined;
while (prevNode.nextSibling && selectionStart.isAfterOrEqual(prevNode.nextSibling.end)) {
while (prevNode.nextSibling && selectionStartOffset >= prevNode.nextSibling.end) {
if (prevNode && prevNode.type !== 'comment') {
oldOption = prevNode;
}
prevNode = prevNode.nextSibling;
}
prevNode = <HtmlNode>getDeepestNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);
prevNode = <HtmlNode>getDeepestFlatNode((prevNode && prevNode.type !== 'comment') ? prevNode : oldOption);
}
}
@ -84,7 +90,7 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
while (!prevNode && currentNode) {
if (currentNode.previousSibling) {
if (currentNode.previousSibling.type !== 'comment') {
prevNode = <HtmlNode>getDeepestNode(currentNode.previousSibling);
prevNode = <HtmlNode>getDeepestFlatNode(currentNode.previousSibling);
} else {
currentNode = currentNode.previousSibling;
}
@ -98,66 +104,66 @@ export function prevItemHTML(selectionStart: vscode.Position, selectionEnd: vsco
return undefined;
}
let attrSelection = getPrevAttribute(selectionStart, selectionEnd, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(prevNode);
const attrSelection = getPrevAttribute(document, selectionStartOffset, selectionEndOffset, prevNode);
return attrSelection ? attrSelection : getSelectionFromNode(document, prevNode);
}
function getSelectionFromNode(node: HtmlNode): vscode.Selection | undefined {
function getSelectionFromNode(document: vscode.TextDocument, node: HtmlNode): vscode.Selection | undefined {
if (node && node.open) {
let selectionStart = (<vscode.Position>node.open.start).translate(0, 1);
let selectionEnd = selectionStart.translate(0, node.name.length);
return new vscode.Selection(selectionStart, selectionEnd);
const selectionStart = node.open.start + 1;
const selectionEnd = selectionStart + node.name.length;
return offsetRangeToSelection(document, selectionStart, selectionEnd);
}
return undefined;
}
function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, node: HtmlNode): vscode.Selection | undefined {
function getNextAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
for (const attr of node.attributes) {
if (selectionEnd.isBefore(attr.start)) {
if (selectionEnd < attr.start) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
if (!attr.value || (<vscode.Position>attr.value.start).isEqual(attr.value.end)) {
if (!attr.value || attr.value.start === attr.value.end) {
// No attr value to select
continue;
}
if ((selectionStart.isEqual(attr.start) && selectionEnd.isEqual(attr.end)) || selectionEnd.isBefore(attr.value.start)) {
if ((selectionStart === attr.start && selectionEnd === attr.end) ||
selectionEnd < attr.value.start) {
// cursor is in attr name, so select full attr value
return new vscode.Selection(attr.value.start, attr.value.end);
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
}
// Fetch the next word in the attr value
if (attr.value.toString().indexOf(' ') === -1) {
// attr value does not have space, so no next word to find
continue;
}
let pos: number | undefined = undefined;
if (selectionStart.isEqual(attr.value.start) && selectionEnd.isEqual(attr.value.end)) {
if (selectionStart === attr.value.start && selectionEnd === attr.value.end) {
pos = -1;
}
if (pos === undefined && selectionEnd.isBefore(attr.end)) {
pos = selectionEnd.character - attr.value.start.character - 1;
if (pos === undefined && selectionEnd < attr.end) {
const selectionEndCharacter = document.positionAt(selectionEnd).character;
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
pos = selectionEndCharacter - attrValueStartCharacter - 1;
}
if (pos !== undefined) {
let [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
if (!isNumber(newSelectionStartOffset) || !isNumber(newSelectionEndOffset)) {
const [newSelectionStartOffset, newSelectionEndOffset] = findNextWord(attr.value.toString(), pos);
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
return;
}
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
const newSelectionStart = attr.value.start + newSelectionStartOffset;
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
}
}
@ -166,44 +172,44 @@ function getNextAttribute(selectionStart: vscode.Position, selectionEnd: vscode.
return;
}
function getPrevAttribute(selectionStart: vscode.Position, selectionEnd: vscode.Position, node: HtmlNode): vscode.Selection | undefined {
function getPrevAttribute(document: vscode.TextDocument, selectionStart: number, selectionEnd: number, node: HtmlNode): vscode.Selection | undefined {
if (!node.attributes || node.attributes.length === 0 || node.type === 'comment') {
return;
}
for (let i = node.attributes.length - 1; i >= 0; i--) {
let attr = node.attributes[i];
if (selectionStart.isBeforeOrEqual(attr.start)) {
const attr = node.attributes[i];
if (selectionStart <= attr.start) {
continue;
}
if (!attr.value || (<vscode.Position>attr.value.start).isEqual(attr.value.end) || selectionStart.isBefore(attr.value.start)) {
if (!attr.value || attr.value.start === attr.value.end || selectionStart < attr.value.start) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
if (selectionStart.isEqual(attr.value.start)) {
if (selectionEnd.isAfterOrEqual(attr.value.end)) {
if (selectionStart === attr.value.start) {
if (selectionEnd >= attr.value.end) {
// select full attr
return new vscode.Selection(attr.start, attr.end);
return offsetRangeToSelection(document, attr.start, attr.end);
}
// select attr value
return new vscode.Selection(attr.value.start, attr.value.end);
return offsetRangeToSelection(document, attr.value.start, attr.value.end);
}
// Fetch the prev word in the attr value
let pos = selectionStart.isAfter(attr.value.end) ? attr.value.toString().length : selectionStart.character - attr.value.start.character;
let [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
if (!isNumber(newSelectionStartOffset) || !isNumber(newSelectionEndOffset)) {
const selectionStartCharacter = document.positionAt(selectionStart).character;
const attrValueStartCharacter = document.positionAt(attr.value.start).character;
const pos = selectionStart > attr.value.end ? attr.value.toString().length :
selectionStartCharacter - attrValueStartCharacter;
const [newSelectionStartOffset, newSelectionEndOffset] = findPrevWord(attr.value.toString(), pos);
if (newSelectionStartOffset === undefined || newSelectionEndOffset === undefined) {
return;
}
if (newSelectionStartOffset >= 0 && newSelectionEndOffset >= 0) {
const newSelectionStart = (<vscode.Position>attr.value.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>attr.value.start).translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
const newSelectionStart = attr.value.start + newSelectionStartOffset;
const newSelectionEnd = attr.value.start + newSelectionEndOffset;
return offsetRangeToSelection(document, newSelectionStart, newSelectionEnd);
}
}

View File

@ -4,11 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getDeepestNode, findNextWord, findPrevWord, getNode } from './util';
import { Node, CssNode, Rule, Property } from 'EmmetNode';
import { getDeepestFlatNode, findNextWord, findPrevWord, getFlatNode, offsetRangeToSelection } from './util';
import { Node, CssNode, Rule, Property } from 'EmmetFlatNode';
export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, rootNode: Node): vscode.Selection | undefined {
let currentNode = <CssNode>getNode(rootNode, endOffset, true);
export function nextItemStylesheet(document: vscode.TextDocument, startPosition: vscode.Position, endPosition: vscode.Position, rootNode: Node): vscode.Selection | undefined {
const startOffset = document.offsetAt(startPosition);
const endOffset = document.offsetAt(endPosition);
let currentNode: CssNode | undefined = <CssNode>getFlatNode(rootNode, endOffset, true);
if (!currentNode) {
currentNode = <CssNode>rootNode;
}
@ -16,27 +18,31 @@ export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vsco
return;
}
// Full property is selected, so select full property value next
if (currentNode.type === 'property' && startOffset.isEqual(currentNode.start) && endOffset.isEqual(currentNode.end)) {
return getSelectionFromProperty(currentNode, startOffset, endOffset, true, 'next');
if (currentNode.type === 'property' &&
startOffset === currentNode.start &&
endOffset === currentNode.end) {
return getSelectionFromProperty(document, currentNode, startOffset, endOffset, true, 'next');
}
// Part or whole of propertyValue is selected, so select the next word in the propertyValue
if (currentNode.type === 'property' && startOffset.isAfterOrEqual((<Property>currentNode).valueToken.start) && endOffset.isBeforeOrEqual((<Property>currentNode).valueToken.end)) {
let singlePropertyValue = getSelectionFromProperty(currentNode, startOffset, endOffset, false, 'next');
if (currentNode.type === 'property' &&
startOffset >= (<Property>currentNode).valueToken.start &&
endOffset <= (<Property>currentNode).valueToken.end) {
let singlePropertyValue = getSelectionFromProperty(document, currentNode, startOffset, endOffset, false, 'next');
if (singlePropertyValue) {
return singlePropertyValue;
}
}
// Cursor is in the selector or in a property
if ((currentNode.type === 'rule' && endOffset.isBefore((<Rule>currentNode).selectorToken.end))
|| (currentNode.type === 'property' && endOffset.isBefore((<Property>currentNode).valueToken.end))) {
return getSelectionFromNode(currentNode);
if ((currentNode.type === 'rule' && endOffset < (<Rule>currentNode).selectorToken.end)
|| (currentNode.type === 'property' && endOffset < (<Property>currentNode).valueToken.end)) {
return getSelectionFromNode(document, currentNode);
}
// Get the first child of current node which is right after the cursor
let nextNode = currentNode.firstChild;
while (nextNode && endOffset.isAfterOrEqual(nextNode.end)) {
while (nextNode && endOffset >= nextNode.end) {
nextNode = nextNode.nextSibling;
}
@ -46,12 +52,13 @@ export function nextItemStylesheet(startOffset: vscode.Position, endOffset: vsco
currentNode = currentNode.parent;
}
return getSelectionFromNode(nextNode);
return nextNode ? getSelectionFromNode(document, nextNode) : undefined;
}
export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vscode.Position, rootNode: CssNode): vscode.Selection | undefined {
let currentNode = <CssNode>getNode(rootNode, startOffset, false);
export function prevItemStylesheet(document: vscode.TextDocument, startPosition: vscode.Position, endPosition: vscode.Position, rootNode: CssNode): vscode.Selection | undefined {
const startOffset = document.offsetAt(startPosition);
const endOffset = document.offsetAt(endPosition);
let currentNode = <CssNode>getFlatNode(rootNode, startOffset, false);
if (!currentNode) {
currentNode = rootNode;
}
@ -60,70 +67,80 @@ export function prevItemStylesheet(startOffset: vscode.Position, endOffset: vsco
}
// Full property value is selected, so select the whole property next
if (currentNode.type === 'property' && startOffset.isEqual((<Property>currentNode).valueToken.start) && endOffset.isEqual((<Property>currentNode).valueToken.end)) {
return getSelectionFromNode(currentNode);
if (currentNode.type === 'property' &&
startOffset === (<Property>currentNode).valueToken.start &&
endOffset === (<Property>currentNode).valueToken.end) {
return getSelectionFromNode(document, currentNode);
}
// Part of propertyValue is selected, so select the prev word in the propertyValue
if (currentNode.type === 'property' && startOffset.isAfterOrEqual((<Property>currentNode).valueToken.start) && endOffset.isBeforeOrEqual((<Property>currentNode).valueToken.end)) {
let singlePropertyValue = getSelectionFromProperty(currentNode, startOffset, endOffset, false, 'prev');
if (currentNode.type === 'property' &&
startOffset >= (<Property>currentNode).valueToken.start &&
endOffset <= (<Property>currentNode).valueToken.end) {
let singlePropertyValue = getSelectionFromProperty(document, currentNode, startOffset, endOffset, false, 'prev');
if (singlePropertyValue) {
return singlePropertyValue;
}
}
if (currentNode.type === 'property' || !currentNode.firstChild || (currentNode.type === 'rule' && startOffset.isBeforeOrEqual(currentNode.firstChild.start))) {
return getSelectionFromNode(currentNode);
if (currentNode.type === 'property' || !currentNode.firstChild ||
(currentNode.type === 'rule' && startOffset <= currentNode.firstChild.start)) {
return getSelectionFromNode(document, currentNode);
}
// Select the child that appears just before the cursor
let prevNode = currentNode.firstChild;
while (prevNode.nextSibling && startOffset.isAfterOrEqual(prevNode.nextSibling.end)) {
let prevNode: CssNode | undefined = currentNode.firstChild;
while (prevNode.nextSibling && startOffset >= prevNode.nextSibling.end) {
prevNode = prevNode.nextSibling;
}
prevNode = <CssNode>getDeepestNode(prevNode);
return getSelectionFromProperty(prevNode, startOffset, endOffset, false, 'prev');
prevNode = <CssNode | undefined>getDeepestFlatNode(prevNode);
return getSelectionFromProperty(document, prevNode, startOffset, endOffset, false, 'prev');
}
function getSelectionFromNode(node: Node): vscode.Selection | undefined {
function getSelectionFromNode(document: vscode.TextDocument, node: Node | undefined): vscode.Selection | undefined {
if (!node) {
return;
}
let nodeToSelect = node.type === 'rule' ? (<Rule>node).selectorToken : node;
return new vscode.Selection(nodeToSelect.start, nodeToSelect.end);
const nodeToSelect = node.type === 'rule' ? (<Rule>node).selectorToken : node;
return offsetRangeToSelection(document, nodeToSelect.start, nodeToSelect.end);
}
function getSelectionFromProperty(node: Node, selectionStart: vscode.Position, selectionEnd: vscode.Position, selectFullValue: boolean, direction: string): vscode.Selection | undefined {
function getSelectionFromProperty(document: vscode.TextDocument, node: Node | undefined, selectionStart: number, selectionEnd: number, selectFullValue: boolean, direction: string): vscode.Selection | undefined {
if (!node || node.type !== 'property') {
return;
}
const propertyNode = <Property>node;
let propertyValue = propertyNode.valueToken.stream.substring(propertyNode.valueToken.start, propertyNode.valueToken.end);
selectFullValue = selectFullValue || (direction === 'prev' && selectionStart.isEqual(propertyNode.valueToken.start) && selectionEnd.isBefore(propertyNode.valueToken.end));
selectFullValue = selectFullValue ||
(direction === 'prev' && selectionStart === propertyNode.valueToken.start && selectionEnd < propertyNode.valueToken.end);
if (selectFullValue) {
return new vscode.Selection(propertyNode.valueToken.start, propertyNode.valueToken.end);
return offsetRangeToSelection(document, propertyNode.valueToken.start, propertyNode.valueToken.end);
}
let pos: number = -1;
if (direction === 'prev') {
if (selectionStart.isEqual(propertyNode.valueToken.start)) {
if (selectionStart === propertyNode.valueToken.start) {
return;
}
pos = selectionStart.isAfter(propertyNode.valueToken.end) ? propertyValue.length : selectionStart.character - propertyNode.valueToken.start.character;
}
if (direction === 'next') {
if (selectionEnd.isEqual(propertyNode.valueToken.end) && (selectionStart.isAfter(propertyNode.valueToken.start) || propertyValue.indexOf(' ') === -1)) {
const selectionStartChar = document.positionAt(selectionStart).character;
const tokenStartChar = document.positionAt(propertyNode.valueToken.start).character;
pos = selectionStart > propertyNode.valueToken.end ? propertyValue.length :
selectionStartChar - tokenStartChar;
} else if (direction === 'next') {
if (selectionEnd === propertyNode.valueToken.end &&
(selectionStart > propertyNode.valueToken.start || !propertyValue.includes(' '))) {
return;
}
pos = selectionEnd.isEqual(propertyNode.valueToken.end) ? -1 : selectionEnd.character - propertyNode.valueToken.start.character - 1;
const selectionEndChar = document.positionAt(selectionEnd).character;
const tokenStartChar = document.positionAt(propertyNode.valueToken.start).character;
pos = selectionEnd === propertyNode.valueToken.end ? -1 :
selectionEndChar - tokenStartChar - 1;
}
@ -132,8 +149,9 @@ function getSelectionFromProperty(node: Node, selectionStart: vscode.Position, s
return;
}
const newSelectionStart = (<vscode.Position>propertyNode.valueToken.start).translate(0, newSelectionStartOffset);
const newSelectionEnd = (<vscode.Position>propertyNode.valueToken.start).translate(0, newSelectionEndOffset);
const tokenStart = document.positionAt(propertyNode.valueToken.start);
const newSelectionStart = tokenStart.translate(0, newSelectionStartOffset);
const newSelectionEnd = tokenStart.translate(0, newSelectionEndOffset);
return new vscode.Selection(newSelectionStart, newSelectionEnd);
}

View File

@ -4,8 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate, getEmmetMode, getEmmetConfiguration } from './util';
import { validate, getEmmetMode, getEmmetConfiguration, getHtmlFlatNode, offsetRangeToVsRange } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
export function splitJoinTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
@ -13,40 +14,43 @@ export function splitJoinTag() {
}
const editor = vscode.window.activeTextEditor;
let rootNode = <HtmlNode>parseDocument(editor.document);
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(editor.document, true);
if (!rootNode) {
return;
}
return editor.edit(editBuilder => {
editor.selections.reverse().forEach(selection => {
let nodeToUpdate = getHtmlNode(editor.document, rootNode, selection.start, true);
const documentText = document.getText();
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(documentText, rootNode, offset, true);
if (nodeToUpdate) {
let textEdit = getRangesToReplace(editor.document, nodeToUpdate);
const textEdit = getRangesToReplace(document, nodeToUpdate);
editBuilder.replace(textEdit.range, textEdit.newText);
}
});
});
}
function getRangesToReplace(document: vscode.TextDocument, nodeToUpdate: HtmlNode): vscode.TextEdit {
function getRangesToReplace(document: vscode.TextDocument, nodeToUpdate: HtmlFlatNode): vscode.TextEdit {
let rangeToReplace: vscode.Range;
let textToReplaceWith: string;
if (!nodeToUpdate.close) {
if (!nodeToUpdate.open || !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;
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 = new vscode.Range(start, end);
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = `></${nodeToUpdate.name}>`;
} 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.open.end - 1;
const end = nodeToUpdate.end;
rangeToReplace = offsetRangeToVsRange(document, start, end);
textToReplaceWith = '/>';
const emmetMode = getEmmetMode(document.languageId, []) || '';
@ -55,8 +59,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

@ -62,13 +62,13 @@ suite('Tests for Next/Previous Select/Edit point and Balance actions', () => {
return withRandomFileEditor(htmlContents, '.html', (editor, _) => {
editor.selections = [new Selection(1, 5, 1, 5)];
let expectedNextEditPoints: [number, number][] = [[4, 16], [6, 8], [10, 2], [20, 0]];
let expectedNextEditPoints: [number, number][] = [[4, 16], [6, 8], [10, 2], [10, 2]];
expectedNextEditPoints.forEach(([line, col]) => {
fetchEditPoint('next');
testSelection(editor.selection, col, line);
});
let expectedPrevEditPoints = [[10, 2], [6, 8], [4, 16], [0, 0]];
let expectedPrevEditPoints = [[6, 8], [4, 16], [4, 16]];
expectedPrevEditPoints.forEach(([line, col]) => {
fetchEditPoint('prev');
testSelection(editor.selection, col, line);
@ -113,7 +113,7 @@ suite('Tests for Next/Previous Select/Edit point and Balance actions', () => {
});
});
test('Emmet Select Next/Prev item at boundary', function(): any {
test('Emmet Select Next/Prev item at boundary', function (): any {
return withRandomFileEditor(htmlContents, '.html', (editor, _) => {
editor.selections = [new Selection(4, 1, 4, 1)];
@ -365,4 +365,4 @@ function testSelection(selection: Selection, startChar: number, startline: numbe
} else {
assert.equal(selection.active.character, endChar);
}
}
}

View File

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
const path = require('path');
const testRunner = require('vscode/lib/testrunner');
const testRunner = require('../../../../test/integration/electron/testrunner');
const options: any = {
ui: 'tdd',
@ -38,3 +38,34 @@ if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
testRunner.configure(options);
export = testRunner;
// import * as path from 'path';
// import * as Mocha from 'mocha';
// import * as glob from 'glob';
// export function run(testsRoot: string, cb: (error: any, failures?: number) => void): void {
// // Create the mocha test
// const mocha = new Mocha({
// ui: 'tdd'
// });
// mocha.useColors(true);
// glob('**/**.test.js', { cwd: testsRoot }, (err, files) => {
// if (err) {
// return cb(err);
// }
// // Add files to the test suite
// files.forEach(f => mocha.addFile(path.resolve(testsRoot, f)));
// try {
// // Run the mocha test
// mocha.run(failures => {
// cb(null, failures);
// });
// } catch (err) {
// console.error(err);
// cb(err);
// }
// });
// }

View File

@ -7,15 +7,16 @@ import 'mocha';
import * as assert from 'assert';
import { withRandomFileEditor } from './testUtils';
import * as vscode from 'vscode';
import { parsePartialStylesheet, getNode } from '../util';
import { parsePartialStylesheet, getFlatNode } from '../util';
import { isValidLocationForEmmetAbbreviation } from '../abbreviationActions';
suite('Tests for partial parse of Stylesheets', () => {
function isValid(doc: vscode.TextDocument, range: vscode.Range, syntax: string): boolean {
const rootNode = parsePartialStylesheet(doc, range.end);
const currentNode = getNode(rootNode, range.end, true);
return isValidLocationForEmmetAbbreviation(doc, rootNode, currentNode, syntax, range.end, range);
const endOffset = doc.offsetAt(range.end);
const currentNode = getFlatNode(rootNode, endOffset, true);
return isValidLocationForEmmetAbbreviation(doc, rootNode, currentNode, syntax, endOffset, range);
}
test('Ignore block comment inside rule', function (): any {
@ -257,4 +258,4 @@ ment */{
});
});
});

View File

@ -175,7 +175,7 @@ suite('Tests for Emmet actions on html tags', () => {
<li><span>Hello</span></li>
<li><span>There</span></li>
<div><li><span>Bye</span></li></div>
\t
\t\t
<span/>
</script>
`;

View File

@ -26,7 +26,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
<div><li><span>Bye</span></li></div>
</ul>
<ul>
<!--<li>Previously Commented Node</li>-->
<!-- <li>Previously Commented Node</li> -->
<li>Another Node</li>
</ul>
<span/>
@ -47,24 +47,24 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<!--<div><li><span>Bye</span></li></div>-->
<li><!-- <span>Hello</span> --></li>
<!-- <li><span>There</span></li> -->
<!-- <div><li><span>Bye</span></li></div> -->
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<style>
.boo {
/*margin: 10px;*/
/* margin: 10px; */
padding: 20px;
}
/*.hoo {
/* .hoo {
margin: 10px;
padding: 20px;
}*/
} */
</style>
</div>
`;
@ -89,24 +89,24 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<li><!-- <span>Hello</span> --></li>
<!-- <li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<style>
.boo {
/*margin: 10px;*/
/* margin: 10px; */
padding: 20px;
}
/*.hoo {
/* .hoo {
margin: 10px;
padding: 20px;
}*/
} */
</style>
</div>
`;
@ -130,19 +130,19 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<!--<li><span>Hello</span></li>
<li><span>There</span></li>-->
<!-- <li><span>Hello</span></li>
<li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<ul>
<!--<li>Previously Commented Node</li>-->
<!-- <li>Previously Commented Node</li> -->
<li>Another Node</li>
</ul>
<span/>
<style>
.boo {
/*margin: 10px;
padding: 20px;*/
/* margin: 10px;
padding: 20px; */
}
.hoo {
margin: 10px;
@ -168,14 +168,14 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<!--<li><span>Hello</span></li>
<li><span>There</span></li>-->
<!-- <li><span>Hello</span></li>
<li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<style>
.boo {
@ -206,16 +206,16 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const expectedContents = `
<div class="hello">
<ul>
<li><!--<span>Hello</span>--></li>
<!--<li><span>There</span></li>-->
<li><!-- <span>Hello</span> --></li>
<!-- <li><span>There</span></li> -->
<div><li><span>Bye</span></li></div>
</ul>
<!--<ul>
<!-- <ul>
<li>Previously Commented Node</li>
<li>Another Node</li>
</ul>-->
</ul> -->
<span/>
<!--<style>
<!-- <style>
.boo {
margin: 10px;
padding: 20px;
@ -224,7 +224,7 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
margin: 10px;
padding: 20px;
}
</style>-->
</style> -->
</div>
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
@ -252,16 +252,16 @@ suite('Tests for Toggle Comment action from Emmet (HTML)', () => {
const templateContents = `
<script type="text/template">
<li><span>Hello</span></li>
<li><!--<span>There</span>--></li>
<li><!-- <span>There</span> --></li>
<div><li><span>Bye</span></li></div>
<span/>
</script>
`;
const expectedContents = `
<script type="text/template">
<!--<li><span>Hello</span></li>-->
<!-- <li><span>Hello</span></li> -->
<li><span>There</span></li>
<div><li><!--<span>Bye</span>--></li></div>
<div><li><!-- <span>Bye</span> --></li></div>
<span/>
</script>
`;
@ -298,13 +298,13 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment with multiple cursors, but no selection (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;*/
/* margin: 10px; */
padding: 10px;
}
/*.two {
/* .two {
height: 42px;
display: none;
}*/
} */
.three {
width: 42px;
}`;
@ -327,13 +327,13 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment with multiple cursors and whole node selected (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;*/
/*padding: 10px;*/
/* margin: 10px; */
/* padding: 10px; */
}
/*.two {
/* .two {
height: 42px;
display: none;
}*/
} */
.three {
width: 42px;
}`;
@ -359,16 +359,16 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment when multiple nodes of same parent are completely under single selection (CSS)', () => {
const expectedContents = `
.one {
/* margin: 10px;
padding: 10px;*/
/* margin: 10px;
padding: 10px; */
}
/*.two {
/* .two {
height: 42px;
display: none;
}
.three {
width: 42px;
}*/`;
} */`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 0, 3, 16), // 2 properties completely under a single selection along with whitespace
@ -389,10 +389,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -417,10 +417,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -445,10 +445,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -473,10 +473,10 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
const expectedContents = `
.one {
margin: 10px;
/*padding: 10px;
/* padding: 10px;
}
.two {
height: 42px;*/
height: 42px; */
display: none;
}
.three {
@ -500,16 +500,16 @@ suite('Tests for Toggle Comment action from Emmet (CSS)', () => {
test('toggle comment when multiple nodes of same parent are partially under single selection (CSS)', () => {
const expectedContents = `
.one {
/*margin: 10px;
padding: 10px;*/
/* margin: 10px;
padding: 10px; */
}
/*.two {
/* .two {
height: 42px;
display: none;
}
.three {
width: 42px;
*/ }`;
*/ }`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(2, 7, 3, 10), // 2 properties partially under a single selection
@ -549,14 +549,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment with multiple cursors selecting nested nodes (SCSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/* height: 42px; */
/*.two {
/* .two {
width: 42px;
}*/
} */
.three {
/*padding: 10px;*/
/* padding: 10px; */
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
@ -578,7 +578,7 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
});
test('toggle comment with multiple cursors selecting several nested nodes (SCSS)', () => {
const expectedContents = `
/*.one {
/* .one {
height: 42px;
.two {
@ -588,7 +588,7 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
.three {
padding: 10px;
}
}*/`;
} */`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
editor.selections = [
new Selection(1, 3, 1, 3), // cursor in the outside rule. And several cursors inside:
@ -611,14 +611,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment with multiple cursors, but no selection (SCSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/* height: 42px; */
/*.two {
/* .two {
width: 42px;
}*/
} */
.three {
/*padding: 10px;*/
/* padding: 10px; */
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
@ -641,14 +641,14 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment with multiple cursors and whole node selected (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;*/
/* height: 42px; */
/*.two {
/* .two {
width: 42px;
}*/
} */
.three {
/*padding: 10px;*/
/* padding: 10px; */
}
}`;
return withRandomFileEditor(contents, 'css', (editor, doc) => {
@ -673,11 +673,11 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment when multiple nodes are completely under single selection (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;
/* height: 42px;
.two {
width: 42px;
}*/
} */
.three {
padding: 10px;
@ -701,11 +701,11 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
test('toggle comment when multiple nodes are partially under single selection (CSS)', () => {
const expectedContents = `
.one {
/*height: 42px;
/* height: 42px;
.two {
width: 42px;
*/ }
*/ }
.three {
padding: 10px;
@ -726,4 +726,29 @@ suite('Tests for Toggle Comment action from Emmet in nested css (SCSS)', () => {
});
});
});
test('toggle comment doesn\'t fail when start and end nodes differ HTML', () => {
const contents = `
<div>
<p>Hello</p>
</div>
`;
const expectedContents = `
<!-- <div>
<p>Hello</p>
</div> -->
`;
return withRandomFileEditor(contents, 'html', (editor, doc) => {
editor.selections = [
new Selection(1, 2, 2, 9), // <div> to <p> inclusive
];
return toggleComment().then(() => {
assert.equal(doc.getText(), expectedContents);
return toggleComment().then(() => {
assert.equal(doc.getText(), contents);
return Promise.resolve();
});
});
});
});
});

View File

@ -3,148 +3,147 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
// import 'mocha';
// import * as assert from 'assert';
// import { Selection } from 'vscode';
// import { withRandomFileEditor, closeAllEditors } from './testUtils';
// import { updateImageSize } from '../updateImageSize';
import 'mocha';
import * as assert from 'assert';
import { Selection } from 'vscode';
import { withRandomFileEditor, closeAllEditors } from './testUtils';
import { updateImageSize } from '../updateImageSize';
// suite('Tests for Emmet actions on html tags', () => {
// teardown(closeAllEditors);
suite('Tests for Emmet actions on html tags', () => {
teardown(closeAllEditors);
// test('update image css with multiple cursors in css file', () => {
// const cssContents = `
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 42px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 42px;
// }
// `;
// const expectedContents = `
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 32px;
// width: 32px;
// }
// `;
// return withRandomFileEditor(cssContents, 'css', (editor, doc) => {
// editor.selections = [
// new Selection(4, 50, 4, 50),
// new Selection(7, 50, 7, 50),
// new Selection(11, 50, 11, 50)
// ];
test('update image css with multiple cursors in css file', () => {
const cssContents = `
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 42px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 42px;
}
`;
const expectedContents = `
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 1024px;
width: 1024px;
}
`;
return withRandomFileEditor(cssContents, 'css', (editor, doc) => {
editor.selections = [
new Selection(4, 50, 4, 50),
new Selection(7, 50, 7, 50),
new Selection(11, 50, 11, 50)
];
// return updateImageSize()!.then(() => {
// assert.equal(doc.getText(), expectedContents);
// return Promise.resolve();
// });
// });
// });
return updateImageSize()!.then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
// test('update image size in css in html file with multiple cursors', () => {
// const htmlWithCssContents = `
// <html>
// <style>
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 42px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 42px;
// }
// </style>
// </html>
// `;
// const expectedContents = `
// <html>
// <style>
// .one {
// margin: 10px;
// padding: 10px;
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .two {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// width: 32px;
// height: 32px;
// }
// .three {
// background-image: url(https://github.com/microsoft/vscode/blob/master/resources/linux/code.png);
// height: 32px;
// width: 32px;
// }
// </style>
// </html>
// `;
// return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => {
// editor.selections = [
// new Selection(6, 50, 6, 50),
// new Selection(9, 50, 9, 50),
// new Selection(13, 50, 13, 50)
// ];
test('update image size in css in html file with multiple cursors', () => {
const htmlWithCssContents = `
<html>
<style>
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 42px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 42px;
}
</style>
</html>
`;
const expectedContents = `
<html>
<style>
.one {
margin: 10px;
padding: 10px;
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.two {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
width: 1024px;
height: 1024px;
}
.three {
background-image: url(https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png);
height: 1024px;
width: 1024px;
}
</style>
</html>
`;
return withRandomFileEditor(htmlWithCssContents, 'html', (editor, doc) => {
editor.selections = [
new Selection(6, 50, 6, 50),
new Selection(9, 50, 9, 50),
new Selection(13, 50, 13, 50)
];
// return updateImageSize()!.then(() => {
// assert.equal(doc.getText(), expectedContents);
// return Promise.resolve();
// });
// });
// });
return updateImageSize()!.then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
// test('update image size in img tag in html file with multiple cursors', () => {
// const htmlwithimgtag = `
// <html>
// <img id="one" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" />
// <img id="two" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" width="56" />
// <img id="three" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" height="56" />
// </html>
// `;
// const expectedContents = `
// <html>
// <img id="one" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" width="32" height="32" />
// <img id="two" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" width="32" height="32" />
// <img id="three" src="https://github.com/microsoft/vscode/blob/master/resources/linux/code.png" height="32" width="32" />
// </html>
// `;
// return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => {
// editor.selections = [
// new Selection(2, 50, 2, 50),
// new Selection(3, 50, 3, 50),
// new Selection(4, 50, 4, 50)
// ];
test('update image size in img tag in html file with multiple cursors', () => {
const htmlwithimgtag = `
<html>
<img id="one" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" />
<img id="two" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" width="56" />
<img id="three" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" height="56" />
</html>
`;
const expectedContents = `
<html>
<img id="one" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" width="1024" height="1024" />
<img id="two" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" width="1024" height="1024" />
<img id="three" src="https://raw.githubusercontent.com/microsoft/vscode/master/resources/linux/code.png" height="1024" width="1024" />
</html>
`;
return withRandomFileEditor(htmlwithimgtag, 'html', (editor, doc) => {
editor.selections = [
new Selection(2, 50, 2, 50),
new Selection(3, 50, 3, 50),
new Selection(4, 50, 4, 50)
];
// return updateImageSize()!.then(() => {
// assert.equal(doc.getText(), expectedContents);
// return Promise.resolve();
// });
// });
// });
// });
return updateImageSize()!.then(() => {
assert.equal(doc.getText(), expectedContents);
return Promise.resolve();
});
});
});
});

View File

@ -9,13 +9,20 @@ import { Selection, workspace, ConfigurationTarget } from 'vscode';
import { withRandomFileEditor, closeAllEditors } from './testUtils';
import { wrapWithAbbreviation, wrapIndividualLinesWithAbbreviation } from '../abbreviationActions';
const htmlContentsForWrapTests = `
const htmlContentsForBlockWrapTests = `
<ul class="nav main">
<li class="item1">img</li>
<li class="item2">$hithere</li>
</ul>
`;
const htmlContentsForInlineWrapTests = `
<ul class="nav main">
<em class="item1">img</em>
<em class="item2">$hithere</em>
</ul>
`;
const wrapBlockElementExpected = `
<ul class="nav main">
<div>
@ -29,15 +36,19 @@ const wrapBlockElementExpected = `
const wrapInlineElementExpected = `
<ul class="nav main">
<span><li class="item1">img</li></span>
<span><li class="item2">$hithere</li></span>
<span><em class="item1">img</em></span>
<span><em class="item2">$hithere</em></span>
</ul>
`;
const wrapSnippetExpected = `
<ul class="nav main">
<a href=""><li class="item1">img</li></a>
<a href=""><li class="item2">$hithere</li></a>
<a href="">
<li class="item1">img</li>
</a>
<a href="">
<li class="item2">$hithere</li>
</a>
</ul>
`;
@ -56,10 +67,16 @@ const wrapMultiLineAbbrExpected = `
</ul>
`;
// technically a bug, but also a feature (requested behaviour)
// https://github.com/microsoft/vscode/issues/78015
const wrapInlineElementExpectedFormatFalse = `
<ul class="nav main">
<h1><li class="item1">img</li></h1>
<h1><li class="item2">$hithere</li></h1>
<h1>
<li class="item1">img</li>
</h1>
<h1>
<li class="item2">$hithere</li>
</h1>
</ul>
`;
@ -73,51 +90,51 @@ suite('Tests for Wrap with Abbreviations', () => {
const oldValueForSyntaxProfiles = workspace.getConfiguration('emmet').inspect('syntaxProfile');
test('Wrap with block element using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected);
return testWrapWithAbbreviation(multiCursors, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with inline element using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected);
return testWrapWithAbbreviation(multiCursors, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests);
});
test('Wrap with snippet using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected);
return testWrapWithAbbreviation(multiCursors, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with multi line abbreviation using multi cursor', () => {
return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected);
return testWrapWithAbbreviation(multiCursors, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with block element using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with inline element using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests);
});
test('Wrap with snippet using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with multi line abbreviation using multi cursor selection', () => {
return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected);
return testWrapWithAbbreviation(multiCursorsWithSelection, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with block element using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'div', wrapBlockElementExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with inline element using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'span', wrapInlineElementExpected, htmlContentsForInlineWrapTests);
});
test('Wrap with snippet using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'a', wrapSnippetExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with multi line abbreviation using multi cursor full line selection', () => {
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected);
return testWrapWithAbbreviation(multiCursorsWithFullLineSelection, 'ul>li', wrapMultiLineAbbrExpected, htmlContentsForBlockWrapTests);
});
test('Wrap with abbreviation and comment filter', () => {
@ -128,15 +145,31 @@ suite('Tests for Wrap with Abbreviations', () => {
`;
const expectedContents = `
<ul class="nav main">
<li class="hello">
line
</li>
<li class="hello">line</li>
<!-- /.hello -->
</ul>
`;
return testWrapWithAbbreviation([new Selection(2, 0, 2, 0)], 'li.hello|c', expectedContents, contents);
});
test('Wrap with abbreviation link', () => {
const contents = `
<ul class="nav main">
line
</ul>
`;
const expectedContents = `
<a href="https://example.com">
<div>
<ul class="nav main">
line
</ul>
</div>
</a>
`;
return testWrapWithAbbreviation([new Selection(1, 1, 1, 1)], 'a[href="https://example.com"]>div', expectedContents, contents);
});
test('Wrap with abbreviation entire node when cursor is on opening tag', () => {
const contents = `
<div class="nav main">
@ -192,8 +225,12 @@ suite('Tests for Wrap with Abbreviations', () => {
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello1"><li class="item1">This $10 is not a tabstop</li></li>
<li class="hello2"><li class="item2">hi.there</li></li>
<li class="hello1">
<li class="item1">This $10 is not a tabstop</li>
</li>
<li class="hello2">
<li class="item2">hi.there</li>
</li>
</ul>
</ul>
`;
@ -210,8 +247,12 @@ suite('Tests for Wrap with Abbreviations', () => {
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello1"><li class="item1">img</li></li>
<li class="hello2"><li class="item2">hi.there</li></li>
<li class="hello1">
<li class="item1">img</li>
</li>
<li class="hello2">
<li class="item2">hi.there</li>
</li>
</ul>
</ul>
`;
@ -228,9 +269,13 @@ suite('Tests for Wrap with Abbreviations', () => {
const wrapIndividualLinesExpected = `
<ul class="nav main">
<ul>
<li class="hello"><li class="item1">img</li></li>
<li class="hello">
<li class="item1">img</li>
</li>
<!-- /.hello -->
<li class="hello"><li class="item2">hi.there</li></li>
<li class="hello">
<li class="item2">hi.there</li>
</li>
<!-- /.hello -->
</ul>
</ul>
@ -257,9 +302,9 @@ suite('Tests for Wrap with Abbreviations', () => {
});
test('Wrap with abbreviation and format set to false', () => {
return workspace.getConfiguration('emmet').update('syntaxProfiles',{ 'html' : { 'format': false } } , ConfigurationTarget.Global).then(() => {
return testWrapWithAbbreviation(multiCursors,'h1',wrapInlineElementExpectedFormatFalse).then(() => {
return workspace.getConfiguration('emmet').update('syntaxProfiles',oldValueForSyntaxProfiles ? oldValueForSyntaxProfiles.globalValue : undefined, ConfigurationTarget.Global);
return workspace.getConfiguration('emmet').update('syntaxProfiles', { 'html' : { 'format': false } }, ConfigurationTarget.Global).then(() => {
return testWrapWithAbbreviation(multiCursors, 'h1', wrapInlineElementExpectedFormatFalse, htmlContentsForBlockWrapTests).then(() => {
return workspace.getConfiguration('emmet').update('syntaxProfiles', oldValueForSyntaxProfiles ? oldValueForSyntaxProfiles.globalValue : undefined, ConfigurationTarget.Global);
});
});
});
@ -302,28 +347,32 @@ suite('Tests for Wrap with Abbreviations', () => {
</ul>
`;
return testWrapWithAbbreviation([new Selection(2,2,3,33)], '.hello', wrapMultiLineJsxExpected, htmlContentsForWrapTests, 'jsx');
return testWrapWithAbbreviation([new Selection(2,2,3,33)], '.hello', wrapMultiLineJsxExpected, htmlContentsForBlockWrapTests, 'jsx');
});
test('Wrap individual line with abbreviation uses className for jsx files', () => {
const wrapIndividualLinesJsxExpected = `
<ul class="nav main">
<div className="hello1"><li class="item1">img</li></div>
<div className="hello2"><li class="item2">$hithere</li></div>
<div className="hello1">
<li class="item1">img</li>
</div>
<div className="hello2">
<li class="item2">$hithere</li>
</div>
</ul>
`;
return testWrapIndividualLinesWithAbbreviation([new Selection(2,2,3,33)], '.hello$*', wrapIndividualLinesJsxExpected, htmlContentsForWrapTests, 'jsx');
return testWrapIndividualLinesWithAbbreviation([new Selection(2,2,3,33)], '.hello$*', wrapIndividualLinesJsxExpected, htmlContentsForBlockWrapTests, 'jsx');
});
});
function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string = htmlContentsForWrapTests, fileExtension: string = 'html'): Thenable<any> {
function testWrapWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string, fileExtension: string = 'html'): Thenable<any> {
return withRandomFileEditor(input, fileExtension, (editor, _) => {
editor.selections = selections;
const promise = wrapWithAbbreviation({ abbreviation });
if (!promise) {
assert.equal(1, 2, 'Wrap with Abbreviation returned undefined.');
assert.equal(1, 2, 'Wrap with Abbreviation returned undefined.');
return Promise.resolve();
}
@ -334,7 +383,7 @@ function testWrapWithAbbreviation(selections: Selection[], abbreviation: string,
});
}
function testWrapIndividualLinesWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string = htmlContentsForWrapTests, fileExtension: string = 'html'): Thenable<any> {
function testWrapIndividualLinesWithAbbreviation(selections: Selection[], abbreviation: string, expectedContents: string, input: string, fileExtension: string = 'html'): Thenable<any> {
return withRandomFileEditor(input, fileExtension, (editor, _) => {
editor.selections = selections;
const promise = wrapIndividualLinesWithAbbreviation({ abbreviation });

View File

@ -4,22 +4,24 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { getNodesInBetween, getNode, getHtmlNode, parseDocument, sameNodes, isStyleSheet, validate } from './util';
import { Node, Stylesheet, Rule } from 'EmmetNode';
import { getNodesInBetween, getFlatNode, getHtmlFlatNode, sameNodes, isStyleSheet, validate, offsetRangeToVsRange, offsetRangeToSelection } from './util';
import { Node, Stylesheet, Rule } from 'EmmetFlatNode';
import parseStylesheet from '@emmetio/css-parser';
import { DocumentStreamReader } from './bufferStream';
import { getRootNode } from './parseDocument';
const startCommentStylesheet = '/*';
const endCommentStylesheet = '*/';
const startCommentHTML = '<!--';
const endCommentHTML = '-->';
let startCommentStylesheet: string;
let endCommentStylesheet: string;
let startCommentHTML: string;
let endCommentHTML: string;
export function toggleComment(): Thenable<boolean> | undefined {
if (!validate() || !vscode.window.activeTextEditor) {
return;
}
setupCommentSpacing();
const editor = vscode.window.activeTextEditor;
let rootNode = parseDocument(editor.document);
const rootNode = getRootNode(editor.document, true);
if (!rootNode) {
return;
}
@ -27,7 +29,7 @@ export function toggleComment(): Thenable<boolean> | undefined {
return editor.edit(editBuilder => {
let allEdits: vscode.TextEdit[][] = [];
editor.selections.reverse().forEach(selection => {
let edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
const edits = isStyleSheet(editor.document.languageId) ? toggleCommentStylesheet(editor.document, selection, <Stylesheet>rootNode) : toggleCommentHTML(editor.document, selection, rootNode!);
if (edits.length > 0) {
allEdits.push(edits);
}
@ -53,21 +55,25 @@ export function toggleComment(): Thenable<boolean> | undefined {
function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Node): vscode.TextEdit[] {
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
const selectionStartOffset = document.offsetAt(selectionStart);
const selectionEndOffset = document.offsetAt(selectionEnd);
const documentText = document.getText();
let startNode = getHtmlNode(document, rootNode, selectionStart, true);
let endNode = getHtmlNode(document, rootNode, selectionEnd, true);
const startNode = getHtmlFlatNode(documentText, rootNode, selectionStartOffset, true);
const endNode = getHtmlFlatNode(documentText, rootNode, selectionEndOffset, true);
if (!startNode || !endNode) {
return [];
}
if (sameNodes(startNode, endNode) && startNode.name === 'style'
&& startNode.open.end.isBefore(selectionStart)
&& startNode.close.start.isAfter(selectionEnd)) {
let buffer = new DocumentStreamReader(document, startNode.open.end, new vscode.Range(startNode.open.end, startNode.close.start));
let cssRootNode = parseStylesheet(buffer);
return toggleCommentStylesheet(selection, cssRootNode);
&& startNode.open && startNode.close
&& startNode.open.end < selectionStartOffset
&& startNode.close.start > selectionEndOffset) {
const buffer = ' '.repeat(startNode.open.end) +
documentText.substring(startNode.open.end, startNode.close.start);
const cssRootNode = parseStylesheet(buffer);
return toggleCommentStylesheet(document, selection, cssRootNode);
}
let allNodes: Node[] = getNodesInBetween(startNode, endNode);
@ -82,8 +88,8 @@ function toggleCommentHTML(document: vscode.TextDocument, selection: vscode.Sele
}
edits.push(new vscode.TextEdit(new vscode.Range(allNodes[0].start, allNodes[0].start), startCommentHTML));
edits.push(new vscode.TextEdit(new vscode.Range(allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[0].start, allNodes[0].start), startCommentHTML));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, allNodes[allNodes.length - 1].end, allNodes[allNodes.length - 1].end), endCommentHTML));
return edits;
}
@ -93,10 +99,8 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs
// If current node is commented, then uncomment and return
if (node.type === 'comment') {
unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.start, node.start.translate(0, startCommentHTML.length)), ''));
unCommentTextEdits.push(new vscode.TextEdit(new vscode.Range(node.end.translate(0, -endCommentHTML.length), node.end), ''));
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.start, node.start + startCommentHTML.length), ''));
unCommentTextEdits.push(new vscode.TextEdit(offsetRangeToVsRange(document, node.end - endCommentHTML.length, node.end), ''));
return unCommentTextEdits;
}
@ -108,32 +112,34 @@ function getRangesToUnCommentHTML(node: Node, document: vscode.TextDocument): vs
return unCommentTextEdits;
}
function toggleCommentStylesheet(selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
let selectionStart = selection.isReversed ? selection.active : selection.anchor;
let selectionEnd = selection.isReversed ? selection.anchor : selection.active;
function toggleCommentStylesheet(document: vscode.TextDocument, selection: vscode.Selection, rootNode: Stylesheet): vscode.TextEdit[] {
const selectionStart = selection.isReversed ? selection.active : selection.anchor;
const selectionEnd = selection.isReversed ? selection.anchor : selection.active;
let selectionStartOffset = document.offsetAt(selectionStart);
let selectionEndOffset = document.offsetAt(selectionEnd);
let startNode = getNode(rootNode, selectionStart, true);
let endNode = getNode(rootNode, selectionEnd, true);
const startNode = getFlatNode(rootNode, selectionStartOffset, true);
const endNode = getFlatNode(rootNode, selectionEndOffset, true);
if (!selection.isEmpty) {
selectionStart = adjustStartNodeCss(startNode, selectionStart, rootNode);
selectionEnd = adjustEndNodeCss(endNode, selectionEnd, rootNode);
selection = new vscode.Selection(selectionStart, selectionEnd);
selectionStartOffset = adjustStartNodeCss(startNode, selectionStartOffset, rootNode);
selectionEndOffset = adjustEndNodeCss(endNode, selectionEndOffset, rootNode);
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
} else if (startNode) {
selectionStart = startNode.start;
selectionEnd = startNode.end;
selection = new vscode.Selection(selectionStart, selectionEnd);
selectionStartOffset = startNode.start;
selectionEndOffset = startNode.end;
selection = offsetRangeToSelection(document, selectionStartOffset, selectionEndOffset);
}
// Uncomment the comments that intersect with the selection.
let rangesToUnComment: vscode.Range[] = [];
let edits: vscode.TextEdit[] = [];
rootNode.comments.forEach(comment => {
let commentRange = new vscode.Range(comment.start, comment.end);
const commentRange = offsetRangeToVsRange(document, comment.start, comment.end);
if (selection.intersection(commentRange)) {
rangesToUnComment.push(commentRange);
edits.push(new vscode.TextEdit(new vscode.Range(comment.start, comment.start.translate(0, startCommentStylesheet.length)), ''));
edits.push(new vscode.TextEdit(new vscode.Range(comment.end.translate(0, -endCommentStylesheet.length), comment.end), ''));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.start, comment.start + startCommentStylesheet.length), ''));
edits.push(new vscode.TextEdit(offsetRangeToVsRange(document, comment.end - endCommentStylesheet.length, comment.end), ''));
}
});
@ -145,20 +151,32 @@ function toggleCommentStylesheet(selection: vscode.Selection, rootNode: Styleshe
new vscode.TextEdit(new vscode.Range(selection.start, selection.start), startCommentStylesheet),
new vscode.TextEdit(new vscode.Range(selection.end, selection.end), endCommentStylesheet)
];
}
function adjustStartNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
function setupCommentSpacing() {
const config: boolean | undefined = vscode.workspace.getConfiguration('editor.comments').get('insertSpace');
if (config) {
startCommentStylesheet = '/* ';
endCommentStylesheet = ' */';
startCommentHTML = '<!-- ';
endCommentHTML = ' -->';
} else {
startCommentStylesheet = '/*';
endCommentStylesheet = '*/';
startCommentHTML = '<!--';
endCommentHTML = '-->';
}
}
function adjustStartNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
for (const comment of rootNode.comments) {
let commentRange = new vscode.Range(comment.start, comment.end);
if (commentRange.contains(pos)) {
return pos;
if (comment.start <= offset && offset <= comment.end) {
return offset;
}
}
if (!node) {
return pos;
return offset;
}
if (node.type === 'property') {
@ -166,32 +184,31 @@ function adjustStartNodeCss(node: Node | null, pos: vscode.Position, rootNode: S
}
const rule = <Rule>node;
if (pos.isBefore(rule.contentStartToken.end) || !rule.firstChild) {
if (offset < rule.contentStartToken.end || !rule.firstChild) {
return rule.start;
}
if (pos.isBefore(rule.firstChild.start)) {
return pos;
if (offset < rule.firstChild.start) {
return offset;
}
let newStartNode = rule.firstChild;
while (newStartNode.nextSibling && pos.isAfter(newStartNode.end)) {
while (newStartNode.nextSibling && offset > newStartNode.end) {
newStartNode = newStartNode.nextSibling;
}
return newStartNode.start;
}
function adjustEndNodeCss(node: Node | null, pos: vscode.Position, rootNode: Stylesheet): vscode.Position {
function adjustEndNodeCss(node: Node | undefined, offset: number, rootNode: Stylesheet): number {
for (const comment of rootNode.comments) {
let commentRange = new vscode.Range(comment.start, comment.end);
if (commentRange.contains(pos)) {
return pos;
if (comment.start <= offset && offset <= comment.end) {
return offset;
}
}
if (!node) {
return pos;
return offset;
}
if (node.type === 'property') {
@ -199,16 +216,16 @@ function adjustEndNodeCss(node: Node | null, pos: vscode.Position, rootNode: Sty
}
const rule = <Rule>node;
if (pos.isEqual(rule.contentEndToken.end) || !rule.firstChild) {
if (offset === rule.contentEndToken.end || !rule.firstChild) {
return rule.end;
}
if (pos.isAfter(rule.children[rule.children.length - 1].end)) {
return pos;
if (offset > rule.children[rule.children.length - 1].end) {
return offset;
}
let newEndNode = rule.children[rule.children.length - 1];
while (newEndNode.previousSibling && pos.isBefore(newEndNode.start)) {
while (newEndNode.previousSibling && offset < newEndNode.start) {
newEndNode = newEndNode.previousSibling;
}

View File

@ -0,0 +1,89 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
declare module 'EmmetFlatNode' {
export interface Node {
start: number
end: number
type: string
parent: Node | undefined
firstChild: Node | undefined
nextSibling: Node | undefined
previousSibling: Node | undefined
children: Node[]
}
export interface Token {
start: number
end: number
stream: BufferStream
toString(): string
}
export interface CssToken extends Token {
size: number
item(number: number): any
type: string
}
export interface HtmlToken extends Token {
value: string
}
export interface Attribute extends Token {
name: Token
value: Token
}
export interface HtmlNode extends Node {
name: string
open: Token | undefined
close: Token | undefined
parent: HtmlNode | undefined
firstChild: HtmlNode | undefined
nextSibling: HtmlNode | undefined
previousSibling: HtmlNode | undefined
children: HtmlNode[]
attributes: Attribute[]
}
export interface CssNode extends Node {
name: string
parent: CssNode | undefined
firstChild: CssNode | undefined
nextSibling: CssNode | undefined
previousSibling: CssNode | undefined
children: CssNode[]
}
export interface Rule extends CssNode {
selectorToken: Token
contentStartToken: Token
contentEndToken: Token
}
export interface Property extends CssNode {
valueToken: Token
separator: string
parent: Rule
terminatorToken: Token
separatorToken: Token
value: string
}
export interface Stylesheet extends Node {
comments: Token[]
}
export interface BufferStream {
peek(): number
next(): number
backUp(n: number): number
current(): string
substring(from: number, to: number): string
eat(match: any): boolean
eatWhile(match: any): boolean
}
}

View File

@ -5,8 +5,10 @@
declare module '@emmetio/css-parser' {
import { BufferStream, Stylesheet } from 'EmmetNode';
import { Stylesheet as FlatStylesheet } from 'EmmetFlatNode';
function parseStylesheet(stream: BufferStream): Stylesheet;
function parseStylesheet(stream: string): FlatStylesheet;
export default parseStylesheet;
}

View File

@ -5,8 +5,10 @@
declare module '@emmetio/html-matcher' {
import { BufferStream, HtmlNode } from 'EmmetNode';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
function parse(stream: BufferStream): HtmlNode;
function parse(stream: string): HtmlFlatNode;
export default parse;
}

View File

@ -5,26 +5,26 @@
// Based on @sergeche's work on the emmet plugin for atom
import { TextEditor, Range, Position, window, TextEdit } from 'vscode';
import { TextEditor, Position, window, TextEdit } from 'vscode';
import * as path from 'path';
import { getImageSize } from './imageSizeHelper';
import { parseDocument, getNode, iterateCSSToken, getCssPropertyFromRule, isStyleSheet, validate } from './util';
import { HtmlNode, CssToken, HtmlToken, Attribute, Property } from 'EmmetNode';
import { getFlatNode, iterateCSSToken, getCssPropertyFromRule, isStyleSheet, validate, offsetRangeToVsRange } from './util';
import { HtmlNode, CssToken, HtmlToken, Attribute, Property } from 'EmmetFlatNode';
import { locateFile } from './locateFile';
import parseStylesheet from '@emmetio/css-parser';
import { DocumentStreamReader } from './bufferStream';
import { getRootNode } from './parseDocument';
/**
* Updates size of context image in given editor
*/
export function updateImageSize() {
export function updateImageSize(): Promise<boolean> | undefined {
if (!validate() || !window.activeTextEditor) {
return;
}
const editor = window.activeTextEditor;
let allUpdatesPromise = editor.selections.reverse().map(selection => {
let position = selection.isReversed ? selection.active : selection.anchor;
const allUpdatesPromise = editor.selections.reverse().map(selection => {
const position = selection.isReversed ? selection.active : selection.anchor;
if (!isStyleSheet(editor.document.languageId)) {
return updateImageSizeHTML(editor, position);
} else {
@ -71,15 +71,19 @@ function updateImageSizeHTML(editor: TextEditor, position: Position): Promise<Te
function updateImageSizeStyleTag(editor: TextEditor, position: Position): Promise<TextEdit[]> {
const getPropertyInsiderStyleTag = (editor: TextEditor): Property | null => {
const rootNode = parseDocument(editor.document);
const currentNode = <HtmlNode>getNode(rootNode, position, true);
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const currentNode = <HtmlNode>getFlatNode(rootNode, offset, true);
if (currentNode && currentNode.name === 'style'
&& currentNode.open.end.isBefore(position)
&& currentNode.close.start.isAfter(position)) {
let buffer = new DocumentStreamReader(editor.document, currentNode.open.end, new Range(currentNode.open.end, currentNode.close.start));
let rootNode = parseStylesheet(buffer);
const node = getNode(rootNode, position, true);
return (node && node.type === 'property') ? <Property>node : null;
&& currentNode.open && currentNode.close
&& currentNode.open.end < offset
&& currentNode.close.start > offset) {
const buffer = ' '.repeat(currentNode.open.end) +
document.getText().substring(currentNode.open.end, currentNode.close.start);
const innerRootNode = parseStylesheet(buffer);
const innerNode = getFlatNode(innerRootNode, offset, true);
return (innerNode && innerNode.type === 'property') ? <Property>innerNode : null;
}
return null;
};
@ -96,7 +100,7 @@ function updateImageSizeCSSFile(editor: TextEditor, position: Position): Promise
*/
function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (editor: TextEditor, position: Position) => Property | null): Promise<TextEdit[]> {
const node = fetchNode(editor, position);
const src = node && getImageSrcCSS(node, position);
const src = node && getImageSrcCSS(editor, node, position);
if (!src) {
return Promise.reject(new Error('No valid image source'));
@ -108,7 +112,7 @@ function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (
// since this action is asynchronous, we have to ensure that editor wasnt
// changed and user didnt moved caret outside <img> node
const prop = fetchNode(editor, position);
if (prop && getImageSrcCSS(prop, position) === src) {
if (prop && getImageSrcCSS(editor, prop, position) === src) {
return updateCSSNode(editor, prop, size.width, size.height);
}
return [];
@ -121,8 +125,10 @@ function updateImageSizeCSS(editor: TextEditor, position: Position, fetchNode: (
* be found
*/
function getImageHTMLNode(editor: TextEditor, position: Position): HtmlNode | null {
const rootNode = parseDocument(editor.document);
const node = <HtmlNode>getNode(rootNode, position, true);
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const node = <HtmlNode>getFlatNode(rootNode, offset, true);
return node && node.name.toLowerCase() === 'img' ? node : null;
}
@ -132,8 +138,10 @@ function getImageHTMLNode(editor: TextEditor, position: Position): HtmlNode | nu
* be found
*/
function getImageCSSNode(editor: TextEditor, position: Position): Property | null {
const rootNode = parseDocument(editor.document);
const node = getNode(rootNode, position, true);
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const node = getFlatNode(rootNode, offset, true);
return node && node.type === 'property' ? <Property>node : null;
}
@ -152,11 +160,11 @@ function getImageSrcHTML(node: HtmlNode): string | undefined {
/**
* Returns image source from given `url()` token
*/
function getImageSrcCSS(node: Property | undefined, position: Position): string | undefined {
function getImageSrcCSS(editor: TextEditor, node: Property | undefined, position: Position): string | undefined {
if (!node) {
return;
}
const urlToken = findUrlToken(node, position);
const urlToken = findUrlToken(editor, node, position);
if (!urlToken) {
return;
}
@ -174,7 +182,12 @@ function getImageSrcCSS(node: Property | undefined, position: Position): string
* Updates size of given HTML node
*/
function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height: number): TextEdit[] {
const document = editor.document;
const srcAttr = getAttribute(node, 'src');
if (!srcAttr) {
return [];
}
const widthAttr = getAttribute(node, 'width');
const heightAttr = getAttribute(node, 'height');
const quote = getAttributeQuote(editor, srcAttr);
@ -186,15 +199,15 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height
if (!widthAttr) {
textToAdd += ` width=${quote}${width}${quote}`;
} else {
edits.push(new TextEdit(new Range(widthAttr.value.start, widthAttr.value.end), String(width)));
edits.push(new TextEdit(offsetRangeToVsRange(document, widthAttr.value.start, widthAttr.value.end), String(width)));
}
if (!heightAttr) {
textToAdd += ` height=${quote}${height}${quote}`;
} else {
edits.push(new TextEdit(new Range(heightAttr.value.start, heightAttr.value.end), String(height)));
edits.push(new TextEdit(offsetRangeToVsRange(document, heightAttr.value.start, heightAttr.value.end), String(height)));
}
if (textToAdd) {
edits.push(new TextEdit(new Range(endOfAttributes, endOfAttributes), textToAdd));
edits.push(new TextEdit(offsetRangeToVsRange(document, endOfAttributes, endOfAttributes), textToAdd));
}
return edits;
@ -204,6 +217,7 @@ function updateHTMLTag(editor: TextEditor, node: HtmlNode, width: number, height
* Updates size of given CSS rule
*/
function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, height: number): TextEdit[] {
const document = editor.document;
const rule = srcProp.parent;
const widthProp = getCssPropertyFromRule(rule, 'width');
const heightProp = getCssPropertyFromRule(rule, 'height');
@ -214,22 +228,22 @@ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, hei
let edits: TextEdit[] = [];
if (!srcProp.terminatorToken) {
edits.push(new TextEdit(new Range(srcProp.end, srcProp.end), ';'));
edits.push(new TextEdit(offsetRangeToVsRange(document, srcProp.end, srcProp.end), ';'));
}
let textToAdd = '';
if (!widthProp) {
textToAdd += `${before}width${separator}${width}px;`;
} else {
edits.push(new TextEdit(new Range(widthProp.valueToken.start, widthProp.valueToken.end), `${width}px`));
edits.push(new TextEdit(offsetRangeToVsRange(document, widthProp.valueToken.start, widthProp.valueToken.end), `${width}px`));
}
if (!heightProp) {
textToAdd += `${before}height${separator}${height}px;`;
} else {
edits.push(new TextEdit(new Range(heightProp.valueToken.start, heightProp.valueToken.end), `${height}px`));
edits.push(new TextEdit(offsetRangeToVsRange(document, heightProp.valueToken.start, heightProp.valueToken.end), `${height}px`));
}
if (textToAdd) {
edits.push(new TextEdit(new Range(srcProp.end, srcProp.end), textToAdd));
edits.push(new TextEdit(offsetRangeToVsRange(document, srcProp.end, srcProp.end), textToAdd));
}
return edits;
@ -238,9 +252,9 @@ function updateCSSNode(editor: TextEditor, srcProp: Property, width: number, hei
/**
* Returns attribute object with `attrName` name from given HTML node
*/
function getAttribute(node: HtmlNode, attrName: string): Attribute {
function getAttribute(node: HtmlNode, attrName: string): Attribute | undefined {
attrName = attrName.toLowerCase();
return node && (node.open as any).attributes.find((attr: any) => attr.name.value.toLowerCase() === attrName);
return node && node.attributes.find(attr => attr.name.toString().toLowerCase() === attrName);
}
/**
@ -248,18 +262,20 @@ function getAttribute(node: HtmlNode, attrName: string): Attribute {
* string if attribute wasnt quoted
*/
function getAttributeQuote(editor: TextEditor, attr: any): string {
const range = new Range(attr.value ? attr.value.end : attr.end, attr.end);
return range.isEmpty ? '' : editor.document.getText(range);
function getAttributeQuote(editor: TextEditor, attr: Attribute): string {
const begin = attr.value ? attr.value.end : attr.end;
const end = attr.end;
return begin === end ? '' : editor.document.getText().substring(begin, end);
}
/**
* Finds 'url' token for given `pos` point in given CSS property `node`
*/
function findUrlToken(node: Property, pos: Position): CssToken | undefined {
function findUrlToken(editor: TextEditor, node: Property, pos: Position): CssToken | undefined {
const offset = editor.document.offsetAt(pos);
for (let i = 0, il = (node as any).parsedValue.length, url; i < il; i++) {
iterateCSSToken((node as any).parsedValue[i], (token: CssToken) => {
if (token.type === 'url' && token.start.isBeforeOrEqual(pos) && token.end.isAfterOrEqual(pos)) {
if (token.type === 'url' && token.start <= offset && token.end >= offset) {
url = token;
return false;
}
@ -279,9 +295,9 @@ function findUrlToken(node: Property, pos: Position): CssToken | undefined {
function getPropertyDelimitor(editor: TextEditor, node: Property): string {
let anchor;
if (anchor = (node.previousSibling || node.parent.contentStartToken)) {
return editor.document.getText(new Range(anchor.end, node.start));
return editor.document.getText().substring(anchor.end, node.start);
} else if (anchor = (node.nextSibling || node.parent.contentEndToken)) {
return editor.document.getText(new Range(node.end, anchor.start));
return editor.document.getText().substring(node.end, anchor.start);
}
return '';

View File

@ -4,23 +4,25 @@
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getHtmlNode, parseDocument, validate } from './util';
import { getHtmlFlatNode, validate } from './util';
import { HtmlNode as HtmlFlatNode } from 'EmmetFlatNode';
import { getRootNode } from './parseDocument';
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);
const editor = vscode.window.activeTextEditor;
const document = editor.document;
const rootNode = <HtmlFlatNode>getRootNode(document, true);
if (!rootNode) {
return;
}
let rangesToUpdate: vscode.Range[] = [];
editor.selections.reverse().forEach(selection => {
rangesToUpdate = rangesToUpdate.concat(getRangesToUpdate(editor, selection, rootNode));
});
const rangesToUpdate = editor.selections.reverse()
.reduce<vscode.Range[]>((prev, selection) =>
prev.concat(getRangesToUpdate(document, selection, rootNode)), []);
return editor.edit(editBuilder => {
rangesToUpdate.forEach(range => {
@ -29,22 +31,27 @@ 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 getRangesFromNode(node: HtmlFlatNode, document: vscode.TextDocument): vscode.Range[] {
let ranges: vscode.Range[] = [];
if (node.open) {
const start = document.positionAt(node.open.start);
ranges.push(new vscode.Range(start.translate(0, 1),
start.translate(0, 1).translate(0, node.name.length)));
}
let openStart = nodeToUpdate.open.start.translate(0, 1);
let openEnd = openStart.translate(0, nodeToUpdate.name.length);
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));
if (node.close) {
const endTagStart = document.positionAt(node.close.start);
const end = document.positionAt(node.close.end);
ranges.push(new vscode.Range(endTagStart.translate(0, 2), end.translate(0, -1)));
}
return ranges;
}
function getRangesToUpdate(document: vscode.TextDocument, selection: vscode.Selection, rootNode: HtmlFlatNode): vscode.Range[] {
const documentText = document.getText();
const offset = document.offsetAt(selection.start);
const nodeToUpdate = getHtmlFlatNode(documentText, rootNode, offset, true);
if (!nodeToUpdate) {
return [];
}
return getRangesFromNode(nodeToUpdate, document);
}

View File

@ -6,10 +6,11 @@
import * as vscode from 'vscode';
import parse from '@emmetio/html-matcher';
import parseStylesheet from '@emmetio/css-parser';
import { Node, HtmlNode, CssToken, Property, Rule, Stylesheet } from 'EmmetNode';
import { Node as FlatNode, HtmlNode as HtmlFlatNode, Property as FlatProperty, Rule as FlatRule, CssToken as FlatCssToken, Stylesheet as FlatStylesheet } from 'EmmetFlatNode';
import { DocumentStreamReader } from './bufferStream';
import * as EmmetHelper from 'vscode-emmet-helper';
import { TextDocument as LSTextDocument } from 'vscode-html-languageservice';
import { TextDocument as LSTextDocument } from 'vscode-languageserver-textdocument';
import { getRootNode } from './parseDocument';
let _emmetHelper: typeof EmmetHelper;
let _currentExtensionsPath: string | undefined = undefined;
@ -21,7 +22,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
@ -130,28 +130,13 @@ 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;
}
/**
* Parses the given document using emmet parsing modules
*/
export function parseDocument(document: vscode.TextDocument, showError: boolean = true): Node | undefined {
let parseContent = isStyleSheet(document.languageId) ? parseStylesheet : parse;
try {
return parseContent(new DocumentStreamReader(document));
} catch (e) {
if (showError) {
vscode.window.showErrorMessage('Emmet: Failed to parse the file');
}
}
return undefined;
}
const closeBrace = 125;
const openBrace = 123;
const slash = 47;
@ -163,39 +148,41 @@ const star = 42;
* @param document vscode.TextDocument
* @param position vscode.Position
*/
export function parsePartialStylesheet(document: vscode.TextDocument, position: vscode.Position): Stylesheet | undefined {
export function parsePartialStylesheet(document: vscode.TextDocument, position: vscode.Position): FlatStylesheet | undefined {
const isCSS = document.languageId === 'css';
let startPosition = new vscode.Position(0, 0);
let endPosition = new vscode.Position(document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length);
const limitCharacter = document.offsetAt(position) - 5000;
const limitPosition = limitCharacter > 0 ? document.positionAt(limitCharacter) : startPosition;
const stream = new DocumentStreamReader(document, position);
const positionOffset = document.offsetAt(position);
let startOffset = 0;
let endOffset = document.getText().length;
const limitCharacter = positionOffset - 5000;
const limitOffset = limitCharacter > 0 ? limitCharacter : startOffset;
const stream = new DocumentStreamReader(document, positionOffset);
function findOpeningCommentBeforePosition(pos: vscode.Position): vscode.Position | undefined {
let text = document.getText(new vscode.Range(0, 0, pos.line, pos.character));
function findOpeningCommentBeforePosition(pos: number): number | undefined {
const text = document.getText().substring(0, pos);
let offset = text.lastIndexOf('/*');
if (offset === -1) {
return;
}
return document.positionAt(offset);
return offset;
}
function findClosingCommentAfterPosition(pos: vscode.Position): vscode.Position | undefined {
let text = document.getText(new vscode.Range(pos.line, pos.character, document.lineCount - 1, document.lineAt(document.lineCount - 1).text.length));
function findClosingCommentAfterPosition(pos: number): number | undefined {
const text = document.getText().substring(pos);
let offset = text.indexOf('*/');
if (offset === -1) {
return;
}
offset += 2 + document.offsetAt(pos);
return document.positionAt(offset);
offset += 2 + pos;
return offset;
}
function consumeLineCommentBackwards() {
if (!isCSS && currentLine !== stream.pos.line) {
currentLine = stream.pos.line;
let startLineComment = document.lineAt(currentLine).text.indexOf('//');
const posLineNumber = document.positionAt(stream.pos).line;
if (!isCSS && currentLine !== posLineNumber) {
currentLine = posLineNumber;
const startLineComment = document.lineAt(currentLine).text.indexOf('//');
if (startLineComment > -1) {
stream.pos = new vscode.Position(currentLine, startLineComment);
stream.pos = document.offsetAt(new vscode.Position(currentLine, startLineComment));
}
}
}
@ -203,7 +190,7 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
function consumeBlockCommentBackwards() {
if (stream.peek() === slash) {
if (stream.backUp(1) === star) {
stream.pos = findOpeningCommentBeforePosition(stream.pos) || startPosition;
stream.pos = findOpeningCommentBeforePosition(stream.pos) ?? startOffset;
} else {
stream.next();
}
@ -213,9 +200,10 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
function consumeCommentForwards() {
if (stream.eat(slash)) {
if (stream.eat(slash) && !isCSS) {
stream.pos = new vscode.Position(stream.pos.line + 1, 0);
const posLineNumber = document.positionAt(stream.pos).line;
stream.pos = document.offsetAt(new vscode.Position(posLineNumber + 1, 0));
} else if (stream.eat(star)) {
stream.pos = findClosingCommentAfterPosition(stream.pos) || endPosition;
stream.pos = findClosingCommentAfterPosition(stream.pos) ?? endOffset;
}
}
}
@ -230,10 +218,10 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
}
if (!stream.eof()) {
endPosition = stream.pos;
endOffset = stream.pos;
}
stream.pos = position;
stream.pos = positionOffset;
let openBracesToFind = 1;
let currentLine = position.line;
let exit = false;
@ -249,7 +237,7 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
case closeBrace:
if (isCSS) {
stream.next();
startPosition = stream.pos;
startOffset = stream.pos;
exit = true;
} else {
openBracesToFind++;
@ -262,17 +250,17 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
break;
}
if (position.line - stream.pos.line > 100 || stream.pos.isBeforeOrEqual(limitPosition)) {
if (position.line - document.positionAt(stream.pos).line > 100
|| stream.pos <= limitOffset) {
exit = true;
}
}
// We are at an opening brace. We need to include its selector.
currentLine = stream.pos.line;
currentLine = document.positionAt(stream.pos).line;
openBracesToFind = 0;
let foundSelector = false;
while (!exit && !stream.sof() && !foundSelector && openBracesToFind >= 0) {
consumeLineCommentBackwards();
const ch = stream.backUp(1);
@ -298,12 +286,13 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
}
if (!stream.sof() && foundSelector) {
startPosition = stream.pos;
startOffset = stream.pos;
}
}
try {
return parseStylesheet(new DocumentStreamReader(document, startPosition, new vscode.Range(startPosition, endPosition)));
const buffer = ' '.repeat(startOffset) + document.getText().substring(startOffset, endOffset);
return parseStylesheet(buffer);
} catch (e) {
return;
}
@ -312,80 +301,101 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position:
/**
* Returns node corresponding to given position in the given root node
*/
export function getNode(root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean) {
export function getFlatNode(root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): FlatNode | undefined {
if (!root) {
return null;
return;
}
let currentNode = root.firstChild;
let foundNode: Node | null = null;
while (currentNode) {
const nodeStart: vscode.Position = currentNode.start;
const nodeEnd: vscode.Position = currentNode.end;
if ((nodeStart.isBefore(position) && nodeEnd.isAfter(position))
|| (includeNodeBoundary && (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)))) {
foundNode = currentNode;
// Dig deeper
currentNode = currentNode.firstChild;
} else {
currentNode = currentNode.nextSibling;
function getFlatNodeChild(child: FlatNode | undefined): FlatNode | undefined {
if (!child) {
return;
}
const nodeStart = child.start;
const nodeEnd = child.end;
if ((nodeStart < offset && nodeEnd > offset)
|| (includeNodeBoundary && nodeStart <= offset && nodeEnd >= offset)) {
return getFlatNodeChildren(child.children) ?? child;
}
else if ('close' in <any>child) {
// We have an HTML node in this case.
// In case this node is an invalid unpaired HTML node,
// we still want to search its children
const htmlChild = <HtmlFlatNode>child;
if (htmlChild.open && !htmlChild.close) {
return getFlatNodeChildren(htmlChild.children);
}
}
return;
}
return foundNode;
function getFlatNodeChildren(children: FlatNode[]): FlatNode | undefined {
for (let i = 0; i < children.length; i++) {
const foundChild = getFlatNodeChild(children[i]);
if (foundChild) {
return foundChild;
}
}
return;
}
return getFlatNodeChildren(root.children);
}
export const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template', 'text/template', 'text/ng-template'];
/**
* Returns HTML node corresponding to given position in the given root node
* Finds the HTML node within an HTML document at a given position
* If position is inside a script tag of type template, then it will be parsed to find the inner HTML node as well
*/
export function getHtmlNode(document: vscode.TextDocument, root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean): HtmlNode | undefined {
let currentNode = <HtmlNode>getNode(root, position, includeNodeBoundary);
export function getHtmlFlatNode(documentText: string, root: FlatNode | undefined, offset: number, includeNodeBoundary: boolean): HtmlFlatNode | undefined {
const currentNode: HtmlFlatNode | undefined = <HtmlFlatNode | undefined>getFlatNode(root, offset, includeNodeBoundary);
if (!currentNode) { return; }
const isTemplateScript = currentNode.name === 'script' &&
(currentNode.attributes &&
currentNode.attributes.some(x => x.name.toString() === 'type'
&& allowedMimeTypesInScriptTag.indexOf(x.value.toString()) > -1));
if (isTemplateScript && currentNode.close &&
(position.isAfter(currentNode.open.end) && position.isBefore(currentNode.close.start))) {
let buffer = new DocumentStreamReader(document, currentNode.open.end, new vscode.Range(currentNode.open.end, currentNode.close.start));
try {
let scriptInnerNodes = parse(buffer);
currentNode = <HtmlNode>getNode(scriptInnerNodes, position, includeNodeBoundary) || currentNode;
} catch (e) { }
&& allowedMimeTypesInScriptTag.includes(x.value.toString())));
if (isTemplateScript
&& currentNode.open
&& offset > currentNode.open.end
&& (!currentNode.close || offset < currentNode.close.start)) {
// blank out the rest of the document and search for the node within
const beforePadding = ' '.repeat(currentNode.open.end);
const endToUse = currentNode.close ? currentNode.close.start : currentNode.end;
const scriptBodyText = beforePadding + documentText.substring(currentNode.open.end, endToUse);
const innerRoot: HtmlFlatNode = parse(scriptBodyText);
const scriptBodyNode = getHtmlFlatNode(scriptBodyText, innerRoot, offset, includeNodeBoundary);
if (scriptBodyNode) {
scriptBodyNode.parent = currentNode;
currentNode.children.push(scriptBodyNode);
return scriptBodyNode;
}
}
return currentNode;
}
/**
* Returns inner range of an html node.
*/
export function getInnerRange(currentNode: HtmlNode): vscode.Range | undefined {
if (!currentNode.close) {
return undefined;
}
return new vscode.Range(currentNode.open.end, currentNode.close.start);
export function offsetRangeToSelection(document: vscode.TextDocument, start: number, end: number): vscode.Selection {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Selection(startPos, endPos);
}
export function offsetRangeToVsRange(document: vscode.TextDocument, start: number, end: number): vscode.Range {
const startPos = document.positionAt(start);
const endPos = document.positionAt(end);
return new vscode.Range(startPos, endPos);
}
/**
* Returns the deepest non comment node under given node
*/
export function getDeepestNode(node: Node | undefined): Node | undefined {
export function getDeepestFlatNode(node: FlatNode | undefined): FlatNode | undefined {
if (!node || !node.children || node.children.length === 0 || !node.children.find(x => x.type !== 'comment')) {
return node;
}
for (let i = node.children.length - 1; i >= 0; i--) {
if (node.children[i].type !== 'comment') {
return getDeepestNode(node.children[i]);
return getDeepestFlatNode(node.children[i]);
}
}
return undefined;
@ -467,7 +477,7 @@ export function findPrevWord(propertyValue: string, pos: number): [number | unde
return [newSelectionStart, newSelectionEnd];
}
export function getNodesInBetween(node1: Node, node2: Node): Node[] {
export function getNodesInBetween(node1: FlatNode, node2: FlatNode): FlatNode[] {
// Same node
if (sameNodes(node1, node2)) {
return [node1];
@ -476,41 +486,46 @@ export function getNodesInBetween(node1: Node, node2: Node): Node[] {
// Not siblings
if (!sameNodes(node1.parent, node2.parent)) {
// node2 is ancestor of node1
if (node2.start.isBefore(node1.start)) {
if (node2.start < node1.start) {
return [node2];
}
// node1 is ancestor of node2
if (node2.start.isBefore(node1.end)) {
if (node2.start < node1.end) {
return [node1];
}
// Get the highest ancestor of node1 that should be commented
while (node1.parent && node1.parent.end.isBefore(node2.start)) {
while (node1.parent && node1.parent.end < node2.start) {
node1 = node1.parent;
}
// Get the highest ancestor of node2 that should be commented
while (node2.parent && node2.parent.start.isAfter(node1.start)) {
while (node2.parent && node2.parent.start > node1.start) {
node2 = node2.parent;
}
}
const siblings: Node[] = [];
let currentNode = node1;
const siblings: FlatNode[] = [];
let currentNode: FlatNode | undefined = node1;
const position = node2.end;
while (currentNode && position.isAfter(currentNode.start)) {
while (currentNode && position > currentNode.start) {
siblings.push(currentNode);
currentNode = currentNode.nextSibling;
}
return siblings;
}
export function sameNodes(node1: Node, node2: Node): boolean {
export function sameNodes(node1: FlatNode | undefined, node2: FlatNode | undefined): boolean {
// return true if they're both undefined
if (!node1 && !node2) {
return true;
}
// return false if only one of them is undefined
if (!node1 || !node2) {
return false;
}
return (<vscode.Position>node1.start).isEqual(node2.start) && (<vscode.Position>node1.end).isEqual(node2.end);
return node1.start === node2.start && node1.end === node2.end;
}
export function getEmmetConfiguration(syntax: string) {
@ -546,7 +561,7 @@ export function getEmmetConfiguration(syntax: string) {
* Itereates by each child, as well as nested child's children, in their order
* and invokes `fn` for each. If `fn` function returns `false`, iteration stops
*/
export function iterateCSSToken(token: CssToken, fn: (x: any) => any): boolean {
export function iterateCSSToken(token: FlatCssToken, fn: (x: any) => any): boolean {
for (let i = 0, il = token.size; i < il; i++) {
if (fn(token.item(i)) === false || iterateCSSToken(token.item(i), fn) === false) {
return false;
@ -558,51 +573,53 @@ export function iterateCSSToken(token: CssToken, fn: (x: any) => any): boolean {
/**
* Returns `name` CSS property from given `rule`
*/
export function getCssPropertyFromRule(rule: Rule, name: string): Property | undefined {
return rule.children.find(node => node.type === 'property' && node.name === name) as Property;
export function getCssPropertyFromRule(rule: FlatRule, name: string): FlatProperty | undefined {
return rule.children.find(node => node.type === 'property' && node.name === name) as FlatProperty;
}
/**
* Returns css property under caret in given editor or `null` if such node cannot
* be found
*/
export function getCssPropertyFromDocument(editor: vscode.TextEditor, position: vscode.Position): Property | null {
const rootNode = parseDocument(editor.document);
const node = getNode(rootNode, position, true);
export function getCssPropertyFromDocument(editor: vscode.TextEditor, position: vscode.Position): FlatProperty | null {
const document = editor.document;
const rootNode = getRootNode(document, true);
const offset = document.offsetAt(position);
const node = getFlatNode(rootNode, offset, true);
if (isStyleSheet(editor.document.languageId)) {
return node && node.type === 'property' ? <Property>node : null;
return node && node.type === 'property' ? <FlatProperty>node : null;
}
let htmlNode = <HtmlNode>node;
const htmlNode = <HtmlFlatNode>node;
if (htmlNode
&& htmlNode.name === 'style'
&& htmlNode.open.end.isBefore(position)
&& htmlNode.close.start.isAfter(position)) {
let buffer = new DocumentStreamReader(editor.document, htmlNode.start, new vscode.Range(htmlNode.start, htmlNode.end));
let rootNode = parseStylesheet(buffer);
const node = getNode(rootNode, position, true);
return (node && node.type === 'property') ? <Property>node : null;
&& htmlNode.open && htmlNode.close
&& htmlNode.open.end < offset
&& htmlNode.close.start > offset) {
const buffer = ' '.repeat(htmlNode.start) +
document.getText().substring(htmlNode.start, htmlNode.end);
const innerRootNode = parseStylesheet(buffer);
const innerNode = getFlatNode(innerRootNode, offset, true);
return (innerNode && innerNode.type === 'property') ? <FlatProperty>innerNode : null;
}
return null;
}
export function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNode: Node | null, position: vscode.Position): Node | undefined {
export function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNode: FlatNode | undefined, position: vscode.Position): FlatNode | undefined {
if (!currentNode) {
return;
}
const currentHtmlNode = <HtmlNode>currentNode;
if (currentHtmlNode && currentHtmlNode.close) {
const innerRange = getInnerRange(currentHtmlNode);
if (innerRange && innerRange.contains(position)) {
const currentHtmlNode = <HtmlFlatNode>currentNode;
if (currentHtmlNode && currentHtmlNode.open && currentHtmlNode.close) {
const offset = document.offsetAt(position);
if (currentHtmlNode.open.end <= offset && offset <= currentHtmlNode.close.start) {
if (currentHtmlNode.name === 'style'
&& currentHtmlNode.open.end.isBefore(position)
&& currentHtmlNode.close.start.isAfter(position)
) {
let buffer = new DocumentStreamReader(document, currentHtmlNode.open.end, new vscode.Range(currentHtmlNode.open.end, currentHtmlNode.close.start));
&& currentHtmlNode.open.end < offset
&& currentHtmlNode.close.start > offset) {
const buffer = ' '.repeat(currentHtmlNode.open.end) + document.getText().substring(currentHtmlNode.open.end, currentHtmlNode.close.start);
return parseStylesheet(buffer);
}
}
@ -610,34 +627,17 @@ export function getEmbeddedCssNodeIfAny(document: vscode.TextDocument, currentNo
return;
}
export function isStyleAttribute(currentNode: Node | null, position: vscode.Position): boolean {
export function isStyleAttribute(currentNode: FlatNode | undefined, offset: number): boolean {
if (!currentNode) {
return false;
}
const currentHtmlNode = <HtmlNode>currentNode;
const currentHtmlNode = <HtmlFlatNode>currentNode;
const index = (currentHtmlNode.attributes || []).findIndex(x => x.name.toString() === 'style');
if (index === -1) {
return false;
}
const styleAttribute = currentHtmlNode.attributes[index];
return position.isAfterOrEqual(styleAttribute.value.start) && position.isBeforeOrEqual(styleAttribute.value.end);
}
export function trimQuotes(s: string) {
if (s.length <= 1) {
return s.replace(/['"]/, '');
}
if (s[0] === `'` || s[0] === `"`) {
s = s.slice(1);
}
if (s[s.length - 1] === `'` || s[s.length - 1] === `"`) {
s = s.slice(0, -1);
}
return s;
return offset >= styleAttribute.value.start && offset <= styleAttribute.value.end;
}
export function isNumber(obj: any): obj is number {
@ -653,3 +653,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']
};
}