Skip to content

Commit abe4303

Browse files
authored
AnnotatedStrings, and a string styling stdlib (#49586)
2 parents 01f6c4c + e5cd9b6 commit abe4303

File tree

21 files changed

+705
-31
lines changed

21 files changed

+705
-31
lines changed

NEWS.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ New language features
88
difference between `public` and `export` is that `public` names do not become
99
available when `using` a package/module. ([#50105])
1010
* `ScopedValue` implement dynamic scope with inheritance across tasks ([#50958]).
11+
* A new `AbstractString` type, `AnnotatedString`, is introduced that allows for
12+
regional annotations to be attached to an underlying string. This type is
13+
particularly useful for holding styling information, and is used extensively
14+
in the new `StyledStrings` standard library. There is also a new `AnnotatedChar`
15+
type, that is the equivalent new `AbstractChar` type.
1116

1217
Language changes
1318
----------------
@@ -51,6 +56,17 @@ New library features
5156
Standard library changes
5257
------------------------
5358

59+
#### StyledStrings
60+
61+
* A new standard library for handling styling in a more comprehensive and structured way.
62+
* The new `Faces` struct serves as a container for text styling information
63+
(think typeface, as well as color and decoration), and comes with a framework
64+
to provide a convenient, extensible (via `addface!`), and customisable (with a
65+
user's `Faces.toml` and `loadfaces!`) approach to
66+
styled content.
67+
* The new `@styled_str` string macro provides a convenient way of creating a
68+
`AnnotatedString` with various faces or other attributes applied.
69+
5470
#### Package Manager
5571

5672
#### LinearAlgebra

base/client.jl

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,10 @@ function exec_options(opts)
271271
interactiveinput = (repl || is_interactive::Bool) && isa(stdin, TTY)
272272
is_interactive::Bool |= interactiveinput
273273

274+
# load terminfo in for styled printing
275+
term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
276+
global current_terminfo = load_terminfo(term_env)
277+
274278
# load ~/.julia/config/startup.jl file
275279
if startup
276280
try
@@ -416,11 +420,9 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Symbol, history_f
416420
end
417421
end
418422
# TODO cleanup REPL_MODULE_REF
419-
420423
if !fallback_repl && interactive && isassigned(REPL_MODULE_REF)
421424
invokelatest(REPL_MODULE_REF[]) do REPL
422425
term_env = get(ENV, "TERM", @static Sys.iswindows() ? "" : "dumb")
423-
global current_terminfo = load_terminfo(term_env)
424426
term = REPL.Terminals.TTYTerminal(term_env, stdin, stdout, stderr)
425427
banner == :no || Base.banner(term, short=banner==:short)
426428
if term.term_type == "dumb"

base/exports.jl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1089,8 +1089,15 @@ public
10891089
Generator,
10901090
ImmutableDict,
10911091
OneTo,
1092+
AnnotatedString,
1093+
AnnotatedChar,
10921094
UUID,
10931095

1096+
# Annotated strings
1097+
annotatedstring,
1098+
annotate!,
1099+
annotations,
1100+
10941101
# Semaphores
10951102
Semaphore,
10961103
acquire,

base/regex.jl

Lines changed: 43 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -212,14 +212,18 @@ julia> hr
212212
"11"
213213
```
214214
"""
215-
struct RegexMatch <: AbstractMatch
216-
match::SubString{String}
217-
captures::Vector{Union{Nothing,SubString{String}}}
215+
struct RegexMatch{S<:AbstractString} <: AbstractMatch
216+
match::SubString{S}
217+
captures::Vector{Union{Nothing,SubString{S}}}
218218
offset::Int
219219
offsets::Vector{Int}
220220
regex::Regex
221221
end
222222

223+
RegexMatch(match::SubString{S}, captures::Vector{Union{Nothing,SubString{S}}},
224+
offset::Union{Int, UInt}, offsets::Vector{Int}, regex::Regex) where {S<:AbstractString} =
225+
RegexMatch{S}(match, captures, offset, offsets, regex)
226+
223227
"""
224228
keys(m::RegexMatch) -> Vector
225229
@@ -423,9 +427,35 @@ function match(re::Regex, str::Union{SubString{String}, String}, idx::Integer,
423427
return result
424428
end
425429

430+
function _annotatedmatch(m::RegexMatch{S}, str::AnnotatedString{S}) where {S<:AbstractString}
431+
RegexMatch{AnnotatedString{S}}(
432+
(@inbounds SubString{AnnotatedString{S}}(
433+
str, m.match.offset, m.match.ncodeunits, Val(:noshift))),
434+
Union{Nothing,SubString{AnnotatedString{S}}}[
435+
if !isnothing(cap)
436+
(@inbounds SubString{AnnotatedString{S}}(
437+
str, cap.offset, cap.ncodeunits, Val(:noshift)))
438+
end for cap in m.captures],
439+
m.offset, m.offsets, m.regex)
440+
end
441+
442+
function match(re::Regex, str::AnnotatedString)
443+
m = match(re, str.string)
444+
if !isnothing(m)
445+
_annotatedmatch(m, str)
446+
end
447+
end
448+
449+
function match(re::Regex, str::AnnotatedString, idx::Integer, add_opts::UInt32=UInt32(0))
450+
m = match(re, str.string, idx, add_opts)
451+
if !isnothing(m)
452+
_annotatedmatch(m, str)
453+
end
454+
end
455+
426456
match(r::Regex, s::AbstractString) = match(r, s, firstindex(s))
427457
match(r::Regex, s::AbstractString, i::Integer) = throw(ArgumentError(
428-
"regex matching is only available for the String type; use String(s) to convert"
458+
"regex matching is only available for the String and AnnotatedString types; use String(s) to convert"
429459
))
430460

431461
findnext(re::Regex, str::Union{String,SubString}, idx::Integer) = _findnext_re(re, str, idx, C_NULL)
@@ -671,18 +701,19 @@ function _replace(io, repl_s::SubstitutionString, str, r, re)
671701
end
672702
end
673703

674-
struct RegexMatchIterator
704+
struct RegexMatchIterator{S <: AbstractString}
675705
regex::Regex
676-
string::String
706+
string::S
677707
overlap::Bool
678708

679-
function RegexMatchIterator(regex::Regex, string::AbstractString, ovr::Bool=false)
680-
new(regex, string, ovr)
681-
end
709+
RegexMatchIterator(regex::Regex, string::AbstractString, ovr::Bool=false) =
710+
new{String}(regex, String(string), ovr)
711+
RegexMatchIterator(regex::Regex, string::AnnotatedString, ovr::Bool=false) =
712+
new{AnnotatedString{String}}(regex, AnnotatedString(String(string.string), string.annotations), ovr)
682713
end
683714
compile(itr::RegexMatchIterator) = (compile(itr.regex); itr)
684-
eltype(::Type{RegexMatchIterator}) = RegexMatch
685-
IteratorSize(::Type{RegexMatchIterator}) = SizeUnknown()
715+
eltype(::Type{<:RegexMatchIterator}) = RegexMatch
716+
IteratorSize(::Type{<:RegexMatchIterator}) = SizeUnknown()
686717

687718
function iterate(itr::RegexMatchIterator, (offset,prevempty)=(1,false))
688719
opts_nonempty = UInt32(PCRE.ANCHORED | PCRE.NOTEMPTY_ATSTART)
@@ -727,7 +758,7 @@ julia> rx = r"a.a"
727758
r"a.a"
728759
729760
julia> m = eachmatch(rx, "a1a2a3a")
730-
Base.RegexMatchIterator(r"a.a", "a1a2a3a", false)
761+
Base.RegexMatchIterator{String}(r"a.a", "a1a2a3a", false)
731762
732763
julia> collect(m)
733764
2-element Vector{RegexMatch}:

0 commit comments

Comments
 (0)