diff --git a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt index d1db2dfd4ba..a752e1ed106 100644 --- a/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt +++ b/idea/src/org/jetbrains/kotlin/idea/search/ideaExtensions/KotlinDefinitionsSearcher.kt @@ -17,9 +17,11 @@ package org.jetbrains.kotlin.idea.search.ideaExtensions import com.intellij.codeInsight.navigation.MethodImplementationsSearch -import com.intellij.openapi.application.runReadAction +import com.intellij.psi.PsiClass import com.intellij.psi.PsiElement import com.intellij.psi.PsiMethod +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.LocalSearchScope import com.intellij.psi.search.SearchScope import com.intellij.psi.search.searches.ClassInheritorsSearch import com.intellij.psi.search.searches.DefinitionsScopedSearch @@ -27,9 +29,13 @@ import com.intellij.util.Processor import com.intellij.util.QueryExecutor import com.intellij.util.containers.ContainerUtil import org.jetbrains.kotlin.asJava.LightClassUtil +import org.jetbrains.kotlin.asJava.classes.KtLightClass import org.jetbrains.kotlin.asJava.elements.KtLightMethod import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.idea.util.application.runReadAction import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.contains import java.util.* class KotlinDefinitionsSearcher : QueryExecutor { @@ -42,6 +48,14 @@ class KotlinDefinitionsSearcher : QueryExecutor processClassImplementations(element, consumer) + is KtLightClass -> { + val useScope = runReadAction { element.useScope } + if (useScope is LocalSearchScope) + processLightClassLocalImplementations(element, useScope, consumer) + else + true + } + is KtNamedFunction, is KtSecondaryConstructor -> processFunctionImplementations(element as KtFunction, scope, consumer) @@ -73,11 +87,30 @@ class KotlinDefinitionsSearcher : QueryExecutor): Boolean { - val psiClass = runReadAction { klass.toLightClass() } - if (psiClass != null) { - return ContainerUtil.process(ClassInheritorsSearch.search(psiClass, true), consumer) + val psiClass = runReadAction { klass.toLightClass() } ?: return true + + val searchScope = runReadAction { psiClass.useScope } + if (searchScope is LocalSearchScope) { + return processLightClassLocalImplementations(psiClass, searchScope, consumer) } - return true + + return ContainerUtil.process(ClassInheritorsSearch.search(psiClass, true), consumer) + } + + private fun processLightClassLocalImplementations(psiClass: KtLightClass, + searchScope: LocalSearchScope, + consumer: Processor): Boolean { + // workaround for IDEA optimization that uses Java PSI traversal to locate inheritors in local search scope + val globalScope = GlobalSearchScope.filesScope(psiClass.project, searchScope.virtualFiles.toList()) + return ContainerUtil.process(ClassInheritorsSearch.search(psiClass, globalScope, true), Processor { candidate -> + val candidateOrigin = candidate.unwrapped ?: candidate + if (candidateOrigin in searchScope) { + consumer.process(candidate) + } + else { + true + } + }) } private fun processFunctionImplementations(function: KtFunction, scope: SearchScope, consumer: Processor): Boolean { diff --git a/idea/testData/search/definitions/testNestedClass.kt b/idea/testData/search/definitions/testNestedClass.kt new file mode 100644 index 00000000000..86ad5d75ef3 --- /dev/null +++ b/idea/testData/search/definitions/testNestedClass.kt @@ -0,0 +1,8 @@ +// CLASS: Outer.Base +// SEARCH: class:Derived + +class Outer { + private abstract class Base + + private class Derived : Base() +} diff --git a/idea/tests/org/jetbrains/kotlin/search/DefinitionsSearchTest.kt b/idea/tests/org/jetbrains/kotlin/search/DefinitionsSearchTest.kt new file mode 100644 index 00000000000..f7e6ea2b7ee --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/search/DefinitionsSearchTest.kt @@ -0,0 +1,47 @@ +/* + * Copyright 2010-2016 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.search + +import com.intellij.openapi.util.io.FileUtil +import com.intellij.psi.search.searches.DefinitionsScopedSearch +import org.jetbrains.kotlin.asJava.classes.KtLightClass +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.test.InTextDirectivesUtils +import java.io.File + +class DefinitionsSearchTest : AbstractSearcherTest() { + fun testNestedClass() { + doTest() + } + + private fun doTest() { + myFixture.configureByFile(fileName) + val directives = InTextDirectivesUtils.findListWithPrefixes(FileUtil.loadFile(File(pathToFile), true), "// CLASS: ") + assertFalse("Specify CLASS directive in test file", directives.isEmpty()) + val superClassName = directives[0] + val psiClass = getPsiClass(superClassName) + checkResult(DefinitionsScopedSearch.search(psiClass)) + + val origin = (psiClass as? KtLightClass)?.kotlinOrigin!! + checkResult(DefinitionsScopedSearch.search(origin)) + } + + override fun getTestDataPath(): String { + return File(PluginTestCaseBase.getTestDataPathBase(), "/search/definitions").path + File.separator + + } +} \ No newline at end of file