Skip to content

WIP: Read the terminal colour scheme on REPL startup#59958

Draft
tecosaur wants to merge 1 commit intoJuliaLang:masterfrom
tecosaur:term-recolour
Draft

WIP: Read the terminal colour scheme on REPL startup#59958
tecosaur wants to merge 1 commit intoJuliaLang:masterfrom
tecosaur:term-recolour

Conversation

@tecosaur
Copy link
Copy Markdown
Member

@tecosaur tecosaur commented Oct 26, 2025

Wouldn't it be nice if Julia/the REPL knew what your terminal colours were, and displayed content appropriately?

useful description coming soon-ish

Sneak peek:

image

Query durations

  • initial query on a local machine: ~0.1s (which I suspect is due to other work that gets scheduled before the response is read)
  • subsequent queries on a local machine: <0.01s
  • initial query over SSH (100ms latency): ~0.3s

Important

This is asynchronous, and does not impact startup latency

Tested terminals

Test script
using StyledStrings
let (; FACES, SimpleColor, rgbcolor) = StyledStrings  # Grab some private API
    colors = Iterators.flatten((c, Symbol("bright_" * String(c))) for c in
        [:black, :green, :cyan, :blue, :magenta, :red, :yellow, :white])
    println(styled"{bold:    Swatch test  Read  Actual}\n ", ''^30)
    termfg, termbg = @. SimpleColor(rgbcolor((:foreground, :background)))
    println(styled"     foreground  {(bg=$termfg):  }    {inverse:  }")
    println(styled"     background {light,grey:▕{(bg=$termbg):  }▏  ▕{:  }▏}")
    for color in colors
        termc = SimpleColor(FACES.basecolors[color])
        println(styled" $(lpad(color, 14))  {(bg=$termc):  }    {(bg=$color):  }")
    end
    print(styled"\n {bold:Platform:} {emphasis:$(Sys.KERNEL){grey:/}$(ENV[\"TERM\"])} {grey:(truecolor }")
    println(if Base.ttyhastruecolor() styled"{green,bold:✓}" else styled"{red,bold:✕}" end, styled"{grey:)}")
    println(styled" Do the swatches match?")
end

Everything works

  • Linux
    • Kitty
    • Wezterm
    • Foot
    • Ghostty
    • Xterm
    • Alacritty
    • St (Suckless)
    • Ubuntu terminal (Gnome terminal?)
    • Tilix
  • Mac
    • Terminal.app
    • iTerm2
    • WezTerm
    • Alacritty
    • Ghostty
    • Kitty
  • Windows
    • Windows terminal (it messes up inverse video though)
    • Windows terminal: WSL

Misbehaving (terminal's fault)

  • Linux
    • Konsole (lies about values)
  • Emacs
    • Eat (doesn't supply colours)
    • Vterm (doesn't report anything)
  • Mac
    • iTerm2 + Tmux (doesn't report anything)
  • Windows
    • Wezterm via Ubuntu WSL (doesn't report anything)

Misbehaving (our fault)

  • Nothing yet 🙂

This is not ready for review yet, I just want CI to build it so that helpful testers on Zulip (:wave:) can try this with juliaup add +pr59958.

@tecosaur tecosaur added REPL Julia's REPL (Read Eval Print Loop) display and printing Aesthetics and correctness of printed representations of objects. labels Oct 26, 2025
@tecosaur tecosaur force-pushed the term-recolour branch 3 times, most recently from 5633335 to 7e22584 Compare October 27, 2025 15:29
@tecosaur tecosaur force-pushed the term-recolour branch 3 times, most recently from 80b448e to 0c05f5b Compare October 29, 2025 19:12
@topolarity
Copy link
Copy Markdown
Member

iTerm2 + Tmux (doesn't report anything)

Any issue that we can file upstream? This is a fairly common configuration, esp. since iTerm2 has tmux integration

@tecosaur
Copy link
Copy Markdown
Member Author

tecosaur commented Oct 31, 2025

Any issue that we can file upstream? This is a fairly common configuration, esp. since iTerm2 has tmux integration

Potentially, there seem to be a few closed OSC-related issues on tmux's repo. I'm also not 100% on the performance: it might be passing through fg/bg info, in which case as of some recent (unpushed) improvements we will still be able to set pick light/dark appropriately and set fg/bg colours.

Testing tmux locally myself, I see that it does pass through OSC 10 and OSC 11 codes (these are the fg/bg codes):

~$ printf '\e]10;?\e\\'
10;rgb:bbbb/c2c2/cfcf

~$ printf '\e]11;?\e\\'
11;rgb:2424/2727/3030

~$ printf '\e]4;1;?\e\\'

~$

@tecosaur
Copy link
Copy Markdown
Member Author

tecosaur commented Nov 2, 2025

I'm not sure what's a good idea to do here in terms of unit testing, since this should be tested against a collection of different terminal emulators.

@jakobjpeters
Copy link
Copy Markdown
Contributor

jakobjpeters commented Nov 20, 2025

Wouldn't it be nice if Julia/the REPL knew what your terminal colours were, and displayed content appropriately?

Yes please!

@tecosaur
Copy link
Copy Markdown
Member Author

tecosaur commented Jan 4, 2026

I now plan on getting this merged after #60527, and have adjusted the PR accordingly.

end

const PENDING_RESPONSES = OSCResponse[]
const RESPONSE_POLLERS = Tuple{Function, Timer}[]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this architecture need to be so complicated with the pollers and the timeouts, why not just set the colors when the response comes in whatever time that might be.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because we want to set the colours all at once after 16 responses have come in, but some terminals only partially respond (and so we'd be waiting forever without a timeout).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why isn't that an integer that just counts how many you got and runs the function when that integer hits 16.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because then we'd never apply the partial response (foreground + background only), which is what we rely on the timeout for.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So two integers then. I really don't see the point of the timeout.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How do we know if we should keep on waiting without a timeout?

@tecosaur
Copy link
Copy Markdown
Member Author

tecosaur commented Feb 26, 2026

Given #61151 and and (my) interest in supporting KKP eventually, it would probably be good to support non-OSC queries too, specifically primary device attributes (DA1: CSI c) and current progressive enhancement status (CSI ? u).

As it is, OSC support lets us query more than just colour: such as clipboard contents (when possible), and OSC 99 support.

Keno added a commit that referenced this pull request Feb 26, 2026
This is prepratory work for #61151 and #59958. The key problem is
that when we issue informational queries to the terminal emulator,
we don't really have any way of knowing when they'll come back.
There could be arbitrary amount of actual user input before or after.
Thus, any code that would attempt to read from the terminal inline
to read OSC responses faces the problem of accidentally getting user
input and likewise, the REPL may see delayed OSC responses that are
not inteded for it. To avoid all that complication, just put the
REPL in charge of OSC response processing. The idea here is that
at some point something issues the OSC query (possibly at startup)
and whenever the response comes in, the REPL will just process it
as if it was user input. This PR intentionally does not define
a query interface, which is delagated to the follow on PRs that
actually make use of this information.

Example:
```
julia> write(stdout, "\e[c")
3

julia> 52 in Base.active_repl.mistate.terminal_properties.da1
true
```
Keno added a commit that referenced this pull request Feb 26, 2026
This is prepratory work for #61151 and #59958. The key problem is
that when we issue informational queries to the terminal emulator,
we don't really have any way of knowing when they'll come back.
There could be arbitrary amount of actual user input before or after.
Thus, any code that would attempt to read from the terminal inline
to read OSC responses faces the problem of accidentally getting user
input and likewise, the REPL may see delayed OSC responses that are
not inteded for it. To avoid all that complication, just put the
REPL in charge of OSC response processing. The idea here is that
at some point something issues the OSC query (possibly at startup)
and whenever the response comes in, the REPL will just process it
as if it was user input. This PR intentionally does not define
a query interface, which is delagated to the follow on PRs that
actually make use of this information.

Example:
```
julia> write(stdout, "\e[c")
3

julia> 52 in Base.active_repl.mistate.terminal_properties.da1
true
```
Keno added a commit that referenced this pull request Feb 27, 2026
This is prepratory work for #61151 and #59958. The key problem is
that when we issue informational queries to the terminal emulator,
we don't really have any way of knowing when they'll come back.
There could be arbitrary amount of actual user input before or after.
Thus, any code that would attempt to read from the terminal inline
to read OSC responses faces the problem of accidentally getting user
input and likewise, the REPL may see delayed OSC responses that are
not inteded for it. To avoid all that complication, just put the
REPL in charge of OSC response processing. The idea here is that
at some point something issues the OSC query (possibly at startup)
and whenever the response comes in, the REPL will just process it
as if it was user input. This PR intentionally does not define
a query interface, which is delagated to the follow on PRs that
actually make use of this information.

Example:
```
julia> write(stdout, "\e[c")
3

julia> 52 in Base.active_repl.mistate.terminal_properties.da1
true
```
Keno added a commit that referenced this pull request Feb 28, 2026
This is prepratory work for #61151 (and could be extended in #59958).
The key problem is that when we issue informational queries to the terminal emulator,
we don't really have any way of knowing when they'll come back.
There could be arbitrary amount of actual user input before or after.
Thus, any code that would attempt to read from the terminal inline
to read OSC responses faces the problem of accidentally getting user
input and likewise, the REPL may see delayed OSC responses that are
not inteded for it. To avoid all that complication, just put the
REPL in charge of OSC response processing. The idea here is that
at some point something issues the OSC query (possibly at startup)
and whenever the response comes in, the REPL will just process it
as if it was user input. This PR intentionally does not define
a query interface, which is delagated to the follow on PRs that
actually make use of this information.

Example:
```
julia> write(stdout, "\e[c")
3

julia> 52 in Base.active_repl.mistate.terminal_properties.da1
true
```
Keno added a commit that referenced this pull request Mar 2, 2026
This is prepratory work for #61151 (and could be extended in #59958).
The key problem is that when we issue informational queries to the terminal emulator,
we don't really have any way of knowing when they'll come back.
There could be arbitrary amount of actual user input before or after.
Thus, any code that would attempt to read from the terminal inline
to read OSC responses faces the problem of accidentally getting user
input and likewise, the REPL may see delayed OSC responses that are
not inteded for it. To avoid all that complication, just put the
REPL in charge of OSC response processing. The idea here is that
at some point something issues the OSC query (possibly at startup)
and whenever the response comes in, the REPL will just process it
as if it was user input. This PR intentionally does not define
a query interface, which is delagated to the follow on PRs that
actually make use of this information.

Example:
```
julia> write(stdout, "\e[c")
3

julia> 52 in Base.active_repl.mistate.terminal_properties.da1
true
```
Keno added a commit that referenced this pull request Mar 2, 2026
This is prepratory work for #61151 and #59958. The key problem is that
when we issue informational queries to the terminal emulator, we don't
really have any way of knowing when they'll come back. There could be
arbitrary amount of actual user input before or after. Thus, any code
that would attempt to read from the terminal inline to read OSC
responses faces the problem of accidentally getting user input and
likewise, the REPL may see delayed OSC responses that are not inteded
for it. To avoid all that complication, just put the REPL in charge of
OSC response processing. The idea here is that at some point something
issues the OSC query (possibly at startup) and whenever the response
comes in, the REPL will just process it as if it was user input. This PR
intentionally does not define a query interface, which is delagated to
the follow on PRs that actually make use of this information.

Example:
```
julia> write(stdout, "\e[c")
3

julia> 52 in Base.active_repl.mistate.terminal_properties.da1
true
```

Written by Claude.
KristofferC pushed a commit that referenced this pull request Mar 5, 2026
This is prepratory work for #61151 and #59958. The key problem is that
when we issue informational queries to the terminal emulator, we don't
really have any way of knowing when they'll come back. There could be
arbitrary amount of actual user input before or after. Thus, any code
that would attempt to read from the terminal inline to read OSC
responses faces the problem of accidentally getting user input and
likewise, the REPL may see delayed OSC responses that are not inteded
for it. To avoid all that complication, just put the REPL in charge of
OSC response processing. The idea here is that at some point something
issues the OSC query (possibly at startup) and whenever the response
comes in, the REPL will just process it as if it was user input. This PR
intentionally does not define a query interface, which is delagated to
the follow on PRs that actually make use of this information.

Example:
```
julia> write(stdout, "\e[c")
3

julia> 52 in Base.active_repl.mistate.terminal_properties.da1
true
```

Written by Claude.

(cherry picked from commit 331421e)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

display and printing Aesthetics and correctness of printed representations of objects. REPL Julia's REPL (Read Eval Print Loop)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants