diff --git a/idea/idea-android/src/org/jetbrains/kotlin/android/KotlinAndroidLineMarkerProvider.kt.as31 b/idea/idea-android/src/org/jetbrains/kotlin/android/KotlinAndroidLineMarkerProvider.kt.as31 new file mode 100644 index 00000000000..09d7d37e255 --- /dev/null +++ b/idea/idea-android/src/org/jetbrains/kotlin/android/KotlinAndroidLineMarkerProvider.kt.as31 @@ -0,0 +1,137 @@ +/* + * Copyright 2010-2017 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.kotlin.android + +import com.android.SdkConstants +import com.android.resources.ResourceType +import com.intellij.codeHighlighting.Pass +import com.intellij.codeInsight.daemon.GutterIconNavigationHandler +import com.intellij.codeInsight.daemon.LineMarkerInfo +import com.intellij.codeInsight.daemon.LineMarkerProvider +import com.intellij.codeInsight.navigation.NavigationUtil +import com.intellij.icons.AllIcons +import com.intellij.ide.highlighter.XmlFileType +import com.intellij.navigation.GotoRelatedItem +import com.intellij.openapi.editor.markup.GutterIconRenderer +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile +import com.intellij.psi.xml.XmlAttributeValue +import com.intellij.ui.awt.RelativePoint +import com.intellij.util.Function +import org.jetbrains.android.dom.manifest.Manifest +import org.jetbrains.android.facet.AndroidFacet +import org.jetbrains.android.resourceManagers.ModuleResourceManagers +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.idea.caches.resolve.unsafeResolveToDescriptor +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode +import java.awt.event.MouseEvent +import javax.swing.Icon + + +class KotlinAndroidLineMarkerProvider : LineMarkerProvider { + override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? = null + + override fun collectSlowLineMarkers(elements: List, result: MutableCollection>) { + elements.forEach { + (it as? KtClass)?.getLineMarkerInfo()?.let { marker -> result.add(marker) } + } + } + + private fun KtClass.getLineMarkerInfo(): LineMarkerInfo? { + val nameIdentifier = nameIdentifier ?: return null + val androidFacet = AndroidFacet.getInstance(this) ?: return null + val manifest = androidFacet.manifest ?: return null + val manifestItems = collectGoToRelatedManifestItems(manifest) + if (manifestItems.isEmpty() && !isClassWithLayoutXml()) { + return null + } + + return LineMarkerInfo( + nameIdentifier, + nameIdentifier.textRange, + AllIcons.FileTypes.Xml, + Pass.LINE_MARKERS, + Function { "Related XML file" }, + GutterIconNavigationHandler { e: MouseEvent, _: PsiElement -> + NavigationUtil + .getRelatedItemsPopup( + manifestItems + collectGoToRelatedLayoutItems(androidFacet), + "Go to Related Files") + .show(RelativePoint(e)) + }, + GutterIconRenderer.Alignment.RIGHT) + } + + private fun KtClass.collectGoToRelatedLayoutItems(androidFacet: AndroidFacet): List { + val resources = mutableSetOf() + accept(object: KtVisitorVoid() { + override fun visitKtElement(element: KtElement) { + element.acceptChildren(this) + } + + override fun visitReferenceExpression(expression: KtReferenceExpression) { + super.visitReferenceExpression(expression) + + val resClassName = ResourceType.LAYOUT.getName() + val info = (expression as? KtSimpleNameExpression)?.let { + getReferredResourceOrManifestField(androidFacet, it, resClassName, true) + } + + if (info == null || info.isFromManifest) { + return + } + + val files = ModuleResourceManagers + .getInstance(androidFacet) + .localResourceManager + .findResourcesByFieldName(resClassName, info.fieldName) + .filterIsInstance() + + resources.addAll(files) + } + }) + + return resources.map { GotoRelatedLayoutItem(it) } + } + + private fun KtClass.collectGoToRelatedManifestItems(manifest: Manifest): List = + findComponentDeclarationInManifest(manifest)?.xmlAttributeValue?.let { listOf(GotoManifestItem(it)) } ?: emptyList() + + private class GotoManifestItem(attributeValue: XmlAttributeValue) : GotoRelatedItem(attributeValue) { + override fun getCustomName(): String? = "AndroidManifest.xml" + override fun getCustomContainerName(): String? = "" + override fun getCustomIcon(): Icon? = XmlFileType.INSTANCE.icon + } + + private class GotoRelatedLayoutItem(private val file: PsiFile) : GotoRelatedItem(file, "Layout Files") { + override fun getCustomContainerName(): String? = "(${file.containingDirectory.name})" + } + + companion object { + private val CLASSES_WITH_LAYOUT_XML = arrayOf( + SdkConstants.CLASS_ACTIVITY, + SdkConstants.CLASS_FRAGMENT, + SdkConstants.CLASS_V4_FRAGMENT, + "android.widget.Adapter") + + private fun KtClass.isClassWithLayoutXml(): Boolean { + val type = (unsafeResolveToDescriptor(BodyResolveMode.PARTIAL) as? ClassDescriptor)?.defaultType ?: return false + return CLASSES_WITH_LAYOUT_XML.any { type.isSubclassOf(it, true) } + } + } +} \ No newline at end of file