Open arshcaria opened 1 year ago
Do you mean a single clink color theme that works with both light and dark backgrounds?
It's only possible by completely avoiding the terminal theme colors for anything at all (CSI SGR params 0,30-37,39,40-47,49,90-97,100-107) and instead using fixed 8-bit or 24-bit color codes.
Because everyone who is making light themes is making them in such a way that "bright/bold" is still bright in light themes, which produces opposite of the intended effect. Similar problems in how they do the 4 gray scale colors, and the default fore/back colors.
I'm thinking about what can potentially be done in Clink to compensate. But terminal usage in general isn't really seamlessly compatible flipping between light/dark backgrounds without reconfiguring all customized colors individually. Even if Clink completely overrides the terminal theme, that will only compensate in Clink. Any other console mode programs that use CSI 1 m
for example will still produce wrong output.
The problem is broader than just Clink.
Same as #256.
I'm going to change the default colors in the "use enhanced defaults" case, so that all colors have sufficient contrast to be readable in both Light and Dark terminal color themes.
However, there's no way for programs to reliably detect whether a terminal is using a Light or Dark color theme. In fact, when a program uses the console APIs to ask Windows Terminal about the color theme, Windows Terminal always reports that the colors are the original default OS black background color theme. There's no way to find out whether Windows Terminal is using a Light theme. There's also no way to find out whether it's using Acrylic background, or what background opacity is used, or what the "effective" background color is at any particular location in the terminal window.
So, there's no way for Clink to automatically update its colors on the fly for Light or Dark themes. So, the closest I can do is adjust the default colors to have sufficient contrast to be readable in both Light and Dark themes.
However, there are two caveats:
color.input
darker and more ugly.color.suggestion
will be hard to tell apart from other dark-colored text.Sorry, this is just a fact of life when using terminals. For output to be fully readable in both Light and Dark themes then it's necessary to either not use colors, or to customize the colors specifically to fit the terminal theme you're using. All shells have similar problems with color customizations, including PowerShell and fish
and bash
and zsh
and etc.
P.S. I recognize that it would be handy to be able to do something simple to select different Clink color settings for different terminal windows. I'm exploring options for that. I have a bunch of half-baked ideas, but I'm not sure yet what will really be possible or feasible (especially since it's impossible for it to be automatic).
I cannot fix this properly in Clink until https://github.com/microsoft/terminal/issues/10639 is fixed.
However, once that's fixed then it becomes possible to make the color.suggestion
setting work properly in both Light and Dark terminal themes (and also color.input
).
One way to alleviate this problem would be a command to show me all the current color settings in their own colors. I'm using a light theme and sometimes the input color makes things illegible. It depends on which color is being used for the stuff I'm typing at the moment.
To fix this right now, I have to figure out which setting I need to change and then fix it. The trouble is, there are many color settings at play and I keep running into "bad" ones as I'm trying to get work done.
I'd like to be able to see all the color settings in their own colors so that I can fix any problem ones while I still have their goofy syntax in my head. As it stands, I never know if I have fixed all the settings that are causing me trouble.
One way to alleviate this problem would be a command to show me all the current color settings in their own colors.
@bruceoberg I think you mean something like the sample script below, which creates a pseudo-command "CLINK_COLOR_TEST
" which can be typed at the command line.
I have some thoughts about some kind of "color themes" kind of thing for Clink. It's complicated because some colors are specified in the Readline init file (.inputrc
file), some are specified in the %LS_COLORS%
, and some are specified in Clink settings. There isn't a good way to have a single fancy wizard tool to aid in configuring colors. And it's hard to have a "theme file" format that could be compatible with all three (but it might be able to supersede them ... but then clink set
gets super problematic).
It would be nice to have a wizard that shows each color setting, plus in-context samples for common ones, a way to modify/pick colors, and a way to save/load themes.
goofy syntax
Do you mean that "bright write on blue" feels like goofy syntax?
Do you mean that terminal escape codes are goofy?
Do you mean the %LS_COLORS%
syntax from Unix/Linux is goofy?
Do you mean something else?
-- This installs a pseudo-command "CLINK_COLOR_TEST", which prints a color
-- sample for each Clink color setting. Each sample is shown with the
-- background color initially set to default, and initially set to white.
if not clink.onfilterinput then
print("color_test.lua requires a newer version of Clink; please upgrade.")
return
end
local function get_clink()
local clink_alias = os.getalias('clink')
if not clink_alias or clink_alias == '' then
return ''
end
return clink_alias:gsub(' $[*]', '')
end
local function maybe_prefix(color)
color = ";"..(color or "")..";"
color = color:gsub("^;0;", ";")
if not color:find(";[34]8;") then
color = color:gsub(";[34]9;", ";")
end
color = color:match("^;*(.-);*$")
if color ~= "" then
color = ";"..color
end
return color
end
local function show_color_sample(name)
local color = settings.get(name)
local sgrBlack = "\x1b[0;48;5;0" .. maybe_prefix(color) .. "m"
local sgrWhite = "\x1b[0;48;5;15" .. maybe_prefix(color) .. "m"
local sgrDefault = "\x1b[0" .. maybe_prefix(color) .. "m"
local sample = string.format("%s Abc %s xyZ \x1b[m %s gTi \x1b[m", sgrBlack, sgrWhite, sgrDefault)
clink.print(string.format("%32s %s %s", name, sample, color))
end
clink.onfilterinput(function (line)
if line == "CLINK_COLOR_TEST" then
print("\nCLINK COLOR SAMPLES:")
print(string.rep(" ", 32) .. " \x1b[4mBlack\x1b[m \x1b[4mWhite\x1b[m \x1b[4mDefault\x1b[m")
local alias = get_clink()
local r = io.popen(alias.." set --list")
for name in r:lines() do
if name:match("^color.") then
show_color_sample(name)
end
end
r:close()
print()
return ""
end
end)
@bruceoberg For the next update, I've added color samples into the clink set
completions. E.g. typing clink set color
Alt-= will list matching completions, and the description for each color setting includes a sample of its current color value. See the second screen shot further below.
Also, you might like the following script.
Run it using the undocumented clink lua
command: clink lua ansi_colors.lua
For usage info and available flags, run: clink lua ansi_colors.lua --help
When run, the script prints a table of ANSI codes; see the first screen shot further below. The color codes are all 256-color codes, i.e. for use with 38;5;number
or 48;5;number
. ANSI escape code is a good article with information about escape codes.
-- Shows ANSI 256-color codes and styles.
--
-- Usage:
-- clink lua ansi_colors.lua [options]
--
-- Options:
-- --bg Show background colors with contrasting foreground colors.
-- --fg Show foreground colors with default background (the default).
-- --black Show foreground colors with black background.
-- --white Show foreground colors with white background.
-- --dark Show foreground colors with black background.
-- --light Show foreground colors with white background.
-- --pretty Show pretty layout for 6x6x6 color cube.
-- --simple Show simple layout for 6x6x6 color cube (the default).
-- --help, -? Show help text.
if not clink then
print("This ansi_colors.lua script requires the standalone Clink Lua engine.")
return
elseif clink.argmatcher then
log.info("This ansi_colors.lua script cannot be loaded into an injected Clink instance; it belongs in a separate directory that doesn't get loaded when Clink is injected.")
return
end
local fg = true
local forcing_background
local pretty_cube = false
local contrast_strategy = 1
local norm = "\x1b[m"
local bold = "\x1b[1m"
local italic = "\x1b[3m"
local underline = "\x1b[4m"
local overline = "\x1b[53m"
local emphasis = "\x1b[36m"
local CSI = "\\x1b["
local param = "\x1b[33m"
for _, a in ipairs(arg) do
if a == "--bg" then
fg = false
elseif a == "--fg" then
fg = true
elseif a == "--black" or a == "--dark" then
fg = true
forcing_background = 0
elseif a == "--white" or a == "--light" then
fg = true
forcing_background = 15
elseif a == "--pretty" then
pretty_cube = true
elseif a == "--simple" then
pretty_cube = false
elseif a == "--help" or a == "-?" or a == "-h" or a == "help" then
print("Shows ANSI 256-color codes.")
print()
print("Usage:")
print(" clink lua ansi_colors.lua [options]")
print()
print("Options:")
print(" --bg Show background colors with contrasting foreground colors.")
print(" --fg Show foreground colors with default background (the default).")
print(" --black Show foreground colors with black background.")
print(" --white Show foreground colors with white background.")
print(" --dark Show foreground colors with black background.")
print(" --light Show foreground colors with white background.")
print(" --pretty Show pretty layout for 6x6x6 color cube.")
print(" --simple Show simple layout for 6x6x6 color cube (the default).")
print(" --help, -? Show help text.")
return
else
print("Unrecognized option '"..tostring(a).."'.")
return
end
end
local function in_range(num, lo, hi)
return num >= lo and num <= hi
end
local hi_ranges = {}
table.insert(hi_ranges, { 0, 7 })
if contrast_strategy == 1 then
for i = 0, 5 do
table.insert(hi_ranges, { 16 + i*36, 16 + i*36 + 17 })
end
elseif contrast_strategy == 2 then
table.insert(hi_ranges, { 16, 123 })
else
-- TODO: calculate luminance to choose contrast color.
end
table.insert(hi_ranges, { 232, 243 })
local contrast_table = {}
for _, r in ipairs(hi_ranges) do
for i = r[1], r[2] do
contrast_table[i] = true
end
end
local function contrast(num)
assert(num >= 0)
assert(num <= 255)
return contrast_table[num] and 15 or 0
end
local function make_sgr(num, addl)
local color
if fg then
color = string.format("%s;38;5;%d", addl or "", num)
if forcing_background then
color = color..string.format(";48;5;%d", forcing_background)
end
else
color = string.format("%s;48;5;%d;38;5;%d", addl or "", num, contrast(num))
end
return color
end
local function make_color(num, addl)
return "\x1b[0;"..make_sgr(num, addl).."m"
end
local function print_sample(num)
local sample = string.format("%s%3d %s", make_color(num), num, norm)
clink.print(sample, NONL)
end
local function center(text)
local width = 3*4*6 + 2
local len = console.cellcount(text)
clink.print(string.rep(" ", (width - len) / 2)..italic..text..norm)
end
print()
center("System colors:")
clink.print(" ", NONL)
for i = 0, 7 do
print_sample(i)
end
clink.print(" ", NONL)
for i = 8, 15 do
print_sample(i)
end
print("\n")
center("Color cube:")
if pretty_cube then
for i = 0, 5 do
for j = 0, 2 do
for k = 0, 5 do
print_sample(16 + i*6 + j*72 + k)
end
clink.print(" ", NONL)
end
print()
end
for i = 5, 0, -1 do
for j = 0, 2 do
for k = 0, 5 do
print_sample(52 + i*6 + j*72 + k)
end
clink.print(" ", NONL)
end
print()
end
else
for i = 0, 5 do
for j = 0, 2 do
for k = 0, 5 do
print_sample(16 + i*36 + j*6 + k)
end
clink.print(" ", NONL)
end
print()
end
for i = 0, 5 do
for j = 0, 2 do
for k = 0, 5 do
print_sample(34 + i*36 + j*6 + k)
end
clink.print(" ", NONL)
end
print()
end
end
print()
center("Grayscale ramp:")
clink.print(" ", NONL)
for i = 232, 243 do
print_sample(i)
end
print()
clink.print(" ", NONL)
for i = 244, 255 do
print_sample(i)
end
print()
print()
center("Styles:")
local styles = {
{ "1", "22", "Boldface" },
{ "3", "23", "Italics" },
{ "7", "27", "Reverse" },
{ "4", "24", "Underline" },
{ "53", "55", "Overline" },
}
local text = ""
local on = ""
local off = ""
for _, s in ipairs(styles) do
if text ~= "" then
text = text.." "
on = on.." "
off = off.." "
end
text = text.."\x1b[0;"..s[1].."m"..s[3]..norm
local fmt = string.format("%%%ds", #s[3]--[[ - 4]])
on = on..string.format(--[["on: "..]]fmt, s[1])
off = off..string.format(--[["off:"..]]fmt, s[2])
end
center(norm..text)
center(norm..on)
center(norm..off)
print()
Thanks for all this @chrisant996. Your lua scripts look like what I want. I'll check them out tonight. I know clink
doesn't have a help
command, but the samples you show above are what I would expect from clink help colors
or equivalent.
And sorry about the goofy syntax snark. I was referring to the sgr
codes. They're sorta like morse code to me... they make sense and do what they say on the tin. But whenever I have to deal with them directly I get a huge headache.
Thanks for all this @chrisant996. Your lua scripts look like what I want. I'll check them out tonight. I know
clink
doesn't have ahelp
command, but the samples you show above are what I would expect fromclink help colors
or equivalent.
A help command would print documentation, and the documentation web page covers that. Documentation is static i.e. it doesn't dynamically report current settings. A help command isn't where something like current color samples would go.
Maybe more like a status command to report various kinds of settings. clink settings
is the closest to that right now.
But "current color samples" is just scratching the surface. I don't want to make a bunch of piecemeal separate enhancements that don't fit together well. That's why I'm taking a slower and more methodical approach to designing a solution for the broader area of "color themes".
And sorry about the goofy syntax snark. I was referring to the
sgr
codes. They're sorta like morse code to me... they make sense and do what they say on the tin. But whenever I have to deal with them directly I get a huge headache.
I think you mean the gobbledygook that goes after the "sgr", right?
The gobbledygook is just how ANSI escape codes work. That's just part of how terminals work.
I didn't define that, and it's not actually part of Clink. 🙃 I agree they're not friendly, which is why Clink tries to allow simpler friendly color strings, unless you want to use fancy/sophisticated escape codes. To use fancy codes, Clink expects awareness of escape codes.
There is a rough edge, though: the "enhanced defaults" set some fancy codes. And so when clink set
reports them, the escape code values are shown regardless whether the user is familiar with escape codes.
I think the best solution there is just education, but I'm open to other ideas.
Based on the conversation in microsoft/terminal#16493, it seems the current view is "Light themes are always going to be broken [in terminals]".
VT terminal specifications were designed for CRT monitors, which had black backgrounds. The fairly recent interest in trying to make Light themes for terminals isn't compatible with the existing VT escape codes for colors and styles. While it's possible to change the default background color in a theme to be white, that breaks the VT color specifications such that it's impossible for apps to present text with readable contrast using the colors in the theme. And currently in Windows Terminal it's impossible for an app to detect whether a Light theme is being used (microsoft/terminal#10639), so an app can't even automatically adjust its colors based on the current theme.
Even though Windows Terminal itself includes some Light themes, the current state of things means Light themes are really faking it, and apps cannot automatically coexist with a Light theme unless the app doesn't use any colors.
So, in order to support Light themes, there are really only two options:
This has always been an issue in Unix and Linux terminals. In Windows Terminal and ConPTY-based terminals it's a problem only until microsoft/terminal#10639 is fixed so that the GetConsoleScreenBufferEx()
API again returns the current color table (like it does in legacy ConHost terminal windows).
In the meantime, I'm working on a way to define / save / load colors themes for Clink.
The main challenge is that colors are stored in the profile, and the same profile is shared by different terminal windows which may be using different background colors.
set CLINK_PROFILE=path_to_profile
to override the profile. That seems heavy-handed, and makes it difficult/impossible to share command history between terminals using different color themes.clink_settings
from the profile. But that interferes with the ability for clink set
to report and/or modify the current settings.I'm exploring other options, and variations of the above options.
The visibility is somewhat poor in light themes of Windows Terminal and JetBrain IDEs.
I know the text colors can be changed for each element in clink config file. But since I use both dark and light themes in different software, is there a way to make clink have good visibility for both light and dark terminal themes?