Emmet identify CDATA for wrap, fixes #123136

This commit is contained in:
Raymond Zhao 2021-05-20 13:20:39 -07:00
parent 97740a7d25
commit 9627b4ea63
No known key found for this signature in database
GPG key ID: 50B2567EE00718B6
3 changed files with 104 additions and 6 deletions

View file

@ -6,7 +6,7 @@
import * as vscode from 'vscode';
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, isOffsetInsideOpenOrCloseTag } from './util';
import { getEmmetHelper, getFlatNode, getHtmlFlatNode, getMappingForIncludedLanguages, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, allowedMimeTypesInScriptTag, toLSTextDocument, isOffsetInsideOpenOrCloseTag } from './util';
import { getRootNode as parseDocument } from './parseDocument';
const localize = nls.loadMessageBundle();
@ -56,7 +56,8 @@ export async function wrapWithAbbreviation(args: any): Promise<boolean> {
let { start, end } = rangeToReplace;
const startOffset = document.offsetAt(start);
const startNode = getFlatNode(rootNode, startOffset, true);
const documentText = document.getText();
const startNode = getHtmlFlatNode(documentText, rootNode, startOffset, true);
if (startNode && isOffsetInsideOpenOrCloseTag(startNode, startOffset)) {
start = document.positionAt(startNode.start);
const nodeEndPosition = document.positionAt(startNode.end);
@ -64,7 +65,7 @@ export async function wrapWithAbbreviation(args: any): Promise<boolean> {
}
const endOffset = document.offsetAt(end);
const endNode = getFlatNode(rootNode, endOffset, true);
const endNode = getHtmlFlatNode(documentText, rootNode, endOffset, true);
if (endNode && isOffsetInsideOpenOrCloseTag(endNode, endOffset)) {
const nodeStartPosition = document.positionAt(endNode.start);
start = nodeStartPosition.isBefore(start) ? nodeStartPosition : start;

View file

@ -219,6 +219,79 @@ suite('Tests for Wrap with Abbreviations', () => {
return testWrapWithAbbreviation([new Selection(3, 2, 3, 2)], 'div', expectedContents, contents);
});
test('Wrap with abbreviation inner node in cdata', () => {
const contents = `
<div class="nav main">
<![CDATA[
<div>
<p>Test 1</p>
</div>
<p>Test 2</p>
]]>
hello
</div>
`;
const expectedContents = `
<div class="nav main">
<![CDATA[
<div>
<p>Test 1</p>
</div>
<div>
<p>Test 2</p>
</div>
]]>
hello
</div>
`;
return testWrapWithAbbreviation([new Selection(6, 5, 6, 5)], 'div', expectedContents, contents);
});
test('Wrap with abbreviation inner node in script in cdata', () => {
const contents = `
<div class="nav main">
<![CDATA[
<script type="text/plain">
<p>Test 1</p>
</script>
<p>Test 2</p>
]]>
hello
</div>
`;
const expectedContents = `
<div class="nav main">
<![CDATA[
<script type="text/plain">
<div>
<p>Test 1</p>
</div>
</script>
<p>Test 2</p>
]]>
hello
</div>
`;
return testWrapWithAbbreviation([new Selection(4, 10, 4, 10)], 'div', expectedContents, contents);
});
test('Wrap with abbreviation inner node in cdata one-liner', () => {
const contents = `
<div class="nav main">
<![CDATA[<p>Test here</p>]]>
hello
</div>
`;
// this result occurs because no selection on the open/close p tag was given
const expectedContents = `
<div class="nav main">
<div><![CDATA[<p>Test here</p>]]></div>
hello
</div>
`;
return testWrapWithAbbreviation([new Selection(2, 15, 2, 15)], 'div', expectedContents, contents);
});
test('Wrap with multiline abbreviation doesnt add extra spaces', () => {
// Issue #29898
const contents = `

View file

@ -381,13 +381,19 @@ export function getHtmlFlatNode(documentText: string, root: FlatNode | undefined
// If the currentNode is a script one, first set up its subtree and then find HTML node.
if (currentNode.name === 'script' && currentNode.children.length === 0) {
setUpScriptNodeSubtree(documentText, currentNode);
currentNode = <HtmlFlatNode | undefined>getFlatNode(currentNode, offset, includeNodeBoundary) ?? currentNode;
const scriptNodeBody = setupScriptNodeSubtree(documentText, currentNode);
if (scriptNodeBody) {
currentNode = getHtmlFlatNode(scriptNodeBody, currentNode, offset, includeNodeBoundary) ?? currentNode;
}
}
else if (currentNode.type === 'cdata') {
const cdataBody = setupCdataNodeSubtree(documentText, currentNode);
currentNode = getHtmlFlatNode(cdataBody, currentNode, offset, includeNodeBoundary) ?? currentNode;
}
return currentNode;
}
export function setUpScriptNodeSubtree(documentText: string, scriptNode: HtmlFlatNode): void {
export function setupScriptNodeSubtree(documentText: string, scriptNode: HtmlFlatNode): string {
const isTemplateScript = scriptNode.name === 'script' &&
(scriptNode.attributes &&
scriptNode.attributes.some(x => x.name.toString() === 'type'
@ -403,7 +409,25 @@ export function setUpScriptNodeSubtree(documentText: string, scriptNode: HtmlFla
scriptNode.children.push(child);
child.parent = scriptNode;
});
return scriptBodyText;
}
return '';
}
export function setupCdataNodeSubtree(documentText: string, cdataNode: HtmlFlatNode): string {
// blank out the rest of the document and generate the subtree.
const cdataStart = '<![CDATA[';
const cdataEnd = ']]>';
const startToUse = cdataNode.start + cdataStart.length;
const endToUse = cdataNode.end - cdataEnd.length;
const beforePadding = ' '.repeat(startToUse);
const cdataBody = beforePadding + documentText.substring(startToUse, endToUse);
const innerRoot: HtmlFlatNode = parse(cdataBody);
innerRoot.children.forEach(child => {
cdataNode.children.push(child);
child.parent = cdataNode;
});
return cdataBody;
}
export function isOffsetInsideOpenOrCloseTag(node: FlatNode, offset: number): boolean {