diff --git a/platform/util/resources/misc/registry.properties b/platform/util/resources/misc/registry.properties
index c27a3f1b19ea..cd86bd76f7f3 100644
--- a/platform/util/resources/misc/registry.properties
+++ b/platform/util/resources/misc/registry.properties
@@ -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)
diff --git a/xml/impl/resources/liveTemplates/zen_html.xml b/xml/impl/resources/liveTemplates/zen_html.xml
index ec5a2d388562..50c8714e5bc0 100644
--- a/xml/impl/resources/liveTemplates/zen_html.xml
+++ b/xml/impl/resources/liveTemplates/zen_html.xml
@@ -655,20 +655,18 @@
-
+
-
-
+
-
@@ -778,30 +776,26 @@
-
-
+
-
-
+
-
-
+
-
-
+
@@ -878,9 +872,8 @@
-
-
+
@@ -909,9 +902,8 @@
-
-
+
@@ -1000,9 +992,8 @@
-
-
+
diff --git a/xml/impl/src/com/intellij/application/options/emmet/EmmetOptions.java b/xml/impl/src/com/intellij/application/options/emmet/EmmetOptions.java
index aacd93a7f753..b603ccb1da20 100644
--- a/xml/impl/src/com/intellij/application/options/emmet/EmmetOptions.java
+++ b/xml/impl/src/com/intellij/application/options/emmet/EmmetOptions.java
@@ -33,8 +33,6 @@ public class EmmetOptions implements PersistentStateComponent {
private boolean myEmmetEnabled = true;
private int myEmmetExpandShortcut = TemplateSettings.TAB_CHAR;
private boolean myPreviewEnabled = false;
- private boolean myCompactBooleanAllowed = true;
- private Set myBooleanAttributes = ContainerUtil.newHashSet("contenteditable", "seamless");
private Set myFiltersEnabledByDefault = ContainerUtil.newHashSet();
@NotNull
@@ -66,22 +64,6 @@ public class EmmetOptions implements PersistentStateComponent {
myPreviewEnabled = previewEnabled;
}
- public boolean isCompactBooleanAllowed() {
- return myCompactBooleanAllowed;
- }
-
- public void setCompactBooleanAllowed(boolean compactBooleanAllowed) {
- myCompactBooleanAllowed = compactBooleanAllowed;
- }
-
- public Set getBooleanAttributes() {
- return myBooleanAttributes;
- }
-
- public void setBooleanAttributes(@NotNull Set booleanAttributes) {
- myBooleanAttributes = booleanAttributes;
- }
-
public boolean isEmmetEnabled() {
return myEmmetEnabled;
}
diff --git a/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.form b/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.form
index 01d1c6a2433a..9cd1ee62449f 100644
--- a/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.form
+++ b/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.form
@@ -8,7 +8,7 @@
-
+
@@ -38,7 +38,7 @@
-
+
@@ -51,14 +51,6 @@
-
-
-
-
-
-
-
-
diff --git a/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.java b/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.java
index 0b39891fb40e..764d74f94375 100644
--- a/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.java
+++ b/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.java
@@ -40,7 +40,6 @@ public class XmlEmmetConfigurable implements UnnamedConfigurable, Disposable, Co
private JBCheckBox myEnablePreviewJBCheckBox;
private CheckBoxList 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 enabledByDefault = emmetOptions.getFiltersEnabledByDefault();
for (ZenCodingFilter filter : ZenCodingFilter.getInstances()) {
diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/GenerationNode.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/GenerationNode.java
index e99663eaefa7..4e03afc67148 100644
--- a/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/GenerationNode.java
+++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/nodes/GenerationNode.java
@@ -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 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 attributes) {
for (XmlAttribute attribute : attributes) {
@@ -526,7 +540,7 @@ public class GenerationNode extends UserDataHolderBase {
private static XmlAttribute findEmptyAttribute(@NotNull List 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;
}
}
diff --git a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java
index b7fb7f2a0870..e859b8548aaa 100644
--- a/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java
+++ b/xml/xml-analysis-impl/src/com/intellij/codeInspection/htmlInspections/HtmlUnknownBooleanAttributeInspectionBase.java
@@ -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),
diff --git a/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java b/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java
index 1e90360c24a4..28aea7442b07 100644
--- a/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java
+++ b/xml/xml-psi-impl/src/com/intellij/xml/util/HtmlUtil.java
@@ -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;
}