diff options
author | Tor Norbye <tnorbye@google.com> | 2011-08-19 17:58:27 -0700 |
---|---|---|
committer | Android Code Review <code-review@android.com> | 2011-08-19 17:58:27 -0700 |
commit | 922e0f13aa83e84727076c48db39df6dde54c951 (patch) | |
tree | 95ba4d68c2d3fdda1d04f2b702fd8e72c2a056b2 | |
parent | 0ffb9316a5f0f12e28e3b72907e2f6b959136f99 (diff) | |
parent | fb49439a6128d6812558d5089f9a4767cd4e3cc0 (diff) | |
download | sdk-922e0f13aa83e84727076c48db39df6dde54c951.zip sdk-922e0f13aa83e84727076c48db39df6dde54c951.tar.gz sdk-922e0f13aa83e84727076c48db39df6dde54c951.tar.bz2 |
Merge "Add Unwrap Refactoring"
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> |