mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
xml rearranger support: ordering of attributes
This commit is contained in:
+3
-1
@@ -31,5 +31,7 @@ public enum ArrangementEntryType {
|
||||
|
||||
CONST, VAR, PROPERTY, EVENT_HANDLER, STATIC_INIT,
|
||||
|
||||
NAMESPACE, TRAIT
|
||||
NAMESPACE, TRAIT,
|
||||
|
||||
XML_TAG, XML_ATTRIBUTE
|
||||
}
|
||||
|
||||
@@ -449,6 +449,7 @@
|
||||
<breadcrumbsPresentationProvider implementation="com.intellij.codeInsight.daemon.impl.tagTreeHighlighting.XmlTagTreeBreadcrumbsPresentationProvider"/>
|
||||
<daemon.changeLocalityDetector implementation="com.intellij.xml.XmlChangeLocalityDetector"/>
|
||||
<xmlStructureViewBuilderProvider implementation="com.intellij.lang.html.structureView.HtmlStructureViewBuilderProvider"/>
|
||||
<lang.rearranger language="XML" implementationClass="com.intellij.xml.arrangement.XmlRearranger"/>
|
||||
</extensions>
|
||||
<extensions defaultExtensionNs="org.jetbrains">
|
||||
<urlOpener implementation="com.intellij.ide.browsers.impl.DefaultUrlOpener" order="last"/>
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<XmlElementArrangementEntry> myEntries = new ArrayList<XmlElementArrangementEntry>();
|
||||
|
||||
@NotNull
|
||||
public List<XmlElementArrangementEntry> getEntries() {
|
||||
return myEntries;
|
||||
}
|
||||
|
||||
public void addEntry(@NotNull XmlElementArrangementEntry entry) {
|
||||
myEntries.add(entry);
|
||||
}
|
||||
}
|
||||
@@ -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<XmlElementArrangementEntry> myStack = new Stack<XmlElementArrangementEntry>();
|
||||
|
||||
private final XmlArrangementParseInfo myInfo;
|
||||
private final Collection<TextRange> myRanges;
|
||||
|
||||
public XmlArrangementVisitor(@NotNull XmlArrangementParseInfo info, @NotNull Collection<TextRange> 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;
|
||||
}
|
||||
}
|
||||
@@ -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<ArrangementEntryType> getTypes() {
|
||||
return Collections.singleton(myType);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ArrangementEntryType getType() {
|
||||
return myType;
|
||||
}
|
||||
}
|
||||
@@ -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<XmlElementArrangementEntry>,
|
||||
ArrangementStandardSettingsAware,
|
||||
ArrangementStandardSettingsRepresentationAware {
|
||||
|
||||
private static final Set<ArrangementEntryType> SUPPORTED_TYPES = EnumSet.of(
|
||||
ArrangementEntryType.XML_ATTRIBUTE, ArrangementEntryType.XML_TAG);
|
||||
|
||||
private static final List<StdArrangementMatchRule> DEFAULT_MATCH_RULES = new ArrayList<StdArrangementMatchRule>();
|
||||
|
||||
private static final StdArrangementSettings DEFAULT_SETTINGS = new StdArrangementSettings(
|
||||
Collections.<ArrangementGroupingRule>emptyList(), DEFAULT_MATCH_RULES);
|
||||
|
||||
@NotNull private static final TObjectIntHashMap<Object> WEIGHTS = new TObjectIntHashMap<Object>();
|
||||
|
||||
@NotNull
|
||||
private static final Comparator<Object> COMPARATOR = new Comparator<Object>() {
|
||||
@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<Set<?>> getMutexes() {
|
||||
return Collections.<Set<?>>singleton(SUPPORTED_TYPES);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Pair<XmlElementArrangementEntry, List<XmlElementArrangementEntry>> parseWithNew(@NotNull PsiElement root,
|
||||
@Nullable Document document,
|
||||
@NotNull Collection<TextRange> 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<XmlElementArrangementEntry> parse(@NotNull PsiElement root,
|
||||
@Nullable Document document,
|
||||
@NotNull Collection<TextRange> 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 <T> List<T> sort(@NotNull Collection<T> ids) {
|
||||
final List<T> result = new ArrayList<T>(ids);
|
||||
Collections.sort(result, COMPARATOR);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
@@ -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: '''<root xmlns:ns="http://ns.com" attr2="value2" attr1="value1" attr3="value3"/>''',
|
||||
expected: '''<root xmlns:ns="http://ns.com" attr1="value1" attr2="value2" attr3="value3"/>''',
|
||||
rules: [rule(BY_NAME, ".*")]
|
||||
)
|
||||
}
|
||||
|
||||
void testAttributeSorting2() {
|
||||
doTest(
|
||||
initial: '''<root attr3="value3" attr2="value2" attr1="value1" xmlns:ns="http://ns.com"/>''',
|
||||
expected: '''<root xmlns:ns="http://ns.com" attr1="value1" attr2="value2" attr3="value3"/>''',
|
||||
rules: [rule(BY_NAME, ".*")]
|
||||
)
|
||||
}
|
||||
|
||||
void testAttributeSorting3() {
|
||||
doTest(
|
||||
initial: '''\
|
||||
<root xmlns:ns1="http://ns1.com" xmlns:ns2="http://ns2.com" attr2="value2" attr1="value1">
|
||||
<tag1 attr2="value2" attr1="value1">
|
||||
<tag2 attr1="value1" attr2="value2"/>
|
||||
<tag3 attr2="value2" attr1="value1" xmlns:ns3="http://ns.com"/>
|
||||
</tag1>
|
||||
</root>
|
||||
''',
|
||||
expected: '''\
|
||||
<root xmlns:ns1="http://ns1.com" xmlns:ns2="http://ns2.com" attr1="value1" attr2="value2">
|
||||
<tag1 attr1="value1" attr2="value2">
|
||||
<tag2 attr1="value1" attr2="value2"/>
|
||||
<tag3 xmlns:ns3="http://ns.com" attr1="value1" attr2="value2"/>
|
||||
</tag1>
|
||||
</root>
|
||||
''',
|
||||
rules: [rule(BY_NAME, ".*")]
|
||||
)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user