1
0
mirror of https://github.com/chylex/IntelliJ-Rainbow-Brackets.git synced 2026-02-19 04:46:34 +01:00

Compare commits

3 Commits

Author SHA1 Message Date
a02e7f0916 Release 1.3.0 2025-10-30 07:59:12 +01:00
0d8e2c9e16 Update Gradle to 8.14.3 and IntelliJ Platform plugin to 2.9.0 2025-10-29 21:38:00 +01:00
3007bdce44 Fix missing read lock in indent guide renderer
Closes #5
2025-10-29 08:09:19 +01:00
31 changed files with 152 additions and 155 deletions

1
.gitignore vendored
View File

@@ -2,4 +2,5 @@
!/.idea/runConfigurations !/.idea/runConfigurations
/.gradle/ /.gradle/
/.intellijPlatform/
/build/ /build/

8
api/build.gradle.kts Normal file
View File

@@ -0,0 +1,8 @@
val ideaVersion: String by project
dependencies {
intellijPlatform {
@Suppress("DEPRECATION")
intellijIdeaUltimate(ideaVersion)
}
}

View File

@@ -16,7 +16,7 @@ object BracePairs {
private val providers = LanguageExtension<BracePairProvider>("com.chylex.coloredbrackets.bracePairProvider") private val providers = LanguageExtension<BracePairProvider>("com.chylex.coloredbrackets.bracePairProvider")
private val bracePairs = lazy { val bracePairs = lazy {
Language.getRegisteredLanguages() Language.getRegisteredLanguages()
.map { language -> .map { language ->
if (language is CompositeLanguage) { if (language is CompositeLanguage) {
@@ -74,15 +74,13 @@ object BracePairs {
.toMap() .toMap()
} }
fun getBracePairs(language: Language): MutableMap<String, MutableList<BracePair>>? = bracePairs.value[language.id] private fun getBraceTypeSetOf(language: Language): Set<IElementType> = language.bracePairs?.values?.flatten()?.map { listOf(it.leftBraceType, it.rightBraceType) }?.flatten()?.toSet() ?: emptySet()
private fun getBraceTypeSetOf(language: Language): Set<IElementType> = getBracePairs(language)?.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()
inline val Language.bracePairs: MutableMap<String, MutableList<BracePair>>?
get() = BracePairs.bracePairs.value[this.id]
inline val Language.braceTypeSet: Set<IElementType>
get() = BracePairs.braceTypeSet(this)
} }
inline val Language.bracePairs: MutableMap<String, MutableList<BracePair>>?
get() = BracePairs.getBracePairs(this)
inline val Language.braceTypeSet: Set<IElementType>
get() = BracePairs.braceTypeSet(this)

View File

@@ -1,6 +1,7 @@
package com.chylex.intellij.coloredbrackets package com.chylex.intellij.coloredbrackets
import com.chylex.intellij.coloredbrackets.settings.RainbowSettings import com.chylex.intellij.coloredbrackets.settings.RainbowSettings
import com.chylex.intellij.coloredbrackets.util.create
import com.chylex.intellij.coloredbrackets.util.memoize import com.chylex.intellij.coloredbrackets.util.memoize
import com.intellij.codeInsight.daemon.impl.HighlightInfo import com.intellij.codeInsight.daemon.impl.HighlightInfo
import com.intellij.codeInsight.daemon.impl.HighlightInfoType import com.intellij.codeInsight.daemon.impl.HighlightInfoType
@@ -137,7 +138,7 @@ object RainbowHighlighter {
} }
private fun genByOption(option: String, rainbowName: String, level: Int) = private fun genByOption(option: String, rainbowName: String, level: Int) =
com.chylex.intellij.coloredbrackets.util.create( create(
"$rainbowName-$level", "$rainbowName-$level",
TextAttributes(randomColor(option), null, null, null, 0) TextAttributes(randomColor(option), null, null, null, 0)
) )

View File

@@ -2,6 +2,7 @@ package com.chylex.intellij.coloredbrackets
import com.chylex.intellij.coloredbrackets.color.Luminosity import com.chylex.intellij.coloredbrackets.color.Luminosity
import com.chylex.intellij.coloredbrackets.color.fromString import com.chylex.intellij.coloredbrackets.color.fromString
import com.chylex.intellij.coloredbrackets.color.randomColor
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.fasterxml.jackson.module.kotlin.readValue import com.fasterxml.jackson.module.kotlin.readValue
@@ -11,7 +12,7 @@ val mapper: ObjectMapper by lazy { jacksonObjectMapper() }
fun randomColor(options: String): Color { fun randomColor(options: String): Color {
val ops: Map<String, String> = mapper.readValue(options) val ops: Map<String, String> = mapper.readValue(options)
return com.chylex.intellij.coloredbrackets.color.randomColor( return randomColor(
fromString(ops.getOrDefault("hue", "random")), fromString(ops.getOrDefault("hue", "random")),
Luminosity.valueOf(ops.getOrDefault("luminosity", "random")) Luminosity.valueOf(ops.getOrDefault("luminosity", "random"))
) )

View File

@@ -5,7 +5,7 @@ import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.SettingsCategory import com.intellij.openapi.components.SettingsCategory
import com.intellij.openapi.components.State import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage import com.intellij.openapi.components.Storage
import com.intellij.util.xmlb.XmlSerializerUtil.copyBean import com.intellij.util.xmlb.XmlSerializerUtil
import org.jetbrains.annotations.Nullable import org.jetbrains.annotations.Nullable
@State(name = "ColoredBracketsSettings", storages = [Storage("colored_brackets.xml")], category = SettingsCategory.UI) @State(name = "ColoredBracketsSettings", storages = [Storage("colored_brackets.xml")], category = SettingsCategory.UI)
@@ -51,7 +51,7 @@ class RainbowSettings : PersistentStateComponent<RainbowSettings> {
override fun getState() = this override fun getState() = this
override fun loadState(state: RainbowSettings) { override fun loadState(state: RainbowSettings) {
copyBean(state, this) XmlSerializerUtil.copyBean(state, this)
} }
companion object { companion object {

View File

@@ -23,7 +23,7 @@ private interface MemoizedCall<in F, out R> {
private class MemoizedHandler<F, in K : MemoizedCall<F, R>, out R>(val f: F) { private class MemoizedHandler<F, in K : MemoizedCall<F, R>, out R>(val f: F) {
private val m = Platform.newConcurrentMap<K, R>() private val m = Platform.newConcurrentMap<K, R>()
operator fun invoke(k: K): R = m[k] ?: run { m.putSafely(k, k(f)) } operator fun invoke(k: K): R = m[k] ?: m.putSafely(k, k(f))
} }
private data class MemoizeKey1<out A, R>(val a: A) : MemoizedCall<(A) -> R, R> { private data class MemoizeKey1<out A, R>(val a: A) : MemoizedCall<(A) -> R, R> {

View File

@@ -96,24 +96,24 @@ abstract class RainbowHighlightVisitor : HighlightVisitor {
private fun fileIsNotHaskellOrIntelliJHaskellPluginNotEnabled(fileType: String) = private fun fileIsNotHaskellOrIntelliJHaskellPluginNotEnabled(fileType: String) =
fileType != "Haskell" || !isIntelliJHaskellEnabled fileType != "Haskell" || !isIntelliJHaskellEnabled
}
} private fun PsiElement.getLineCount(): Int {
try {
fun PsiElement.getLineCount(): Int { val doc = containingFile?.let { PsiDocumentManager.getInstance(project).getDocument(it) }
try { if (doc != null) {
val doc = containingFile?.let { PsiDocumentManager.getInstance(project).getDocument(it) } val spaceRange = textRange ?: TextRange.EMPTY_RANGE
if (doc != null) {
val spaceRange = textRange ?: TextRange.EMPTY_RANGE if (spaceRange.endOffset <= doc.textLength && spaceRange.startOffset < spaceRange.endOffset) {
val startLine = doc.getLineNumber(spaceRange.startOffset)
if (spaceRange.endOffset <= doc.textLength && spaceRange.startOffset < spaceRange.endOffset) { val endLine = doc.getLineNumber(spaceRange.endOffset - 1)
val startLine = doc.getLineNumber(spaceRange.startOffset)
val endLine = doc.getLineNumber(spaceRange.endOffset - 1) return endLine - startLine + 1
}
return endLine - startLine + 1 }
return StringUtil.getLineBreakCount(text ?: "") + 1
} catch (e: Throwable) {
return 0
} }
} }
return StringUtil.getLineBreakCount(text ?: "") + 1
} catch (e: Throwable) {
return 0
} }
} }

View File

@@ -1,129 +1,103 @@
@file:Suppress("ConvertLambdaToReference") @file:Suppress("ConvertLambdaToReference")
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import kotlin.io.path.Path
plugins { plugins {
kotlin("jvm") kotlin("jvm")
id("org.jetbrains.intellij") id("org.jetbrains.intellij.platform")
} }
group = "com.chylex.intellij.coloredbrackets" group = "com.chylex.intellij.coloredbrackets"
version = "1.2.0" version = "1.3.0"
val ideaVersion = "2023.3"
allprojects { allprojects {
apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "org.jetbrains.kotlin.jvm")
apply(plugin = "org.jetbrains.intellij") apply(plugin = "org.jetbrains.intellij.platform")
ext {
set("ideaVersion", ideaVersion)
}
repositories { repositories {
mavenCentral() mavenCentral()
intellijPlatform {
defaultRepositories()
}
} }
intellij { intellijPlatform {
version.set("2023.3") pluginConfiguration {
updateSinceUntilBuild.set(false) ideaVersion {
sinceBuild.set("233")
untilBuild.set(provider { null })
}
}
} }
kotlin { kotlin {
jvmToolchain(17) jvmToolchain(17)
}
compilerOptions {
tasks.withType<KotlinCompile> { freeCompilerArgs = listOf(
kotlinOptions.freeCompilerArgs = listOf( "-X" + "jvm-default=all",
"-Xjvm-default=all" )
) }
} }
} }
subprojects { subprojects {
tasks.buildSearchableOptions { intellijPlatform {
enabled = false buildSearchableOptions = false
} }
} }
idea { idea {
module { module {
excludeDirs.add(file("build"))
excludeDirs.add(file("gradle")) excludeDirs.add(file("gradle"))
} }
} }
intellij {
type.set("IU")
plugins.set(
listOf(
// Built-in
"Groovy",
"JavaScript",
"com.intellij.css",
"com.intellij.database",
"com.intellij.java",
"org.intellij.plugins.markdown",
"org.jetbrains.kotlin",
"org.jetbrains.plugins.yaml",
// Downloaded
"Dart:233.11799.172", // https://plugins.jetbrains.com/plugin/6351-dart/versions/stable
"Pythonid:233.11799.300", // https://plugins.jetbrains.com/plugin/631-python/versions
"com.jetbrains.php:233.11799.300", // https://plugins.jetbrains.com/plugin/6610-php/versions
"com.jetbrains.sh:233.11799.165", // https://plugins.jetbrains.com/plugin/13122-shell-script/versions
"org.intellij.scala:2023.3.19", // https://plugins.jetbrains.com/plugin/1347-scala/versions
"org.jetbrains.plugins.go-template:233.11799.172", // https://plugins.jetbrains.com/plugin/10581-go-template/versions
"org.jetbrains.plugins.ruby:233.11799.300", // https://plugins.jetbrains.com/plugin/1293-ruby/versions
)
)
}
dependencies { dependencies {
project(":api")
intellijPlatform {
@Suppress("DEPRECATION")
intellijIdeaUltimate(ideaVersion)
bundledPlugin("JavaScript")
bundledPlugin("com.intellij.css")
bundledPlugin("com.intellij.database")
bundledPlugin("com.intellij.java")
bundledPlugin("org.intellij.groovy")
bundledPlugin("org.intellij.plugins.markdown")
bundledPlugin("org.jetbrains.kotlin")
bundledPlugin("org.jetbrains.plugins.yaml")
plugin("Dart", "233.11799.172") // https://plugins.jetbrains.com/plugin/6351-dart/versions/stable
plugin("PythonCore", "233.11799.300") // https://plugins.jetbrains.com/plugin/631-python/versions
plugin("com.jetbrains.php", "233.11799.300") // https://plugins.jetbrains.com/plugin/6610-php/versions
plugin("com.jetbrains.sh", "233.11799.165") // https://plugins.jetbrains.com/plugin/13122-shell-script/versions
plugin("org.intellij.scala", "2023.3.19") // https://plugins.jetbrains.com/plugin/1347-scala/versions
plugin("org.jetbrains.plugins.go-template", "233.11799.172") // https://plugins.jetbrains.com/plugin/10581-go-template/versions
plugin("org.jetbrains.plugins.ruby", "233.11799.300") // https://plugins.jetbrains.com/plugin/1293-ruby/versions
testFramework(TestFrameworkType.Plugin.Java)
pluginComposedModule(implementation(project(":api")))
pluginComposedModule(implementation(project(":clion")))
pluginComposedModule(implementation(project(":rider")))
}
testImplementation("junit:junit:4.13.2") testImplementation("junit:junit:4.13.2")
testImplementation("io.kotest:kotest-assertions-core:5.8.0") { testImplementation("io.kotest:kotest-assertions-core:5.8.0") {
exclude(group = "org.jetbrains.kotlin") exclude(group = "org.jetbrains.kotlin")
} }
} }
tasks.patchPluginXml {
sinceBuild.set("233")
}
tasks.test { tasks.test {
useJUnit() useJUnit()
} }
tasks.buildPlugin {
val projectName = rootProject.name
val instrumentedJarName = "instrumented-$projectName-$version"
for (ide in listOf("clion", "rider")) {
val task = project(":$ide").tasks.buildPlugin
dependsOn(task)
from(task.map { it.outputs.files.map(::zipTree) }) {
include("$ide/lib/instrumented-$ide.jar")
into("lib")
eachFile {
val newName = name.replace("instrumented-", "${instrumentedJarName}-")
val newPath = relativePath.segments.dropLast(3).plus(newName)
relativePath = RelativePath(true, *newPath.toTypedArray())
}
includeEmptyDirs = false
}
}
doLast {
val expectedPaths = listOf(
Path(projectName, "lib", "instrumented-$projectName-$version-clion.jar"),
Path(projectName, "lib", "instrumented-$projectName-$version-rider.jar"),
Path(projectName, "lib", "instrumented-$projectName-$version.jar"),
Path(projectName, "lib", "searchableOptions-$version.jar"),
)
val jarFiles = zipTree(outputs.files.singleFile)
for (expectedPath in expectedPaths) {
val found = jarFiles.find { it.toPath().endsWith(expectedPath) }
checkNotNull(found) { "Expected path not found: $expectedPath" }
}
}
}

View File

@@ -1,13 +1,12 @@
intellij { val ideaVersion: String by project
type.set("CL")
plugins.set(listOf(
// Built-in
"cidr-base-plugin",
//"org.jetbrains.plugins.clion.radler" // Only in 2024.1 or newer. Worked around by only including the .xml file, and taking the implementation from Rider.
))
}
dependencies { dependencies {
implementation(rootProject) implementation(project(":api"))
intellijPlatform {
clion(ideaVersion)
bundledPlugin("com.intellij.clion")
// bundledPlugin("org.jetbrains.plugins.clion.radler") // Only in 2024.1 or newer. Worked around by only including the .xml file, and taking the implementation from Rider.
}
} }

View File

@@ -1 +1,2 @@
kotlin.stdlib.default.dependency=false kotlin.stdlib.default.dependency=false
org.gradle.jvmargs=-Xmx1G

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -1,7 +1,11 @@
intellij { val ideaVersion: String by project
type.set("RD")
}
dependencies { dependencies {
implementation(rootProject) implementation(project(":api"))
intellijPlatform {
rider(ideaVersion) {
useInstaller = false
}
}
} }

View File

@@ -1,6 +1,6 @@
package com.chylex.intellij.coloredbrackets.visitor package com.chylex.intellij.coloredbrackets.visitor
import com.chylex.intellij.coloredbrackets.bracePairs import com.chylex.intellij.coloredbrackets.BracePairs.bracePairs
import com.chylex.intellij.coloredbrackets.settings.RainbowSettings import com.chylex.intellij.coloredbrackets.settings.RainbowSettings
import com.intellij.lang.BracePair import com.intellij.lang.BracePair
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement

View File

@@ -3,9 +3,10 @@ rootProject.name = "ColoredBrackets"
pluginManagement { pluginManagement {
plugins { plugins {
kotlin("jvm") version "1.9.21" kotlin("jvm") version "1.9.21"
id("org.jetbrains.intellij") version "1.17.4" id("org.jetbrains.intellij.platform") version "2.9.0"
} }
} }
include("api")
include("clion") include("clion")
include("rider") include("rider")

View File

@@ -16,7 +16,7 @@ class KotlinLambdaExpressionArrowAnnotator : Annotator {
if ((element as? LeafPsiElement)?.elementType == KtTokens.ARROW) { if ((element as? LeafPsiElement)?.elementType == KtTokens.ARROW) {
RainbowInfo.RAINBOW_INFO_KEY[element.parent]?.color?.let { RainbowInfo.RAINBOW_INFO_KEY[element.parent]?.color?.let {
holder.newSilentAnnotation(HighlightSeverity.INFORMATION) holder.newSilentAnnotation(HighlightSeverity.INFORMATION)
.range(element) .range(element as PsiElement) // Cast necessary due to overload conflict in Kotlin 2 compiler.
.textAttributes( .textAttributes(
com.chylex.intellij.coloredbrackets.util.create( com.chylex.intellij.coloredbrackets.util.create(
"rainbow-kotlin-arrow", "rainbow-kotlin-arrow",

View File

@@ -7,6 +7,7 @@ import com.chylex.intellij.coloredbrackets.util.findNextSibling
import com.chylex.intellij.coloredbrackets.util.findPrevSibling import com.chylex.intellij.coloredbrackets.util.findPrevSibling
import com.chylex.intellij.coloredbrackets.util.lineNumber import com.chylex.intellij.coloredbrackets.util.lineNumber
import com.chylex.intellij.coloredbrackets.util.startOffset import com.chylex.intellij.coloredbrackets.util.startOffset
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.SoftWrap import com.intellij.openapi.editor.SoftWrap
@@ -165,24 +166,28 @@ class RainbowIndentGuideRenderer : CustomHighlighterRenderer {
val virtualFile = editor.virtualFile?.takeIf { it.isValid } ?: 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 val project = editor.project ?: return null
val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return null
var element = try {
psiFile.findElementAt(highlighter.endOffset)?.parent ?: return null
} catch (e: Throwable) {
return null
}
var rainbowInfo = RainbowInfo.RAINBOW_INFO_KEY[element] return ReadAction.compute<RainbowInfo, Throwable> {
if (rainbowInfo == null && psiFile is XmlFile && element !is XmlTag) { val psiFile = PsiManager.getInstance(project).findFile(virtualFile) ?: return@compute null
element = PsiTreeUtil.findFirstParent(element, true, XML_TAG_PARENT_CONDITION) ?: return null var element = try {
rainbowInfo = RainbowInfo.RAINBOW_INFO_KEY[element] ?: return null psiFile.findElementAt(highlighter.endOffset)?.parent ?: return@compute null
} catch (_: Throwable) {
return@compute null
}
var rainbowInfo = RainbowInfo.RAINBOW_INFO_KEY[element]
if (rainbowInfo == null && psiFile is XmlFile && element !is XmlTag) {
element = PsiTreeUtil.findFirstParent(element, true, XML_TAG_PARENT_CONDITION) ?: return@compute null
rainbowInfo = RainbowInfo.RAINBOW_INFO_KEY[element] ?: return@compute null
}
if (!element.isValid || !checkBoundary(document, element, highlighter)) {
null
}
else {
rainbowInfo
}
} }
if (!element.isValid || !checkBoundary(document, element, highlighter)) {
return null
}
return rainbowInfo
} }
/*** /***

View File

@@ -1,7 +1,7 @@
package com.chylex.intellij.coloredbrackets.visitor package com.chylex.intellij.coloredbrackets.visitor
import com.chylex.intellij.coloredbrackets.bracePairs import com.chylex.intellij.coloredbrackets.BracePairs.bracePairs
import com.chylex.intellij.coloredbrackets.braceTypeSet import com.chylex.intellij.coloredbrackets.BracePairs.braceTypeSet
import com.chylex.intellij.coloredbrackets.settings.RainbowSettings import com.chylex.intellij.coloredbrackets.settings.RainbowSettings
import com.intellij.codeInsight.daemon.impl.HighlightVisitor import com.intellij.codeInsight.daemon.impl.HighlightVisitor
import com.intellij.lang.BracePair import com.intellij.lang.BracePair

View File

@@ -18,6 +18,10 @@
]]></description> ]]></description>
<change-notes><![CDATA[ <change-notes><![CDATA[
<b>Version 1.3.0</b>
<ul>
<li>Fixed assertion error caused by missing read lock in indent guide renderer.</li>
</ul>
<b>Version 1.2.0</b> <b>Version 1.2.0</b>
<ul> <ul>
<li>Fixed not re-highlighting open files after changing settings.</li> <li>Fixed not re-highlighting open files after changing settings.</li>