Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: handle colors in Dict limit printing #37568

Merged
merged 3 commits into from
May 11, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 0 additions & 21 deletions base/dict.jl
Original file line number Diff line number Diff line change
@@ -1,26 +1,5 @@
# This file is a part of Julia. License is MIT: https://julialang.org/license

function _truncate_at_width_or_chars(str, width, chars="", truncmark="…")
truncwidth = textwidth(truncmark)
(width <= 0 || width < truncwidth) && return ""

wid = truncidx = lastidx = 0
for (idx, c) in pairs(str)
lastidx = idx
wid += textwidth(c)
wid >= width - truncwidth && truncidx == 0 && (truncidx = lastidx)
(wid >= width || c in chars) && break
end

lastidx != 0 && str[lastidx] in chars && (lastidx = prevind(str, lastidx))
truncidx == 0 && (truncidx = lastidx)
if lastidx < lastindex(str)
return String(SubString(str, 1, truncidx) * truncmark)
else
return String(str)
end
end

function show(io::IO, t::AbstractDict{K,V}) where V where K
recur_io = IOContext(io, :SHOWN_SET => t,
:typeinfo => eltype(t))
Expand Down
60 changes: 56 additions & 4 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,58 @@ end
show(io::IO, ::MIME"text/plain", c::ComposedFunction) = show(io, c)
show(io::IO, ::MIME"text/plain", c::Returns) = show(io, c)

const ansi_regex = r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])"
# An iterator similar to `pairs` but skips over "tokens" corresponding to
# ansi sequences
struct IgnoreAnsiIterator
captures::Base.RegexMatchIterator
end
IgnoreAnsiIterator(s::AbstractString) =
IgnoreAnsiIterator(eachmatch(ansi_regex, s))

Base.IteratorSize(::Type{IgnoreAnsiIterator}) = Base.SizeUnknown()
function iterate(I::IgnoreAnsiIterator, (i, m_st)=(1, iterate(I.captures)))
# Advance until the next non ansi sequence
if m_st !== nothing
m, j = m_st
if m.offset == i
i += sizeof(m.match)
return iterate(I, (i, iterate(I.captures, j)))
end
end
ci = iterate(I.captures.string, i)
ci === nothing && return nothing
i_prev = i
(c, i) = ci
return (i_prev => c), (i, m_st)
end

function _truncate_at_width_or_chars(io::IO, str, width, chars="", truncmark="…")
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure it is worthwhile to compile this function for every io type it might encounter (which is usually on the order of 5 with various IOContext/TTY/IOBuffer combinations)

Suggested change
function _truncate_at_width_or_chars(io::IO, str, width, chars="", truncmark="")
function _truncate_at_width_or_chars(ignore_ansi::Bool, str, width, chars="", truncmark="")

Copy link
Sponsor Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Updated.

truncwidth = textwidth(truncmark)
(width <= 0 || width < truncwidth) && return ""
wid = truncidx = lastidx = 0
color = get(io, :color, false) && match(ansi_regex, str) !== nothing
color && (truncmark = "\e[0m" * truncmark)
I = color ? IgnoreAnsiIterator(str) : pairs(str)
for (_lastidx, c) in I
lastidx = _lastidx
wid += textwidth(c)
if wid >= (width - truncwidth) && truncidx == 0
truncidx = lastidx
end
(wid >= width || c in chars) && break
end
if lastidx != 0 && str[lastidx] in chars
lastidx = prevind(str, lastidx)
end
truncidx == 0 && (truncidx = lastidx)
if lastidx < lastindex(str)
return String(SubString(str, 1, truncidx) * truncmark)
else
return String(str)
end
end

function show(io::IO, ::MIME"text/plain", iter::Union{KeySet,ValueIterator})
isempty(iter) && get(io, :compact, false) && return show(io, iter)
summary(io, iter)
Expand All @@ -70,7 +122,7 @@ function show(io::IO, ::MIME"text/plain", iter::Union{KeySet,ValueIterator})

if limit
str = sprint(show, v, context=io, sizehint=0)
str = _truncate_at_width_or_chars(str, cols, "\r\n")
str = _truncate_at_width_or_chars(io, str, cols, "\r\n")
print(io, str)
else
show(io, v)
Expand Down Expand Up @@ -128,15 +180,15 @@ function show(io::IO, ::MIME"text/plain", t::AbstractDict{K,V}) where {K,V}
end

if limit
key = rpad(_truncate_at_width_or_chars(ks[i], keylen, "\r\n"), keylen)
key = rpad(_truncate_at_width_or_chars(recur_io, ks[i], keylen, "\r\n"), keylen)
else
key = sprint(show, k, context=recur_io_k, sizehint=0)
end
print(recur_io, key)
print(io, " => ")

if limit
val = _truncate_at_width_or_chars(vs[i], cols - keylen, "\r\n")
val = _truncate_at_width_or_chars(recur_io, vs[i], cols - keylen, "\r\n")
print(io, val)
else
show(recur_io_v, v)
Expand Down Expand Up @@ -180,7 +232,7 @@ function show(io::IO, ::MIME"text/plain", t::AbstractSet{T}) where T

if limit
str = sprint(show, v, context=recur_io, sizehint=0)
print(io, _truncate_at_width_or_chars(str, cols, "\r\n"))
print(io, _truncate_at_width_or_chars(io, str, cols, "\r\n"))
else
show(recur_io, v)
end
Expand Down
36 changes: 36 additions & 0 deletions test/dict.jl
Original file line number Diff line number Diff line change
Expand Up @@ -368,6 +368,26 @@ end
close(io)
end


struct RainBowString
s::String
end

function Base.show(io::IO, rbs::RainBowString)
for s in rbs.s
_, color = rand(Base.text_colors)
print(io, color, s, "\e[0m")
end
end

@testset "Display with colors" begin
d = Dict([randstring(8) => [RainBowString(randstring(8)) for i in 1:10] for j in 1:5]...)
str = sprint(io -> show(io, MIME("text/plain"), d); context = (:displaysize=>(30,80), :color=>true, :limit=>true))
lines = split(str, '\n')
@test all(endswith('…'), lines[2:end])
@test all(x -> length(x) > 100, lines[2:end])
end

@testset "Issue #15739" begin # Compact REPL printouts of an `AbstractDict` use brackets when appropriate
d = Dict((1=>2) => (3=>45), (3=>10) => (10=>11))
buf = IOBuffer()
Expand Down Expand Up @@ -1259,3 +1279,19 @@ end
sizehint!(d, 10)
@test length(d.slots) < 100
end

using Random

struct RainBowString
s::String
end

function Base.show(io::IO, rbs::RainBowString)
for s in rbs.s
_, color = rand(Base.text_colors)
print(io, color, s, "\e[0m")
end
end


d = Dict([randstring(8) => [RainBowString(randstring(8)) for i in 1:10] for j in 1:5]...)