Skip to content

Commit 43f88eb

Browse files
committed
Introduce AnnotatedIOBuffer
This allows for styled content to be constructed incrementally, without resorting to repeated concatenation. It operates very similarly to IOContext, just with a special `write` method and specifically wrapping an IOBuffer.
1 parent f71228d commit 43f88eb

File tree

2 files changed

+65
-0
lines changed

2 files changed

+65
-0
lines changed

base/show.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,6 +429,9 @@ function show_circular(io::IOContext, @nospecialize(x))
429429
return false
430430
end
431431

432+
AnnotatedIOBuffer(io::IOContext{IOBuffer}) = AnnotatedIOBuffer(io.io, io.dict)
433+
IOContext(io::AnnotatedIOBuffer) = IOContext(io.io, io.dict)
434+
432435
"""
433436
show([io::IO = stdout], x)
434437

base/strings/annotated.jl

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,3 +386,65 @@ annotations(s::SubString{<:AnnotatedString}, pos::UnitRange{<:Integer}) =
386386
Get all annotations of `chr`.
387387
"""
388388
annotations(c::AnnotatedChar) = c.annotations
389+
390+
## AnnotatedIOBuffer
391+
392+
struct AnnotatedIOBuffer <: IO
393+
io::IOBuffer
394+
annotations::Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}
395+
dict::ImmutableDict{Symbol, Any}
396+
end
397+
398+
AnnotatedIOBuffer(io::IOBuffer, dict::ImmutableDict = ImmutableDict{Symbol,Any}()) =
399+
AnnotatedIOBuffer(io, Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}(), dict)
400+
401+
function AnnotatedIOBuffer(io::IOBuffer, (k, v)::Pair{Symbol, Any}, kvs::Pair{Symbol, Any}...)
402+
dict = ImmutableDict{Symbol,Any}(k, v)
403+
for (k, v) in kvs
404+
dict = ImmutableDict{Symbol,Any}(dict, k, v)
405+
end
406+
AnnotatedIOBuffer(io, dict)
407+
end
408+
409+
AnnotatedIOBuffer() = AnnotatedIOBuffer(IOBuffer())
410+
411+
function show(io::IO, annio::AnnotatedIOBuffer)
412+
show(io, AnnotatedIOBuffer)
413+
print(io, '(', annio.io.size, " bytes)")
414+
end
415+
416+
position(io::AnnotatedIOBuffer) = position(io.io)
417+
lock(io::AnnotatedIOBuffer) = lock(io.io)
418+
unlock(io::AnnotatedIOBuffer) = unlock(io.io)
419+
get(io::AnnotatedIOBuffer, key, default) = key == :color || get(io.dict, key, default)
420+
displaysize(io::AnnotatedIOBuffer) =
421+
if haskey(io.dict, :displaysize) io.dict[:displaysize]::Tuple{Int,Int} else displaysize(io.io) end
422+
423+
write(io::AnnotatedIOBuffer, x) = write(io.io, x)
424+
425+
function write(io::AnnotatedIOBuffer, astr::Union{AnnotatedString, SubString{<:AnnotatedString}})
426+
astr = AnnotatedString(astr)
427+
offset = position(io.io)
428+
for (region, annot) in astr.annotations
429+
start, stop = first(region), last(region)
430+
push!(io.annotations, (start+offset:stop+offset, annot))
431+
end
432+
write(io.io, astr)
433+
end
434+
write(io::AnnotatedIOBuffer, achr::AnnotatedChar) = write(aio, AnnotatedString(achr))
435+
436+
# To prevent method ambiguity
437+
write(io::AnnotatedIOBuffer, x::Char) = write(io.io, x)
438+
write(io::AnnotatedIOBuffer, x::Union{SubString{String}, String}) = write(io.io, x)
439+
write(io::AnnotatedIOBuffer, x::AbstractString) = write(io.io, x)
440+
441+
function take!(aiob::AnnotatedIOBuffer)
442+
str = String(take!(aiob.io))
443+
annot = copy(aiob.annotations)
444+
empty!(aiob.annotations)
445+
seekstart(aiob.io)
446+
str, annot
447+
end
448+
449+
AnnotatedString((str, annots)::Tuple{<:AbstractString, Vector{Tuple{UnitRange{Int}, Pair{Symbol, Any}}}}) =
450+
AnnotatedString(str, annots)

0 commit comments

Comments
 (0)