5 Commits
16 changed files with 237 additions and 78 deletions
-1
View File
@@ -1,5 +1,4 @@
/.idea/* /.idea/*
!/.idea/dictionaries
!/.idea/runConfigurations !/.idea/runConfigurations
/.gradle/ /.gradle/
-7
View File
@@ -1,7 +0,0 @@
<component name="ProjectDictionaryState">
<dictionary name="project">
<words>
<w>Rainbowify</w>
</words>
</dictionary>
</component>
+4
View File
@@ -39,3 +39,7 @@ dependencies {
excludeCoroutines() excludeCoroutines()
} }
} }
intellijPlatform {
buildSearchableOptions = true
}
@@ -57,7 +57,8 @@ object BracePairs {
true true
} }
} }
?.flatMap { listOf(Pair(it.leftBraceType.toString(), it), Pair(it.rightBraceType.toString(), it)) } ?.map { listOf(Pair(it.leftBraceType.toString(), it), Pair(it.rightBraceType.toString(), it)) }
?.flatten()
?.forEach { ?.forEach {
val bracePairs = braceMap[it.first] val bracePairs = braceMap[it.first]
if (bracePairs == null) { if (bracePairs == null) {
@@ -73,7 +74,7 @@ object BracePairs {
.toMap() .toMap()
} }
private fun getBraceTypeSetOf(language: Language): Set<IElementType> = language.bracePairs?.values?.flatten()?.flatMap { listOf(it.leftBraceType, it.rightBraceType) }?.toSet() ?: emptySet() private fun getBraceTypeSetOf(language: Language): Set<IElementType> = language.bracePairs?.values?.flatten()?.map { listOf(it.leftBraceType, it.rightBraceType) }?.flatten()?.toSet() ?: emptySet()
val braceTypeSet: (Language) -> Set<IElementType> = { language: Language -> getBraceTypeSetOf(language) }.memoize() val braceTypeSet: (Language) -> Set<IElementType> = { language: Language -> getBraceTypeSetOf(language) }.memoize()
@@ -0,0 +1,123 @@
package com.chylex.intellij.coloredbrackets.annotator
import com.chylex.intellij.coloredbrackets.RainbowHighlighter.NAME_ANGLE_BRACKETS
import com.chylex.intellij.coloredbrackets.RainbowHighlighter.NAME_ROUND_BRACKETS
import com.chylex.intellij.coloredbrackets.RainbowHighlighter.NAME_SQUARE_BRACKETS
import com.chylex.intellij.coloredbrackets.RainbowHighlighter.NAME_SQUIGGLY_BRACKETS
import com.chylex.intellij.coloredbrackets.RainbowHighlighter.getRainbowColorByLevel
import com.chylex.intellij.coloredbrackets.annotator.RainbowUtils.annotateUtil
import com.chylex.intellij.coloredbrackets.annotator.RainbowUtils.settings
import com.chylex.intellij.coloredbrackets.settings.RainbowSettings
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.annotation.Annotator
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.editor.colors.EditorColorsManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.impl.source.tree.LeafPsiElement
class RainbowAnnotator : Annotator {
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
val settings = settings
if (settings.isRainbowEnabled && element is LeafPsiElement) {
if (!settings.applyColorsOfRoundForAllBrackets) {
if (settings.isEnableRainbowRoundBrackets) annotateUtil(element, holder, "(", ")", NAME_ROUND_BRACKETS)
if (settings.isEnableRainbowSquareBrackets) annotateUtil(element, holder, "[", "]", NAME_SQUARE_BRACKETS)
if (settings.isEnableRainbowSquigglyBrackets) annotateUtil(element, holder, "{", "}", NAME_SQUIGGLY_BRACKETS)
if (settings.isEnableRainbowAngleBrackets) annotateUtil(element, holder, "<", ">", NAME_ANGLE_BRACKETS)
}
else {
if (settings.isEnableRainbowRoundBrackets) annotateUtil(element, holder, "(", ")", NAME_ROUND_BRACKETS)
if (settings.isEnableRainbowSquareBrackets) annotateUtil(element, holder, "[", "]", NAME_ROUND_BRACKETS)
if (settings.isEnableRainbowSquigglyBrackets) annotateUtil(element, holder, "{", "}", NAME_ROUND_BRACKETS)
if (settings.isEnableRainbowAngleBrackets) annotateUtil(element, holder, "<", ">", NAME_ROUND_BRACKETS)
}
}
}
}
object RainbowUtils {
private val leftBracketsSet = setOf("(", "[", "{", "<")
private val rightBracketsSet = setOf(")", "]", "}", ">")
val settings
get() = RainbowSettings.instance
private tailrec fun iterateChildren(
LEFT: String,
RIGHT: String,
currentNode: PsiElement,
currentLevel: Int,
currentChild: PsiElement,
): Int {
val calculatedLevel = if (currentChild is LeafPsiElement) {
//Using `currentChild.elementType.toString()` if we didn't want add more dependencies.
if (!settings.cycleCountOnAllBrackets) {
when (currentChild.text) {
LEFT -> currentLevel + 1
RIGHT -> currentLevel - 1
else -> currentLevel
}
}
else {
when {
leftBracketsSet.contains(currentChild.text) -> currentLevel + 1
rightBracketsSet.contains(currentChild.text) -> currentLevel - 1
else -> currentLevel
}
}
}
else currentLevel
return if ((currentChild != currentNode) && (currentChild != currentNode.parent.lastChild))
iterateChildren(LEFT, RIGHT, currentNode, calculatedLevel, currentChild.nextSibling)
else
calculatedLevel
}
private tailrec fun iterateParents(
LEFT: String,
RIGHT: String,
currentNode: PsiElement,
currentLevel: Int,
): Int = if (currentNode.parent !is PsiFile) {
val calculatedLevel = iterateChildren(LEFT, RIGHT, currentNode, currentLevel, currentNode.parent.firstChild)
iterateParents(LEFT, RIGHT, currentNode.parent, calculatedLevel)
}
else currentLevel
private fun getBracketLevel(element: LeafPsiElement, LEFT: String, RIGHT: String): Int {
//Using `element.elementType.toString()` if we didn't want add more dependencies.
val startLevel = if (element.text == RIGHT) 0 else -1
return iterateParents(LEFT, RIGHT, element, startLevel)
}
fun annotateUtil(
element: LeafPsiElement, holder: AnnotationHolder,
LEFT: String, RIGHT: String, rainbowName: String,
) {
//Using `element.elementType.toString()` if we didn't want add more dependencies.
val level = when (element.text) {
LEFT, RIGHT -> getBracketLevel(element, LEFT, RIGHT)
else -> -1
}
val scheme = EditorColorsManager.getInstance().globalScheme
if (RainbowSettings.instance.isDoNOTRainbowifyTheFirstLevel) {
if (level >= 1) {
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
.range(element.psi)
.textAttributes(getRainbowColorByLevel(scheme, rainbowName, level))
.create()
}
}
else {
if (level >= 0) {
holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
.range(element.psi)
.textAttributes(getRainbowColorByLevel(scheme, rainbowName, level))
.create()
}
}
}
}
@@ -134,14 +134,13 @@ private fun matchColor(hueValue: Int, hue: Hue): Color {
hueVal -= 360 hueVal -= 360
} }
for (color in Color.entries) { for (color in Color.values()) {
if (hueVal in color.hueRange.first..color.hueRange.second) { if (hueVal in color.hueRange.first..color.hueRange.second) {
return color return color
} }
} }
// Returning Monochrome if we can't find a value, but this should never happen // Returning Monochrome if we can't find a value, but this should never happen
Color.monochrome return Color.monochrome
} }
} }
} }
@@ -18,8 +18,8 @@ import com.intellij.openapi.editor.impl.view.VisualLinesIterator
import com.intellij.openapi.editor.markup.CustomHighlighterRenderer import com.intellij.openapi.editor.markup.CustomHighlighterRenderer
import com.intellij.openapi.editor.markup.RangeHighlighter import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.util.Condition import com.intellij.openapi.util.Condition
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.util.PsiTreeUtil import com.intellij.psi.util.PsiTreeUtil
import com.intellij.psi.xml.XmlFile import com.intellij.psi.xml.XmlFile
import com.intellij.psi.xml.XmlTag import com.intellij.psi.xml.XmlTag
@@ -36,15 +36,13 @@ import kotlin.math.max
* */ * */
class RainbowIndentGuideRenderer : CustomHighlighterRenderer { class RainbowIndentGuideRenderer : CustomHighlighterRenderer {
override fun paint(editor: Editor, highlighter: RangeHighlighter, g: Graphics) { override fun paint(editor: Editor, highlighter: RangeHighlighter, g: Graphics) {
if (editor !is EditorEx) { if (editor !is EditorEx) return
return
} val rainbowInfo = getRainbowInfo(editor, highlighter) ?: return
val startOffset = highlighter.startOffset val startOffset = highlighter.startOffset
val doc = highlighter.document val doc = highlighter.document
if (startOffset >= doc.textLength) { if (startOffset >= doc.textLength) return
return
}
val endOffset = highlighter.endOffset val endOffset = highlighter.endOffset
@@ -62,21 +60,15 @@ class RainbowIndentGuideRenderer : CustomHighlighterRenderer {
val startPosition = editor.offsetToVisualPosition(off) val startPosition = editor.offsetToVisualPosition(off)
val indentColumn = startPosition.column val indentColumn = startPosition.column
if (indentColumn <= 0) { if (indentColumn <= 0) return
return
}
val foldingModel = editor.foldingModel val foldingModel = editor.foldingModel
if (foldingModel.isOffsetCollapsed(off)) { if (foldingModel.isOffsetCollapsed(off)) return
return
}
val headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off))) val headerRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineEndOffset(doc.getLineNumber(off)))
val tailRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineStartOffset(doc.getLineNumber(endOffset))) val tailRegion = foldingModel.getCollapsedRegionAtOffset(doc.getLineStartOffset(doc.getLineNumber(endOffset)))
if (tailRegion != null && tailRegion === headerRegion) { if (tailRegion != null && tailRegion === headerRegion) return
return
}
val guide = editor.indentsModel.caretIndentGuide val guide = editor.indentsModel.caretIndentGuide
val selected = if (guide != null) { val selected = if (guide != null) {
@@ -86,7 +78,7 @@ class RainbowIndentGuideRenderer : CustomHighlighterRenderer {
} }
else false else false
val lineHeight = editor.lineHeight val lineHeight = editor.getLineHeight()
val start = editor.visualPositionToXY(startPosition) val start = editor.visualPositionToXY(startPosition)
start.y += lineHeight start.y += lineHeight
val endPosition = editor.offsetToVisualPosition(endOffset) val endPosition = editor.offsetToVisualPosition(endOffset)
@@ -103,11 +95,7 @@ class RainbowIndentGuideRenderer : CustomHighlighterRenderer {
} }
maxY = StrictMath.min(maxY, clip.y + clip.height) maxY = StrictMath.min(maxY, clip.y + clip.height)
} }
if (start.y >= maxY) { if (start.y >= maxY) return
return
}
val rainbowInfo = getRainbowInfo(editor, highlighter) ?: return
val targetX = max(0, start.x + EditorPainter.getIndentGuideShift(editor)).toDouble() val targetX = max(0, start.x + EditorPainter.getIndentGuideShift(editor)).toDouble()
g.color = if (selected) { g.color = if (selected) {
rainbowInfo.color rainbowInfo.color
@@ -175,11 +163,12 @@ class RainbowIndentGuideRenderer : CustomHighlighterRenderer {
} }
private fun getRainbowInfo(editor: EditorEx, highlighter: RangeHighlighter): RainbowInfo? { private fun getRainbowInfo(editor: EditorEx, highlighter: RangeHighlighter): RainbowInfo? {
val project = editor.project ?: return null val virtualFile = editor.virtualFile?.takeIf { it.isValid } ?: return null
val document = editor.document val document = editor.document
val project = editor.project ?: return null
return ReadAction.compute<RainbowInfo, Throwable> { return ReadAction.compute<RainbowInfo, Throwable> {
val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(document) ?: return@compute null val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return@compute null
var element = try { var element = try {
psiFile.findElementAt(highlighter.endOffset)?.parent ?: return@compute null psiFile.findElementAt(highlighter.endOffset)?.parent ?: return@compute null
} catch (_: Throwable) { } catch (_: Throwable) {
@@ -280,5 +269,6 @@ class RainbowIndentGuideRenderer : CustomHighlighterRenderer {
private fun XmlTag.getEndTagStartLineNumber(document: Document): Int? = private fun XmlTag.getEndTagStartLineNumber(document: Document): Int? =
lastChild?.findPrevSibling(XML_END_TAG_START_CONDITION)?.let { document.lineNumber(it.startOffset) } lastChild?.findPrevSibling(XML_END_TAG_START_CONDITION)?.let { document.lineNumber(it.startOffset) }
} }
} }
@@ -46,9 +46,7 @@ class RainbowIndentsPass internal constructor(
override fun doCollectInformation(progress: ProgressIndicator) { override fun doCollectInformation(progress: ProgressIndicator) {
val stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT) val stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT)
if (stamp != null && stamp == nowStamp()) { if (stamp != null && stamp == nowStamp()) return
return
}
myDescriptors = buildDescriptors() myDescriptors = buildDescriptors()
@@ -74,9 +72,7 @@ class RainbowIndentsPass internal constructor(
val stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT) val stamp = myEditor.getUserData(LAST_TIME_INDENTS_BUILT)
val nowStamp = nowStamp() val nowStamp = nowStamp()
if (stamp == nowStamp) { if (stamp == nowStamp) return
return
}
myEditor.putUserData(LAST_TIME_INDENTS_BUILT, nowStamp) myEditor.putUserData(LAST_TIME_INDENTS_BUILT, nowStamp)
@@ -146,9 +142,7 @@ class RainbowIndentsPass internal constructor(
} }
private fun buildDescriptors(): List<IndentGuideDescriptor> { private fun buildDescriptors(): List<IndentGuideDescriptor> {
if (!isRainbowIndentGuidesShown(this.myProject)) { if (!isRainbowIndentGuidesShown(this.myProject)) return emptyList()
return emptyList()
}
val calculator = IndentsCalculator() val calculator = IndentsCalculator()
calculator.calculate() calculator.calculate()
@@ -17,7 +17,7 @@ class RainbowConfigurable : SearchableConfigurable {
} }
override fun isModified(): Boolean { override fun isModified(): Boolean {
return settingsForm?.isModified ?: false return settingsForm?.isModified ?: return false
} }
@Throws(ConfigurationException::class) @Throws(ConfigurationException::class)
@@ -39,7 +39,7 @@ class RainbowOptionsPanel(
private lateinit var colorLabel4: JLabel private lateinit var colorLabel4: JLabel
private lateinit var colorLabel5: JLabel private lateinit var colorLabel5: JLabel
private val colorLabels = arrayOf(colorLabel1, colorLabel2, colorLabel3, colorLabel4, colorLabel5) private val colorLabels: Array<JLabel>
private lateinit var color1: ColorPanel private lateinit var color1: ColorPanel
private lateinit var color2: ColorPanel private lateinit var color2: ColorPanel
@@ -47,7 +47,7 @@ class RainbowOptionsPanel(
private lateinit var color4: ColorPanel private lateinit var color4: ColorPanel
private lateinit var color5: ColorPanel private lateinit var color5: ColorPanel
private val colors = arrayOf(color1, color2, color3, color4, color5) private val colors: Array<ColorPanel>
private lateinit var gradientLabel: JLabel private lateinit var gradientLabel: JLabel
@@ -56,6 +56,9 @@ class RainbowOptionsPanel(
EventDispatcher.create(ColorAndFontSettingsListener::class.java) EventDispatcher.create(ColorAndFontSettingsListener::class.java)
init { init {
colors = arrayOf(color1, color2, color3, color4, color5)
colorLabels = arrayOf(colorLabel1, colorLabel2, colorLabel3, colorLabel4, colorLabel5)
val actionListener = ActionListener { val actionListener = ActionListener {
eventDispatcher.multicaster.settingsChanged() eventDispatcher.multicaster.settingsChanged()
options.stateChanged() options.stateChanged()
@@ -67,9 +70,7 @@ class RainbowOptionsPanel(
options.addListener(object : ColorAndFontSettingsListener.Abstract() { options.addListener(object : ColorAndFontSettingsListener.Abstract() {
override fun settingsChanged() { override fun settingsChanged() {
if (!schemesProvider.areSchemesLoaded()) { if (!schemesProvider.areSchemesLoaded()) return
return
}
if (optionsTree.selectedValue != null) { if (optionsTree.selectedValue != null) {
// update options after global state change // update options after global state change
processListValueChanged() processListValueChanged()
@@ -235,9 +236,7 @@ class RainbowOptionsPanel(
} }
val pathInChild = findOption(childObject, matcher) val pathInChild = findOption(childObject, matcher)
if (pathInChild != null) { if (pathInChild != null) return pathInChild
return pathInChild
}
} }
return null return null
+1 -6
View File
@@ -18,11 +18,6 @@
]]></description> ]]></description>
<change-notes><![CDATA[ <change-notes><![CDATA[
<b>Version 1.4.0</b>
<ul>
<li>Updated to IntelliJ Platform 2025.3.</li>
<li>Removed support for the Haskell plugin, since it is no longer available.</li>
</ul>
<b>Version 1.3.0</b> <b>Version 1.3.0</b>
<ul> <ul>
<li>Fixed assertion error caused by missing read lock in indent guide renderer.</li> <li>Fixed assertion error caused by missing read lock in indent guide renderer.</li>
@@ -43,7 +38,7 @@
<ul> <ul>
<li>Restored support for CLion and Rider.</li> <li>Restored support for CLion and Rider.</li>
<li>Added support for Settings Sync.</li> <li>Added support for Settings Sync.</li>
<li>Fixed service initialization warnings reported by IntelliJ Platform 2024.2.</li> <li>Fixed service initialization warnings reported by IJ 2024.2.</li>
</ul> </ul>
]]></change-notes> ]]></change-notes>
@@ -60,7 +60,7 @@ console.log(a == b)
fun testIssue21() { fun testIssue21() {
@Language("JavaScript") val code = $$"open (${f})\nopen (${f} )" @Language("JavaScript") val code = "open (\$" + "{f})\n" + "open (\$" + "{f} )"
myFixture.configureByText(JavaScriptFileType, code) myFixture.configureByText(JavaScriptFileType, code)
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
@@ -23,7 +23,7 @@ end
myFixture.configureByText(RubyFileType.RUBY, code) myFixture.configureByText(RubyFileType.RUBY, code)
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
val doHighlighting = myFixture.doHighlighting() val doHighlighting = myFixture.doHighlighting()
assertFalse(doHighlighting.isEmpty()) assertFalse(doHighlighting.isEmpty());
doHighlighting.getBrackets().shouldBe( doHighlighting.getBrackets().shouldBe(
arrayOf( arrayOf(
roundLevel(0), roundLevel(0),
@@ -42,7 +42,7 @@ foobar(p1: "", p2: false, p3: 1)
myFixture.configureByText(RubyFileType.RUBY, code) myFixture.configureByText(RubyFileType.RUBY, code)
PsiDocumentManager.getInstance(project).commitAllDocuments() PsiDocumentManager.getInstance(project).commitAllDocuments()
val doHighlighting = myFixture.doHighlighting() val doHighlighting = myFixture.doHighlighting()
assertFalse(doHighlighting.isEmpty()) assertFalse(doHighlighting.isEmpty());
doHighlighting.getBrackets().shouldBe( doHighlighting.getBrackets().shouldBe(
arrayOf( arrayOf(
roundLevel(0), roundLevel(0),
@@ -8,6 +8,78 @@ import io.kotest.matchers.shouldBe
import org.intellij.lang.annotations.Language import org.intellij.lang.annotations.Language
class RainbowXMLTest : LightJavaCodeInsightFixtureTestCase() { class RainbowXMLTest : LightJavaCodeInsightFixtureTestCase() {
fun `disabled for non-determinist results of testRainbowTagNameForXML`() {
@Language("XML") val code =
"""
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note SYSTEM>
<idea-plugin>
<name>Rainbow Brackets</name>
<description>
<p>Supported languages:</p>
<p>Java, Scala, Clojure, Kotlin, Python, Haskell, Agda, Rust, JavaScript, TypeScript, Erlang, Go, Groovy, Ruby, Elixir, ObjectiveC, PHP, C#, HTML, XML, SQL, Apex language ...</p>
<br/>
</description>
</idea-plugin>
""".trimIndent()
val rainbowSettings = RainbowSettings.instance
rainbowSettings.rainbowifyTagNameInXML = true
myFixture.configureByText(XmlFileType.INSTANCE, code)
PsiDocumentManager.getInstance(project).commitAllDocuments()
val doHighlighting = myFixture.doHighlighting()
assertFalse(doHighlighting.isEmpty())
doHighlighting.getBrackets().shouldBe(
arrayOf(
angleLevel(0),
angleLevel(0),
angleLevel(0),
angleLevel(0),
angleLevel(0),
angleLevel(0),//idea-plugin
angleLevel(0),
angleLevel(1),
angleLevel(1),//name
angleLevel(1),
angleLevel(1),
angleLevel(1),//name
angleLevel(1),
angleLevel(1),
angleLevel(1),//description
angleLevel(1),
angleLevel(2),
angleLevel(2),//p
angleLevel(2),
angleLevel(2),
angleLevel(2),//p
angleLevel(2),
angleLevel(2),
angleLevel(2),//p
angleLevel(2),
angleLevel(2),
angleLevel(2),//p
angleLevel(2),
angleLevel(2),
angleLevel(2),//br
angleLevel(2),
angleLevel(1),
angleLevel(1),//description
angleLevel(1),
angleLevel(0),
angleLevel(0),//idea-plugin
angleLevel(0)
)
)
}
fun testRainbowForXML() { fun testRainbowForXML() {
@Language("XML") val code = @Language("XML") val code =
""" """
+3 -13
View File
@@ -6,7 +6,7 @@ plugins {
} }
group = "com.chylex.intellij.coloredbrackets" group = "com.chylex.intellij.coloredbrackets"
version = "1.4.0" version = "1.3.0"
val ideaVersion = "2025.3" val ideaVersion = "2025.3"
@@ -29,6 +29,8 @@ allprojects {
intellijPlatform { intellijPlatform {
sandboxContainer.set(layout.buildDirectory.map { it.dir("idea-sandbox") }) sandboxContainer.set(layout.buildDirectory.map { it.dir("idea-sandbox") })
buildSearchableOptions = false
pluginConfiguration { pluginConfiguration {
ideaVersion { ideaVersion {
sinceBuild.set("253") sinceBuild.set("253")
@@ -48,14 +50,6 @@ allprojects {
} }
} }
subprojects {
version = rootProject.version
intellijPlatform {
buildSearchableOptions = false
}
}
idea { idea {
module { module {
excludeDirs.add(file(".kotlin")) excludeDirs.add(file(".kotlin"))
@@ -75,10 +69,6 @@ dependencies {
} }
} }
intellijPlatform {
buildSearchableOptions.set(true)
}
tasks.test { tasks.test {
useJUnit() useJUnit()
} }
+1 -1
View File
@@ -3,7 +3,7 @@ rootProject.name = "ColoredBrackets"
pluginManagement { pluginManagement {
plugins { plugins {
kotlin("jvm") version "2.2.20" kotlin("jvm") version "2.2.20"
id("org.jetbrains.intellij.platform") version "2.18.1" id("org.jetbrains.intellij.platform") version "2.16.0"
} }
} }