@@ -459,3 +459,113 @@ function annotated_chartransform(f::Function, str::AnnotatedString, state=nothin
459459 end
460460 AnnotatedString (String (take! (outstr)), annots)
461461end
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(Base.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