How can I create a fancier REPL prompt with styled strings? (was: Make `Base.textwidth` ignore ANSI sequences)

I’m trying to spice up my Julia REPL prompt with ANSI color codes (not just one single color, but multiple colors in the same line). However, it messes up the cursor alignment, because the code in REPL.jl also counts the ANSI control sequences when it computes the length of the prompt. AFAICT, the “culprit” is Base.textwidth:

julia> print("\e[4munderlined\e[0m")
underlined

julia> textwidth("\e[4munderlined\e[0m")
16

16 is too much, it should return 11.

I was wondering if there was an easy workaround for this.

Are you maybe looking for

The \e is already ignored, since it’s invisible, and this could arguably be considered a bug, to not ignore what follows and is interpreted for it, and then get 10. Not too important since (assume a similar feature for underlining):

julia> textwidth(styled"{yellow:hello} {blue:there}")
11
1 Like

You could strip out the ANSI escapes (which are not part of Unicode) before calling textwidth, e.g. Strange characters in windows terminal - #4 by digital_carver

But probably better to use StyledStrings

Yeah, StyledStrings could be the way to go, up from v1.11… I guess I’ll just have to wait (“mama said”).

Nah, this whole thing is deep inside the REPL.jl library, I don’t want to mess with that.

See the package readme:

This is a standard library as of Julia 1.11. A version compatible with Julia 1.0 through to 1.10 has also been registered in General — allowing StyledStrings to be used anywhere without compromising compatibility.

1 Like

Well, StyledString.jl might be backported to earlier Julia versions, but the support is limited. First and foremost, the REPL prompt doesn’t expect AnnotatedStrings, and it throws a typeasset.

TypeError: in typeassert, expected String, got a value of type StyledStrings.AnnotatedStrings.AnnotatedString{String}
    Stacktrace:
      [1] write_prompt(terminal::IO, s::Union{AbstractString, Function}, color::Bool)
        @ REPL.LineEdit ~/.local/share/mise/installs/julia/1.10.5/share/julia/stdlib/v1.10/REPL/src/LineEdit.jl:1525

(On a related note, in Julia 1.10, concatenating StyledStrings seems to be a PITA, regular join and * return plain Strings. The documentation says otherwise. I also can’t get it to load my faces.toml file on startup. Odd.)

join and * were originally supported, but had to be reluctantly dropped due to a huge latency cost (tons of invalidations). They’ll have to be 1.11+.

Documentation needs to be updated.

Loading faces.toml on startup should work? Things to try:

  • Set a custom face in your faces.toml
  • Verify that your faces.toml is located at joinpath(first(DEPOT_PATH), "config", "faces.toml")
  • See if it’s loaded after using StyledStrings by trying something like styled"{my_custom_face:test test}".

Thanks for the heads up, good to know.

I have a very simple ~/.julia/config/faces.toml:

[my_custom_face]
background = '#444444'
foreground = '#949494'

Then:

julia> using StyledStrings

julia> styled"{my_custom_face:test test}"
"test test" # this is not visibly styled

julia> StyledStrings.load_customisations!(force=true)

julia> styled"{my_custom_face:test test}"
"test test" # this is visibly styled

image

This happens both with 1.10.5 and 1.11.0-rc4. Using --startup-file=no doesn’t help, either, so its not my local setup. But we are digressing, perhaps this could be continued in an issue reported to the StyledStrings.jl repo.

Ah, I suspect this could be from an issue resulting from precompiling that I recently fixed.

2 Likes

I tried this on Julia v1.11-rc4. Sadly, it seems that this issue, i.e., that the REPL prompt doesn’t accept AnnotatedStrings, holds for v1.11 as well, which makes such strings unusable in the prompt:

Unhandled Task ERROR: TypeError: in typeassert, expected String, got a value of type Base.AnnotatedString{String}
Stacktrace:
  [1] write_prompt(terminal::IO, s::Union{AbstractString, Function}, color::Bool)
    @ REPL.LineEdit ~/.local/share/mise/installs/julia/1.11.0-rc4/share/julia/stdlib/v1.11/REPL/src/LineEdit.jl:1618

It seems that the type spec on line 1618 is too strong:

    promptstr = prompt_string(s)::String

Too bad. Can I hope that this gets fixed before the release of v1.11?

1.11 is out now, so that’s not happening unfortunately. @caleb-allen did spin up a PR a while back looking at this: Use `StyledStrings` for REPL prompt styling by caleb-allen · Pull Request #51887 · JuliaLang/julia · GitHub, but I think we’ve both been nerd-sniped since then by the attraction that is the prospect of nicer REPL code.

The fix I mentioned earlier will have to come in 1.11.1, since StyledStrings wasn’t bumped between rc4 and 1.11.0, unfortunately. It’s been suggested to me I should be more willing to merge my own PRs about AnnotatedStrings/StyledStrings, but it feels dodgy to me :sweat_smile:.

2 Likes

May have some time to revisit that PR next week, would love to follow through on that

1 Like