Skip to content

Read the terminal colour scheme on REPL startup - #59958

Merged
oscardssmith merged 1 commit into
JuliaLang:masterfrom
tecosaur:term-recolour
Jul 27, 2026
Merged

oscardssmith merged 1 commit into
JuliaLang:masterfrom
tecosaur:term-recolour

Conversation

@tecosaur

@tecosaur tecosaur commented Oct 26, 2025 •

Copy link
Copy Markdown
Member

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

This introduces light/dark theme detection, as well as your ANSI 8-colour theme. With this capability, we can avoid accidental low-contrast highlights and related issues that will only become worse as we try to extend the graphical capabilities of the REPL.

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 🙂

@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

tecosaur commented Oct 31, 2025 •

Copy link
Copy Markdown
Member Author

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

tecosaur commented Nov 2, 2025

Copy link
Copy Markdown
Member Author

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

jakobjpeters commented Nov 20, 2025 •

Copy link
Copy Markdown
Contributor

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

Yes please!

@tecosaur

tecosaur commented Jan 4, 2026

Copy link
Copy Markdown
Member Author

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

Comment thread stdlib/REPL/src/term_osc.jl Outdated
@tecosaur

tecosaur commented Feb 26, 2026 •

Copy link
Copy Markdown
Member Author

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)
mkitti pushed a commit to mkitti/julia that referenced this pull request May 22, 2026
This is prepratory work for JuliaLang#61151 and JuliaLang#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.
@tecosaur
tecosaur force-pushed the term-recolour branch 3 times, most recently from 7175f77 to ca6a67a Compare July 26, 2026 10:13
@tecosaur
tecosaur marked this pull request as ready for review July 26, 2026 10:13
Query the terminal for its foreground, background, and ANSI palette
colours on REPL startup, and apply them via StyledStrings.setcolors!,
so that displayed content adapts to the user's actual terminal theme.

The palette is queried before the foreground and background, letting the
latter's response act as a completion sentinel: terminals answer queries
in order, so once fg/bg arrive, every colour the terminal intends to
report has been seen. Terminals that answer only fg/bg still yield a
usable partial result, and those that answer nothing leave the colours
untouched. This avoids waiting on a timeout, which would misbehave over
high-latency connections and under multiplexers.

Responses are collected through the keymap, alongside the existing DA1
handling, so we don't need to block and wait.
@tecosaur
tecosaur requested a review from Keno July 26, 2026 10:22
@tecosaur

Copy link
Copy Markdown
Member Author

I think this should be good to go now

@oscardssmith
oscardssmith merged commit bcbea40 into JuliaLang:master Jul 27, 2026
9 checks passed
@tecosaur tecosaur changed the title WIP: Read the terminal colour scheme on REPL startup Read the terminal colour scheme on REPL startup Jul 27, 2026
@vtjnash

vtjnash commented Sep 26, 2026

Copy link
Copy Markdown
Member

BTW, there's also a more modern way to do this query now with CSI ? 996 n (which enables live reporting of theme changes and is also better supported in new releases of tmux – though also is implemented in fewer terminal emulators right now)

@tecosaur

Copy link
Copy Markdown
Member Author

Thanks for raising that, I'll look into it.

@tecosaur

Copy link
Copy Markdown
Member Author

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.

8 participants