@@ -403,20 +403,23 @@ const FACES = let default = Dict{Symbol, Face}(
403403 :bright_magenta => (r = 0xbf , g = 0x60 , b = 0xca ),
404404 :bright_cyan => (r = 0x26 , g = 0xc6 , b = 0xda ),
405405 :bright_white => (r = 0xf6 , g = 0xf5 , b = 0xf4 ))
406- (; default, basecolors,
406+ (themes = (base = default, light = Dict {Symbol, Face} (), dark = Dict {Symbol, Face} ()),
407+ modifications = (base = Dict {Symbol, Face} (), light = Dict {Symbol, Face} (), dark = Dict {Symbol, Face} ()),
407408 current = ScopedValue (copy (default)),
408- modifications = ScopedValue ( Dict {Symbol, Face} ()) ,
409+ basecolors = basecolors ,
409410 lock = ReentrantLock ())
410411end
411412
412413# # Adding and resetting faces ##
413414
414415"""
415- addface!(name::Symbol => default::Face)
416+ addface!(name::Symbol => default::Face, theme::Symbol = :base )
416417
417418Create a new face by the name `name`. So long as no face already exists by this
418- name, `default` is added to both `FACES``.default` and (a copy of) to
419- `FACES`.`current`, with the current value returned.
419+ name, `default` is added to both `FACES.themes[theme]` and (a copy of) to
420+ `FACES.current`, with the current value returned.
421+
422+ The `theme` should be either `:base`, `:light`, or `:dark`.
420423
421424Should the face `name` already exist, `nothing` is returned.
422425
@@ -429,11 +432,12 @@ Face (sample)
429432 underline: true
430433```
431434"""
432- function addface! ((name, default):: Pair{Symbol, Face} )
433- @lock FACES. lock if ! haskey (FACES. default, name)
434- FACES. default[name] = default
435- FACES. current[][name] = if haskey (FACES. current[], name)
436- merge (copy (default), FACES. current[][name])
435+ function addface! ((name, default):: Pair{Symbol, Face} , theme:: Symbol = :base )
436+ current = FACES. current[]
437+ @lock FACES. lock if ! haskey (FACES. themes[theme], name)
438+ FACES. themes[theme][name] = default
439+ current[name] = if haskey (current, name)
440+ merge (copy (default), current[name])
437441 else
438442 copy (default)
439443 end
@@ -449,10 +453,12 @@ function resetfaces!()
449453 @lock FACES. lock begin
450454 current = FACES. current[]
451455 empty! (current)
452- for (key, val) in FACES. default
456+ for (key, val) in FACES. themes . base
453457 current[key] = val
454458 end
455- empty! (FACES. modifications[])
459+ if current === FACES. current. default # Only when top-level
460+ map (empty!, values (FACES. modifications))
461+ end
456462 current
457463 end
458464end
@@ -466,13 +472,15 @@ If the face `name` does not exist, nothing is done and `nothing` returned.
466472In the unlikely event that the face `name` does not have a default value,
467473it is deleted, a warning message is printed, and `nothing` returned.
468474"""
469- function resetfaces! (name:: Symbol )
470- @lock FACES. lock if ! haskey (FACES. current[], name)
471- elseif haskey (FACES. default, name)
472- delete! (FACES. modifications[], name)
473- FACES. current[][name] = copy (FACES. default[name])
475+ function resetfaces! (name:: Symbol , theme:: Symbol = :base )
476+ current = FACES. current[]
477+ @lock FACES. lock if ! haskey (current, name) # Nothing to reset
478+ elseif haskey (FACES. themes[theme], name)
479+ current === FACES. current. default &&
480+ delete! (FACES. modifications[theme], name)
481+ current[name] = copy (FACES. themes[theme][name])
474482 else # This shouldn't happen
475- delete! (FACES . current[] , name)
483+ delete! (current, name)
476484 @warn """ The face $name was reset, but it had no default value, and so has been deleted instead!,
477485 This should not have happened, perhaps the face was added without using `addface!`?"""
478486 end
@@ -658,18 +666,17 @@ Face (sample)
658666 foreground: #ff0000
659667```
660668"""
661- function loadface! ((name, update):: Pair{Symbol, Face} )
669+ function loadface! ((name, update):: Pair{Symbol, Face} , theme :: Symbol = :base )
662670 @lock FACES. lock begin
663- mface = get (FACES. modifications[], name, nothing )
664- if ! isnothing (mface)
665- update = merge (mface, update)
666- end
667- FACES. modifications[][name] = update
668- cface = get (FACES. current[], name, nothing )
669- if ! isnothing (cface)
670- update = merge (cface, update)
671+ current = FACES. current[]
672+ if FACES. current. default === current # Only save top-level modifications
673+ mface = get (FACES. modifications[theme], name, nothing )
674+ isnothing (mface) || (update = merge (mface, update))
675+ FACES. modifications[theme][name] = update
671676 end
672- FACES. current[][name] = update
677+ cface = get (current, name, nothing )
678+ isnothing (cface) || (update = merge (cface, update))
679+ current[name] = update
673680 end
674681end
675682
684691
685692For each face specified in `Dict`, load it to `FACES``.current`.
686693"""
687- function loaduserfaces! (faces:: Dict{String, Any} , prefix:: Union{String, Nothing} = nothing )
694+ function loaduserfaces! (faces:: Dict{String, Any} , prefix:: Union{String, Nothing} = nothing , theme:: Symbol = :base )
695+ theme == :base && prefix ∈ map (String, setdiff (keys (FACES. themes), (:base ,))) &&
696+ return loaduserfaces! (faces, nothing , Symbol (prefix))
688697 for (name, spec) in faces
689698 fullname = if isnothing (prefix)
690699 name
@@ -694,7 +703,7 @@ function loaduserfaces!(faces::Dict{String, Any}, prefix::Union{String, Nothing}
694703 fspec = filter ((_, v):: Pair -> ! (v isa Dict), spec)
695704 fnest = filter ((_, v):: Pair -> v isa Dict, spec)
696705 ! isempty (fspec) &&
697- loadface! (Symbol (fullname) => convert (Face, fspec))
706+ loadface! (Symbol (fullname) => convert (Face, fspec), theme )
698707 ! isempty (fnest) &&
699708 loaduserfaces! (fnest, fullname)
700709 end
@@ -783,23 +792,60 @@ function recolor(f::Function)
783792 nothing
784793end
785794
795+ """
796+ setcolors!(color::Vector{Pair{Symbol, RGBTuple}})
797+
798+ Update the known base colors with those in `color`, and recalculate current faces.
799+
800+ `color` should be a complete list of known colours. If `:foreground` and
801+ `:background` are both specified, the faces in the light/dark theme will be
802+ loaded. Otherwise, only the base theme will be applied.
803+ """
786804function setcolors! (color:: Vector{Pair{Symbol, RGBTuple}} )
787- @lock recolor_lock begin
805+ lock (recolor_lock)
806+ lock (FACES. lock)
807+ try
808+ # Apply colors
809+ fg, bg = nothing , nothing
788810 for (name, rgb) in color
789811 FACES. basecolors[name] = rgb
812+ if name === :foreground
813+ fg = rgb
814+ elseif name === :background
815+ bg = rgb
816+ end
817+ end
818+ newtheme = if ! isnothing (fg) && ! isnothing (bg)
819+ :unknown
820+ else
821+ ifelse (sum (fg) > sum (bg), :light , :dark )
790822 end
823+ # Reset all themes to defaults
791824 current = FACES. current[]
792- for ( name, _) in FACES. modifications[]
793- default = get (FACES. default , name, nothing )
825+ for theme in keys (FACES . themes), ( name, _) in FACES. modifications[theme ]
826+ default = get (FACES. themes . base , name, nothing )
794827 isnothing (default) && continue
795828 current[name] = default
796829 end
830+ if newtheme ∈ keys (FACES. themes)
831+ for (name, face) in FACES. themes[newtheme]
832+ current[name] = merge (current[name], face)
833+ end
834+ end
835+ # Run recolor hooks
797836 for hook in recolor_hooks
798837 hook ()
799838 end
800- for (name, face) in FACES. modifications[]
801- current[name] = merge (current[name], face)
839+ # Layer on modifications
840+ for theme in keys (FACES. themes)
841+ theme ∈ (:base , newtheme) || continue
842+ for (name, face) in FACES. modifications[theme]
843+ current[name] = merge (current[name], face)
844+ end
802845 end
846+ finally
847+ unlock (FACES. lock)
848+ unlock (recolor_lock)
803849 end
804850end
805851
0 commit comments