From ee33c09714bdabacf5ed512e0a89b8bed1a95e8b Mon Sep 17 00:00:00 2001 From: "Pavel V. Talanov" Date: Tue, 7 Oct 2014 13:49:23 +0400 Subject: [PATCH] Android: navigate to resource Implementation mostly copied from android plugin #KT-5596 Fixed --- .../kotlin-android-plugin.iml | 1 + .../KotlinAndroidGotoDeclarationHandler.java | 152 ++++++++++++++++++ .../android/navigation/gotoResourceHelper.kt | 112 +++++++++++++ idea/src/META-INF/android.xml | 3 + 4 files changed, 268 insertions(+) create mode 100644 idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/KotlinAndroidGotoDeclarationHandler.java create mode 100644 idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/gotoResourceHelper.kt diff --git a/idea/kotlin-android-plugin/kotlin-android-plugin.iml b/idea/kotlin-android-plugin/kotlin-android-plugin.iml index 2ddbd26993d..8f1c11eea81 100644 --- a/idea/kotlin-android-plugin/kotlin-android-plugin.iml +++ b/idea/kotlin-android-plugin/kotlin-android-plugin.iml @@ -10,6 +10,7 @@ + diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/KotlinAndroidGotoDeclarationHandler.java b/idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/KotlinAndroidGotoDeclarationHandler.java new file mode 100644 index 00000000000..f5d0a130b49 --- /dev/null +++ b/idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/KotlinAndroidGotoDeclarationHandler.java @@ -0,0 +1,152 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.android.navigation; + +import com.android.resources.ResourceType; +import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler; +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.xml.XmlElement; +import org.jetbrains.android.dom.AndroidAttributeValue; +import org.jetbrains.android.dom.manifest.Manifest; +import org.jetbrains.android.dom.manifest.ManifestElementWithRequiredName; +import org.jetbrains.android.dom.resources.Attr; +import org.jetbrains.android.dom.resources.DeclareStyleable; +import org.jetbrains.android.facet.AndroidFacet; +import org.jetbrains.android.resourceManagers.LocalResourceManager; +import org.jetbrains.android.resourceManagers.ResourceManager; +import org.jetbrains.android.util.AndroidResourceUtil; +import org.jetbrains.android.util.AndroidUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +// TODO: ask for extension point +// this class is mostly copied from org.jetbrains.android.AndroidGotoDeclarationHandler +public class KotlinAndroidGotoDeclarationHandler implements GotoDeclarationHandler { + @Override + public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement sourceElement, int offset, Editor editor) { + JetSimpleNameExpression referenceExpression = NavigationPackage.getReferenceExpression(sourceElement); + if (referenceExpression == null) { + return null; + } + + PsiFile file = referenceExpression.getContainingFile(); + if (file == null) { + return null; + } + + AndroidFacet facet = AndroidFacet.getInstance(file); + if (facet == null) { + return null; + } + + AndroidResourceUtil.MyReferredResourceFieldInfo info = NavigationPackage.getInfo(referenceExpression, facet); + + if (info == null) return null; + + String nestedClassName = info.getClassName(); + String fieldName = info.getFieldName(); + List resourceList = new ArrayList(); + + if (info.isFromManifest()) { + collectManifestElements(nestedClassName, fieldName, facet, resourceList); + } + else { + ResourceManager manager = info.isSystem() + ? facet.getSystemResourceManager(false) + : facet.getLocalResourceManager(); + if (manager == null) { + return null; + } + manager.collectLazyResourceElements(nestedClassName, fieldName, false, referenceExpression, resourceList); + + if (manager instanceof LocalResourceManager) { + LocalResourceManager lrm = (LocalResourceManager) manager; + + if (nestedClassName.equals(ResourceType.ATTR.getName())) { + for (Attr attr : lrm.findAttrs(fieldName)) { + resourceList.add(attr.getName().getXmlAttributeValue()); + } + } + else if (nestedClassName.equals(ResourceType.STYLEABLE.getName())) { + for (DeclareStyleable styleable : lrm.findStyleables(fieldName)) { + resourceList.add(styleable.getName().getXmlAttributeValue()); + } + + for (Attr styleable : lrm.findStyleableAttributesByFieldName(fieldName)) { + resourceList.add(styleable.getName().getXmlAttributeValue()); + } + } + } + } + + if (resourceList.size() > 1) { + // Sort to ensure the output is stable, and to prefer the base folders + Collections.sort(resourceList, AndroidResourceUtil.RESOURCE_ELEMENT_COMPARATOR); + } + + return resourceList.toArray(new PsiElement[resourceList.size()]); + } + + private static void collectManifestElements( + @NotNull String nestedClassName, + @NotNull String fieldName, + @NotNull AndroidFacet facet, + @NotNull List result + ) { + Manifest manifest = facet.getManifest(); + + if (manifest == null) { + return; + } + List list; + + if ("permission".equals(nestedClassName)) { + list = manifest.getPermissions(); + } + else if ("permission_group".equals(nestedClassName)) { + list = manifest.getPermissionGroups(); + } + else { + return; + } + for (ManifestElementWithRequiredName domElement : list) { + AndroidAttributeValue nameAttribute = domElement.getName(); + String name = nameAttribute.getValue(); + + if (AndroidUtils.equal(name, fieldName, false)) { + XmlElement psiElement = nameAttribute.getXmlAttributeValue(); + + if (psiElement != null) { + result.add(psiElement); + } + } + } + } + + @Override + public String getActionText(DataContext context) { + return null; + } +} diff --git a/idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/gotoResourceHelper.kt b/idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/gotoResourceHelper.kt new file mode 100644 index 00000000000..33cc97e03b3 --- /dev/null +++ b/idea/kotlin-android-plugin/src/org/jetbrains/jet/android/navigation/gotoResourceHelper.kt @@ -0,0 +1,112 @@ +/* + * Copyright 2010-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.jet.android.navigation + +import org.jetbrains.android.util.AndroidResourceUtil +import com.intellij.psi.PsiElement +import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.jet.lang.psi.JetSimpleNameExpression +import com.intellij.psi.util.PsiTreeUtil +import com.intellij.psi.PsiClass +import org.jetbrains.android.util.AndroidUtils +import com.android.SdkConstants +import org.jetbrains.android.augment.AndroidPsiElementFinder +import org.jetbrains.jet.lang.psi.JetDotQualifiedExpression +import org.jetbrains.jet.lang.psi.psiUtil.getReceiverExpression +import org.jetbrains.jet.lang.psi.JetExpression + +private fun getReferenceExpression(element: PsiElement?): JetSimpleNameExpression? { + return PsiTreeUtil.getParentOfType(element, javaClass()) +} + +// given 'R.a.b' returns info for all three parts of the expression 'a', 'b', 'R' +private fun getInfo( + referenceExpression: JetSimpleNameExpression, + facet: AndroidFacet +): AndroidResourceUtil.MyReferredResourceFieldInfo? { + val info = getReferredInfo(referenceExpression, facet) + if (info != null) return info + + val topMostQualified = referenceExpression.getParentQualified().getParentQualified() ?: return null + val selectorCandidate = topMostQualified.getSelectorExpression() as? JetSimpleNameExpression ?: return null + return getReferredInfo(selectorCandidate, facet) +} + +private fun JetExpression?.getParentQualified(): JetDotQualifiedExpression? { + return this?.getParent() as? JetDotQualifiedExpression +} + +// returns info if passed expression is 'b' in 'R.a.b' +private fun getReferredInfo( + lastPart: JetSimpleNameExpression, + facet: AndroidFacet +): AndroidResourceUtil.MyReferredResourceFieldInfo? { + val resFieldName = lastPart.getReferencedName() + if (resFieldName.isEmpty()) return null + + val middlePart = getReceiverAsSimpleNameExpression(lastPart) ?: return null + + val resClassName = middlePart.getReferencedName() + if (resClassName.isEmpty()) return null + + val firstPart = getReceiverAsSimpleNameExpression(middlePart) ?: return null + + val reference = firstPart.getReference() ?: return null + + val resolvedClass = reference.resolve() as? PsiClass ?: return null + + //the following code is copied from + // org.jetbrains.android.util.AndroidResourceUtil.getReferredResourceOrManifestField + // (org.jetbrains.android.facet.AndroidFacet, com.intellij.psi.PsiReferenceExpression, java.lang.String, boolean) + val classShortName = resolvedClass.getName() + + val fromManifest = AndroidUtils.MANIFEST_CLASS_NAME == classShortName + + if (!fromManifest && AndroidUtils.R_CLASS_NAME != classShortName) { + return null + } + val qName = resolvedClass.getQualifiedName() + + if (SdkConstants.CLASS_R == qName || AndroidPsiElementFinder.INTERNAL_R_CLASS_QNAME == qName) { + return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, true, false) + } + val containingFile = resolvedClass.getContainingFile() ?: return null + + val isFromCorrectFile = + if (fromManifest) AndroidResourceUtil.isManifestJavaFile(facet, containingFile) + else AndroidResourceUtil.isRJavaFile(facet, containingFile) + + if (!isFromCorrectFile) { + return null + } + + return AndroidResourceUtil.MyReferredResourceFieldInfo(resClassName, resFieldName, false, fromManifest) +} + +private fun getReceiverAsSimpleNameExpression(exp: JetSimpleNameExpression): JetSimpleNameExpression? { + val receiver = exp.getReceiverExpression() + return when (receiver) { + is JetSimpleNameExpression -> { + receiver + } + is JetDotQualifiedExpression -> { + receiver.getSelectorExpression() as? JetSimpleNameExpression + } + else -> null + } + +} \ No newline at end of file diff --git a/idea/src/META-INF/android.xml b/idea/src/META-INF/android.xml index 6fb76a66265..dd83633fe0d 100644 --- a/idea/src/META-INF/android.xml +++ b/idea/src/META-INF/android.xml @@ -1,2 +1,5 @@ + + + \ No newline at end of file