Emmet: use html settings on handling boolean attribute

This commit is contained in:
Alexander Zolotov
2014-12-24 19:44:38 +03:00
parent b2e27cfbfc
commit 22f3dc074e
8 changed files with 102 additions and 93 deletions
@@ -470,6 +470,7 @@ spy.js.realtime.evaluation=false
spy.js.realtime.evaluation.description=Enables spy-js autocomplete and realtime evaluation
new.css.schema.enabled=true
html.prefer.short.notation.of.boolean.attributes=true
editor.disable.rtl=false
editor.disable.rtl.description=Disables RTL support in editor (which is broken now anyway)
+18 -27
View File
@@ -655,20 +655,18 @@
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;select name=&quot;...&quot; id=&quot;...&quot; disabled=&quot;...&quot;&gt;...&lt;/select&gt;" name="select:disabled" toReformat="true"
toShortenFQNames="true" value="&lt;select name=&quot;$VAR0$&quot; id=&quot;$VAR1$&quot; disabled=&quot;$VAR2$&quot;&gt;$END$&lt;/select&gt;">
<template description="&lt;select name=&quot;...&quot; id=&quot;...&quot; disabled&gt;...&lt;/select&gt;" name="select:disabled" toReformat="true"
toShortenFQNames="true" value="&lt;select name=&quot;$VAR0$&quot; id=&quot;$VAR1$&quot; disabled&gt;$END$&lt;/select&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR1"/>
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR2"/>
<context>
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;select name=&quot;...&quot; id=&quot;...&quot; disabled=&quot;...&quot;&gt;...&lt;/select&gt;" name="select:d" toReformat="true"
toShortenFQNames="true" value="&lt;select name=&quot;$VAR0$&quot; id=&quot;$VAR1$&quot; disabled=&quot;$VAR2$&quot;&gt;$END$&lt;/select&gt;">
<template description="&lt;select name=&quot;...&quot; id=&quot;...&quot; disabled&gt;...&lt;/select&gt;" name="select:d" toReformat="true"
toShortenFQNames="true" value="&lt;select name=&quot;$VAR0$&quot; id=&quot;$VAR1$&quot; disabled&gt;$END$&lt;/select&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR1"/>
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR2"/>
<context>
<option name="HTML_TEXT" value="true"/>
</context>
@@ -778,30 +776,26 @@
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;button disabled=&quot;&quot;&gt;...&lt;/button&gt;" name="button:disabled" toReformat="true" toShortenFQNames="true"
value="&lt;button disabled=&quot;$VAR0$&quot;&gt;$END$&lt;/button&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<template description="&lt;button disabled&gt;...&lt;/button&gt;" name="button:disabled" toReformat="true" toShortenFQNames="true"
value="&lt;button disabled&gt;$END$&lt;/button&gt;">
<context>
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;button disabled=&quot;&quot;&gt;...&lt;/button&gt;" name="button:d" toReformat="true" toShortenFQNames="true"
value="&lt;button disabled=&quot;$VAR0$&quot;&gt;$END$&lt;/button&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<template description="&lt;button disabled&gt;...&lt;/button&gt;" name="button:d" toReformat="true" toShortenFQNames="true"
value="&lt;button disabled&gt;$END$&lt;/button&gt;">
<context>
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;fieldset disabled=&quot;&quot;&gt;...&lt;/fieldset&gt;" name="fieldset:disabled" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled=&quot;$VAR0$&quot;&gt;$END$&lt;/fieldset&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<template description="&lt;fieldset disabled&gt;...&lt;/fieldset&gt;" name="fieldset:disabled" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled&gt;$END$&lt;/fieldset&gt;">
<context>
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;fieldset disabled=&quot;&quot;&gt;...&lt;/fieldset&gt;" name="fieldset:d" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled=&quot;$VAR0$&quot;&gt;$END$&lt;/fieldset&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<template description="&lt;fieldset disabled&gt;...&lt;/fieldset&gt;" name="fieldset:d" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled&gt;$END$&lt;/fieldset&gt;">
<context>
<option name="HTML_TEXT" value="true"/>
</context>
@@ -878,9 +872,8 @@
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;fieldset disabled=&quot;&quot;&gt;...&lt;/fieldset&gt;" name="fst:d" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled=&quot;$VAR0$&quot;&gt;$END$&lt;/fieldset&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<template description="&lt;fieldset disabled&gt;...&lt;/fieldset&gt;" name="fst:d" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled&gt;$END$&lt;/fieldset&gt;">
<context>
<option name="HTML_TEXT" value="true"/>
</context>
@@ -909,9 +902,8 @@
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;button disabled=&quot;&quot;&gt;...&lt;/button&gt;" name="btn:d" toReformat="true" toShortenFQNames="true"
value="&lt;button disabled=&quot;$VAR0$&quot;&gt;$END$&lt;/button&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<template description="&lt;button disabled&gt;...&lt;/button&gt;" name="btn:d" toReformat="true" toShortenFQNames="true"
value="&lt;button disabled&gt;$END$&lt;/button&gt;">
<context>
<option name="HTML_TEXT" value="true"/>
</context>
@@ -1000,9 +992,8 @@
<option name="HTML_TEXT" value="true"/>
</context>
</template>
<template description="&lt;fieldset disabled=&quot;&quot;&gt;...&lt;/fieldset&gt;" name="fset:d" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled=&quot;$VAR0$&quot;&gt;$END$&lt;/fieldset&gt;">
<variable alwaysStopAt="true" defaultValue="&quot;&quot;" expression="" name="VAR0"/>
<template description="&lt;fieldset disabled&gt;...&lt;/fieldset&gt;" name="fset:d" toReformat="true" toShortenFQNames="true"
value="&lt;fieldset disabled&gt;$END$&lt;/fieldset&gt;">
<context>
<option name="HTML_TEXT" value="true"/>
</context>
@@ -33,8 +33,6 @@ public class EmmetOptions implements PersistentStateComponent<EmmetOptions> {
private boolean myEmmetEnabled = true;
private int myEmmetExpandShortcut = TemplateSettings.TAB_CHAR;
private boolean myPreviewEnabled = false;
private boolean myCompactBooleanAllowed = true;
private Set<String> myBooleanAttributes = ContainerUtil.newHashSet("contenteditable", "seamless");
private Set<String> myFiltersEnabledByDefault = ContainerUtil.newHashSet();
@NotNull
@@ -66,22 +64,6 @@ public class EmmetOptions implements PersistentStateComponent<EmmetOptions> {
myPreviewEnabled = previewEnabled;
}
public boolean isCompactBooleanAllowed() {
return myCompactBooleanAllowed;
}
public void setCompactBooleanAllowed(boolean compactBooleanAllowed) {
myCompactBooleanAllowed = compactBooleanAllowed;
}
public Set<String> getBooleanAttributes() {
return myBooleanAttributes;
}
public void setBooleanAttributes(@NotNull Set<String> booleanAttributes) {
myBooleanAttributes = booleanAttributes;
}
public boolean isEmmetEnabled() {
return myEmmetEnabled;
}
@@ -8,7 +8,7 @@
<properties/>
<border type="none"/>
<children>
<grid id="369dc" layout-manager="GridLayoutManager" row-count="4" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="369dc" layout-manager="GridLayoutManager" row-count="3" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="1" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -38,7 +38,7 @@
<grid id="cdbb7" binding="myFiltersListPanel" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="1" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
@@ -51,14 +51,6 @@
</component>
</children>
</grid>
<component id="3bd4e" class="com.intellij.ui.components.JBCheckBox" binding="myAllowCompactBooleanAttributesJBCheckBox" default-binding="true">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="9" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/XmlBundle" key="emmet.allow.compact.boolean.attributes"/>
</properties>
</component>
</children>
</grid>
</children>
@@ -40,7 +40,6 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
private JBCheckBox myEnablePreviewJBCheckBox;
private CheckBoxList<ZenCodingFilter> myFiltersCheckBoxList;
private JPanel myFiltersListPanel;
private JBCheckBox myAllowCompactBooleanAttributesJBCheckBox;
public XmlEmmetConfigurable() {
myEnableEmmetJBCheckBox.addActionListener(new ActionListener() {
@@ -48,7 +47,6 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
public void actionPerformed(ActionEvent e) {
boolean selected = myEnableEmmetJBCheckBox.isSelected();
myEnablePreviewJBCheckBox.setEnabled(selected);
myAllowCompactBooleanAttributesJBCheckBox.setEnabled(selected);
myFiltersCheckBoxList.setEnabled(selected);
}
});
@@ -76,7 +74,6 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
EmmetOptions emmetOptions = EmmetOptions.getInstance();
return emmetOptions.isEmmetEnabled() != myEnableEmmetJBCheckBox.isSelected() ||
emmetOptions.isPreviewEnabled() != myEnablePreviewJBCheckBox.isSelected() ||
emmetOptions.isCompactBooleanAllowed() != myAllowCompactBooleanAttributesJBCheckBox.isSelected() ||
!emmetOptions.getFiltersEnabledByDefault().equals(enabledFilters());
}
@@ -85,7 +82,6 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
EmmetOptions emmetOptions = EmmetOptions.getInstance();
emmetOptions.setEmmetEnabled(myEnableEmmetJBCheckBox.isSelected());
emmetOptions.setPreviewEnabled(myEnablePreviewJBCheckBox.isSelected());
emmetOptions.setCompactBooleanAllowed(myAllowCompactBooleanAttributesJBCheckBox.isSelected());
emmetOptions.setFiltersEnabledByDefault(enabledFilters());
}
@@ -96,8 +92,6 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
myEnableEmmetJBCheckBox.setSelected(emmetOptions.isEmmetEnabled());
myEnablePreviewJBCheckBox.setEnabled(emmetOptions.isEmmetEnabled());
myEnablePreviewJBCheckBox.setSelected(emmetOptions.isPreviewEnabled());
myAllowCompactBooleanAttributesJBCheckBox.setEnabled(emmetOptions.isEmmetEnabled());
myAllowCompactBooleanAttributesJBCheckBox.setSelected(emmetOptions.isCompactBooleanAllowed());
Set<String> enabledByDefault = emmetOptions.getFiltersEnabledByDefault();
for (ZenCodingFilter filter : ZenCodingFilter.getInstances()) {
@@ -16,7 +16,6 @@
package com.intellij.codeInsight.template.emmet.nodes;
import com.google.common.base.Strings;
import com.intellij.application.options.emmet.EmmetOptions;
import com.intellij.codeInsight.template.CustomTemplateCallback;
import com.intellij.codeInsight.template.LiveTemplateBuilder;
import com.intellij.codeInsight.template.emmet.XmlEmmetParser;
@@ -51,11 +50,13 @@ import com.intellij.util.LocalTimeCounter;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.util.HtmlUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.regex.Pattern;
import static com.google.common.collect.Lists.newArrayList;
@@ -74,6 +75,7 @@ public class GenerationNode extends UserDataHolderBase {
private GenerationNode myParent;
private boolean myContainsSurroundedTextMarker = false;
public static final Pattern ATTRIBUTE_VARIABLE_PATTERN = Pattern.compile("\\$[A-z_0-9]+\\$");
public GenerationNode(TemplateToken templateToken,
int numberInIteration,
@@ -444,38 +446,42 @@ public class GenerationNode extends UserDataHolderBase {
}
}
for (Map.Entry<String, String> attribute : attributes.entrySet()) {
final String attributeName = attribute.getKey();
final XmlAttribute xmlAttribute = tag.getAttribute(attributeName);
if (xmlAttribute != null) {
final String attributeValue = attribute.getValue();
if (ZenCodingUtil.containsSurroundedTextMarker(attributeValue)) {
myContainsSurroundedTextMarker = true;
}
for (XmlAttribute xmlAttribute : tag.getAttributes()) {
final String attributeName = xmlAttribute.getName();
final XmlAttributeValue xmlAttributeValueElement = xmlAttribute.getValueElement();
if (xmlAttributeValueElement != null && !attributes.containsKey(attributeName)) {
continue;
}
final EmmetOptions emmetOptions = EmmetOptions.getInstance();
if (isBooleanAttribute(attributeName, attributeValue)) {
if (emmetOptions.isCompactBooleanAllowed() && ZenCodingUtil.isHtml(callback)) {
final XmlAttributeValue valueElement = xmlAttribute.getValueElement();
if (valueElement != null) {
final PsiElement prevSibling = valueElement.getPrevSibling();
if (prevSibling != null && prevSibling.textMatches("=")) {
xmlAttribute.deleteChildRange(prevSibling, valueElement);
}
String attributeValue = StringUtil.notNullize(attributes.get(attributeName), StringUtil.notNullize(xmlAttribute.getValue()));
if (ZenCodingUtil.containsSurroundedTextMarker(attributeValue)) {
myContainsSurroundedTextMarker = true;
}
if (isBooleanAttribute(attributeValue, xmlAttribute, ZenCodingUtil.isHtml(callback))) {
if (HtmlUtil.isShortNotationOfBooleanAttributePreferred()) {
if (xmlAttributeValueElement != null) {
final PsiElement prevSibling = xmlAttributeValueElement.getPrevSibling();
if (prevSibling != null && prevSibling.textMatches("=")) {
xmlAttribute.deleteChildRange(prevSibling, xmlAttributeValueElement);
}
}
else {
tag.setAttribute(attributeName, attributeName);
}
}
else {
tag.setAttribute(attributeName,
Strings.isNullOrEmpty(attributeValue)
? "$" + prepareVariableName(attributeName) + "$"
: ZenCodingUtil.getValue(attributeValue, myNumberInIteration, myTotalIterations, mySurroundedText));
if (xmlAttributeValueElement == null) {
xmlAttribute.delete();
}
tag.setAttribute(attributeName, attributeName);
}
}
else {
if (xmlAttributeValueElement == null) {
xmlAttribute.delete();
}
tag.setAttribute(attributeName, StringUtil.isEmpty(attributeValue)
? "$" + prepareVariableName(attributeName) + "$"
: ZenCodingUtil.getValue(attributeValue, myNumberInIteration, myTotalIterations, mySurroundedText));
}
}
// remove all implicit and default attributes
@@ -487,11 +493,15 @@ public class GenerationNode extends UserDataHolderBase {
}
}
private static boolean isBooleanAttribute(String name, String value) {
if (value.equals(XmlEmmetParser.BOOLEAN_ATTRIBUTE_VALUE)) {
private static boolean isBooleanAttribute(@Nullable String attributeValue, @NotNull XmlAttribute xmlAttribute, boolean isHtml) {
if (XmlEmmetParser.BOOLEAN_ATTRIBUTE_VALUE.equals(attributeValue)) {
return true;
}
return Strings.isNullOrEmpty(value) && EmmetOptions.getInstance().getBooleanAttributes().contains(name.toLowerCase(Locale.US));
if (isHtml) {
final XmlAttributeDescriptor descriptor = xmlAttribute.getDescriptor();
return descriptor != null && HtmlUtil.isBooleanAttribute(descriptor, xmlAttribute);
}
return false;
}
private static boolean isDefaultAttribute(String xmlAttributeLocalName) {
@@ -502,6 +512,10 @@ public class GenerationNode extends UserDataHolderBase {
return StringUtil.startsWithChar(xmlAttributeLocalName, '!');
}
private static boolean isEmptyValue(String attributeValue) {
return StringUtil.isEmpty(attributeValue) || ATTRIBUTE_VARIABLE_PATTERN.matcher(attributeValue).matches();
}
@Nullable
private static XmlAttribute findDefaultAttribute(@NotNull List<XmlAttribute> attributes) {
for (XmlAttribute attribute : attributes) {
@@ -526,7 +540,7 @@ public class GenerationNode extends UserDataHolderBase {
private static XmlAttribute findEmptyAttribute(@NotNull List<XmlAttribute> attributes) {
for (XmlAttribute attribute : attributes) {
final String attributeValue = attribute.getValue();
if (StringUtil.isEmpty(attributeValue) || StringUtil.startsWithChar(attributeValue, '$') && StringUtil.endsWithChar(attributeValue, '$')) {
if (isEmptyValue(attributeValue)) {
return attribute;
}
}
@@ -86,7 +86,7 @@ public abstract class HtmlUnknownBooleanAttributeInspectionBase extends HtmlUnkn
XmlAttributeDescriptor attributeDescriptor = elementDescriptor.getAttributeDescriptor(attribute);
if (attributeDescriptor != null) {
String name = attribute.getName();
if (!HtmlUtil.isBooleanAttribute(attributeDescriptor) && (!isCustomValuesEnabled() || !isCustomValue(name))) {
if (!HtmlUtil.isBooleanAttribute(attributeDescriptor, null) && (!isCustomValuesEnabled() || !isCustomValue(name))) {
LocalQuickFix[] quickFixes = new LocalQuickFix[]{
new AddCustomHtmlElementIntentionAction(BOOLEAN_ATTRIBUTE_KEY, name, XmlBundle.message("add.custom.html.boolean.attribute", name)),
XmlQuickFixFactory.getInstance().addAttributeValueFix(attribute),
@@ -23,9 +23,13 @@ import com.intellij.javaee.ExternalResourceManagerEx;
import com.intellij.lang.Language;
import com.intellij.lang.html.HTMLLanguage;
import com.intellij.lang.xhtml.XHTMLLanguage;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.util.registry.RegistryValue;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.CharsetToolkit;
import com.intellij.openapi.vfs.VirtualFile;
@@ -55,6 +59,7 @@ import gnu.trove.THashSet;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.nio.charset.Charset;
import java.util.*;
@@ -237,8 +242,25 @@ public class HtmlUtil {
public static String[] getHtmlTagNames() {
return HtmlDescriptorsTable.getHtmlTagNames();
}
public static boolean isShortNotationOfBooleanAttributePreferred() {
return Registry.is("html.prefer.short.notation.of.boolean.attributes", true);
}
@TestOnly
public static void setShortNotationOfBooleanAttributeIsPreferred(boolean value, Disposable parent) {
final boolean oldValue = isShortNotationOfBooleanAttributePreferred();
final RegistryValue registryValue = Registry.get("html.prefer.short.notation.of.boolean.attributes");
registryValue.setValue(value);
Disposer.register(parent, new Disposable() {
@Override
public void dispose() {
registryValue.setValue(oldValue);
}
});
}
public static boolean isBooleanAttribute(@NotNull XmlAttributeDescriptor descriptor) {
public static boolean isBooleanAttribute(@NotNull XmlAttributeDescriptor descriptor, @Nullable XmlElement context) {
final String[] values = descriptor.getEnumeratedValues();
if (values == null) {
return false;
@@ -250,6 +272,19 @@ public class HtmlUtil {
else if (values.length == 1) {
return descriptor.getName().equals(values[0]);
}
return context != null && isCustomBooleanAttribute(descriptor.getName(), context);
}
public static boolean isCustomBooleanAttribute(@NotNull String attributeName, @NotNull XmlElement context) {
final String entitiesString = getEntitiesString(context, XmlEntitiesInspection.BOOLEAN_ATTRIBUTE_SHORT_NAME);
if (entitiesString != null) {
StringTokenizer tokenizer = new StringTokenizer(entitiesString, ",");
while (tokenizer.hasMoreElements()) {
if (tokenizer.nextToken().equalsIgnoreCase(attributeName)) {
return true;
}
}
}
return false;
}