WEB-20704 Emmet: allow to configure bem separators

This commit is contained in:
Alexander Zolotov
2016-05-19 21:27:13 +03:00
parent bc72fce6d4
commit 03f41ede3b
6 changed files with 224 additions and 126 deletions
@@ -230,10 +230,14 @@ emmet.update.tag.title=Update tag with Emmet
emmet.configuration.title=Emmet
emmet.enable.label=&Enable XML/HTML Emmet
emmet.filters.enabled.by.default=Filters enabled by default
emmet.bem.title=BEM
emmet.enable.preview=Enable &abbreviation preview
emmet.expand.abbreviation.with=Expand &abbreviation with
emmet.href.autodetect=Enable automatic &URL recognition while wrapping text with <a> tag
emmet.add.edit.point.at.the.end.of.template=Add edit &point at the end of template
emmet.bem.class.name.element.separator.label=Class name's element separator:
emmet.bem.class.name.modifier.separator.label=Class name's modifier separator:
emmet.bem.short.element.prefix.label=Short element prefix:
title.cannot.create.html.file=Cannot create HTML file
new.html.file.action=HTML File
@@ -21,6 +21,7 @@ import com.intellij.openapi.components.PersistentStateComponent;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.components.State;
import com.intellij.openapi.components.Storage;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.xmlb.XmlSerializerUtil;
import org.jetbrains.annotations.NotNull;
@@ -36,10 +37,15 @@ public class EmmetOptions implements PersistentStateComponent<EmmetOptions> {
private boolean myEmmetEnabled = true;
private int myEmmetExpandShortcut = TemplateSettings.TAB_CHAR;
private boolean myPreviewEnabled = false;
@NotNull
private Set<String> myFiltersEnabledByDefault = ContainerUtil.newHashSet();
private boolean myHrefAutoDetectEnabled = true;
private boolean myAddEditPointAtTheEndOfTemplate = false;
@NotNull private String myBemElementSeparator = "__";
@NotNull private String myBemModifierSeparator = "_";
@NotNull private String myBemShortElementPrefix = "-";
@NotNull
public Set<String> getFiltersEnabledByDefault() {
return myFiltersEnabledByDefault;
@@ -76,7 +82,7 @@ public class EmmetOptions implements PersistentStateComponent<EmmetOptions> {
public void setEmmetEnabled(boolean emmetEnabled) {
myEmmetEnabled = emmetEnabled;
}
public void setHrefAutoDetectEnabled(boolean hrefAutoDetectEnabled) {
myHrefAutoDetectEnabled = hrefAutoDetectEnabled;
}
@@ -93,6 +99,33 @@ public class EmmetOptions implements PersistentStateComponent<EmmetOptions> {
myAddEditPointAtTheEndOfTemplate = addEditPointAtTheEndOfTemplate;
}
@NotNull
public String getBemElementSeparator() {
return myBemElementSeparator;
}
public void setBemElementSeparator(@Nullable String bemElementSeparator) {
myBemElementSeparator = StringUtil.notNullize(bemElementSeparator);
}
@NotNull
public String getBemModifierSeparator() {
return myBemModifierSeparator;
}
public void setBemModifierSeparator(@Nullable String bemModifierSeparator) {
myBemModifierSeparator = StringUtil.notNullize(bemModifierSeparator);
}
@NotNull
public String getBemShortElementPrefix() {
return myBemShortElementPrefix;
}
public void setBemShortElementPrefix(@Nullable String bemShortElementPrefix) {
myBemShortElementPrefix = StringUtil.notNullize(bemShortElementPrefix);
}
@Nullable
@Override
public EmmetOptions getState() {
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.application.options.emmet.XmlEmmetConfigurable">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="5" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="6" 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>
<xy x="20" y="20" width="999" height="140"/>
<xy x="20" y="20" width="1797" height="282"/>
</constraints>
<properties/>
<border type="none"/>
@@ -27,7 +27,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="4" 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="5" 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"/>
@@ -49,6 +49,74 @@
<text resource-bundle="messages/XmlBundle" key="emmet.add.edit.point.at.the.end.of.template"/>
</properties>
</component>
<grid id="fb640" binding="myBemPanel" layout-manager="GridLayoutManager" row-count="4" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="4" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none" title="BEM"/>
<children>
<component id="48744" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/XmlBundle" key="emmet.bem.class.name.element.separator.label"/>
</properties>
</component>
<hspacer id="2356c">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
<vspacer id="4ace0">
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="3083" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/XmlBundle" key="emmet.bem.class.name.modifier.separator.label"/>
</properties>
</component>
<component id="9b3ce" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="2" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/XmlBundle" key="emmet.bem.short.element.prefix.label"/>
</properties>
</component>
<component id="525" class="com.intellij.ui.components.JBTextField" binding="myBemElementSeparatorTextField">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="50" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="fddbf" class="com.intellij.ui.components.JBTextField" binding="myBemModifierSeparatorTextField">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="50" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
<component id="2659b" class="com.intellij.ui.components.JBTextField" binding="myBemShortElementPrefixTextField">
<constraints>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="8" fill="0" indent="0" use-parent-layout="false">
<preferred-size width="50" height="-1"/>
</grid>
</constraints>
<properties/>
</component>
</children>
</grid>
</children>
</grid>
</form>
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,7 +23,10 @@ import com.intellij.openapi.options.SearchableConfigurable;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.IdeBorderFactory;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.JBUI;
import com.intellij.util.ui.UIUtil;
import com.intellij.xml.XmlBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
@@ -44,6 +47,10 @@ public class XmlEmmetConfigurable implements SearchableConfigurable, Disposable,
private JPanel myFiltersListPanel;
private JBCheckBox myEnableHrefAutodetectJBCheckBox;
private JBCheckBox myAddEditPointAtTheEndOfTemplateJBCheckBox;
private JBTextField myBemElementSeparatorTextField;
private JBTextField myBemModifierSeparatorTextField;
private JBTextField myBemShortElementPrefixTextField;
private JPanel myBemPanel;
private Map<String, JBCheckBox> myFilterCheckboxes = ContainerUtil.newHashMap();
@@ -53,23 +60,26 @@ public class XmlEmmetConfigurable implements SearchableConfigurable, Disposable,
public void actionPerformed(ActionEvent e) {
boolean selected = myEnableEmmetJBCheckBox.isSelected();
myEnablePreviewJBCheckBox.setEnabled(selected);
myAddEditPointAtTheEndOfTemplateJBCheckBox.setEnabled(selected);
myFiltersListPanel.setEnabled(selected);
myEnableHrefAutodetectJBCheckBox.setEnabled(selected);
myAddEditPointAtTheEndOfTemplateJBCheckBox.setEnabled(selected);
UIUtil.setEnabled(myBemPanel, selected, true);
for (JBCheckBox checkBox : myFilterCheckboxes.values()) {
checkBox.setEnabled(selected);
}
}
});
myFiltersListPanel.setBorder(IdeBorderFactory.createTitledBorder(XmlBundle.message("emmet.filters.enabled.by.default")));
myBemPanel.setBorder(IdeBorderFactory.createTitledBorder(XmlBundle.message("emmet.bem.title")));
createFiltersCheckboxes();
}
public void createFiltersCheckboxes() {
final List<ZenCodingFilter> filters = ZenCodingFilter.getInstances();
final GridBagLayout layoutManager = new GridBagLayout();
final GridBagConstraints constraints = new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
new Insets(0, 0, 0, 0), 0, 0);
final GridBagConstraints constraints = new GridBagConstraints(0, 0, 1, 1, 1, 1, GridBagConstraints.NORTHWEST, GridBagConstraints.NONE,
JBUI.emptyInsets(), 0, 0);
myFiltersListPanel.setLayout(layoutManager);
for (int i = 0; i < filters.size(); i++) {
ZenCodingFilter filter = filters.get(i);
@@ -100,7 +110,10 @@ public class XmlEmmetConfigurable implements SearchableConfigurable, Disposable,
emmetOptions.isPreviewEnabled() != myEnablePreviewJBCheckBox.isSelected() ||
emmetOptions.isHrefAutoDetectEnabled() != myEnableHrefAutodetectJBCheckBox.isSelected() ||
emmetOptions.isAddEditPointAtTheEndOfTemplate() != myAddEditPointAtTheEndOfTemplateJBCheckBox.isSelected() ||
!emmetOptions.getFiltersEnabledByDefault().equals(enabledFilters());
!emmetOptions.getFiltersEnabledByDefault().equals(enabledFilters()) ||
!emmetOptions.getBemElementSeparator().equals(myBemElementSeparatorTextField.getText()) ||
!emmetOptions.getBemModifierSeparator().equals(myBemModifierSeparatorTextField.getText()) ||
!emmetOptions.getBemShortElementPrefix().equals(myBemShortElementPrefixTextField.getText());
}
@Override
@@ -111,9 +124,11 @@ public class XmlEmmetConfigurable implements SearchableConfigurable, Disposable,
emmetOptions.setHrefAutoDetectEnabled(myEnableHrefAutodetectJBCheckBox.isSelected());
emmetOptions.setAddEditPointAtTheEndOfTemplate(myAddEditPointAtTheEndOfTemplateJBCheckBox.isSelected());
emmetOptions.setFiltersEnabledByDefault(enabledFilters());
emmetOptions.setBemElementSeparator(myBemElementSeparatorTextField.getText());
emmetOptions.setBemModifierSeparator(myBemModifierSeparatorTextField.getText());
emmetOptions.setBemShortElementPrefix(myBemShortElementPrefixTextField.getText());
}
@Override
public void reset() {
EmmetOptions emmetOptions = EmmetOptions.getInstance();
@@ -124,6 +139,10 @@ public class XmlEmmetConfigurable implements SearchableConfigurable, Disposable,
myEnableHrefAutodetectJBCheckBox.setSelected(emmetOptions.isHrefAutoDetectEnabled());
myAddEditPointAtTheEndOfTemplateJBCheckBox.setEnabled(emmetOptions.isEmmetEnabled());
myAddEditPointAtTheEndOfTemplateJBCheckBox.setSelected(emmetOptions.isAddEditPointAtTheEndOfTemplate());
myBemElementSeparatorTextField.setText(emmetOptions.getBemElementSeparator());
myBemModifierSeparatorTextField.setText(emmetOptions.getBemModifierSeparator());
myBemShortElementPrefixTextField.setText(emmetOptions.getBemShortElementPrefix());
Set<String> enabledByDefault = emmetOptions.getFiltersEnabledByDefault();
for (ZenCodingFilter filter : ZenCodingFilter.getInstances()) {
@@ -15,77 +15,32 @@
*/
package com.intellij.codeInsight.template.emmet.filters;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableSet;
import com.intellij.application.options.emmet.EmmetOptions;
import com.intellij.codeInsight.template.emmet.nodes.GenerationNode;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xml.util.HtmlUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import static com.google.common.collect.Iterables.*;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Lists.newLinkedList;
/**
* Bem filter for emmet support.
* See the original source code here: https://github.com/emmetio/emmet/blob/master/javascript/filters/bem.js
* And documentation here: http://docs.emmet.io/filters/bem/
*/
public class BemEmmetFilter extends ZenCodingFilter {
public static final String SUFFIX = "bem";
private static final String SUFFIX = "bem";
private static final Key<BemState> BEM_STATE = Key.create("BEM_STATE");
private static final String ELEMENT_SEPARATOR = "__";
private static final String MODIFIER_SEPARATOR = "_";
private static final String SHORT_ELEMENT_PREFIX = "-";
private static final Joiner ELEMENTS_JOINER = Joiner.on(ELEMENT_SEPARATOR).skipNulls();
private static final Splitter ELEMENTS_SPLITTER = Splitter.on(ELEMENT_SEPARATOR);
private static final Splitter MODIFIERS_SPLITTER = Splitter.on(MODIFIER_SEPARATOR).limit(2);
private static final Splitter CLASS_NAME_SPLITTER = Splitter.on(' ').trimResults().omitEmptyStrings();
private static final Joiner CLASS_NAME_JOINER = Joiner.on(' ');
private static final Function<String, String> CLASS_NAME_NORMALIZER = new Function<String, String>() {
@NotNull
@Override
public String apply(@NotNull String input) {
if (!input.startsWith(SHORT_ELEMENT_PREFIX)) {
return input;
}
StringBuilder result = new StringBuilder();
while (input.startsWith(SHORT_ELEMENT_PREFIX)) {
input = input.substring(SHORT_ELEMENT_PREFIX.length());
result.append(ELEMENT_SEPARATOR);
}
return result.append(input).toString();
}
};
private static final Predicate<String> BLOCK_NAME_PREDICATE = new Predicate<String>() {
@Override
public boolean apply(String className) {
return Pattern.compile("^[A-z]-").matcher(className).matches();
}
};
private static final Predicate<String> STARTS_WITH_LETTER = new Predicate<String>() {
@Override
public boolean apply(@Nullable String input) {
return input != null && input.length() > 0 && Character.isLetter(input.charAt(0));
}
};
private static final Pattern BLOCK_NAME_PATTERN = Pattern.compile("^[A-z]-");
@NotNull
@Override
@@ -109,30 +64,37 @@ public class BemEmmetFilter extends ZenCodingFilter {
public GenerationNode filterNode(@NotNull final GenerationNode node) {
final Map<String, String> attributes = node.getTemplateToken().getAttributes();
String classValue = attributes.get(HtmlUtil.CLASS_ATTRIBUTE_NAME);
if (classValue != null) {
Iterable<String> classNames = extractClasses(classValue);
EmmetOptions emmetOptions = EmmetOptions.getInstance();
if (classValue != null && emmetOptions != null) {
String elementSeparator = emmetOptions.getBemElementSeparator();
String modifierSeparator = emmetOptions.getBemModifierSeparator();
String shortElementPrefix = emmetOptions.getBemShortElementPrefix();
List<String> classNames = ContainerUtil.map(HtmlUtil.splitClassNames(classValue), (s) -> {
return normalizeClassName(s, elementSeparator, shortElementPrefix);
});
BEM_STATE.set(node, new BemState(suggestBlockName(classNames), null, null));
final Set<String> newClassNames = ImmutableSet.copyOf(concat(transform(classNames, new Function<String, Iterable<String>>() {
@Override
public Iterable<String> apply(String className) {
return processClassName(className, node);
}
})));
attributes.put(HtmlUtil.CLASS_ATTRIBUTE_NAME, CLASS_NAME_JOINER.join(newClassNames));
Set<String> newClassNames = ContainerUtil.newLinkedHashSet();
for (String className : classNames) {
ContainerUtil.addAll(newClassNames, processClassName(className, node, elementSeparator, modifierSeparator));
}
attributes.put(HtmlUtil.CLASS_ATTRIBUTE_NAME, StringUtil.join(newClassNames, " "));
}
return node;
}
private static Iterable<String> processClassName(String className, @NotNull GenerationNode node) {
className = fillWithBemElements(className, node);
className = fillWithBemModifiers(className, node);
private static Iterable<String> processClassName(@NotNull String className, @NotNull GenerationNode node,
@NotNull String elementSeparator, @NotNull String modifierSeparator) {
className = fillWithBemElements(className, node, elementSeparator);
className = fillWithBemModifiers(className, node, modifierSeparator);
BemState nodeBemState = BEM_STATE.get(node);
BemState bemState = extractBemStateFromClassName(className);
List<String> result = newLinkedList();
BemState bemState = extractBemStateFromClassName(className, elementSeparator, modifierSeparator);
List<String> result = ContainerUtil.newArrayList();
if (!bemState.isEmpty()) {
String nodeBlockValue = nodeBemState != null ? nodeBemState.getBlock() : null;
String block = bemState.getBlock();
if (StringUtil.isEmpty(block)) {
block = StringUtil.notNullize(nodeBlockValue);
@@ -141,18 +103,18 @@ public class BemEmmetFilter extends ZenCodingFilter {
String prefix = block;
String element = bemState.getElement();
if (StringUtil.isNotEmpty(element)) {
prefix += ELEMENT_SEPARATOR + element;
prefix += elementSeparator + element;
}
result.add(prefix);
String modifier = bemState.getModifier();
if (StringUtil.isNotEmpty(modifier)) {
result.add(prefix + MODIFIER_SEPARATOR + modifier);
result.add(prefix + modifierSeparator + modifier);
}
BemState newNodeBemState = bemState.copy();
if (StringUtil.isNotEmpty(nodeBlockValue) && StringUtil.isEmpty(modifier)) {
// save old value
newNodeBemState.setBlock(nodeBlockValue);
newNodeBemState.setBlock(nodeBlockValue);
}
BEM_STATE.set(node, newNodeBemState);
}
@@ -163,56 +125,54 @@ public class BemEmmetFilter extends ZenCodingFilter {
}
@NotNull
private static BemState extractBemStateFromClassName(@NotNull String className) {
private static BemState extractBemStateFromClassName(@NotNull String className, String elementSeparator, String modifierSeparator) {
final BemState result = new BemState();
if (className.contains(ELEMENT_SEPARATOR)) {
final Iterator<String> elementsIterator = ELEMENTS_SPLITTER.split(className).iterator();
result.setBlock(elementsIterator.next());
int indexOfElementSeparator = className.indexOf(elementSeparator);
if (indexOfElementSeparator >= 0) {
result.setBlock(className.substring(0, indexOfElementSeparator));
result.setElement(className.substring(indexOfElementSeparator + elementSeparator.length()));
final Collection<String> elementParts = newLinkedList();
while (elementsIterator.hasNext()) {
final String elementPart = elementsIterator.next();
if (!elementsIterator.hasNext()) {
final List<String> elementModifiers = newArrayList(MODIFIERS_SPLITTER.split(elementPart));
elementParts.add(getFirst(elementModifiers, null));
if (elementModifiers.size() > 1) {
result.setModifier(getLast(elementModifiers, ""));
}
}
else {
elementParts.add(elementPart);
}
int lastIndexOfElementSeparator = className.lastIndexOf(elementSeparator);
assert lastIndexOfElementSeparator >= 0;
int indexOfModifierSeparator = className.indexOf(modifierSeparator, lastIndexOfElementSeparator + elementSeparator.length());
if (indexOfModifierSeparator >= 0) {
result.setModifier(className.substring(indexOfModifierSeparator + modifierSeparator.length()));
result.setElement(className.substring(indexOfElementSeparator + elementSeparator.length(), indexOfModifierSeparator));
}
result.setElement(ELEMENTS_JOINER.join(elementParts));
}
else if (className.contains(MODIFIER_SEPARATOR)) {
final Iterable<String> blockModifiers = MODIFIERS_SPLITTER.split(className);
result.setBlock(getFirst(blockModifiers, ""));
result.setModifier(getLast(blockModifiers, ""));
else {
int indexOfModifierSeparator = className.indexOf(modifierSeparator);
if (indexOfModifierSeparator >= 0) {
result.setBlock(className.substring(0, indexOfModifierSeparator));
result.setModifier(className.substring(indexOfModifierSeparator + modifierSeparator.length()));
}
}
return result;
}
@NotNull
private static String fillWithBemElements(@NotNull String className, @NotNull GenerationNode node) {
return transformClassNameToBemFormat(className, ELEMENT_SEPARATOR, node);
private static String fillWithBemElements(@NotNull String className, @NotNull GenerationNode node, @NotNull String separator) {
return transformClassNameToBemFormat(className, separator, node, false);
}
@NotNull
private static String fillWithBemModifiers(@NotNull String className, @NotNull GenerationNode node) {
return transformClassNameToBemFormat(className, MODIFIER_SEPARATOR, node);
private static String fillWithBemModifiers(@NotNull String className, @NotNull GenerationNode node, @NotNull String separator) {
return transformClassNameToBemFormat(className, separator, node, true);
}
/**
* Adduction className to BEM format according to tags structure.
* Adjusting className to BEM format according to tags structure.
*
* @param className
* @param separator handling separator
* @param node current node
* @param separator handling separator
* @param node current node
* @param isModifierSeparator whether we're trying to handle modifier
* @return class name in BEM format
*/
@NotNull
private static String transformClassNameToBemFormat(@NotNull String className, @NotNull String separator, @NotNull GenerationNode node) {
private static String transformClassNameToBemFormat(@NotNull String className, @NotNull String separator,
@NotNull GenerationNode node, boolean isModifierSeparator) {
Pair<String, Integer> cleanStringAndDepth = getCleanStringAndDepth(className, separator);
Integer depth = cleanStringAndDepth.second;
if (depth > 0) {
@@ -227,7 +187,7 @@ public class BemEmmetFilter extends ZenCodingFilter {
String prefix = bemState.getBlock();
if (!StringUtil.isEmpty(prefix)) {
String element = bemState.getElement();
if (MODIFIER_SEPARATOR.equals(separator) && !StringUtil.isEmpty(element)) {
if (isModifierSeparator && !StringUtil.isEmpty(element)) {
prefix = prefix + separator + element;
}
return prefix + separator + cleanStringAndDepth.first;
@@ -247,24 +207,13 @@ public class BemEmmetFilter extends ZenCodingFilter {
@NotNull
private static Pair<String, Integer> getCleanStringAndDepth(@NotNull String name, @NotNull String separator) {
int result = 0;
while (name.startsWith(separator)) {
while (!separator.isEmpty() && name.startsWith(separator)) {
result++;
name = name.substring(separator.length());
}
return Pair.create(name, result);
}
/**
* Extract all normalized class names from class attribute value
*
* @param classAttributeValue
* @return collection of normalized class names
*/
@NotNull
private static Iterable<String> extractClasses(String classAttributeValue) {
return transform(CLASS_NAME_SPLITTER.split(classAttributeValue), CLASS_NAME_NORMALIZER);
}
/**
* Suggest block name by class names.
* Returns first class started with pattern [a-z]-
@@ -275,7 +224,27 @@ public class BemEmmetFilter extends ZenCodingFilter {
*/
@NotNull
private static String suggestBlockName(Iterable<String> classNames) {
return find(classNames, BLOCK_NAME_PREDICATE, find(classNames, STARTS_WITH_LETTER, ""));
String result = ContainerUtil.find(classNames, className -> BLOCK_NAME_PATTERN.matcher(className).matches());
if (result == null) {
result = ContainerUtil.find(classNames, s -> s != null && !s.isEmpty() && Character.isLetter(s.charAt(0)));
}
return StringUtil.notNullize(result);
}
@NotNull
private static String normalizeClassName(@NotNull String className,
@NotNull String elementSeparator,
@NotNull String shortElementPrefix) {
if (shortElementPrefix.isEmpty() || !className.startsWith(shortElementPrefix)) {
return className;
}
StringBuilder result = new StringBuilder();
while (className.startsWith(shortElementPrefix)) {
className = className.substring(shortElementPrefix.length());
result.append(elementSeparator);
}
return result.append(className).toString();
}
private static class BemState {
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2015 JetBrains s.r.o.
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -694,4 +694,9 @@ public class HtmlUtil {
return true;
}
}
public static Iterable<String> splitClassNames(@NotNull String classAttributeValue) {
// comma is useduse as separator because class name cannot contain comma but it can be part of JSF classes attributes
return StringUtil.tokenize(classAttributeValue, " \t,");
}
}