Skip to content

Commit

Permalink
Fix unicodes and indented pretty printing (#1581)
Browse files Browse the repository at this point in the history
  • Loading branch information
aaruni96 authored Feb 16, 2024
1 parent 0b5e698 commit 3246a27
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 13 deletions.
89 changes: 76 additions & 13 deletions src/PrettyPrinting.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1889,30 +1889,93 @@ function _write_line(io::IOCustom, str::AbstractString)
# printed to an IOBuffer for comparisons
c = _isbuffer(io) && !io.force_newlines ? typemax(Int) : displaysize(io)[2]
ind = io.indent_level * textwidth(io.indent_str)
limit = c - ind > 0 ? c - ind : c
# there might be already something written
if c - ind - io.printed < 0
spaceleft = mod(c - ind - io.printed, c)
else
spaceleft = c - ind - io.printed
end
#@show spaceleft
firstlen = min(spaceleft, length(str))
firststr = str[1:firstlen]
if io.lowercasefirst
written += write(io.io, lowercasefirst(firststr))
io.lowercasefirst = false
else
written += write(io.io, firststr)
io.lowercasefirst = false
str = lowercasefirst(str)
io.lowercasefirst = false
end
io.printed += textwidth(firststr)
reststr = str[firstlen + 1:end]
it = Iterators.partition(1:textwidth(reststr), c - ind > 0 ? c - ind : c)
for i in it
# The following code deals with line wrapping of Unicode text, including
# double-width symbols and more.
_graphemes = Base.Unicode.graphemes(str)
firstlen = min(spaceleft, length(_graphemes))
# make an iterator over valid indices
firstiter = Base.Iterators.take(_graphemes, firstlen)
restiter = Base.Iterators.drop(_graphemes, firstlen)
firststr = join(firstiter)
width = textwidth(firststr)
if length(firstiter) == width
written += write(io.io, firststr)
io.printed += width
else
#firstline is wider than number of graphemes
partcollect = collect(firstiter)
printstr = ""
j = 1
width = 0
while width < (limit)
printstr *= partcollect[j]
j += 1
width += textwidth(partcollect[j])
if j > length(partcollect)
break
end
end
written += write(io.io, printstr)
io.printed += width

#the spillover string
written += write(io.io, "\n")
written += write_indent(io)
written += write(io.io, reststr[i])
io.printed = textwidth(reststr[i])
printstr = join(collect(firstiter)[j:end])
written += write(io.io, printstr)
io.printed += textwidth(printstr)
end
it = Iterators.partition(1:length(restiter), limit)
restcollect = collect(restiter)
for i in it
# partitions of the spillover text
partcollect = restcollect[i]
partstr = join(partcollect)
width = textwidth(partstr)
if width < (limit) || length(i) == width
written += write(io.io, "\n")
written += write_indent(io)
written += write(io.io, partstr)
io.printed = width
else
# width is more than the number of graphemes
# we can only ever get double length lines
# (assuming non standard width can only be 2.)
# (see https://github.com/alacritty/alacritty/issues/265#issue-199665364 )
printstr = ""
j = 1
while textwidth(printstr) < (limit)
printstr *= partcollect[j]
j += 1
if j > length(partcollect)
break
end
end
written += write(io.io, "\n")
written += write_indent(io)
written += write(io.io, printstr)
io.printed = textwidth(printstr)
# print the second part
# there are at most two parts due to our assumption
# that no grapheme exceeds double width
printstr = join(partcollect[j:end])
written += write(io.io, "\n")
written += write_indent(io)
written += write(io.io, printstr)
io.printed = textwidth(printstr)
end
end
return written
end
Expand Down
143 changes: 143 additions & 0 deletions test/PrettyPrinting-test.jl
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,30 @@ end
" test\n" *
" test"

# Test unicode
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
println(io, "testing unicode")
print(io, AbstractAlgebra.Indent(), "ŎŚĊĂŖ")
@test String(take!(io)) == "testing unicode\n" *
" ŎŚĊĂŖ"

# Test evil unicodes
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
_, c = displaysize(io)
print(io, AbstractAlgebra.Indent())
ellipses = String([0xe2, 0x80, 0xa6])
wedge = String([0xe2, 0x88, 0xa7])
iacute = String([0xc3, 0xad])
str = wedge ^25 * ellipses^25 * iacute^50
print(io, "aa", str)
@test String(take!(io)) == " aa∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧∧" *
"…………………………………………………………………" *
"íííííííííííííííííííííííííí\n" *
" íííííííííííííííííííííííí"


# Test string longer than width
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
Expand All @@ -442,6 +466,125 @@ end
" aa" * "t"^(c - 6) * "\n" *
" tttttt"

# Test unicode string longer than width
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
_, c = displaysize(io)
print(io, AbstractAlgebra.Indent())
println(io, "Ŏ"^c)
println(io, "aa", "Ś"^c)
print(io, AbstractAlgebra.Indent())
print(io, "aa", "Ŗ"^c)
@test String(take!(io)) == " " * "Ŏ"^(c-2) * "\n" *
" ŎŎ" * "\n" *
" aa" * "Ś"^(c-4) * "\n" *
" ŚŚŚŚ" * "\n" *
" aa" * "Ŗ"^(c-6) * "\n" *
" ŖŖŖŖŖŖ"

# Test evil unicode string much longer than width
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
_, c = displaysize(io)
ellipses = String([0xe2, 0x80, 0xa6])
wedge = String([0xe2, 0x88, 0xa7])
iacute = String([0xc3, 0xad])
evil_a = String([0x61, 0xcc, 0x81, 0xcc, 0xa7, 0xcc, 0xa7])
print(io, AbstractAlgebra.Indent())
println(io, "Ŏ"^c)
println(io, ellipses^c)
println(io, "aa", "Ś"^c)
println(io, "bb", wedge^c)
print(io, AbstractAlgebra.Indent())
println(io, "aa", "Ŗ"^c)
print(io, iacute^c)
println(io, evil_a^c)
print(io, evil_a^c)
@test String(take!(io)) == " " * "Ŏ"^(c-2) * "\n" *
" ŎŎ" * "\n" *
" " * ellipses^(c-2) * "\n" *
" " * ellipses^2 * "\n" *
" aa" * "Ś"^(c-4) * "\n" *
" ŚŚŚŚ" * "\n" *
" bb" * wedge^(c-4) * "\n" *
" " * wedge^4 * "\n" *
" aa" * "Ŗ"^(c-6) * "\n" *
" ŖŖŖŖŖŖ" * "\n" *
" " * iacute^(c-4) * "\n" *
" " * iacute^4 * evil_a^(c-8) * "\n" *
" " * evil_a^(8) * "\n" *
" " * evil_a^(c-4) * "\n" *
" " * evil_a^4

# Test graphemes with non standard width
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
_, c = displaysize(io)
boat = String([0xe2, 0x9b, 0xb5])
family = String([0xf0, 0x9f, 0x91, 0xaa])
print(io, AbstractAlgebra.Indent())
println(io, (boat * family)^40)
print(io, (boat * family)^40)
@test String(take!(io)) == " " * (boat*family)^19 * boat * "\n" *
" " * (family*boat)^19 * family * "\n" *
" " * boat * family * "\n" *
" " * (boat*family)^19 * boat * "\n" *
" " * (family*boat)^19 * family * "\n" *
" " * boat * family

# Test graphemes with standard and non standard width mixed in
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
_, c = displaysize(io)
ellipses = String([0xe2, 0x80, 0xa6])
wedge = String([0xe2, 0x88, 0xa7])
iacute = String([0xc3, 0xad])
evil_a = String([0x61, 0xcc, 0x81, 0xcc, 0xa7, 0xcc, 0xa7])
boat = String([0xe2, 0x9b, 0xb5])
family = String([0xf0, 0x9f, 0x91, 0xaa])
print(io, AbstractAlgebra.Indent())
println(io, "Ŏ"^c)
println(io, ellipses^c)
println(io, "aa", "Ś"^c)
println(io, boat^(3*c))
println(io, "bb", wedge^c)
print(io, AbstractAlgebra.Indent())
println(io, "aa", "Ŗ"^c)
println(io, family^(3*c))
println(io, iacute^c)
println(io, evil_a^c)
print(io, evil_a^c)
@test String(take!(io)) == " " * "Ŏ"^(c-2) * "\n" *
" ŎŎ" * "\n" *
" " * ellipses^(c-2) * "\n" *
" " * ellipses^2 * "\n" *
" aa" * "Ś"^(c-4) * "\n" *
" ŚŚŚŚ" * "\n" *
" " * boat^39 * "\n" *
" " * boat^39 * "\n" *
" " * boat^39 * "\n" *
" " * boat^39 * "\n" *
" " * boat^39 * "\n" *
" " * boat^39 * "\n" *
" " * boat^6 * "\n" *
" bb" * wedge^(c-4) * "\n" *
" " * wedge^4 * "\n" *
" aa" * "Ŗ"^(c-6) * "\n" *
" ŖŖŖŖŖŖ" * "\n" *
" " * family^38 * "\n" *
" " * family^38 * "\n" *
" " * family^38 * "\n" *
" " * family^38 * "\n" *
" " * family^38 * "\n" *
" " * family^38 * "\n" *
" " * family^12 * "\n" *
" " * iacute^(c-4) * "\n" *
" " * iacute^4 *"\n" *
" " * evil_a^(c-4) * "\n" *
" " * evil_a^(4) * "\n" *
" " * evil_a^(c-4) * "\n" *
" " * evil_a^4

# Test too much indentation
io = IOBuffer()
io = AbstractAlgebra.pretty(io, force_newlines = true)
Expand Down

0 comments on commit 3246a27

Please sign in to comment.