Skip to content

Commit

Permalink
Take into account color and unicode in matrix alignment (JuliaLang#45751
Browse files Browse the repository at this point in the history
)

Without this, alignment would count characters rather than
textwidth as well as counting inline escape sequences in
colored output. Fix that by using uncolored printing for
alignment and textwidth rather than number of codepoints.
  • Loading branch information
Keno authored and pcjentsch committed Aug 18, 2022
1 parent ca447d6 commit 1611996
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 16 deletions.
38 changes: 22 additions & 16 deletions base/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2783,6 +2783,9 @@ function dump(arg; maxdepth=DUMP_DEFAULT_MAXDEPTH)
dump(IOContext(stdout::IO, :limit => true, :module => mod), arg; maxdepth=maxdepth)
end

nocolor(io::IO) = IOContext(io, :color => false)
alignment_from_show(io::IO, x::Any) =
textwidth(sprint(show, x, context=nocolor(io), sizehint=0))

"""
`alignment(io, X)` returns a tuple (left,right) showing how many characters are
Expand All @@ -2800,35 +2803,38 @@ julia> Base.alignment(stdout, 1 + 10im)
(3, 5)
```
"""
alignment(io::IO, x::Any) = (0, length(sprint(show, x, context=io, sizehint=0)))
alignment(io::IO, x::Number) = (length(sprint(show, x, context=io, sizehint=0)), 0)
alignment(io::IO, x::Integer) = (length(sprint(show, x, context=io, sizehint=0)), 0)
alignment(io::IO, x::Any) = (0, alignment_from_show(io, x))
alignment(io::IO, x::Number) = (alignment_from_show(io, x), 0)
alignment(io::IO, x::Integer) = (alignment_from_show(io, x), 0)
function alignment(io::IO, x::Real)
m = match(r"^(.*?)((?:[\.eEfF].*)?)$", sprint(show, x, context=io, sizehint=0))
m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) :
(length(m.captures[1]), length(m.captures[2]))
s = sprint(show, x, context=nocolor(io), sizehint=0)
m = match(r"^(.*?)((?:[\.eEfF].*)?)$", s)
m === nothing ? (textwidth(s), 0) :
(textwidth(m.captures[1]), textwidth(m.captures[2]))
end
function alignment(io::IO, x::Complex)
m = match(r"^(.*[^ef][\+\-])(.*)$", sprint(show, x, context=io, sizehint=0))
m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) :
(length(m.captures[1]), length(m.captures[2]))
s = sprint(show, x, context=nocolor(io), sizehint=0)
m = match(r"^(.*[^ef][\+\-])(.*)$", s)
m === nothing ? (textwidth(s), 0) :
(textwidth(m.captures[1]), textwidth(m.captures[2]))
end
function alignment(io::IO, x::Rational)
m = match(r"^(.*?/)(/.*)$", sprint(show, x, context=io, sizehint=0))
m === nothing ? (length(sprint(show, x, context=io, sizehint=0)), 0) :
(length(m.captures[1]), length(m.captures[2]))
s = sprint(show, x, context=nocolor(io), sizehint=0)
m = match(r"^(.*?/)(/.*)$", s)
m === nothing ? (textwidth(s), 0) :
(textwidth(m.captures[1]), textwidth(m.captures[2]))
end

function alignment(io::IO, x::Pair)
s = sprint(show, x, context=io, sizehint=0)
fullwidth = alignment_from_show(io, x)
if !isdelimited(io, x) # i.e. use "=>" for display
ctx = IOContext(io, :typeinfo => gettypeinfos(io, x)[1])
left = length(sprint(show, x.first, context=ctx, sizehint=0))
left = alignment_from_show(ctx, x.first)
left += 2 * !isdelimited(ctx, x.first) # for parens around p.first
left += !(get(io, :compact, false)::Bool) # spaces are added around "=>"
(left+1, length(s)-left-1) # +1 for the "=" part of "=>"
(left+1, fullwidth-left-1) # +1 for the "=" part of "=>"
else
(0, length(s)) # as for x::Any
(0, fullwidth) # as for x::Any
end
end

Expand Down
13 changes: 13 additions & 0 deletions test/show.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2360,3 +2360,16 @@ end
@test sprint(show, setenv(setcpuaffinity(`true`, [1, 2]), "A" => "B")) ==
"""setenv(setcpuaffinity(`true`, [1, 2]),["A=B"])"""
end

# Test that alignment takes into account unicode and computes alignment without
# color/formatting.

struct ColoredLetter; end
Base.show(io::IO, ces::ColoredLetter) = Base.printstyled(io, 'A'; color=:red)

struct ⛵; end
Base.show(io::IO, ces::⛵) = Base.print(io, '')

@test Base.alignment(stdout, ()) == (0, 2)
@test Base.alignment(IOContext(IOBuffer(), :color=>true), ColoredLetter()) == (0, 1)
@test Base.alignment(IOContext(IOBuffer(), :color=>false), ColoredLetter()) == (0, 1)

0 comments on commit 1611996

Please sign in to comment.