rust-cli / anstyle

ANSI text styling
https://docs.rs/anstyle
Other
121 stars 20 forks source link

ANSI color is not retained across multiple `print(ln)!`'s in Windows legacy console #230

Open nooriro opened 1 week ago

nooriro commented 1 week ago

Cargo.toml

[package]
name = "anstream-test"
version = "0.1.0"
edition = "2021"

[dependencies]
anstream = "0.6.18"

src/main.rs

use anstream::{print, println};

fn main() {
    print!("\x1b[90m(print!) This text is displayed in gray.\n");
    print!("(print!) This text should also be displayed in gray.\n");
    println!("\x1b[90m(println!) This text is displayed in gray.");
    println!("(println!) This text should also be displayed in gray.\x1b[0m");
}

Steps to reproduce

Run cargo run in CMD/PowerShell with 'Legacy Console mode' enabled.

anstream-win-legacyconsole-01-241121

Expected result

All the lines should be displayed in gray, just like in the non-legacy Windows Console.

anstream-win-legacyconsole-02-241121 anstream-win-legacyconsole-03-241121

Actual result

Only the lines that output \x1b[90m are displayed in gray (1st and 3rd lines).

anstream-win-legacyconsole-04-241121

Environment

epage commented 1 week ago

Each println! call will call stdout https://github.com/rust-cli/anstyle/blob/fabe0c31e56a8f4cec5bda325dfafbe773ef6621/crates/anstream/src/_macros.rs#L130-L151

Each stdout call creates a new AutoStream https://github.com/rust-cli/anstyle/blob/fabe0c31e56a8f4cec5bda325dfafbe773ef6621/crates/anstream/src/lib.rs#L70-L73

AutoStream is stateless, forwarding on calls https://github.com/rust-cli/anstyle/blob/fabe0c31e56a8f4cec5bda325dfafbe773ef6621/crates/anstream/src/auto.rs#L256-L262

WinconStream is stateful but only within an instance and not across instances https://github.com/rust-cli/anstyle/blob/fabe0c31e56a8f4cec5bda325dfafbe773ef6621/crates/anstream/src/wincon.rs#L115-L131

The next layer down of WinconStream only deals with global for tracking the original colors of the terminal https://github.com/rust-cli/anstyle/blob/fabe0c31e56a8f4cec5bda325dfafbe773ef6621/crates/anstyle-wincon/src/stream.rs#L140-L150

During one of the redesigns, I had explored keeping global state to "remember" where we left off between instances of WinconStream I think the big problem I ran into was that the stream would be unlocked and I couldn't guarantee what happened to the output between instances. This was assuming someone was explicitly acquiring and releasing the AutoStream for complete messages. The interaction of this with println, especially with a user intentionally bleeding output across calls, was overlooked in this case.

The easy workaround is to call stdout() and write to it, especially locking it. Writing to locked output is already a best practice because of how slow individual print calls can be from acquiring and releasing the lock (Rust-wide and not just our wrappers).

As for fixing this, we'll need to think on this more of what we want to optimize for and what is the right answer.