diff --git a/plugins/android/src/META-INF/plugin.xml b/plugins/android/src/META-INF/plugin.xml index 3401b464d370..5131f5775455 100644 --- a/plugins/android/src/META-INF/plugin.xml +++ b/plugins/android/src/META-INF/plugin.xml @@ -195,6 +195,9 @@ + + xmlTag() + diff --git a/plugins/android/src/org/jetbrains/android/AndroidCompletionContributor.java b/plugins/android/src/org/jetbrains/android/AndroidCompletionContributor.java index b22c3ef12c87..056b697d1b9a 100644 --- a/plugins/android/src/org/jetbrains/android/AndroidCompletionContributor.java +++ b/plugins/android/src/org/jetbrains/android/AndroidCompletionContributor.java @@ -19,8 +19,11 @@ import com.intellij.codeInsight.completion.CompletionContributor; import com.intellij.codeInsight.completion.CompletionParameters; import com.intellij.codeInsight.completion.CompletionResultSet; import com.intellij.codeInsight.lookup.LookupElementBuilder; +import com.intellij.lang.ASTNode; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.xml.XmlChildRole; import com.intellij.psi.xml.XmlFile; import com.intellij.psi.xml.XmlTag; import org.jetbrains.android.dom.animation.AndroidAnimationUtils; @@ -52,42 +55,34 @@ public class AndroidCompletionContributor extends CompletionContributor { } } - private static boolean complete(@NotNull AndroidFacet facet, PsiElement position, CompletionResultSet resultSet) { - PsiElement parent = position.getParent(); - if (parent instanceof XmlTag) { - XmlTag tag = (XmlTag)parent; - if (tag.getParentTag() == null) { - PsiFile file = tag.getContainingFile().getOriginalFile(); - if (file instanceof XmlFile) { - XmlFile xmlFile = (XmlFile)file; - if (ManifestDomFileDescription.isManifestFile(xmlFile)) { - resultSet.addElement(LookupElementBuilder.create("manifest")); - return false; - } - else if (LayoutDomFileDescription.isLayoutFile(xmlFile)) { - addAll(AndroidLayoutUtil.getPossibleRoots(facet), resultSet); - return false; - } - else if (AnimationDomFileDescription.isAnimationFile(xmlFile)) { - addAll(AndroidAnimationUtils.getPossibleChildren(facet), resultSet); - return false; - } - else if (AnimatorDomFileDescription.isAnimatorFile(xmlFile)) { - addAll(AndroidAnimatorUtil.getPossibleChildren(), resultSet); - return false; - } - else if (XmlResourceDomFileDescription.isXmlResourceFile(xmlFile)) { - addAll(AndroidXmlResourcesUtil.getPossibleRoots(facet), resultSet); - return false; - } - else if (AndroidDrawableDomUtil.isDrawableResourceFile(xmlFile)) { - addAll(AndroidDrawableDomUtil.getPossibleRoots(), resultSet); - } - else if (ColorDomFileDescription.isColorResourceFile(xmlFile)) { - addAll(Arrays.asList(DrawableStateListDomFileDescription.SELECTOR_TAG_NAME), resultSet); - } - } - } + private static boolean complete(@NotNull AndroidFacet facet, @NotNull XmlFile xmlFile, @NotNull CompletionResultSet resultSet) { + if (ManifestDomFileDescription.isManifestFile(xmlFile)) { + resultSet.addElement(LookupElementBuilder.create("manifest")); + return false; + } + else if (LayoutDomFileDescription.isLayoutFile(xmlFile)) { + addAll(AndroidLayoutUtil.getPossibleRoots(facet), resultSet); + return false; + } + else if (AnimationDomFileDescription.isAnimationFile(xmlFile)) { + addAll(AndroidAnimationUtils.getPossibleChildren(facet), resultSet); + return false; + } + else if (AnimatorDomFileDescription.isAnimatorFile(xmlFile)) { + addAll(AndroidAnimatorUtil.getPossibleChildren(), resultSet); + return false; + } + else if (XmlResourceDomFileDescription.isXmlResourceFile(xmlFile)) { + addAll(AndroidXmlResourcesUtil.getPossibleRoots(facet), resultSet); + return false; + } + else if (AndroidDrawableDomUtil.isDrawableResourceFile(xmlFile)) { + addAll(AndroidDrawableDomUtil.getPossibleRoots(), resultSet); + return false; + } + else if (ColorDomFileDescription.isColorResourceFile(xmlFile)) { + addAll(Arrays.asList(DrawableStateListDomFileDescription.SELECTOR_TAG_NAME), resultSet); + return false; } return true; } @@ -96,8 +91,43 @@ public class AndroidCompletionContributor extends CompletionContributor { public void fillCompletionVariants(CompletionParameters parameters, CompletionResultSet resultSet) { PsiElement position = parameters.getPosition(); AndroidFacet facet = AndroidFacet.getInstance(position); - if (facet == null) return; - if (!complete(facet, position, resultSet)) { + + if (facet == null) { + return; + } + PsiElement parent = position.getParent(); + + if (!(parent instanceof XmlTag)) { + return; + } + XmlTag tag = (XmlTag)parent; + + if (tag.getParentTag() != null) { + return; + } + final ASTNode startTagName = XmlChildRole.START_TAG_NAME_FINDER.findChild(tag.getNode()); + + if (startTagName == null || startTagName.getPsi() != position) { + return; + } + final PsiFile file = tag.getContainingFile(); + if (!(file instanceof XmlFile)) { + return; + } + final PsiReference reference = file.findReferenceAt(parameters.getOffset()); + if (reference != null) { + final PsiElement element = reference.getElement(); + if (element != null) { + final int refOffset = element.getTextRange().getStartOffset() + + reference.getRangeInElement().getStartOffset(); + if (refOffset != position.getTextRange().getStartOffset()) { + // do not provide completion if we're inside some reference starting in the middle of tag name + return; + } + } + } + + if (!complete(facet, (XmlFile)file, resultSet)) { resultSet.stopHere(); } } diff --git a/plugins/android/src/org/jetbrains/android/dom/AndroidDomElementDescriptorProvider.java b/plugins/android/src/org/jetbrains/android/dom/AndroidDomElementDescriptorProvider.java index f419a2f315b3..03e38fd4274e 100644 --- a/plugins/android/src/org/jetbrains/android/dom/AndroidDomElementDescriptorProvider.java +++ b/plugins/android/src/org/jetbrains/android/dom/AndroidDomElementDescriptorProvider.java @@ -17,6 +17,7 @@ package org.jetbrains.android.dom; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; import com.intellij.psi.impl.source.xml.XmlElementDescriptorProvider; @@ -32,6 +33,7 @@ import org.jetbrains.android.dom.xml.XmlResourceElement; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.facet.SimpleClassMapConstructor; import org.jetbrains.android.util.AndroidUtils; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.Map; @@ -45,7 +47,7 @@ import java.util.Map; */ public class AndroidDomElementDescriptorProvider implements XmlElementDescriptorProvider { @Nullable - private static XmlElementDescriptor getDescriptor(DomElement domElement, XmlTag tag, String baseClassName) { + private static XmlElementDescriptor getDescriptor(DomElement domElement, XmlTag tag, @Nullable String baseClassName) { AndroidFacet facet = AndroidFacet.getInstance(domElement); if (facet == null) return null; PsiClass aClass; @@ -78,6 +80,15 @@ public class AndroidDomElementDescriptorProvider implements XmlElementDescriptor } public XmlElementDescriptor getDescriptor(XmlTag tag) { + final Pair pair = getDomElementAndBaseClassQName(tag); + if (pair == null) { + return null; + } + return getDescriptor(pair.getFirst(), tag, pair.getSecond()); + } + + @Nullable + public static Pair getDomElementAndBaseClassQName(@NotNull XmlTag tag) { Project project = tag.getProject(); if (project.isDefault()) return null; final DomElement domElement = DomManager.getDomManager(project).getDomElement(tag); @@ -92,6 +103,6 @@ public class AndroidDomElementDescriptorProvider implements XmlElementDescriptor else if (domElement instanceof XmlResourceElement) { className = AndroidXmlResourcesUtil.PREFERENCE_CLASS_NAME; } - return getDescriptor(domElement, tag, className); + return Pair.create((AndroidDomElement)domElement, className); } } diff --git a/plugins/android/src/org/jetbrains/android/dom/AndroidXmlExtension.java b/plugins/android/src/org/jetbrains/android/dom/AndroidXmlExtension.java index c6c0d332cb62..f9a2a537017c 100644 --- a/plugins/android/src/org/jetbrains/android/dom/AndroidXmlExtension.java +++ b/plugins/android/src/org/jetbrains/android/dom/AndroidXmlExtension.java @@ -26,14 +26,18 @@ import com.intellij.xml.DefaultXmlExtension; import org.jetbrains.android.dom.manifest.ManifestDomFileDescription; import org.jetbrains.android.facet.AndroidFacet; import org.jetbrains.android.util.AndroidResourceUtil; +import org.jetbrains.annotations.Nullable; /** * @author Eugene.Kudelevsky */ public class AndroidXmlExtension extends DefaultXmlExtension { + @Nullable @Override public TagNameReference createTagNameReference(ASTNode nameElement, boolean startTagFlag) { - return new AndroidClassTagNameReference(nameElement, startTagFlag); + return AndroidXmlReferenceProvider.areReferencesProvidedByReferenceProvider(nameElement) + ? null + : new AndroidClassTagNameReference(nameElement, startTagFlag); } @Override diff --git a/plugins/android/src/org/jetbrains/android/dom/AndroidXmlReferenceProvider.java b/plugins/android/src/org/jetbrains/android/dom/AndroidXmlReferenceProvider.java new file mode 100644 index 000000000000..03983c27f831 --- /dev/null +++ b/plugins/android/src/org/jetbrains/android/dom/AndroidXmlReferenceProvider.java @@ -0,0 +1,232 @@ +package org.jetbrains.android.dom; + +import com.intellij.codeInsight.completion.JavaLookupElementBuilder; +import com.intellij.lang.ASTNode; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.*; +import com.intellij.psi.impl.source.resolve.ResolveCache; +import com.intellij.psi.search.searches.ClassInheritorsSearch; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.xml.XmlChildRole; +import com.intellij.psi.xml.XmlTag; +import com.intellij.util.ArrayUtil; +import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ProcessingContext; +import com.intellij.util.Processor; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; +import java.util.List; + +/** + * @author Eugene.Kudelevsky + */ +public class AndroidXmlReferenceProvider extends PsiReferenceProvider { + @NotNull + @Override + public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) { + if (!(element instanceof XmlTag)) { + return PsiReference.EMPTY_ARRAY; + } + final Module module = ModuleUtilCore.findModuleForPsiElement(element); + + if (module == null || AndroidFacet.getInstance(module) == null) { + return PsiReference.EMPTY_ARRAY; + } + final ASTNode startTagName = XmlChildRole.START_TAG_NAME_FINDER.findChild(element.getNode()); + + final String baseClassQName = computeBaseClass((XmlTag)element); + if (baseClassQName == null) { + return PsiReference.EMPTY_ARRAY; + } + final List result = new ArrayList(); + final XmlTag tag = (XmlTag)element; + + if (startTagName != null && areReferencesProvidedByReferenceProvider(startTagName)) { + addReferences(tag, startTagName.getPsi(), result, module, baseClassQName, true); + } + final ASTNode closingTagName = XmlChildRole.CLOSING_TAG_NAME_FINDER.findChild(element.getNode()); + + if (closingTagName != null && areReferencesProvidedByReferenceProvider(closingTagName)) { + addReferences(tag, closingTagName.getPsi(), result, module, baseClassQName, false); + } + return result.toArray(new PsiReference[result.size()]); + } + + private static void addReferences(@NotNull XmlTag tag, + @NotNull PsiElement nameElement, + @NotNull List result, + @NotNull Module module, + @NotNull String baseClassQName, + boolean startTag) { + final String text = nameElement.getText(); + if (text == null) { + return; + } + final String[] nameParts = text.split("\\."); + + if (nameParts.length == 0) { + return; + } + int offset = 0; + + for (int i = 0; i < nameParts.length; i++) { + final String name = nameParts[i]; + + if (name.length() > 0) { + offset += name.length(); + final TextRange range = new TextRange(offset - name.length(), offset); + final boolean isPackage = i < nameParts.length - 1; + result.add(new MyClassOrPackageReference(tag, nameElement, range, isPackage, module, baseClassQName, startTag)); + } + offset++; + } + } + + public static boolean areReferencesProvidedByReferenceProvider(ASTNode nameElement) { + if (nameElement != null) { + final PsiElement psiNameElement = nameElement.getPsi(); + final XmlTag tag = psiNameElement != null + ? PsiTreeUtil.getParentOfType(psiNameElement, XmlTag.class) + : null; + if (tag != null) { + final String baseClassQName = computeBaseClass(tag); + + if (baseClassQName != null) { + final String text = nameElement.getText(); + return text != null && text.contains("."); + } + } + } + return false; + } + + @Nullable + private static String computeBaseClass(XmlTag context) { + final XmlTag parentTag = context.getParentTag(); + final Pair pair = + AndroidDomElementDescriptorProvider.getDomElementAndBaseClassQName(parentTag != null ? parentTag : context); + return pair != null ? pair.getSecond() : null; + } + + private static class MyClassOrPackageReference extends PsiReferenceBase { + private final PsiElement myNameElement; + private final TextRange myRangeInNameElement; + private final boolean myIsPackage; + private final Module myModule; + private final String myBaseClassQName; + private final boolean myStartTag; + + public MyClassOrPackageReference(@NotNull XmlTag tag, + @NotNull PsiElement nameElement, + @NotNull TextRange rangeInNameElement, + boolean isPackage, + @NotNull Module module, + @NotNull String baseClassQName, + boolean startTag) { + super(tag, rangeInParent(nameElement, rangeInNameElement), true); + myNameElement = nameElement; + myRangeInNameElement = rangeInNameElement; + myIsPackage = isPackage; + myModule = module; + myBaseClassQName = baseClassQName; + myStartTag = startTag; + } + + private static TextRange rangeInParent(PsiElement element, TextRange range) { + final int offset = element.getStartOffsetInParent(); + return new TextRange(range.getStartOffset() + offset, range.getEndOffset() + offset); + } + + @Override + public PsiElement resolve() { + return ResolveCache.getInstance(myElement.getProject()).resolveWithCaching(this, new ResolveCache.Resolver() { + @Nullable + @Override + public PsiElement resolve(@NotNull PsiReference reference, boolean incompleteCode) { + return resolveInner(); + } + }, false, false); + } + + @Nullable + private PsiElement resolveInner() { + final int end = myRangeInNameElement.getEndOffset(); + final String value = myNameElement.getText().substring(0, end); + final JavaPsiFacade facade = JavaPsiFacade.getInstance(myElement.getProject()); + + return myIsPackage ? + facade.findPackage(value) : + facade.findClass(value, myModule.getModuleWithDependenciesAndLibrariesScope(false)); + } + + @NotNull + @Override + public Object[] getVariants() { + final String prefix = myNameElement.getText().substring(0, myRangeInNameElement.getStartOffset()); + + if (!myStartTag) { + final ASTNode startTagNode = XmlChildRole.START_TAG_NAME_FINDER.findChild(myElement.getNode()); + if (startTagNode != null) { + final String startTagName = startTagNode.getText(); + if (startTagName != null) { + if (startTagName.startsWith(prefix)) { + return new Object[]{startTagName.substring(prefix.length())}; + } + } + } + return EMPTY_ARRAY; + } + final Project project = myModule.getProject(); + final PsiClass baseClass = + JavaPsiFacade.getInstance(project).findClass(myBaseClassQName, myModule.getModuleWithDependenciesAndLibrariesScope(false)); + + if (baseClass == null) { + return EMPTY_ARRAY; + } + final List result = new ArrayList(); + + ClassInheritorsSearch.search(baseClass, myModule.getModuleWithDependenciesScope(), true, true, false).forEach( + new Processor() { + @Override + public boolean process(PsiClass psiClass) { + if (psiClass.getContainingClass() != null) { + return true; + } + String name = psiClass.getQualifiedName(); + + if (name != null && name.startsWith(prefix)) { + name = name.substring(prefix.length()); + result.add(JavaLookupElementBuilder.forClass(psiClass, name, true)); + } + return true; + } + }); + return ArrayUtil.toObjectArray(result); + } + + @Override + public PsiElement bindToElement(@NotNull PsiElement element) throws IncorrectOperationException { + final String newName = myIsPackage + ? ((PsiPackage)element).getQualifiedName() + : ((PsiClass)element).getQualifiedName(); + final ElementManipulator manipulator = ElementManipulators.getManipulator(myNameElement); + final TextRange range = new TextRange(0, myRangeInNameElement.getEndOffset()); + return manipulator != null ? manipulator.handleContentChange(myNameElement, range, newName) : element; + } + + @Nullable + @Override + public PsiElement handleElementRename(String newElementName) throws IncorrectOperationException { + final ElementManipulator manipulator = ElementManipulators.getManipulator(myNameElement); + assert manipulator != null : "Cannot find manipulator for " + myNameElement; + return manipulator.handleContentChange(myNameElement, myRangeInNameElement, newElementName); + } + } +} diff --git a/plugins/android/testData/dom/layout/IncorrectView.java b/plugins/android/testData/dom/layout/IncorrectView.java new file mode 100644 index 000000000000..bcff1eefb7b7 --- /dev/null +++ b/plugins/android/testData/dom/layout/IncorrectView.java @@ -0,0 +1,6 @@ +package p1.p2; + +import android.view.View; + +public class IncorrectView { +} diff --git a/plugins/android/testData/dom/layout/LabelView1.java b/plugins/android/testData/dom/layout/LabelView1.java new file mode 100644 index 000000000000..1968838d1999 --- /dev/null +++ b/plugins/android/testData/dom/layout/LabelView1.java @@ -0,0 +1,6 @@ +package p1.p2; + +import android.view.View; + +public class LabelView1 extends View { +} diff --git a/plugins/android/testData/dom/layout/ctn1.xml b/plugins/android/testData/dom/layout/ctn1.xml new file mode 100644 index 000000000000..cb14768da081 --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn1.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn10.xml b/plugins/android/testData/dom/layout/ctn10.xml new file mode 100644 index 000000000000..445d6f8996c3 --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn10.xml @@ -0,0 +1,2 @@ + +> \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn2.xml b/plugins/android/testData/dom/layout/ctn2.xml new file mode 100644 index 000000000000..aaa5e93753cc --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn2.xml @@ -0,0 +1,3 @@ + + < + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn2_after.xml b/plugins/android/testData/dom/layout/ctn2_after.xml new file mode 100644 index 000000000000..ab0528149012 --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn2_after.xml @@ -0,0 +1,3 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn3.xml b/plugins/android/testData/dom/layout/ctn3.xml new file mode 100644 index 000000000000..31152aeef2cd --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn3.xml @@ -0,0 +1 @@ + xmlns:android="http://schemas.android.com/apk/res/android"/> \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn3_after.xml b/plugins/android/testData/dom/layout/ctn3_after.xml new file mode 100644 index 000000000000..825e46a90227 --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn3_after.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn4.xml b/plugins/android/testData/dom/layout/ctn4.xml new file mode 100644 index 000000000000..4f13cbbd083f --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn4.xml @@ -0,0 +1 @@ + xmlns:android="http://schemas.android.com/apk/res/android"/> \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn5.xml b/plugins/android/testData/dom/layout/ctn5.xml new file mode 100644 index 000000000000..9df776cfb0ad --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn5.xml @@ -0,0 +1 @@ +< xmlns:android="http://schemas.android.com/apk/res/android"/> \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn5_after.xml b/plugins/android/testData/dom/layout/ctn5_after.xml new file mode 100644 index 000000000000..825e46a90227 --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn5_after.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn6.xml b/plugins/android/testData/dom/layout/ctn6.xml new file mode 100644 index 000000000000..23ce49dc43bb --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn6.xml @@ -0,0 +1,4 @@ + + + > + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn6_after.xml b/plugins/android/testData/dom/layout/ctn6_after.xml new file mode 100644 index 000000000000..f869ae3a551b --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn6_after.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn7.xml b/plugins/android/testData/dom/layout/ctn7.xml new file mode 100644 index 000000000000..1846f6c61f4c --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn7.xml @@ -0,0 +1,4 @@ + + + > + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn8.xml b/plugins/android/testData/dom/layout/ctn8.xml new file mode 100644 index 000000000000..cfb3ae051bef --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn8.xml @@ -0,0 +1,4 @@ + + + > + \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn9.xml b/plugins/android/testData/dom/layout/ctn9.xml new file mode 100644 index 000000000000..8714b283507a --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn9.xml @@ -0,0 +1,2 @@ + +> \ No newline at end of file diff --git a/plugins/android/testData/dom/layout/ctn9_after.xml b/plugins/android/testData/dom/layout/ctn9_after.xml new file mode 100644 index 000000000000..e469832121a5 --- /dev/null +++ b/plugins/android/testData/dom/layout/ctn9_after.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/plugins/android/testData/dom/xml/MyPreference.java b/plugins/android/testData/dom/xml/MyPreference.java new file mode 100644 index 000000000000..02918f2fea2c --- /dev/null +++ b/plugins/android/testData/dom/xml/MyPreference.java @@ -0,0 +1,6 @@ +package p1.p2; + +import android.preference.CheckBoxPreference; + +public class MyPreference extends CheckBoxPreference { +} diff --git a/plugins/android/testData/dom/xml/customPref1.xml b/plugins/android/testData/dom/xml/customPref1.xml new file mode 100644 index 000000000000..d434639e322c --- /dev/null +++ b/plugins/android/testData/dom/xml/customPref1.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/plugins/android/testData/dom/xml/customPref1_after.xml b/plugins/android/testData/dom/xml/customPref1_after.xml new file mode 100644 index 000000000000..a24a4204ee4d --- /dev/null +++ b/plugins/android/testData/dom/xml/customPref1_after.xml @@ -0,0 +1 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/dom/xml/customPref2_after.xml b/plugins/android/testData/dom/xml/customPref2_after.xml new file mode 100644 index 000000000000..18f80fbec4ba --- /dev/null +++ b/plugins/android/testData/dom/xml/customPref2_after.xml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/plugins/android/testData/rename/Dummy.java b/plugins/android/testData/rename/Dummy.java new file mode 100644 index 000000000000..63c0c9ad410a --- /dev/null +++ b/plugins/android/testData/rename/Dummy.java @@ -0,0 +1,3 @@ +package p1.newp; + +public class Dummy {} \ No newline at end of file diff --git a/plugins/android/testData/rename/MyPreference.java b/plugins/android/testData/rename/MyPreference.java new file mode 100644 index 000000000000..02918f2fea2c --- /dev/null +++ b/plugins/android/testData/rename/MyPreference.java @@ -0,0 +1,6 @@ +package p1.p2; + +import android.preference.CheckBoxPreference; + +public class MyPreference extends CheckBoxPreference { +} diff --git a/plugins/android/testData/rename/custom_pref.xml b/plugins/android/testData/rename/custom_pref.xml new file mode 100644 index 000000000000..d7bbf6d351d6 --- /dev/null +++ b/plugins/android/testData/rename/custom_pref.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/rename/custom_pref_after.xml b/plugins/android/testData/rename/custom_pref_after.xml new file mode 100644 index 000000000000..9fa9aefe4b18 --- /dev/null +++ b/plugins/android/testData/rename/custom_pref_after.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/rename/layout_widget1.xml b/plugins/android/testData/rename/layout_widget1.xml new file mode 100644 index 000000000000..93dcdbc0af98 --- /dev/null +++ b/plugins/android/testData/rename/layout_widget1.xml @@ -0,0 +1,4 @@ + +2.MyWidget xmlns:android="http://schemas.android.com/apk/res/android"> + + \ No newline at end of file diff --git a/plugins/android/testData/rename/layout_widget1_after.xml b/plugins/android/testData/rename/layout_widget1_after.xml new file mode 100644 index 000000000000..0151e9eb5230 --- /dev/null +++ b/plugins/android/testData/rename/layout_widget1_after.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testData/rename/layout_widget2.xml b/plugins/android/testData/rename/layout_widget2.xml new file mode 100644 index 000000000000..93dcdbc0af98 --- /dev/null +++ b/plugins/android/testData/rename/layout_widget2.xml @@ -0,0 +1,4 @@ + +2.MyWidget xmlns:android="http://schemas.android.com/apk/res/android"> + + \ No newline at end of file diff --git a/plugins/android/testData/rename/layout_widget2_after.xml b/plugins/android/testData/rename/layout_widget2_after.xml new file mode 100644 index 000000000000..b90181025adc --- /dev/null +++ b/plugins/android/testData/rename/layout_widget2_after.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/plugins/android/testSrc/org/jetbrains/android/AndroidRenameTest.java b/plugins/android/testSrc/org/jetbrains/android/AndroidRenameTest.java index e51b34026cfb..9aecbb360b17 100644 --- a/plugins/android/testSrc/org/jetbrains/android/AndroidRenameTest.java +++ b/plugins/android/testSrc/org/jetbrains/android/AndroidRenameTest.java @@ -306,6 +306,28 @@ public class AndroidRenameTest extends AndroidTestCase { myFixture.checkResultByFile(BASE_PATH + "layout_widget_after.xml"); } + public void testRenameWidgetPackage1() throws Throwable { + createManifest(); + myFixture.copyFileToProject(BASE_PATH + "MyWidget.java", "src/p1/p2/MyWidget.java"); + VirtualFile file = myFixture.copyFileToProject(BASE_PATH + "layout_widget1.xml", "res/layout/layout_widget1.xml"); + myFixture.configureFromExistingVirtualFile(file); + checkAndRename("newPackage"); + myFixture.checkResultByFile(BASE_PATH + "layout_widget1_after.xml"); + } + + public void testMoveWidgetPackage1() throws Throwable { + createManifest(); + myFixture.copyFileToProject(BASE_PATH + "Dummy.java", "src/p1/newp/Dummy.java"); + myFixture.copyFileToProject(BASE_PATH + "MyWidget.java", "src/p1/p2/MyWidget.java"); + myFixture.copyFileToProject(BASE_PATH + "MyPreference.java", "src/p1/p2/MyPreference.java"); + final VirtualFile f = myFixture.copyFileToProject(BASE_PATH + "layout_widget2.xml", "res/layout/layout_widget2.xml"); + myFixture.configureFromExistingVirtualFile(f); + myFixture.copyFileToProject(BASE_PATH + "custom_pref.xml", "res/xml/custom_pref.xml"); + doMovePackage("p1.p2", "p1.newp"); + myFixture.checkResultByFile("res/layout/layout_widget2.xml", BASE_PATH + "layout_widget2_after.xml", false); + myFixture.checkResultByFile("res/xml/custom_pref.xml", BASE_PATH + "custom_pref_after.xml", false); + } + private void doMovePackageTest(String packageName, String newPackageName, String activityPath) throws Exception { myFixture.copyDirectoryToProject(BASE_PATH + "empty", "src/p1/p2/p3"); myFixture.copyFileToProject(BASE_PATH + "MyActivity.java", activityPath); diff --git a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java index d478c81d768c..dce91c81b43b 100644 --- a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java +++ b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidLayoutDomTest.java @@ -2,6 +2,7 @@ package org.jetbrains.android.dom; import com.android.sdklib.SdkConstants; import com.intellij.codeInsight.TargetElementUtilBase; +import com.intellij.codeInsight.completion.CompletionType; import com.intellij.codeInsight.completion.impl.CamelHumpMatcher; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInsight.intention.IntentionAction; @@ -99,6 +100,68 @@ public class AndroidLayoutDomTest extends AndroidDomTest { toTestCompletion("ctn.xml", "ctn_after.xml"); } + public void testCustomTagCompletion1() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + copyFileToProject("LabelView1.java", "src/p1/p2/LabelView1.java"); + copyFileToProject("IncorrectView.java", "src/p1/p2/IncorrectView.java"); + doTestCompletionVariants("ctn1.xml", "p2.LabelView", "p2.LabelView1"); + } + + public void testCustomTagCompletion2() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + VirtualFile file = copyFileToProject("ctn2.xml"); + myFixture.configureFromExistingVirtualFile(file); + myFixture.complete(CompletionType.BASIC); + myFixture.type("p1\n"); + myFixture.checkResultByFile(testFolder + '/' + "ctn2_after.xml"); + } + + public void testCustomTagCompletion3() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + toTestCompletion("ctn3.xml", "ctn3_after.xml"); + } + + public void testCustomTagCompletion4() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + doTestCompletionVariants("ctn4.xml", "LabelView"); + } + + public void testCustomTagCompletion5() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + VirtualFile file = copyFileToProject("ctn5.xml"); + myFixture.configureFromExistingVirtualFile(file); + myFixture.complete(CompletionType.BASIC); + myFixture.type("p1\n"); + myFixture.checkResultByFile(testFolder + '/' + "ctn5_after.xml"); + } + + public void testCustomTagCompletion6() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + toTestCompletion("ctn6.xml", "ctn6_after.xml"); + } + + public void testCustomTagCompletion7() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + toTestCompletion("ctn7.xml", "ctn6_after.xml"); + } + + public void testCustomTagCompletion8() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + copyFileToProject("LabelView1.java", "src/p1/p2/LabelView1.java"); + doTestCompletionVariants("ctn8.xml", "LabelView"); + } + + public void testCustomTagCompletion9() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + toTestCompletion("ctn9.xml", "ctn9_after.xml"); + } + + public void testCustomTagCompletion10() throws Throwable { + copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); + copyFileToProject("LabelView1.java", "src/p1/p2/LabelView1.java"); + doTestCompletionVariants("ctn10.xml", "LabelView"); + } + public void testCustomAttributeNameCompletion() throws Throwable { copyFileToProject("LabelView.java", "src/p1/p2/LabelView.java"); doTestCompletionVariants("can.xml", "text", "textColor", "textSize"); @@ -302,7 +365,7 @@ public class AndroidLayoutDomTest extends AndroidDomTest { myFixture.configureFromExistingVirtualFile(file); PsiFile psiFile = myFixture.getFile(); String text = psiFile.getText(); - int rootOffset = text.indexOf("android.widget.ScrollView"); + int rootOffset = text.indexOf("ScrollView"); PsiReference rootReference = psiFile.findReferenceAt(rootOffset); assertNotNull(rootReference); PsiElement rootViewClass = rootReference.resolve(); diff --git a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidXmlResourcesDomTest.java b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidXmlResourcesDomTest.java index 9563ba6c8b14..ecadbba9b792 100644 --- a/plugins/android/testSrc/org/jetbrains/android/dom/AndroidXmlResourcesDomTest.java +++ b/plugins/android/testSrc/org/jetbrains/android/dom/AndroidXmlResourcesDomTest.java @@ -133,6 +133,16 @@ public class AndroidXmlResourcesDomTest extends AndroidDomTest { doTestHighlighting(); } + public void testCustomPreference1() throws Throwable { + copyFileToProject("MyPreference.java", "src/p1/p2/MyPreference.java"); + toTestCompletion("customPref1.xml", "customPref1_after.xml"); + } + + public void testCustomPreference2() throws Throwable { + copyFileToProject("MyPreference.java", "src/p1/p2/MyPreference.java"); + toTestCompletion("customPref2.xml", "customPref2_after.xml"); + } + public void testPreferenceHeaders1() throws Throwable { doTestCompletion(); } diff --git a/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java b/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java index 23f7c254c96b..36b847d56788 100644 --- a/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java +++ b/xml/impl/src/com/intellij/psi/impl/source/xml/XmlTagImpl.java @@ -150,13 +150,17 @@ public class XmlTagImpl extends XmlElementImpl implements XmlTag { String prefix = getNamespacePrefix(); TagNameReference startTagRef = TagNameReference.createTagNameReference(this, startTagName, true); - refs.add(startTagRef); + if (startTagRef != null) { + refs.add(startTagRef); + } if (prefix.length() > 0) { refs.add(createPrefixReference(startTagName, prefix, startTagRef)); } if (endTagName != null) { TagNameReference endTagRef = TagNameReference.createTagNameReference(this, endTagName, false); - refs.add(endTagRef); + if (endTagRef != null) { + refs.add(endTagRef); + } prefix = XmlUtil.findPrefixByQualifiedName(endTagName.getText()); if (StringUtil.isNotEmpty(prefix)) { refs.add(createPrefixReference(endTagName, prefix, endTagRef)); diff --git a/xml/impl/src/com/intellij/xml/XmlExtension.java b/xml/impl/src/com/intellij/xml/XmlExtension.java index cddc55479e31..79d6d47fc49e 100644 --- a/xml/impl/src/com/intellij/xml/XmlExtension.java +++ b/xml/impl/src/com/intellij/xml/XmlExtension.java @@ -32,7 +32,10 @@ import com.intellij.psi.impl.source.xml.SchemaPrefix; import com.intellij.psi.impl.source.xml.TagNameReference; import com.intellij.psi.search.LocalSearchScope; import com.intellij.psi.search.SearchScope; -import com.intellij.psi.xml.*; +import com.intellij.psi.xml.XmlAttribute; +import com.intellij.psi.xml.XmlDocument; +import com.intellij.psi.xml.XmlFile; +import com.intellij.psi.xml.XmlTag; import com.intellij.util.IncorrectOperationException; import com.intellij.xml.util.XmlUtil; import org.jetbrains.annotations.NonNls; @@ -78,6 +81,7 @@ public abstract class XmlExtension { @NotNull public abstract Set guessUnboundNamespaces(@NotNull PsiElement element, final XmlFile file); + @Nullable public TagNameReference createTagNameReference(final ASTNode nameElement, final boolean startTagFlag) { return new TagNameReference(nameElement, startTagFlag); }