Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

incorrect colors when using nested styling #45

Open
LordMZTE opened this issue Jan 6, 2022 · 1 comment
Open

incorrect colors when using nested styling #45

LordMZTE opened this issue Jan 6, 2022 · 1 comment

Comments

@LordMZTE
Copy link
Contributor

LordMZTE commented Jan 6, 2022

When using multiple nested colored strings, like this:

use owo_colors::{AnsiColors, OwoColorize};

fn main() {
    println!(
        "{}",
        format!("TEST {} TEST", "TEST".color(AnsiColors::Red)).color(AnsiColors::Green)
    );
}

the color ends up getting reset in the middle of the string.

This means that the this is going to print test strings of these colors: green, red, white. But in this example, I'd expect green, red, green.

I'm assuming this is happening because the coloring doesn't take into account the reset escape sequence in the inner string.

@jam1garner
Copy link
Owner

Apologies for taking so long to respond, but this is a hard issue to muster up the desire to respond as I don't think the answer is one people would enjoy, and I don't like telling people that I can't think of a way to have our cake and eat it too. Let me explain.

I'd be curious at people's thoughts about how this should be handled. Basically the only way to actually do this is to either:

  1. have a symmetric formatting scheme (this isn't a thing in ANSI escape sequences, so not an option)
  2. parse ANSI sequences out of strings before formatting them (hard to do without either allocations or improper ANSI handling)
  3. a compile-time understanding of formatting operations

As I mentioned option 1 isn't a thing because ANSI escape sequences are more like commands to evaluate as you go along rather than a format to parse and use for styling info. An example of symmetry would be HTML: <b>this is bold <b> this is also bold</b> this is still bold</b>. While ANSI escape sequences are closer to telling a printer to switch its ink color before continuing (context-less).

owo-colors actually does its best to support the cases that don't hit this issue, such as if you try and nest a foreground color inside of a background color:

        println!(
            "{}",
            format!("test test {} test test", "blue".blue()).on_red()
        );

Now as for option 2... this is a tough one. This is actually what colored does except it has corner cases it doesn't handle as well. It also is not robust enough to handle efficiencies in owo-colors' escape sequence emission scheme due to the fact it only attempts to parse the "clear all styles" escape sequence, while owo-colors actually puts effort into using more efficient clear sequences such as "clear just the foreground color". This means not only would owo-colors need the parsing code of colored (which imo is already unacceptable, both in its allocations and in the fact the logic isn't robust. This is not to disparage their work but they are making a tradeoff that I'd like to avoid making) but on top of that, owo-colors would need to parse more escape sequences, put effort into making it allocation-free and then also. Needless to say this is a lot of effort for a feature that is far from zero-cost (as it'd mean every formatting operation would involve stack-buffering all text as it is formatted and then parsing out escape sequences in a streaming manner, yikes!).

Now option number 3: some means of compile-time understanding of formatting. This one aligns much closer to owo-colors' design but is fundamentally at odds with Rust's formatting scheme. Since Rust's formatting is designed to be a non-leaky abstraction with no context, there's no way for us to actually inform the inner colors to switch back to the top-level color rather than clearing the color. However even this has sharp edges, as it'd require users actually use a recursive formatting scheme (eg format_args!() instead of format!() in your above example) so it'd still have footguns.

There's also technically secret option 4: providing my own formatting scheme that is context-aware and thus can handle this. I am not sure how I would personally feel about this. I've genuinely considered this, but the idea is basically: make a println/format/writeln/etc replacement proc-macro crate that uses owo-colors only at compile-time in order to bake in the colors.

I'd imagine it'd look something like this:

use owo_fmt::*;

println!("{+green}TEST {+red}TEST{-red} TEST{-green}");

(syntax could use some work, sorry)

That way since the actual format string has the context of what's starting and what's ending, it could properly nest things at compile-time rather than finding a way to cheat Rust's formatting mechanism or parse ANSI escape sequences at runtime.

If anyone else has any thoughts though I'd be interested in hearing ideas.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants