Skip to content

Commit 5465fcb

Browse files
committed
Split default+modified faces into base/light/dark
1 parent 953d67a commit 5465fcb

File tree

4 files changed

+102
-49
lines changed

4 files changed

+102
-49
lines changed

docs/src/internals.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ StyledStrings.Legacy.RENAMED_COLORS
1616
StyledStrings.Legacy.legacy_color
1717
StyledStrings.Legacy.load_env_colors!
1818
StyledStrings.ansi_4bit
19+
StyledStrings.setcolors!
1920
StyledStrings.face!
2021
StyledStrings.getface
2122
StyledStrings.load_customisations!

src/faces.jl

Lines changed: 92 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -312,8 +312,8 @@ Globally named [`Face`](@ref)s.
312312
(potentially modified) set of faces. This two-set system allows for any
313313
modifications to the active faces to be undone.
314314
"""
315-
const FACES = let default = Dict{Symbol, Face}(
316-
# Default is special, it must be completely specified
315+
const FACES = let base = Dict{Symbol, Face}(
316+
# Base is special, it must be completely specified
317317
# and everything inherits from it.
318318
:default => Face(
319319
"monospace", 120, # font, height
@@ -350,7 +350,7 @@ const FACES = let default = Dict{Symbol, Face}(
350350
:bright_white => Face(foreground=:bright_white),
351351
# Useful common faces
352352
:shadow => Face(foreground=:bright_black),
353-
:region => Face(background=0x3a3a3a),
353+
:region => Face(background=0x636363),
354354
:emphasis => Face(foreground=:blue),
355355
:highlight => Face(inherit=:emphasis, inverse=true),
356356
:code => Face(foreground=:cyan),
@@ -382,6 +382,12 @@ const FACES = let default = Dict{Symbol, Face}(
382382
:repl_prompt_pkg => Face(inherit=[:blue, :repl_prompt]),
383383
:repl_prompt_beep => Face(inherit=[:shadow, :repl_prompt]),
384384
)
385+
light = Dict{Symbol, Face}(
386+
:region => Face(background=0x979797),
387+
)
388+
dark = Dict{Symbol, Face}(
389+
:region => Face(background=0x363636),
390+
)
385391
basecolors = Dict{Symbol, RGBTuple}(
386392
:background => (r = 0xff, g = 0xff, b = 0xff),
387393
:foreground => (r = 0x00, g = 0x00, b = 0x00),
@@ -401,20 +407,23 @@ const FACES = let default = Dict{Symbol, Face}(
401407
:bright_magenta => (r = 0xbf, g = 0x60, b = 0xca),
402408
:bright_cyan => (r = 0x26, g = 0xc6, b = 0xda),
403409
:bright_white => (r = 0xf6, g = 0xf5, b = 0xf4))
404-
(; default, basecolors,
405-
current = ScopedValue(copy(default)),
406-
modifications = ScopedValue(Dict{Symbol, Face}()),
410+
(themes = (; base, light, dark),
411+
modifications = (base = Dict{Symbol, Face}(), light = Dict{Symbol, Face}(), dark = Dict{Symbol, Face}()),
412+
current = ScopedValue(copy(base)),
413+
basecolors = basecolors,
407414
lock = ReentrantLock())
408415
end
409416

410417
## Adding and resetting faces ##
411418

412419
"""
413-
addface!(name::Symbol => default::Face)
420+
addface!(name::Symbol => default::Face, theme::Symbol = :base)
414421
415422
Create a new face by the name `name`. So long as no face already exists by this
416-
name, `default` is added to both `FACES``.default` and (a copy of) to
417-
`FACES`.`current`, with the current value returned.
423+
name, `default` is added to both `FACES.themes[theme]` and (a copy of) to
424+
`FACES.current`, with the current value returned.
425+
426+
The `theme` should be either `:base`, `:light`, or `:dark`.
418427
419428
Should the face `name` already exist, `nothing` is returned.
420429
@@ -427,11 +436,12 @@ Face (sample)
427436
underline: true
428437
```
429438
"""
430-
function addface!((name, default)::Pair{Symbol, Face})
431-
@lock FACES.lock if !haskey(FACES.default, name)
432-
FACES.default[name] = default
433-
FACES.current[][name] = if haskey(FACES.current[], name)
434-
merge(copy(default), FACES.current[][name])
439+
function addface!((name, default)::Pair{Symbol, Face}, theme::Symbol = :base)
440+
current = FACES.current[]
441+
@lock FACES.lock if !haskey(FACES.themes[theme], name)
442+
FACES.themes[theme][name] = default
443+
current[name] = if haskey(current, name)
444+
merge(copy(default), current[name])
435445
else
436446
copy(default)
437447
end
@@ -447,10 +457,12 @@ function resetfaces!()
447457
@lock FACES.lock begin
448458
current = FACES.current[]
449459
empty!(current)
450-
for (key, val) in FACES.default
460+
for (key, val) in FACES.themes.base
451461
current[key] = val
452462
end
453-
empty!(FACES.modifications[])
463+
if current === FACES.current.default # Only when top-level
464+
map(empty!, values(FACES.modifications))
465+
end
454466
current
455467
end
456468
end
@@ -464,13 +476,15 @@ If the face `name` does not exist, nothing is done and `nothing` returned.
464476
In the unlikely event that the face `name` does not have a default value,
465477
it is deleted, a warning message is printed, and `nothing` returned.
466478
"""
467-
function resetfaces!(name::Symbol)
468-
@lock FACES.lock if !haskey(FACES.current[], name)
469-
elseif haskey(FACES.default, name)
470-
delete!(FACES.modifications[], name)
471-
FACES.current[][name] = copy(FACES.default[name])
479+
function resetfaces!(name::Symbol, theme::Symbol = :base)
480+
current = FACES.current[]
481+
@lock FACES.lock if !haskey(current, name) # Nothing to reset
482+
elseif haskey(FACES.themes[theme], name)
483+
current === FACES.current.default &&
484+
delete!(FACES.modifications[theme], name)
485+
current[name] = copy(FACES.themes[theme][name])
472486
else # This shouldn't happen
473-
delete!(FACES.current[], name)
487+
delete!(current, name)
474488
@warn """The face $name was reset, but it had no default value, and so has been deleted instead!,
475489
This should not have happened, perhaps the face was added without using `addface!`?"""
476490
end
@@ -656,18 +670,17 @@ Face (sample)
656670
foreground: #ff0000
657671
```
658672
"""
659-
function loadface!((name, update)::Pair{Symbol, Face})
673+
function loadface!((name, update)::Pair{Symbol, Face}, theme::Symbol = :base)
660674
@lock FACES.lock begin
661-
mface = get(FACES.modifications[], name, nothing)
662-
if !isnothing(mface)
663-
update = merge(mface, update)
664-
end
665-
FACES.modifications[][name] = update
666-
cface = get(FACES.current[], name, nothing)
667-
if !isnothing(cface)
668-
update = merge(cface, update)
675+
current = FACES.current[]
676+
if FACES.current.default === current # Only save top-level modifications
677+
mface = get(FACES.modifications[theme], name, nothing)
678+
isnothing(mface) || (update = merge(mface, update))
679+
FACES.modifications[theme][name] = update
669680
end
670-
FACES.current[][name] = update
681+
cface = get(current, name, nothing)
682+
isnothing(cface) || (update = merge(cface, update))
683+
current[name] = update
671684
end
672685
end
673686

@@ -682,7 +695,9 @@ end
682695
683696
For each face specified in `Dict`, load it to `FACES``.current`.
684697
"""
685-
function loaduserfaces!(faces::Dict{String, Any}, prefix::Union{String, Nothing}=nothing)
698+
function loaduserfaces!(faces::Dict{String, Any}, prefix::Union{String, Nothing}=nothing, theme::Symbol = :base)
699+
theme == :base && prefix map(String, setdiff(keys(FACES.themes), (:base,))) &&
700+
return loaduserfaces!(faces, nothing, Symbol(prefix))
686701
for (name, spec) in faces
687702
fullname = if isnothing(prefix)
688703
name
@@ -692,9 +707,9 @@ function loaduserfaces!(faces::Dict{String, Any}, prefix::Union{String, Nothing}
692707
fspec = filter((_, v)::Pair -> !(v isa Dict), spec)
693708
fnest = filter((_, v)::Pair -> v isa Dict, spec)
694709
!isempty(fspec) &&
695-
loadface!(Symbol(fullname) => convert(Face, fspec))
710+
loadface!(Symbol(fullname) => convert(Face, fspec), theme)
696711
!isempty(fnest) &&
697-
loaduserfaces!(fnest, fullname)
712+
loaduserfaces!(fnest, fullname, theme)
698713
end
699714
end
700715

@@ -781,23 +796,60 @@ function recolor(f::Function)
781796
nothing
782797
end
783798

799+
"""
800+
setcolors!(color::Vector{Pair{Symbol, RGBTuple}})
801+
802+
Update the known base colors with those in `color`, and recalculate current faces.
803+
804+
`color` should be a complete list of known colours. If `:foreground` and
805+
`:background` are both specified, the faces in the light/dark theme will be
806+
loaded. Otherwise, only the base theme will be applied.
807+
"""
784808
function setcolors!(color::Vector{Pair{Symbol, RGBTuple}})
785-
@lock recolor_lock begin
809+
lock(recolor_lock)
810+
lock(FACES.lock)
811+
try
812+
# Apply colors
813+
fg, bg = nothing, nothing
786814
for (name, rgb) in color
787815
FACES.basecolors[name] = rgb
816+
if name === :foreground
817+
fg = rgb
818+
elseif name === :background
819+
bg = rgb
820+
end
821+
end
822+
newtheme = if isnothing(fg) || isnothing(bg)
823+
:unknown
824+
else
825+
ifelse(sum(fg) > sum(bg), :dark, :light)
788826
end
827+
# Reset all themes to defaults
789828
current = FACES.current[]
790-
for (name, _) in FACES.modifications[]
791-
default = get(FACES.default, name, nothing)
829+
for theme in keys(FACES.themes), (name, _) in FACES.modifications[theme]
830+
default = get(FACES.themes.base, name, nothing)
792831
isnothing(default) && continue
793832
current[name] = default
794833
end
834+
if newtheme keys(FACES.themes)
835+
for (name, face) in FACES.themes[newtheme]
836+
current[name] = merge(current[name], face)
837+
end
838+
end
839+
# Run recolor hooks
795840
for hook in recolor_hooks
796841
hook()
797842
end
798-
for (name, face) in FACES.modifications[]
799-
current[name] = merge(current[name], face)
843+
# Layer on modifications
844+
for theme in keys(FACES.themes)
845+
theme (:base, newtheme) || continue
846+
for (name, face) in FACES.modifications[theme]
847+
current[name] = merge(current[name], face)
848+
end
800849
end
850+
finally
851+
unlock(FACES.lock)
852+
unlock(recolor_lock)
801853
end
802854
end
803855

src/io.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ function _ansi_writer(string_writer::F, io::IO, s::Union{<:AnnotatedString, SubS
243243
# We need to make sure that the customisations are loaded
244244
# before we start outputting any styled content.
245245
load_customisations!()
246-
default = FACES.default[:default]
246+
default = FACES.themes.base[:default]
247247
if get(io, :color, false)::Bool
248248
buf = IOBuffer() # Avoid the overhead in repeatedly printing to `stdout`
249249
lastface::Face = default

test/runtests.jl

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -254,35 +254,35 @@ end
254254
strikethrough: false
255255
inverse: false\
256256
"""
257-
@test sprint(show, MIME("text/plain"), FACES.default[:red], context = :color => true) |> choppkg ==
257+
@test sprint(show, MIME("text/plain"), FACES.themes.base[:red], context = :color => true) |> choppkg ==
258258
"""
259259
Face (\e[31msample\e[39m)
260260
foreground: \e[31m■\e[39m red\
261261
"""
262-
@test sprint(show, FACES.default[:red]) |> choppkg ==
262+
@test sprint(show, FACES.themes.base[:red]) |> choppkg ==
263263
"Face(foreground=SimpleColor(:red))"
264-
@test sprint(show, MIME("text/plain"), FACES.default[:red], context = :compact => true) |> choppkg ==
264+
@test sprint(show, MIME("text/plain"), FACES.themes.base[:red], context = :compact => true) |> choppkg ==
265265
"Face(foreground=SimpleColor(:red))"
266-
@test sprint(show, MIME("text/plain"), FACES.default[:red], context = (:compact => true, :color => true)) |> choppkg ==
266+
@test sprint(show, MIME("text/plain"), FACES.themes.base[:red], context = (:compact => true, :color => true)) |> choppkg ==
267267
"Face(\e[31msample\e[39m)"
268-
@test sprint(show, MIME("text/plain"), FACES.default[:highlight], context = :compact => true) |> choppkg ==
268+
@test sprint(show, MIME("text/plain"), FACES.themes.base[:highlight], context = :compact => true) |> choppkg ==
269269
"Face(inverse=true, inherit=[:emphasis])"
270270
with_terminfo(vt100) do # Not truecolor capable
271-
@test sprint(show, MIME("text/plain"), FACES.default[:region], context = :color => true) |> choppkg ==
271+
@test sprint(show, MIME("text/plain"), FACES.themes.base[:region], context = :color => true) |> choppkg ==
272272
"""
273273
Face (\e[48;5;237msample\e[49m)
274274
background: \e[38;5;237m■\e[39m #3a3a3a\
275275
"""
276276
end
277277
with_terminfo(fancy_term) do # Truecolor capable
278-
@test sprint(show, MIME("text/plain"), FACES.default[:region], context = :color => true) |> choppkg ==
278+
@test sprint(show, MIME("text/plain"), FACES.themes.base[:region], context = :color => true) |> choppkg ==
279279
"""
280280
Face (\e[48;2;58;58;58msample\e[49m)
281281
background: \e[38;2;58;58;58m■\e[39m #3a3a3a\
282282
"""
283283
end
284284
with_terminfo(vt100) do # Ensure `enter_reverse_mode` exists
285-
@test sprint(show, MIME("text/plain"), FACES.default[:highlight], context = :color => true) |> choppkg ==
285+
@test sprint(show, MIME("text/plain"), FACES.themes.base[:highlight], context = :color => true) |> choppkg ==
286286
"""
287287
Face (\e[34m\e[7msample\e[39m\e[27m)
288288
inverse: true

0 commit comments

Comments
 (0)