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