From 769e49a3a4679d5c265856f1ba1a3d327b76d506 Mon Sep 17 00:00:00 2001 From: TEC Date: Sun, 11 Aug 2024 12:21:50 +0800 Subject: [PATCH] Adopt Base's annotated types/functions as API This opens the door to shuffling parts currently implemented in Base into this stdlib without breaking any public APIs. --- docs/src/examples.md | 12 +++---- docs/src/index.md | 83 +++++++++++++++++++++++++++++++++++++++----- src/StyledStrings.jl | 5 ++- src/io.jl | 4 +-- src/legacy.jl | 6 ++-- test/runtests.jl | 4 +-- 6 files changed, 91 insertions(+), 23 deletions(-) diff --git a/docs/src/examples.md b/docs/src/examples.md index 97fbcd07..81b80aa7 100644 --- a/docs/src/examples.md +++ b/docs/src/examples.md @@ -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 @@ -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) ``` diff --git a/docs/src/index.md b/docs/src/index.md index 7f91616f..567758f8 100644 --- a/docs/src/index.md +++ b/docs/src/index.md @@ -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 @@ -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). @@ -29,7 +36,7 @@ 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 @@ -37,7 +44,53 @@ 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) @@ -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. @@ -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) @@ -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. @@ -271,6 +324,18 @@ 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 @@ -278,7 +343,7 @@ 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) ``` diff --git a/src/StyledStrings.jl b/src/StyledStrings.jl index 6d332a32..822d6539 100644 --- a/src/StyledStrings.jl +++ b/src/StyledStrings.jl @@ -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 diff --git a/src/io.jl b/src/io.jl index bbd6efdf..40b4c862 100644 --- a/src/io.jl +++ b/src/io.jl @@ -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}}, @@ -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 diff --git a/src/legacy.jl b/src/legacy.jl index 6b2e320c..e4c92e6b 100644 --- a/src/legacy.jl +++ b/src/legacy.jl @@ -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}) @@ -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) diff --git a/test/runtests.jl b/test/runtests.jl index 9aec2471..c2baad5e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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