Skip to content

Commit f8192fe

Browse files
committed
Document StyledStrings and Faces
1 parent 5aaff18 commit f8192fe

File tree

2 files changed

+237
-0
lines changed

2 files changed

+237
-0
lines changed

doc/src/base/strings.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ Core.String(::AbstractString)
1717
Base.SubString
1818
Base.LazyString
1919
Base.@lazy_str
20+
Base.StyledString
21+
Base.StyledChar
22+
Base.styledstring
23+
Base.Face
24+
Base.@S_str
25+
Base.addface!
26+
Base.loadfaces!
2027
Base.transcode
2128
Base.unsafe_string
2229
Base.ncodeunits(::AbstractString)

doc/src/manual/strings.md

Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1203,3 +1203,233 @@ Notice that the first two backslashes appear verbatim in the output, since they
12031203
precede a quote character.
12041204
However, the next backslash character escapes the backslash that follows it, and the
12051205
last backslash escapes a quote, since these backslashes appear before a quote.
1206+
1207+
## [Styling](@id man-styling)
1208+
1209+
When working with strings, formatting and styling often appear as a secondary
1210+
concern.
1211+
1212+
!!! note
1213+
For instance, when printing to a terminal you might want to sprinkle [ANSI
1214+
escape
1215+
sequences](https://en.wikipedia.org/wiki/ANSI_escape_code#SGR_(Select_Graphic_Rendition)_parameters)
1216+
in the output, when outputting HTML styling constructs (`<span style="...">`,
1217+
etc.) serve a similar purpose, and so on. It is possible to simply insert the
1218+
raw styling constructs into the string next to the content itself, but it
1219+
quickly becomes apparent that this is not well suited for anything but the most
1220+
basic use-cases. Not all terminals support the same ANSI codes, the styling
1221+
constructs need to be painstakingly removed when calculating the width of
1222+
already-styled content, and that's before you even get into handling
1223+
multiple output formats.
1224+
1225+
Instead of leaving this headache to be widely experienced downstream, it is
1226+
tackled head-on by the introduction of a special string type
1227+
([`StyledString`](@ref)). This string type wraps any other string type and
1228+
allows for formating information to be applied to regions (e.g. characters 1
1229+
through to 7 are bold and red).
1230+
1231+
Regions of a string are styled by applying [`Face`](@ref)s to them —a
1232+
structure that holds styling information— (think "typeface"). As a
1233+
convenience, it is possible to name a face in the global faces dictionary
1234+
instead of giving the [`Face`](@ref) directly.
1235+
1236+
Along with these capabilities, we also provide a convenient way for constructing
1237+
[`StyledString`](@ref)s, detailed in [Styled String Literals](@ref man-styled-string-literals).
1238+
1239+
```jldoctest
1240+
julia> S"{yellow:hello} {blue:there}"
1241+
"hello there" # prints with colour in the REPL
1242+
```
1243+
1244+
### [Styled Strings](@id man-styled-strings)
1245+
1246+
[`StyledString`](@ref)s wrap another string and overlay a list of tagged
1247+
regions. All generic string operations are applied to the underlying string.
1248+
When possible though, styling information is maintained (e.g. when
1249+
[`split`](@ref)ting a [`StyledString`](@ref)).
1250+
1251+
To concatenate printable values and styled strings together (maintaining styling
1252+
information), the [`styledstring`](@ref) function can be used (as a counterpart
1253+
to [`string`](@ref)).
1254+
1255+
```jldoctest
1256+
julia> str = StyledString("hello there",
1257+
[(1:5, :face => :yellow), (7:11, :face => :blue)])
1258+
"hello there"
1259+
1260+
julia> collect(Base.eachstyle(str))
1261+
3-element Vector{Tuple{SubString{String}, Vector{Pair{Symbol, Any}}}}:
1262+
("hello", [:face => :yellow])
1263+
(" ", [])
1264+
("there", [:face => :blue])
1265+
1266+
julia> length(str)
1267+
11
1268+
1269+
julia> lpad(str, 14)
1270+
" hello there"
1271+
1272+
julia> typeof(lpad(str, 7))
1273+
StyledString{String}
1274+
1275+
julia> str2 = StyledString(" julia", [(2:6, :face => :magenta)])
1276+
1277+
julia> styledstring(str, str2)
1278+
"hello there julia"
1279+
1280+
julia> str * str2 == styledstring(str, str2) # *-concatination still works
1281+
true
1282+
1283+
julia> collect(Base.eachstyle(str * str2))
1284+
5-element Vector{Tuple{SubString{String}, Vector{Pair{Symbol, Any}}}}:
1285+
("hello", [:face => :yellow])
1286+
(" ", [])
1287+
("there", [:face => :blue])
1288+
(" ", [])
1289+
("julia", [:face => :magenta])
1290+
```
1291+
1292+
#### Advanced uses
1293+
1294+
While styling is the focus here, the system is actually more general. It allows
1295+
for arbitrary annotations to be added to regions of the underlying string. Each
1296+
annotation is in the form of a `tag::Symbol => value::Any` tagged value. You can
1297+
tag any information you want, such as source location the string was extracted
1298+
from, or an alternative form of some text, but the most obvious use case is to
1299+
hold styling information (which use a `:face` tag).
1300+
1301+
### [Faces](@id man-faces)
1302+
1303+
#### The `Face` type
1304+
1305+
A [`Face`](@ref) specifies details of a typeface that text can be set in. It
1306+
covers a set of basic attributes that generalise well across different formats,
1307+
namely:
1308+
1309+
- `height`
1310+
- `weight`
1311+
- `slant`
1312+
- `foreground`
1313+
- `background`
1314+
- `underline`
1315+
- `strikethrough`
1316+
- `inverse`
1317+
- `inherit`
1318+
1319+
For details on the particular forms these attributes take, see the
1320+
[`Face`](@ref) docstring, but of particular interest is `inherit` as it allows
1321+
you to _inherit_ attributes from other [`Face`](@ref)s.
1322+
1323+
#### The global `FACES` dictionary
1324+
1325+
To make referring to particular styles more convenient, there is a global
1326+
`Dict{Symbol, Face}` that allows for [`Face`](@ref)s to be referred to simply by
1327+
name. Packages can add faces to this dictionary via the [`Base.addface!`](@ref)
1328+
function, and the loaded faces can be easily [customised](@ref man-face-toml).
1329+
1330+
!!! warning
1331+
Any package registering new faces should ensure that they are prefixed
1332+
by the package name, i.e. follow the format `mypackage_myface`.
1333+
This is important for predictability, and to prevent name clashes.
1334+
1335+
There is one set of exemptions to the package-prefix rule, the set of basic
1336+
faces that are part of the default value of the faces dictionary.
1337+
1338+
##### [Basic faces](@id man-basic-faces)
1339+
1340+
Basic faces are intended represent a general idea, that is widely applicable.
1341+
1342+
For setting some text with a certain attribute, we have the `bold`, `italic`,
1343+
`underline`, `strikethrough`, and `inverse` faces.
1344+
1345+
There are also named faces for the 16 terminal colours: `black`, `red`, `green`,
1346+
`yellow`, `blue`, `magenta`, `cyan`, `white`, `bright_black`/`grey`/`gray`,
1347+
`bright_red`, `bright_green`, `bright_blue`, `bright_magenta`, `bright_cyan`,
1348+
and `bright_white`.
1349+
1350+
For shadowed text (i.e. dim but there) there is the `shadow` face. To indicate a
1351+
selected region, there is the `region` face. Similarly for emphasis and
1352+
highlighting the `emphasis` and `highlight` faces are defined. There is also
1353+
`code` for code-like text.
1354+
1355+
For visually indicating the severity of messages the `error`, `warning`,
1356+
`success`, `info`, `note`, and `tip` faces are defined.
1357+
1358+
#### [Customisation of faces (`Faces.toml`)](@id man-face-toml)
1359+
1360+
It is good for the name faces in the global face dictionary to be customizable.
1361+
Theming and aesthetics are nice, and it is important for accessibility reasons
1362+
too. A TOML file can be parsed into a list of [`Face`](@ref) specifications that
1363+
are merged with the pre-existing entry in the face dictionary.
1364+
1365+
A [`Face`](@ref) is represented in TOML like so:
1366+
1367+
```toml
1368+
[facename]
1369+
attribute = "value"
1370+
...
1371+
1372+
[package.facename]
1373+
attribute = "value"
1374+
```
1375+
1376+
For example, if the `shadow` face is too hard to read it can be made brighter
1377+
like so:
1378+
1379+
```toml
1380+
[shadow]
1381+
foreground = "white"
1382+
```
1383+
1384+
#### Applying faces to a `StyledString`
1385+
1386+
By convention, the `:face` attributes of a [`StyledString`](@ref) hold
1387+
information on the [`Face`](@ref)s that currently apply. This can be given in
1388+
multiple forms, as a single `Symbol` naming a [`Face`](@ref)s in the global face
1389+
dictionary, a [`Face`](@ref) itself, or a vector of either.
1390+
1391+
The `show(::IO, ::MIME"text/plain", ::StyledString)` and `show(::IO,
1392+
::MIME"text/html", ::StyledString)` methods both look at the `:face` attributes
1393+
and merge them all together when determining the overall styling.
1394+
1395+
We can supply `:face` attributes to a `StyledString` during construction, add
1396+
them to the properties list afterwards, or use the convenient [Styled String
1397+
literals](@ref man-styled-string-literals).
1398+
1399+
```jldoctest
1400+
julia> str1 = StyledString("blue text", [(1:9, :face => Face(foreground=:blue))])
1401+
"blue text"
1402+
1403+
julia> str2 = StyledString("blue text", :face => Face(foreground=:blue))
1404+
"blue text"
1405+
1406+
julia> str1 == str2
1407+
true
1408+
1409+
julia> sprint(show, MIME("text/plain"), str1, context = :color => true)
1410+
"\"\e[34mblue text\e[39m\""
1411+
1412+
julia> sprint(show, MIME("text/html"), str1, context = :color => true)
1413+
"<pre><span style=\"color: #000080;\">blue text</span></pre>"
1414+
```
1415+
1416+
## [Styled String Literals](@id man-styled-string-literals)
1417+
1418+
To ease construction of [`StyledString`](@ref)s with [`Face`](@ref)s applied,
1419+
the [`S"..."`](@ref @S_str) styled string literal allows for the content and
1420+
attributes to be easily expressed together via a custom grammar.
1421+
1422+
Within a [`S"..."`](@ref @S_str) literal, curly parenthesis are considered
1423+
special characters and must be escaped in normal usage (`\{`, `\}`). This allows
1424+
them to be used to express annotations with (nestable) `{annotations...:text}`
1425+
constructs.
1426+
1427+
The `annotations...` component is a comma-separated list of three types of annotations.
1428+
- Face names
1429+
- Inline `Face` expressions `(key=val,...)`
1430+
- `key=value` pairs
1431+
1432+
Interpolation is possible everywhere except for inline face keys.
1433+
1434+
For more information on the grammar, see the extended help of the
1435+
[`S"..."`](@ref @S_str) docstring.

0 commit comments

Comments
 (0)