More thoroughly theme the terminal

This commit is contained in:
TEC 2024-05-09 17:10:10 +08:00
parent 12bcf3a060
commit 7974004490
Signed by: tec
SSH Key Fingerprint: SHA256:eobz41Mnm0/iYWBvWThftS0ElEs1ftBr6jamutnXc/A
2 changed files with 189 additions and 28 deletions

View File

@ -5,36 +5,13 @@ using StyledStrings: Face, getface, resetfaces!, loadface!,
const THEMES = Dict{Symbol, Vector{Pair{Symbol, Face}}}()
function load(theme::Symbol, term::Bool=true)
faces = get(THEMES, theme, nothing)
isnothing(faces) && throw(ArgumentError("The doom theme $theme is not defined."))
resetfaces!()
foreach(loadface!, faces)
term || return
setcolor(id::String, ::Nothing) = print(stdout, "\e]", id, ";\a")
setcolor(id::String, color::SimpleColor) = if color.value isa RGBTuple
print(stdout, "\e]", id, ";rgb:", join(string.(values(color.value), base=16), '/'), "\a")
else
print(stdout, "\e]", id, ";", string(color.value), "\a")
end
(; foreground, background) = getface()
setcolor("10", if foreground.value isa RGBTuple foreground end)
setcolor("11", if background.value isa RGBTuple background end)
cursor = getface(:cursor)
setcolor("12", if cursor.background.value isa RGBTuple && cursor.background != background
cursor.background end)
end
function reset(term::Bool=true)
resetfaces!()
term || return
print(stdout, "\e]10;\a\e]11;\a\e]12;\a")
@eval for theme in readdir(joinpath(@__DIR__, "themes"))
include("themes/$theme")
end
function list()
for theme in sort(keys(THEMES) |> collect)
withfaces(Dict(THEMES[theme])) do
print("\e[0m")
withfaces(THEMES[theme]) do
println(styled" $(rpad(theme, 30)) \
{emphasis:■} {julia_funcall:■} {julia_symbol:■} {julia_type:■} {julia_string:■} \
{(fg=red):} {(fg=green):} {(fg=yellow):} {(fg=blue):} \
@ -43,8 +20,20 @@ function list()
end
end
@eval for theme in readdir(joinpath(@__DIR__, "themes"))
include("themes/$theme")
function load!(theme::Symbol, term::Bool=true)
faces = get(THEMES, theme, nothing)
isnothing(faces) && throw(ArgumentError("The doom theme $theme is not defined."))
resetfaces!()
foreach(loadface!, faces)
term && theme_terminal!(faces)
end
include("termtheme.jl")
function __init__()
if isinteractive()
atexit(() -> reset!(true))
end
end
end

172
src/termtheme.jl Normal file
View File

@ -0,0 +1,172 @@
const ANSI_COLOR_CODES = (
black = "0",
red = "1",
green = "2",
yellow = "3",
blue = "4",
magenta = "5",
cyan = "6",
white = "7",
bright_black = "8",
grey = "8",
gray = "8",
bright_red = "9",
bright_green = "10",
bright_yellow = "11",
bright_blue = "12",
bright_magenta = "13",
bright_cyan = "14",
bright_white = "15",
)
const OSC_PARAMS = (
foreground = "10",
background = "11",
cursor = "12",
highlight = "17",
)
const INITIAL_TERM_COLOURING = Dict{Symbol, SimpleColor}();
function term_raw!(raw::Bool)
Base.check_open(stdin)
if Sys.iswindows() && Base.ispty(stdin)
run((raw ? `stty raw -echo onlcr -ocrnl opost` : `stty sane`),
stdin, stdout, stderr)
true
else
ccall(:jl_tty_set_mode, Int32, (Ptr{Cvoid},Int32), stdin.handle::Ptr{Cvoid}, raw) != -1
end
end
function read_osc_response(timeout::Real = 0.05)
outbytes = UInt8[]
start = time()
lock(stdin.cond)
Base.iolock_begin()
while start - time() < timeout && (isempty(outbytes) || last(outbytes) (UInt8('\\'), UInt8('\a'), UInt8('\3')))
if bytesavailable(stdin.buffer) > 0
push!(outbytes, read(stdin.buffer, UInt8))
else
stdin.readerror === nothing || throw(stdin.readerror)
isopen(stdin) || break
Base.start_reading(stdin) # ensure we are reading
Base.iolock_end()
# @info "Waiting for stdin.cond"
wait(stdin.cond)
# @info "Finished for stdin.cond"
unlock(stdin.cond)
Base.iolock_begin()
lock(stdin.cond)
end
end
Base.iolock_end()
unlock(stdin.cond)
String(outbytes)
end
function interpret_osc_color(output::String)
startswith(output, "\e]") || return
seppos = findlast(';', output)
isnothing(seppos) && return
code = output[ncodeunits("\e]")+1:seppos-1]
output = @view output[seppos+1:end]
if endswith(output, "\e\\")
output = @view output[begin:end-2]
else endswith(output, "\a")
output = @view output[begin:end-1]
end
if startswith(output, "rgb:")
components = split(output[ncodeunits("rgb:")+1:end], '/')
length(components) == 3 || return
elseif startswith(output, "rgba:")
components = split(output[ncodeunits("rgba:")+1:end], '/')
length(components) == 4 || return
else
return
end
rgbstr = (components[1], components[2], components[3])
validcolorhex(chex) =
!isempty(chex) && all(c -> c in '0':'9' || c in 'a':'f' || c in 'A':'F', chex)
all(validcolorhex, rgbstr) || return
rgb = map(chex -> UInt8(parse(Int, chex, base=16) ÷ 16^(length(chex) - 2)), rgbstr)
code, SimpleColor(rgb[1], rgb[2], rgb[3])
end
function read_colours!(out::Dict{Symbol, SimpleColor})
term_raw!(true)
for code in values(OSC_PARAMS)
print(stdout, "\e]", code, ";?\a")
end
for name in keys(OSC_PARAMS)
val = read_osc_response() |> interpret_osc_color
isnothing(val) && continue
out[name] = last(val)
end
print(stdout, "\e]4;")
join(stdout, values(ANSI_COLOR_CODES), ";?;")
print(stdout, ";?\a")
for _ in 1:length(ANSI_COLOR_CODES)
val = read_osc_response() |> interpret_osc_color
isnothing(val) && continue
code, colour = val
code = chopprefix(code, "4;")
name = :_
for (key, kcode) in pairs(ANSI_COLOR_CODES)
if code == kcode
name = key
break
end
end
out[name] = colour
end
term_raw!(false)
out
end
function set_termcolor!(id::Symbol, color::Union{SimpleColor, Nothing}=nothing)
isempty(INITIAL_TERM_COLOURING) && read_colours!(INITIAL_TERM_COLOURING)
default = get(INITIAL_TERM_COLOURING, id, nothing)
isnothing(default) && return false # Only make changes than can be undone
color = something(color, default)
# Ignore non-RGBTuple colours because they'll be ugly
color.value isa RGBTuple || return false
code = if haskey(OSC_PARAMS, id)
print(stdout, "\e]", OSC_PARAMS[id], ";rgb:")
elseif haskey(ANSI_COLOR_CODES, id)
print(stdout, "\e]4;" * ANSI_COLOR_CODES[id], ";rgb:")
else
return
end
join(stdout, string.(values(color.value), base=16), '/')
print(stdout, "\a")
true
end
function theme_terminal!(theme::Vector{Pair{Symbol, Face}})
get(Base.current_terminfo, :can_change, false) || return
(; foreground, background) = getface()
if set_termcolor!(:foreground, foreground)
loadface!(:default => Face(foreground = :default))
end
if set_termcolor!(:background, background)
loadface!(:default => Face(background = :default))
end
cursor = getface(:cursor)
set_termcolor!(:cursor, if cursor.background != background; cursor.background end)
highlight = getface(:highlight)
set_termcolor!(:highlight, if highlight.background != background; highlight.background end)
for name in keys(ANSI_COLOR_CODES)
if set_termcolor!(name, getface(name).foreground)
loadface!(name => Face(foreground = name))
end
end
end
function reset!(term::Bool=true)
resetfaces!()
term && get(Base.current_terminfo, :can_change, false) || return
for key in keys(INITIAL_TERM_COLOURING)
set_termcolor!(key)
end
end