From f040d21bf8f427bba4c5141727e0fbb3cde68de4 Mon Sep 17 00:00:00 2001 From: kimikage Date: Thu, 29 Oct 2020 02:54:54 +0900 Subject: [PATCH] Change color scheme for 216 colors (#8) --- src/AnsiColoredPrinters.jl | 54 ++++++++------ src/colors.jl | 79 +++++++++++++++----- src/html.jl | 10 +-- test/colors.jl | 145 +++++++++++++++++++++++++++++++++++++ test/runtests.jl | 3 + 5 files changed, 247 insertions(+), 44 deletions(-) create mode 100644 test/colors.jl diff --git a/src/AnsiColoredPrinters.jl b/src/AnsiColoredPrinters.jl index 25fecf0..4cfb67a 100644 --- a/src/AnsiColoredPrinters.jl +++ b/src/AnsiColoredPrinters.jl @@ -4,13 +4,17 @@ import Base: show, showable export HTMLPrinter +struct SGRColor + class::String + hex::String + SGRColor(class::AbstractString="", hex::AbstractString="") = new(class, hex) +end + mutable struct SGRContext - fg::String - fghex::String - bg::String - bghex::String + fg::SGRColor + bg::SGRColor flags::BitVector - SGRContext() = new("", "", "", "", falses(128)) + SGRContext() = new(SGRColor(), SGRColor(), falses(128)) end abstract type AbstractPrinter end @@ -19,8 +23,8 @@ include("colors.jl") include("html.jl") function reset_color(ctx::SGRContext) - ctx.fg, ctx.fghex = "", "" - ctx.bg, ctx.bghex = "", "" + ctx.fg = SGRColor() + ctx.bg = SGRColor() end function reset(ctx::SGRContext) @@ -37,16 +41,26 @@ function reset(printer::AbstractPrinter) reset(printer.prevctx) end +function copy!(dest::SGRContext, src::SGRContext) + dest.fg = src.fg + dest.bg = src.bg + dest.flags .= src.flags +end + escape_char(printer::AbstractPrinter, c::UInt8) = nothing function show_body(io::IO, printer::AbstractPrinter) reset(printer) buf = printer.buf - + ctx_changed = false while !eof(buf) c = read(buf, UInt8) if c !== UInt8('\e') - apply_changes(io, printer) + if ctx_changed + apply_changes(io, printer) + copy!(printer.prevctx, printer.ctx) + ctx_changed = false + end ec = escape_char(printer, c) write(io, ec === nothing ? c : ec) continue @@ -70,6 +84,7 @@ function show_body(io::IO, printer::AbstractPrinter) isempty(astr) && break end end + ctx_changed = printer.prevctx != printer.ctx end while !isempty(printer.stack) # force closing @@ -89,9 +104,9 @@ function parse_sgrcodes(ctx::SGRContext, astr::AbstractString) ctx.flags[di] = false ctx.flags[di + (di === 5)] = false elseif (m = match(r"^39;?", astr)) !== nothing - ctx.fg = "" + ctx.fg = SGRColor() elseif (m = match(r"^49;?", astr)) !== nothing - ctx.bg = "" + ctx.bg = SGRColor() elseif (m = match(r"^([349][0-7]|10[0-7]);?", astr)) !== nothing set_16colors!(ctx, m.captures[1]) elseif (m = match(r"^([345]8);5;(\d{0,3});?", astr)) !== nothing @@ -128,21 +143,18 @@ function apply_changes(io::IO, printer::HTMLPrinter) for di = 1:9 if prevctx.flags[di] != ctx.flags[di] - class = "sgr$di" + class = string(di) marks .|= map(c -> c == class, stack) ctx.flags[di] && push!(nstack, class) end end - prevctx.flags .= ctx.flags - if prevctx.fg != ctx.fg || prevctx.fghex != ctx.fghex || invert - prevctx.fg, prevctx.fghex = ctx.fg, ctx.fghex - marks .|= map(c -> occursin(r"^sgr(?:3[0-7]|9[0-7]|38_[25])$", c), stack) - isempty(ctx.fg) || push!(nstack, ctx.fg) + if prevctx.fg != ctx.fg || invert + marks .|= map(c -> occursin(r"^(?:3[0-7]|9[0-7]|38_[25])$", c), stack) + isnormal(ctx.fg) || push!(nstack, ctx.fg.class) end - if prevctx.bg != ctx.bg || prevctx.bghex != ctx.bghex || invert - prevctx.bg, prevctx.bghex = ctx.bg, ctx.bghex - marks .|= map(c -> occursin(r"^sgr(?:4[0-7]|10[0-7]|48_[25])$", c), stack) - isempty(ctx.bg) || push!(nstack, ctx.bg) + if prevctx.bg != ctx.bg || invert + marks .|= map(c -> occursin(r"^(?:4[0-7]|10[0-7]|48_[25])$", c), stack) + isnormal(ctx.bg) || push!(nstack, ctx.bg.class) end poplevel = findfirst(marks) if poplevel !== nothing diff --git a/src/colors.jl b/src/colors.jl index a3b272f..63afb41 100644 --- a/src/colors.jl +++ b/src/colors.jl @@ -1,4 +1,47 @@ +isnormal(c::SGRColor) = isempty(c.class) + +is216color(c::SGRColor) = is216color(c.hex) + +function is216color(hex::AbstractString) + hex == "000" || hex == "fff" || occursin(r"(?:00|5f|87|af|d7|ff){3}$", hex) +end + +function codes(c::SGRColor) + m = match(r"^([345]8)_([25])$", c.class) + m === nothing && return (parse(Int, c.class),) + code, sub = m.captures + codei = parse(Int, code) + if sub == "5" + if is216color(c) + h = parse(UInt32, c.hex, base=16) + h === 0x00000fff && return (codei, 5, 231) + r = (h >> 0x10) % UInt8 ÷ 0x30 + g = (h >> 0x08) % UInt8 ÷ 0x30 + b = (h >> 0x00) % UInt8 ÷ 0x30 + return (codei, 5, (r * 0x24 + g * 0x6 + b) + 16) + else + h = parse(UInt8, c.hex[1:2], base=16) + g = (h - 0x8) ÷ 0xa + return (codei, 5, g + 232) + end + else + if length(c.hex) == 3 + h = parse(UInt16, c.hex, base=16) + r = (h >> 0x8) + g = (h >> 0x4) & 0xf + b = h & 0xf + return (codei, 2, r * 17, g * 17, b * 17) + else + h = parse(UInt32, c.hex, base=16) + r = (h >> 0x10) + g = (h >> 0x8) & 0xff + b = h & 0xff + return (codei, 2, Int(r), Int(g), Int(b)) + end + end +end + function short_hex(r::UInt8, g::UInt8, b::UInt8) rgb6 = UInt32(r) << 0x10 + UInt32(g) << 0x8 + b rgb6 === (rgb6 & 0x0f0f0f) * 0x11 || return string(rgb6, pad=6, base=16) @@ -6,41 +49,41 @@ function short_hex(r::UInt8, g::UInt8, b::UInt8) end function set_16colors!(ctx::SGRContext, d::AbstractString) - class = "sgr" * d if d[1] === '3' || d[1] === '9' - ctx.fg, ctx.fghex = class, "" + ctx.fg = SGRColor(d) else - ctx.bg, ctx.bghex = class, "" + ctx.bg = SGRColor(d) end end +const SCALE_216 = UInt8[0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] + function set_256colors!(ctx::SGRContext, d::AbstractString, color::AbstractString) fore = d[1] === '3' - class = "sgr" hex = "" colorid = isempty(color) ? 0x0 : parse(UInt8, color) if colorid < 0x8 - class *= d[1] * string(colorid) + class = d[1] * string(colorid) elseif colorid < 0x10 - class *= (fore ? "9" : "10") * string(colorid - 0x8) + class = (fore ? "9" : "10") * string(colorid - 0x8) else if colorid < 0xe8 c = colorid - 0x10 - r = c ÷ 0x24 - g = c ÷ 0x6 % 0x6 - b = c % 0x6 - hex = string(r * 0x300 + g * 0x30 + b * 0x3, pad=3, base=16) + r = SCALE_216[c ÷ 0x24 + 1] + g = SCALE_216[c ÷ 0x6 % 0x6 + 1] + b = SCALE_216[c % 0x6 + 1] + hex = short_hex(r, g, b) else g = (colorid - 0xe8) * 0xa + 0x8 hex = short_hex(g, g, g) end - class *= d * "_5" + class = d * "_5" end if fore - ctx.fg, ctx.fghex = class, hex + ctx.fg = SGRColor(class, hex) else - ctx.bg, ctx.bghex = class, hex + ctx.bg = SGRColor(class, hex) end end @@ -50,14 +93,14 @@ function set_24bitcolors!(ctx::SGRContext, d::AbstractString, g8 = isempty(g) ? 0x0 : parse(UInt8, g) b8 = isempty(b) ? 0x0 : parse(UInt8, b) hex = short_hex(r8, g8, b8) - if occursin(r"^[0369cf]{3}$", hex) || (r8 === g8 === b8 && (r8 - 8) % 10 == 0) - class = "sgr" * d * "_5" + if is216color(hex) || (r8 === g8 === b8 && (r8 - 8) % 10 == 0) + class = d * "_5" else - class = "sgr" * d * "_2" + class = d * "_2" end if d == "38" - ctx.fg, ctx.fghex = class, hex + ctx.fg = SGRColor(class, hex) else - ctx.bg, ctx.bghex = class, hex + ctx.bg = SGRColor(class, hex) end end diff --git a/src/html.jl b/src/html.jl index edc2d01..93317b8 100644 --- a/src/html.jl +++ b/src/html.jl @@ -39,12 +39,12 @@ end function start_new_state(io::IO, printer::HTMLPrinter) class = printer.stack[end] ctx = printer.ctx - if occursin(r"^sgr38_[25]$", class) - write(io, "") - elseif occursin(r"^sgr48_[25]$", class) - write(io, "") + if occursin(r"^38_[25]$", class) + write(io, "") + elseif occursin(r"^48_[25]$", class) + write(io, "") else - write(io, "") + write(io, "") end end diff --git a/test/colors.jl b/test/colors.jl new file mode 100644 index 0000000..91a2ee1 --- /dev/null +++ b/test/colors.jl @@ -0,0 +1,145 @@ +using Test, AnsiColoredPrinters + +@testset "isnormal" begin + n = AnsiColoredPrinters.SGRColor() + @test AnsiColoredPrinters.isnormal(n) + + c16 = AnsiColoredPrinters.SGRColor("30") + @test !AnsiColoredPrinters.isnormal(c16) + + c256 = AnsiColoredPrinters.SGRColor("38_5", "000") + @test !AnsiColoredPrinters.isnormal(c256) +end + +@testset "is216color" begin + c000 = AnsiColoredPrinters.SGRColor("38_5", "000") + @test AnsiColoredPrinters.is216color(c000) + + c080808 = AnsiColoredPrinters.SGRColor("48_5", "080808") + @test !AnsiColoredPrinters.is216color(c080808) + + cabc = AnsiColoredPrinters.SGRColor("38_2", "abc") + @test !AnsiColoredPrinters.is216color(cabc) +end + +@testset "set_16colors!" begin + ctx = AnsiColoredPrinters.SGRContext() + + AnsiColoredPrinters.set_16colors!(ctx, "30") + @test ctx.fg.class == "30" + @test isempty(ctx.fg.hex) + @test AnsiColoredPrinters.codes(ctx.fg) === (30,) + + AnsiColoredPrinters.set_16colors!(ctx, "47") + @test ctx.bg.class == "47" + @test isempty(ctx.bg.hex) + @test AnsiColoredPrinters.codes(ctx.bg) === (47,) + + AnsiColoredPrinters.set_16colors!(ctx, "97") + @test ctx.fg.class == "97" + @test isempty(ctx.fg.hex) + @test AnsiColoredPrinters.codes(ctx.fg) === (97,) + + AnsiColoredPrinters.set_16colors!(ctx, "100") + @test ctx.bg.class == "100" + @test isempty(ctx.bg.hex) + @test AnsiColoredPrinters.codes(ctx.bg) === (100,) +end + +@testset "set_256colors!" begin + ctx = AnsiColoredPrinters.SGRContext() + + # 16 colors + AnsiColoredPrinters.set_256colors!(ctx, "38", "1") + @test ctx.fg.class == "31" + @test isempty(ctx.fg.hex) + @test AnsiColoredPrinters.codes(ctx.fg) === (31,) + + AnsiColoredPrinters.set_256colors!(ctx, "48", "6") + @test ctx.bg.class == "46" + @test isempty(ctx.bg.hex) + @test AnsiColoredPrinters.codes(ctx.bg) === (46,) + + AnsiColoredPrinters.set_256colors!(ctx, "38", "15") + @test ctx.fg.class == "97" + @test isempty(ctx.fg.hex) + @test AnsiColoredPrinters.codes(ctx.fg) === (97,) + + AnsiColoredPrinters.set_256colors!(ctx, "48", "8") + @test ctx.bg.class == "100" + @test isempty(ctx.bg.hex) + @test AnsiColoredPrinters.codes(ctx.bg) === (100,) + + # 216 colors (6 * 6 * 6) + AnsiColoredPrinters.set_256colors!(ctx, "38", "16") + @test ctx.fg.class == "38_5" + @test ctx.fg.hex == "000" + @test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 16) + + AnsiColoredPrinters.set_256colors!(ctx, "48", "17") + @test ctx.bg.class == "48_5" + @test ctx.bg.hex == "00005f" + @test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 17) + + AnsiColoredPrinters.set_256colors!(ctx, "38", "110") + @test ctx.fg.class == "38_5" + @test ctx.fg.hex == "87afd7" + @test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 110) + + AnsiColoredPrinters.set_256colors!(ctx, "38", "230") + @test ctx.fg.class == "38_5" + @test ctx.fg.hex == "ffffd7" + @test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 230) + + AnsiColoredPrinters.set_256colors!(ctx, "48", "231") + @test ctx.bg.class == "48_5" + @test ctx.bg.hex == "fff" + @test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 231) + + # grays + AnsiColoredPrinters.set_256colors!(ctx, "38", "232") + @test ctx.fg.class == "38_5" + @test ctx.fg.hex == "080808" + @test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 232) + + AnsiColoredPrinters.set_256colors!(ctx, "48", "255") + @test ctx.bg.class == "48_5" + @test ctx.bg.hex == "eee" + @test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 255) +end + +@testset "set_24bitcolors" begin + ctx = AnsiColoredPrinters.SGRContext() + + AnsiColoredPrinters.set_24bitcolors!(ctx, "38", "0", "128", "255") + @test ctx.fg.class == "38_2" + @test ctx.fg.hex == "0080ff" + @test AnsiColoredPrinters.codes(ctx.fg) === (38, 2, 0, 128, 255) + + AnsiColoredPrinters.set_24bitcolors!(ctx, "48", "170", "187", "204") + @test ctx.bg.class == "48_2" + @test ctx.bg.hex == "abc" + @test AnsiColoredPrinters.codes(ctx.bg) === (48, 2, 170, 187, 204) + + # 216 colors + AnsiColoredPrinters.set_24bitcolors!(ctx, "38", "0", "0", "0") + @test ctx.fg.class == "38_5" + @test ctx.fg.hex == "000" + @test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 16) + + AnsiColoredPrinters.set_24bitcolors!(ctx, "48", "0", "0", "95") + @test ctx.bg.class == "48_5" + @test ctx.bg.hex == "00005f" + @test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 17) + + # grays + AnsiColoredPrinters.set_24bitcolors!(ctx, "38", "8", "8", "8") + @test ctx.fg.class == "38_5" + @test ctx.fg.hex == "080808" + @test AnsiColoredPrinters.codes(ctx.fg) === (38, 5, 232) + + AnsiColoredPrinters.set_24bitcolors!(ctx, "48", "238", "238", "238") + @test ctx.bg.class == "48_5" + @test ctx.bg.hex == "eee" + @test AnsiColoredPrinters.codes(ctx.bg) === (48, 5, 255) +end diff --git a/test/runtests.jl b/test/runtests.jl index 35e02ea..6d1c4ff 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,5 +1,8 @@ using Test, AnsiColoredPrinters +@testset "colors" begin + include("colors.jl") +end @testset "HTMLPrinter" begin include("html.jl") end