Skip to content

Commit 5f9f311

Browse files
committed
Simplify implementation with eachmatch
1 parent 98411c8 commit 5f9f311

File tree

2 files changed

+35
-53
lines changed

2 files changed

+35
-53
lines changed

base/show.jl

Lines changed: 26 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -48,58 +48,40 @@ show(io::IO, ::MIME"text/plain", c::ComposedFunction) = show(io, c)
4848
show(io::IO, ::MIME"text/plain", c::Returns) = show(io, c)
4949
show(io::IO, ::MIME"text/plain", s::Splat) = show(io, s)
5050

51-
const ansi_regex = r"\G\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])"
51+
const ansi_regex = r"(?s)(?:\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~]))|."
5252

5353
# Pseudo-character representing an ANSI delimiter
5454
struct ANSIDelimiter
5555
del::SubString{String}
5656
end
57-
Base.ncodeunits(c::ANSIDelimiter) = ncodeunits(c.del)
58-
Base.textwidth(::ANSIDelimiter) = 0
57+
ncodeunits(c::ANSIDelimiter) = ncodeunits(c.del)
58+
textwidth(::ANSIDelimiter) = 0
5959

60-
# String wrapper whose indexing yields either a Char or an ANSIDelimiter
61-
struct ANSIString{T<:AbstractString}
62-
str::T
60+
# An iterator similar to `pairs(::String)` but whose values are Char or ANSIDelimiter
61+
struct ANSIIterator
62+
captures::RegexMatchIterator
6363
end
64+
ANSIIterator(s::AbstractString) = ANSIIterator(eachmatch(ansi_regex, s))
6465

65-
Base.IteratorSize(::Type{ANSIString{T}}) where {T} = Base.SizeUnknown()
66-
Base.eltype(::Type{ANSIString{T}}) where {T} = Union{eltype(T),ANSIDelimiter}
67-
function Base.getindex(s::ANSIString, i)
68-
m = match(ansi_regex, s.str, i)
69-
m isa RegexMatch && return ANSIDelimiter(m.match)
70-
return s.str[i]
66+
IteratorSize(::Type{ANSIIterator}) = SizeUnknown()
67+
eltype(::Type{ANSIIterator}) = Pair{Int, Union{Char,ANSIDelimiter}}
68+
function iterate(I::ANSIIterator, (i, m_st)=(1, iterate(I.captures)))
69+
m_st === nothing && return nothing
70+
m, (j, new_m_st) = m_st
71+
c = lastindex(m.match) == 1 ? only(m.match) : ANSIDelimiter(m.match)
72+
return (i => c, (j, iterate(I.captures, (j, new_m_st))))
7173
end
74+
textwidth(I::ANSIIterator) = mapreduce(textwidthlast, +, I; init=0)
7275

73-
function Base.iterate(s::ANSIString, i=firstindex(s.str))
74-
y = iterate(pairs(s), i) # pairs(::ANSIString) is defined as ANSIStringPairs
75-
y === nothing && return nothing
76-
((_, c), next) = y
77-
return (c, next)
78-
end
79-
Base.textwidth(s::ANSIString) = mapreduce(textwidth, +, s; init=0)
80-
81-
# An iterator similar to `pairs(::String)` but whose values are either Char or ANSIDelimiter
82-
struct ANSIStringPairs{T<:AbstractString}
83-
s::ANSIString{T}
84-
end
85-
86-
Base.IteratorSize(::Type{ANSIStringPairs{T}}) where {T} = Base.SizeUnknown()
87-
Base.eltype(::Type{ANSIStringPairs{T}}) where {T} = Pair{Int, Union{eltype(T),ANSIDelimiter}}
88-
function Base.iterate(e::ANSIStringPairs, i=firstindex(e.s.str))
89-
i > ncodeunits(e.s.str) && return nothing
90-
c = e.s[i]
91-
return (i => c, i + ncodeunits(c))
92-
end
93-
Base.pairs(s::ANSIString) = ANSIStringPairs(s)
94-
95-
96-
function _find_lastidx(str, width, chars, truncwidth, ignore_ANSI)
97-
lastidx = 0
98-
truncidx = 0 # if str needs to be truncated, index of truncation.
99-
stop = false # when set, only ANSI delimiters will be kept as new characters.
76+
function _truncate_at_width_or_chars(ignore_ANSI::Bool, str, width, rpad=false, chars="\r\n", truncmark="")
77+
truncwidth = textwidth(truncmark)
78+
(width <= 0 || width < truncwidth) && return ""
79+
wid = truncidx = lastidx = 0
80+
# if str needs to be truncated, truncidx is the index of truncation.
81+
stop = false # once set, only ANSI delimiters will be kept as new characters.
10082
needANSIend = false # set if the last ANSI delimiter before truncidx is not "\033[0m".
101-
wid = 0
102-
for (i, c) in (ignore_ANSI ? pairs(ANSIString(str)) : pairs(str))
83+
I = ignore_ANSI ? ANSIIterator(str) : pairs(str)
84+
for (i, c) in I
10385
if c isa ANSIDelimiter
10486
truncidx == 0 && (needANSIend = c != "\033[0m")
10587
lastidx = i + ncodeunits(c) - 1
@@ -112,20 +94,11 @@ function _find_lastidx(str, width, chars, truncwidth, ignore_ANSI)
11294
stop = wid >= width
11395
end
11496
end
115-
truncidx == 0 && (truncidx = lastidx)
116-
return lastidx, truncidx, needANSIend, wid
117-
end
118-
119-
function _truncate_at_width_or_chars(ignore_ANSI::Bool, str, width, rpad=false, chars="\r\n", truncmark="")
120-
truncwidth = textwidth(truncmark)
121-
(width <= 0 || width < truncwidth) && return rpad ? ' '^width : ""
122-
# possible_substring is a subset of str at least as large as the returned string
123-
possible_substring = SubString(str, 1, thisind(str, min(ncodeunits(str), width+1)))
124-
lastidx, truncidx, needANSIend, wid = _find_lastidx(str, width, chars, truncwidth, ignore_ANSI)
12597
lastidx == 0 && return rpad ? ' '^width : ""
12698
str[lastidx] in chars && (lastidx = prevind(str, lastidx))
12799
ANSIend = needANSIend ? "\033[0m" : ""
128100
pad = rpad ? repeat(' ', max(0, width-wid)) : ""
101+
truncidx == 0 && (truncidx = lastidx)
129102
if lastidx < lastindex(str)
130103
return string(SubString(str, 1, truncidx), ANSIend, truncmark, pad)
131104
else
@@ -197,8 +170,8 @@ function show(io::IO, ::MIME"text/plain", t::AbstractDict{K,V}) where {K,V}
197170
i > rows && break
198171
ks[i] = sprint(show, k, context=recur_io_k, sizehint=0)
199172
vs[i] = sprint(show, v, context=recur_io_v, sizehint=0)
200-
keywidth = clamp(hascolor ? textwidth(ANSIString(ks[i])) : textwidth(ks[i]), keywidth, cols)
201-
valwidth = clamp(hascolor ? textwidth(ANSIString(vs[i])) : textwidth(vs[i]), valwidth, cols)
173+
keywidth = clamp(hascolor ? textwidth(ANSIIterator(ks[i])) : textwidth(ks[i]), keywidth, cols)
174+
valwidth = clamp(hascolor ? textwidth(ANSIIterator(vs[i])) : textwidth(vs[i]), valwidth, cols)
202175
end
203176
if keywidth > max(div(cols, 2), cols - valwidth)
204177
keywidth = max(cld(cols, 3), cols - valwidth)

test/dict.jl

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,15 @@ end
459459
d11 = Dict(RainbowString("abcdefgh", false, true, false) => 0, "123456" => 1)
460460
str11 = sprint(io -> show(io, MIME("text/plain"), d11); context = (:displaysize=>(30,80), :color=>true, :limit=>true))
461461
@test endswith(str11, "\033[0m => 0")
462+
463+
d12 = Dict(RainbowString(repeat(Char(48+i), 4), (i&1)==1, (i&2)==2, (i&4)==4) => i for i in 1:8)
464+
str12 = sprint(io -> show(io, MIME("text/plain"), d12); context = (:displaysize=>(30,80), :color=>true, :limit=>true))
465+
@test !occursin('', str12)
466+
467+
d13 = Dict(RainbowString("foo\nbar") => 74)
468+
str13 = sprint(io -> show(io, MIME("text/plain"), d13); context = (:displaysize=>(30,80), :color=>true, :limit=>true))
469+
@test count('\n', str13) == 1
470+
@test occursin('', str13)
462471
end
463472

464473
@testset "Issue #15739" begin # Compact REPL printouts of an `AbstractDict` use brackets when appropriate

0 commit comments

Comments
 (0)