提交 cc84951f 编写于 作者: J Jackson Kearl 提交者: Ramya Rao

Allow Emmet "Go to matching pair" to work inside script tags (#51173)

* Allow Emmet "Go to matching pair" to work inside script tags

* Refactor template script detection and node extraction.

* Futher refactoring of Emmet template identification logic.

* Emmet: select entire script tag when unable to idnetify an inner element

* Remove redundant nullity check
上级 1e8d42b5
......@@ -5,11 +5,10 @@
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 } from './util';
import { getEmmetHelper, getNode, getInnerRange, getMappingForIncludedLanguages, parseDocument, validate, getEmmetConfiguration, isStyleSheet, getEmmetMode, parsePartialStylesheet, isStyleAttribute, getEmbeddedCssNodeIfAny, isTemplateScript } from './util';
const trimRegex = /[\u00a0]*[\d|#|\-|\*|\u2022]+\.?/;
const hexColorRegex = /^#[\d,a-f,A-F]{0,6}$/;
const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template', 'text/template'];
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',
......@@ -445,9 +444,7 @@ export function isValidLocationForEmmetAbbreviation(document: vscode.TextDocumen
if (currentHtmlNode) {
if (currentHtmlNode.name === 'script') {
return (currentHtmlNode.attributes
&& currentHtmlNode.attributes.some(x => x.name.toString() === 'type'
&& allowedMimeTypesInScriptTag.indexOf(x.value.toString()) > -1));
return isTemplateScript(currentHtmlNode);
}
const innerRange = getInnerRange(currentHtmlNode);
......
......@@ -5,18 +5,17 @@
import * as vscode from 'vscode';
import { HtmlNode } from 'EmmetNode';
import { getNode, parseDocument, validate } from './util';
import { getHtmlNode, parseDocument, validate } from './util';
export function matchTag() {
if (!validate(false) || !vscode.window.activeTextEditor) {
return;
}
const editor = vscode.window.activeTextEditor;
let rootNode = <HtmlNode>parseDocument(editor.document);
if (!rootNode) {
return;
}
const editor = vscode.window.activeTextEditor;
let rootNode: HtmlNode = <HtmlNode>parseDocument(editor.document);
if (!rootNode) { return; }
let updatedSelections: vscode.Selection[] = [];
editor.selections.forEach(selection => {
......@@ -32,10 +31,8 @@ export function matchTag() {
}
function getUpdatedSelections(editor: vscode.TextEditor, position: vscode.Position, rootNode: HtmlNode): vscode.Selection | undefined {
let currentNode = <HtmlNode>getNode(rootNode, position, true);
if (!currentNode) {
return;
}
let currentNode = getHtmlNode(editor.document, rootNode, position, 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))) {
......@@ -45,6 +42,4 @@ function getUpdatedSelections(editor: vscode.TextEditor, position: vscode.Positi
// 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);
}
}
\ No newline at end of file
......@@ -117,7 +117,7 @@ suite('Tests for Emmet actions on html tags', () => {
</div>
`;
const oldValueForSyntaxProfiles = workspace.getConfiguration('emmet').inspect('syntaxProfiles');
return workspace.getConfiguration('emmet').update('syntaxProfiles', {jsx: {selfClosingStyle: 'xhtml'}}, ConfigurationTarget.Global).then(() =>{
return workspace.getConfiguration('emmet').update('syntaxProfiles', { jsx: { selfClosingStyle: 'xhtml' } }, ConfigurationTarget.Global).then(() => {
return withRandomFileEditor(contents, 'jsx', (editor, doc) => {
editor.selections = [
new Selection(3, 17, 3, 17), // join tag
......@@ -156,6 +156,32 @@ suite('Tests for Emmet actions on html tags', () => {
});
});
test('match tag with template scripts', () => {
let templateScript = `
<script type="text/template">
<div>
Hello
</div>
</script>`;
return withRandomFileEditor(templateScript, 'html', (editor, doc) => {
editor.selections = [
new Selection(2, 2, 2, 2), // just before div tag starts, i.e before <
];
matchTag();
editor.selections.forEach(selection => {
assert.equal(selection.active.line, 4);
assert.equal(selection.active.character, 4);
assert.equal(selection.anchor.line, 4);
assert.equal(selection.anchor.character, 4);
});
return Promise.resolve();
});
});
test('merge lines of tag with children when empty selection', () => {
const expectedContents = `
<div class="hello">
......
......@@ -47,6 +47,8 @@ export const LANGUAGE_MODES: any = {
'typescriptreact': ['!', '.', '}', '*', '$', ']', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
};
const allowedMimeTypesInScriptTag = ['text/html', 'text/plain', 'text/x-template', 'text/template'];
const emmetModes = ['html', 'pug', 'slim', 'haml', 'xml', 'xsl', 'jsx', 'css', 'scss', 'sass', 'less', 'stylus'];
// Explicitly map languages that have built-in grammar in VS Code to their parent language
......@@ -308,6 +310,24 @@ export function getNode(root: Node | undefined, position: vscode.Position, inclu
return foundNode;
}
export function getHtmlNode(document: vscode.TextDocument, root: Node | undefined, position: vscode.Position, includeNodeBoundary: boolean = false): HtmlNode | undefined {
let currentNode = <HtmlNode>getNode(root, position, true);
if (!currentNode) { return; }
if (isTemplateScript(currentNode) && 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, true) || currentNode;
} catch (e) { }
}
return currentNode;
}
/**
* Returns inner range of an html node.
* @param currentNode
......@@ -562,4 +582,11 @@ export function isStyleAttribute(currentNode: Node | null, position: vscode.Posi
}
const styleAttribute = currentHtmlNode.attributes[index];
return position.isAfterOrEqual(styleAttribute.value.start) && position.isBeforeOrEqual(styleAttribute.value.end);
}
\ No newline at end of file
}
export function isTemplateScript(currentNode: HtmlNode): boolean {
return currentNode.name === 'script' &&
(currentNode.attributes &&
currentNode.attributes.some(x => x.name.toString() === 'type'
&& allowedMimeTypesInScriptTag.indexOf(x.value.toString()) > -1));
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册