Skip to content

Commit

Permalink
Intellij Plugin: Implement registered name codeVision hint (#248)
Browse files Browse the repository at this point in the history
* Implement registered name codeVision hint

* Rename codeVision class

* Specify type explicitly

* Fix type specification

* Update plugin versions and fix detekt errors

* Fix compatibility with IJ203

* Fix missed renames
  • Loading branch information
chippmann authored Jul 18, 2021
1 parent dbf85dc commit a5410dd
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 6 deletions.
12 changes: 7 additions & 5 deletions kt/plugins/godot-intellij-plugin/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,24 @@ plugins {
id("io.gitlab.arturbosch.detekt") version "1.16.0"
}

//sdk version: https://github.com/JetBrains/intellij-community/releases
//kotlin plugin version: https://plugins.jetbrains.com/plugin/6954-kotlin/versions
val buildMatrix: Map<String, BuildConfig> = mapOf(
"IJ203" to BuildConfig(
"203.7717.56",
"203.8084.24",
"IJ2020.3",
"IJ183",
VersionRange("203.1", "203.*"),
listOf("2020.1.4", "2020.2.3", "2020.3"),
listOf("java", "org.jetbrains.kotlin:203-1.4.32-release-IJ7148.5", "gradle")
listOf("java", "org.jetbrains.kotlin:203-1.5.21-release-316-IJ7717.8", "gradle")
),
"IJ211" to BuildConfig(
"211.6693.111",
"211.7628.21",
"IJ2021.1",
"IJ183",
VersionRange("211.1", "211.*"),
listOf("2021.1.1"),
listOf("java", "org.jetbrains.kotlin:211-1.4.32-release-IJ6693.72", "gradle")
listOf("2021.1.1", "2021.1.2", "2021.1.3"),
listOf("java", "org.jetbrains.kotlin:211-1.5.21-release-317-IJ7442.40", "gradle")
)
)

Expand Down
1 change: 1 addition & 0 deletions kt/plugins/godot-intellij-plugin/detekt-config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,5 @@ complexity:
'**/godot/intellij/plugin/data/cache/signalconnection/SignalConnectionCache.kt',
'**/godot/intellij/plugin/annotator/clazz/RegisterClassAnnotator.kt',
'**/godot/intellij/plugin/module/GodotModuleBuilder.kt',
'**/godot/intellij/plugin/codevision/RegisteredNameInlayHint.kt',
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package godot.intellij.plugin.codevision

import com.intellij.codeInsight.hints.ChangeListener
import com.intellij.codeInsight.hints.FactoryInlayHintsCollector
import com.intellij.codeInsight.hints.ImmediateConfigurable
import com.intellij.codeInsight.hints.InlayHintsCollector
import com.intellij.codeInsight.hints.InlayHintsProvider
import com.intellij.codeInsight.hints.InlayHintsSink
import com.intellij.codeInsight.hints.InlayPresentationFactory
import com.intellij.codeInsight.hints.NoSettings
import com.intellij.codeInsight.hints.SettingsKey
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.refactoring.suggested.startOffset
import com.intellij.ui.layout.panel
import godot.intellij.plugin.GodotPluginBundle
import godot.intellij.plugin.data.model.REGISTER_CLASS_ANNOTATION
import godot.intellij.plugin.data.model.REGISTER_FUNCTION_ANNOTATION
import godot.intellij.plugin.data.model.REGISTER_PROPERTY_ANNOTATION
import godot.intellij.plugin.data.model.REGISTER_SIGNAL_ANNOTATION
import godot.intellij.plugin.extension.camelToSnakeCase
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.KtClass
import org.jetbrains.kotlin.psi.KtFile
import org.jetbrains.kotlin.psi.KtNamedFunction
import org.jetbrains.kotlin.psi.KtProperty
import java.awt.Point
import java.awt.datatransfer.StringSelection
import java.awt.event.MouseEvent
import javax.swing.JPanel

@Suppress("UnstableApiUsage")
class RegisteredNameInlayHint : InlayHintsProvider<NoSettings> {

override val key: SettingsKey<NoSettings> = SettingsKey("RegisteredNameInlayHint")
override val name: String = GodotPluginBundle.message("codeVision.name")
override val previewText: String? = null

override fun createConfigurable(settings: NoSettings): ImmediateConfigurable = object : ImmediateConfigurable {
override fun reset() {
// no op
}

override fun createComponent(listener: ChangeListener): JPanel = panel {}
override val cases: List<ImmediateConfigurable.Case> = emptyList()
override val mainCheckboxText: String = GodotPluginBundle.message("codeVision.settings.enableCheckbox")
}

override fun createSettings(): NoSettings = NoSettings()

override fun getCollectorFor(
file: PsiFile,
editor: Editor,
settings: NoSettings,
sink: InlayHintsSink
): InlayHintsCollector {
return object : InlayHintsCollector, FactoryInlayHintsCollector(editor) {
override fun collect(element: PsiElement, editor: Editor, sink: InlayHintsSink): Boolean {
return if (element is KtFile) {
val classes = element
.children
.filterIsInstance<KtClass>()

val registeredClasses = classes
.filter { it.findAnnotation(FqName(REGISTER_CLASS_ANNOTATION)) != null }

val functions = classes
.flatMap { it.declarations.filterIsInstance<KtNamedFunction>() }
.filter { it.findAnnotation(FqName(REGISTER_FUNCTION_ANNOTATION)) != null }
.filter { it.name != null }

val properties = classes
.flatMap { it.declarations.filterIsInstance<KtProperty>() }
.filter { it.findAnnotation(FqName(REGISTER_PROPERTY_ANNOTATION)) != null }
.filter { it.name != null }

val signals = classes
.flatMap { it.declarations.filterIsInstance<KtProperty>() }
.filter { it.findAnnotation(FqName(REGISTER_SIGNAL_ANNOTATION)) != null }
.filter { it.name != null }

registeredClasses.forEach { ktClass ->
showCodeVision(
textOffset = ktClass.textOffset,
startOffset = ktClass.startOffset,
convertedName = ktClass
.fqName
?.asString()
?.replace(".", "_")
?: "<unknown>",
editor = editor,
sink = sink
)
}

functions.forEach { ktFunction ->
showCodeVision(
textOffset = ktFunction.textOffset,
startOffset = ktFunction.startOffset,
convertedName = ktFunction.name?.camelToSnakeCase() ?: "<unknown>",
editor = editor,
sink = sink
)
}

properties.forEach { ktProperty ->
showCodeVision(
textOffset = ktProperty.textOffset,
startOffset = ktProperty.startOffset,
convertedName = ktProperty.name?.camelToSnakeCase() ?: "<unknown>",
editor = editor,
sink = sink
)
}

signals.forEach { ktProperty ->
showCodeVision(
textOffset = ktProperty.textOffset,
startOffset = ktProperty.startOffset,
convertedName = ktProperty
.name
?.removePrefix("signal")
?.camelToSnakeCase()
?: "<unknown>",
editor = editor,
sink = sink
)
}

registeredClasses.isNotEmpty() || functions.isNotEmpty() || properties.isNotEmpty() || signals.isNotEmpty()
} else false
}
}
}

private fun FactoryInlayHintsCollector.showCodeVision(
textOffset: Int,
startOffset: Int,
convertedName: String,
editor: Editor,
sink: InlayHintsSink
) {
val line = editor.document.getLineNumber(startOffset)
val lineStart = editor.document.getLineStartOffset(line)
val indent = EditorUtil.getPlainSpaceWidth(editor) * (startOffset - lineStart)
sink.addBlockElement(
offset = textOffset,
relatesToPrecedingText = false,
showAbove = true,
priority = 0,
presentation = factory.inset(
factory.withTooltip(
GodotPluginBundle.message("codeVision.registeredName.tooltip"),
factory.referenceOnHover(
factory.text(
GodotPluginBundle.message(
"codeVision.registeredName.text",
convertedName
)
),
// has to be explicit for backwards compatibility with IJ203
@Suppress("ObjectLiteralToLambda")
object : InlayPresentationFactory.ClickListener {
override fun onClick(event: MouseEvent, translated: Point) {
CopyPasteManager.getInstance().setContents(StringSelection(convertedName))
}
}
)
),
indent
)
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package godot.intellij.plugin.extension

import java.util.Locale

fun String.snakeToLowerCamelCase(): String {
return "_[a-zA-Z]"
.toRegex()
Expand All @@ -10,3 +12,10 @@ fun String.snakeToLowerCamelCase(): String {
.toUpperCase()
}
}

//TODO: replace once entry gen checks and helper functions are in a shared module
fun String.camelToSnakeCase(): String {
return "(?<=[a-zA-Z0-9])[A-Z]".toRegex().replace(this) {
"_${it.value}"
}.toLowerCase(Locale.ENGLISH)
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
<annotator implementationClass="godot.intellij.plugin.annotator.reference.FunctionReferenceAnnotator" language="JVM"/>
<annotator implementationClass="godot.intellij.plugin.annotator.copy.CopyModificationAnnotator" language="JVM"/>
<codeInsight.lineMarkerProvider language="JVM" implementationClass="godot.intellij.plugin.linemarker.SignalConnectionLineMarker"/>
<codeInsight.inlayProvider language="JVM" implementationClass="godot.intellij.plugin.codevision.RegisteredNameInlayHint"/>
</extensions>

<resource-bundle>messages.generalLabels</resource-bundle>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,4 +69,7 @@ quickFix.signal.initializer.familyName=Use "by signal" delegate
quickFix.signal.mutability.familyName=Make signal not mutable
notification.signal.mutability.error.title=@RegisterSignal Quick Fix
notification.signal.mutability.error.content=Could not change the mutability of signal {0}. Change it manually

codeVision.name=RegisteredNameInlayHint
codeVision.settings.enableCheckbox=Display registered name above element
codeVision.registeredName.tooltip=Click to copy registered name
codeVision.registeredName.text=Registered as {0}

0 comments on commit a5410dd

Please sign in to comment.