From 58e83106fe24146318f7ebb5fb6c6e29ec67d0d6 Mon Sep 17 00:00:00 2001 From: Eugene Kudelevsky Date: Fri, 17 Aug 2012 14:38:34 +0400 Subject: [PATCH] IDEA-80865 extract as include refactoring --- ...oidDesignerRefactoringContextProvider.java | 27 +- .../AndroidDesignerEditorPanel.java | 8 +- .../propertyTable/editors/ResourceDialog.java | 9 +- .../messages/AndroidBundle.properties | 3 +- plugins/android/src/META-INF/plugin.xml | 6 +- .../actions/CreateResourceFileAction.java | 22 +- .../CreateFileResourceQuickFix.java | 6 +- .../AndroidBaseLayoutRefactoringAction.java | 9 +- .../AndroidBaseXmlRefactoringAction.java | 172 +++++++-- .../AndroidExtractAsIncludeAction.java | 343 ++++++++++++++++++ .../AndroidExtractStyleAction.java | 9 +- .../AndroidInlineStyleReferenceAction.java | 9 +- .../AndroidRefactoringContextProvider.java | 5 +- .../refactoring/extractAsInclude/1.xml | 12 + .../refactoring/extractAsInclude/10.xml | 13 + .../refactoring/extractAsInclude/10_after.xml | 8 + .../extractAsInclude/10_extracted.xml | 8 + .../refactoring/extractAsInclude/1_after.xml | 8 + .../extractAsInclude/1_extracted.xml | 6 + .../refactoring/extractAsInclude/2.xml | 12 + .../refactoring/extractAsInclude/2_after.xml | 8 + .../extractAsInclude/2_extracted.xml | 6 + .../refactoring/extractAsInclude/3.xml | 12 + .../refactoring/extractAsInclude/4.xml | 12 + .../refactoring/extractAsInclude/5.xml | 12 + .../refactoring/extractAsInclude/6.xml | 17 + .../refactoring/extractAsInclude/6_after.xml | 8 + .../extractAsInclude/6_extracted.xml | 13 + .../refactoring/extractAsInclude/7.xml | 19 + .../refactoring/extractAsInclude/7_after.xml | 7 + .../extractAsInclude/7_extracted.xml | 16 + .../refactoring/extractAsInclude/8.xml | 19 + .../refactoring/extractAsInclude/9.xml | 16 + .../refactoring/extractAsInclude/9_after.xml | 8 + .../extractAsInclude/9_extracted.xml | 12 + .../AndroidExtractAsIncludeTest.java | 72 ++++ 36 files changed, 890 insertions(+), 62 deletions(-) create mode 100644 plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractAsIncludeAction.java create mode 100644 plugins/android/testData/refactoring/extractAsInclude/1.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/10.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/10_after.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/10_extracted.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/1_after.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/1_extracted.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/2.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/2_after.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/2_extracted.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/3.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/4.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/5.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/6.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/6_after.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/6_extracted.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/7.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/7_after.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/7_extracted.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/8.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/9.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/9_after.xml create mode 100644 plugins/android/testData/refactoring/extractAsInclude/9_extracted.xml create mode 100644 plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidExtractAsIncludeTest.java diff --git a/plugins/android-designer/src/com/intellij/android/designer/AndroidDesignerRefactoringContextProvider.java b/plugins/android-designer/src/com/intellij/android/designer/AndroidDesignerRefactoringContextProvider.java index 13854f92f2d4..b21a04883511 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/AndroidDesignerRefactoringContextProvider.java +++ b/plugins/android-designer/src/com/intellij/android/designer/AndroidDesignerRefactoringContextProvider.java @@ -9,30 +9,39 @@ import com.intellij.psi.xml.XmlTag; import org.jetbrains.android.refactoring.AndroidRefactoringContextProvider; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; import java.util.List; /** * @author Eugene.Kudelevsky */ public class AndroidDesignerRefactoringContextProvider implements AndroidRefactoringContextProvider { + @NotNull @Override - public XmlTag getComponentTag(@NotNull DataContext dataContext) { + public XmlTag[] getComponentTags(@NotNull DataContext dataContext) { final EditableArea area = EditableArea.DATA_KEY.getData(dataContext); if (area == null) { - return null; + return XmlTag.EMPTY; } final List selection = area.getSelection(); - if (selection.size() != 1) { - return null; + if (selection.size() == 0) { + return XmlTag.EMPTY; } - final RadComponent component = selection.get(0); + final List tags = new ArrayList(selection.size()); - if (!(component instanceof RadViewComponent)) { - return null; + for (RadComponent component : selection) { + if (!(component instanceof RadViewComponent)) { + return XmlTag.EMPTY; + } + final XmlTag tag = ((RadViewComponent)component).getTag(); + + if (tag == null || tag.equals(EmptyXmlTag.INSTANCE)) { + return XmlTag.EMPTY; + } + tags.add(tag); } - final XmlTag tag = ((RadViewComponent)component).getTag(); - return tag != null && !tag.equals(EmptyXmlTag.INSTANCE) ? tag : null; + return tags.toArray(new XmlTag[tags.size()]); } } diff --git a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java index 8bbd5ae9d78a..f9692f344727 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java +++ b/plugins/android-designer/src/com/intellij/android/designer/designSurface/AndroidDesignerEditorPanel.java @@ -60,6 +60,7 @@ import com.intellij.util.ThrowableConsumer; import com.intellij.util.ThrowableRunnable; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.maven.AndroidMavenUtil; +import org.jetbrains.android.refactoring.AndroidExtractAsIncludeAction; import org.jetbrains.android.refactoring.AndroidExtractStyleAction; import org.jetbrains.android.refactoring.AndroidInlineStyleReferenceAction; import org.jetbrains.android.sdk.AndroidPlatform; @@ -163,10 +164,13 @@ public final class AndroidDesignerEditorPanel extends DesignerEditorPanel { final ActionManager manager = ActionManager.getInstance(); AnAction action = manager.getAction(AndroidExtractStyleAction.ACTION_ID); - group.add(new AndroidRefactoringActionWrapper("_Extract style", action)); + group.add(new AndroidRefactoringActionWrapper("_Extract Style", action)); action = manager.getAction(AndroidInlineStyleReferenceAction.ACTION_ID); - group.add(new AndroidRefactoringActionWrapper("_Inline style", action)); + group.add(new AndroidRefactoringActionWrapper("_Inline Style", action)); + + action = manager.getAction(AndroidExtractAsIncludeAction.ACTION_ID); + group.add(new AndroidRefactoringActionWrapper("E_xtract Layout", action)); return group; } diff --git a/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java b/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java index 3fd12fa584e3..a7a912c00be6 100644 --- a/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java +++ b/plugins/android-designer/src/com/intellij/android/designer/propertyTable/editors/ResourceDialog.java @@ -34,8 +34,7 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import com.intellij.ui.*; import com.intellij.ui.components.JBTabbedPane; @@ -246,10 +245,10 @@ public class ResourceDialog extends DialogWrapper implements TreeSelectionListen private void createNewResourceFile(ResourceType resourceType) { AndroidFacet facet = AndroidFacet.getInstance(myModule); - PsiElement[] elements = CreateResourceFileAction.createFileResource(facet, resourceType, null, true); + XmlFile newFile = CreateResourceFileAction.createFileResource(facet, resourceType, null, true); - if (elements.length == 1) { - String name = ((PsiFile)elements[0]).getName(); + if (newFile != null) { + String name = newFile.getName(); int index = name.lastIndexOf('.'); if (index != -1) { name = name.substring(0, index); diff --git a/plugins/android/resources/messages/AndroidBundle.properties b/plugins/android/resources/messages/AndroidBundle.properties index cd6b5595a3d0..8b6f534025db 100644 --- a/plugins/android/resources/messages/AndroidBundle.properties +++ b/plugins/android/resources/messages/AndroidBundle.properties @@ -425,4 +425,5 @@ android.extract.style.title=Extract Android Style android.inline.style.title=Inline Android Style android.inline.style.command.name=Inline Style ''{0}'' android.inline.style.inline.all.text=Inline &all references and remove the style -android.inline.style.inline.this.text=Inline &this usage and keep the style \ No newline at end of file +android.inline.style.inline.this.text=Inline &this usage and keep the style +android.extract.as.include.title=Extract Android Layout \ No newline at end of file diff --git a/plugins/android/src/META-INF/plugin.xml b/plugins/android/src/META-INF/plugin.xml index 69e6dbe3d537..64b3c6a4c6af 100644 --- a/plugins/android/src/META-INF/plugin.xml +++ b/plugins/android/src/META-INF/plugin.xml @@ -82,10 +82,14 @@ icon="/icons/attachDebugger.png"> - + + + diff --git a/plugins/android/src/org/jetbrains/android/actions/CreateResourceFileAction.java b/plugins/android/src/org/jetbrains/android/actions/CreateResourceFileAction.java index 2d00b43d8c21..c43e5892ec48 100644 --- a/plugins/android/src/org/jetbrains/android/actions/CreateResourceFileAction.java +++ b/plugins/android/src/org/jetbrains/android/actions/CreateResourceFileAction.java @@ -37,6 +37,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiManager; +import com.intellij.psi.xml.XmlFile; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.util.AndroidBundle; import org.jetbrains.android.util.AndroidResourceUtil; @@ -91,11 +92,24 @@ public class CreateResourceFileAction extends CreateElementActionBase { }); } + @Nullable + public static XmlFile createFileResource(@NotNull AndroidFacet facet, + @NotNull final ResourceType resType, + @Nullable String resName, + boolean chooseResName) { + final PsiElement[] elements = doCreateFileResource(facet, resType, resName, chooseResName); + if (elements.length == 0) { + return null; + } + assert elements.length == 1 && elements[0] instanceof XmlFile; + return (XmlFile)elements[0]; + } + @NotNull - public static PsiElement[] createFileResource(@NotNull AndroidFacet facet, - @NotNull final ResourceType resType, - @Nullable String resName, - boolean chooseResName) { + private static PsiElement[] doCreateFileResource(@NotNull AndroidFacet facet, + @NotNull final ResourceType resType, + @Nullable String resName, + boolean chooseResName) { final CreateResourceFileAction action = getInstance(); final String subdirName; diff --git a/plugins/android/src/org/jetbrains/android/inspections/CreateFileResourceQuickFix.java b/plugins/android/src/org/jetbrains/android/inspections/CreateFileResourceQuickFix.java index 6d0acaae2706..fee6d203a052 100644 --- a/plugins/android/src/org/jetbrains/android/inspections/CreateFileResourceQuickFix.java +++ b/plugins/android/src/org/jetbrains/android/inspections/CreateFileResourceQuickFix.java @@ -14,9 +14,9 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDirectory; -import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; +import com.intellij.psi.xml.XmlFile; import com.intellij.util.IncorrectOperationException; import org.jetbrains.android.actions.CreateResourceFileAction; import org.jetbrains.android.actions.CreateTypedResourceFileAction; @@ -73,9 +73,9 @@ public class CreateFileResourceQuickFix implements LocalQuickFix, IntentionActio @Override public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { - final PsiElement[] createdElements = + final XmlFile newFile = CreateResourceFileAction.createFileResource(myFacet, myResourceType, myResourceName + ".xml", myChooseResName); - if (createdElements.length > 0) { + if (newFile != null) { UndoUtil.markPsiFileForUndo(myFile); } } diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseLayoutRefactoringAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseLayoutRefactoringAction.java index bafcd607c3a1..7baa64de4aa2 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseLayoutRefactoringAction.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseLayoutRefactoringAction.java @@ -16,8 +16,13 @@ import org.jetbrains.annotations.Nullable; public abstract class AndroidBaseLayoutRefactoringAction extends AndroidBaseXmlRefactoringAction { @Override - protected boolean isEnabled(@NotNull XmlTag tag) { - return getLayoutViewElement(tag) != null; + protected boolean isEnabledForTags(@NotNull XmlTag[] tags) { + for (XmlTag tag : tags) { + if (getLayoutViewElement(tag) == null) { + return false; + } + } + return true; } @Override diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseXmlRefactoringAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseXmlRefactoringAction.java index 045ec30b211b..37a7405b68af 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseXmlRefactoringAction.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidBaseXmlRefactoringAction.java @@ -6,9 +6,14 @@ import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.actionSystem.LangDataKeys; import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.SelectionModel; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiComment; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; @@ -26,27 +31,124 @@ import org.jetbrains.annotations.Nullable; abstract class AndroidBaseXmlRefactoringAction extends BaseRefactoringAction { @Override protected boolean isAvailableOnElementInEditorAndFile(PsiElement element, Editor editor, PsiFile file, DataContext context) { + final XmlTag[] tags = getXmlTagsFromExternalContext(context); + if (tags.length > 0) { + return AndroidFacet.getInstance(tags[0]) != null && isEnabledForTags(tags); + } + + final TextRange range = getNonEmptySelectionRange(editor); + if (range != null) { + final Pair psiRange = getExtractableRange( + file, range.getStartOffset(), range.getEndOffset()); + return psiRange != null && isEnabledForPsiRange(psiRange.getFirst(), psiRange.getSecond()); + } + if (element == null || AndroidFacet.getInstance(element) == null || PsiTreeUtil.getParentOfType(element, XmlText.class) != null) { return false; } final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class); - return tag != null && isEnabled(tag); + return tag != null && isEnabledForTags(new XmlTag[]{tag}); + } + + @Nullable + private static TextRange getNonEmptySelectionRange(Editor editor) { + if (editor != null) { + final SelectionModel model = editor.getSelectionModel(); + + if (model.hasSelection()) { + final int start = model.getSelectionStart(); + final int end = model.getSelectionEnd(); + + if (start < end) { + return TextRange.create(start, end); + } + } + } + return null; + } + + @Nullable + private static Pair getExtractableRange(PsiFile file, int start, int end) { + PsiElement startElement = file.findElementAt(start); + PsiElement parent = startElement != null ? startElement.getParent() : null; + + while (parent != null && + !(parent instanceof PsiFile) && + parent.getTextRange().getStartOffset() == startElement.getTextRange().getStartOffset()) { + startElement = parent; + parent = parent.getParent(); + } + PsiElement endElement = file.findElementAt(end - 1); + parent = endElement != null ? endElement.getParent() : null; + + while (parent != null && + !(parent instanceof PsiFile) && + parent.getTextRange().getEndOffset() == endElement.getTextRange().getEndOffset()) { + endElement = parent; + parent = parent.getParent(); + } + + if (startElement == null || endElement == null) { + return null; + } + final PsiElement commonParent = startElement.getParent(); + + if (commonParent == null || + !(commonParent instanceof XmlTag) || + commonParent != endElement.getParent()) { + return null; + } + PsiElement e = startElement; + boolean containTag = false; + + while (e != null) { + if (!(e instanceof XmlText) && + !(e instanceof XmlTag) && + !(e instanceof PsiWhiteSpace) && + !(e instanceof PsiComment)) { + return null; + } + if (e instanceof XmlTag) { + containTag = true; + } + if (e == endElement) { + break; + } + e = e.getNextSibling(); + } + return e != null && containTag + ? Pair.create(startElement, endElement) + : null; } @Override protected boolean isEnabledOnElements(PsiElement[] elements) { - if (elements.length != 1) { + if (elements.length == 0) { return false; } final PsiElement element = elements[0]; - return element instanceof XmlTag && - AndroidFacet.getInstance(element) != null && - isEnabled((XmlTag)element); + + if (AndroidFacet.getInstance(element) == null) { + return false; + } + final XmlTag[] tags = new XmlTag[elements.length]; + + for (int i = 0; i < tags.length; i++) { + if (!(elements[i] instanceof XmlTag)) { + return false; + } + tags[i] = (XmlTag)elements[i]; + } + return isEnabledForTags(tags); } - protected abstract boolean isEnabled(@NotNull XmlTag tag); + protected abstract boolean isEnabledForTags(@NotNull XmlTag[] tags); + + protected boolean isEnabledForPsiRange(@NotNull PsiElement from, @Nullable PsiElement to) { + return false; + } @Override protected boolean isAvailableForLanguage(Language language) { @@ -74,7 +176,11 @@ abstract class AndroidBaseXmlRefactoringAction extends BaseRefactoringAction { return data; } if (LangDataKeys.PSI_ELEMENT.is(dataId)) { - return getXmlTagFromExternalContext(context); + final XmlTag[] tags = getXmlTagsFromExternalContext(context); + return tags.length == 1 ? tags[0] : null; + } + else if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) { + return getXmlTagsFromExternalContext(context); } return null; } @@ -83,12 +189,15 @@ abstract class AndroidBaseXmlRefactoringAction extends BaseRefactoringAction { e.getActionManager(), e.getModifiers())); } - protected abstract void doRefactor(@NotNull Project project, @NotNull XmlTag tag); + protected abstract void doRefactorForTags(@NotNull Project project, @NotNull XmlTag[] tags); + + protected void doRefactorForPsiRange(@NotNull Project project, @NotNull PsiFile file, @NotNull PsiElement from, @NotNull PsiElement to) { + } @Override protected RefactoringActionHandler getHandler(DataContext dataContext) { - final XmlTag componentTag = getXmlTagFromExternalContext(dataContext); - return new MyHandler(componentTag); + final XmlTag[] componentTags = getXmlTagsFromExternalContext(dataContext); + return new MyHandler(componentTags); } @Override @@ -96,35 +205,46 @@ abstract class AndroidBaseXmlRefactoringAction extends BaseRefactoringAction { return false; } - @Nullable - protected static XmlTag getXmlTagFromExternalContext(DataContext dataContext) { + @NotNull + protected static XmlTag[] getXmlTagsFromExternalContext(DataContext dataContext) { if (dataContext == null) { - return null; + return XmlTag.EMPTY; } for (AndroidRefactoringContextProvider provider : AndroidRefactoringContextProvider.EP_NAME.getExtensions()) { - final XmlTag componentTag = provider.getComponentTag(dataContext); + final XmlTag[] componentTags = provider.getComponentTags(dataContext); - if (componentTag != null) { - return componentTag; + if (componentTags.length > 0) { + return componentTags; } } - return null; + return XmlTag.EMPTY; } private class MyHandler implements RefactoringActionHandler { - private final XmlTag myTagFromExternalContext; + private final XmlTag[] myTagsFromExternalContext; - private MyHandler(@Nullable XmlTag tagFromExternalContext) { - myTagFromExternalContext = tagFromExternalContext; + private MyHandler(@NotNull XmlTag[] tagsFromExternalContext) { + myTagsFromExternalContext = tagsFromExternalContext; } @Override public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) { - if (myTagFromExternalContext != null) { - doRefactor(project, myTagFromExternalContext); + if (myTagsFromExternalContext.length > 0) { + doRefactorForTags(project, myTagsFromExternalContext); return; } + + final TextRange range = getNonEmptySelectionRange(editor); + if (range != null) { + final Pair psiRange = getExtractableRange( + file, range.getStartOffset(), range.getEndOffset()); + if (psiRange != null) { + doRefactorForPsiRange(project, file, psiRange.getFirst(), psiRange.getSecond()); + } + return; + } + final PsiElement element = getElementAtCaret(editor, file); if (element == null) { return; @@ -133,13 +253,13 @@ abstract class AndroidBaseXmlRefactoringAction extends BaseRefactoringAction { if (tag == null) { return; } - doRefactor(project, tag); + doRefactorForTags(project, new XmlTag[]{tag}); } @Override public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) { - if (myTagFromExternalContext != null) { - doRefactor(project, myTagFromExternalContext); + if (myTagsFromExternalContext.length > 0) { + doRefactorForTags(project, myTagsFromExternalContext); return; } if (elements.length != 1) { @@ -149,7 +269,7 @@ abstract class AndroidBaseXmlRefactoringAction extends BaseRefactoringAction { if (!(element instanceof XmlTag)) { return; } - doRefactor(project, (XmlTag)element); + doRefactorForTags(project, new XmlTag[]{(XmlTag)element}); } } } diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractAsIncludeAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractAsIncludeAction.java new file mode 100644 index 000000000000..dd0eb1d31392 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractAsIncludeAction.java @@ -0,0 +1,343 @@ +package org.jetbrains.android.refactoring; + +import com.android.resources.ResourceType; +import com.intellij.openapi.command.UndoConfirmationPolicy; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.InputValidatorEx; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.TextRange; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.psi.*; +import com.intellij.psi.codeStyle.CodeStyleManager; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.containers.HashSet; +import com.intellij.util.xml.DomElement; +import com.intellij.util.xml.DomManager; +import org.jetbrains.android.dom.layout.Include; +import org.jetbrains.android.dom.layout.LayoutViewElement; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.util.AndroidBundle; +import org.jetbrains.android.util.AndroidCommonUtils; +import org.jetbrains.android.util.AndroidUtils; +import org.jetbrains.annotations.NonNls; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.TestOnly; + +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidExtractAsIncludeAction extends AndroidBaseLayoutRefactoringAction { + @NonNls public static final String ACTION_ID = "AndroidExtractAsIncludeAction"; + + private final MyTestConfig myTestConfig; + + public AndroidExtractAsIncludeAction() { + myTestConfig = null; + } + + @TestOnly + public AndroidExtractAsIncludeAction(@Nullable MyTestConfig testConfig) { + myTestConfig = testConfig; + } + + @Override + protected void doRefactorForTags(@NotNull Project project, @NotNull XmlTag[] tags) { + if (tags.length == 0) { + return; + } + final PsiFile file = tags[0].getContainingFile(); + if (file == null) { + return; + } + XmlTag startTag = null; + XmlTag endTag = null; + int startOffset = Integer.MAX_VALUE; + int endOffset = -1; + + for (XmlTag tag : tags) { + final TextRange range = tag.getTextRange(); + + final int start = range.getStartOffset(); + if (start < startOffset) { + startOffset = start; + startTag = tag; + } + + final int end = range.getEndOffset(); + if (end > endOffset) { + endOffset = end; + endTag = tag; + } + } + assert startTag != null && endTag != null; + doRefactorForPsiRange(project, file, startTag, endTag); + } + + @Override + protected boolean isEnabledForTags(@NotNull XmlTag[] tags) { + if (tags.length == 0) { + return false; + } + final DomManager domManager = DomManager.getDomManager(tags[0].getProject()); + boolean containsViewElement = false; + + for (XmlTag tag : tags) { + final DomElement domElement = domManager.getDomElement(tag); + + if (!isSuitableDomElement(domElement)) { + return false; + } + if (domElement instanceof LayoutViewElement) { + containsViewElement = true; + } + } + if (!containsViewElement) { + return false; + } + final PsiElement parent = tags[0].getParent(); + + if (!(parent instanceof XmlTag) || parent.getContainingFile() == null) { + return false; + } + + for (int i = 1; i < tags.length; i++) { + if (tags[i].getParent() != parent) { + return false; + } + } + return true; + } + + @Override + protected void doRefactorForPsiRange(@NotNull final Project project, @NotNull final PsiFile file, @NotNull final PsiElement from, + @NotNull final PsiElement to) { + final PsiDirectory dir = file.getContainingDirectory(); + if (dir == null) { + return; + } + final AndroidFacet facet = AndroidFacet.getInstance(from); + assert facet != null; + + final XmlTag parentTag = PsiTreeUtil.getParentOfType(from, XmlTag.class); + assert parentTag != null; + + final List tagsInRange = collectAllTags(from, to); + assert tagsInRange.size() > 0 : "there is no tag inside the range"; + + final String title = AndroidBundle.message("android.extract.as.include.title"); + final String fileName; + + if (myTestConfig != null) { + fileName = myTestConfig.myLayoutFileName; + } + else { + fileName = Messages.showInputDialog(project, "Enter new layout file name", title, Messages.getQuestionIcon(), + null, new MyInputValidatorEx(dir)); + } + + if (fileName == null) { + return; + } + final String fileName1 = addXmlExtensionIfNecessary(fileName); + + new WriteCommandAction.Simple(project, "Extract '" + fileName1 + "' layout", file) { + @Override + protected void run() throws Throwable { + doRefactor(project, file, from, to, dir, parentTag, fileName1, tagsInRange.size() > 1); + } + + @Override + protected UndoConfirmationPolicy getUndoConfirmationPolicy() { + return UndoConfirmationPolicy.REQUEST_CONFIRMATION; + } + }.execute(); + } + + private static void doRefactor(Project project, + PsiFile file, + PsiElement from, + PsiElement to, + PsiDirectory dir, + XmlTag parentTag, + String fileName, + boolean wrapWithMerge) { + final String textToExtract = file.getText().substring(from.getTextRange().getStartOffset(), + to.getTextRange().getEndOffset()); + final XmlFile newFile; + try { + final PsiFile f = dir.createFile(fileName); + assert f instanceof XmlFile; + newFile = (XmlFile)f; + } + catch (Exception e) { + AndroidUtils.reportError(project, e.getClass().getName() + ": " + e.getMessage(), + AndroidBundle.message("android.extract.as.include.title")); + return; + } + final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project); + final Document document = documentManager.getDocument(newFile); + assert document != null; + document.setText("\n" + + (wrapWithMerge ? "\n" + textToExtract + "\n" : textToExtract)); + documentManager.commitDocument(document); + + final Set unknownPrefixes = new HashSet(); + + newFile.accept(new XmlRecursiveElementVisitor() { + @Override + public void visitXmlTag(XmlTag tag) { + super.visitXmlTag(tag); + final String prefix = tag.getNamespacePrefix(); + + if (!unknownPrefixes.contains(prefix) && tag.getNamespace().length() == 0) { + unknownPrefixes.add(prefix); + } + } + + @Override + public void visitXmlAttribute(XmlAttribute attribute) { + final String prefix = attribute.getNamespacePrefix(); + + if (!unknownPrefixes.contains(prefix) && attribute.getNamespace().length() == 0) { + unknownPrefixes.add(prefix); + } + } + }); + + final XmlTag rootTag = newFile.getRootTag(); + assert rootTag != null; + final XmlElementFactory elementFactory = XmlElementFactory.getInstance(project); + final XmlAttribute[] attributes = rootTag.getAttributes(); + final XmlAttribute firstAttribute = attributes.length > 0 ? attributes[0] : null; + + for (String prefix : unknownPrefixes) { + final String namespace = parentTag.getNamespaceByPrefix(prefix); + final String xmlNsAttrName = "xmlns:" + prefix; + + if (namespace.length() > 0 && rootTag.getAttribute(xmlNsAttrName) == null) { + final XmlAttribute xmlnsAttr = elementFactory.createXmlAttribute(xmlNsAttrName, namespace); + + if (firstAttribute != null) { + rootTag.addBefore(xmlnsAttr, firstAttribute); + } + else { + rootTag.add(xmlnsAttr); + } + } + } + final String resourceName = AndroidCommonUtils.getResourceName(ResourceType.LAYOUT.getName(), fileName); + final XmlTag includeTag = elementFactory.createTagFromText(""); + parentTag.addAfter(includeTag, to); + parentTag.deleteChildRange(from, to); + + final CodeStyleManager codeStyleManager = CodeStyleManager.getInstance(project); + codeStyleManager.reformat(newFile); + } + + @NotNull + private static String addXmlExtensionIfNecessary(@NotNull String inputString) { + final String ext = FileUtil.getExtension(inputString); + return "xml".equals(ext) ? inputString : inputString + ".xml"; + } + + @NotNull + private static List collectAllTags(PsiElement from, PsiElement to) { + final List result = new ArrayList(); + PsiElement e = from; + + while (e != null) { + if (e instanceof XmlTag) { + result.add((XmlTag)e); + } + if (e == to) { + break; + } + e = e.getNextSibling(); + } + assert e != null : "invalid range"; + return result; + } + + @Override + protected boolean isEnabledForPsiRange(@NotNull PsiElement from, @Nullable PsiElement to) { + final DomManager domManager = DomManager.getDomManager(from.getProject()); + PsiElement e = from; + boolean containsViewElement = false; + + while (e != null) { + if (e instanceof XmlTag) { + final DomElement domElement = domManager.getDomElement((XmlTag)e); + + if (!isSuitableDomElement(domElement)) { + return false; + } + if (domElement instanceof LayoutViewElement) { + containsViewElement = true; + } + } + if (e == to) { + break; + } + e = e.getNextSibling(); + } + return containsViewElement; + } + + private static boolean isSuitableDomElement(DomElement element) { + return element instanceof LayoutViewElement || + element instanceof Include; + } + + static class MyTestConfig { + private final String myLayoutFileName; + + MyTestConfig(@NotNull String layoutFileName) { + myLayoutFileName = layoutFileName; + } + } + + private static class MyInputValidatorEx implements InputValidatorEx { + private final PsiDirectory myDirectory; + + public MyInputValidatorEx(@NotNull PsiDirectory directory) { + myDirectory = directory; + } + + @Nullable + @Override + public String getErrorText(String inputString) { + if (inputString.length() == 0) { + return null; + } + final String fileName = addXmlExtensionIfNecessary(inputString); + try { + myDirectory.checkCreateFile(fileName); + } + catch (IncorrectOperationException e) { + return e.getMessage(); + } + return null; + } + + @Override + public boolean checkInput(String inputString) { + return inputString.length() > 0; + } + + @Override + public boolean canClose(String inputString) { + return checkInput(inputString) && getErrorText(inputString) == null; + } + } +} diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java index 6802e4629257..552e88fcf2e4 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidExtractStyleAction.java @@ -56,8 +56,8 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio myTestConfig = testConfig; } - protected boolean isEnabled(@NotNull XmlTag tag) { - return doIsEnabled(tag); + protected boolean isEnabledForTags(@NotNull XmlTag[] tags) { + return tags.length == 1 && doIsEnabled(tags[0]); } public static boolean doIsEnabled(@NotNull XmlTag tag) { @@ -222,7 +222,10 @@ public class AndroidExtractStyleAction extends AndroidBaseLayoutRefactoringActio } @Override - protected void doRefactor(@NotNull Project project, @NotNull XmlTag tag) { + protected void doRefactorForTags(@NotNull Project project, @NotNull XmlTag[] tags) { + assert tags.length == 1; + + final XmlTag tag = tags[0]; final Module module = ModuleUtilCore.findModuleForPsiElement(tag); assert module != null; doExtractStyle(module, tag, true, myTestConfig); diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java index a3d8362a88c7..5a69d1965029 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidInlineStyleReferenceAction.java @@ -39,7 +39,10 @@ public class AndroidInlineStyleReferenceAction extends AndroidBaseXmlRefactoring } @Override - protected void doRefactor(@NotNull Project project, @NotNull final XmlTag tag) { + protected void doRefactorForTags(@NotNull Project project, @NotNull final XmlTag[] tags) { + assert tags.length == 1; + final XmlTag tag = tags[0]; + final PsiFile file = tag.getContainingFile(); if (file == null) { return; @@ -83,8 +86,8 @@ public class AndroidInlineStyleReferenceAction extends AndroidBaseXmlRefactoring } @Override - protected boolean isEnabled(@NotNull XmlTag tag) { - return AndroidInlineUtil.getUsageData(tag) != null; + protected boolean isEnabledForTags(@NotNull XmlTag[] tags) { + return tags.length == 1 && AndroidInlineUtil.getUsageData(tags[0]) != null; } @Override diff --git a/plugins/android/src/org/jetbrains/android/refactoring/AndroidRefactoringContextProvider.java b/plugins/android/src/org/jetbrains/android/refactoring/AndroidRefactoringContextProvider.java index e3a625139e53..1c890891f7c7 100644 --- a/plugins/android/src/org/jetbrains/android/refactoring/AndroidRefactoringContextProvider.java +++ b/plugins/android/src/org/jetbrains/android/refactoring/AndroidRefactoringContextProvider.java @@ -4,7 +4,6 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.extensions.ExtensionPointName; import com.intellij.psi.xml.XmlTag; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * @author Eugene.Kudelevsky @@ -13,6 +12,6 @@ public interface AndroidRefactoringContextProvider { ExtensionPointName EP_NAME = ExtensionPointName.create("org.jetbrains.android.refactoringContextProvider"); - @Nullable - XmlTag getComponentTag(@NotNull DataContext dataContext); + @NotNull + XmlTag[] getComponentTags(@NotNull DataContext dataContext); } diff --git a/plugins/android/testData/refactoring/extractAsInclude/1.xml b/plugins/android/testData/refactoring/extractAsInclude/1.xml new file mode 100644 index 000000000000..901e8eac50aa --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/1.xml @@ -0,0 +1,12 @@ + + + ew + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="Hello World, MyActivity" + /> + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/10.xml b/plugins/android/testData/refactoring/extractAsInclude/10.xml new file mode 100644 index 000000000000..c3d4423ab39c --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/10.xml @@ -0,0 +1,13 @@ + + + out_width="fill_parent" + andr1:layout_height="wrap_content" + andr1:text="Hello World, MyActivity" + app:customAttribute="Hello" + /> + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/10_after.xml b/plugins/android/testData/refactoring/extractAsInclude/10_after.xml new file mode 100644 index 000000000000..e5a340fcb490 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/10_after.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/10_extracted.xml b/plugins/android/testData/refactoring/extractAsInclude/10_extracted.xml new file mode 100644 index 000000000000..e3352d4ff998 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/10_extracted.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/1_after.xml b/plugins/android/testData/refactoring/extractAsInclude/1_after.xml new file mode 100644 index 000000000000..fc34f12d16cc --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/1_after.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/1_extracted.xml b/plugins/android/testData/refactoring/extractAsInclude/1_extracted.xml new file mode 100644 index 000000000000..b8df71713f0f --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/1_extracted.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/2.xml b/plugins/android/testData/refactoring/extractAsInclude/2.xml new file mode 100644 index 000000000000..1aa9f1d5714d --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/2.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/2_after.xml b/plugins/android/testData/refactoring/extractAsInclude/2_after.xml new file mode 100644 index 000000000000..fc34f12d16cc --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/2_after.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/2_extracted.xml b/plugins/android/testData/refactoring/extractAsInclude/2_extracted.xml new file mode 100644 index 000000000000..b8df71713f0f --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/2_extracted.xml @@ -0,0 +1,6 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/3.xml b/plugins/android/testData/refactoring/extractAsInclude/3.xml new file mode 100644 index 000000000000..85686f0f7a85 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/3.xml @@ -0,0 +1,12 @@ + + + extView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="Hello World, MyActivity" + /> + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/4.xml b/plugins/android/testData/refactoring/extractAsInclude/4.xml new file mode 100644 index 000000000000..22ffd403a204 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/4.xml @@ -0,0 +1,12 @@ + +rLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + > + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/5.xml b/plugins/android/testData/refactoring/extractAsInclude/5.xml new file mode 100644 index 000000000000..47af1405228e --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/5.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/6.xml b/plugins/android/testData/refactoring/extractAsInclude/6.xml new file mode 100644 index 000000000000..13ae57c34b91 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/6.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/6_after.xml b/plugins/android/testData/refactoring/extractAsInclude/6_after.xml new file mode 100644 index 000000000000..fc34f12d16cc --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/6_after.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/6_extracted.xml b/plugins/android/testData/refactoring/extractAsInclude/6_extracted.xml new file mode 100644 index 000000000000..96722f24b962 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/6_extracted.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/7.xml b/plugins/android/testData/refactoring/extractAsInclude/7.xml new file mode 100644 index 000000000000..58677c48b7ae --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/7.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/7_after.xml b/plugins/android/testData/refactoring/extractAsInclude/7_after.xml new file mode 100644 index 000000000000..1c911799e8de --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/7_after.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/7_extracted.xml b/plugins/android/testData/refactoring/extractAsInclude/7_extracted.xml new file mode 100644 index 000000000000..d539954dc1cb --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/7_extracted.xml @@ -0,0 +1,16 @@ + + + + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/8.xml b/plugins/android/testData/refactoring/extractAsInclude/8.xml new file mode 100644 index 000000000000..389d667923d8 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/8.xml @@ -0,0 +1,19 @@ + + + + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/9.xml b/plugins/android/testData/refactoring/extractAsInclude/9.xml new file mode 100644 index 000000000000..599fdae2ab84 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/9.xml @@ -0,0 +1,16 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/9_after.xml b/plugins/android/testData/refactoring/extractAsInclude/9_after.xml new file mode 100644 index 000000000000..e5a340fcb490 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/9_after.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/refactoring/extractAsInclude/9_extracted.xml b/plugins/android/testData/refactoring/extractAsInclude/9_extracted.xml new file mode 100644 index 000000000000..d6913882a828 --- /dev/null +++ b/plugins/android/testData/refactoring/extractAsInclude/9_extracted.xml @@ -0,0 +1,12 @@ + + + + + \ No newline at end of file diff --git a/plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidExtractAsIncludeTest.java b/plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidExtractAsIncludeTest.java new file mode 100644 index 000000000000..867af4477d25 --- /dev/null +++ b/plugins/android/testSrc/org/jetbrains/android/refactoring/AndroidExtractAsIncludeTest.java @@ -0,0 +1,72 @@ +package org.jetbrains.android.refactoring; + +import com.intellij.openapi.actionSystem.Presentation; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.android.AndroidTestCase; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidExtractAsIncludeTest extends AndroidTestCase { + private static final String BASE_PATH = "refactoring/extractAsInclude/"; + + public void test1() throws Exception { + doTest(); + } + + public void test2() throws Exception { + doTest(); + } + + public void test3() throws Exception { + doTestDisabled(); + } + + public void test4() throws Exception { + doTestDisabled(); + } + + public void test5() throws Exception { + doTestDisabled(); + } + + public void test6() throws Exception { + doTest(); + } + + public void test7() throws Exception { + doTest(); + } + + public void test8() throws Exception { + doTestDisabled(); + } + + public void test9() throws Exception { + doTest(); + } + + public void test10() throws Exception { + doTest(); + } + + private void doTest() { + final String testName = getTestName(true); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + myFixture.configureFromExistingVirtualFile(f); + final String extractedFileName = "extracted.xml"; + myFixture.testAction(new AndroidExtractAsIncludeAction(new AndroidExtractAsIncludeAction.MyTestConfig(extractedFileName))); + myFixture.checkResultByFile(BASE_PATH + testName + "_after.xml", true); + myFixture.checkResultByFile("res/layout/" + extractedFileName, BASE_PATH + testName + "_extracted.xml", true); + } + + private void doTestDisabled() { + final String testName = getTestName(true); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + testName + ".xml", "res/layout/test.xml"); + myFixture.configureFromExistingVirtualFile(f); + final Presentation p = + myFixture.testAction(new AndroidExtractAsIncludeAction(new AndroidExtractAsIncludeAction.MyTestConfig("extracted.xml"))); + assertTrue(p.isVisible()); + assertFalse(p.isEnabled()); + } +}