Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
6fe0e25
Parameterise annotations by their value type
tecosaur Dec 25, 2025
5f5a491
Dispatch annotated display on the value type
tecosaur Sep 20, 2026
ea10a54
Make region iteration lazy
tecosaur Sep 21, 2026
8d58551
Bump StyledStrings and JuliaSyntaxHighlighting
tecosaur Sep 28, 2026
db98e85
Use faces from palettes in the stdlib
tecosaur Dec 25, 2025
2ba1a88
Make the terminfo loader safe to trim
tecosaur Sep 20, 2026
6c61f0c
Check styled output in the trim test
tecosaur Sep 27, 2026
a429304
Merge each styling pass's annotations once
tecosaur Sep 23, 2026
cf7e6a5
Keep annotations when joining annotated substrings
tecosaur Sep 27, 2026
bd480e7
Give a substring the eltype of its string
tecosaur Oct 8, 2026
32a8e4f
Sum annotatedstring's size hints in a loop
tecosaur Oct 11, 2026
88c3151
Keep the types of completion and history strings
tecosaur Oct 13, 2026
2f59104
Display annotations through each value type's state
tecosaur Oct 15, 2026
e09cdb8
Drop the labels of annotations
tecosaur Oct 15, 2026
ea5bf0b
Deprecate the labelled forms of annotations
tecosaur Oct 15, 2026
24b3e7f
Display any value type through its concrete types, compiled once
tecosaur Oct 16, 2026
997a800
Read labelled annotations wherever they are given
tecosaur Oct 16, 2026
c8138c8
Document how annotations display
tecosaur Oct 16, 2026
575ec10
Specialise annotated replace on its pairs
tecosaur Oct 16, 2026
f5996de
Remove duplicated replace tests
tecosaur Oct 16, 2026
7a166d2
Render only the annotations that display
tecosaur Oct 16, 2026
5d29754
Add NEWS for annotated strings and StyledStrings
tecosaur Sep 22, 2026
6ea1e4c
Bump StyledStrings
tecosaur Oct 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,16 @@ New library functions
New library features
--------------------

* `AnnotatedString` and `AnnotatedChar` take a second type parameter, the type of their annotation
values, and `AnnotatedIOBuffer` takes it as its only parameter. It is inferred from the
annotations given, and widened as strings are combined. A type written without it, such as
`Vector{AnnotatedString{String}}`, no longer names a concrete type. Packages set how values of
their types display through the functions of `Base.AnnotatedDisplay` ([#60527]).
* Annotations no longer have a label: the type of an annotation's value says what it is. A string's
annotations are `(; region, value)` NamedTuples, given as `(region, value)`, `region => value`, or
with `annotate!(str, [range], value)`. The labelled forms are deprecated. Code that reads
annotations from both older and newer versions can check
`a.value isa Face || (hasproperty(a, :label) && a.label === :face)` ([#60527]).
* `IOContext` supports a new boolean `hexunsigned` option that allows for printing unsigned integers in
decimal instead of hexadecimal ([#60267]).
* `lazy"..."` strings now support a flag `lazy"..."c` that adds `compact` and `limit` flags to the
Expand Down Expand Up @@ -274,6 +284,12 @@ Standard library changes

#### JuliaSyntaxHighlighting

* Highlighting annotates with `Face`s rather than `Symbol` face names, from a palette in the
`julia` namespace, so the faces are still customised as `julia_keyword` and so on. Other
packages can use them as `face"JuliaSyntaxHighlighting.keyword"`, or after
`@usepalette JuliaSyntaxHighlighting`. `highlight!` needs a string whose value type can hold a
`Face` ([#60527]).

#### Libdl

* `Libdl.LazyLibrary` is not compatible with `--trim`.
Expand All @@ -291,6 +307,8 @@ Standard library changes
* Many, many bug fixes and minor tweaks; overall behavior is now much closer to CommonMark ([#59977], [#60502]).
* Table columns whose delimiter cell has no `:` (such as `---`) are now left-aligned, as on
GitHub, instead of right-aligned.
* Markdown's faces form a palette, so they are customised in `faces.toml` under `[Markdown]`
rather than `[markdown]` ([#60527]).

### Mmap

Expand Down Expand Up @@ -326,6 +344,17 @@ Standard library changes
array on all processes, e.g. so the file backing a file-backed `SharedArray` can be deleted
immediately ([#62488]).

#### StyledStrings

* Named faces are `Face` objects rather than `Symbol`s, looked up with `face"name"`. Packages define
theirs in palettes with `@defpalette` and `@registerpalette`, which also hold light and dark
variants, and `@usepalette` brings other modules' palettes into scope. In a module with a palette,
`styled"..."` and `face"..."` reject an unknown face name. Styled strings hold `Face` and `Link`
values, with the value type `StyledValue`, and other `{key=value:…}` annotations are
`Pair{Symbol, Any}` values. `remapfaces` substitutes the faces of a string, and a `faces.toml`
value of `"inherit"` resets an attribute to what the face inherits. Annotations whose values are
`Symbol`s naming faces are no longer styled ([#60527]).

#### Test
* Pressing `^C` twice at an empty `julia>` prompt now cancels all still-running
work started by earlier evaluations (e.g. a runaway `@async` task spewing
Expand Down
64 changes: 64 additions & 0 deletions base/deprecated.jl
Original file line number Diff line number Diff line change
Expand Up @@ -747,4 +747,68 @@ end
@noinline tan_domain_error(x) = throw_finite_domainerror(:tan, x)
end

# Annotations with labels, from before the type of a value said what it is (#60527)

# The kind of annotation a label stands for: a type of value, or by default the label itself, as
# the key of a `Pair{Symbol, Any}`. StyledStrings gives `:face` and `:link` their types.
labelled_kind(::Val{label}) where {label} = label

# The value a labelled annotation stands for, which a package can give for its own labels: by
# default its value when it is of the label's kind, and otherwise `label => value`. Every labelled
# form but deletion reads its values here.
function labelled_value(label::Symbol, value)
depwarn("annotations with a label are deprecated; give the value alone, whose type says what it is", :labelled_value)
labelled_value(Val(label), value)
end
function labelled_value(::Val{label}, value) where {label}
kind = labelled_kind(Val(label))
if kind isa Type && value isa kind value else Pair{Symbol, Any}(label, value) end
end

# A string reads labelled annotations as it reads each annotation, in `AnnotatedString{S}`

function AnnotatedChar(c::AbstractChar, annots::Vector{<:Union{Tuple{Symbol, Any}, NamedTuple{(:label, :value)}}})
isempty(annots) && return AnnotatedChar(c, Any[])
AnnotatedChar(c, [labelled_value(label, value) for (label, value) in annots])
end
AnnotatedChar(c::AnnotatedChar, annots::Vector{<:Union{Tuple{Symbol, Any}, NamedTuple{(:label, :value)}}}) =
AnnotatedChar(c, AnnotatedChar(c.char, annots).annotations)

function _annotate!(annlist::Vector{<:RegionAnnotation}, region::UnitRange{Int}, label::Symbol, value)
if isnothing(value)
kind = labelled_kind(Val(label))
deleteat!(annlist, findall(annlist) do annot
annot.region == region &&
if kind isa Type annot.value isa kind else annot.value isa Pair{Symbol} && first(annot.value) === kind end
end)
else
_annotate!(annlist, region, labelled_value(label, value))
end
end

"""
annotate!(str, [range::UnitRange{Int}], label::Symbol, nothing)

Remove the annotations over exactly `range` of the kind that `label` stands for: `Face`s for
`:face` and `Link`s for `:link`, with StyledStrings, and otherwise `label => value` pairs. With
a value other than `nothing`, this labelled form is deprecated, and annotates with the value
when it is of that kind, or else with `label => value`.
"""
annotate!(s::AnnotatedString, range::UnitRange{Int}, label::Symbol, val) =
(_annotate!(s.annotations, range, label, val); s)

annotate!(ss::AnnotatedString, label::Symbol, val) =
annotate!(ss, firstindex(ss):lastindex(ss), label, val)

annotate!(s::SubString{<:AnnotatedString}, range::UnitRange{Int}, label::Symbol, val) =
(annotate!(s.string, s.offset .+ (range), label, val); s)

annotate!(s::SubString{<:AnnotatedString}, label::Symbol, val) =
(annotate!(s.string, s.offset .+ (1:s.ncodeunits), label, val); s)

annotate!(io::AnnotatedIOBuffer, range::UnitRange{Int}, label::Symbol, @nospecialize(val::Any)) =
(_annotate!(io.annotations, range, label, val); io)

annotate!(c::AnnotatedChar, label::Symbol, value) = annotate!(c, labelled_value(label, value))

# END 1.14 deprecations
6 changes: 3 additions & 3 deletions base/logging/ConsoleLogger.jl
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ function handle_message(logger::ConsoleLogger, level::LogLevel, message, _module
# split into lines. This is specialised to improve type inference,
# and reduce the risk of resulting method invalidations.
message = string(message)
msglines = if Base._isannotated(message) && !isempty(Base.annotations(message)::Vector{Base.RegionAnnotation})
message = Base.AnnotatedString(String(message)::String, Base.annotations(message)::Vector{Base.RegionAnnotation})
@NamedTuple{indent::Int, msg::Union{SubString{Base.AnnotatedString{String}}, SubString{String}}}[
msglines = if Base._isannotated(message)
message = Base.AnnotatedString{String, Any}(String(message)::String, Base.annotations(message))
@NamedTuple{indent::Int, msg::Union{SubString{Base.AnnotatedString{String, Any}}, SubString{String}}}[
(indent=0, msg=l) for l in split(chomp(message), '\n')]
else
[(indent=0, msg=l) for l in split(
Expand Down
10 changes: 5 additions & 5 deletions base/regex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -453,10 +453,10 @@ function match(re::Regex, str::DenseUTF8String, idx::Integer,
return result
end

function _annotatedmatch(m::RegexMatch{S}, str::AnnotatedString{S}) where {S<:AbstractString}
RegexMatch{AnnotatedString{S}}(
function _annotatedmatch(m::RegexMatch{S}, str::AnnotatedString{S, V}) where {S, V}
RegexMatch{AnnotatedString{S, V}}(
(@inbounds raw_substring(str, m.match.offset + 1, m.match.ncodeunits)),
Union{Nothing,SubString{AnnotatedString{S}}}[
Union{Nothing,SubString{AnnotatedString{S, V}}}[
if !isnothing(cap)
(@inbounds raw_substring(str, cap.offset + 1, cap.ncodeunits))
end for cap in m.captures],
Expand Down Expand Up @@ -739,8 +739,8 @@ struct RegexMatchIterator{S <: AbstractString}

RegexMatchIterator(regex::Regex, string::AbstractString, ovr::Bool=false) =
new{String}(regex, String(string), ovr)
RegexMatchIterator(regex::Regex, string::AnnotatedString, ovr::Bool=false) =
new{AnnotatedString{String}}(regex, AnnotatedString(String(string.string), string.annotations), ovr)
RegexMatchIterator(regex::Regex, string::AnnotatedString{S, V}, ovr::Bool=false) where {S, V} =
new{AnnotatedString{String, V}}(regex, AnnotatedString(String(string.string), string.annotations), ovr)
end
compile(itr::RegexMatchIterator) = (compile(itr.regex); itr)
eltype(::Type{<:RegexMatchIterator}) = RegexMatch
Expand Down
Loading
Loading