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:
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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');
|
||||
|
@ -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';
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
46
lib/vscode/extensions/emmet/src/parseDocument.ts
Normal file
46
lib/vscode/extensions/emmet/src/parseDocument.ts
Normal 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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
@ -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 */{
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
});
|
||||
|
@ -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>
|
||||
`;
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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 });
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
89
lib/vscode/extensions/emmet/src/typings/EmmetFlatNode.d.ts
vendored
Normal file
89
lib/vscode/extensions/emmet/src/typings/EmmetFlatNode.d.ts
vendored
Normal 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
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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 wasn’t
|
||||
// changed and user didn’t 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 wasn’t 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 '';
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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']
|
||||
};
|
||||
}
|
||||
|
Reference in New Issue
Block a user