diff --git a/platform/platform-resources-en/src/messages/XmlBundle.properties b/platform/platform-resources-en/src/messages/XmlBundle.properties index 6e3c3046ca8a..f657358c6182 100644 --- a/platform/platform-resources-en/src/messages/XmlBundle.properties +++ b/platform/platform-resources-en/src/messages/XmlBundle.properties @@ -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 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 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 ce2159e712f8..00a69e4927c3 100644 --- a/xml/impl/src/com/intellij/application/options/emmet/EmmetOptions.java +++ b/xml/impl/src/com/intellij/application/options/emmet/EmmetOptions.java @@ -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 { private boolean myEmmetEnabled = true; private int myEmmetExpandShortcut = TemplateSettings.TAB_CHAR; private boolean myPreviewEnabled = false; + @NotNull private Set 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 getFiltersEnabledByDefault() { return myFiltersEnabledByDefault; @@ -76,7 +82,7 @@ public class EmmetOptions implements PersistentStateComponent { public void setEmmetEnabled(boolean emmetEnabled) { myEmmetEnabled = emmetEnabled; } - + public void setHrefAutoDetectEnabled(boolean hrefAutoDetectEnabled) { myHrefAutoDetectEnabled = hrefAutoDetectEnabled; } @@ -93,6 +99,33 @@ public class EmmetOptions implements PersistentStateComponent { 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() { 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 34249970dc29..0f71dbd40e59 100644 --- a/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.form +++ b/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.form @@ -1,9 +1,9 @@
- + - + @@ -27,7 +27,7 @@ - + @@ -49,6 +49,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 65b10f779695..ef613593789f 100644 --- a/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.java +++ b/xml/impl/src/com/intellij/application/options/emmet/XmlEmmetConfigurable.java @@ -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 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 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 enabledByDefault = emmetOptions.getFiltersEnabledByDefault(); for (ZenCodingFilter filter : ZenCodingFilter.getInstances()) { diff --git a/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/BemEmmetFilter.java b/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/BemEmmetFilter.java index bc43a0c2b7dd..37e14608ebdf 100644 --- a/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/BemEmmetFilter.java +++ b/xml/impl/src/com/intellij/codeInsight/template/emmet/filters/BemEmmetFilter.java @@ -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 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 CLASS_NAME_NORMALIZER = new Function() { - @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 BLOCK_NAME_PREDICATE = new Predicate() { - @Override - public boolean apply(String className) { - return Pattern.compile("^[A-z]-").matcher(className).matches(); - } - }; - private static final Predicate STARTS_WITH_LETTER = new Predicate() { - @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 attributes = node.getTemplateToken().getAttributes(); String classValue = attributes.get(HtmlUtil.CLASS_ATTRIBUTE_NAME); - if (classValue != null) { - Iterable 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 classNames = ContainerUtil.map(HtmlUtil.splitClassNames(classValue), (s) -> { + return normalizeClassName(s, elementSeparator, shortElementPrefix); + }); + BEM_STATE.set(node, new BemState(suggestBlockName(classNames), null, null)); - final Set newClassNames = ImmutableSet.copyOf(concat(transform(classNames, new Function>() { - @Override - public Iterable apply(String className) { - return processClassName(className, node); - } - }))); - attributes.put(HtmlUtil.CLASS_ATTRIBUTE_NAME, CLASS_NAME_JOINER.join(newClassNames)); + Set 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 processClassName(String className, @NotNull GenerationNode node) { - className = fillWithBemElements(className, node); - className = fillWithBemModifiers(className, node); + private static Iterable 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 result = newLinkedList(); + BemState bemState = extractBemStateFromClassName(className, elementSeparator, modifierSeparator); + List 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 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 elementParts = newLinkedList(); - while (elementsIterator.hasNext()) { - final String elementPart = elementsIterator.next(); - if (!elementsIterator.hasNext()) { - final List 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 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 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 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 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 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 { 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 390e325b0b10..45fb5f1359c0 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 @@ -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 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,"); + } }