From 57470b7151ec89be55394d9f08bd63a3328debd0 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Thu, 14 Feb 2013 14:24:17 +0400 Subject: [PATCH] xml rearranger support: ordering of attributes --- .../match/ArrangementEntryType.java | 4 +- .../src/META-INF/XmlPlugin.xml | 1 + .../options/XmlCodeStyleMainPanel.java | 2 + .../xml/arrangement/XmlArrangementPanel.java | 33 ++++ .../arrangement/XmlArrangementParseInfo.java | 23 +++ .../arrangement/XmlArrangementVisitor.java | 103 ++++++++++ .../XmlElementArrangementEntry.java | 50 +++++ .../xml/arrangement/XmlRearranger.java | 182 ++++++++++++++++++ .../xml/arrangement/XmlRearrangerTest.groovy | 53 +++++ 9 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 xml/impl/src/com/intellij/xml/arrangement/XmlArrangementPanel.java create mode 100644 xml/impl/src/com/intellij/xml/arrangement/XmlArrangementParseInfo.java create mode 100644 xml/impl/src/com/intellij/xml/arrangement/XmlArrangementVisitor.java create mode 100644 xml/impl/src/com/intellij/xml/arrangement/XmlElementArrangementEntry.java create mode 100644 xml/impl/src/com/intellij/xml/arrangement/XmlRearranger.java create mode 100644 xml/tests/src/com/intellij/xml/arrangement/XmlRearrangerTest.groovy diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/match/ArrangementEntryType.java b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/match/ArrangementEntryType.java index 773b7664f60b..654443211792 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/match/ArrangementEntryType.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/arrangement/match/ArrangementEntryType.java @@ -31,5 +31,7 @@ public enum ArrangementEntryType { CONST, VAR, PROPERTY, EVENT_HANDLER, STATIC_INIT, - NAMESPACE, TRAIT + NAMESPACE, TRAIT, + + XML_TAG, XML_ATTRIBUTE } diff --git a/platform/platform-resources/src/META-INF/XmlPlugin.xml b/platform/platform-resources/src/META-INF/XmlPlugin.xml index b08ebdc840c8..e8b62c5d4bab 100644 --- a/platform/platform-resources/src/META-INF/XmlPlugin.xml +++ b/platform/platform-resources/src/META-INF/XmlPlugin.xml @@ -449,6 +449,7 @@ + diff --git a/xml/impl/src/com/intellij/application/options/XmlCodeStyleMainPanel.java b/xml/impl/src/com/intellij/application/options/XmlCodeStyleMainPanel.java index e465fb04741a..6771e128506c 100644 --- a/xml/impl/src/com/intellij/application/options/XmlCodeStyleMainPanel.java +++ b/xml/impl/src/com/intellij/application/options/XmlCodeStyleMainPanel.java @@ -19,6 +19,7 @@ import com.intellij.lang.xml.XMLLanguage; import com.intellij.openapi.extensions.Extensions; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CodeStyleSettingsProvider; +import com.intellij.xml.arrangement.XmlArrangementPanel; /** * @author Rustam Vishnyakov @@ -32,6 +33,7 @@ public class XmlCodeStyleMainPanel extends TabbedLanguageCodeStylePanel { protected void initTabs(CodeStyleSettings settings) { addIndentOptionsTab(settings); addTab(new CodeStyleXmlPanel(settings)); + addTab(new XmlArrangementPanel(settings)); for (CodeStyleSettingsProvider provider : Extensions.getExtensions(CodeStyleSettingsProvider.EXTENSION_POINT_NAME)) { if (provider.getLanguage() == XMLLanguage.INSTANCE && !provider.hasSettingsPage()) { diff --git a/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementPanel.java b/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementPanel.java new file mode 100644 index 000000000000..1b3c0ae6f3b9 --- /dev/null +++ b/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementPanel.java @@ -0,0 +1,33 @@ +package com.intellij.xml.arrangement; + +import com.intellij.application.options.codeStyle.arrangement.ArrangementSettingsPanel; +import com.intellij.ide.highlighter.XmlFileType; +import com.intellij.lang.xml.XMLLanguage; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import org.jetbrains.annotations.NotNull; + +/** + * @author Eugene.Kudelevsky + */ +public class XmlArrangementPanel extends ArrangementSettingsPanel { + public XmlArrangementPanel(@NotNull CodeStyleSettings settings) { + super(settings, XMLLanguage.INSTANCE); + } + + @Override + protected int getRightMargin() { + return 80; + } + + @NotNull + @Override + protected FileType getFileType() { + return XmlFileType.INSTANCE; + } + + @Override + protected String getPreviewText() { + return null; + } +} diff --git a/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementParseInfo.java b/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementParseInfo.java new file mode 100644 index 000000000000..8b5261b6e398 --- /dev/null +++ b/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementParseInfo.java @@ -0,0 +1,23 @@ +package com.intellij.xml.arrangement; + +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Eugene.Kudelevsky + */ +public class XmlArrangementParseInfo { + + private final List myEntries = new ArrayList(); + + @NotNull + public List getEntries() { + return myEntries; + } + + public void addEntry(@NotNull XmlElementArrangementEntry entry) { + myEntries.add(entry); + } +} diff --git a/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementVisitor.java b/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementVisitor.java new file mode 100644 index 000000000000..e455843ed7db --- /dev/null +++ b/xml/impl/src/com/intellij/xml/arrangement/XmlArrangementVisitor.java @@ -0,0 +1,103 @@ +package com.intellij.xml.arrangement; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.XmlElementVisitor; +import com.intellij.psi.codeStyle.arrangement.DefaultArrangementEntry; +import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.containers.Stack; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collection; + +/** + * @author Eugene.Kudelevsky + */ +public class XmlArrangementVisitor extends XmlElementVisitor { + + private final Stack myStack = new Stack(); + + private final XmlArrangementParseInfo myInfo; + private final Collection myRanges; + + public XmlArrangementVisitor(@NotNull XmlArrangementParseInfo info, @NotNull Collection ranges) { + myInfo = info; + myRanges = ranges; + } + + @Override + public void visitXmlFile(XmlFile file) { + final XmlTag tag = file.getRootTag(); + + if (tag != null) { + tag.accept(this); + } + } + + @Override + public void visitXmlTag(XmlTag tag) { + final XmlElementArrangementEntry entry = createNewEntry( + tag.getTextRange(), ArrangementEntryType.XML_TAG, null, true); + processEntry(entry, tag); + } + + @Override + public void visitXmlAttribute(XmlAttribute attribute) { + final String name = attribute.isNamespaceDeclaration() ? "" : attribute.getName(); + final XmlElementArrangementEntry entry = createNewEntry( + attribute.getTextRange(), ArrangementEntryType.XML_ATTRIBUTE, name, true); + processEntry(entry, null); + } + + private void processEntry(@Nullable XmlElementArrangementEntry entry, @Nullable PsiElement nextElement) { + if (entry == null || nextElement == null) { + return; + } + myStack.push(entry); + try { + nextElement.acceptChildren(this); + } + finally { + myStack.pop(); + } + } + + @Nullable + private XmlElementArrangementEntry createNewEntry(@NotNull TextRange range, + @NotNull ArrangementEntryType type, + @Nullable String name, + boolean canBeMatched) { + if (!isWithinBounds(range)) { + return null; + } + final DefaultArrangementEntry current = getCurrent(); + final XmlElementArrangementEntry entry = new XmlElementArrangementEntry( + current, range, type, name, canBeMatched); + + if (current == null) { + myInfo.addEntry(entry); + } + else { + current.addChild(entry); + } + return entry; + } + + @Nullable + private DefaultArrangementEntry getCurrent() { + return myStack.isEmpty() ? null : myStack.peek(); + } + + private boolean isWithinBounds(@NotNull TextRange range) { + for (TextRange textRange : myRanges) { + if (textRange.intersects(range)) { + return true; + } + } + return false; + } +} diff --git a/xml/impl/src/com/intellij/xml/arrangement/XmlElementArrangementEntry.java b/xml/impl/src/com/intellij/xml/arrangement/XmlElementArrangementEntry.java new file mode 100644 index 000000000000..e315a79fbd8a --- /dev/null +++ b/xml/impl/src/com/intellij/xml/arrangement/XmlElementArrangementEntry.java @@ -0,0 +1,50 @@ +package com.intellij.xml.arrangement; + +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.codeStyle.arrangement.ArrangementEntry; +import com.intellij.psi.codeStyle.arrangement.DefaultArrangementEntry; +import com.intellij.psi.codeStyle.arrangement.NameAwareArrangementEntry; +import com.intellij.psi.codeStyle.arrangement.TypeAwareArrangementEntry; +import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.Set; + +/** + * @author Eugene.Kudelevsky + */ +public class XmlElementArrangementEntry extends DefaultArrangementEntry + implements TypeAwareArrangementEntry, NameAwareArrangementEntry { + + private final ArrangementEntryType myType; + private final String myName; + + public XmlElementArrangementEntry(@Nullable ArrangementEntry parent, + @NotNull TextRange range, + @NotNull ArrangementEntryType type, + @Nullable String name, + boolean canBeMatched) { + super(parent, range.getStartOffset(), range.getEndOffset(), canBeMatched); + myName = name; + myType = type; + } + + @Nullable + @Override + public String getName() { + return myName; + } + + @NotNull + @Override + public Set getTypes() { + return Collections.singleton(myType); + } + + @NotNull + public ArrangementEntryType getType() { + return myType; + } +} diff --git a/xml/impl/src/com/intellij/xml/arrangement/XmlRearranger.java b/xml/impl/src/com/intellij/xml/arrangement/XmlRearranger.java new file mode 100644 index 000000000000..a13db532b501 --- /dev/null +++ b/xml/impl/src/com/intellij/xml/arrangement/XmlRearranger.java @@ -0,0 +1,182 @@ +package com.intellij.xml.arrangement; + +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiElement; +import com.intellij.psi.codeStyle.CodeStyleSettings; +import com.intellij.psi.codeStyle.arrangement.ArrangementSettings; +import com.intellij.psi.codeStyle.arrangement.Rearranger; +import com.intellij.psi.codeStyle.arrangement.StdArrangementSettings; +import com.intellij.psi.codeStyle.arrangement.group.ArrangementGroupingRule; +import com.intellij.psi.codeStyle.arrangement.group.ArrangementGroupingType; +import com.intellij.psi.codeStyle.arrangement.match.ArrangementEntryType; +import com.intellij.psi.codeStyle.arrangement.match.ArrangementModifier; +import com.intellij.psi.codeStyle.arrangement.match.StdArrangementMatchRule; +import com.intellij.psi.codeStyle.arrangement.model.ArrangementMatchCondition; +import com.intellij.psi.codeStyle.arrangement.order.ArrangementEntryOrderType; +import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsAware; +import com.intellij.psi.codeStyle.arrangement.settings.ArrangementStandardSettingsRepresentationAware; +import com.intellij.psi.codeStyle.arrangement.settings.DefaultArrangementSettingsRepresentationManager; +import gnu.trove.TObjectIntHashMap; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.*; + +/** + * @author Eugene.Kudelevsky + */ +public class XmlRearranger + implements Rearranger, + ArrangementStandardSettingsAware, + ArrangementStandardSettingsRepresentationAware { + + private static final Set SUPPORTED_TYPES = EnumSet.of( + ArrangementEntryType.XML_ATTRIBUTE, ArrangementEntryType.XML_TAG); + + private static final List DEFAULT_MATCH_RULES = new ArrayList(); + + private static final StdArrangementSettings DEFAULT_SETTINGS = new StdArrangementSettings( + Collections.emptyList(), DEFAULT_MATCH_RULES); + + @NotNull private static final TObjectIntHashMap WEIGHTS = new TObjectIntHashMap(); + + @NotNull + private static final Comparator COMPARATOR = new Comparator() { + @Override + public int compare(Object o1, Object o2) { + if (WEIGHTS.containsKey(o1) && WEIGHTS.containsKey(o2)) { + return WEIGHTS.get(o1) - WEIGHTS.get(o2); + } + else if (WEIGHTS.containsKey(o1) && !WEIGHTS.containsKey(o2)) { + return -1; + } + else if (!WEIGHTS.containsKey(o1) && WEIGHTS.containsKey(o2)) { + return 1; + } + else { + return o1.hashCode() - o2.hashCode(); + } + } + }; + + static { + final Object[] ids = { + ArrangementEntryType.XML_TAG, ArrangementEntryType.XML_ATTRIBUTE + }; + for (int i = 0; i < ids.length; i++) { + WEIGHTS.put(ids[i], i); + } + } + + @Nullable + @Override + public StdArrangementSettings getDefaultSettings() { + return DEFAULT_SETTINGS; + } + + @Override + public boolean isNameFilterSupported() { + return true; + } + + @Override + public boolean isEnabled(@NotNull ArrangementEntryType type, @Nullable ArrangementMatchCondition current) { + return SUPPORTED_TYPES.contains(type); + } + + @Override + public boolean isEnabled(@NotNull ArrangementModifier modifier, @Nullable ArrangementMatchCondition current) { + return false; + } + + @Override + public boolean isEnabled(@NotNull ArrangementGroupingType groupingType, @Nullable ArrangementEntryOrderType orderType) { + return false; + } + + @NotNull + @Override + public Collection> getMutexes() { + return Collections.>singleton(SUPPORTED_TYPES); + } + + @Nullable + @Override + public Pair> parseWithNew(@NotNull PsiElement root, + @Nullable Document document, + @NotNull Collection ranges, + @NotNull PsiElement element, + @Nullable ArrangementSettings settings) { + final XmlArrangementParseInfo newEntryInfo = new XmlArrangementParseInfo(); + element.accept(new XmlArrangementVisitor(newEntryInfo, Collections.singleton(element.getTextRange()))); + + if (newEntryInfo.getEntries().size() != 1) { + return null; + } + final XmlElementArrangementEntry entry = newEntryInfo.getEntries().get(0); + final XmlArrangementParseInfo existingEntriesInfo = new XmlArrangementParseInfo(); + root.accept(new XmlArrangementVisitor(existingEntriesInfo, ranges)); + return Pair.create(entry, existingEntriesInfo.getEntries()); + } + + @NotNull + @Override + public List parse(@NotNull PsiElement root, + @Nullable Document document, + @NotNull Collection ranges, + @Nullable ArrangementSettings settings) { + final XmlArrangementParseInfo parseInfo = new XmlArrangementParseInfo(); + root.accept(new XmlArrangementVisitor(parseInfo, ranges)); + return parseInfo.getEntries(); + } + + @Override + public int getBlankLines(@NotNull CodeStyleSettings settings, + @Nullable XmlElementArrangementEntry parent, + @Nullable XmlElementArrangementEntry previous, + @NotNull XmlElementArrangementEntry target) { + return -1; + } + + @NotNull + @Override + public String getDisplayValue(@NotNull ArrangementEntryType type) { + switch (type) { + case XML_TAG: + return "tag"; + case XML_ATTRIBUTE: + return "attribute"; + default: + return DefaultArrangementSettingsRepresentationManager. + INSTANCE.getDisplayValue(type); + } + } + + @NotNull + @Override + public String getDisplayValue(@NotNull ArrangementModifier modifier) { + return DefaultArrangementSettingsRepresentationManager.INSTANCE.getDisplayValue(modifier); + } + + @NotNull + @Override + public String getDisplayValue(@NotNull ArrangementGroupingType groupingType) { + return DefaultArrangementSettingsRepresentationManager.INSTANCE.getDisplayValue(groupingType); + } + + @NotNull + @Override + public String getDisplayValue(@NotNull ArrangementEntryOrderType orderType) { + return DefaultArrangementSettingsRepresentationManager.INSTANCE.getDisplayValue(orderType); + } + + @NotNull + @Override + public List sort(@NotNull Collection ids) { + final List result = new ArrayList(ids); + Collections.sort(result, COMPARATOR); + return result; + } +} diff --git a/xml/tests/src/com/intellij/xml/arrangement/XmlRearrangerTest.groovy b/xml/tests/src/com/intellij/xml/arrangement/XmlRearrangerTest.groovy new file mode 100644 index 000000000000..0f252fa0ab24 --- /dev/null +++ b/xml/tests/src/com/intellij/xml/arrangement/XmlRearrangerTest.groovy @@ -0,0 +1,53 @@ +package com.intellij.xml.arrangement +import com.intellij.ide.highlighter.XmlFileType +import com.intellij.lang.xml.XMLLanguage +import com.intellij.psi.codeStyle.arrangement.AbstractRearrangerTest + +import static com.intellij.psi.codeStyle.arrangement.order.ArrangementEntryOrderType.BY_NAME +/** + * @author Eugene.Kudelevsky + */ +class XmlRearrangerTest extends AbstractRearrangerTest { + XmlRearrangerTest() { + fileType = XmlFileType.INSTANCE + language = XMLLanguage.INSTANCE + } + + void testAttributeSorting1() { + doTest( + initial: '''''', + expected: '''''', + rules: [rule(BY_NAME, ".*")] + ) + } + + void testAttributeSorting2() { + doTest( + initial: '''''', + expected: '''''', + rules: [rule(BY_NAME, ".*")] + ) + } + + void testAttributeSorting3() { + doTest( + initial: '''\ + + + + + + +''', + expected: '''\ + + + + + + +''', + rules: [rule(BY_NAME, ".*")] + ) + } +}