181: Fix compatibility after AS 3.0 was merged to 181
This commit is contained in:
committed by
Nikolay Krasko
parent
e322c2d70b
commit
f31b56a936
+137
@@ -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<PsiElement>, result: MutableCollection<LineMarkerInfo<PsiElement>>) {
|
||||||
|
elements.forEach {
|
||||||
|
(it as? KtClass)?.getLineMarkerInfo()?.let { marker -> result.add(marker) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtClass.getLineMarkerInfo(): LineMarkerInfo<PsiElement>? {
|
||||||
|
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<GotoRelatedItem> {
|
||||||
|
val resources = mutableSetOf<PsiFile>()
|
||||||
|
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<PsiFile>()
|
||||||
|
|
||||||
|
resources.addAll(files)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return resources.map { GotoRelatedLayoutItem(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KtClass.collectGoToRelatedManifestItems(manifest: Manifest): List<GotoRelatedItem> =
|
||||||
|
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) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+270
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
* 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.ide.common.rendering.api.ResourceValue;
|
||||||
|
import com.android.ide.common.resources.ResourceItem;
|
||||||
|
import com.android.ide.common.resources.ResourceRepository;
|
||||||
|
import com.android.ide.common.resources.ResourceResolver;
|
||||||
|
import com.android.resources.ResourceType;
|
||||||
|
import com.android.tools.idea.configurations.Configuration;
|
||||||
|
import com.android.tools.idea.configurations.ConfigurationManager;
|
||||||
|
import com.android.tools.idea.res.AppResourceRepository;
|
||||||
|
import com.android.tools.idea.res.ResourceHelper;
|
||||||
|
import com.android.tools.idea.ui.resourcechooser.ColorPicker;
|
||||||
|
import com.android.utils.XmlUtils;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction;
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||||
|
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||||
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
|
import com.intellij.openapi.editor.Editor;
|
||||||
|
import com.intellij.openapi.editor.markup.GutterIconRenderer;
|
||||||
|
import com.intellij.openapi.module.Module;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
|
import com.intellij.psi.PsiElement;
|
||||||
|
import com.intellij.psi.PsiFile;
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
|
import com.intellij.psi.xml.XmlAttribute;
|
||||||
|
import com.intellij.psi.xml.XmlAttributeValue;
|
||||||
|
import com.intellij.psi.xml.XmlTag;
|
||||||
|
import com.intellij.util.ui.ColorIcon;
|
||||||
|
import com.intellij.util.ui.EmptyIcon;
|
||||||
|
import com.intellij.util.ui.JBUI;
|
||||||
|
import org.jetbrains.android.facet.AndroidFacet;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.w3c.dom.Document;
|
||||||
|
import org.w3c.dom.Element;
|
||||||
|
import org.w3c.dom.Node;
|
||||||
|
import org.w3c.dom.NodeList;
|
||||||
|
|
||||||
|
import javax.swing.*;
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import static com.android.SdkConstants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains copied privates from AndroidColorAnnotator, so we could use them for Kotlin AndroidResourceReferenceAnnotator
|
||||||
|
*/
|
||||||
|
public class ResourceReferenceAnnotatorUtil {
|
||||||
|
|
||||||
|
public static final int ICON_SIZE = 8;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static File pickBitmapFromXml(@NotNull File file, @NotNull ResourceResolver resourceResolver, @NotNull Project project) {
|
||||||
|
try {
|
||||||
|
String xml = Files.toString(file, Charsets.UTF_8);
|
||||||
|
Document document = XmlUtils.parseDocumentSilently(xml, true);
|
||||||
|
if (document != null && document.getDocumentElement() != null) {
|
||||||
|
Element root = document.getDocumentElement();
|
||||||
|
String tag = root.getTagName();
|
||||||
|
Element target = null;
|
||||||
|
String attribute = null;
|
||||||
|
if ("vector".equals(tag)) {
|
||||||
|
// Vectors are handled in the icon cache
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
else if ("bitmap".equals(tag) || "nine-patch".equals(tag)) {
|
||||||
|
target = root;
|
||||||
|
attribute = ATTR_SRC;
|
||||||
|
}
|
||||||
|
else if ("selector".equals(tag) ||
|
||||||
|
"level-list".equals(tag) ||
|
||||||
|
"layer-list".equals(tag) ||
|
||||||
|
"transition".equals(tag)) {
|
||||||
|
NodeList children = root.getChildNodes();
|
||||||
|
for (int i = children.getLength() - 1; i >= 0; i--) {
|
||||||
|
Node item = children.item(i);
|
||||||
|
if (item.getNodeType() == Node.ELEMENT_NODE && TAG_ITEM.equals(item.getNodeName())) {
|
||||||
|
target = (Element)item;
|
||||||
|
if (target.hasAttributeNS(ANDROID_URI, ATTR_DRAWABLE)) {
|
||||||
|
attribute = ATTR_DRAWABLE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if ("clip".equals(tag) || "inset".equals(tag) || "scale".equals(tag)) {
|
||||||
|
target = root;
|
||||||
|
attribute = ATTR_DRAWABLE;
|
||||||
|
} else {
|
||||||
|
// <shape> etc - no bitmap to be found
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (attribute != null && target.hasAttributeNS(ANDROID_URI, attribute)) {
|
||||||
|
String src = target.getAttributeNS(ANDROID_URI, attribute);
|
||||||
|
ResourceValue value = resourceResolver.findResValue(src, false);
|
||||||
|
if (value != null) {
|
||||||
|
return ResourceHelper.resolveDrawable(resourceResolver, value, project);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Throwable ignore) {
|
||||||
|
// Not logging for now; afraid to risk unexpected crashes in upcoming preview. TODO: Re-enable.
|
||||||
|
//Logger.getInstance(AndroidColorAnnotator.class).warn(String.format("Could not read/render icon image %1$s", file), e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Looks up the resource item of the given type and name for the given configuration, if any */
|
||||||
|
@Nullable
|
||||||
|
public static ResourceValue findResourceValue(ResourceType type,
|
||||||
|
String name,
|
||||||
|
boolean isFramework,
|
||||||
|
Module module,
|
||||||
|
Configuration configuration) {
|
||||||
|
if (isFramework) {
|
||||||
|
ResourceRepository frameworkResources = configuration.getFrameworkResources();
|
||||||
|
if (frameworkResources == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!frameworkResources.hasResourceItem(type, name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
ResourceItem item = frameworkResources.getResourceItem(type, name);
|
||||||
|
return item.getResourceValue(type, configuration.getFullConfig(), false);
|
||||||
|
} else {
|
||||||
|
AppResourceRepository appResources = AppResourceRepository.getOrCreateInstance(module);
|
||||||
|
if (appResources == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!appResources.hasResourceItem(type, name)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return appResources.getConfiguredValue(type, name, configuration.getFullConfig());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Picks a suitable configuration to use for resource resolution */
|
||||||
|
@Nullable
|
||||||
|
public static Configuration pickConfiguration(AndroidFacet facet, Module module, PsiFile file) {
|
||||||
|
VirtualFile virtualFile = file.getVirtualFile();
|
||||||
|
if(virtualFile == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
VirtualFile parent = virtualFile.getParent();
|
||||||
|
if(parent == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
String parentName = parent.getName();
|
||||||
|
VirtualFile layout;
|
||||||
|
if(!parentName.startsWith("layout")) {
|
||||||
|
layout = ResourceHelper.pickAnyLayoutFile(module, facet);
|
||||||
|
if(layout == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
layout = virtualFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ConfigurationManager.getOrCreateInstance(module).getConfiguration(layout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ColorRenderer extends GutterIconRenderer {
|
||||||
|
private final PsiElement myElement;
|
||||||
|
private final Color myColor;
|
||||||
|
|
||||||
|
ColorRenderer(@NotNull PsiElement element, @Nullable Color color) {
|
||||||
|
myElement = element;
|
||||||
|
myColor = color;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public Icon getIcon() {
|
||||||
|
Color color = getCurrentColor();
|
||||||
|
return JBUI.scale(color == null ? EmptyIcon.create(ICON_SIZE) : new ColorIcon(ICON_SIZE, color));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Color getCurrentColor() {
|
||||||
|
if (myColor != null) {
|
||||||
|
return myColor;
|
||||||
|
} else if (myElement instanceof XmlTag) {
|
||||||
|
return ResourceHelper.parseColor(((XmlTag)myElement).getValue().getText());
|
||||||
|
} else if (myElement instanceof XmlAttributeValue) {
|
||||||
|
return ResourceHelper.parseColor(((XmlAttributeValue)myElement).getValue());
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AnAction getClickAction() {
|
||||||
|
if (myColor != null) { // Cannot set colors that were derived
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new AnAction() {
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(AnActionEvent e) {
|
||||||
|
final Editor editor = CommonDataKeys.EDITOR.getData(e.getDataContext());
|
||||||
|
if (editor != null) {
|
||||||
|
// Need ARGB support in platform color chooser; see
|
||||||
|
// https://youtrack.jetbrains.com/issue/IDEA-123498
|
||||||
|
//final Color color =
|
||||||
|
// ColorChooser.chooseColor(editor.getComponent(), AndroidBundle.message("android.choose.color"), getCurrentColor());
|
||||||
|
final Color color = ColorPicker.showDialog(editor.getComponent(), "Choose Color", getCurrentColor(), true, null, false);
|
||||||
|
if (color != null) {
|
||||||
|
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
if (myElement instanceof XmlTag) {
|
||||||
|
((XmlTag)myElement).getValue().setText(ResourceHelper.colorToString(color));
|
||||||
|
} else if (myElement instanceof XmlAttributeValue) {
|
||||||
|
XmlAttribute attribute = PsiTreeUtil.getParentOfType(myElement, XmlAttribute.class);
|
||||||
|
if (attribute != null) {
|
||||||
|
attribute.setValue(ResourceHelper.colorToString(color));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
|
ColorRenderer that = (ColorRenderer)o;
|
||||||
|
// TODO: Compare with modification count in app resources (if not framework)
|
||||||
|
if (myColor != null ? !myColor.equals(that.myColor) : that.myColor != null) return false;
|
||||||
|
if (!myElement.equals(that.myElement)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = myElement.hashCode();
|
||||||
|
result = 31 * result + (myColor != null ? myColor.hashCode() : 0);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
* 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.debugger
|
||||||
|
|
||||||
|
import com.intellij.openapi.module.ModuleManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.roots.ProjectRootModificationTracker
|
||||||
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
|
import com.intellij.psi.util.CachedValuesManager
|
||||||
|
import org.jetbrains.android.facet.AndroidFacet
|
||||||
|
import org.jetbrains.android.sdk.AndroidSdkData
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.AndroidDexer
|
||||||
|
import org.jetbrains.kotlin.idea.debugger.evaluate.classLoading.ClassToLoad
|
||||||
|
import java.io.File
|
||||||
|
import java.net.URLClassLoader
|
||||||
|
import java.security.ProtectionDomain
|
||||||
|
|
||||||
|
class AndroidDexerImpl(val project: Project) : AndroidDexer {
|
||||||
|
private val cachedDexWrapper = CachedValuesManager.getManager(project).createCachedValue({
|
||||||
|
val dexWrapper = doGetAndroidDexFile()?.let { dexJarFile ->
|
||||||
|
val androidDexWrapperName = AndroidDexWrapper::class.java.canonicalName
|
||||||
|
val classBytes = this.javaClass.classLoader.getResource(
|
||||||
|
androidDexWrapperName.replace('.', '/') + ".class").readBytes()
|
||||||
|
|
||||||
|
val dexClassLoader = object : URLClassLoader(arrayOf(dexJarFile.toURI().toURL()), this::class.java.classLoader) {
|
||||||
|
init {
|
||||||
|
defineClass(androidDexWrapperName, classBytes, 0, classBytes.size, null as ProtectionDomain?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class.forName(androidDexWrapperName, true, dexClassLoader).newInstance()
|
||||||
|
}
|
||||||
|
|
||||||
|
CachedValueProvider.Result.createSingleDependency(dexWrapper, ProjectRootModificationTracker.getInstance(project))
|
||||||
|
}, /* trackValue = */ false)
|
||||||
|
|
||||||
|
override fun dex(classes: Collection<ClassToLoad>): ByteArray? {
|
||||||
|
val dexWrapper = cachedDexWrapper.value
|
||||||
|
val dexMethod = dexWrapper::class.java.methods.firstOrNull { it.name == "dex" } ?: return null
|
||||||
|
return dexMethod.invoke(dexWrapper, classes) as? ByteArray ?: return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doGetAndroidDexFile(): File? {
|
||||||
|
for (module in ModuleManager.getInstance(project).modules) {
|
||||||
|
val androidFacet = AndroidFacet.getInstance(module) ?: continue
|
||||||
|
val sdkData = AndroidSdkData.getSdkData(androidFacet) ?: continue
|
||||||
|
val latestBuildTool = sdkData.getLatestBuildTool(/* allowPreview = */ false)
|
||||||
|
?: sdkData.getLatestBuildTool(/* allowPreview = */ true)
|
||||||
|
?: continue
|
||||||
|
|
||||||
|
val dxJar = File(latestBuildTool.location, "lib/dx.jar")
|
||||||
|
if (dxJar.exists()) {
|
||||||
|
return dxJar
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
+258
@@ -0,0 +1,258 @@
|
|||||||
|
/*
|
||||||
|
* 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.folding
|
||||||
|
|
||||||
|
import com.android.SdkConstants.*
|
||||||
|
import com.android.ide.common.resources.configuration.FolderConfiguration
|
||||||
|
import com.android.ide.common.resources.configuration.LocaleQualifier
|
||||||
|
import com.android.resources.ResourceType
|
||||||
|
import com.android.tools.idea.folding.AndroidFoldingSettings
|
||||||
|
import com.android.tools.idea.res.AppResourceRepository
|
||||||
|
import com.android.tools.idea.res.LocalResourceRepository
|
||||||
|
import com.intellij.lang.ASTNode
|
||||||
|
import com.intellij.lang.folding.FoldingBuilderEx
|
||||||
|
import com.intellij.lang.folding.FoldingDescriptor
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.components.ServiceManager
|
||||||
|
import com.intellij.openapi.editor.Document
|
||||||
|
import com.intellij.openapi.module.ModuleUtilCore
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.psi.PsiClass
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.impl.source.SourceTreeToPsiMap
|
||||||
|
import org.jetbrains.android.facet.AndroidFacet
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.uast.*
|
||||||
|
import org.jetbrains.uast.visitor.AbstractUastVisitor
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceFoldingBuilder : FoldingBuilderEx() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// See lint's StringFormatDetector
|
||||||
|
private val FORMAT = Pattern.compile("%(\\d+\\$)?([-+#, 0(<]*)?(\\d+)?(\\.\\d+)?([tT])?([a-zA-Z%])")
|
||||||
|
private val FOLD_MAX_LENGTH = 60
|
||||||
|
private val UNIT_TEST_MODE: Boolean = ApplicationManager.getApplication().isUnitTestMode
|
||||||
|
private val RESOURCE_TYPES = listOf(ResourceType.STRING,
|
||||||
|
ResourceType.DIMEN,
|
||||||
|
ResourceType.INTEGER,
|
||||||
|
ResourceType.PLURALS)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val isFoldingEnabled = AndroidFoldingSettings.getInstance().isCollapseAndroidStrings
|
||||||
|
|
||||||
|
override fun getPlaceholderText(node: ASTNode): String? {
|
||||||
|
|
||||||
|
tailrec fun UElement.unwrapReferenceAndGetValue(resources: LocalResourceRepository): String? = when (this) {
|
||||||
|
is UQualifiedReferenceExpression -> selector.unwrapReferenceAndGetValue(resources)
|
||||||
|
is UCallExpression -> (valueArguments.firstOrNull() as? UReferenceExpression)?.getAndroidResourceValue(resources, this)
|
||||||
|
else -> (this as? UReferenceExpression)?.getAndroidResourceValue(resources)
|
||||||
|
}
|
||||||
|
|
||||||
|
val element = SourceTreeToPsiMap.treeElementToPsi(node) ?: return null
|
||||||
|
val appResources = getAppResources(element) ?: return null
|
||||||
|
val uastContext = ServiceManager.getService(element.project, UastContext::class.java) ?: return null
|
||||||
|
return uastContext.convertElement(element, null, null)?.unwrapReferenceAndGetValue(appResources)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun buildFoldRegions(root: PsiElement, document: Document, quick: Boolean): Array<FoldingDescriptor> {
|
||||||
|
if (root !is KtFile || quick && !UNIT_TEST_MODE || !isFoldingEnabled || AndroidFacet.getInstance(root) == null) {
|
||||||
|
return emptyArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val file = root.toUElement()
|
||||||
|
val result = arrayListOf<FoldingDescriptor>()
|
||||||
|
file?.accept(object : AbstractUastVisitor() {
|
||||||
|
override fun visitSimpleNameReferenceExpression(node: USimpleNameReferenceExpression): Boolean {
|
||||||
|
node.getFoldingDescriptor()?.let { result.add(it) }
|
||||||
|
return super.visitSimpleNameReferenceExpression(node)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return result.toTypedArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isCollapsedByDefault(node: ASTNode): Boolean = isFoldingEnabled
|
||||||
|
|
||||||
|
private fun UReferenceExpression.getFoldingDescriptor(): FoldingDescriptor? {
|
||||||
|
val resolved = resolve() ?: return null
|
||||||
|
val resourceType = resolved.getAndroidResourceType() ?: return null
|
||||||
|
if (resourceType !in RESOURCE_TYPES) return null
|
||||||
|
|
||||||
|
fun UElement.createFoldingDescriptor() = psi?.let { psi ->
|
||||||
|
val dependencies: Set<PsiElement> = setOf(psi)
|
||||||
|
FoldingDescriptor(psi.node, psi.textRange, null, dependencies)
|
||||||
|
}
|
||||||
|
|
||||||
|
val element = uastParent as? UQualifiedReferenceExpression ?: this
|
||||||
|
val getResourceValueCall = (element.uastParent as? UCallExpression)?.takeIf { it.isFoldableGetResourceValueCall() }
|
||||||
|
if (getResourceValueCall != null) {
|
||||||
|
val qualifiedCall = getResourceValueCall.uastParent as? UQualifiedReferenceExpression
|
||||||
|
if (qualifiedCall?.selector == getResourceValueCall) {
|
||||||
|
return qualifiedCall.createFoldingDescriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
return getResourceValueCall.createFoldingDescriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
return element.createFoldingDescriptor()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UCallExpression.isFoldableGetResourceValueCall(): Boolean {
|
||||||
|
return methodName == "getString" ||
|
||||||
|
methodName == "getText" ||
|
||||||
|
methodName == "getInteger" ||
|
||||||
|
methodName?.startsWith("getDimension") ?: false ||
|
||||||
|
methodName?.startsWith("getQuantityString") ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.getAndroidResourceType(): ResourceType? {
|
||||||
|
val elementType = parent as? PsiClass ?: return null
|
||||||
|
val elementPackage = elementType.parent as? PsiClass ?: return null
|
||||||
|
if (R_CLASS != elementPackage.name) return null
|
||||||
|
if (elementPackage.qualifiedName != "$ANDROID_PKG.$R_CLASS") {
|
||||||
|
return ResourceType.getEnum(elementType.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun UReferenceExpression.getAndroidResourceValue(resources: LocalResourceRepository, call: UCallExpression? = null): String? {
|
||||||
|
val resourceType = resolve()?.getAndroidResourceType() ?: return null
|
||||||
|
val referenceConfig = FolderConfiguration().apply { localeQualifier = LocaleQualifier("xx") }
|
||||||
|
val key = resolvedName ?: return null
|
||||||
|
val resourceValue = resources.getResourceValue(resourceType, key, referenceConfig) ?: return null
|
||||||
|
val text = if (call != null) formatArguments(call, resourceValue) else resourceValue
|
||||||
|
|
||||||
|
if (resourceType == ResourceType.STRING || resourceType == ResourceType.PLURALS) {
|
||||||
|
return '"' + StringUtil.shortenTextWithEllipsis(text, FOLD_MAX_LENGTH - 2, 0) + '"'
|
||||||
|
}
|
||||||
|
else if (text.length <= 1) {
|
||||||
|
// Don't just inline empty or one-character replacements: they can't be expanded by a mouse click
|
||||||
|
// so are hard to use without knowing about the folding keyboard shortcut to toggle folding.
|
||||||
|
// This is similar to how IntelliJ 14 handles call parameters
|
||||||
|
return key + ": " + text
|
||||||
|
}
|
||||||
|
|
||||||
|
return StringUtil.shortenTextWithEllipsis(text, FOLD_MAX_LENGTH, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private tailrec fun LocalResourceRepository.getResourceValue(
|
||||||
|
type: ResourceType,
|
||||||
|
name: String,
|
||||||
|
referenceConfig: FolderConfiguration): String? {
|
||||||
|
val value = getConfiguredValue(type, name, referenceConfig)?.value ?: return null
|
||||||
|
if (!value.startsWith('@')) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
val (referencedTypeName, referencedName) = value.substring(1).split('/').takeIf { it.size == 2 } ?: return value
|
||||||
|
val referencedType = ResourceType.getEnum(referencedTypeName) ?: return value
|
||||||
|
return getResourceValue(referencedType, referencedName, referenceConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converted from com.android.tools.idea.folding.InlinedResource#insertArguments
|
||||||
|
private fun formatArguments(callExpression: UCallExpression, formatString: String): String {
|
||||||
|
if (!formatString.contains('%')) {
|
||||||
|
return formatString
|
||||||
|
}
|
||||||
|
|
||||||
|
val args = callExpression.valueArguments
|
||||||
|
if (args.isEmpty() || !args.first().isPsiValid) {
|
||||||
|
return formatString
|
||||||
|
}
|
||||||
|
|
||||||
|
val matcher = FORMAT.matcher(formatString)
|
||||||
|
var index = 0
|
||||||
|
var prevIndex = 0
|
||||||
|
var nextNumber = 1
|
||||||
|
var start = 0
|
||||||
|
val sb = StringBuilder(2 * formatString.length)
|
||||||
|
while (true) {
|
||||||
|
if (matcher.find(index)) {
|
||||||
|
if ("%" == matcher.group(6)) {
|
||||||
|
index = matcher.end()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val matchStart = matcher.start()
|
||||||
|
// Make sure this is not an escaped '%'
|
||||||
|
while (prevIndex < matchStart) {
|
||||||
|
val c = formatString[prevIndex]
|
||||||
|
if (c == '\\') {
|
||||||
|
prevIndex++
|
||||||
|
}
|
||||||
|
prevIndex++
|
||||||
|
}
|
||||||
|
if (prevIndex > matchStart) {
|
||||||
|
// We're in an escape, ignore this result
|
||||||
|
index = prevIndex
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
index = matcher.end()
|
||||||
|
|
||||||
|
// Shouldn't throw a number format exception since we've already
|
||||||
|
// matched the pattern in the regexp
|
||||||
|
val number: Int
|
||||||
|
var numberString: String? = matcher.group(1)
|
||||||
|
if (numberString != null) {
|
||||||
|
// Strip off trailing $
|
||||||
|
numberString = numberString.substring(0, numberString.length - 1)
|
||||||
|
number = Integer.parseInt(numberString)
|
||||||
|
nextNumber = number + 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
number = nextNumber++
|
||||||
|
}
|
||||||
|
|
||||||
|
if (number > 0 && number < args.size) {
|
||||||
|
val argExpression = args[number]
|
||||||
|
var value: Any? = argExpression.evaluate()
|
||||||
|
|
||||||
|
if (value == null) {
|
||||||
|
value = args[number].asSourceString()
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i in start..matchStart - 1) {
|
||||||
|
sb.append(formatString[i])
|
||||||
|
}
|
||||||
|
|
||||||
|
sb.append("{")
|
||||||
|
sb.append(value)
|
||||||
|
sb.append('}')
|
||||||
|
start = index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var i = start
|
||||||
|
val n = formatString.length
|
||||||
|
while (i < n) {
|
||||||
|
sb.append(formatString[i])
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAppResources(element: PsiElement): LocalResourceRepository? = ModuleUtilCore.findModuleForPsiElement(element)?.let {
|
||||||
|
AppResourceRepository.findExistingInstance(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
+83
@@ -0,0 +1,83 @@
|
|||||||
|
/*
|
||||||
|
* 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.inspection
|
||||||
|
|
||||||
|
import com.android.tools.idea.model.AndroidModuleInfo
|
||||||
|
import com.intellij.codeInspection.*
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.psi.PsiElementVisitor
|
||||||
|
import org.jetbrains.android.facet.AndroidFacet
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToCall
|
||||||
|
import org.jetbrains.kotlin.idea.inspections.AbstractKotlinInspection
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.KtPsiUtil.isUnsafeCast
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.addTypeArgument
|
||||||
|
|
||||||
|
class TypeParameterFindViewByIdInspection : AbstractKotlinInspection(), CleanupLocalInspectionTool {
|
||||||
|
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean, session: LocalInspectionToolSession): PsiElementVisitor {
|
||||||
|
val compileSdk = AndroidFacet.getInstance(session.file)
|
||||||
|
?.let { facet -> AndroidModuleInfo.getInstance(facet) }
|
||||||
|
?.buildSdkVersion
|
||||||
|
?.apiLevel
|
||||||
|
|
||||||
|
if (compileSdk == null || compileSdk < 26) {
|
||||||
|
return KtVisitorVoid()
|
||||||
|
}
|
||||||
|
|
||||||
|
return object : KtVisitorVoid() {
|
||||||
|
override fun visitCallExpression(expression: KtCallExpression) {
|
||||||
|
super.visitCallExpression(expression)
|
||||||
|
if (expression.calleeExpression?.text != "findViewById" || expression.typeArguments.isNotEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val parentCast = (expression.parent as? KtBinaryExpressionWithTypeRHS)?.takeIf { isUnsafeCast(it) } ?: return
|
||||||
|
val typeText = parentCast.right?.getTypeTextWithoutQuestionMark() ?: return
|
||||||
|
val callableDescriptor = expression.resolveToCall()?.resultingDescriptor ?: return
|
||||||
|
if (callableDescriptor.name.asString() != "findViewById" || callableDescriptor.typeParameters.size != 1) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
holder.registerProblem(
|
||||||
|
parentCast,
|
||||||
|
"Can be converted to findViewById<$typeText>(...)",
|
||||||
|
ConvertCastToFindViewByIdWithTypeParameter())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConvertCastToFindViewByIdWithTypeParameter : LocalQuickFix {
|
||||||
|
override fun getFamilyName(): String = "Convert cast to findViewById with type parameter"
|
||||||
|
|
||||||
|
override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
|
||||||
|
val cast = descriptor.psiElement as? KtBinaryExpressionWithTypeRHS ?: return
|
||||||
|
val typeText = cast.right?.getTypeTextWithoutQuestionMark() ?: return
|
||||||
|
val call = cast.left as? KtCallExpression ?: return
|
||||||
|
|
||||||
|
val newCall = call.copy() as KtCallExpression
|
||||||
|
val typeArgument = KtPsiFactory(call).createTypeArgument(typeText)
|
||||||
|
newCall.addTypeArgument(typeArgument)
|
||||||
|
|
||||||
|
cast.replace(newCall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun KtTypeReference.getTypeTextWithoutQuestionMark(): String? =
|
||||||
|
(typeElement as? KtNullableType)?.innerType?.text ?: typeElement?.text
|
||||||
|
}
|
||||||
|
}
|
||||||
+149
@@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 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.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.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.ModuleResourceManagers;
|
||||||
|
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.kotlin.android.AndroidUtilKt;
|
||||||
|
import org.jetbrains.kotlin.psi.KtSimpleNameExpression;
|
||||||
|
|
||||||
|
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) {
|
||||||
|
KtSimpleNameExpression referenceExpression = GotoResourceHelperKt.getReferenceExpression(sourceElement);
|
||||||
|
if (referenceExpression == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidFacet facet = AndroidUtilKt.getAndroidFacetForFile(referenceExpression);
|
||||||
|
if (facet == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
AndroidResourceUtil.MyReferredResourceFieldInfo info = GotoResourceHelperKt.getInfo(referenceExpression, facet);
|
||||||
|
|
||||||
|
if (info == null) return null;
|
||||||
|
|
||||||
|
String nestedClassName = info.getClassName();
|
||||||
|
String fieldName = info.getFieldName();
|
||||||
|
List<PsiElement> resourceList = new ArrayList<PsiElement>();
|
||||||
|
|
||||||
|
if (info.isFromManifest()) {
|
||||||
|
collectManifestElements(nestedClassName, fieldName, facet, resourceList);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
ModuleResourceManagers managers = ModuleResourceManagers.getInstance(facet);
|
||||||
|
ResourceManager manager = info.isSystem()
|
||||||
|
? managers.getSystemResourceManager(false)
|
||||||
|
: managers.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<PsiElement> result
|
||||||
|
) {
|
||||||
|
Manifest manifest = facet.getManifest();
|
||||||
|
|
||||||
|
if (manifest == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<? extends ManifestElementWithRequiredName> list;
|
||||||
|
|
||||||
|
if ("permission".equals(nestedClassName)) {
|
||||||
|
list = manifest.getPermissions();
|
||||||
|
}
|
||||||
|
else if ("permission_group".equals(nestedClassName)) {
|
||||||
|
list = manifest.getPermissionGroups();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (ManifestElementWithRequiredName domElement : list) {
|
||||||
|
AndroidAttributeValue<String> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<idea-plugin>
|
||||||
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
|
<externalAnnotator language="kotlin" implementationClass="org.jetbrains.android.inspections.lint.AndroidLintExternalAnnotator"/>
|
||||||
|
</extensions>
|
||||||
|
|
||||||
|
<extensions defaultExtensionNs="org.jetbrains.uast">
|
||||||
|
<uastLanguagePlugin implementation="org.jetbrains.uast.kotlin.KotlinUastLanguagePlugin"/>
|
||||||
|
</extensions>
|
||||||
|
</idea-plugin>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user