aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTor Norbye <tnorbye@google.com>2011-08-19 17:58:27 -0700
committerAndroid Code Review <code-review@android.com>2011-08-19 17:58:27 -0700
commit922e0f13aa83e84727076c48db39df6dde54c951 (patch)
tree95ba4d68c2d3fdda1d04f2b702fd8e72c2a056b2
parent0ffb9316a5f0f12e28e3b72907e2f6b959136f99 (diff)
parentfb49439a6128d6812558d5089f9a4767cd4e3cc0 (diff)
downloadsdk-922e0f13aa83e84727076c48db39df6dde54c951.zip
sdk-922e0f13aa83e84727076c48db39df6dde54c951.tar.gz
sdk-922e0f13aa83e84727076c48db39df6dde54c951.tar.bz2
Merge "Add Unwrap Refactoring"
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java16
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java22
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java2
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java3
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapAction.java47
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapContribution.java40
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java239
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapWizard.java31
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java94
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoringTest.java58
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt1
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap1.xml20
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap2.xml20
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap3.xml19
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.info4
-rw-r--r--eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.xml26
18 files changed, 651 insertions, 14 deletions
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
index 3d4621c..48f9966 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/plugin.xml
@@ -686,6 +686,15 @@
tooltip="Wraps Views in a new container">
</action>
<action
+ class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.UnwrapAction"
+ definitionId="com.android.ide.eclipse.adt.refactoring.unwrap"
+ id="com.android.ide.eclipse.adt.actions.Unwrap"
+ label="Remove Container..."
+ menubarPath="org.eclipse.jdt.ui.refactoring.menu/com.android.ide.eclipse.adt.refactoring.menu/android"
+ style="push"
+ tooltip="Unwraps Views by Removing their container">
+ </action>
+ <action
class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutAction"
definitionId="com.android.ide.eclipse.adt.refactoring.convert"
id="com.android.ide.eclipse.adt.actions.ChangeLayout"
@@ -838,6 +847,12 @@
</command>
<command
categoryId="com.android.ide.eclipse.adt.refactoring.category"
+ description="Unwraps Views From Their Container"
+ id="com.android.ide.eclipse.adt.refactoring.unwrap"
+ name="Remove Container">
+ </command>
+ <command
+ categoryId="com.android.ide.eclipse.adt.refactoring.category"
description="Converts Layouts from One Type to Another"
id="com.android.ide.eclipse.adt.refactoring.convert"
name="Change Layout">
@@ -867,6 +882,10 @@
id="com.android.ide.eclipse.adt.refactoring.wrapin">
</contribution>
<contribution
+ class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.UnwrapContribution"
+ id="com.android.ide.eclipse.adt.refactoring.unwrap">
+ </contribution>
+ <contribution
class="com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLayoutContribution"
id="com.android.ide.eclipse.adt.refactoring.convert">
</contribution>
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java
index 60a567e..ad0981b 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/formatting/AndroidXmlFormattingStrategy.java
@@ -146,7 +146,17 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
}
}
- private TextEdit format(IStructuredModel model, int start, int length) {
+ /**
+ * Creates a {@link TextEdit} for formatting the given model's XML in the text range
+ * starting at offset start with the given length. Note that the exact formatting
+ * offsets may be adjusted to format a complete element.
+ *
+ * @param model the model to be formatted
+ * @param start the starting offset
+ * @param length the length of the text range to be formatted
+ * @return a {@link TextEdit} which edits the model into a formatted document
+ */
+ public static TextEdit format(IStructuredModel model, int start, int length) {
int end = start + length;
TextEdit edit = new MultiTextEdit();
@@ -348,7 +358,7 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
* adjusted (for example to make the edit smaller if the beginning and/or end is
* identical, and so on)
*/
- private ReplaceEdit createReplaceEdit(IStructuredDocument document, int replaceStart,
+ private static ReplaceEdit createReplaceEdit(IStructuredDocument document, int replaceStart,
int replaceEnd, String formatted, XmlFormatPreferences prefs) {
// If replacing a node somewhere in the middle, start the replacement at the
// beginning of the current line
@@ -488,7 +498,7 @@ public class AndroidXmlFormattingStrategy extends ContextBasedFormattingStrategy
/**
* Guess what style to use to edit the given document - layout, resource, manifest, ... ? */
- private XmlFormatStyle guessStyle(IStructuredModel model, Document domDocument) {
+ private static XmlFormatStyle guessStyle(IStructuredModel model, Document domDocument) {
// The "layout" style is used for most XML resource file types:
// layouts, color-lists and state-lists, animations, drawables, menus, etc
XmlFormatStyle style = XmlFormatStyle.LAYOUT;
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
index c66ae33..8fa82e1 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DomUtilities.java
@@ -741,18 +741,30 @@ public class DomUtilities {
* @return the DOM document, or null
*/
public static Document parseStructuredDocument(String xml) {
+ IStructuredModel model = createStructuredModel(xml);
+ if (model instanceof IDOMModel) {
+ IDOMModel domModel = (IDOMModel) model;
+ return domModel.getDocument();
+ }
+
+ return null;
+ }
+
+ /**
+ * Parses the given XML string and builds an Eclipse structured model for it.
+ *
+ * @param xml the XML content to be parsed (must be well formed)
+ * @return the structured model
+ */
+ public static IStructuredModel createStructuredModel(String xml) {
IModelManager modelManager = StructuredModelManager.getModelManager();
IStructuredModel model = modelManager.createUnManagedStructuredModelFor(ContentTypeID_XML);
IStructuredDocument document = model.getStructuredDocument();
model.aboutToChangeModel();
document.set(xml);
model.changedModel();
- if (model instanceof IDOMModel) {
- IDOMModel domModel = (IDOMModel) model;
- return domModel.getDocument();
- }
- return null;
+ return model;
}
/**
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java
index 7a2f7d5..c641ed7 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/gle2/DynamicContextMenu.java
@@ -39,6 +39,7 @@ import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeLay
import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ChangeViewAction;
import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractIncludeAction;
import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.ExtractStyleAction;
+import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.UnwrapAction;
import com.android.ide.eclipse.adt.internal.editors.layout.refactoring.WrapInAction;
import com.android.ide.eclipse.adt.internal.editors.layout.uimodel.UiViewElementNode;
@@ -238,6 +239,9 @@ class DynamicContextMenu {
mMenuManager.insertBefore(endId, ExtractIncludeAction.create(mEditor));
mMenuManager.insertBefore(endId, ExtractStyleAction.create(mEditor));
mMenuManager.insertBefore(endId, WrapInAction.create(mEditor));
+ if (selection.size() == 1 && !(selection.get(0).isRoot())) {
+ mMenuManager.insertBefore(endId, UnwrapAction.create(mEditor));
+ }
if (selection.size() == 1 && (selection.get(0).isLayout() ||
selection.get(0).getViewInfo().getName().equals(FQCN_GESTURE_OVERLAY_VIEW))) {
mMenuManager.insertBefore(endId, ChangeLayoutAction.create(mEditor));
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
index 4baad1d..38a40f8 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RefactoringAssistant.java
@@ -162,6 +162,8 @@ public class RefactoringAssistant implements IQuickAssistProcessor {
new RefactoringProposal(editor,
new WrapInRefactoring(file, editor, textSelection, null)),
new RefactoringProposal(editor,
+ new UnwrapRefactoring(file, editor, textSelection, null)),
+ new RefactoringProposal(editor,
new ChangeViewRefactoring(file, editor, textSelection, null)),
new RefactoringProposal(editor,
new ChangeLayoutRefactoring(file, editor, textSelection, null)),
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
index 51bbc38..c9cb32c 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/RelativeLayoutConversionHelper.java
@@ -207,7 +207,8 @@ class RelativeLayoutConversionHelper {
private void deleteRemovedElements(List<Element> delete) {
if (mFlatten && delete.size() > 0) {
for (Element element : delete) {
- mRefactoring.removeElementTags(mRootEdit, element, delete);
+ mRefactoring.removeElementTags(mRootEdit, element, delete,
+ true /*changeIndentation*/);
}
}
}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapAction.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapAction.java
new file mode 100644
index 0000000..55c8b1f
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapAction.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+
+import org.eclipse.jface.action.IAction;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizard;
+import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
+
+/**
+ * Action executed when the "Remove Container" menu item is invoked.
+ */
+public class UnwrapAction extends VisualRefactoringAction {
+ @Override
+ public void run(IAction action) {
+ if ((mTextSelection != null || mTreeSelection != null) && mFile != null) {
+ UnwrapRefactoring ref = new UnwrapRefactoring(mFile, mEditor,
+ mTextSelection, mTreeSelection);
+ RefactoringWizard wizard = new UnwrapWizard(ref, mEditor);
+ RefactoringWizardOpenOperation op = new RefactoringWizardOpenOperation(wizard);
+ try {
+ op.run(mWindow.getShell(), wizard.getDefaultPageTitle());
+ } catch (InterruptedException e) {
+ // Interrupted. Pass.
+ }
+ }
+ }
+
+ public static IAction create(LayoutEditor editor) {
+ return create("Remove Container...", editor, UnwrapAction.class);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapContribution.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapContribution.java
new file mode 100644
index 0000000..0869fd6
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapContribution.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+
+import org.eclipse.ltk.core.refactoring.RefactoringContribution;
+import org.eclipse.ltk.core.refactoring.RefactoringDescriptor;
+
+import java.util.Map;
+
+public class UnwrapContribution extends RefactoringContribution {
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public RefactoringDescriptor createDescriptor(String id, String project, String description,
+ String comment, Map arguments, int flags) throws IllegalArgumentException {
+ return new UnwrapRefactoring.Descriptor(project, description, comment, arguments);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public Map retrieveArgumentMap(RefactoringDescriptor descriptor) {
+ if (descriptor instanceof UnwrapRefactoring.Descriptor) {
+ return ((UnwrapRefactoring.Descriptor) descriptor).getArguments();
+ }
+ return super.retrieveArgumentMap(descriptor);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java
new file mode 100644
index 0000000..f9a4c07
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoring.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+
+import static com.android.ide.common.layout.LayoutConstants.ANDROID_URI;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_HEIGHT;
+import static com.android.ide.common.layout.LayoutConstants.ATTR_LAYOUT_WIDTH;
+import static com.android.ide.eclipse.adt.AdtConstants.EXT_XML;
+
+import com.android.annotations.VisibleForTesting;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+import com.android.ide.eclipse.adt.internal.editors.layout.gle2.DomUtilities;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.OperationCanceledException;
+import org.eclipse.jface.text.ITextSelection;
+import org.eclipse.jface.viewers.ITreeSelection;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.Refactoring;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.eclipse.ltk.core.refactoring.TextFileChange;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.wst.sse.core.internal.provisional.IndexedRegion;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Removes the layout surrounding the current selection (or if the current selection has
+ * children, removes the current layout), and migrates namespace and layout attributes.
+ */
+@SuppressWarnings("restriction") // XML model
+public class UnwrapRefactoring extends VisualRefactoring {
+ private Element mContainer;
+
+ /**
+ * This constructor is solely used by {@link Descriptor},
+ * to replay a previous refactoring.
+ * @param arguments argument map created by #createArgumentMap.
+ */
+ UnwrapRefactoring(Map<String, String> arguments) {
+ super(arguments);
+ }
+
+ public UnwrapRefactoring(IFile file, LayoutEditor editor, ITextSelection selection,
+ ITreeSelection treeSelection) {
+ super(file, editor, selection, treeSelection);
+ }
+
+ @VisibleForTesting
+ UnwrapRefactoring(List<Element> selectedElements, LayoutEditor editor) {
+ super(selectedElements, editor);
+ }
+
+ @Override
+ public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException,
+ OperationCanceledException {
+ RefactoringStatus status = new RefactoringStatus();
+
+ try {
+ pm.beginTask("Checking preconditions...", 6);
+
+ if (mSelectionStart == -1 || mSelectionEnd == -1) {
+ status.addFatalError("No selection to wrap");
+ return status;
+ }
+
+ // Make sure that the selection all has the same parent?
+ if (mElements.size() == 0) {
+ status.addFatalError("Nothing to unwrap");
+ return status;
+ }
+
+ Element first = mElements.get(0);
+
+ // Determine the element of the container to be removed.
+ // If you've selected a non-container, or you've selected multiple
+ // elements, then it's the parent which should be removed. Otherwise,
+ // it's the selection itself which represents the container.
+ boolean useParent = mElements.size() > 1;
+ if (!useParent) {
+ if (DomUtilities.getChildren(first).size() == 0) {
+ useParent = true;
+ }
+ }
+ Node parent = first.getParentNode();
+ if (parent instanceof Document) {
+ mContainer = first;
+ List<Element> elements = DomUtilities.getChildren(mContainer);
+ if (elements.size() == 0) {
+ status.addFatalError(
+ "Cannot remove container when it has no children");
+ return status;
+ }
+ } else if (useParent && (parent instanceof Element)) {
+ mContainer = (Element) parent;
+ } else {
+ mContainer = first;
+ }
+
+ for (Element element : mElements) {
+ if (element.getParentNode() != parent) {
+ status.addFatalError(
+ "All unwrapped elements must share the same parent element");
+ return status;
+ }
+ }
+
+ // Ensure that if we are removing the root, that it has only one child
+ // such that there is a new single root
+ if (mContainer.getParentNode() instanceof Document) {
+ if (DomUtilities.getChildren(mContainer).size() > 1) {
+ status.addFatalError(
+ "Cannot remove root: it has more than one child "
+ + "which would result in multiple new roots");
+ return status;
+ }
+ }
+
+ pm.worked(1);
+ return status;
+
+ } finally {
+ pm.done();
+ }
+ }
+
+ @Override
+ protected VisualRefactoringDescriptor createDescriptor() {
+ String comment = getName();
+ return new Descriptor(
+ mProject.getName(), //project
+ comment, //description
+ comment, //comment
+ createArgumentMap());
+ }
+
+ @Override
+ public String getName() {
+ return "Remove Container";
+ }
+
+ @Override
+ protected List<Change> computeChanges(IProgressMonitor monitor) {
+ // (1) If the removed parent is the root container, transfer its
+ // namespace declarations
+ // (2) Remove the root element completely
+ // (3) Transfer layout attributes?
+ // (4) Check for Java R.file usages?
+
+ IFile file = mEditor.getInputFile();
+ List<Change> changes = new ArrayList<Change>();
+ MultiTextEdit rootEdit = new MultiTextEdit();
+
+ // Transfer namespace elements?
+ if (mContainer.getParentNode() instanceof Document) {
+ List<Element> elements = DomUtilities.getChildren(mContainer);
+ assert elements.size() == 1;
+ Element newRoot = elements.get(0);
+
+ List<Attr> declarations = findNamespaceAttributes(mContainer);
+ for (Attr attribute : declarations) {
+ if (attribute instanceof IndexedRegion) {
+ setAttribute(rootEdit, newRoot, attribute.getNamespaceURI(),
+ attribute.getPrefix(), attribute.getLocalName(), attribute.getValue());
+ }
+ }
+ }
+
+ // Transfer layout_ attributes (other than width and height)
+ List<Element> children = DomUtilities.getChildren(mContainer);
+ if (children.size() == 1) {
+ List<Attr> layoutAttributes = findLayoutAttributes(mContainer);
+ for (Attr attribute : layoutAttributes) {
+ String name = attribute.getLocalName();
+ if ((name.equals(ATTR_LAYOUT_WIDTH) || name.equals(ATTR_LAYOUT_HEIGHT))
+ && ANDROID_URI.equals(attribute.getNamespaceURI())) {
+ // Already handled specially
+ continue;
+ }
+ }
+ }
+
+ // Remove the root
+ removeElementTags(rootEdit, mContainer, Collections.<Element>emptyList() /* skip */,
+ false /*changeIndentation*/);
+
+ MultiTextEdit formatted = reformat(rootEdit, XmlFormatStyle.LAYOUT);
+ if (formatted != null) {
+ rootEdit = formatted;
+ }
+
+ TextFileChange change = new TextFileChange(file.getName(), file);
+ change.setEdit(rootEdit);
+ change.setTextType(EXT_XML);
+ changes.add(change);
+ return changes;
+ }
+
+ @Override
+ VisualRefactoringWizard createWizard() {
+ return new UnwrapWizard(this, mEditor);
+ }
+
+ public static class Descriptor extends VisualRefactoringDescriptor {
+ public Descriptor(String project, String description, String comment,
+ Map<String, String> arguments) {
+ super("com.android.ide.eclipse.adt.refactoring.unwrap", //$NON-NLS-1$
+ project, description, comment, arguments);
+ }
+
+ @Override
+ protected Refactoring createRefactoring(Map<String, String> args) {
+ return new UnwrapRefactoring(args);
+ }
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapWizard.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapWizard.java
new file mode 100644
index 0000000..efa11e0
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapWizard.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+
+import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
+
+public class UnwrapWizard extends VisualRefactoringWizard {
+ public UnwrapWizard(UnwrapRefactoring ref, LayoutEditor editor) {
+ super(ref, editor);
+ setDefaultPageTitle("Remove Container");
+ }
+
+ @Override
+ protected void addUserInputPages() {
+ // This refactoring takes no parameters
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
index 49ab894..b7ed71e 100644
--- a/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
+++ b/eclipse/plugins/com.android.ide.eclipse.adt/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/VisualRefactoring.java
@@ -30,6 +30,9 @@ import static com.android.ide.eclipse.adt.internal.editors.descriptors.XmlnsAttr
import com.android.annotations.VisibleForTesting;
import com.android.ide.eclipse.adt.AdtPlugin;
import com.android.ide.eclipse.adt.internal.editors.AndroidXmlEditor;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatPreferences;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlFormatStyle;
+import com.android.ide.eclipse.adt.internal.editors.formatting.XmlPrettyPrinter;
import com.android.ide.eclipse.adt.internal.editors.layout.LayoutEditor;
import com.android.ide.eclipse.adt.internal.editors.layout.configuration.ConfigurationComposite;
import com.android.ide.eclipse.adt.internal.editors.layout.descriptors.ViewElementDescriptor;
@@ -52,6 +55,7 @@ import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.viewers.ITreeSelection;
@@ -1036,7 +1040,8 @@ public abstract class VisualRefactoring extends Refactoring {
*
* TODO: Rename this to "unwrap" ? And allow for handling nested deletions.
*/
- protected void removeElementTags(MultiTextEdit rootEdit, Element element, List<Element> skip) {
+ protected void removeElementTags(MultiTextEdit rootEdit, Element element, List<Element> skip,
+ boolean changeIndentation) {
IndexedRegion elementRegion = getRegion(element);
if (elementRegion == null) {
return;
@@ -1069,8 +1074,6 @@ public abstract class VisualRefactoring extends Refactoring {
}
}
-
-
// Find the close tag
// Look at all attribute values and look for an id reference match
region = doc.getRegionAtCharacterOffset(elementRegion.getEndOffset()
@@ -1096,7 +1099,7 @@ public abstract class VisualRefactoring extends Refactoring {
}
// Dedent the contents
- if (startLineInclusive != -1 && endLineInclusive != -1) {
+ if (changeIndentation && startLineInclusive != -1 && endLineInclusive != -1) {
String indent = AndroidXmlEditor.getIndentAtOffset(doc, getRegion(element)
.getStartOffset());
setIndentation(rootEdit, indent, doc, startLineInclusive, endLineInclusive,
@@ -1217,7 +1220,7 @@ public abstract class VisualRefactoring extends Refactoring {
}
if (deleteLine) {
startOffset = lineBegin;
- endOffset = lineEnd + 1;
+ endOffset = Math.min(doc.getLength(), lineEnd + 1);
}
} catch (BadLocationException e) {
AdtPlugin.log(e, null);
@@ -1227,6 +1230,87 @@ public abstract class VisualRefactoring extends Refactoring {
return new DeleteEdit(startOffset, endOffset - startOffset);
}
+ /**
+ * Rewrite the edits in the given {@link MultiTextEdit} such that same edits are
+ * applied, but the resulting range is also formatted
+ */
+ protected MultiTextEdit reformat(MultiTextEdit edit, XmlFormatStyle style) {
+ IDocument document = new org.eclipse.jface.text.Document();
+ String xml = mEditor.getStructuredDocument().get();
+ document.set(xml);
+
+ try {
+ edit.apply(document);
+ } catch (MalformedTreeException e) {
+ AdtPlugin.log(e, null);
+ return null; // Abort formatting
+ } catch (BadLocationException e) {
+ AdtPlugin.log(e, null);
+ return null; // Abort formatting
+ }
+
+ String actual = document.get();
+
+ // TODO: Try to format only the affected portion of the document.
+ // To do that we need to find out what the affected offsets are; we know
+ // the MultiTextEdit's affected range, but that is referring to offsets
+ // in the old document. Use that to compute offsets in the new document.
+ //int distanceFromEnd = actual.length() - edit.getExclusiveEnd();
+ //IStructuredModel model = DomUtilities.createStructuredModel(actual);
+ //int start = edit.getOffset();
+ //int end = actual.length() - distanceFromEnd;
+ //int length = end - start;
+ //TextEdit format = AndroidXmlFormattingStrategy.format(model, start, length);
+ XmlFormatPreferences formatPrefs = XmlFormatPreferences.create();
+ String formatted = XmlPrettyPrinter.prettyPrint(actual, formatPrefs, style,
+ null /*lineSeparator*/);
+
+
+ // Figure out how much of the before and after strings are identical and narrow
+ // the replacement scope
+ boolean foundDifference = false;
+ int firstDifference = 0;
+ int lastDifference = formatted.length();
+ int start = 0;
+ int end = xml.length();
+
+ for (int i = 0, j = start; i < formatted.length() && j < end; i++, j++) {
+ if (formatted.charAt(i) != xml.charAt(j)) {
+ firstDifference = i;
+ foundDifference = true;
+ break;
+ }
+ }
+
+ if (!foundDifference) {
+ // No differences - the document is already formatted, nothing to do
+ return null;
+ }
+
+ lastDifference = firstDifference + 1;
+ for (int i = formatted.length() - 1, j = end - 1;
+ i > firstDifference && j > start;
+ i--, j--) {
+ if (formatted.charAt(i) != xml.charAt(j)) {
+ lastDifference = i + 1;
+ break;
+ }
+ }
+
+ start += firstDifference;
+ end -= (formatted.length() - lastDifference);
+ end = Math.max(start, end);
+ formatted = formatted.substring(firstDifference, lastDifference);
+
+ ReplaceEdit format = new ReplaceEdit(start, end - start,
+ formatted);
+
+ MultiTextEdit newEdit = new MultiTextEdit();
+ newEdit.addChild(format);
+
+ return newEdit;
+ }
+
protected ViewElementDescriptor getElementDescriptor(String fqcn) {
AndroidTargetData data = mEditor.getTargetData();
if (data != null) {
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoringTest.java b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoringTest.java
new file mode 100644
index 0000000..0541a63
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/UnwrapRefactoringTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Eclipse Public License, Version 1.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.eclipse.org/org/documents/epl-v10.php
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.ide.eclipse.adt.internal.editors.layout.refactoring;
+
+import org.eclipse.core.resources.IFile;
+import org.eclipse.core.runtime.NullProgressMonitor;
+import org.eclipse.ltk.core.refactoring.Change;
+import org.eclipse.ltk.core.refactoring.RefactoringStatus;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+public class UnwrapRefactoringTest extends RefactoringTest {
+
+ public void testUnwrap1() throws Exception {
+ // Unwrap view with parent, no children - this will unwrap the parent (frame layout)
+ checkRefactoring("unwrap.xml", "@+id/button");
+ }
+
+ public void testUnwrap2() throws Exception {
+ // Unwrap view with parent and children; this should unwrap the element itself
+ checkRefactoring("unwrap.xml", "@+id/frame");
+ }
+
+ public void testUnwrap3() throws Exception {
+ // Unwrap root: should transfer namespace
+ checkRefactoring("unwrap.xml", "@+id/linear");
+ }
+
+ private void checkRefactoring(String basename, String id) throws Exception {
+ IFile file = getLayoutFile(getProject(), basename);
+ TestContext info = setupTestContext(file, basename);
+ TestLayoutEditor layoutEditor = info.mLayoutEditor;
+ List<Element> selectedElements = getElements(info.mElement, id);
+ assertEquals(1, selectedElements.size());
+
+ UnwrapRefactoring refactoring = new UnwrapRefactoring(selectedElements,
+ layoutEditor);
+
+ RefactoringStatus status = refactoring.checkInitialConditions(new NullProgressMonitor());
+ assertFalse(status.hasError());
+ List<Change> changes = refactoring.computeChanges(new NullProgressMonitor());
+ checkEdits(basename, changes);
+ }
+}
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt
index 95187d3..0eb1cd2 100644
--- a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/sample1a-expected-assistant2.txt
@@ -1,5 +1,6 @@
Quick assistant in sample1a.xml for <Bu^tton android:text:
Wrap in Container : Initiates the given refactoring operation
+Remove Container : Initiates the given refactoring operation
Change Widget Type : Initiates the given refactoring operation
Change Layout : Initiates the given refactoring operation
Extract Style : Initiates the given refactoring operation
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap1.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap1.xml
new file mode 100644
index 0000000..e5aba11
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap1.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+
+ <Button
+ android:id="@+id/button"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap2.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap2.xml
new file mode 100644
index 0000000..e5aba11
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap2.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+
+ <Button
+ android:id="@+id/button"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+
+</LinearLayout> \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap3.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap3.xml
new file mode 100644
index 0000000..348b2bc
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap-expected-unwrap3.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/frame"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+
+ <Button
+ android:id="@+id/button"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+
+</FrameLayout> \ No newline at end of file
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.info b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.info
new file mode 100644
index 0000000..c5461b3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.info
@@ -0,0 +1,4 @@
+android.widget.LinearLayout [0,56,1280,104] <LinearLayout>
+ android.widget.FrameLayout [0,0,1280,48] <FrameLayout>
+ android.widget.TextView [0,0,1280,17] <TextView>
+ android.widget.Button [0,0,1280,48] <Button>
diff --git a/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.xml b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.xml
new file mode 100644
index 0000000..db94dc3
--- /dev/null
+++ b/eclipse/plugins/com.android.ide.eclipse.tests/src/com/android/ide/eclipse/adt/internal/editors/layout/refactoring/testdata/unwrap.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/linear"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical" >
+
+ <FrameLayout
+ android:id="@+id/frame"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" >
+
+ <TextView
+ android:id="@+id/text"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+
+ <Button
+ android:id="@+id/button"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/hello" />
+ </FrameLayout>
+
+</LinearLayout>