Skip to content

Commit e72c867

Browse files
committed
Add eachregion(::AnnotatedString) implementation to Base
1 parent 96b00c7 commit e72c867

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed

base/strings/annotated.jl

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,3 +459,113 @@ function annotated_chartransform(f::Function, str::AnnotatedString, state=nothin
459459
end
460460
AnnotatedString(String(take!(outstr)), annots)
461461
end
462+
463+
struct RegionIterator{S <: AbstractString}
464+
str::S
465+
regions::Vector{UnitRange{Int}}
466+
annotations::Vector{Vector{NamedTuple{(:label,:value),Tuple{Symbol,Any}}}}
467+
end
468+
469+
length(si::RegionIterator) = length(si.regions)
470+
471+
@propagate_inbounds function iterate(si::RegionIterator, i::Integer=1)
472+
if i <= length(si.regions)
473+
@inbounds ((SubString(si.str, si.regions[i]), si.annotations[i]), i+1)
474+
end
475+
end
476+
477+
eltype(::RegionIterator{S}) where { S <: AbstractString} =
478+
Tuple{SubString{S}, Vector{NamedTuple{(:label,:value),Tuple{Symbol,Any}}}}
479+
480+
"""
481+
eachregion(s::AnnotatedString{S})
482+
eachregion(s::SubString{AnnotatedString{S}})
483+
484+
Identify the contiguous substrings of `s` with a constant annotations, and return
485+
an iterator which provides each substring and the applicable annotations as a
486+
`Tuple{SubString{S}, Vector{NamedTuple{(:label,:value),Tuple{Symbol,Any}}}}`.
487+
488+
# Examples
489+
490+
```jldoctest
491+
julia> collect(eachregion(AnnotatedString(
492+
"hey there", [(1:3, :face, :bold), (5:9, :face, :italic)])))
493+
3-element Vector{Tuple{SubString{String}, Vector{@NamedTuple{label::Symbol, value}}}}:
494+
("hey", [@NamedTuple{label::Symbol, value}((:face, :bold))])
495+
(" ", [])
496+
("there", [@NamedTuple{label::Symbol, value}((:face, :italic))])
497+
```
498+
"""
499+
function eachregion(s::AnnotatedString, subregion::UnitRange{Int}=firstindex(s):lastindex(s))
500+
isempty(s) || isempty(subregion) &&
501+
return RegionIterator(s.string, UnitRange{Int}[], Vector{NamedTuple{(:label,:value),Tuple{Symbol,Any}}}[])
502+
events = annotation_events(s, subregion)
503+
isempty(events) && return RegionIterator(s.string, [subregion], [NamedTuple{(:label,:value),Tuple{Symbol,Any}}[]])
504+
annotvals = NamedTuple{(:label,:value),Tuple{Symbol,Any}}[
505+
(; label, value) for (; label, value) in annotations(s)]
506+
regions = Vector{UnitRange{Int}}()
507+
annots = Vector{Vector{NamedTuple{(:label,:value),Tuple{Symbol,Any}}}}()
508+
pos = first(events).pos
509+
if pos > first(subregion)
510+
push!(regions, thisind(s, first(subregion)):prevind(s, pos))
511+
push!(annots, [])
512+
end
513+
activelist = Int[]
514+
for event in events
515+
if event.pos != pos
516+
push!(regions, pos:prevind(s, event.pos))
517+
push!(annots, annotvals[activelist])
518+
pos = event.pos
519+
end
520+
if event.active
521+
insert!(activelist, searchsortedfirst(activelist, event.index), event.index)
522+
else
523+
deleteat!(activelist, searchsortedfirst(activelist, event.index))
524+
end
525+
end
526+
if last(events).pos < nextind(s, last(subregion))
527+
push!(regions, last(events).pos:thisind(s, last(subregion)))
528+
push!(annots, [])
529+
end
530+
RegionIterator(s.string, regions, annots)
531+
end
532+
533+
function eachregion(s::SubString{<:AnnotatedString}, pos::UnitRange{Int}=firstindex(s):lastindex(s))
534+
if isempty(s)
535+
RegionIterator(
536+
s.string,
537+
Vector{UnitRange{Int}}(),
538+
Vector{Vector{NamedTuple{(:label,:value),Tuple{Symbol,Any}}}}(),
539+
)
540+
else
541+
eachregion(s.string, first(pos)+s.offset:last(pos)+s.offset)
542+
end
543+
end
544+
545+
"""
546+
annotation_events(string::AbstractString, annots::Vector{@NamedTuple{region::UnitRange{Int},label::Symbol,value::Any}}, subregion::UnitRange{Int})
547+
annotation_events(string::AnnotatedString, subregion::UnitRange{Int})
548+
549+
Find all annotation "change events" that occur within a `subregion` of `annots`,
550+
with respect to `string`. When `string` is styled, `annots` is inferred.
551+
552+
Each change event is given in the form of a `@NamedTuple{pos::Int, active::Bool,
553+
index::Int}` where `pos` is the position of the event, `active` is a boolean
554+
indicating whether the annotation is being activated or deactivated, and `index`
555+
is the index of the annotation in question.
556+
"""
557+
function annotation_events(s::AbstractString, annots::Vector{NamedTuple{(:region,:label,:value),Tuple{UnitRange{Int},Symbol,Any}}}, subregion::UnitRange{Int})
558+
events = Vector{NamedTuple{(:pos, :active, :index), Tuple{Int, Bool, Int}}}() # Position, Active?, Annotation index
559+
for (i, (; region)) in enumerate(annots)
560+
if !isempty(intersect(subregion, region))
561+
start, stop = max(first(subregion), first(region)), min(last(subregion), last(region))
562+
start <= stop || continue # Currently can't handle empty regions
563+
push!(events, (pos=thisind(s, start), active=true, index=i))
564+
push!(events, (pos=nextind(s, stop), active=false, index=i))
565+
end
566+
end
567+
sort(events, by=e -> e.pos)
568+
end
569+
570+
annotation_events(s::AnnotatedString, subregion::UnitRange{Int}) =
571+
annotation_events(s.string, annotations(s), subregion)

0 commit comments

Comments
 (0)