Closed boyter closed 1 year ago
@boyter the first 16 colors in the tcell colornames and color constants are the terminal's color palette for those colors. Here are the color constants, and their associated hex value. Using any of these will cause the terminal to use the palette defined color for it. The hex value, AND the constant are usable, in case you want to use strings.
should... if it is xterm compatible
ColorBlack: 0x000000,
ColorMaroon: 0x800000,
ColorGreen: 0x008000,
ColorOlive: 0x808000,
ColorNavy: 0x000080,
ColorPurple: 0x800080,
ColorTeal: 0x008080,
ColorSilver: 0xC0C0C0,
ColorGray: 0x808080,
ColorRed: 0xFF0000,
ColorLime: 0x00FF00,
ColorYellow: 0xFFFF00,
ColorBlue: 0x0000FF,
ColorFuchsia: 0xFF00FF,
ColorAqua: 0x00FFFF,
ColorWhite: 0xFFFFFF,
This can be a bit confusing because you are using a constant or string that is NOT the actual color from their palette. To be able to find their actual colors used in the palette you will need to do some OSC escape sequences to query their term emulator.
https://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h3-Operating-System-Commands
Specifically Ps=4 and ? for the indexed colors.
Yes confusing. Actually I must confess I still am.
I guess what I would need to know, is given a tview application how do I set it up to respect the user's preferences?
I create the tview application like so https://github.com/boyter/cs/blob/master/tui.go#L250 and set colours based on the above (other than one I am going to replace), but I am not sure what else I need to do here. Also how does one refer to the colours correctly inside coloured content where you use [red]
or [white]
for example?
This is discussed in one of two questions in the FAQ: https://github.com/rivo/tview/wiki/FAQ In short, it's not possible to determine a user's color settings.
You can offer your users a choice of a few predefined themes but it will not be automatic.
@rivo @boyter
In short, it's not possible to determine a user's color settings.
I will say this is mostly incorrect.
Rather it is not possible to CONSISTENTLY and RELIABLY determine a user's color settings, because of terminal emulators supporting the xterm OSC 4 queries and tmux settings concerning allow passthrough.
See this file where I query the OSC color palette for the first 16 colors and then the OSC teens for background/select/cursor color palettes. I will not explain but you can get a gist as to what is going on. THere are issues where tmux can passthrough the esc sequences and the emulator can respond but sometimes there is issues reading stdin reliably without user input as well as using stderr to output the escape sequences while also having tview use stderr to print the TUI.
Here is a short gif of the color palette bar test, then running my app which queries the colors via OSC and outputting each with the associated ansi name.
package coolor
import (
"fmt"
"log"
"os"
"os/exec"
"os/signal"
"syscall"
// "time"
"golang.org/x/sys/unix"
"github.com/creack/pty"
"github.com/gookit/color"
"golang.org/x/term"
"github.com/digitallyserviced/coolors/coolor/colorinfo"
)
const (
tesc = "\033Ptmux;\033\033]"
tcesc = "\007\033\\"
esc = "\033]"
cesc = "\007"
)
var (
oe = ""
ce = ""
)
func EscReqColor(idx int) (string, string) {
oe = esc
ce = cesc
if os.Getenv("TMUX") != "" {
color.Yellowf("Within tmux environment... TMUX=%q\n", os.Getenv("TMUX"))
oe = tesc
ce = tcesc
return oe, ce
}
color.Yellowf("tmux not detected... SHLVL=%s\n ", os.Getenv("SHLVL"))
return oe, ce
}
const (
ioctlReadTermios = syscall.TCGETS
ioctlWriteTermios = syscall.TCSETS
)
var (
fd int
termios *unix.Termios
inputBuffer = make([]byte, 32)
)
func write(c byte) {
doLog("%c", c)
}
func get(ptmx *os.File) string {
n, _ := ptmx.Read(inputBuffer)
return string(inputBuffer[:n])
}
func size() (int, int) {
ws, _ := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
return int(ws.Col), int(ws.Row)
}
func hideCursor() {
doLog("\x1b[?25l")
}
func showCursor() {
doLog("\x1b[?25h")
}
func clear() {
fmt.Print("\x1b[2J")
}
func setCursor(x, y int) {
doLog("\x1b[%d;%dH", y, x)
}
func reset() {
showCursor()
setCursor(0, 0)
_ = unix.IoctlSetTermios(fd, ioctlWriteTermios, termios)
}
func initVT100(fd int) {
termios, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
if err != nil {
panic(err)
}
newState := *termios
newState.Lflag &^= unix.ECHO // Disable echo
newState.Lflag &^= unix.ICANON // Disable buffering
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newState); err != nil {
panic(err)
}
}
func queryOscColor(ptmx *os.File, idx int) {
s, e := oe, ce
fmt.Fprintf(ptmx, "%s%d;?%s\n", s, idx, e)
// color.Fprintf(w io.Writer, format string, a ...interface{})
// color.Bluef("Press ENTER %s to continue if necessary...", "")
// fmt.Fprintf(ptmx, "\n")
}
func queryIndexColor(ptmx *os.File, idx int) {
s, e := oe, ce
fmt.Fprintf(ptmx, "%s4;%d;?%s\n", s, idx, e)
// fmt.Fprintf(ptmx, "\n")
}
func readOscColor(ptmx *os.File, idx int) (r, g, b uint) {
var i, rr, gg, bb uint = 0, 0, 0, 0
txt := get(ptmx)
n, err := fmt.Sscanf(txt, "\x1b]%d;rgb:%02x%02x/%02x%02x/%02x%02x\x1b", &i, &r, &rr, &g, &gg, &b, &bb)
if err != nil || n != 7 {
}
return
}
func readIndexColor(ptmx *os.File, idx int) (r, g, b uint) {
var i, rr, gg, bb uint = 0, 0, 0, 0
txt := get(ptmx)
n, err := fmt.Sscanf(txt, "\x1b]4;%d;rgb:%02x%02x/%02x%02x/%02x%02x\x1b", &i, &r, &rr, &g, &gg, &b, &bb)
if err != nil || n != 7 {
}
return
}
func QueryColorScheme(n int) []Color {
initVT100(int(os.Stderr.Fd()))
clear()
EscReqColor(0)
color.SetOutput(os.Stderr)
cols := make([]Color, n+7)
ps := []int{10, 11, 12, 13, 14, 17, 19}
color.Bluef("Keep pressing ENTER until the UI launches...", "")
for i, v := range ps {
done := make(chan struct{})
go func() {
r, g, b := readOscColor(os.Stdin, v)
x := Color{float64(r) / 255.0, float64(g) / 255.0, float64(b) / 255.0}
cols[n+i] = x
Base16KeyScheme.Editables[i].SetOriginal(x.Hex())
close(done)
}()
queryOscColor(os.Stderr, v)
// go func() {
// tickk := time.NewTicker(time.Millisecond * 100)
// for {
// select {
// case <-done:
// return
// case <-tickk.C:
// fmt.Fprintf(os.Stdin, "\n")
// }
// }
// }()
<-done
}
for i := 0; i < n; i++ {
// color.Println(a ...interface{})
// color.Infof("Querying xterm indexed color #%d (%s)\n", i, colorinfo.BaseXtermAnsiColorNames[i])
done := make(chan struct{})
go func() {
r, g, b := readIndexColor(os.Stdin, i)
x := Color{float64(r) / 255.0, float64(g) / 255.0, float64(b) / 255.0}
cols[i] = x
color.Printf("Color #%d resulted with (<bg=%[2]s>%[2]s</>)\n", i, x.Hex()[1:])
Base16KeyScheme.Editables[len(ps)+i].SetOriginal(x.Hex())
close(done)
}()
color.Printf("Querying ansi color #%d (<%[2]s>%[2]s</>)\n", i, colorinfo.BaseXtermAnsiColorNames[i])
queryIndexColor(os.Stderr, i)
// go func() {
// tickk := time.NewTicker(time.Millisecond * 100)
// for {
// select {
// case <-done:
// return
// case <-tickk.C:
// fmt.Fprintf(os.Stderr, "\n")
// }
// }
// }()
<-done
}
reset()
return cols
}
func test() error {
c := exec.Command("zsh")
ptmx, err := pty.Start(c)
if err != nil {
return err
}
defer func() { _ = ptmx.Close() }() // Best effort.
ch := make(chan os.Signal, 1)
signal.Notify(ch, syscall.SIGWINCH)
go func() {
for range ch {
if err := pty.InheritSize(os.Stdin, ptmx); err != nil {
log.Printf("error resizing pty: %s", err)
}
}
}()
ch <- syscall.SIGWINCH // Initial resize.
defer func() { signal.Stop(ch); close(ch) }() // Cleanup signals when done.
oldState, err := term.MakeRaw(int(ptmx.Fd()))
if err != nil {
panic(err)
}
defer func() { _ = term.Restore(int(ptmx.Fd()), oldState) }() // Best effort.
return nil
}
Yes confusing. Actually I must confess I still am.
I am going to try and explain the terminal xterm/ansi coloring schemes/palettes here to hopefully clear things up.
Xterm began to allow color schemes to be set for user terminal emulators. However how do you rewrite the colors for things when they are already outputting a specific color index/name/HEX value?
Well that is why those simple FF0000 (red) colors are rewritten to the user's defined palette.
The xterm/ansi 16 indexed colors (base16) are the indexed colors 0-15, 8 regular, 8 bright/dim. from black to white.
The base16 color schemes you see usually only change these first 16 colors, because again, they are the most commonly set/used colors when coloring items in the terminal because back in the day you did not define an rgb/css hex value for colors... You specified the color index from the palette that was available.
The color derived from indexed palettes were originally 2, then 8, then 16/8 (dims), then 16 anything, then some 220 color gradient and then another 30 or so greys (256). Now all of these colors can be set specifically to any rgb value, as well as the OSC escapes allowing you to set even specific HSL/RGB values.
Basically you are working within a system that has had to work from legacy/older ways of doing things while also adding features for newer systems while not fucking up the old stuff.
Below you can see how I separated the colors hex strings, and then the actual names.
// strict/hard base ansi colors
var BaseXterm = []string{
"#000000",
"#800000",
"#008000",
"#808000",
"#000080",
"#800080",
"#008080",
"#707070",
}
var BaseBrightXterm = []string{
"#A9A9A9",
"#FF0000",
"#00FF00",
"#FFFF00",
"#0000FF",
"#FF00FF",
"#00FFFF",
"#FFFFFF",
}
// base16 xterm color names
var BaseXtermAnsiColorNames = []string{
"black", // 0
"maroon",
"green",
"olive",
"navy",
"purple",
"teal",
"silver", // 7
"gray", // 0 bright
"red",
"lime",
"yellow",
"blue",
"fuchsia",
"aqua",
"white", // 7 bright
}
I'll get @gdamore from tcell
in the loop here as this is really a tcell
topic and not tview
. If this is ever supported at some point by tcell
, it will be supported by tview
as well.
Cool. I don't mind putting in the work to support this myself, or indeed adding themes to have it mostly work across the board but ideally something that is applied across everything would be better, even if its just an example to follow from that perhaps we can setup for people to follow should they want to do this.
I'm closing this issue now. If there is anything left to discuss regarding this topic, please open an issue with tcell
. As soon as tcell
supports this, I can implement it in tview
, too. In short, tview
does not interact with the terminal directly so any solution to this tcell
's responsibility to implement.
tcell does respect colors, if you ask it to. However, if you use the low order color names, or convert the color to a palette color (via the PaletteColor function), then it will respect user preferences set up in the terminal.
So the application developer has complete control to decide whether to strictly enforce and control color (via RGB colors) or to use palette colors.
The default for the first 16 colors (actually the first 256 colors) is to respect the palette of the user.
The first 16 colors in tcell are:
ColorBlack = ColorValid + iota
ColorMaroon
ColorGreen
ColorOlive
ColorNavy
ColorPurple
ColorTeal
ColorSilver
ColorGray
ColorRed
ColorLime
ColorYellow
ColorBlue
ColorFuchsia
ColorAqua
ColorWhite
And they have values:
ColorBlack: 0x000000,
ColorMaroon: 0x800000,
ColorGreen: 0x008000,
ColorOlive: 0x808000,
ColorNavy: 0x000080,
ColorPurple: 0x800080,
ColorTeal: 0x008080,
ColorSilver: 0xC0C0C0,
ColorGray: 0x808080,
ColorRed: 0xFF0000,
ColorLime: 0x00FF00,
ColorYellow: 0xFFFF00,
ColorBlue: 0x0000FF,
ColorFuchsia: 0xFF00FF,
ColorAqua: 0x00FFFF,
ColorWhite: 0xFFFFFF,
Note that these values may be different than what you've chosen, as some actual terminals represent these somewhat differently. But if you want to use the palette/theme of the user, then stick to these color values.
I just had a look at this in iTerm on macOS. The colour preferences have two sections:
If I change the "ANSI Colors" on the right hand side, the tview
application will automatically change accordingly. On the other hand, if I make changes on the left hand side for "Basic Colors", it has no effect. (I don't know what these "basic colours" refer to in the context of a terminal.)
The "Color Presets..." dialog opens up a number of predefined themes from which I can choose one. Some of them change the ANSI colour palette, others don't.
So I guess it's all really a matter of how a terminal colour theme is defined. If "Light Mode" also changes the ANSI palette, tview
should look as expected.
I have found a way.
I've discovered a neat little trick.
By inserting the following code before tview.NewApplication()
, you can ensure that all the fields in tview are set to your terminal's default color scheme.
theme := tview.Theme{
PrimitiveBackgroundColor: tcell.ColorDefault,
ContrastBackgroundColor: tcell.ColorDefault,
MoreContrastBackgroundColor: tcell.ColorDefault,
BorderColor: tcell.ColorDefault,
TitleColor: tcell.ColorDefault,
GraphicsColor: tcell.ColorDefault,
PrimaryTextColor: tcell.ColorDefault,
SecondaryTextColor: tcell.ColorDefault,
TertiaryTextColor: tcell.ColorDefault,
InverseTextColor: tcell.ColorDefault,
ContrastSecondaryTextColor: tcell.ColorDefault,
}
tview.Styles = theme
To start, this is a fantastic library and I love it to bits. Thank you so much for having created it.
I had a look through issues, the documentation and a few other places but was unable to find an answer to this.
Is it possible to have tview respect someones existing terminal colors and use those in the display?
I am running into the issue where its in "dark" mode where the persons terminal has a white background. Its a similar question for colors for display and such.
I had a look at https://github.com/gdamore/tcell and it looks like that might support it, but I don't have enough knowledge over it to say one way or the other.