From d601be5e6a0323c366f0cc17748116a2bc4df439 Mon Sep 17 00:00:00 2001 From: Jake Wharton Date: Wed, 19 Feb 2025 00:51:01 -0500 Subject: [PATCH] Perform rudimentary ansi color level detection (#711) There is a lot to add here, but this gets us off the ground. --- gradle/libs.versions.toml | 4 ---- mosaic-runtime/build.gradle | 7 ------- .../kotlin/com/jakewharton/mosaic/ansi.kt | 11 ----------- .../kotlin/com/jakewharton/mosaic/mosaic.kt | 7 ++----- .../kotlin/com/jakewharton/mosaic/terminal.kt | 18 ++++++++++++++++++ 5 files changed, 20 insertions(+), 27 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6106fe40..7a566537 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,7 +2,6 @@ jetbrains-compose = "1.7.3" kotlin = "2.1.10" kotlinx-coroutines = "1.10.1" -mordant = "3.0.2" [libraries] kotlin-plugin-core = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } @@ -29,9 +28,6 @@ spotless-gradlePlugin = "com.diffplug.spotless:spotless-plugin-gradle:7.0.2" ktlint-core = "com.pinterest.ktlint:ktlint-cli:1.3.1" ktlint-composeRules = "io.nlopez.compose.rules:ktlint:0.4.22" -mordant-core = { module = "com.github.ajalt.mordant:mordant-core", version.ref = "mordant" } -mordant-jvmJna = { module = "com.github.ajalt.mordant:mordant-jvm-jna", version.ref = "mordant" } - clikt = "com.github.ajalt.clikt:clikt:5.0.3" codepoints = "de.cketti.unicode:kotlin-codepoints:0.9.0" finalizationHook = "com.jakewharton.finalization:finalization-hook:0.1.0" diff --git a/mosaic-runtime/build.gradle b/mosaic-runtime/build.gradle index 1e432f10..b1d134c6 100644 --- a/mosaic-runtime/build.gradle +++ b/mosaic-runtime/build.gradle @@ -22,7 +22,6 @@ kotlin { implementation projects.mosaicTerminal implementation libs.androidx.collection implementation libs.finalizationHook - implementation libs.mordant.core implementation libs.codepoints } } @@ -34,12 +33,6 @@ kotlin { implementation libs.assertk } } - - jvmMain { - dependencies { - implementation libs.mordant.jvmJna - } - } } compilerOptions.freeCompilerArgs.add('-Xexpect-actual-classes') diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ansi.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ansi.kt index e6fe4631..8cd9fc61 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ansi.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/ansi.kt @@ -1,7 +1,5 @@ package com.jakewharton.mosaic -import com.github.ajalt.mordant.rendering.AnsiLevel as MordantAnsiLevel -import com.jakewharton.mosaic.ui.AnsiLevel import com.jakewharton.mosaic.ui.Color import kotlin.math.roundToInt @@ -49,15 +47,6 @@ internal const val ansiBgColorOffset = 10 internal const val ansiSelectorColor256 = 5 internal const val ansiSelectorColorRgb = 2 -internal fun MordantAnsiLevel.toMosaicAnsiLevel(): AnsiLevel { - return when (this) { - MordantAnsiLevel.NONE -> AnsiLevel.NONE - MordantAnsiLevel.ANSI16 -> AnsiLevel.ANSI16 - MordantAnsiLevel.ANSI256 -> AnsiLevel.ANSI256 - MordantAnsiLevel.TRUECOLOR -> AnsiLevel.TRUECOLOR - } -} - // simpler version without full conversion to HSV // https://github.com/ajalt/colormath/blob/4a0cc9796c743cb4965407204ee63b40aaf22fca/colormath/src/commonMain/kotlin/com/github/ajalt/colormath/model/RGB.kt#L301 internal fun Color.toAnsi16Code(): Int { diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt index f1d35efa..95835693 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/mosaic.kt @@ -21,7 +21,6 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import androidx.lifecycle.compose.LocalLifecycleOwner -import com.github.ajalt.mordant.terminal.Terminal as MordantTerminal import com.jakewharton.finalization.withFinalizationHook import com.jakewharton.mosaic.layout.KeyEvent import com.jakewharton.mosaic.layout.MosaicNode @@ -92,8 +91,6 @@ private const val StageDefaultQueries = 1 private const val StageNormalOperation = 0 internal suspend fun runMosaic(isTest: Boolean, content: @Composable () -> Unit) { - val mordantTerminal = MordantTerminal() - // Entering raw mode can fail, so perform it before any additional control sequences which change // settings. We also need to be in character mode to query capabilities with control sequences. val rawMode = if (!isTest && env("MOSAIC_RAW_MODE") != "false") { @@ -291,7 +288,7 @@ internal suspend fun runMosaic(isTest: Boolean, content: @Composable () -> Unit) } val rendering = createRendering( - ansiLevel = mordantTerminal.terminalInfo.ansiLevel.toMosaicAnsiLevel(), + ansiLevel = detectAnsiLevel(), synchronizedRendering = supportsSynchronizedRendering, ) @@ -299,7 +296,7 @@ internal suspend fun runMosaic(isTest: Boolean, content: @Composable () -> Unit) val mosaicComposition = MosaicComposition( coroutineContext = coroutineContext + clock, onDraw = { rootNode -> - mordantTerminal.rawPrint(rendering.render(rootNode).toString()) + print(rendering.render(rootNode).toString()) }, keyEvents = keyEvents, terminalState = terminalState, diff --git a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/terminal.kt b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/terminal.kt index ce2cc104..87f99b25 100644 --- a/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/terminal.kt +++ b/mosaic-runtime/src/commonMain/kotlin/com/jakewharton/mosaic/terminal.kt @@ -3,6 +3,7 @@ package com.jakewharton.mosaic import androidx.compose.runtime.Immutable import androidx.compose.runtime.ProvidableCompositionLocal import androidx.compose.runtime.compositionLocalOf +import com.jakewharton.mosaic.ui.AnsiLevel import com.jakewharton.mosaic.ui.unit.IntSize import dev.drewhamilton.poko.Poko @@ -32,3 +33,20 @@ internal inline fun Terminal.copy( darkTheme: Boolean = this.darkTheme, size: IntSize = this.size, ) = Terminal(focused, darkTheme, size) + +internal fun detectAnsiLevel(): AnsiLevel { + if (env("NO_COLOR").orEmpty().isNotEmpty()) { + return AnsiLevel.NONE + } + val term = env("COLORTERM") ?: env("TERM") ?: "dumb" + if (term.contains("24bit", ignoreCase = true) || term.contains("truecolor", ignoreCase = true)) { + return AnsiLevel.TRUECOLOR + } + if (term.contains("256")) { + return AnsiLevel.ANSI256 + } + if (term != "dumb") { + return AnsiLevel.ANSI16 + } + return AnsiLevel.NONE +}