KT-38027 Support Code Vision feature in Kotlin
This commit is contained in:
@@ -40,6 +40,7 @@ import org.jetbrains.kotlin.idea.AbstractSmartSelectionTest
|
|||||||
import org.jetbrains.kotlin.idea.actions.AbstractGotoTestOrCodeActionTest
|
import org.jetbrains.kotlin.idea.actions.AbstractGotoTestOrCodeActionTest
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.*
|
import org.jetbrains.kotlin.idea.caches.resolve.*
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.*
|
import org.jetbrains.kotlin.idea.codeInsight.*
|
||||||
|
import org.jetbrains.kotlin.idea.codeInsight.codevision.AbstractKotlinCodeVisionProviderTest
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractCodeInsightActionTest
|
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractCodeInsightActionTest
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateHashCodeAndEqualsActionTest
|
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateHashCodeAndEqualsActionTest
|
||||||
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateTestSupportMethodActionTest
|
import org.jetbrains.kotlin.idea.codeInsight.generate.AbstractGenerateTestSupportMethodActionTest
|
||||||
@@ -867,6 +868,10 @@ fun main() {
|
|||||||
model("codeInsight/postfix")
|
model("codeInsight/postfix")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
testClass<AbstractKotlinCodeVisionProviderTest> {
|
||||||
|
model("codeInsight/codeVision")
|
||||||
|
}
|
||||||
|
|
||||||
testClass<AbstractScriptConfigurationHighlightingTest> {
|
testClass<AbstractScriptConfigurationHighlightingTest> {
|
||||||
model("script/definition/highlighting", extension = null, recursive = false)
|
model("script/definition/highlighting", extension = null, recursive = false)
|
||||||
model("script/definition/complex", extension = null, recursive = false, testMethod = "doComplexTest")
|
model("script/definition/complex", extension = null, recursive = false, testMethod = "doComplexTest")
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -2231,3 +2231,18 @@ title.import.layout=Import Layout
|
|||||||
title.packages.to.use.import.with=Packages to Use Import with '*'
|
title.packages.to.use.import.with=Packages to Use Import with '*'
|
||||||
redundant.qualifier.unnecessary.non.direct.parent.class.qualifier=Unnecessary non-direct parent classes qualifiers
|
redundant.qualifier.unnecessary.non.direct.parent.class.qualifier=Unnecessary non-direct parent classes qualifiers
|
||||||
fix.add.exception.to.throws=Add ''{0}''
|
fix.add.exception.to.throws=Add ''{0}''
|
||||||
|
|
||||||
|
hints.title.codevision=Code Vision
|
||||||
|
hints.title.codevision.show.hints.for=Show hints for:
|
||||||
|
hints.title.codevision.usages=Usages
|
||||||
|
hints.title.codevision.inheritors=Implementations/Inheritors/Overrides
|
||||||
|
|
||||||
|
hints.codevision.usages.format={0, choice, 1#1 Usage|2#{0,number} Usages}
|
||||||
|
hints.codevision.usages.too_many.format={0,number}+ Usages
|
||||||
|
hints.codevision.implementations.format={0, choice, 1#1 Implementation|2#{0,number} Implementations}
|
||||||
|
hints.codevision.implementations.too_many.format={0,number}+ Implementations
|
||||||
|
hints.codevision.inheritors.format={0, choice, 1#1 Inheritor|2#{0,number} Inheritors}
|
||||||
|
hints.codevision.inheritors.to_many.format={0,number}+ Inheritors
|
||||||
|
hints.codevision.overrides.format={0, choice, 1#1 Override|2#{0,number} Overrides}
|
||||||
|
hints.codevision.overrides.to_many.format={0,number}+ Overrides
|
||||||
|
hints.codevision.settings=Settings...
|
||||||
@@ -3374,6 +3374,9 @@
|
|||||||
<stripTrailingSpacesFilterFactory implementation="org.jetbrains.kotlin.idea.editor.KotlinStripTrailingSpacesFilterFactory"/>
|
<stripTrailingSpacesFilterFactory implementation="org.jetbrains.kotlin.idea.editor.KotlinStripTrailingSpacesFilterFactory"/>
|
||||||
|
|
||||||
<navbar implementation="org.jetbrains.kotlin.idea.navigationToolbar.KotlinNavBarModelExtension" order="first"/>
|
<navbar implementation="org.jetbrains.kotlin.idea.navigationToolbar.KotlinNavBarModelExtension" order="first"/>
|
||||||
|
|
||||||
|
<statistics.counterUsagesCollector groupId="kotlin.code.vision" version="1"/>
|
||||||
|
<codeInsight.inlayProvider language="kotlin" implementationClass="org.jetbrains.kotlin.idea.codeInsight.codevision.KotlinCodeVisionProvider"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
<extensions defaultExtensionNs="org.jetbrains.kotlin">
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:Suppress("UnstableApiUsage")
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.hints.ChangeListener
|
||||||
|
import com.intellij.codeInsight.hints.ImmediateConfigurable
|
||||||
|
import com.intellij.codeInsight.hints.settings.InlayHintsConfigurable
|
||||||
|
import com.intellij.internal.statistic.eventLog.FeatureUsageData
|
||||||
|
import com.intellij.internal.statistic.service.fus.collectors.FUCounterUsageLogger
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.ui.layout.panel
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
|
||||||
|
// FIX ME WHEN BUNCH 192 REMOVED
|
||||||
|
typealias CodeVisionInlayHintsConfigurable = InlayHintsConfigurable
|
||||||
|
|
||||||
|
fun logUsageStatistics(project: Project?, groupId: String, eventId: String) =
|
||||||
|
FUCounterUsageLogger.getInstance().logEvent(project, groupId, eventId)
|
||||||
|
|
||||||
|
fun logUsageStatistics(project: Project?, groupId: String, eventId: String, data: FeatureUsageData) =
|
||||||
|
FUCounterUsageLogger.getInstance().logEvent(project, groupId, eventId, data)
|
||||||
|
|
||||||
|
fun createImmediateConfigurable(settings: KotlinCodeVisionProvider.KotlinCodeVisionSettings): ImmediateConfigurable {
|
||||||
|
return object : ImmediateConfigurable {
|
||||||
|
override fun createComponent(listener: ChangeListener): JPanel {
|
||||||
|
return panel {}
|
||||||
|
}
|
||||||
|
|
||||||
|
override val cases: List<ImmediateConfigurable.Case>
|
||||||
|
get() = listOf(
|
||||||
|
ImmediateConfigurable.Case(
|
||||||
|
KotlinBundle.message("hints.title.codevision.usages"),
|
||||||
|
"usages",
|
||||||
|
settings::showUsages
|
||||||
|
),
|
||||||
|
ImmediateConfigurable.Case(
|
||||||
|
KotlinBundle.message("hints.title.codevision.inheritors"),
|
||||||
|
"inheritors",
|
||||||
|
settings::showInheritors
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
override val mainCheckboxText: String
|
||||||
|
get() = KotlinBundle.message("hints.title.codevision.show.hints.for")
|
||||||
|
}
|
||||||
|
}
|
||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.hints.ChangeListener
|
||||||
|
import com.intellij.codeInsight.hints.ImmediateConfigurable
|
||||||
|
import com.intellij.codeInsight.hints.config.InlayHintsConfigurable
|
||||||
|
import com.intellij.internal.statistic.eventLog.FeatureUsageData
|
||||||
|
import com.intellij.internal.statistic.service.fus.collectors.FUCounterUsageLogger
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.ui.components.JBCheckBox
|
||||||
|
import com.intellij.ui.layout.panel
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
|
import javax.swing.JPanel
|
||||||
|
|
||||||
|
|
||||||
|
typealias CodeVisionInlayHintsConfigurable = InlayHintsConfigurable
|
||||||
|
|
||||||
|
fun logUsageStatistics(project: Project?, groupId: String, eventId: String) {
|
||||||
|
project?.let { FUCounterUsageLogger.getInstance().logEvent(project, groupId, eventId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logUsageStatistics(project: Project?, groupId: String, eventId: String, data: FeatureUsageData) {
|
||||||
|
project?.let { FUCounterUsageLogger.getInstance().logEvent(project, groupId, eventId, data) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createImmediateConfigurable(settings: KotlinCodeVisionProvider.KotlinCodeVisionSettings): ImmediateConfigurable {
|
||||||
|
return object : ImmediateConfigurable {
|
||||||
|
val usagesText = KotlinBundle.message("hints.title.codevision.usages")
|
||||||
|
val inheritorsText = KotlinBundle.message("hints.title.codevision.inheritors")
|
||||||
|
|
||||||
|
private val usagesField = JBCheckBox(usagesText, settings.showUsages)
|
||||||
|
private val inheritorsField = JBCheckBox(inheritorsText, settings.showInheritors)
|
||||||
|
|
||||||
|
override fun createComponent(listener: ChangeListener): JPanel {
|
||||||
|
usagesField.isSelected = settings.showUsages
|
||||||
|
usagesField.addActionListener { settings.showUsages = usagesField.isSelected }
|
||||||
|
|
||||||
|
inheritorsField.isSelected = settings.showInheritors
|
||||||
|
inheritorsField.addActionListener { settings.showInheritors = inheritorsField.isSelected }
|
||||||
|
|
||||||
|
return panel {
|
||||||
|
row { usagesField(pushX) }
|
||||||
|
row { inheritorsField(pushX) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+262
@@ -0,0 +1,262 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.hints.FactoryInlayHintsCollector
|
||||||
|
import com.intellij.codeInsight.hints.InlayHintsSink
|
||||||
|
import com.intellij.codeInsight.hints.presentation.AttributesTransformerPresentation
|
||||||
|
import com.intellij.codeInsight.hints.presentation.InlayPresentation
|
||||||
|
import com.intellij.codeInsight.hints.presentation.MouseButton
|
||||||
|
import com.intellij.codeInsight.hints.presentation.PresentationFactory
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.editor.colors.EditorColors
|
||||||
|
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.roots.ProjectRootModificationTracker
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.search.searches.DirectClassInheritorsSearch
|
||||||
|
import com.intellij.psi.search.searches.MethodReferencesSearch
|
||||||
|
import com.intellij.psi.search.searches.OverridingMethodsSearch
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
|
import com.intellij.psi.util.CachedValue
|
||||||
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
|
import com.intellij.psi.util.CachedValuesManager
|
||||||
|
import com.intellij.psi.util.PsiModificationTracker
|
||||||
|
import com.intellij.util.ArrayUtil
|
||||||
|
import com.intellij.util.Processor
|
||||||
|
import org.jetbrains.kotlin.asJava.LightClassUtil
|
||||||
|
import org.jetbrains.kotlin.asJava.toLightClass
|
||||||
|
import org.jetbrains.kotlin.idea.refactoring.isAbstract
|
||||||
|
import org.jetbrains.kotlin.idea.search.declarationsSearch.toPossiblyFakeLightMethods
|
||||||
|
import org.jetbrains.kotlin.idea.search.ideaExtensions.KotlinDefinitionsSearcher
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
|
import org.jetbrains.kotlin.psi.KtFunction
|
||||||
|
import org.jetbrains.kotlin.psi.KtFunctionLiteral
|
||||||
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
|
|
||||||
|
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
class KotlinCodeVisionHintsCollector(
|
||||||
|
editor: Editor, private val showUsages: Boolean, private val showInheritors: Boolean,
|
||||||
|
private val usagesLimit: Int, private val inheritorsLimit: Int
|
||||||
|
) : FactoryInlayHintsCollector(editor) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val hintsKey = Key.create<CachedValue<CachedHints>>("CodeVisionHints")
|
||||||
|
|
||||||
|
|
||||||
|
fun searchUsages(element: PsiElement, usagesLimit: Int): Usages? {
|
||||||
|
val countingProcessor = CountingUpToLimitProcessor<Any>(usagesLimit)
|
||||||
|
if (element is KtClass) {
|
||||||
|
ReferencesSearch.search(element).forEach(countingProcessor)
|
||||||
|
} else {
|
||||||
|
element.toPossiblyFakeLightMethods().firstOrNull() // inline methods don't have light analogues (fake needed)
|
||||||
|
?.let { MethodReferencesSearch.search(it).forEach(countingProcessor) }
|
||||||
|
}
|
||||||
|
|
||||||
|
val (usagesNum, limitReached) = countingProcessor
|
||||||
|
return if (usagesNum > 0) Usages(usagesNum, limitReached) else null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchFunctionOverrides(function: KtFunction, inheritorsLimit: Int): KotlinCodeVisionLimitedHint? {
|
||||||
|
return LightClassUtil.getLightClassMethod(function)?.let { it ->
|
||||||
|
val countingProcessor = CountingUpToLimitProcessor<Any>(inheritorsLimit)
|
||||||
|
OverridingMethodsSearch.search(it, true).forEach(countingProcessor)
|
||||||
|
val (overridingNum, limitReached) = countingProcessor
|
||||||
|
|
||||||
|
if (overridingNum > 0) {
|
||||||
|
if (function.isAbstract()) FunctionImplementations(overridingNum, limitReached)
|
||||||
|
else FunctionOverrides(overridingNum, limitReached)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchClassInheritors(clazz: KtClass, inheritorsLimit: Int): KotlinCodeVisionLimitedHint? {
|
||||||
|
return clazz.toLightClass()?.let {
|
||||||
|
val countingProcessor = CountingUpToLimitProcessor<Any>(inheritorsLimit)
|
||||||
|
DirectClassInheritorsSearch.search(it, clazz.useScope, true).forEach(countingProcessor)
|
||||||
|
val (inheritorsNum, limitReached) = countingProcessor
|
||||||
|
if (inheritorsNum > 0) {
|
||||||
|
if (clazz.isInterface()) InterfaceImplementations(inheritorsNum, limitReached)
|
||||||
|
else ClassInheritors(inheritorsNum, limitReached)
|
||||||
|
} else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun searchPropertyOverriding(property: KtProperty, inheritorsLimit: Int): KotlinCodeVisionLimitedHint? {
|
||||||
|
val countingProcessor = CountingUpToLimitProcessor<PsiElement>(inheritorsLimit)
|
||||||
|
KotlinDefinitionsSearcher.processPropertyImplementationsMethods(
|
||||||
|
property.toPossiblyFakeLightMethods(),
|
||||||
|
GlobalSearchScope.allScope(property.project),
|
||||||
|
countingProcessor
|
||||||
|
)
|
||||||
|
val (overridesNum, limitReached) = countingProcessor
|
||||||
|
return if (overridesNum > 0) PropertyOverrides(overridesNum, limitReached) else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun collect(element: PsiElement, editor: Editor, sink: InlayHintsSink): Boolean {
|
||||||
|
if (!showUsages && !showInheritors) return false
|
||||||
|
if (!isElementOfInterest(element)) return true
|
||||||
|
if (editor.project == null) return false
|
||||||
|
|
||||||
|
val hints = retrieveHints(editor.project!!, element)
|
||||||
|
val inlayPresentations =
|
||||||
|
hints.takeIf { it.isNotEmpty() }?.let { listOf(prepareBlockElements(element, editor, hints)) } ?: emptyList()
|
||||||
|
|
||||||
|
inlayPresentations.forEach { hintPair ->
|
||||||
|
sink.addBlockElement(
|
||||||
|
hintPair.first,
|
||||||
|
relatesToPrecedingText = true,
|
||||||
|
showAbove = true,
|
||||||
|
priority = 0,
|
||||||
|
presentation = hintPair.second
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun retrieveHints(project: Project, element: PsiElement): List<KotlinCodeVisionLimitedHint> {
|
||||||
|
val cachedValueProvider = CachedHintsProvider(
|
||||||
|
element, showUsages, showInheritors, usagesLimit, inheritorsLimit
|
||||||
|
)
|
||||||
|
|
||||||
|
return CachedValuesManager.getManager(project).run {
|
||||||
|
this.getCachedValue(element, hintsKey, cachedValueProvider, false).takeUnless { isStale(it) } ?: run {
|
||||||
|
this.createCachedValue(cachedValueProvider).also { element.putUserData(hintsKey, it) }.value
|
||||||
|
}
|
||||||
|
}.hints
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isStale(cachedValue: CachedHints): Boolean {
|
||||||
|
return cachedValue.showUsages != showUsages || cachedValue.showInheritors != showInheritors
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("GrazieInspection")
|
||||||
|
private fun prepareBlockElements(
|
||||||
|
element: PsiElement,
|
||||||
|
editor: Editor,
|
||||||
|
hints: List<KotlinCodeVisionHint>
|
||||||
|
): Pair<Int, InlayPresentation> {
|
||||||
|
|
||||||
|
assert(hints.isNotEmpty()) { "Attempt to build block elements whereas hints don't exist" }
|
||||||
|
assert(hints.size <= 2) { "Hints other than usages-implementations are not expected" }
|
||||||
|
|
||||||
|
val offset = element.textRange.startOffset
|
||||||
|
val line = editor.document.getLineNumber(offset)
|
||||||
|
val lineStart = editor.document.getLineStartOffset(line)
|
||||||
|
val indent = offset - lineStart
|
||||||
|
|
||||||
|
/*
|
||||||
|
* presentations: <indent>[<Usages>][<space><Inheritors>]
|
||||||
|
* hints: hint[0] hint[1]
|
||||||
|
*/
|
||||||
|
val presentations = arrayOfNulls<InlayPresentation>(hints.size * 2) // 2 or 4
|
||||||
|
presentations[0] = factory.text(StringUtil.repeat(" ", indent))
|
||||||
|
var pInd = 1
|
||||||
|
for (hInd in hints.indices) { // handling usages & inheritors
|
||||||
|
val hint: KotlinCodeVisionHint = hints[hInd]
|
||||||
|
if (hInd != 0)
|
||||||
|
presentations[pInd++] = factory.text(" ")
|
||||||
|
|
||||||
|
presentations[pInd++] = createPresentation(factory, element, editor, hint) // either Usages or Inheritors
|
||||||
|
}
|
||||||
|
|
||||||
|
val filledPresentations = presentations.requireNoNulls()
|
||||||
|
|
||||||
|
val seq = factory.seq(*filledPresentations)
|
||||||
|
val withAppearingSettings = factory.changeOnHover(seq, {
|
||||||
|
val spaceAndSettings = arrayOf(factory.text(" "), createSettings(factory, element, editor))
|
||||||
|
val withSettings = ArrayUtil.mergeArrays(filledPresentations, spaceAndSettings)
|
||||||
|
factory.seq(*withSettings)
|
||||||
|
}) { true }
|
||||||
|
|
||||||
|
return Pair(lineStart, withAppearingSettings)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isElementOfInterest(element: PsiElement): Boolean =
|
||||||
|
element is KtClass
|
||||||
|
|| (element is KtFunction && element !is KtFunctionLiteral)
|
||||||
|
|| (element is KtProperty && !element.isLocal)
|
||||||
|
|
||||||
|
private fun createPresentation(
|
||||||
|
factory: PresentationFactory, element: PsiElement, editor: Editor, result: KotlinCodeVisionHint
|
||||||
|
): InlayPresentation {
|
||||||
|
val text = factory.smallText(result.regularText)
|
||||||
|
return factory.changeOnHover(text, {
|
||||||
|
val onClick = factory.onClick(text, MouseButton.Left)
|
||||||
|
{ event, _ -> result.onClick(editor, element, event) }
|
||||||
|
applyReferenceColor(onClick)
|
||||||
|
}) { true }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyReferenceColor(presentation: InlayPresentation): InlayPresentation {
|
||||||
|
return AttributesTransformerPresentation(presentation) {
|
||||||
|
val attributes = EditorColorsManager.getInstance()
|
||||||
|
.globalScheme.getAttributes(EditorColors.REFERENCE_HYPERLINK_COLOR).clone()
|
||||||
|
attributes.apply { effectType = null }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSettings(factory: PresentationFactory, element: PsiElement, editor: Editor): InlayPresentation {
|
||||||
|
return createPresentation(factory, element, editor, SettingsHint())
|
||||||
|
}
|
||||||
|
|
||||||
|
class CachedHintsProvider(
|
||||||
|
private val element: PsiElement,
|
||||||
|
private val showUsages: Boolean,
|
||||||
|
private val showInheritors: Boolean,
|
||||||
|
private val usagesLimit: Int,
|
||||||
|
private val inheritorsLimit: Int
|
||||||
|
) : CachedValueProvider<CachedHints> {
|
||||||
|
|
||||||
|
override fun compute(): CachedValueProvider.Result<CachedHints>? {
|
||||||
|
val hints: MutableList<KotlinCodeVisionLimitedHint> = SmartList()
|
||||||
|
|
||||||
|
if (showUsages)
|
||||||
|
searchUsages(element, usagesLimit)?.let { hints += it }
|
||||||
|
|
||||||
|
if (showInheritors) {
|
||||||
|
when (element) {
|
||||||
|
is KtFunction -> searchFunctionOverrides(element, inheritorsLimit)?.let { hints += it }
|
||||||
|
is KtClass -> searchClassInheritors(element, inheritorsLimit)?.let { hints += it }
|
||||||
|
is KtProperty -> searchPropertyOverriding(element, inheritorsLimit)?.let { hints += it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return CachedValueProvider.Result(
|
||||||
|
CachedHints(hints, this.showUsages, this.showInheritors),
|
||||||
|
// kotlinOutOfCodeBlockTracker doesn't work for usages: new reference inside the block is not detected
|
||||||
|
// MODIFICATION_COUNT {+: file reopening -> cache hit, -: any code modification resets all caches (records are in PSI)}
|
||||||
|
PsiModificationTracker.MODIFICATION_COUNT,
|
||||||
|
ProjectRootModificationTracker.getInstance(element.project) // tracking project structure changes
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CountingUpToLimitProcessor<T>(private val limit: Int) : Processor<T> {
|
||||||
|
private val findings = mutableSetOf<T>() // for properties it's crucial not to calculate setters and getters together
|
||||||
|
|
||||||
|
override fun process(t: T): Boolean {
|
||||||
|
findings.add(t)
|
||||||
|
return findings.size < limit
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun component1(): Int = findings.size
|
||||||
|
operator fun component2(): Boolean = findings.size >= limit
|
||||||
|
}
|
||||||
|
|
||||||
|
data class CachedHints(
|
||||||
|
val hints: List<KotlinCodeVisionLimitedHint>,
|
||||||
|
val showUsages: Boolean,
|
||||||
|
val showInheritors: Boolean
|
||||||
|
)
|
||||||
|
}
|
||||||
+131
@@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
||||||
|
import com.intellij.internal.statistic.eventLog.FeatureUsageData
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
|
import org.jetbrains.kotlin.idea.highlighter.markers.OVERRIDDEN_FUNCTION
|
||||||
|
import org.jetbrains.kotlin.idea.highlighter.markers.OVERRIDDEN_PROPERTY
|
||||||
|
import org.jetbrains.kotlin.idea.highlighter.markers.SUBCLASSED_CLASS
|
||||||
|
import org.jetbrains.kotlin.psi.KtClass
|
||||||
|
import org.jetbrains.kotlin.psi.KtFunction
|
||||||
|
import org.jetbrains.kotlin.psi.KtProperty
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
|
||||||
|
abstract class KotlinCodeVisionHint(hintKey: String) {
|
||||||
|
open val regularText: String = KotlinBundle.message(hintKey)
|
||||||
|
|
||||||
|
abstract fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?)
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class KotlinCodeVisionLimitedHint(num: Int, limitReached: Boolean, regularHintKey: String, tooManyHintKey: String) :
|
||||||
|
KotlinCodeVisionHint(regularHintKey) {
|
||||||
|
|
||||||
|
override val regularText: String =
|
||||||
|
if (limitReached) KotlinBundle.message(tooManyHintKey, num) else KotlinBundle.message(regularHintKey, num)
|
||||||
|
}
|
||||||
|
|
||||||
|
private const val IMPLEMENTATIONS_KEY = "hints.codevision.implementations.format"
|
||||||
|
private const val IMPLEMENTATIONS_TO_MANY_KEY = "hints.codevision.implementations.too_many.format"
|
||||||
|
|
||||||
|
private const val INHERITORS_KEY = "hints.codevision.inheritors.format"
|
||||||
|
private const val INHERITORS_TO_MANY_KEY = "hints.codevision.inheritors.to_many.format"
|
||||||
|
|
||||||
|
private const val OVERRIDES_KEY = "hints.codevision.overrides.format"
|
||||||
|
private const val OVERRIDES_TOO_MANY_KEY = "hints.codevision.overrides.to_many.format"
|
||||||
|
|
||||||
|
private const val USAGES_KEY = "hints.codevision.usages.format"
|
||||||
|
private const val USAGES_TOO_MANY_KEY = "hints.codevision.usages.too_many.format"
|
||||||
|
|
||||||
|
private const val SETTINGS_FORMAT = "hints.codevision.settings"
|
||||||
|
|
||||||
|
// FUS = Feature Usage Statistics
|
||||||
|
const val FUS_GROUP_ID = "kotlin.code.vision"
|
||||||
|
const val USAGES_CLICKED_EVENT_ID = "usages.clicked"
|
||||||
|
const val INHERITORS_CLICKED_EVENT_ID = "inheritors.clicked"
|
||||||
|
const val SETTING_CLICKED_EVENT_ID = "setting.clicked"
|
||||||
|
|
||||||
|
// FUD = Feature Usage Data
|
||||||
|
const val FUD_KEY = "location"
|
||||||
|
const val FUD_FUNCTION = "function"
|
||||||
|
const val FUD_PROPERTY = "property"
|
||||||
|
const val FUD_CLASS = "class"
|
||||||
|
const val FUD_INTERFACE = "interface"
|
||||||
|
|
||||||
|
class Usages(usagesNum: Int, limitReached: Boolean) :
|
||||||
|
KotlinCodeVisionLimitedHint(usagesNum, limitReached, USAGES_KEY, USAGES_TOO_MANY_KEY) {
|
||||||
|
|
||||||
|
override fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?) {
|
||||||
|
logUsageStatistics(editor.project, FUS_GROUP_ID, USAGES_CLICKED_EVENT_ID)
|
||||||
|
GotoDeclarationAction.startFindUsages(editor, editor.project!!, element)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionOverrides(overridesNum: Int, limitReached: Boolean) :
|
||||||
|
KotlinCodeVisionLimitedHint(overridesNum, limitReached, OVERRIDES_KEY, OVERRIDES_TOO_MANY_KEY) {
|
||||||
|
|
||||||
|
override fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?) {
|
||||||
|
val data = FeatureUsageData().addData(FUD_KEY, FUD_FUNCTION)
|
||||||
|
logUsageStatistics(editor.project, FUS_GROUP_ID, INHERITORS_CLICKED_EVENT_ID, data)
|
||||||
|
val navigationHandler = OVERRIDDEN_FUNCTION.navigationHandler
|
||||||
|
navigationHandler.navigate(event, (element as KtFunction).nameIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FunctionImplementations(implNum: Int, limitReached: Boolean) :
|
||||||
|
KotlinCodeVisionLimitedHint(implNum, limitReached, IMPLEMENTATIONS_KEY, IMPLEMENTATIONS_TO_MANY_KEY) {
|
||||||
|
|
||||||
|
override fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?) {
|
||||||
|
val data = FeatureUsageData().addData(FUD_KEY, FUD_FUNCTION)
|
||||||
|
logUsageStatistics(editor.project, FUS_GROUP_ID, INHERITORS_CLICKED_EVENT_ID, data)
|
||||||
|
val navigationHandler = OVERRIDDEN_FUNCTION.navigationHandler
|
||||||
|
navigationHandler.navigate(event, (element as KtFunction).nameIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PropertyOverrides(overridesNum: Int, limitReached: Boolean) :
|
||||||
|
KotlinCodeVisionLimitedHint(overridesNum, limitReached, OVERRIDES_KEY, OVERRIDES_TOO_MANY_KEY) {
|
||||||
|
|
||||||
|
override fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?) {
|
||||||
|
val data = FeatureUsageData().addData(FUD_KEY, FUD_PROPERTY)
|
||||||
|
logUsageStatistics(editor.project, FUS_GROUP_ID, INHERITORS_CLICKED_EVENT_ID, data)
|
||||||
|
val navigationHandler = OVERRIDDEN_PROPERTY.navigationHandler
|
||||||
|
navigationHandler.navigate(event, (element as KtProperty).nameIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ClassInheritors(inheritorsNum: Int, limitReached: Boolean) :
|
||||||
|
KotlinCodeVisionLimitedHint(inheritorsNum, limitReached, INHERITORS_KEY, INHERITORS_TO_MANY_KEY) {
|
||||||
|
|
||||||
|
override fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?) {
|
||||||
|
val data = FeatureUsageData().addData(FUD_KEY, FUD_CLASS)
|
||||||
|
logUsageStatistics(editor.project, FUS_GROUP_ID, INHERITORS_CLICKED_EVENT_ID, data)
|
||||||
|
val navigationHandler = SUBCLASSED_CLASS.navigationHandler
|
||||||
|
navigationHandler.navigate(event, (element as KtClass).nameIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class InterfaceImplementations(implNum: Int, limitReached: Boolean) :
|
||||||
|
KotlinCodeVisionLimitedHint(implNum, limitReached, IMPLEMENTATIONS_KEY, IMPLEMENTATIONS_TO_MANY_KEY) {
|
||||||
|
|
||||||
|
override fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?) {
|
||||||
|
val data = FeatureUsageData().addData(FUD_KEY, FUD_INTERFACE)
|
||||||
|
logUsageStatistics(editor.project, FUS_GROUP_ID, INHERITORS_CLICKED_EVENT_ID, data)
|
||||||
|
val navigationHandler = SUBCLASSED_CLASS.navigationHandler
|
||||||
|
navigationHandler.navigate(event, (element as KtClass).nameIdentifier)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SettingsHint : KotlinCodeVisionHint(SETTINGS_FORMAT) {
|
||||||
|
override fun onClick(editor: Editor, element: PsiElement, event: MouseEvent?) {
|
||||||
|
val project = element.project
|
||||||
|
logUsageStatistics(project, FUS_GROUP_ID, SETTING_CLICKED_EVENT_ID)
|
||||||
|
CodeVisionInlayHintsConfigurable.showSettingsDialogForLanguage(project, element.language)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.hints.*
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinBundle
|
||||||
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
|
|
||||||
|
@Suppress("UnstableApiUsage")
|
||||||
|
class KotlinCodeVisionProvider : InlayHintsProvider<KotlinCodeVisionProvider.KotlinCodeVisionSettings> {
|
||||||
|
|
||||||
|
override val key: SettingsKey<KotlinCodeVisionSettings> = SettingsKey("CodeVision")
|
||||||
|
override val name: String = KotlinBundle.message("hints.title.codevision")
|
||||||
|
override val previewText: String? = null
|
||||||
|
|
||||||
|
var usagesLimit: Int = 100
|
||||||
|
var inheritorsLimit: Int = 100
|
||||||
|
|
||||||
|
override fun isLanguageSupported(language: Language): Boolean = language is KotlinLanguage
|
||||||
|
|
||||||
|
override fun createConfigurable(settings: KotlinCodeVisionSettings): ImmediateConfigurable = createImmediateConfigurable(settings)
|
||||||
|
|
||||||
|
override fun createSettings(): KotlinCodeVisionSettings = KotlinCodeVisionSettings()
|
||||||
|
|
||||||
|
override fun getCollectorFor(
|
||||||
|
file: PsiFile, editor: Editor, settings: KotlinCodeVisionSettings, sink: InlayHintsSink
|
||||||
|
): InlayHintsCollector? {
|
||||||
|
if (!settings.showUsages && !settings.showInheritors) return null
|
||||||
|
return KotlinCodeVisionHintsCollector(editor, settings.showUsages, settings.showInheritors, usagesLimit, inheritorsLimit)
|
||||||
|
}
|
||||||
|
|
||||||
|
data class KotlinCodeVisionSettings(var showUsages: Boolean = false, var showInheritors: Boolean = false)
|
||||||
|
}
|
||||||
@@ -144,7 +144,7 @@ interface TestableLineMarkerNavigator {
|
|||||||
fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor?
|
fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor?
|
||||||
}
|
}
|
||||||
|
|
||||||
private val SUBCLASSED_CLASS = MarkerType(
|
val SUBCLASSED_CLASS = MarkerType(
|
||||||
"SUBCLASSED_CLASS",
|
"SUBCLASSED_CLASS",
|
||||||
{ getPsiClass(it)?.let(::getSubclassedClassTooltip) },
|
{ getPsiClass(it)?.let(::getSubclassedClassTooltip) },
|
||||||
object : LineMarkerNavigator() {
|
object : LineMarkerNavigator() {
|
||||||
@@ -155,7 +155,7 @@ private val SUBCLASSED_CLASS = MarkerType(
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
private val OVERRIDDEN_FUNCTION = object : MarkerType(
|
val OVERRIDDEN_FUNCTION = object : MarkerType(
|
||||||
"OVERRIDDEN_FUNCTION",
|
"OVERRIDDEN_FUNCTION",
|
||||||
{ getPsiMethod(it)?.let(::getOverriddenMethodTooltip) },
|
{ getPsiMethod(it)?.let(::getOverriddenMethodTooltip) },
|
||||||
object : LineMarkerNavigator() {
|
object : LineMarkerNavigator() {
|
||||||
@@ -176,7 +176,7 @@ private val OVERRIDDEN_FUNCTION = object : MarkerType(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val OVERRIDDEN_PROPERTY = object : MarkerType(
|
val OVERRIDDEN_PROPERTY = object : MarkerType(
|
||||||
"OVERRIDDEN_PROPERTY",
|
"OVERRIDDEN_PROPERTY",
|
||||||
{ it?.let { getOverriddenPropertyTooltip(it.parent as KtNamedDeclaration) } },
|
{ it?.let { getOverriddenPropertyTooltip(it.parent as KtNamedDeclaration) } },
|
||||||
object : LineMarkerNavigator() {
|
object : LineMarkerNavigator() {
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// MODE: inheritors
|
||||||
|
|
||||||
|
<# block [ 2 Inheritors] #>
|
||||||
|
abstract class SomeClass {
|
||||||
|
<# block [ 1 Override] #>
|
||||||
|
open fun someFun() = ""
|
||||||
|
<# block [ 2 Implementations] #>
|
||||||
|
abstract fun someAbstractFun()
|
||||||
|
}
|
||||||
|
|
||||||
|
class DerivedClassA : SomeClass {
|
||||||
|
override fun someFun() = "overridden"
|
||||||
|
override fun someAbstractFun() = "overridden"
|
||||||
|
}
|
||||||
|
class DerivedClassB : SomeClass {
|
||||||
|
override fun someAbstractFun() = "overridden"
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// MODE: inheritors
|
||||||
|
|
||||||
|
<# block [ 4 Inheritors] #>
|
||||||
|
open class SomeClass {
|
||||||
|
class NestedDerivedClass: SomeClass() {} // <== (1): nested class
|
||||||
|
}
|
||||||
|
<# block [ 1 Inheritor] #>
|
||||||
|
open class DerivedClass : SomeClass {} // <== (2): direct derived one
|
||||||
|
class AnotherDerivedClass : SomeClass {} // <== (3): yet another derived one
|
||||||
|
class DerivedDerivedClass : DerivedClass { // <== (): indirect inheritor of SomeClass
|
||||||
|
fun main() {
|
||||||
|
val someClassInstance = object : SomeClass() { // <== (4): anonymous derived one
|
||||||
|
val somethingHere = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// MODE: usages
|
||||||
|
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
abstract class SomeClass {
|
||||||
|
<# block [ 3 Usages] #>
|
||||||
|
abstract fun someFun(): String
|
||||||
|
fun someOtherFun() = someFun() // <== (1): delegation from another method
|
||||||
|
val someProperty = someFun() // <== (2): property initializer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val instance = object: SomeClass {
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
override fun someFun(): String {} // <== (): used below
|
||||||
|
}
|
||||||
|
instance.someFun() <== (3): call on an instance
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
// MODE: inheritors
|
||||||
|
|
||||||
|
<# block [ 1 Inheritor] #>
|
||||||
|
abstract class SomeClass {
|
||||||
|
<# block [ 1 Override] #>
|
||||||
|
abstract val someAbstractProperty: Int
|
||||||
|
<# block [ 2 Overrides] #>
|
||||||
|
open val nonAbstractProperty: Int = 10
|
||||||
|
open val notToBeOverriddenProperty: Int = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
<# block [ 1 Inheritor] #>
|
||||||
|
open class DerivedClassA : SomeClass() {
|
||||||
|
override val someAbstractProperty: Int = 5
|
||||||
|
<# block [ 1 Override] #>
|
||||||
|
override val nonAbstractProperty: Int = 15 // NOTE that DerivedClassB overrides both getter and setter but counted once
|
||||||
|
}
|
||||||
|
|
||||||
|
class DerivedClassB : DerivedClassA() {
|
||||||
|
override var nonAbstractProperty: Int = 15
|
||||||
|
get() = 20
|
||||||
|
set(value) {field = value / 2}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
// MODE: usages
|
||||||
|
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
interface SomeClass {
|
||||||
|
<# block [ 3 Usages] #>
|
||||||
|
var someProperty = "initialized"
|
||||||
|
fun someFun() = "it's " + someProperty // <== (1): reference from expression
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val instance = object: SomeClass {}
|
||||||
|
val someString = instance.someProperty // <== (2): getter call
|
||||||
|
instance.someProperty = "anotherValue" // <== (3): setter call
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// MODE: usages
|
||||||
|
|
||||||
|
<# block [ 5 Usages] #>
|
||||||
|
open class SomeClass {}
|
||||||
|
class SomeOtherClass : SomeClass {} // <== (1): class extension
|
||||||
|
class SomeYetOtherClass : SomeClass { // <== (2): class extension
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
fun acceptsClass(param: SomeClass) {} // <== (3): parameter type
|
||||||
|
fun returnsInterface(): SomeClass {} // <== (4): return type
|
||||||
|
fun main() = acceptsClass(object : SomeClass {}) // <== (5): anonymous class instance
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// MODE: usages
|
||||||
|
|
||||||
|
<# block [ 3 Usages] #>
|
||||||
|
fun function(param: String): Int = 1
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
fun higherOrderFun(s: String, param: (String) -> Int) = param(s)
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
function("someString")
|
||||||
|
val functionRef = ::function
|
||||||
|
higherOrderFun("someString", ::function)
|
||||||
|
}
|
||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
// MODE: inheritors
|
||||||
|
|
||||||
|
<# block [ 1 Implementation] #>
|
||||||
|
interface SomeInterface {
|
||||||
|
<# block [ 2 Implementations] #>
|
||||||
|
fun interfaceMethodA()
|
||||||
|
}
|
||||||
|
<# block [ 1 Inheritor] #>
|
||||||
|
open class SomeClass : SomeInterface {
|
||||||
|
<# block [ 1 Override] #>
|
||||||
|
override fun interfaceMethodA() {} // <== (1)
|
||||||
|
}
|
||||||
|
|
||||||
|
class SomeDerivedClass : SomeClass() {
|
||||||
|
override fun interfaceMethodA() {} // <== (2)
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
// MODE: inheritors
|
||||||
|
|
||||||
|
<# block [ 3 Implementations] #>
|
||||||
|
interface SomeInterface {}
|
||||||
|
interface SomeOtherInterface : SomeInterface {} // <== (1): interface extension
|
||||||
|
class SomeClass : SomeInterface { // <== (2): interface implementation
|
||||||
|
fun acceptsInterface(param: SomeInterface) {}
|
||||||
|
fun main() = acceptsInterface(object : SomeInterface {}) // <== (3): anonymous class instance
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
// MODE: usages
|
||||||
|
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
interface SomeInterface {
|
||||||
|
<# block [ 3 Usages] #>
|
||||||
|
fun someFun(): String
|
||||||
|
fun someOtherFun() = someFun() // <== (1): delegation from another interface method
|
||||||
|
val someProperty = someFun() // <== (2): property initializer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val instance = object: SomeInterface {
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
override fun someFun(): String {} // <== (): used below
|
||||||
|
}
|
||||||
|
instance.someFun() <== (3): call on an instance
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// MODE: inheritors
|
||||||
|
|
||||||
|
<# block [ 1 Implementation] #>
|
||||||
|
interface SomeInterface {
|
||||||
|
<# block [ 1 Override] #>
|
||||||
|
fun interfaceMethodA() = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
class SomeClass : SomeInterface {
|
||||||
|
override fun interfaceMethodA() = 20 // <== (1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// MODE: inheritors
|
||||||
|
|
||||||
|
<# block [ 1 Implementation] #>
|
||||||
|
interface SomeInterface {
|
||||||
|
<# block [ 1 Override] #>
|
||||||
|
open val interfaceProperty: String
|
||||||
|
}
|
||||||
|
|
||||||
|
class SomeClass : SomeInterface {
|
||||||
|
override val interfaceProperty: String = "overridden" // <== (1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
// MODE: usages
|
||||||
|
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
interface SomeInterface {
|
||||||
|
<# block [ 2 Usages] #>
|
||||||
|
val someProperty = "initialized"
|
||||||
|
fun someFun() = "it's " + someProperty // <== (1):
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main() {
|
||||||
|
val instance = object: SomeInterface {}
|
||||||
|
val someString = instance.someProperty // <== (2): call on an instance
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
// MODE: usages
|
||||||
|
|
||||||
|
<# block [ 5 Usages] #>
|
||||||
|
interface SomeInterface {}
|
||||||
|
interface SomeOtherInterface : SomeInterface {} // <== (1): interface extension
|
||||||
|
class SomeClass : SomeInterface { // <== (2): interface implementation
|
||||||
|
<# block [ 1 Usage] #>
|
||||||
|
fun acceptsInterface(param: SomeInterface) {} // <== (3): parameter type
|
||||||
|
fun returnsInterface(): SomeInterface {} // <== (4): return type
|
||||||
|
fun main() = acceptsInterface(object : SomeInterface {}) // <== (5): anonymous class instance
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
// MODE: usages-&-inheritors
|
||||||
|
// USAGES-LIMIT: 3
|
||||||
|
// INHERITORS-LIMIT: 2
|
||||||
|
|
||||||
|
<# block [ 3+ Usages 2+ Inheritors] #>
|
||||||
|
open class SomeClass {
|
||||||
|
class NestedDerivedClass: SomeClass() {} // <== (1): nested class
|
||||||
|
}
|
||||||
|
<# block [ 1 Usage 1 Inheritor] #>
|
||||||
|
open class DerivedClass : SomeClass {} // <== (2): direct derived one
|
||||||
|
class AnotherDerivedClass : SomeClass {} // <== (3): yet another derived one
|
||||||
|
class DerivedDerivedClass : DerivedClass { // <== (): indirect inheritor of SomeClass
|
||||||
|
fun main() {
|
||||||
|
val someClassInstance = object : SomeClass() // { <== (4): anonymous derived one
|
||||||
|
val somethingHere = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// MODE: usages-&-inheritors
|
||||||
|
|
||||||
|
<# block [ 4 Usages 4 Inheritors] #>
|
||||||
|
open class SomeClass {
|
||||||
|
class NestedDerivedClass: SomeClass() {} // <== (1): nested class
|
||||||
|
}
|
||||||
|
<# block [ 1 Usage 1 Inheritor] #>
|
||||||
|
open class DerivedClass : SomeClass {} // <== (2): direct derived one
|
||||||
|
class AnotherDerivedClass : SomeClass {} // <== (3): yet another derived one
|
||||||
|
class DerivedDerivedClass : DerivedClass { // <== (): indirect inheritor of SomeClass
|
||||||
|
fun main() {
|
||||||
|
val someClassInstance = object : SomeClass() { // <== (4): anonymous derived one
|
||||||
|
val somethingHere = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+47
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.io.FileUtil
|
||||||
|
import com.intellij.testFramework.utils.inlays.InlayHintsProviderTestCase
|
||||||
|
import org.jetbrains.kotlin.idea.codeInsight.codevision.KotlinCodeVisionProvider.KotlinCodeVisionSettings
|
||||||
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
open class AbstractKotlinCodeVisionProviderTest : InlayHintsProviderTestCase() { // Abstract- prefix is just a convention for GenerateTests
|
||||||
|
|
||||||
|
fun doTest(testPath: String) { // named according to the convention imposed by GenerateTests
|
||||||
|
assertThatActualHintsMatch(testPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun assertThatActualHintsMatch(fileName: String) {
|
||||||
|
val fileContents = FileUtil.loadFile(File(fileName), true)
|
||||||
|
|
||||||
|
val usagesLimit = InTextDirectivesUtils.findStringWithPrefixes(fileContents, "// USAGES-LIMIT: ")?.toInt() ?: 100
|
||||||
|
val inheritorsLimit = InTextDirectivesUtils.findStringWithPrefixes(fileContents, "// INHERITORS-LIMIT: ")?.toInt() ?: 100
|
||||||
|
|
||||||
|
val codeVisionProvider = KotlinCodeVisionProvider()
|
||||||
|
codeVisionProvider.usagesLimit = usagesLimit
|
||||||
|
codeVisionProvider.inheritorsLimit = inheritorsLimit
|
||||||
|
|
||||||
|
val mode: KotlinCodeVisionSettings = when (InTextDirectivesUtils.findStringWithPrefixes(fileContents, "// MODE: ")) {
|
||||||
|
"inheritors" -> inheritorsEnabled()
|
||||||
|
"usages" -> usagesEnabled()
|
||||||
|
"usages-&-inheritors" -> usagesAndInheritorsEnabled()
|
||||||
|
else -> codeVisionDisabled()
|
||||||
|
}
|
||||||
|
|
||||||
|
testProvider("kotlinCodeVision.kt", fileContents, codeVisionProvider, mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun usagesAndInheritorsEnabled(): KotlinCodeVisionSettings = KotlinCodeVisionSettings(showUsages = true, showInheritors = true)
|
||||||
|
|
||||||
|
private fun inheritorsEnabled(): KotlinCodeVisionSettings = KotlinCodeVisionSettings(showUsages = false, showInheritors = true)
|
||||||
|
|
||||||
|
private fun usagesEnabled(): KotlinCodeVisionSettings = KotlinCodeVisionSettings(showUsages = true, showInheritors = false)
|
||||||
|
|
||||||
|
private fun codeVisionDisabled(): KotlinCodeVisionSettings = KotlinCodeVisionSettings(showUsages = false, showInheritors = false)
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision;
|
||||||
|
|
||||||
|
import com.intellij.testFramework.TestDataPath;
|
||||||
|
import org.jetbrains.kotlin.test.JUnit3RunnerWithInners;
|
||||||
|
import org.jetbrains.kotlin.test.KotlinTestUtils;
|
||||||
|
import org.jetbrains.kotlin.test.TestMetadata;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */
|
||||||
|
@SuppressWarnings("all")
|
||||||
|
@TestMetadata("idea/testData/codeInsight/codeVision")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public class KotlinCodeVisionProviderTestGenerated extends AbstractKotlinCodeVisionProviderTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInCodeVision() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/codeInsight/codeVision"), Pattern.compile("^(.+)\\.kt$"), null, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ClassFunctionOverrides.kt")
|
||||||
|
public void testClassFunctionOverrides() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/ClassFunctionOverrides.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ClassInheritors.kt")
|
||||||
|
public void testClassInheritors() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/ClassInheritors.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ClassMethodUsages.kt")
|
||||||
|
public void testClassMethodUsages() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/ClassMethodUsages.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ClassPropertiesOverrides.kt")
|
||||||
|
public void testClassPropertiesOverrides() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/ClassPropertiesOverrides.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ClassPropertyUsages.kt")
|
||||||
|
public void testClassPropertyUsages() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/ClassPropertyUsages.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("ClassUsages.kt")
|
||||||
|
public void testClassUsages() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/ClassUsages.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("GlobalFunctionUsages.kt")
|
||||||
|
public void testGlobalFunctionUsages() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/GlobalFunctionUsages.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InterfaceAbstractMethodImplementation.kt")
|
||||||
|
public void testInterfaceAbstractMethodImplementation() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/InterfaceAbstractMethodImplementation.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InterfaceImplementations.kt")
|
||||||
|
public void testInterfaceImplementations() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/InterfaceImplementations.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InterfaceMethodUsages.kt")
|
||||||
|
public void testInterfaceMethodUsages() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/InterfaceMethodUsages.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InterfaceMethodsOverrides.kt")
|
||||||
|
public void testInterfaceMethodsOverrides() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/InterfaceMethodsOverrides.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InterfacePropertiesOverrides.kt")
|
||||||
|
public void testInterfacePropertiesOverrides() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/InterfacePropertiesOverrides.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InterfacePropertyUsages.kt")
|
||||||
|
public void testInterfacePropertyUsages() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/InterfacePropertyUsages.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("InterfaceUsages.kt")
|
||||||
|
public void testInterfaceUsages() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/InterfaceUsages.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("TooManyUsagesAndInheritors.kt")
|
||||||
|
public void testTooManyUsagesAndInheritors() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/TooManyUsagesAndInheritors.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("UsagesAndInheritanceTogether.kt")
|
||||||
|
public void testUsagesAndInheritanceTogether() throws Exception {
|
||||||
|
runTest("idea/testData/codeInsight/codeVision/UsagesAndInheritanceTogether.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -0,0 +1 @@
|
|||||||
|
package org.jetbrains.kotlin.idea.codeInsight.codevision;
|
||||||
Reference in New Issue
Block a user