From 89c8152c6356bacc3c0c70803ed2222e3a330262 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 9 Aug 2024 20:30:47 -0400 Subject: [PATCH 1/6] use textwidth for string display truncation --- base/strings/io.jl | 32 +++++++++++++------------------- base/strings/util.jl | 22 +++++++++++++--------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/base/strings/io.jl b/base/strings/io.jl index acbd945c8e137..d5a9c6ef38d23 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -210,35 +210,29 @@ function show( # one line in collection, seven otherwise get(io, :typeinfo, nothing) === nothing && (limit *= 7) end + limit -= 2 # quote chars # early out for short strings - len = ncodeunits(str) - len ≤ limit - 2 && # quote chars - return show(io, str) + check_textwidth(str, limit) && return show(io, str) # these don't depend on string data units = codeunit(str) == UInt8 ? "bytes" : "code units" skip_text(skip) = " ⋯ $skip $units ⋯ " - short = length(skip_text("")) + 4 # quote chars - chars = max(limit, short + 1) - short # at least 1 digit - # figure out how many characters to print in elided case - chars -= d = ndigits(len - chars) # first adjustment - chars += d - ndigits(len - chars) # second if needed - chars = max(0, chars) + # longest possible replacement string for omitted chars + max_replacement = skip_text(ncodeunits(str) * 100) # *100 for 2 inner quote chars - # find head & tail, avoiding O(length(str)) computation - head = nextind(str, 0, 1 + (chars + 1) ÷ 2) - tail = prevind(str, len + 1, chars ÷ 2) + head, tail = string_truncate_boundaries(str, limit, max_replacement, Val(:center)) # threshold: min chars skipped to make elision worthwhile - t = short + ndigits(len - chars) - 1 - n = tail - head # skipped code units - if 4t ≤ n || t ≤ n && t ≤ length(str, head, tail-1) - skip = skip_text(n) - show(io, SubString(str, 1:prevind(str, head))) - printstyled(io, skip; color=:light_yellow, bold=true) - show(io, SubString(str, tail)) + afterhead = nextind(str, head) + n = tail - afterhead # skipped code units + replacement = skip_text(n) + t = ncodeunits(replacement) # length of replacement (textwidth == ncodeunits here) + @views if 4t ≤ n || t ≤ n && t ≤ textwidth(str[afterhead:tail]) + show(io, str[begin:head]) + printstyled(io, replacement; color=:light_yellow, bold=true) + show(io, str[tail:end]) else show(io, str) end diff --git a/base/strings/util.jl b/base/strings/util.jl index 0ba76e1c76fa0..913b346303658 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -613,22 +613,26 @@ function ctruncate(str::AbstractString, maxwidth::Integer, replacement::Union{Ab end end -function string_truncate_boundaries( - str::AbstractString, - maxwidth::Integer, - replacement::Union{AbstractString,AbstractChar}, - ::Val{mode}, - prefer_left::Bool = true) where {mode} - +# return whether textwidth(str) <= maxwidth +function check_textwidth(str::AbstractString, maxwidth::Integer) maxwidth >= 0 || throw(ArgumentError("maxwidth $maxwidth should be non-negative")) # check efficiently for early return if str is less wide than maxwidth total_width = 0 for c in str total_width += textwidth(c) - total_width > maxwidth && break + total_width > maxwidth && return false end - total_width <= maxwidth && return nothing + return true +end + +function string_truncate_boundaries( + str::AbstractString, + maxwidth::Integer, + replacement::Union{AbstractString,AbstractChar}, + ::Val{mode}, + prefer_left::Bool = true) where {mode} + check_textwidth(str, maxwidth) && return nothing l0, _ = left, right = firstindex(str), lastindex(str) width = textwidth(replacement) From 6f80764a89755183f1f4ebe707eb8366f469d2a9 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 9 Aug 2024 20:50:57 -0400 Subject: [PATCH 2/6] test fixes --- base/strings/io.jl | 2 +- test/show.jl | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/base/strings/io.jl b/base/strings/io.jl index d5a9c6ef38d23..ce8be3e0e4017 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -210,7 +210,7 @@ function show( # one line in collection, seven otherwise get(io, :typeinfo, nothing) === nothing && (limit *= 7) end - limit -= 2 # quote chars + limit = max(0, limit-2) # quote chars # early out for short strings check_textwidth(str, limit) && return show(io, str) diff --git a/test/show.jl b/test/show.jl index 63663152d9d91..d38d7e5003fc5 100644 --- a/test/show.jl +++ b/test/show.jl @@ -893,11 +893,11 @@ end @testset "default elision limit" begin r = replstr("x"^1000) - @test length(r) == 7*80 - @test r == repr("x"^271) * " ⋯ 459 bytes ⋯ " * repr("x"^270) + @test length(r) == 7*80-1 + @test r == repr("x"^270) * " ⋯ 460 bytes ⋯ " * repr("x"^270) r = replstr(["x"^1000]) @test length(r) < 120 - @test r == "1-element Vector{String}:\n " * repr("x"^31) * " ⋯ 939 bytes ⋯ " * repr("x"^30) + @test r == "1-element Vector{String}:\n " * repr("x"^30) * " ⋯ 940 bytes ⋯ " * repr("x"^30) end end From 2f6c0939e5da1c5d9288fc538656e8494a1dff8d Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 9 Aug 2024 21:40:17 -0400 Subject: [PATCH 3/6] bugfix --- base/strings/io.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/strings/io.jl b/base/strings/io.jl index ce8be3e0e4017..bb0b7ae5854f4 100644 --- a/base/strings/io.jl +++ b/base/strings/io.jl @@ -229,7 +229,7 @@ function show( n = tail - afterhead # skipped code units replacement = skip_text(n) t = ncodeunits(replacement) # length of replacement (textwidth == ncodeunits here) - @views if 4t ≤ n || t ≤ n && t ≤ textwidth(str[afterhead:tail]) + @views if 4t ≤ n || t ≤ n && t ≤ textwidth(str[afterhead:prevind(str,tail)]) show(io, str[begin:head]) printstyled(io, replacement; color=:light_yellow, bold=true) show(io, str[tail:end]) From 24b804c26000b235f25f0f84f8b627423ec1652e Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 9 Aug 2024 21:52:29 -0400 Subject: [PATCH 4/6] test updates --- test/show.jl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/show.jl b/test/show.jl index d38d7e5003fc5..15fa9be679b56 100644 --- a/test/show.jl +++ b/test/show.jl @@ -865,19 +865,19 @@ end # string show with elision @testset "string show with elision" begin @testset "elision logic" begin - strs = ["A", "∀", "∀A", "A∀", "😃"] + strs = ["A", "∀", "∀A", "A∀", "😃", "x̂"] for limit = 0:100, len = 0:100, str in strs str = str^len str = str[1:nextind(str, 0, len)] out = sprint() do io show(io, MIME"text/plain"(), str; limit) end - lower = length("\"\" ⋯ $(ncodeunits(str)) bytes ⋯ \"\"") + lower = textwidth("\"\" ⋯ $(ncodeunits(str)) bytes ⋯ \"\"") limit = max(limit, lower) - if length(str) + 2 ≤ limit + if textwidth(str) + 2 ≤ limit+1 && !contains(out, '⋯') @test eval(Meta.parse(out)) == str else - @test limit-!isascii(str) <= length(out) <= limit + @test limit-2 <= textwidth(out) <= limit re = r"(\"[^\"]*\") ⋯ (\d+) bytes ⋯ (\"[^\"]*\")" m = match(re, out) head = eval(Meta.parse(m.captures[1])) From 904098441c687ea7aaea8daa863a1f4b55d46db2 Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 9 Aug 2024 21:55:56 -0400 Subject: [PATCH 5/6] check tweak --- base/strings/util.jl | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/strings/util.jl b/base/strings/util.jl index 913b346303658..65d6cab74a9b5 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -615,8 +615,6 @@ end # return whether textwidth(str) <= maxwidth function check_textwidth(str::AbstractString, maxwidth::Integer) - maxwidth >= 0 || throw(ArgumentError("maxwidth $maxwidth should be non-negative")) - # check efficiently for early return if str is less wide than maxwidth total_width = 0 for c in str @@ -632,6 +630,7 @@ function string_truncate_boundaries( replacement::Union{AbstractString,AbstractChar}, ::Val{mode}, prefer_left::Bool = true) where {mode} + maxwidth >= 0 || throw(ArgumentError("maxwidth $maxwidth should be non-negative")) check_textwidth(str, maxwidth) && return nothing l0, _ = left, right = firstindex(str), lastindex(str) From 0b334bd487c44de95d1a5941314fa4ffef80da3d Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Fri, 9 Aug 2024 21:56:27 -0400 Subject: [PATCH 6/6] comment fix --- base/strings/util.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/strings/util.jl b/base/strings/util.jl index 65d6cab74a9b5..04d451a4fd288 100644 --- a/base/strings/util.jl +++ b/base/strings/util.jl @@ -615,7 +615,7 @@ end # return whether textwidth(str) <= maxwidth function check_textwidth(str::AbstractString, maxwidth::Integer) - # check efficiently for early return if str is less wide than maxwidth + # check efficiently for early return if str is wider than maxwidth total_width = 0 for c in str total_width += textwidth(c)