• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

JuliaLang / julia / 1293

03 Oct 2025 01:05AM UTC coverage: 76.952% (-0.1%) from 77.068%
1293

push

buildkite

web-flow
Support superscript small q (#59544)

Co-authored-by: Steven G. Johnson <stevenj@alum.mit.edu>

61282 of 79637 relevant lines covered (76.95%)

21700458.33 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

48.91
/base/strings/annotated_io.jl
1
# This file is a part of Julia. License is MIT: https://julialang.org/license
2

3
## AnnotatedIOBuffer
4

5
struct AnnotatedIOBuffer <: AbstractPipe
6
    io::IOBuffer
252✔
7
    annotations::Vector{RegionAnnotation}
8
end
9

10
AnnotatedIOBuffer(io::IOBuffer) = AnnotatedIOBuffer(io, Vector{RegionAnnotation}())
252✔
11
AnnotatedIOBuffer() = AnnotatedIOBuffer(IOBuffer())
252✔
12

13
function show(io::IO, aio::AnnotatedIOBuffer)
14
    show(io, AnnotatedIOBuffer)
2✔
15
    size = filesize(aio.io)
2✔
16
    print(io, '(', size, " byte", ifelse(size == 1, "", "s"), ", ",
2✔
17
          length(aio.annotations), " annotation", ifelse(length(aio.annotations) == 1, "", "s"), ")")
18
end
19

20
pipe_reader(io::AnnotatedIOBuffer) = io.io
290✔
21
pipe_writer(io::AnnotatedIOBuffer) = io.io
4,831✔
22

23
# Useful `IOBuffer` methods that we don't get from `AbstractPipe`
24
position(io::AnnotatedIOBuffer) = position(io.io)
411✔
25
seek(io::AnnotatedIOBuffer, n::Integer) = (seek(io.io, n); io)
468✔
26
seekend(io::AnnotatedIOBuffer) = (seekend(io.io); io)
×
27
skip(io::AnnotatedIOBuffer, n::Integer) = (skip(io.io, n); io)
2✔
28
copy(io::AnnotatedIOBuffer) = AnnotatedIOBuffer(copy(io.io), copy(io.annotations))
×
29

30
annotations(io::AnnotatedIOBuffer) = io.annotations
×
31

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

35
function write(io::AnnotatedIOBuffer, astr::Union{AnnotatedString, SubString{<:AnnotatedString}})
26✔
36
    astr = AnnotatedString(astr)
274✔
37
    offset = position(io.io)
274✔
38
    eof(io) || _clear_annotations_in_region!(io.annotations, offset+1:offset+ncodeunits(astr))
282✔
39
    _insert_annotations!(io, astr.annotations)
274✔
40
    write(io.io, String(astr))
274✔
41
end
42

43
write(io::AnnotatedIOBuffer, c::AnnotatedChar) =
16✔
44
    write(io, AnnotatedString(string(c), [(region=1:ncodeunits(c), a...) for a in c.annotations]))
45
write(io::AnnotatedIOBuffer, x::AbstractString) = write(io.io, x)
×
46
write(io::AnnotatedIOBuffer, s::Union{SubString{String}, String}) = write(io.io, s)
322✔
47
write(io::AnnotatedIOBuffer, b::UInt8) = write(io.io, b)
80✔
48

49
function write(dest::AnnotatedIOBuffer, src::AnnotatedIOBuffer)
×
50
    destpos = position(dest)
×
51
    isappending = eof(dest)
×
52
    srcpos = position(src)
×
53
    nb = write(dest.io, src.io)
×
54
    isappending || _clear_annotations_in_region!(dest.annotations, destpos:destpos+nb)
×
55
    srcannots = [setindex(annot, max(1 + srcpos, first(annot.region)):last(annot.region), :region)
×
56
                 for annot in src.annotations if first(annot.region) >= srcpos]
57
    _insert_annotations!(dest, srcannots, destpos - srcpos)
×
58
    nb
×
59
end
60

61
# So that read/writes with `IOContext` (and any similar `AbstractPipe` wrappers)
62
# work as expected.
63
function write(io::AbstractPipe, s::Union{AnnotatedString, SubString{<:AnnotatedString}})
2✔
64
    if pipe_writer(io) isa AnnotatedIOBuffer
2✔
65
        write(pipe_writer(io), s)
2✔
66
    else
67
        invoke(write, Tuple{IO, typeof(s)}, io, s)
×
68
    end::Int
69
end
70

71
# Can't be part of the `Union` above because it introduces method ambiguities
72
function write(io::AbstractPipe, c::AnnotatedChar)
2✔
73
    if pipe_writer(io) isa AnnotatedIOBuffer
2✔
74
        write(pipe_writer(io), c)
2✔
75
    else
76
        invoke(write, Tuple{IO, typeof(c)}, io, c)
×
77
    end::Int
78
end
79

80
function read(io::AnnotatedIOBuffer, ::Type{AnnotatedString{T}}) where {T <: AbstractString}
5✔
81
    if (start = position(io)) == 0
5✔
82
        AnnotatedString(read(io.io, T), copy(io.annotations))
5✔
83
    else
84
        annots = [setindex(annot, UnitRange{Int}(max(1, first(annot.region) - start), last(annot.region)-start), :region)
×
85
                  for annot in io.annotations if last(annot.region) > start]
86
        AnnotatedString(read(io.io, T), annots)
×
87
    end
88
end
89
read(io::AnnotatedIOBuffer, ::Type{AnnotatedString{AbstractString}}) = read(io, AnnotatedString{String})
×
90
read(io::AnnotatedIOBuffer, ::Type{AnnotatedString}) = read(io, AnnotatedString{String})
172✔
91

92
function read(io::AnnotatedIOBuffer, ::Type{AnnotatedChar{T}}) where {T <: AbstractChar}
×
93
    pos = position(io)
×
94
    char = read(io.io, T)
×
95
    annots = [NamedTuple{(:label, :value)}(annot) for annot in io.annotations if pos+1 in annot.region]
×
96
    AnnotatedChar(char, annots)
×
97
end
98
read(io::AnnotatedIOBuffer, ::Type{AnnotatedChar{AbstractChar}}) = read(io, AnnotatedChar{Char})
×
99
read(io::AnnotatedIOBuffer, ::Type{AnnotatedChar}) = read(io, AnnotatedChar{Char})
×
100

101
function truncate(io::AnnotatedIOBuffer, size::Integer)
8✔
102
    truncate(io.io, size)
8✔
103
    filter!(ann -> first(ann.region) <= size, io.annotations)
24✔
104
    map!(ann -> setindex(ann, first(ann.region):min(size, last(ann.region)), :region),
16✔
105
         io.annotations, io.annotations)
106
    io
8✔
107
end
108

109
"""
110
    _clear_annotations_in_region!(annotations::Vector{$RegionAnnotation}, span::UnitRange{Int})
111

112
Erase the presence of `annotations` within a certain `span`.
113

114
This operates by removing all elements of `annotations` that are entirely
115
contained in `span`, truncating ranges that partially overlap, and splitting
116
annotations that subsume `span` to just exist either side of `span`.
117
"""
118
function _clear_annotations_in_region!(annotations::Vector{RegionAnnotation}, span::UnitRange{Int})
×
119
    # Clear out any overlapping pre-existing annotations.
120
    filter!(ann -> first(ann.region) < first(span) || last(ann.region) > last(span), annotations)
×
121
    extras = Tuple{Int, RegionAnnotation}[]
×
122
    for i in eachindex(annotations)
×
123
        annot = annotations[i]
×
124
        region = annot.region
×
125
        # Test for partial overlap
126
        if first(region) <= first(span) <= last(region) || first(region) <= last(span) <= last(region)
×
127
            annotations[i] =
×
128
                setindex(annot,
129
                         if first(region) < first(span)
130
                             first(region):first(span)-1
×
131
                         else
132
                             last(span)+1:last(region)
×
133
                         end,
134
                         :region)
135
            # If `span` fits exactly within `region`, then we've only copied over
136
            # the beginning overhang, but also need to conserve the end overhang.
137
            if first(region) < first(span) && last(span) < last(region)
×
138
                push!(extras, (i, setindex(annot, last(span)+1:last(region), :region)))
×
139
            end
140
        end
141
    end
×
142
    # Insert any extra entries in the appropriate position
143
    for (offset, (i, entry)) in enumerate(extras)
×
144
        insert!(annotations, i + offset, entry)
×
145
    end
×
146
    annotations
×
147
end
148

149
"""
150
    _insert_annotations!(io::AnnotatedIOBuffer, annotations::Vector{$RegionAnnotation}, offset::Int = position(io))
151

152
Register new `annotations` in `io`, applying an `offset` to their regions.
153

154
The largely consists of simply shifting the regions of `annotations` by `offset`
155
and pushing them onto `io`'s annotations. However, when it is possible to merge
156
the new annotations with recent annotations in accordance with the semantics
157
outlined in [`AnnotatedString`](@ref), we do so. More specifically, when there
158
is a run of the most recent annotations that are also present as the first
159
`annotations`, with the same value and adjacent regions, the new annotations are
160
merged into the existing recent annotations by simply extending their range.
161

162
This is implemented so that one can say write an `AnnotatedString` to an
163
`AnnotatedIOBuffer` one character at a time without needlessly producing a
164
new annotation for each character.
165
"""
166
function _insert_annotations!(io::AnnotatedIOBuffer, annotations::Vector{RegionAnnotation}, offset::Int = position(io))
8✔
167
    run = 0
282✔
168
    if !isempty(io.annotations) && last(last(io.annotations).region) == offset
8✔
169
        for i in reverse(axes(annotations, 1))
4✔
170
            annot = annotations[i]
4✔
171
            first(annot.region) == 1 || continue
4✔
172
            i <= length(io.annotations) || continue
4✔
173
            if annot.label == last(io.annotations).label && annot.value == last(io.annotations).value
4✔
174
                valid_run = true
×
175
                for runlen in 1:i
×
176
                    new = annotations[begin+runlen-1]
×
177
                    old = io.annotations[end-i+runlen]
×
178
                    if last(old.region) != offset || first(new.region) != 1 || old.label != new.label || old.value != new.value
×
179
                        valid_run = false
×
180
                        break
×
181
                    end
182
                end
×
183
                if valid_run
×
184
                    run = i
×
185
                    break
×
186
                end
187
            end
188
        end
4✔
189
    end
190
    for runindex in 0:run-1
8✔
191
        old_index = lastindex(io.annotations) - run + 1 + runindex
×
192
        old = io.annotations[old_index]
×
193
        new = annotations[begin+runindex]
×
194
        io.annotations[old_index] = setindex(old, first(old.region):last(new.region)+offset, :region)
×
195
    end
×
196
    for index in run+1:lastindex(annotations)
8✔
197
        annot = annotations[index]
15✔
198
        start, stop = first(annot.region), last(annot.region)
15✔
199
        push!(io.annotations, setindex(annotations[index], start+offset:stop+offset, :region))
15✔
200
    end
15✔
201
end
202

203
function printstyled end
204

205
# NOTE: This is an interim solution to the invalidations caused
206
# by the split styled display implementation. This should be
207
# replaced by a more robust solution (such as a consolidation of
208
# the type and method definitions) in the near future.
209
module AnnotatedDisplay
210

211
using ..Base: IO, SubString, AnnotatedString, AnnotatedChar, AnnotatedIOBuffer
212
using ..Base: eachregion, invoke_in_world, tls_world_age
213

214
# Write
215

216
ansi_write(f::Function, io::IO, x::Any) = f(io, String(x))
×
217

218
ansi_write_(f::Function, io::IO, @nospecialize(x::Any)) =
11,916✔
219
    invoke_in_world(tls_world_age(), ansi_write, f, io, x)
220

221
Base.write(io::IO, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}) =
×
222
    ansi_write_(write, io, s)::Int
223

224
Base.write(io::IO, c::AnnotatedChar) =
×
225
    ansi_write_(write, io, c)::Int
226

227
function Base.write(io::IO, aio::AnnotatedIOBuffer)
4✔
228
    if get(io, :color, false) == true
4✔
229
        # This does introduce an overhead that technically
230
        # could be avoided, but I'm not sure that it's currently
231
        # worth the effort to implement an efficient version of
232
        # writing from an AnnotatedIOBuffer with style.
233
        # In the meantime, by converting to an `AnnotatedString` we can just
234
        # reuse all the work done to make that work.
235
        ansi_write_(write, io, read(aio, AnnotatedString))::Int
2✔
236
    else
237
        write(io, aio.io)
2✔
238
    end
239
end
240

241
# Print
242

243
Base.print(io::IO, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}) =
11,440✔
244
    (ansi_write_(write, io, s); nothing)
2,565✔
245

246
Base.print(io::IO, s::AnnotatedChar) =
472✔
247
    (ansi_write_(write, io, s); nothing)
472✔
248

249
Base.print(io::AnnotatedIOBuffer, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}) =
230✔
250
    (write(io, s); nothing)
58✔
251

252
Base.print(io::AnnotatedIOBuffer, c::AnnotatedChar) =
×
253
    (write(io, c); nothing)
×
254

255
styled_print(io::AnnotatedIOBuffer, msg::Any, kwargs::Any) = print(io, msg...)
×
256

257
styled_print_(io::AnnotatedIOBuffer, @nospecialize(msg), @nospecialize(kwargs)) =
10✔
258
    invoke_in_world(tls_world_age(), styled_print, io, msg, kwargs)::Nothing
259

260
Base.printstyled(io::AnnotatedIOBuffer, msg...; kwargs...) =
20✔
261
    styled_print_(io, msg, kwargs)
262

263
# Escape
264

265
Base.escape_string(io::IO, s::Union{<:AnnotatedString, SubString{<:AnnotatedString}},
266
              esc = ""; keep = (), ascii::Bool=false, fullhex::Bool=false) =
4✔
267
    (ansi_write_((io, s) -> escape_string(io, s, esc; keep, ascii, fullhex), io, s); nothing)
4✔
268

269
# Show
270

271
show_annot(io::IO, ::Any) = nothing
×
272
show_annot(io::IO, ::MIME, ::Any) = nothing
×
273

274
show_annot_(io::IO, @nospecialize(x::Any)) =
×
275
    invoke_in_world(tls_world_age(), show_annot, io, x)::Nothing
276

277
show_annot_(io::IO, m::MIME, @nospecialize(x::Any)) =
4✔
278
    invoke_in_world(tls_world_age(), show_annot, io, m, x)::Nothing
279

280
Base.show(io::IO, m::MIME"text/html", s::Union{<:AnnotatedString, SubString{<:AnnotatedString}}) =
4✔
281
    show_annot_(io, m, s)
282

283
Base.show(io::IO, m::MIME"text/html", c::AnnotatedChar) =
×
284
    show_annot_(io, m, c)
285

286
end
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc