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 — ConstantA 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 — ConstantGlobally 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.Legacy.ANSI_256_COLORS — ConstantA mapping from 256-color codes indicies to 8-bit colours.
StyledStrings.Legacy.NAMED_COLORS — ConstantA list of all named colors recognised, including both the old light_* and new bright_* named colors.
StyledStrings.Legacy.RENAMED_COLORS — ConstantA mapping from old named colours to the new names, specifically from light_* to bright_*.
StyledStrings.Legacy.legacy_color — Functionlegacy_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! — Functionload_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 — Functionansi_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.face! — Functionface!(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 — Functiongetface(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.loadface! — Functionloadface!(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! — Functionloaduserfaces!(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! — Functionresetfaces!()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.termcolor — Functiontermcolor(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 — Functiontermcolor24bit(io::IO, color::RGBTuple, category::Char)Print to io the 24-bit SGR color code to set the category8 slot to color.
StyledStrings.termcolor8bit — Functiontermcolor8bit(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.load_customisations! — Functionload_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.
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 — ModuleStyledMarkupA 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 — TypeStateA 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 — Functionisnextchar(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 — Functionismacro(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! — Functionstyerr!(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 — Functionhygienic_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! — Functionaddpart!(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! — Functionescaped!(state::State, i::Int, char::Char)Parse the escaped character char, at index i, into state
StyledStrings.StyledMarkup.interpolated! — Functioninterpolated!(state::State, i::Int, _)Interpolate the expression starting at i, and add it as a part to state.
StyledStrings.StyledMarkup.readexpr! — Functionreadexpr!(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! — Functionskipwhitespace!(state::State)Skip forwards all space, tab, and newline characters in state.s
StyledStrings.StyledMarkup.read_while! — Functionread_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! — Functionbegin_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! — Functionend_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! — Functionread_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! — Functionread_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! — Functionread_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! — Functionrun_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! — Functionannotatedstring_optimize!(str::AnnotatedString)Merge contiguous identical annotations in str.