package org.jetbrains.android.formatter; import com.intellij.formatting.FormattingDocumentModel; import com.intellij.openapi.util.InvalidDataException; import com.intellij.openapi.util.JDOMExternalizable; import com.intellij.openapi.util.WriteExternalException; import com.intellij.psi.PsiElement; import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.intellij.psi.codeStyle.CustomCodeStyleSettings; import com.intellij.psi.formatter.xml.XmlPolicy; import com.intellij.psi.xml.XmlTag; import com.intellij.util.xml.DomManager; import com.intellij.util.xmlb.SkipDefaultValuesSerializationFilters; import com.intellij.util.xmlb.XmlSerializer; import org.jdom.Element; import org.jetbrains.android.dom.resources.Style; import static com.android.SdkConstants.*; /** * @author Eugene.Kudelevsky */ public class AndroidXmlCodeStyleSettings extends CustomCodeStyleSettings { public boolean USE_CUSTOM_SETTINGS = false; public LayoutSettings LAYOUT_SETTINGS = new LayoutSettings(); public ManifestSettings MANIFEST_SETTINGS = new ManifestSettings(); public ValueResourceFileSettings VALUE_RESOURCE_FILE_SETTINGS = new ValueResourceFileSettings(); public OtherSettings OTHER_SETTINGS = new OtherSettings(); public AndroidXmlCodeStyleSettings(CodeStyleSettings container) { super("AndroidXmlCodeStyleSettings", container); } public static AndroidXmlCodeStyleSettings getInstance(CodeStyleSettings settings) { return settings.getCustomSettings(AndroidXmlCodeStyleSettings.class); } @Override public Object clone() { try { final AndroidXmlCodeStyleSettings cloned = (AndroidXmlCodeStyleSettings)super.clone(); cloned.LAYOUT_SETTINGS = (LayoutSettings)LAYOUT_SETTINGS.clone(); cloned.MANIFEST_SETTINGS = (ManifestSettings)MANIFEST_SETTINGS.clone(); cloned.VALUE_RESOURCE_FILE_SETTINGS = (ValueResourceFileSettings)VALUE_RESOURCE_FILE_SETTINGS.clone(); cloned.OTHER_SETTINGS = (OtherSettings)OTHER_SETTINGS.clone(); return cloned; } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } public static class MySettings implements JDOMExternalizable, Cloneable { public int WRAP_ATTRIBUTES; public boolean INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE; public boolean INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE; @Override public void readExternal(Element element) throws InvalidDataException { XmlSerializer.deserializeInto(this, element); } @Override public void writeExternal(Element element) throws WriteExternalException { XmlSerializer.serializeInto(this, element, new SkipDefaultValuesSerializationFilters()); } public XmlPolicy createXmlPolicy(CodeStyleSettings settings, FormattingDocumentModel documentModel) { return new AndroidXmlPolicy(settings, this, documentModel); } @Override protected MySettings clone() throws CloneNotSupportedException { try { return (MySettings)super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException(e); } } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final MySettings s = (MySettings)o; return INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE == s.INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE && INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE == s.INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE && WRAP_ATTRIBUTES == s.WRAP_ATTRIBUTES; } @Override public int hashCode() { int result = WRAP_ATTRIBUTES; result = 31 * result + (INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE ? 1 : 0); result = 31 * result + (INSERT_LINE_BREAK_AFTER_LAST_ATTRIBUTE ? 1 : 0); return result; } } public static class LayoutSettings extends MySettings { public boolean INSERT_BLANK_LINE_BEFORE_TAG = true; { WRAP_ATTRIBUTES = CommonCodeStyleSettings.WRAP_ALWAYS; INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE = true; } @Override public XmlPolicy createXmlPolicy(CodeStyleSettings settings, FormattingDocumentModel documentModel) { return new AndroidXmlPolicy(settings, this, documentModel) { @Override public boolean insertLineBreakBeforeTag(XmlTag xmlTag) { return INSERT_BLANK_LINE_BEFORE_TAG; } @Override public boolean insertLineBreakAfterTagBegin(XmlTag tag) { return INSERT_BLANK_LINE_BEFORE_TAG; } }; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; final LayoutSettings settings = (LayoutSettings)o; return INSERT_BLANK_LINE_BEFORE_TAG == settings.INSERT_BLANK_LINE_BEFORE_TAG; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (INSERT_BLANK_LINE_BEFORE_TAG ? 1 : 0); return result; } } public static class ManifestSettings extends MySettings { public boolean GROUP_TAGS_WITH_SAME_NAME = true; { WRAP_ATTRIBUTES = CommonCodeStyleSettings.WRAP_ALWAYS; INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE = true; } @Override public XmlPolicy createXmlPolicy(CodeStyleSettings settings, FormattingDocumentModel documentModel) { return new AndroidXmlPolicy(settings, this, documentModel) { @Override public boolean insertLineBreakBeforeTag(XmlTag xmlTag) { if (GROUP_TAGS_WITH_SAME_NAME) { PsiElement element = getPrevSiblingElement(xmlTag); if (element instanceof XmlTag) { final String name1 = ((XmlTag)element).getName(); final String name2 = xmlTag.getName(); if (!name1.equals(name2)) { element = getPrevSiblingElement(element); if (element instanceof XmlTag && ((XmlTag)element).getName().equals(name1)) { return true; } element = getNextSiblingElement(xmlTag); return element instanceof XmlTag && ((XmlTag)element).getName().equals(name2); } } } return false; } @Override public boolean insertLineBreakAfterTagBegin(XmlTag tag) { return GROUP_TAGS_WITH_SAME_NAME && tag.getParentTag() == null; } }; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; final ManifestSettings settings = (ManifestSettings)o; return GROUP_TAGS_WITH_SAME_NAME == settings.GROUP_TAGS_WITH_SAME_NAME; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (GROUP_TAGS_WITH_SAME_NAME ? 1 : 0); return result; } } public static class ValueResourceFileSettings extends MySettings { public boolean INSERT_LINE_BREAKS_AROUND_STYLE = true; { WRAP_ATTRIBUTES = CommonCodeStyleSettings.DO_NOT_WRAP; INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE = false; } @Override public XmlPolicy createXmlPolicy(CodeStyleSettings settings, FormattingDocumentModel documentModel) { return new AndroidXmlPolicy(settings, this, documentModel) { @Override public boolean insertLineBreakAfterTagBegin(XmlTag tag) { if (!INSERT_LINE_BREAKS_AROUND_STYLE) { return false; } final XmlTag[] subTags = tag.getSubTags(); return subTags.length != 0 && isStyleTag(subTags[0]); } @Override public boolean keepWhiteSpacesInsideTag(XmlTag tag) { if (super.keepWhiteSpacesInsideTag(tag)) { return true; } boolean inItem = false; while (tag != null) { String tagName = tag.getName(); if (TAG_ITEM.equals(tagName)) { inItem = true; } else if (TAG_STRING.equals(tagName)) { return true; } else if (TAG_STRING_ARRAY.equals(tagName) || TAG_PLURALS.equals(tagName)) { return inItem; } tag = tag.getParentTag(); } return false; } @Override public boolean insertLineBreakBeforeTag(XmlTag xmlTag) { if (!INSERT_LINE_BREAKS_AROUND_STYLE) { return false; } if (isStyleTag(xmlTag)) { return true; } final PsiElement sibling = getPrevSiblingElement(xmlTag); return sibling instanceof XmlTag && isStyleTag((XmlTag)sibling); } private boolean isStyleTag(XmlTag tag) { return DomManager.getDomManager(tag.getProject()). getDomElement(tag) instanceof Style; } }; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; if (!super.equals(o)) return false; final ValueResourceFileSettings settings = (ValueResourceFileSettings)o; return INSERT_LINE_BREAKS_AROUND_STYLE == settings.INSERT_LINE_BREAKS_AROUND_STYLE; } @Override public int hashCode() { int result = super.hashCode(); result = 31 * result + (INSERT_LINE_BREAKS_AROUND_STYLE ? 1 : 0); return result; } } public static class OtherSettings extends MySettings { { WRAP_ATTRIBUTES = CommonCodeStyleSettings.WRAP_ALWAYS; INSERT_LINE_BREAK_BEFORE_FIRST_ATTRIBUTE = true; } } }