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_COLORSConstant

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".

source
StyledStrings.FACESConstant

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.

source
StyledStrings.Legacy.legacy_colorFunction
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.

source
StyledStrings.ansi_4bit_color_codeFunction
ansi_4bit_color_code(color::Symbol, background::Bool=false)

Provide the color code (30-37, 40-47, 90-97, 100-107) for color, as an integer. When background is set the background variant will be provided, otherwise the provided code is for setting the foreground color.

source
StyledStrings.eachregionFunction
eachregion(s::AnnotatedString{S})
eachregion(s::SubString{AnnotatedString{S}})

Identify the contiguous substrings of s with a constant annotations, and return an iterator which provides each substring and the applicable annotations as a Tuple{SubString{S}, Vector{@NamedTuple{label::Symbol, value::Any}}}.

Examples

julia> collect(StyledStrings.eachregion(AnnotatedString(
           "hey there", [(1:3, :face, :bold), (5:9, :face, :italic)])))
3-element Vector{Tuple{SubString{String}, Vector{@NamedTuple{label::Symbol, value}}}}:
 ("hey", [@NamedTuple{label::Symbol, value}((:face, :bold))])
 (" ", [])
 ("there", [@NamedTuple{label::Symbol, value}((:face, :italic))])
source
StyledStrings.annotation_eventsFunction
annotation_events(string::AbstractString, annots::Vector{@NamedTuple{region::UnitRange{Int}, label::Symbol, value::Any}}, subregion::UnitRange{Int})
annotation_events(string::AnnotatedString, subregion::UnitRange{Int})

Find all annotation "change events" that occur within a subregion of annots, with respect to string. When string is styled, annots is inferred.

Each change event is given in the form of a @NamedTuple{pos::Int, active::Bool, index::Int} where pos is the position of the event, active is a boolean indicating whether the annotation is being activated or deactivated, and index is the index of the annotation in question.

source
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.

source
StyledStrings.getfaceFunction
getface(faces)

Obtain the final merged face from faces, an iterator of Faces, face name Symbols, and lists thereof.

source
getface(annotations::Vector{@NamedTuple{label::Symbol, value::Any}})

Combine all of the :face annotations with getfaces.

source
getface()

Obtain the default face.

source
getface(s::AnnotatedString, i::Integer)

Get the merged Face that applies to s at index i.

source
getface(c::AnnotatedChar)

Get the merged Face that applies to c.

source
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: #ff0000
source
StyledStrings.loaduserfaces!Function
loaduserfaces!(faces::Dict{String, Any})

For each face specified in Dict, load it to FACES.current.

source
loaduserfaces!(tomlfile::String)

Load all faces declared in the Faces.toml file tomlfile.

source
StyledStrings.resetfaces!Function
resetfaces!()

Reset the current global face dictionary to the default value.

source
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.

source
StyledStrings.termcolorFunction
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.

source
termcolor(io::IO, ::Nothing, category::Char)

Print to io the SGR code to reset the color for category.

source
StyledStrings.termcolor24bitFunction
termcolor24bit(io::IO, color::RGBTuple, category::Char)

Print to io the 24-bit SGR color code to set the category8 slot to color.

source
StyledStrings.termcolor8bitFunction
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.

source
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.

source

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.StyledMarkupModule
StyledMarkup

A 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 styled function, 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!(...)      │
 ╰╌╌╌╌╌┬╌╌╌╌╌╌╌╌╌╌╌╌╌╌╯
       │
       ▼
     Result

Of course, as usual, the devil is in the details.

source
StyledStrings.StyledMarkup.StateType
State

A 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 string
  • bytes::Vector{UInt8}, the codeunits of content. This is a Vector{UInt8} instead of a CodeUnits{UInt8} because we need to be able to modify the array, for instance when erasing escape characters.
  • s::Iterators.Stateful, an (index, char) iterator of content
  • mod::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 to annotatedstring produce the styled markup string. The types of its values are highly diverse, hence the Any element 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, where style may be just a symbol (referring to a face), a Tuple{Symbol, Any} annotation, or an Expr that evaluates to a valid annotation (when mod is 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 the content index and the index in the resulting styled string, as markup structures are absorbed.
  • point::Int, the current index in content.
  • 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 of styerr!.
source
StyledStrings.StyledMarkup.isnextcharFunction
isnextchar(state::State, char::Char) -> Bool
isnextchar(state::State, chars::NTuple{N, Char}) -> Bool

Check if state has a next character, and if so whether it is char or one of chars.

source
StyledStrings.StyledMarkup.ismacroFunction
ismacro(state::State) -> Bool

Check 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.

source
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.

source
StyledStrings.StyledMarkup.hygienic_evalFunction
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.

source
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.

source
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.

source
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.

source
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')
source
StyledStrings.StyledMarkup.read_annotation!Function
read_annotation!(state::State, i::Int, char::Char, newstyles::Vector) -> Bool

Read 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, use read_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.

source
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).

source