TerminalCapabilities.kt

package com.depanalyzer.tui

import com.github.ajalt.mordant.rendering.AnsiLevel
import java.nio.charset.Charset

data class TerminalCapabilities(
    val ansiLevel: AnsiLevel,
    val isTty: Boolean,
    val isCi: Boolean,
    val supportsInteractiveTui: Boolean,
    val supportsUnicodeGlyphs: Boolean
)

class TerminalCapabilitiesDetector(
    private val envProvider: (String) -> String? = { System.getenv(it) },
    private val hasConsole: () -> Boolean = { System.console() != null },
    private val consoleCharsetProvider: () -> Charset? = { System.console()?.charset() },
    private val osNameProvider: () -> String = { System.getProperty("os.name").orEmpty() }
) {
    fun detect(noColor: Boolean = false): TerminalCapabilities {
        val isTty = hasConsole()
        val isCi = isCiEnvironment()
        val noColorEnv = envProvider("NO_COLOR") != null
        val dumbTerm = envProvider("TERM").equals("dumb", ignoreCase = true)

        val ansiLevel = if (noColor || noColorEnv || dumbTerm || isCi || !isTty) {
            AnsiLevel.NONE
        } else {
            AnsiLevel.TRUECOLOR
        }

        return TerminalCapabilities(
            ansiLevel = ansiLevel,
            isTty = isTty,
            isCi = isCi,
            supportsInteractiveTui = isTty && !isCi,
            supportsUnicodeGlyphs = supportsUnicodeGlyphs(isTty, isCi)
        )
    }

    private fun supportsUnicodeGlyphs(isTty: Boolean, isCi: Boolean): Boolean {
        val forced = envProvider("DEPANALYZER_TUI_UNICODE")?.trim()?.lowercase()
        if (forced in setOf("1", "true", "yes", "on")) return true
        if (forced in setOf("0", "false", "no", "off")) return false
        if (!isTty || isCi) return false

        val osName = osNameProvider()
        if (!osName.contains("windows", ignoreCase = true)) {
            return true
        }

        val charset = consoleCharsetProvider()?.name().orEmpty()
        return charset.equals("UTF-8", ignoreCase = true) || charset.equals("UTF8", ignoreCase = true)
    }

    private fun isCiEnvironment(): Boolean {
        val ci = envProvider("CI")
        if (ci != null && ci != "0" && !ci.equals("false", ignoreCase = true)) {
            return true
        }

        if (envProvider("GITHUB_ACTIONS").equals("true", ignoreCase = true)) {
            return true
        }

        return envProvider("JENKINS_URL") != null || envProvider("BUILD_NUMBER") != null
    }
}