From d9d74729674c9b392d478c7d184409a03890bb87 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. It would be nice to actually show the relevant Annotated* docstrings, but unfortunately due to https://github.com/JuliaDocs/Documenter.jl/issues/1781 it's a bit difficult to actually do so. We should re-visit this later. --- docs/src/examples.md | 10 +++---- docs/src/index.md | 64 ++++++++++++++++++++++++++++++++++++++++--- src/StyledStrings.jl | 5 +++- src/io.jl | 4 +-- src/legacy.jl | 6 ++-- src/regioniterator.jl | 2 +- test/runtests.jl | 4 +-- 7 files changed, 77 insertions(+), 18 deletions(-) diff --git a/docs/src/examples.md b/docs/src/examples.md index 97fbcd07..73f1aa69 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 @@ -181,25 +181,25 @@ 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. ```@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 c7591ddc..04f6c628 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 +``` + !!! note The API for StyledStrings and AnnotatedStrings is considered experimental and is subject to change between Julia versions. @@ -41,6 +48,52 @@ using StyledStrings styled"{yellow:hello} {blue:there}" ``` +## [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 Base.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 Base.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 Base.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 Base.AnnotatedString) can be accessed +and modified via the [`annotations`](@ref StyledStrings.annotations) and +[`annotate!`](@ref StyledStrings.annotate!) functions. + ## Styling via [`AnnotatedString`](@ref Base.AnnotatedString)s ## [Faces](@id stdlib-styledstrings-faces) @@ -153,7 +206,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) @@ -275,6 +328,9 @@ arbitrarily nest and overlap, \colorbox[HTML]{3a3a3a}{\color[HTML]{33d079}like ## [API reference](@id stdlib-styledstrings-api) + +### Styling and Faces + ```@docs StyledStrings.@styled_str StyledStrings.styled @@ -282,7 +338,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 1eb4b5a9..69adaf1d 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 4a60121c..2f326706 100644 --- a/src/io.jl +++ b/src/io.jl @@ -263,7 +263,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}}, @@ -293,7 +293,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/src/regioniterator.jl b/src/regioniterator.jl index 0fff37e2..4cbeccf5 100644 --- a/src/regioniterator.jl +++ b/src/regioniterator.jl @@ -28,7 +28,7 @@ an iterator which provides each substring and the applicable annotations as a # Examples ```jldoctest -julia> collect(StyledStrings.eachregion(Base.AnnotatedString( +julia> collect(StyledStrings.eachregion(AnnotatedString( "hey there", [(1:3, :face => :bold), (5:9, :face => :italic)]))) 3-element Vector{Tuple{SubString{String}, Vector{Pair{Symbol, Any}}}}: ("hey", [:face => :bold]) 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