Документация Engee

StyledStrings

Note The API for StyledStrings and AnnotatedStrings is considered experimental and is subject to change between Julia versions.

Styling

When working with strings, formatting and styling often appear as a secondary concern.

For instance, when printing to a terminal you might want to sprinkle ANSI escape sequences in the output, when outputting HTML styling constructs (<span style="...">, etc.) serve a similar purpose, and so on. It is possible to simply insert the raw styling constructs into the string next to the content itself, but it quickly becomes apparent that this is not well suited for anything but the most basic use cases. Not all terminals support the same ANSI codes, the styling constructs need to be painstakingly removed when calculating the width of already-styled content, and that’s before you even get into handling multiple output formats.

Instead of leaving this headache to be widely experienced downstream, it is tackled head-on by the introduction of a special string type (AnnotatedString). This string type wraps any other AbstractString type and allows for formatting information to be applied to regions (e.g. characters 1 through to 7 are bold and red).

Regions of a string are styled by applying Faces (think "typeface") to them — a structure that holds styling information. As a convenience, faces in the global faces dictionary (e.g. shadow) can just be named instead of giving the Face directly.

Along with these capabilities, we also provide a convenient way for constructing AnnotatedStrings, detailed in Styled String Literals.

Annotated Strings

It is sometimes useful to be able to hold metadata relating to regions of a string. A AnnotatedString wraps another string and allows for regions of it to be annotated with labelled values (:label => value). All generic string operations are applied to the underlying string. However, when possible, styling information is preserved. This means you can manipulate a AnnotatedString --taking substrings, padding them, concatenating them with other strings-- and the metadata annotations will "come along for the ride".

This string type is fundamental to the StyledStrings stdlib, which uses :face-labelled annotations to hold styling information.

When concatenating a AnnotatedString, take care to use annotatedstring instead of string if you want to keep the string annotations.

julia> str = AnnotatedString("hello there", [(1:5, :word, :greeting), (7:11, :label, 1)])
"hello there"

julia> length(str)
11

julia> lpad(str, 14)
"   hello there"

julia> typeof(lpad(str, 7))
AnnotatedString{String}

julia> str2 = AnnotatedString(" julia", [(2:6, :face, :magenta)])
" julia"

julia> annotatedstring(str, str2)
"hello there julia"

julia> str * str2 == annotatedstring(str, str2) # *-concatenation works
true

The annotations of a AnnotatedString can be accessed and modified via the annotations and annotate! functions.

Styling via AnnotatedStrings

Faces

The Face type

A Face specifies details of a typeface that text can be set in. It covers a set of basic attributes that generalize well across different formats, namely:

  • font

  • height

  • weight

  • slant

  • foreground

  • background

  • underline

  • strikethrough

  • inverse

  • inherit

For details on the particular forms these attributes take, see the Face docstring, but of particular interest is inherit as it allows you to inherit attributes from other Faces.

The global faces dictionary

To make referring to particular styles more convenient, there is a global Dict{Symbol, Face} that allows for Faces to be referred to simply by name. Packages can add faces to this dictionary via the addface! function, and the loaded faces can be easily customized.

Appropriate face naming Any package registering new faces should ensure that they are prefixed by the package name, i.e. follow the format mypackage_myface. This is important for predictability, and to prevent name clashes.

Furthermore, packages should take care to use (and introduce) _semantic_ faces (like `code`) over direct colours and styles (like `cyan`). This is helpful in a number of ways, from making the intent in usage more obvious, aiding composability, and making user customisation more intuitive.

There are two set of exemptions to the package-prefix rule:

  • the set of basic faces that are part of the default value of the faces dictionary

  • faces introduced by Julia’s own standard library, namely JuliaSyntaxHighlighting

Basic faces

Basic faces are intended to represent a general idea that is widely applicable.

For setting some text with a certain attribute, we have the bold, light, italic, underline, strikethrough, and inverse faces.

There are also named faces for the 16 terminal colors: black, red, green, yellow, blue, magenta, cyan, white, bright_black/grey/gray, bright_red, bright_green, bright_blue, bright_magenta, bright_cyan, and bright_white.

For shadowed text (i.e. dim but there) there is the shadow face. To indicate a selected region, there is the region face. Similarly for emphasis and highlighting the emphasis and highlight faces are defined. There is also code for code-like text.

For visually indicating the severity of messages, the error, warning, success, info, note, and tip faces are defined.

Customisation of faces (Faces.toml)

It is good for the name faces in the global face dictionary to be customizable. Theming and aesthetics are nice, and it is important for accessibility reasons too. A TOML file can be parsed into a list of Face specifications that are merged with the pre-existing entry in the face dictionary.

A Face is represented in TOML like so:

[facename]
attribute = "value"
...

[package.facename]
attribute = "value"

For example, if the shadow face is too hard to read it can be made brighter like so:

[shadow]
foreground = "white"

On initialization, the config/faces.toml file under the first Julia depot (usually ~/.julia) is loaded.

Applying faces to a AnnotatedString

By convention, the :face attributes of a AnnotatedString hold information on the Faces that currently apply. This can be given in multiple forms, as a single Symbol naming a Faces in the global face dictionary, a Face itself, or a vector of either.

The show(::IO, ::MIME"text/plain", ::AnnotatedString) and show(::IO, ::MIME"text/html", ::AnnotatedString) methods both look at the :face attributes and merge them all together when determining the overall styling.

We can supply :face attributes to a AnnotatedString during construction, add them to the properties list afterwards, or use the convenient Styled String literals.

Styled String Literals

To ease construction of AnnotatedStrings with Faces applied, the styled"..." styled string literal allows for the content and attributes to be easily expressed together via a custom grammar.

Within a styled"..." literal, curly braces are considered special characters and must be escaped in normal usage (\{, \}). This allows them to be used to express annotations with (nestable) {annotations...:text} constructs.

The annotations... component is a comma-separated list of three types of annotations.

  • Face names

  • Inline Face expressions (key=val,...)

  • key=value pairs

Interpolation is possible everywhere except for inline face keys.

For more information on the grammar, see the extended help of the styled"..." docstring.

As an example, we can demonstrate the list of built-in faces mentioned above like so:

julia> println(styled"
The basic font-style attributes are {bold:bold}, {light:light}, {italic:italic},
{underline:underline}, and {strikethrough:strikethrough}.

In terms of color, we have named faces for the 16 standard terminal colors:
 {black:■} {red:■} {green:■} {yellow:■} {blue:■} {magenta:■} {cyan:■} {white:■}
 {bright_black:■} {bright_red:■} {bright_green:■} {bright_yellow:■} {bright_blue:■} {bright_magenta:■} {bright_cyan:■} {bright_white:■}

Since {code:bright_black} is effectively grey, we define two aliases for it:
{code:grey} and {code:gray} to allow for regional spelling differences.

To flip the foreground and background colors of some text, you can use the
{code:inverse} face, for example: {magenta:some {inverse:inverse} text}.

The intent-based basic faces are {shadow:shadow} (for dim but visible text),
{region:region} for selections, {emphasis:emphasis}, and {highlight:highlight}.
As above, {code:code} is used for code-like text.

Lastly, we have the 'message severity' faces: {error:error}, {warning:warning},
{success:success}, {info:info}, {note:note}, and {tip:tip}.

Remember that all these faces (and any user or package-defined ones) can
arbitrarily nest and overlap, {region,tip:like {bold,italic:so}}.")
 The basic font-style attributes are bold, light, italic,
 underline, and strikethrough.

 In terms of color, we have named faces for the 16 standard terminal colors:
  ■ ■ ■ ■ ■ ■ ■ ■
  ■ ■ ■ ■ ■ ■ ■ ■

 Since bright_black is effectively grey, we define two aliases for it:
 grey and gray to allow for regional spelling differences.

 To flip the foreground and background colors of some text, you can use the
 inverse face, for example: some inverse text.

 The intent-based basic faces are shadow (for dim but visible text),
 region for selections, emphasis, and highlight.
 As above, code is used for code-like text.

 Lastly, we have the 'message severity' faces: error, warning,
 success, info, note, and tip.

 Remember that all these faces (and any user or package-defined ones) can
 arbitrarily nest and overlap, like so.

API reference

Styling and Faces

@styled_str -> AnnotatedString

Создает стилизованную строку. Внутри строки структуры {<specs>:<content>} применяют форматирование к <content> в соответствии со списком разделенных запятыми спецификаций <specs>. Каждая спецификация может иметь форму названия шрифта, встроенной спецификации шрифта или пары key=value. Если в значении есть символы ,=:{}, оно должно быть заключено в {...}.

Интерполяция строк с помощью $ работает так же, как и обычные строки, за исключением того, что кавычки необходимо экранировать. Шрифты, ключи и значения также можно интерполировать с помощью $.

Пример

styled"The {bold:{italic:quick} {(foreground=#cd853f):brown} fox} jumped over the {link={https://en.wikipedia.org/wiki/Laziness}:lazy} dog"

Расширенная справка

Этот макрос можно описать следующей грамматикой EBNF:

styledstring = { styled | interpolated | escaped | plain } ;

specialchar = '{' | '}' | '$' | '\"' ;
anychar = [\u0-\u1fffff] ;
plain = { anychar - specialchar } ;
escaped = '\\', specialchar ;

interpolated = '$', ? expr ? | '$(', ? expr ?, ')' ;

styled = '{', ws, annotations, ':', content, '}' ;
content = { interpolated | plain | escaped | styled } ;
annotations = annotation | annotations, ws, ',', ws, annotation ;
annotation = face | inlineface | keyvalue ;
ws = { ' ' | '\t' | '\n' } ; (* whitespace *)

face = facename | interpolated ;
facename = [A-Za-z0-9_]+ ;

inlineface = '(', ws, [ faceprop ], { ws, ',', faceprop }, ws, ')' ;
faceprop = [a-z]+, ws, '=', ws, ( [^,)]+ | interpolated) ;

keyvalue = key, ws, '=', ws, value ;
key = ( [^\0${}=,:], [^\0=,:]* ) | interpolated ;
value = simplevalue | curlybraced | interpolated ;
curlybraced = '{' { escaped | plain } '}' ;
simplevalue = [^${},:], [^,:]* ;

Дополнительное условие, не оговоренное в приведенной выше грамматике, заключается в том, что plain должно быть допустимыми входными данными для unescape_string, при этом specialchar сохраняется.

Приведенная выше грамматика для inlineface упрощена, фактическая реализация немного сложнее. Полное описание поведения приведено ниже.

faceprop = ( 'face', ws, '=', ws, ( ? string ? | interpolated ) ) |
           ( 'height', ws, '=', ws, ( ? number ? | interpolated ) ) |
           ( 'weight', ws, '=', ws, ( symbol | interpolated ) ) |
           ( 'slant', ws, '=', ws, ( symbol | interpolated ) ) |
           ( ( 'foreground' | 'fg' | 'background' | 'bg' ),
               ws, '=', ws, ( simplecolor | interpolated ) ) |
           ( 'underline', ws, '=', ws, ( underline | interpolated ) ) |
           ( 'strikethrough', ws, '=', ws, ( bool | interpolated ) ) |
           ( 'inverse', ws, '=', ws, ( bool | interpolated ) ) |
           ( 'inherit', ws, '=', ws, ( inherit | interpolated ) ) ;

nothing = 'nothing' ;
bool = 'true' | 'false' ;
symbol = [^ ,)]+ ;
hexcolor = ('#' | '0x'), [0-9a-f]{6} ;
simplecolor = hexcolor | symbol | nothing ;

underline = nothing | bool | simplecolor | underlinestyled;
underlinestyled = '(', ws, ('' | nothing | simplecolor | interpolated), ws,
                  ',', ws, ( symbol | interpolated ), ws ')' ;

inherit = ( '[', inheritval, { ',', inheritval }, ']' ) | inheritval;
inheritval = ws, ':'?, symbol ;
styled(content::AbstractString) -> AnnotatedString

Создает стилизованную строку. Внутри строки структуры {<specs>:<content>} применяют форматирование к <content> в соответствии со списком разделенных запятыми спецификаций <specs>. Каждая спецификация может иметь форму названия шрифта, встроенной спецификации шрифта или пары key=value. Если в значении есть символы ,=:{}, оно должно быть заключено в {...}.

Это функциональный эквивалент макроса @styled_str, только без возможности интерполяции.

Face — это коллекция графических атрибутов для отображения текста. Шрифты управляют тем, как текст отображается в терминале, а возможно, и в других местах.

Большую часть времени Face будет храниться в глобальных словарях шрифтов как уникальная связь с символом имени шрифта, и чаще всего обращение будет происходить по этому имени, а не к самому объекту Face.

Атрибуты

Все атрибуты можно задать с помощью конструктора именованных аргументов; по умолчанию они равны nothing.

  • height (Int или Float64): высота либо в деципунктах (Int), либо как множитель базового размера (Float64).

  • weight (Symbol): один из символов (от самого тусклого до самого насыщенного) :thin, :extralight, :light, :semilight, :normal, :medium, :semibold, :bold, :extrabold или :black. В терминалах при любой насыщенности больше :normal текст отображается полужирным шрифтом, а в терминалах, поддерживающих текст переменной яркости, при любой насыщенности меньше :normal текст отображается тусклым шрифтом.

  • slant (Symbol): один из символов :italic, :oblique или :normal.

  • foreground (SimpleColor): цвет переднего плана текста.

  • background (SimpleColor): цвет фона текста.

  • underline, подчеркивание текста в одной из следующих форм:

    • Bool: должен ли текст подчеркиваться. MarkdownAST.LineBreak()

    • SimpleColor: текст должен подчеркиваться этим цветом. MarkdownAST.LineBreak()

    • Tuple{Nothing, Symbol}: текст должен подчеркиваться с использованием стиля, заданного символом :straight, :double, :curly, :dotted или :dashed. MarkdownAST.LineBreak()

    • Tuple{SimpleColor, Symbol}: текст должен подчеркиваться указанным цветом SimpleColor и с использованием стиля, заданного символом, как и ранее.

  • strikethrough (Bool): должен ли текст зачеркиваться.

  • inverse (Bool): должны ли инвертироваться цвета переднего плана и фона.

  • inherit (Vector{Symbol}): имена шрифтов, от которых должно происходить наследование, причем приоритет имеют более ранние шрифты. Все шрифты наследуются от шрифта :default.

addface!(name::Symbol => default::Face)

Создает новый шрифт с именем name. Если шрифтов с таким именем еще нет, default добавляется как к FACES`.default`, так и к (копии) FACES.current, при этом возвращается текущее значение.

Если шрифт name уже существует, возвращается nothing.

Примеры

julia> addface!(:mypkg_myface => Face(slant=:italic, underline=true))
Face (sample)
         slant: italic
     underline: true
withfaces(f, kv::Pair...)
withfaces(f, kvpair_itr)

Выполняет f с FACES`.current`, временно измененным нулем или более аргументов :name => val kv, или kvpair_itr, который создает значения в форме kv.

Функция withfaces обычно применяется с синтаксисом withfaces(kv...) do ... end. С помощью значения nothing можно временно отключить шрифт (если он был задан). Когда функция withfaces возвращает управление, восстанавливается исходный FACES`.current`.

Примеры

julia> withfaces(:yellow => Face(foreground=:red), :green => :blue) do
           println(styled"{yellow:red} and {green:blue} mixed make {magenta:purple}")
       end
red and blue mixed make purple
struct SimpleColor

Базовое представление цвета, предназначенное для стилизации строк. Может содержать либо именованный цвет (например, :red), либо объект RGBTuple, представляющий собой кортеж NamedTuple, определяющий цвет r, g, b с глубиной 8 бит.

Конструкторы

SimpleColor(name::Symbol)  # например, :red
SimpleColor(rgb::RGBTuple) # например, (r=1, b=2, g=3)
SimpleColor(r::Integer, b::Integer, b::Integer)
SimpleColor(rgb::UInt32)   # например, 0x123456

См. также описание tryparse(SimpleColor, rgb::String).

parse(::Type{SimpleColor}, rgb::String)

Аналог tryparse(SimpleColor, rgb::String), который выдает ошибку вместо возврата nothing.

tryparse(::Type{SimpleColor}, rgb::String)

Пытается проанализировать rgb как SimpleColor. Если значение rgb начинается с # и имеет длину 7, оно преобразуется в SimpleColor на основе RGBTuple. Если значение rgb начинается с a--z, rgb интерпретируется как название цвета и преобразуется в SimpleColor на основе Symbol.

В противном случае возвращается nothing.

Примеры

julia> tryparse(SimpleColor, "blue")
SimpleColor(blue)

julia> tryparse(SimpleColor, "#9558b2")
SimpleColor(#9558b2)

julia> tryparse(SimpleColor, "#nocolor")
merge(initial::Face, others::Face...)

Объединяет свойства шрифта initial и others, причем последние имеют приоритет.