From 596ab8ac3e35a940dd44f1ac3145c1d4394d3d97 Mon Sep 17 00:00:00 2001 From: Lionel Zoubritzky Date: Mon, 6 Jun 2022 19:45:07 +0200 Subject: [PATCH] Simplify fix --- base/show.jl | 118 ++++++++------------------------------------------- base/util.jl | 31 ++++++++++++++ test/dict.jl | 25 ++++++++--- 3 files changed, 66 insertions(+), 108 deletions(-) diff --git a/base/show.jl b/base/show.jl index ab900004f2fe41..9e8dc30dd2cdd4 100644 --- a/base/show.jl +++ b/base/show.jl @@ -51,69 +51,19 @@ show(io::IO, ::MIME"text/plain", s::Splat) = show(io, s) const possible_ansi_regex = r"\x1B(?:[@-Z\\-_\[])" const start_ansi_regex = r"\G(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])" -const text_colors = Dict{Union{Symbol,Int},String}( - :black => "\033[30m", - :red => "\033[31m", - :green => "\033[32m", - :yellow => "\033[33m", - :blue => "\033[34m", - :magenta => "\033[35m", - :cyan => "\033[36m", - :white => "\033[37m", - :light_black => "\033[90m", # gray - :light_red => "\033[91m", - :light_green => "\033[92m", - :light_yellow => "\033[93m", - :light_blue => "\033[94m", - :light_magenta => "\033[95m", - :light_cyan => "\033[96m", - :light_white => "\033[97m", - :normal => "\033[0m", - :default => "\033[39m", - :bold => "\033[1m", - :underline => "\033[4m", - :blink => "\033[5m", - :reverse => "\033[7m", - :hidden => "\033[8m", - :nothing => "", -) - -for i in 0:255 - text_colors[i] = String(['\033', '[', '3', '8', ';', '5', ';', string(i)..., 'm']) -end - -const ansi_code_index = Dict{String,Int8}(x[2:end] => 6 for x in values(text_colors) if !isempty(x)) -merge!(ansi_code_index, Dict{String,Int8}( - "[1m" => 1, # bold - "[4m" => 2, # underline - "[5m" => 3, # blink - "[7m" => 4, # reverse - "[8m" => 5, # hidden - "[22m" => -1, # remove bold - "[24m" => -2, # remove underline - "[25m" => -3, # remove blink - "[27m" => -4, # remove reverse - "[28m" => -5, # remove hidden - "[39m" => -6, # reset default color - "[0m" => 0 # reset everything -)) - -function find_lastidx_withcolor(str, width, chars, truncwidth, start) - idx = lastidx = start - truncidx = 0 # if str needs to be truncated, index of truncation. - wid = textwidth(SubString(str, 1, idx)) # text width up to idx. +function _find_lastidx(str, width, chars, truncwidth, start) + idx = start + lastidx = idx == 0 ? 0 : prevind(str, idx) + wid = textwidth(SubString(str, 1, lastidx)) # text width up to lastidx. if wid ≥ width - truncwidth - # ensure that truncidx is correctly set in the main loop + # ensure that truncidx is correctly set in the main loop. idx = 1 wid = lastidx = 0 end - ansi_mask = zero(UInt32) # one-hot encoding of the encountered ansi color characters. - # For example, if `ansi_mask == (1 << 7) | (1 << 3) | (1 << 2)`, i.e. if - # `ansi_mask == 0b10001100`, then the ansi delimiters number 3 ("blink") and number 2 - # ("underline") were encountered without their corresponding end delimiter, as well as - # an unknown ansi character (represented by the bit in position 7). + truncidx = 0 # if str needs to be truncated, index of truncation. stop = false # when set, only ansi delimiters will be kept as new characters. - firstmatch = 0 # index of the first ansi delimiter + firstmatch = 0 # index of the first ansi delimiter. + noansi = true # set if the substring should be completed with a "\033[0m" delimiter. while true str_iter = iterate(str, idx) isnothing(str_iter) && break @@ -121,51 +71,21 @@ function find_lastidx_withcolor(str, width, chars, truncwidth, start) c, idx = str_iter m = c == '\033' ? match(start_ansi_regex, str, idx) : nothing stop && isnothing(m) && break + last_lastidx = lastidx lastidx = _lastidx if !isnothing(m) firstmatch == 0 && (firstmatch = lastidx) - ansi_idx = get(ansi_code_index, m.match, Int8(7)) - if ansi_idx > 0 - ansi_mask |= (one(UInt32) << (ansi_idx % UInt8)) # set the bit - elseif ansi_idx < 0 - ansi_mask &= ~(one(UInt32) << (-ansi_idx % UInt8)) # erase the bit - else # encountered code "\033[0m" a.k.a. erase all properties - ansi_mask = zero(UInt32) - end + noansi = m.match == "[0m" s = sizeof(m.match) idx += s - lastidx += s + lastidx = idx - 1 # the last character of an ansi delimiter is always of size 1. continue end wid += textwidth(c) - if wid >= (width - truncwidth) && truncidx == 0 - truncidx = lastidx - end + truncidx == 0 && wid > (width - truncwidth) && (truncidx = last_lastidx) stop = (wid >= width || c in chars) end - if lastidx < lastindex(str) && truncidx < firstmatch - ansi_mask = zero(UInt32) # disregard ansi delimiters if they are truncated away - end - return lastidx, truncidx, ansi_mask -end - -function find_lastidx_nocolor(str, width, chars, truncwidth) - lastidx = 0 - truncidx = 0 - idx = 1 - wid = 0 - while true - str_iter = iterate(str, idx) - isnothing(str_iter) && break - lastidx = idx - c, idx = str_iter - wid += textwidth(c) - if wid >= (width - truncwidth) && truncidx == 0 - truncidx = lastidx - end - (wid >= width || c in chars) && break - end - return lastidx, truncidx + return lastidx, truncidx, noansi || (lastidx < lastindex(str) && truncidx < firstmatch) end function _truncate_at_width_or_chars(ignore_ansi::Bool, str, width, chars="", truncmark="…") @@ -174,19 +94,15 @@ function _truncate_at_width_or_chars(ignore_ansi::Bool, str, width, chars="", tr # possible_substring is a subset of str at least as large as the returned string possible_substring = SubString(str, 1, thisind(str, min(ncodeunits(str), width+1))) m = ignore_ansi ? match(possible_ansi_regex, possible_substring) : nothing - if isnothing(m) - lastidx, truncidx = find_lastidx_nocolor(str, width, chars, truncwidth) - ansi_mask = zero(UInt32) - else - lastidx, truncidx, ansi_mask = find_lastidx_withcolor(str, width, chars, truncwidth, m.offset) - end + start_offset = isnothing(m) ? thisind(str, min(ncodeunits(str), width-truncwidth)) : m.offset + lastidx, truncidx, noansi = _find_lastidx(str, width, chars, truncwidth, start_offset) lastidx == 0 && return "" str[lastidx] in chars && (lastidx = prevind(str, lastidx)) truncidx == 0 && (truncidx = lastidx) if lastidx < lastindex(str) - return string(SubString(str, 1, truncidx), iszero(ansi_mask) ? "" : "\033[0m", truncmark) + return string(SubString(str, 1, truncidx), noansi ? "" : "\033[0m", truncmark) else - return iszero(ansi_mask) ? String(str) : string(str, "\033[0m") + return noansi ? String(str) : string(str, "\033[0m") end end diff --git a/base/util.jl b/base/util.jl index db3803a34ecfc6..df9e29790deb66 100644 --- a/base/util.jl +++ b/base/util.jl @@ -2,6 +2,37 @@ ## printing with color ## +const text_colors = Dict{Union{Symbol,Int},String}( + :black => "\033[30m", + :red => "\033[31m", + :green => "\033[32m", + :yellow => "\033[33m", + :blue => "\033[34m", + :magenta => "\033[35m", + :cyan => "\033[36m", + :white => "\033[37m", + :light_black => "\033[90m", # gray + :light_red => "\033[91m", + :light_green => "\033[92m", + :light_yellow => "\033[93m", + :light_blue => "\033[94m", + :light_magenta => "\033[95m", + :light_cyan => "\033[96m", + :light_white => "\033[97m", + :normal => "\033[0m", + :default => "\033[39m", + :bold => "\033[1m", + :underline => "\033[4m", + :blink => "\033[5m", + :reverse => "\033[7m", + :hidden => "\033[8m", + :nothing => "", +) + +for i in 0:255 + text_colors[i] = "\033[38;5;$(i)m" +end + const disable_text_style = Dict{Symbol,String}( :bold => "\033[22m", :underline => "\033[24m", diff --git a/test/dict.jl b/test/dict.jl index 11d1deb67e733b..ad6695fac066a5 100644 --- a/test/dict.jl +++ b/test/dict.jl @@ -406,18 +406,19 @@ end d2 = Dict(:foo => RainbowString("bar")) str2 = sprint(io -> show(io, MIME("text/plain"), d2); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) @test !occursin('…', str2) - @test endswith(str2, "\033[39m") + @test endswith(str2, "\033[0m") d3 = Dict(:foo => RainbowString("bar", true)) str3 = sprint(io -> show(io, MIME("text/plain"), d3); context = (:displaysize=>(30,80), :color=>true, :limit=>true)) - @test endswith(str3, "\033[22m\033[39m") + @test !occursin('…', str3) + @test endswith(str3, "\033[0m") d4 = Dict(RainbowString(randstring(8), true) => nothing) str4 = sprint(io -> show(io, MIME("text/plain"), d4); context = (:displaysize=>(30,20), :color=>true, :limit=>true)) @test endswith(str4, "\033[0m… => nothing") - d5 = Dict(RainbowString(randstring(20), false, true, false) => nothing) - str5 = sprint(io -> show(io, MIME("text/plain"), d5); context = (:displaysize=>(30,20), :color=>true, :limit=>true)) + d5 = Dict(RainbowString(randstring(30), false, true, false) => nothing) + str5 = sprint(io -> show(io, MIME("text/plain"), d5); context = (:displaysize=>(30,30), :color=>true, :limit=>true)) @test endswith(str5, "\033[0m… => nothing") d6 = Dict(randstring(8) => RainbowString(randstring(30), true, true, false) for _ in 1:3) @@ -426,21 +427,31 @@ end @test all(endswith("\033[0m…"), lines6[2:end]) @test all(x -> length(x) > 100, lines6[2:end]) - d7 = Dict(randstring(8) => RainbowString(randstring(30), false, false)) + d7 = Dict(randstring(8) => RainbowString(randstring(30))) str7 = sprint(io -> show(io, MIME("text/plain"), d7); context = (:displaysize=>(30,20), :color=>true, :limit=>true)) line7 = split(str7, '\n')[2] @test endswith(line7, "\033[0m…") @test length(line7) > 100 d8 = Dict(:x => RainbowString(randstring(10), false, false, false, 6)) - str8 = sprint(io -> show(io, MIME("text/plain"), d8); context = (:displaysize=>(30,15), :color=>true, :limit=>true)) + str8 = sprint(io -> show(io, MIME("text/plain"), d8); context = (:displaysize=>(30,14), :color=>true, :limit=>true)) line8 = split(str8, '\n')[2] @test !occursin(line8, "\033[") - @test length(line8) == 15 + @test length(line8) == 14 str8_long = sprint(io -> show(io, MIME("text/plain"), d8); context = (:displaysize=>(30,16), :color=>true, :limit=>true)) line8_long = split(str8_long, '\n')[2] @test endswith(line8_long, "\033[0m…") @test length(line8_long) > 20 + + d9 = Dict(:x => RainbowString(repeat('苹', 5), false, true, false)) + str9 = sprint(io -> show(io, MIME("text/plain"), d9); context = (:displaysize=>(30,15), :color=>true, :limit=>true)) + @test endswith(str9, "\033[0m…") + @test count('苹', str9) == 3 + + d10 = Dict(:xy => RainbowString(repeat('苹', 5), false, true, false)) + str10 = sprint(io -> show(io, MIME("text/plain"), d10); context = (:displaysize=>(30,15), :color=>true, :limit=>true)) + @test endswith(str10, "\033[0m…") + @test count('苹', str10) == 2 end @testset "Issue #15739" begin # Compact REPL printouts of an `AbstractDict` use brackets when appropriate