OSDN Git Service

Fix and turn on the auto-format XML option
authorTor Norbye <tnorbye@google.com>
Fri, 12 Aug 2011 21:55:31 +0000 (14:55 -0700)
committerTor Norbye <tnorbye@google.com>
Fri, 19 Aug 2011 23:32:18 +0000 (16:32 -0700)
This changeset turns on the option to "Automatically format the XML
edited by the visual layout editor" by default.

It also fixes several issues related to it:

- First, the auto-format code was previously hooked up to synchronous
  insert and delete node events, which meant during a larger edit it
  would apply formatting repeatedly and before the edit was complete
  (which made the DOM model keep reparsing to update document based
  back into its internal model).

- Another problem was that the auto format only applied to added and
  removed nodes; attribute edits were not considered (which made sense
  given that the old formatter did not support reordering of
  attributes, but now that it does attribute changes requires
  reformatting as well.)

- The old reformatter was trying to work with the Eclipse XML
  formatter, which does not handle partial document formats well
  (there were a bunch of workarounds, and even with those there were
  some problems). The new formatter does not, so I've changed the
  option to be tied to the custom formatter; the automatic formatting
  of layout editor changes is now enabled only when the custom editor
  is enabled.

  This also takes advantage of the new formatter's ability to only
  format the attributes section of a document, so if you just tweak
  the attribute of a node, the children's formatting is not affected.

- The formatter would apply to more than just the layout editor; any
  UI editor which used UiElementNodes would be affected. I've added a
  method, by default false, where each UI editor can opt into this.
  I'd like to get Raphael's feedback on the manifest editor before
  possibly adding it for that editor as well (and updating the option
  name to something more generic).

- The mechanism for ignoring XML document changes (already used for
  refactoring) is now also connected to reformatting, so when the
  formatter runs it sets an "ignore" flag such that there is no
  second XML model loading when the changes are just as a result
  of a formatting option.

This changeset also tweaks the formatting behavior of comments;
instead of -always- adding a blank line before line comments (other
than suffix comments), it now considers the previous document and
preserves the spacing used there (though it will collapse multiple
lines into one).

Change-Id: I04c2b80836c4d9874dca789c2f2cbc8c8c7cfa76

eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/AndroidXmlEditor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlFormatPreferences.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinter.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/LayoutEditor.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/uimodel/UiElementNode.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/AdtPrefs.java
eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/preferences/EditorsPage.java
eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/formatting/XmlPrettyPrinterTest.java

index 7b7dc26..c854b48 100644 (file)
@@ -139,6 +139,18 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
     private int mIsEditXmlModelPending;
 
     /**
+     * Usually null, but during an editing operation, represents the highest
+     * node which should be formatted when the editing operation is complete.
+     */
+    private UiElementNode mFormatNode;
+
+    /**
+     * Whether {@link #mFormatNode} should be formatted recursively, or just
+     * the node itself (its arguments)
+     */
+    private boolean mFormatChildren;
+
+    /**
      * Creates a form editor.
      * <p/>The editor will setup a {@link ITargetChangeListener} and call
      * {@link #initUiRootNode(boolean)}, when the SDK or the target changes.
@@ -896,6 +908,35 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
                     // Notify the model we're done modifying it. This must *always* be executed.
                     model.changedModel();
 
+                    if (AdtPrefs.getPrefs().getFormatGuiXml() && mFormatNode != null) {
+                        if (!mFormatNode.hasError()) {
+                            if (mFormatNode == getUiRootNode()) {
+                                reformatDocument();
+                            } else {
+                                Node node = mFormatNode.getXmlNode();
+                                if (node instanceof IndexedRegion) {
+                                    IndexedRegion region = (IndexedRegion) node;
+                                    int begin = region.getStartOffset();
+                                    int end = region.getEndOffset();
+
+                                    if (!mFormatChildren) {
+                                        // This will format just the attribute list
+                                        end = begin + 1;
+                                    }
+
+                                    model.aboutToChangeModel();
+                                    try {
+                                        reformatRegion(begin, end);
+                                    } finally {
+                                        model.changedModel();
+                                    }
+                                }
+                            }
+                        }
+                        mFormatNode = null;
+                        mFormatChildren = false;
+                    }
+
                     // Clean up the undo unit. This is done more than once as explained
                     // above for beginRecording.
                     for (int i = 0; i < undoReverseCount; i++) {
@@ -917,6 +958,55 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
     }
 
     /**
+     * Does this editor participate in the "format GUI editor changes" option?
+     *
+     * @return true if this editor supports automatically formatting XML
+     *         affected by GUI changes
+     */
+    public boolean supportsFormatOnGuiEdit() {
+        return false;
+    }
+
+    /**
+     * Mark the given node as needing to be formatted when the current edits are
+     * done, provided the user has turned that option on (see
+     * {@link AdtPrefs#getFormatGuiXml()}).
+     *
+     * @param node the node to be scheduled for formatting
+     * @param attributesOnly if true, only update the attributes list of the
+     *            node, otherwise update the node recursively (e.g. all children
+     *            too)
+     */
+    public void scheduleNodeReformat(UiElementNode node, boolean attributesOnly) {
+        if (!supportsFormatOnGuiEdit()) {
+            return;
+        }
+
+        if (node == mFormatNode) {
+            if (!attributesOnly) {
+                mFormatChildren = true;
+            }
+        } else if (mFormatNode == null) {
+            mFormatNode = node;
+            mFormatChildren = !attributesOnly;
+        } else {
+            if (mFormatNode.isAncestorOf(node)) {
+                mFormatChildren = true;
+            } else if (node.isAncestorOf(mFormatNode)) {
+                mFormatNode = node;
+                mFormatChildren = true;
+            } else {
+                // Two independent nodes; format their closest common ancestor.
+                // Later we could consider having a small number of independent nodes
+                // and formatting those, and only switching to formatting the common ancestor
+                // when the number of individual nodes gets large.
+                mFormatChildren = true;
+                mFormatNode = UiElementNode.getCommonAncestor(mFormatNode, node);
+            }
+        }
+    }
+
+    /**
      * Creates an "undo recording" session by calling the undoableAction runnable
      * under an undo session.
      * <p/>
@@ -1104,31 +1194,35 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
         end = Math.min(end, documentLength);
         begin = Math.min(begin, end);
 
-        // It turns out the XML formatter does *NOT* format things correctly if you
-        // select just a region of text. You *MUST* also include the leading whitespace
-        // on the line, or it will dedent all the content to column 0. Therefore,
-        // we must figure out the offset of the start of the line that contains the
-        // beginning of the tag.
-        try {
-            IRegion lineInformation = document.getLineInformationOfOffset(begin);
-            if (lineInformation != null) {
-                int lineBegin = lineInformation.getOffset();
-                if (lineBegin != begin) {
-                    begin = lineBegin;
-                } else if (begin > 0) {
-                    // Trick #2: It turns out that, if an XML element starts in column 0,
-                    // then the XML formatter will NOT indent it (even if its parent is
-                    // indented). If you on the other hand include the end of the previous
-                    // line (the newline), THEN the formatter also correctly inserts the
-                    // element. Therefore, we adjust the beginning range to include the
-                    // previous line (if we are not already in column 0 of the first line)
-                    // in the case where the element starts the line.
-                    begin--;
+        if (!AdtPrefs.getPrefs().getUseCustomXmlFormatter()) {
+            // Workarounds which only apply to the builtin Eclipse formatter:
+            //
+            // It turns out the XML formatter does *NOT* format things correctly if you
+            // select just a region of text. You *MUST* also include the leading whitespace
+            // on the line, or it will dedent all the content to column 0. Therefore,
+            // we must figure out the offset of the start of the line that contains the
+            // beginning of the tag.
+            try {
+                IRegion lineInformation = document.getLineInformationOfOffset(begin);
+                if (lineInformation != null) {
+                    int lineBegin = lineInformation.getOffset();
+                    if (lineBegin != begin) {
+                        begin = lineBegin;
+                    } else if (begin > 0) {
+                        // Trick #2: It turns out that, if an XML element starts in column 0,
+                        // then the XML formatter will NOT indent it (even if its parent is
+                        // indented). If you on the other hand include the end of the previous
+                        // line (the newline), THEN the formatter also correctly inserts the
+                        // element. Therefore, we adjust the beginning range to include the
+                        // previous line (if we are not already in column 0 of the first line)
+                        // in the case where the element starts the line.
+                        begin--;
+                    }
                 }
+            } catch (BadLocationException e) {
+                // This cannot happen because we already clamped the offsets
+                AdtPlugin.log(e, e.toString());
             }
-        } catch (BadLocationException e) {
-            // This cannot happen because we already clamped the offsets
-            AdtPlugin.log(e, e.toString());
         }
 
         if (textViewer instanceof StructuredTextViewer) {
@@ -1138,7 +1232,17 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
             if (canFormat) {
                 StyledText textWidget = textViewer.getTextWidget();
                 textWidget.setSelection(begin, end);
-                structuredTextViewer.doOperation(operation);
+
+                try {
+                    // Formatting does not affect the XML model so ignore notifications
+                    // about model edits from this
+                    mIgnoreXmlUpdate = true;
+                    structuredTextViewer.doOperation(operation);
+                } finally {
+                    mIgnoreXmlUpdate = false;
+                }
+
+                textWidget.setSelection(0, 0);
             }
         }
     }
@@ -1171,7 +1275,14 @@ public abstract class AndroidXmlEditor extends FormEditor implements IResourceCh
             int operation = StructuredTextViewer.FORMAT_DOCUMENT;
             boolean canFormat = structuredTextViewer.canDoOperation(operation);
             if (canFormat) {
-                structuredTextViewer.doOperation(operation);
+                try {
+                    // Formatting does not affect the XML model so ignore notifications
+                    // about model edits from this
+                    mIgnoreXmlUpdate = true;
+                    structuredTextViewer.doOperation(operation);
+                } finally {
+                    mIgnoreXmlUpdate = false;
+                }
             }
         }
     }
index 077e20f..60a567e 100644 (file)
@@ -27,7 +27,6 @@ import static org.eclipse.wst.xml.core.internal.regions.DOMRegionContext.XML_TAG
 
 import com.android.ide.eclipse.adt.AdtPlugin;
 import com.android.ide.eclipse.adt.AdtUtils;
-import com.android.ide.eclipse.adt.internal.editors.AndroidXmlAutoEditStrategy;
 import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
 import com.android.ide.eclipse.adt.internal.editors.resources.descriptors.ResourcesDescriptors;
 import com.android.ide.eclipse.adt.internal.preferences.AdtPrefs;
@@ -83,6 +82,9 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
     private final LinkedList<TypedPosition> mPartitions = new LinkedList<TypedPosition>();
     private ContextBasedFormattingStrategy mDelegate = null;
 
+    /**
+     * Creates a new {@link AndroidXmlFormattingStrategy}
+     */
     public AndroidXmlFormattingStrategy() {
     }
 
index 0728974..dba33c3 100644 (file)
@@ -53,6 +53,12 @@ public class XmlFormatPreferences {
     private XmlFormatPreferences() {
     }
 
+    /**
+     * Creates a new {@link XmlFormatPreferences} based on the current settings
+     * in {@link AdtPrefs}
+     *
+     * @return an {@link XmlFormatPreferences} object
+     */
     public static XmlFormatPreferences create() {
         XmlFormatPreferences p = new XmlFormatPreferences();
         AdtPrefs prefs = AdtPrefs.getPrefs();
@@ -69,6 +75,11 @@ public class XmlFormatPreferences {
     // The XML module settings do not have a public API. We should replace this with JDT
     // settings anyway since that's more likely what users have configured and want applied
     // to their XML files
+    /**
+     * Returns the string to use to indent one indentation level
+     *
+     * @return the string used to indent one indentation level
+     */
     @SuppressWarnings({
             "restriction", "deprecation"
     })
index 7a58515..b1592d4 100644 (file)
@@ -322,21 +322,42 @@ public class XmlPrettyPrinter {
             }
         }
 
-        // Put the comment on a line on its own? Only if it does not follow some other comment
-        // (e.g. is the first child in an element or follows some other element only separated
-        // by whitespace)
+        // Put the comment on a line on its own? Only if it was separated by a blank line
+        // in the previous version of the document. In other words, if the document
+        // adds blank lines between comments this formatter will preserve that fact, and vice
+        // versa for a tightly formatted document it will preserve that convention as well.
         if (!mPrefs.removeEmptyLines && depth > 0 && !isSuffixComment) {
             Node curr = node.getPreviousSibling();
-            if (curr == null
-                    || curr.getNodeType() == Node.ELEMENT_NODE
-                    || (curr.getNodeType() == Node.TEXT_NODE
-                            && curr.getNodeValue().trim().length() == 0
-                            && (curr.getPreviousSibling() == null
-                               || curr.getPreviousSibling().getNodeType() == Node.ELEMENT_NODE))) {
+            if (curr == null) {
                 mOut.append(mLineSeparator);
+            } else if (curr.getNodeType() == Node.TEXT_NODE) {
+                String text = curr.getNodeValue();
+                // Count how many newlines we find in the trailing whitespace of the
+                // text node
+                int newLines = 0;
+                for (int i = text.length() - 1; i >= 0; i--) {
+                    char c = text.charAt(i);
+                    if (Character.isWhitespace(c)) {
+                        if (c == '\n') {
+                            newLines++;
+                            if (newLines == 2) {
+                                break;
+                            }
+                        }
+                    } else {
+                        break;
+                    }
+                }
+                if (newLines >= 2) {
+                    mOut.append(mLineSeparator);
+                } else if (text.trim().length() == 0 && curr.getPreviousSibling() == null) {
+                    // Comment before first child in node
+                    mOut.append(mLineSeparator);
+                }
             }
         }
 
+
         // TODO: Reformat the comment text?
         if (!multiLine) {
             if (!isSuffixComment) {
index 49b1aef..4ad7083 100644 (file)
@@ -330,6 +330,11 @@ public class LayoutEditor extends AndroidXmlEditor implements IShowEditorInput,
         mGraphicalEditor.recomputeLayout();
     }
 
+    @Override
+    public boolean supportsFormatOnGuiEdit() {
+        return true;
+    }
+
     /**
      * Returns the custom IContentOutlinePage or IPropertySheetPage when asked for it.
      */
index 59c07b1..c66ae33 100644 (file)
@@ -54,6 +54,9 @@ import java.util.Set;
 import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 
+/**
+ * Various utility methods for manipulating DOM nodes.
+ */
 @SuppressWarnings("restriction") // No replacement for restricted XML model yet
 public class DomUtilities {
     private static final String AMPERSAND_ENTITY = "&amp;"; //$NON-NLS-1$
index eb24870..41fbec5 100644 (file)
@@ -1082,9 +1082,7 @@ public class UiElementNode implements IPropertySource {
             node.setDirty(false);
         }
 
-        if (mUiParent != null) {
-            mUiParent.formatOnInsert(this);
-        }
+        getEditor().scheduleNodeReformat(this, false);
 
         invokeUiUpdateListeners(UiUpdateState.CREATED);
         return mXmlNode;
@@ -1121,10 +1119,6 @@ public class UiElementNode implements IPropertySource {
             xmlParent.removeChild(previousSibling);
         }
 
-        if (mUiParent != null) {
-            mUiParent.formatOnDeletion(this);
-        }
-
         invokeUiUpdateListeners(UiUpdateState.DELETED);
         return oldXmlNode;
     }
@@ -1615,6 +1609,17 @@ public class UiElementNode implements IPropertySource {
                 return result;
             }
 
+            if (AdtPrefs.getPrefs().getFormatGuiXml() && getEditor().supportsFormatOnGuiEdit()) {
+                // If auto formatting, don't bother with attribute sorting here since the
+                // order will be corrected as soon as the edit is committed anyway
+                for (UiAttributeNode uiAttribute : dirtyAttributes) {
+                    commitAttributeToXml(uiAttribute, uiAttribute.getCurrentValue());
+                    uiAttribute.setDirty(false);
+                }
+
+                return result;
+            }
+
             String firstName = dirtyAttributes.get(0).getDescriptor().getXmlLocalName();
             NamedNodeMap attributes = ((Element) element).getAttributes();
             List<Attr> move = new ArrayList<Attr>();
@@ -1806,6 +1811,8 @@ public class UiElementNode implements IPropertySource {
             value = ""; //$NON-NLS-1$ -- this removes an attribute
         }
 
+        getEditor().scheduleNodeReformat(this, true);
+
         // Try with all internal attributes
         UiAttributeNode uiAttr = setInternalAttrValue(
                 getAllUiAttributes(), attrXmlName, attrNsUri, value, override);
@@ -2055,75 +2062,45 @@ public class UiElementNode implements IPropertySource {
         }
     }
 
-    /** Handles reformatting of the XML buffer when a given node has been inserted.
-     *
-     * @param node The node that was inserted.
-     */
-    private void formatOnInsert(UiElementNode node) {
-        // Reformat parent if it's the first child (such that it for example can force
-        // children into their own lines.)
-        if (mUiChildren.size() == 1) {
-            reformat();
-        } else {
-            // In theory, we should ONLY have to reformat the node itself:
-            // uiNode.reformat();
-            //
-            // However, the XML formatter does not correctly handle this; in particular
-            // it will -dedent- a correctly indented child. Here's an example:
-            //
-            // @formatter:off
-            //    <?xml version="1.0" encoding="utf-8"?>
-            //    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-            //        android:layout_width="fill_parent" android:layout_height="fill_parent"
-            //        android:orientation="vertical">
-            //        <LinearLayout android:id="@+id/LinearLayout01"
-            //            android:layout_width="wrap_content" android:layout_height="wrap_content">
-            //            <Button android:id="@+id/Button03"></Button>
-            //        </LinearLayout>
-            //    </LinearLayout>
-            // @formatter:on
-            //
-            // If we  have just inserted the button inside the nested LinearLayout, and
-            // attempt to format it, it will incorrectly dedent the button to be flush with
-            // its parent.
-            //
-            // Therefore, for now, in this case, format the PARENT on insert. This means that
-            // siblings can be formatted as well, but that can't be helped.
-
-            // This should be "uiNode.reformat();" instead of "reformat()" if formatting
-            // worked correctly:
-            reformat();
-        }
-    }
-
     /**
-     * Handles reformatting of the XML buffer when a given node has been removed.
+     * Returns true if this node is an ancestor (parent, grandparent, and so on)
+     * of the given node. Note that a node is not considered an ancestor of
+     * itself.
      *
-     * @param node The node that was removed.
+     * @param node the node to test
+     * @return true if this node is an ancestor of the given node
      */
-    private void formatOnDeletion(UiElementNode node) {
-        // Reformat parent if it's the last child removed, such that we can for example
-        // place the closing element back on the same line as the opening tag (if the
-        // user has that mode configured in the formatting options.)
-        if (mUiChildren.size() <= 1) {
-            // <= 1 instead of == 0: turns out the parent hasn't always deleted
-            // this child from its its children list yet.
-            reformat();
+    public boolean isAncestorOf(UiElementNode node) {
+        node = node.getUiParent();
+        while (node != null) {
+            if (node == this) {
+                return true;
+            }
+            node = node.getUiParent();
         }
+        return false;
     }
 
     /**
-     * Reformats the XML corresponding to the given XML node. This will do nothing if we have
-     * errors, or if the user has turned off XML auto-formatting.
+     * Finds the nearest common parent of the two given nodes (which could be one of the
+     * two nodes as well)
+     *
+     * @param node1 the first node to test
+     * @param node2 the second node to test
+     * @return the nearest common parent of the two given nodes
      */
-    private void reformat() {
-        if (mHasError || !AdtPrefs.getPrefs().getFormatGuiXml()) {
-            return;
+    public static UiElementNode getCommonAncestor(UiElementNode node1, UiElementNode node2) {
+        while (node2 != null) {
+            UiElementNode current = node1;
+            while (current != null && current != node2) {
+                current = current.getUiParent();
+            }
+            if (current == node2) {
+                return current;
+            }
+            node2 = node2.getUiParent();
         }
 
-        AndroidXmlEditor editor = getEditor();
-        if (editor != null && mXmlNode != null) {
-            editor.reformatNode(mXmlNode);
-        }
+        return null;
     }
 }
index 435a101..287b1c3 100644 (file)
@@ -18,6 +18,7 @@ package com.android.ide.eclipse.adt.internal.preferences;
 
 
 import com.android.ide.eclipse.adt.AdtPlugin;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
 import com.android.prefs.AndroidLocation.AndroidLocationException;
 import com.android.sdklib.internal.build.DebugKeyProvider;
 import com.android.sdklib.internal.build.DebugKeyProvider.KeytoolException;
@@ -241,26 +242,68 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
         return mBuildForceResResfresh;
     }
 
+    /**
+     * Should changes made by GUI editors automatically format the corresponding XML nodes
+     * affected by the edit?
+     *
+     * @return true if the GUI editors should format affected XML regions
+     */
     public boolean getFormatGuiXml() {
-        return mFormatGuiXml;
+        // The format-GUI-editors flag only applies when the custom formatter is used,
+        // since the built-in formatter has problems editing partial documents
+        return mFormatGuiXml && mCustomXmlFormatter;
     }
 
+    /**
+     * Should the XML formatter use a custom Android XML formatter (following
+     * Android code style) or use the builtin Eclipse XML formatter?
+     *
+     * @return true if the Android formatter should be used instead of the
+     *         default Eclipse one
+     */
     public boolean getUseCustomXmlFormatter() {
         return mCustomXmlFormatter;
     }
 
+    /**
+     * Should the Android XML formatter use the Eclipse XML indentation settings
+     * (usually one tab character) instead of the default 4 space character
+     * indent?
+     *
+     * @return true if the Eclipse XML indentation settings should be use
+     */
     public boolean isUseEclipseIndent() {
         return mUseEclipseIndent;
     }
 
+    /**
+     * Should the Android XML formatter try to avoid inserting blank lines to
+     * make the format as compact as possible (no blank lines between elements,
+     * no blank lines surrounding comments, etc).
+     *
+     * @return true to remove blank lines
+     */
     public boolean isRemoveEmptyLines() {
         return mRemoveEmptyLines;
     }
 
+    /**
+     * Should the Android XML formatter attempt to place a single attribute on
+     * the same line as the element open tag?
+     *
+     * @return true if single-attribute elements should place the attribute on
+     *         the same line as the element open tag
+     */
     public boolean isOneAttributeOnFirstLine() {
         return mOneAttributeOnFirstLine;
     }
 
+    /**
+     * Returns the sort order to be applied to the attributes (one of which can
+     * be {@link AttributeSortOrder#NO_SORTING}).
+     *
+     * @return the sort order to apply to the attributes
+     */
     public AttributeSortOrder getAttributeSort() {
         if (mAttributeSort == null) {
             return AttributeSortOrder.LOGICAL;
@@ -268,10 +311,24 @@ public final class AdtPrefs extends AbstractPreferenceInitializer {
         return mAttributeSort;
     }
 
+    /**
+     * Returns whether a space should be inserted before the closing {@code >}
+     * character in open tags and before the closing {@code />} characters in
+     * empty tag. Note that the {@link XmlFormatStyle#RESOURCE} style overrides
+     * this setting to make it more compact for the {@code <item>} elements.
+     *
+     * @return true if an empty space should be inserted before {@code >} or
+     *         {@code />}.
+     */
     public boolean isSpaceBeforeClose() {
         return mSpaceBeforeClose;
     }
 
+    /**
+     * Returns whether the file should be automatically formatted on save.
+     *
+     * @return true if the XML files should be formatted on save.
+     */
     public boolean isFormatOnSave() {
         return mFormatOnSave;
     }
index 95b0826..1dbf996 100644 (file)
@@ -43,7 +43,11 @@ public class EditorsPage extends FieldEditorPreferencePage implements IWorkbench
     private BooleanFieldEditor mRemoveEmptyEditor;
     private BooleanFieldEditor mOneAttrPerLineEditor;
     private BooleanFieldEditor mSpaceBeforeCloseEditor;
+    private BooleanFieldEditor mFormatGuiXmlEditor;
 
+    /**
+     * Constructs a new Android editors preference page
+     */
     public EditorsPage() {
         super(GRID);
         setPreferenceStore(AdtPlugin.getDefault().getPreferenceStore());
@@ -113,9 +117,10 @@ public class EditorsPage extends FieldEditorPreferencePage implements IWorkbench
                 },
                 parent, true));
 
-        addField(new BooleanFieldEditor(AdtPrefs.PREFS_FORMAT_GUI_XML,
+        mFormatGuiXmlEditor = new BooleanFieldEditor(AdtPrefs.PREFS_FORMAT_GUI_XML,
                 "Automatically format the XML edited by the visual layout editor",
-                parent));
+                parent);
+        addField(mFormatGuiXmlEditor);
 
         addField(new BooleanFieldEditor(AdtPrefs.PREFS_FORMAT_ON_SAVE,
                 "Format on Save",
@@ -131,6 +136,7 @@ public class EditorsPage extends FieldEditorPreferencePage implements IWorkbench
         mRemoveEmptyEditor.setEnabled(enabled, parent);
         mOneAttrPerLineEditor.setEnabled(enabled, parent);
         mSpaceBeforeCloseEditor.setEnabled(enabled, parent);
+        mFormatGuiXmlEditor.setEnabled(enabled, parent);
     }
 
     /**
index 449f52d..0aa4af3 100644 (file)
@@ -363,7 +363,6 @@ public class XmlPrettyPrinterTest extends TestCase {
                 "<resources>\n" +
                 "\n" +
                 "    <dimen name=\"colorstrip_height\">6dip</dimen>\n" +
-                "\n" +
                 "    <!-- comment1 -->\n" +
                 "    <dimen name=\"title_height\">45dip</dimen>\n" +
                 "\n" +
@@ -439,5 +438,40 @@ public class XmlPrettyPrinterTest extends TestCase {
                 "</resources>");
     }
 
+    public void testLineCommentSpacing() throws Exception {
+        checkFormat(
+                XmlFormatStyle.RESOURCE,
+                "<resources>\n" +
+                "\n" +
+                "    <dimen name=\"colorstrip_height\">6dip</dimen>\n" +
+                "    <!-- comment1 -->\n" +
+                "    <dimen name=\"title_height\">45dip</dimen>\n" +
+                "    <!-- comment2: no newlines -->\n" +
+                "    <dimen name=\"now_playing_height\">90dip</dimen>\n" +
+                "    <dimen name=\"text_size_small\">14sp</dimen>\n" +
+                "\n" +
+                "    <!-- comment3: newline above and below -->\n" +
+                "\n" +
+                "    <dimen name=\"text_size_medium\">18sp</dimen>\n" +
+                "    <dimen name=\"text_size_large\">22sp</dimen>\n" +
+                "\n" +
+                "</resources>",
+
+                "<resources>\n" +
+                "\n" +
+                "    <dimen name=\"colorstrip_height\">6dip</dimen>\n" +
+                "    <!-- comment1 -->\n" +
+                "    <dimen name=\"title_height\">45dip</dimen>\n" +
+                "    <!-- comment2: no newlines -->\n" +
+                "    <dimen name=\"now_playing_height\">90dip</dimen>\n" +
+                "    <dimen name=\"text_size_small\">14sp</dimen>\n" +
+                "\n" +
+                "    <!-- comment3: newline above and below -->\n" +
+                "\n" +
+                "    <dimen name=\"text_size_medium\">18sp</dimen>\n" +
+                "    <dimen name=\"text_size_large\">22sp</dimen>\n" +
+                "\n" +
+                "</resources>");
+    }
 
 }