Internals
Everything documented in this page is internal and subject to breaking changes, even in minor version updates of Julia or StyledStrings.jl. If you are curious about the internals, read on, but if you want to depend on them, please consider opening a pull request or issue to discuss making them part of the public API.
StyledStrings.ANSI_4BIT_COLORS — Constant
A mapping between ANSI named colours and indices in the standard 256-color table. The standard colors are 0-7, and high intensity colors 8-15.
The high intensity colors are prefixed by "bright". The "brightblack" color is given two aliases: "grey" and "gray".
StyledStrings.FACES — Constant
Globally named Faces.
default gives the initial values of the faces, and current holds the active (potentially modified) set of faces. This two-set system allows for any modifications to the active faces to be undone.
StyledStrings.MAX_COLOR_FORWARDS — Constant
MAX_COLOR_FORWARDSThe maximum number of times to follow color references when resolving a color.
StyledStrings.UNRESOLVED_COLOR_FALLBACK — Constant
UNRESOLVED_COLOR_FALLBACKThe fallback RGBTuple used when asking for a color that is not defined.
StyledStrings.Legacy.ANSI_256_COLORS — Constant
A mapping from 256-color codes indicies to 8-bit colours.
StyledStrings.Legacy.NAMED_COLORS — Constant
A list of all named colors recognised, including both the old light_* and new bright_* named colors.
StyledStrings.Legacy.RENAMED_COLORS — Constant
A mapping from old named colours to the new names, specifically from light_* to bright_*.
StyledStrings.Legacy.legacy_color — Function
legacy_color(color::Union{String, Symbol, Int})Attempt to obtain a SimpleColor for a "legacy" color value color.
When this is not possible, nothing is returned.
StyledStrings.Legacy.load_env_colors! — Function
load_env_colors!()Try to emulate the effect of the various *_color() functions of Base, by loading any specified colours as foregrounds of the relevant faces.
StyledStrings.ansi_4bit — Function
ansi_4bit(color::Integer, background::Bool=false)Provide the color code (30-37, 40-47, 90-97, 100-107) for color (0–15).
When background is set the background variant will be provided, otherwise the provided code is for setting the foreground color.
StyledStrings.setcolors! — Function
setcolors!(colors::Vector{Pair{Symbol, RGBTuple}})Update the known base colors with those in colors, and recalculate current faces.
color should be a complete list of known colours. If :foreground and :background are both specified, the faces in the light/dark theme will be loaded. Otherwise, only the base theme will be applied.
StyledStrings.face! — Function
face!(str::Union{<:AnnotatedString, <:SubString{<:AnnotatedString}},
[range::UnitRange{Int},] face::Union{Symbol, Face})Apply face to str, along range if specified or the whole of str.
StyledStrings.getface — Function
getface(faces)Obtain the final merged face from faces, an iterator of Faces, face name Symbols, and lists thereof.
getface(annotations::Vector{@NamedTuple{label::Symbol, value::Any}})Combine all of the :face annotations with getfaces.
getface()Obtain the default face.
getface(s::AnnotatedString, i::Integer)Get the merged Face that applies to s at index i.
getface(c::AnnotatedChar)Get the merged Face that applies to c.
StyledStrings.load_customisations! — Function
load_customisations!(; force::Bool=false)Load customisations from the user's faces.toml file, if it exists as well as the current environment.
This function should be called before producing any output in situations where the user's customisations should be considered. This is called automatically when printing text or HTML output, and when calling withfaces, but may need to be called manually in unusual situations.
Unless force is set, customisations are only applied when this function is called for the first time, and subsequent calls are a no-op.
StyledStrings.loadface! — Function
loadface!(name::Symbol => update::Face)Merge the face name in FACES.current with update. If the face name does not already exist in FACES.current, then it is set to update. To reset a face, update can be set to nothing.
Examples
julia> loadface!(:red => Face(foreground=0xff0000))
Face (sample)
foreground: #ff0000StyledStrings.loaduserfaces! — Function
loaduserfaces!(faces::Dict{String, Any})For each face specified in Dict, load it to FACES.current.
loaduserfaces!(tomlfile::String)Load all faces declared in the Faces.toml file tomlfile.
StyledStrings.resetfaces! — Function
resetfaces!()Reset the current global face dictionary to the default value.
resetfaces!(name::Symbol)Reset the face name to its default value, which is returned.
If the face name does not exist, nothing is done and nothing returned. In the unlikely event that the face name does not have a default value, it is deleted, a warning message is printed, and nothing returned.
StyledStrings.rgbcolor — Function
rgbcolor(color::Union{Symbol, SimpleColor})Resolve a color to an RGBTuple.
The resolution follows these steps:
- If
coloris aSimpleColorholding anRGBTuple, that is returned. - If
colornames a face, the face's foreground color is used. - If
colornames a base color, that color is used. - Otherwise,
UNRESOLVED_COLOR_FALLBACK(bright pink) is returned.
StyledStrings.termcolor — Function
termcolor(io::IO, color::SimpleColor, category::Char)Print to io the SGR code to set the category's slot to color, where category is set as follows:
'3'sets the foreground color'4'sets the background color'5'sets the underline color
If color is a SimpleColor{Symbol}, the value should be a a member of ANSI_4BIT_COLORS. Any other value will cause the color to be reset.
If color is a SimpleColor{RGBTuple} and get_have_truecolor() returns true, 24-bit color is used. Otherwise, an 8-bit approximation of color is used.
If color is unknown, no output is produced.
termcolor(io::IO, ::Nothing, category::Char)Print to io the SGR code to reset the color for category.
StyledStrings.termcolor24bit — Function
termcolor24bit(io::IO, color::RGBTuple, category::Char)Print to io the 24-bit SGR color code to set the category8 slot to color.
StyledStrings.termcolor8bit — Function
termcolor8bit(io::IO, color::RGBTuple, category::Char)Print to io the best 8-bit SGR color code that sets the category color to be close to color.
StyledStrings.try_rgbcolor — Function
try_rgbcolor(name::Symbol, stamina::Int = MAX_COLOR_FORWARDS)Attempt to resolve name to an RGBTuple, taking up to stamina steps.
Styled Markup parsing
While some of the internals above are useful outside StyledStrings, and unlikely to be broken (but with no guarantees!), apart from the exported string macro and styled function, the details of StyledMarkup documented below consists entirely of implementation details that should under no circumstances be referenced outside of StyledStrings .
If you're curious about how exactly styled markup strings are parsed, they should provide some insight though.
StyledStrings.StyledMarkup — Module
StyledMarkupA sub-module of StyledStrings that specifically deals with parsing styled markup strings. To this end, two entrypoints are provided:
- The
styled""string macro, which is generally preferred. - They
styledfunction, which allows for use with runtime-provided strings, when needed.
Overall, this module essentially functions as a state machine with a few extra niceties (like detailed error reporting) sprinkled on top. The overall design can be largely summed up with the following diagram:
╭String─────────╮
│ Styled markup │
╰──────┬────────╯
│╭╴[module]
││
╭┴┴State─╮
╰┬───────╯
│
╭╴run_state_machine!╶╮
│ ╭─────┼─╼ escaped!
│ Apply rules: │ │
│ "\\" ▶──────╯ ╭───┼─╼[interpolated!] ──▶ readexpr!, addpart!
│ "$" ▶────────╯ │
│ "{" ▶────────────┼─╼ begin_style! ──▶ read_annotation!
│ "}" ▶─────╮ │ ├─╼ read_inlineface! [readexpr!]
│ ╰──────┼─╼ end_style! ╰─╼ read_face_or_keyval!
│ addpart!(...) │
╰╌╌╌╌╌┬╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯
│
▼
ResultOf course, as usual, the devil is in the details.
StyledStrings.StyledMarkup.State — Type
StateA struct representing of the parser state (if you squint, a state monad even).
To create the initial state, use the constructor: State(content::AbstractString, mod::Union{Module, Nothing}=nothing) -> State
Its fields are as follows:
content::String, the (unescaped) input stringbytes::Vector{UInt8}, the codeunits ofcontent. This is aVector{UInt8}instead of aCodeUnits{UInt8}because we need to be able to modify the array, for instance when erasing escape characters.s::Iterators.Stateful, an(index, char)iterator ofcontentmod::Union{Module, Nothing}, the (optional) context with which to evaluate inline expressions in. This should be provided iff the styled markup comes from a macro invocation.parts::Vector{Any}, the result of the parsing, a list of elements that when passed toannotatedstringproduce the styled markup string. The types of its values are highly diverse, hence theAnyelement type.active_styles::Vector{Vector{Tuple{Int, Int, Union{Symbol, Expr, Tuple{Symbol, Any}}}}}}, A list of batches of styles that have yet to be applied to any content. Entries of a batch consist of(source_position, start_position, style)tuples, wherestylemay be just a symbol (referring to a face), aTuple{Symbol, Any}annotation, or anExprthat evaluates to a valid annotation (whenmodis set).pending_styles::Vector{Tuple{UnitRange{Int}, Union{Symbol, Expr, Tuple{Symbol, Any}}}}, A list of styles that have been terminated, and so are known to occur over a certain range, but have yet to be applied.offset::Int, a record of the between thecontentindex and the index in the resulting styled string, as markup structures are absorbed.point::Int, the current index incontent.escape::Bool, whether the last character seen was an escape character.interpolations::Int, how many interpolated values have been seen. Knowing whether or not anything needs to be evaluated allows the resulting string to be computed at macroexpansion time, when possible, and knowing how many allows for some micro-optimisations.errors::Vector, any errors raised during parsing. We collect them instead of immediately throwing so that we can list as many issues as possible at once, instead of forcing the author of the invalid styled markup to resolve each issue one at a time. This is expected to be populated by invocations ofstyerr!.
StyledStrings.StyledMarkup.isnextchar — Function
isnextchar(state::State, char::Char) -> Bool
isnextchar(state::State, chars::NTuple{N, Char}) -> BoolCheck if state has a next character, and if so whether it is char or one of chars.
StyledStrings.StyledMarkup.ismacro — Function
ismacro(state::State) -> BoolCheck whether state is indicated to come from a macro invocation, according to whether state.mod is set or not.
While this function is rather trivial, it clarifies the intent when used instead of just checking state.mod.
StyledStrings.StyledMarkup.styerr! — Function
styerr!(state::State, message::AbstractString, position::Union{Nothing, Int}=nothing, hint::String="around here")Register an error in state based on erroneous content at or around position (if known, and with a certain hint as to the location), with the nature of the error given by message.
StyledStrings.StyledMarkup.hygienic_eval — Function
hygienic_eval(state::State, expr)Evaluate expr within the scope of state's module. This replicates part of the behind-the-scenes behaviour of macro expansion, we just need to manually invoke it due to the particularities around dealing with code from a foreign module that we parse ourselves.
StyledStrings.StyledMarkup.addpart! — Function
addpart!(state::State, stop::Int)Create a new part from state.point to stop, applying all pending styles.
This consumes all the content between state.point and stop, and shifts state.point to be the index after stop.
addpart!(state::State, start::Int, expr, stop::Int)Create a new part based on (the eventual evaluation of) expr, running from start to stop, taking the currently active styles into account.
StyledStrings.StyledMarkup.escaped! — Function
escaped!(state::State, i::Int, char::Char)Parse the escaped character char, at index i, into state
StyledStrings.StyledMarkup.interpolated! — Function
interpolated!(state::State, i::Int, _)Interpolate the expression starting at i, and add it as a part to state.
StyledStrings.StyledMarkup.readexpr! — Function
readexpr!(state::State, pos::Int = first(popfirst!(state.s)) + 1)Read the expression starting at pos in state.content, and consume state.s as appropriate to align the iterator to the end of the expression.
StyledStrings.StyledMarkup.skipwhitespace! — Function
skipwhitespace!(state::State)Skip forwards all space, tab, and newline characters in state.s
StyledStrings.StyledMarkup.read_while! — Function
read_while!(f::Function, state::Base.Stateful, lastchar::Char)Read state until f(::Char) is false.
Given a Stateful that iterates (_, char::Char) pairs, and a predicate f(::Char)::Bool, return (str, lastchar), where str::String contains all the char for which f(char) == true, and lastchar the last char element seen, or the input lastchar there are no elements of state.
Examples
julia> s = Base.Stateful(pairs("abc"));
julia> read_while!(isnumeric, s, 'w')
("", 'a')
julia> first(s) # s is mutated
2 => 'b'
julia> read_while!(isascii, Base.Stateful(pairs("123Σω")), 'k')
("123", 'Σ')
julia> read_while!(isascii, Base.Stateful(pairs("abcde")), 'α')
("abcde", 'e')
julia> read_while!(isascii , Base.Stateful(pairs("")), 'k')
("", 'k')StyledStrings.StyledMarkup.begin_style! — Function
begin_style!(state::State, i::Int, char::Char)Parse the style declaration beginning at i (char) with read_annotation!, and register it in the active styles list.
StyledStrings.StyledMarkup.end_style! — Function
end_style!(state::State, i::Int, char::Char)Close of the most recent active style in state, making it a pending style.
StyledStrings.StyledMarkup.read_annotation! — Function
read_annotation!(state::State, i::Int, char::Char, newstyles::Vector) -> BoolRead the annotations at i (char), and push the style read to newstyles.
This skips whitespace and checks what the next character in state.s is, detects the form of the annotation, and parses it using the appropriate specialised function like so:
:, end of annotation, do nothing(, inline face declaration, useread_inlineface!- otherwise, use
read_face_or_keyval!
After parsing the annotation, returns a boolean value signifying whether there is an immediately subsequent annotation to be read.
StyledStrings.StyledMarkup.read_inlineface! — Function
read_inlineface!(state::State, i::Int, char::Char, newstyles)Read an inline face declaration from state, at position i (char), and add it to newstyles.
StyledStrings.StyledMarkup.read_face_or_keyval! — Function
read_face_or_keyval!(state::State, i::Int, char::Char, newstyles)Read an inline face or key-value pair from state at position i (char), and add it to newstyles.
StyledStrings.StyledMarkup.run_state_machine! — Function
run_state_machine!(state::State)Iterate through state.s, applying the parsing rules for the top-level of syntax and calling the relevant specialised functions.
Upon completion, state.s should be fully consumed and state.parts fully populated (along with state.errors).
StyledStrings.StyledMarkup.annotatedstring_optimize! — Function
annotatedstring_optimize!(str::AnnotatedString)Merge contiguous identical annotations in str.