Skip to content

Commit

Permalink
Adopt Base's annotated types/functions as API
Browse files Browse the repository at this point in the history
This opens the door to shuffling parts currently implemented in Base
into this stdlib without breaking any public APIs.
  • Loading branch information
tecosaur authored and KristofferC committed Sep 19, 2024
1 parent cfcfe8c commit 769e49a
Show file tree
Hide file tree
Showing 6 changed files with 91 additions and 23 deletions.
12 changes: 6 additions & 6 deletions docs/src/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ A styled string can be constructed manually, but the [`styled"..."`](@ref
```@repl examples
using StyledStrings
str = styled"{yellow:hello} {blue:there}"
(String(str), Base.annotations(str))
(String(str), annotations(str))
```

```@setup example
Expand Down Expand Up @@ -178,28 +178,28 @@ styled"It's nice to include $color, and it composes too: {bold,inverse:$color}"
```

Sometimes it's useful to compose a string incrementally, or interoperate with
other `IO`-based code. For these use-cases, the [`AnnotatedIOBuffer`](@ref Base.AnnotatedIOBuffer) is very handy, as you can [`read`](@ref Base.read) an [`AnnotatedString`](@ref Base.AnnotatedString) from it.
other `IO`-based code. For these use-cases, the [`AnnotatedIOBuffer`](@ref StyledStrings.AnnotatedIOBuffer) is very handy, as you can [`read`](@ref Base.read) an [`AnnotatedString`](@ref StyledStrings.AnnotatedString) from it.

```@repl examples
aio = Base.AnnotatedIOBuffer()
aio = AnnotatedIOBuffer()
typ = Int
print(aio, typ)
while typ != Any # We'll pretend that `supertypes` doesn't exist.
typ = supertype(typ)
print(aio, styled" {bright_red:<:} $typ")
end
read(seekstart(aio), Base.AnnotatedString)
read(seekstart(aio), AnnotatedString)
```

StyledStrings adds a specialised [`printstyled`](@ref) method `printstyled(::AnnotatedIOBuffer, ...)` that means that you can pass an `AnnotatedIOBuffer` as IO to "legacy" code written to use `printstyled`, and extract all the styling as though it had used [`styled"..."`](@ref @styled_str) macros.

```@repl
aio = Base.AnnotatedIOBuffer()
aio = AnnotatedIOBuffer()
printstyled(aio, 'c', color=:red)
printstyled(aio, 'o', color=:yellow)
printstyled(aio, 'l', color=:green)
printstyled(aio, 'o', color=:blue)
printstyled(aio, 'r', color=:magenta)
read(seekstart(aio), Base.AnnotatedString)
read(seekstart(aio), AnnotatedString)
read(seekstart(aio), String)
```
83 changes: 74 additions & 9 deletions docs/src/index.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# [StyledStrings](@id stdlib-styledstrings)

```@meta
CurrentModule = StyledStrings
DocTestSetup = quote
using StyledStrings
end
```

## [Styling](@id stdlib-styledstrings-styling)

When working with strings, formatting and styling often appear as a secondary
Expand All @@ -19,7 +26,7 @@ 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`](@ref Base.AnnotatedString)). This string type wraps any other
([`AnnotatedString`](@ref StyledStrings.AnnotatedString)). This string type wraps any other
[`AbstractString`](@ref) type and allows for formatting information to be applied to regions (e.g.
characters 1 through to 7 are bold and red).

Expand All @@ -29,15 +36,61 @@ convenience, faces in the global faces dictionary (e.g. `shadow`) can just be
named instead of giving the [`Face`](@ref StyledStrings.Face) directly.

Along with these capabilities, we also provide a convenient way for constructing
[`AnnotatedString`](@ref Base.AnnotatedString)s, detailed in [Styled String
[`AnnotatedString`](@ref StyledStrings.AnnotatedString)s, detailed in [Styled String
Literals](@ref stdlib-styledstring-literals).

```@repl demo
using StyledStrings
styled"{yellow:hello} {blue:there}"
```

## Styling via [`AnnotatedString`](@ref Base.AnnotatedString)s
## [Annotated Strings](@id man-annotated-strings)

It is sometimes useful to be able to hold metadata relating to regions of a
string. A [`AnnotatedString`](@ref StyledStrings.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`](@ref StyledStrings.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](@ref
stdlib-styledstrings), which uses `:face`-labelled annotations to hold styling
information.

When concatenating a [`AnnotatedString`](@ref StyledStrings.AnnotatedString), take care to use
[`annotatedstring`](@ref StyledStrings.annotatedstring) instead of [`string`](@ref) if you want
to keep the string annotations.

```jldoctest
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`](@ref StyledStrings.AnnotatedString) can be accessed
and modified via the [`annotations`](@ref StyledStrings.annotations) and
[`annotate!`](@ref StyledStrings.annotate!) functions.

## Styling via [`AnnotatedString`](@ref StyledStrings.AnnotatedString)s

## [Faces](@id stdlib-styledstrings-faces)

Expand Down Expand Up @@ -135,7 +188,7 @@ On initialization, the `config/faces.toml` file under the first Julia depot (usu
### Applying faces to a `AnnotatedString`

By convention, the `:face` attributes of a [`AnnotatedString`](@ref
Base.AnnotatedString) hold information on the [`Face`](@ref StyledStrings.Face)s
StyledStrings.AnnotatedString) hold information on the [`Face`](@ref StyledStrings.Face)s
that currently apply. This can be given in multiple forms, as a single `Symbol`
naming a [`Face`](@ref StyledStrings.Face)s in the global face dictionary, a
[`Face`](@ref StyledStrings.Face) itself, or a vector of either.
Expand All @@ -149,7 +202,7 @@ them to the properties list afterwards, or use the convenient [Styled String
literals](@ref stdlib-styledstring-literals).

```@repl demo
str1 = Base.AnnotatedString("blue text", [(1:9, :face => :blue)])
str1 = AnnotatedString("blue text", [(1:9, :face => :blue)])
str2 = styled"{blue:blue text}"
str1 == str2
sprint(print, str1, context = :color => true)
Expand All @@ -158,7 +211,7 @@ sprint(show, MIME("text/html"), str1, context = :color => true)

## [Styled String Literals](@id stdlib-styledstring-literals)

To ease construction of [`AnnotatedString`](@ref Base.AnnotatedString)s with [`Face`](@ref StyledStrings.Face)s applied,
To ease construction of [`AnnotatedString`](@ref StyledStrings.AnnotatedString)s with [`Face`](@ref StyledStrings.Face)s applied,
the [`styled"..."`](@ref @styled_str) styled string literal allows for the content and
attributes to be easily expressed together via a custom grammar.

Expand Down Expand Up @@ -271,14 +324,26 @@ arbitrarily nest and overlap, \colorbox[HTML]{3a3a3a}{\color[HTML]{33d079}like

## [API reference](@id stdlib-styledstrings-api)

### Annotated Strings

```@docs
Base.AnnotatedString
Base.AnnotatedChar
Base.annotatedstring
Base.annotations
Base.annotate!
```

### Styling and Faces

```@docs
StyledStrings.@styled_str
StyledStrings.styled
StyledStrings.Face
StyledStrings.addface!
StyledStrings.withfaces
StyledStrings.SimpleColor
Base.parse(::Type{StyledStrings.SimpleColor}, ::String)
Base.tryparse(::Type{StyledStrings.SimpleColor}, ::String)
Base.merge(::StyledStrings.Face, ::StyledStrings.Face)
StyledStrings.parse(::Type{StyledStrings.SimpleColor}, ::String)
StyledStrings.tryparse(::Type{StyledStrings.SimpleColor}, ::String)
StyledStrings.merge(::StyledStrings.Face, ::StyledStrings.Face)
```
5 changes: 4 additions & 1 deletion src/StyledStrings.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@

module StyledStrings

using Base: AnnotatedString, AnnotatedChar, annotations, annotate!, annotatedstring
using Base: AnnotatedString, AnnotatedChar, AnnotatedIOBuffer, annotations, annotate!, annotatedstring
using Base.ScopedValues: ScopedValue, with, @with

# While these are imported from Base, we claim them as part of the `StyledStrings` API.
export AnnotatedString, AnnotatedChar, AnnotatedIOBuffer, annotations, annotate!, annotatedstring

export @styled_str
public Face, addface!, withfaces, styled, SimpleColor

Expand Down
4 changes: 2 additions & 2 deletions src/io.jl
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ Base.print(io::IO, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}) =

# We need to make sure that printing to an `AnnotatedIOBuffer` calls `write` not `print`
# so we get the specialised handling that `_ansi_writer` doesn't provide.
Base.print(io::Base.AnnotatedIOBuffer, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}) =
Base.print(io::AnnotatedIOBuffer, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}) =
(write(io, s); nothing)

Base.escape_string(io::IO, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}},
Expand Down Expand Up @@ -290,7 +290,7 @@ function Base.show(io::IO, c::AnnotatedChar)
end
end

function Base.write(io::IO, aio::Base.AnnotatedIOBuffer)
function Base.write(io::IO, aio::AnnotatedIOBuffer)
if get(io, :color, false) == true
# This does introduce an overhead that technically
# could be avoided, but I'm not sure that it's currently
Expand Down
6 changes: 3 additions & 3 deletions src/legacy.jl
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

module Legacy

using ..StyledStrings: SimpleColor, Face, loadface!, face!
using ..StyledStrings: SimpleColor, Face, loadface!, face!, AnnotatedIOBuffer, annotatedstring

"""
legacy_color(color::Union{String, Symbol, Int})
Expand Down Expand Up @@ -123,11 +123,11 @@ function load_env_colors!()
end
end

function Base.printstyled(io::Base.AnnotatedIOBuffer, msg...;
function Base.printstyled(io::AnnotatedIOBuffer, msg...;
bold::Bool=false, italic::Bool=false, underline::Bool=false,
blink::Bool=false, reverse::Bool=false, hidden::Bool=false,
color::Union{Symbol, Int}=:normal)
str = Base.annotatedstring(msg...)
str = annotatedstring(msg...)
bold && face!(str, :bold)
italic && face!(str, :italic)
underline && face!(str, :underline)
Expand Down
4 changes: 2 additions & 2 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
using Test

using StyledStrings: StyledStrings, Legacy, SimpleColor, FACES, Face,
@styled_str, styled, StyledMarkup, eachregion, getface, addface!, loadface!, resetfaces!
@styled_str, styled, StyledMarkup, eachregion, getface, addface!, loadface!, resetfaces!,
AnnotatedString, AnnotatedChar, AnnotatedIOBuffer, annotations
using .StyledMarkup: MalformedStylingMacro
using Base: AnnotatedString, AnnotatedChar, AnnotatedIOBuffer, annotations

const NON_STDLIB_TESTS = Main == @__MODULE__
if NON_STDLIB_TESTS
Expand Down

0 comments on commit 769e49a

Please sign in to comment.