ajalt / mordant

Multiplatform text styling for Kotlin command-line applications
https://ajalt.github.io/mordant/
Apache License 2.0
935 stars 33 forks source link

Could TerminalDetection be made public? #171

Open mikehearn opened 2 months ago

mikehearn commented 2 months ago

I have an app where for various reasons it's convenient to call println() from code that doesn't know about Mordant (and shouldn't). To make this work I can redirect stdout so System.out goes to a PrintStream that prints via Mordant, and Mordant prints via the original stdout stream. Unfortunately this is awkward in the current API, because Terminal requires TerminalInterface requires TerminalInfo, but the only public way to get this object is .... Terminal. So you have to create Terminal twice. If TerminalDetection were public, this could be avoided.

Alternatively, it'd be good if Mordant could do this interception itself.

ajalt commented 2 months ago

I'm not quite following the description, could you give a short code example of what you're trying to do?

mikehearn commented 2 months ago

I'm trying to simplify the process of doing progress tracking (on the JVM for now, but possibly in future for KMP libraries too).

        fun get(): ProgressReport.Tracker {
            if (globalTracker != null)
                return globalTracker!!

            // Set up Mordant. We need to customize the interface to break the loop that would otherwise occur when we override stdout.
            // And we need to create Terminal twice, because TerminalDetection is internal.
            val tmp = Terminal()
            val redirectInterface = RedirectingTerminalInterface(System.out, System.err, tmp.info)
            val terminal = Terminal(terminalInterface = redirectInterface)

            // Ensure printing to stdout still works and pushes the messages _above_ the animation.
            System.setOut(PrintStreamWrapper(terminal, System.out))

The problem here is that to redirect stdout I need a Terminal with a custom TerminalInterface, but to implement TerminalInterface I need a TerminalInfo, but to get a TerminalInfo I need a Terminal. So there's a loop.

ajalt commented 2 months ago

I see, so your RedirectingTerminalInterface sends the terminal output to the original stdout, then your PrintStreamWrapper sends System.out to the terminal, and you do that so you can use System.out.println etc. while and animation is running.

The only thing the Terminal constructor does is call TerminalDetection, so there isn't actually any problem with creating two terminals like you're doing, but I agree that it looks odd and that I should add a batter way to do that.