@@ -312,8 +312,8 @@ Globally named [`Face`](@ref)s.
312312(potentially modified) set of faces. This two-set system allows for any
313313modifications 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 ())
408415end
409416
410417# # Adding and resetting faces ##
411418
412419"""
413- addface!(name::Symbol => default::Face)
420+ addface!(name::Symbol => default::Face, theme::Symbol = :base )
414421
415422Create 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
419428Should 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
456468end
@@ -464,13 +476,15 @@ If the face `name` does not exist, nothing is done and `nothing` returned.
464476In the unlikely event that the face `name` does not have a default value,
465477it 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
672685end
673686
682695
683696For 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
699714end
700715
@@ -781,23 +796,60 @@ function recolor(f::Function)
781796 nothing
782797end
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+ """
784808function 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
802854end
803855
0 commit comments