Refactor and allow configuring of color_name_parser

- A third parameter (undocumented) can be used to modify the initial
configuration of the trie.
- Allow printing a trie in thicc mode.
This commit is contained in:
Ashkan Kiani 2019-10-19 11:51:33 -07:00
parent 9333430ba8
commit 34c8ce7980
4 changed files with 138 additions and 62 deletions

View file

@ -11,20 +11,34 @@ local nvim_buf_get_lines = vim.api.nvim_buf_get_lines
local nvim_get_current_buf = vim.api.nvim_get_current_buf
local band, lshift, bor, tohex = bit.band, bit.lshift, bit.bor, bit.tohex
local rshift = bit.rshift
local floor = math.floor
local floor, min, max = math.floor, math.min, math.max
local COLOR_MAP
local COLOR_TRIE
local COLOR_NAME_MINLEN, COLOR_NAME_MAXLEN
local COLOR_NAME_SETTINGS = {
lowercase = false;
strip_digits = true;
}
--- Setup the COLOR_MAP and COLOR_TRIE
local function initialize_trie()
if not COLOR_TRIE then
COLOR_MAP = nvim.get_color_map()
COLOR_MAP = {}
COLOR_TRIE = Trie()
for k, v in pairs(COLOR_MAP) do
COLOR_MAP[k] = tohex(v, 6)
COLOR_TRIE:insert(k)
for k, v in pairs(nvim.get_color_map()) do
if not (COLOR_NAME_SETTINGS.strip_digits and k:match("%d+$")) then
COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#k, COLOR_NAME_MINLEN) or #k
COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#k, COLOR_NAME_MAXLEN) or #k
local rgb_hex = tohex(v, 6)
COLOR_MAP[k] = rgb_hex
COLOR_TRIE:insert(k)
if COLOR_NAME_SETTINGS.lowercase then
local lowercase = k:lower()
COLOR_MAP[lowercase] = rgb_hex
COLOR_TRIE:insert(lowercase)
end
end
end
end
end
@ -53,6 +67,15 @@ local DEFAULT_OPTIONS = {
mode = 'background'; -- Set the display mode.
}
-- -- TODO use rgb as the return value from the matcher functions
-- -- instead of the rgb_hex. Can be the highlight key as well
-- -- when you shift it left 8 bits. Use the lower 8 bits for
-- -- indicating which highlight mode to use.
-- ffi.cdef [[
-- typedef struct { uint8_t r, g, b; } colorizer_rgb;
-- ]]
-- local rgb_t = ffi.typeof 'colorizer_rgb'
-- Create a lookup table where the bottom 4 bits are used to indicate the
-- category and the top 4 bits are the hex value of the ASCII byte.
local BYTE_CATEGORY = ffi.new 'uint8_t[256]'
@ -144,11 +167,12 @@ local function hsl_to_rgb(h, s, l)
return 255*hue_to_rgb(p, q, h + 1/3), 255*hue_to_rgb(p, q, h), 255*hue_to_rgb(p, q, h - 1/3)
end
local function name_parser(line, i)
local function color_name_parser(line, i)
if i > 1 and byte_is_alphanumeric(line:byte(i-1)) then
return
end
local prefix = COLOR_TRIE:longest_prefix(line:sub(i))
if #line < i + COLOR_NAME_MINLEN - 1 then return end
local prefix = COLOR_TRIE:longest_prefix(line, i)
if prefix then
-- Check if there is a letter here so as to disallow matching here.
-- Take the Blue out of Blueberry
@ -174,7 +198,7 @@ local function rgb_hex_parser(line, i, minlen, maxlen)
local n = j + maxlen
local alpha
local v = 0
while j <= math.min(n, #line) do
while j <= min(n, #line) do
local b = line:byte(j)
if not byte_is_hex(b) then break end
if j - i >= 7 then
@ -205,23 +229,22 @@ end
-- Things like pumblend might be useful here.
local css_fn = {}
do
local css_rgb_fn_minimum_length = #'rgb(0,0,0)' - 1
local css_rgba_fn_minimum_length = #'rgba(0,0,0,0)' - 1
local css_hsl_fn_minimum_length = #'hsl(0,0%,0%)' - 1
local css_hsla_fn_minimum_length = #'hsla(0,0%,0%,0)' - 1
local CSS_RGB_FN_MINIMUM_LENGTH = #'rgb(0,0,0)' - 1
local CSS_RGBA_FN_MINIMUM_LENGTH = #'rgba(0,0,0,0)' - 1
local CSS_HSL_FN_MINIMUM_LENGTH = #'hsl(0,0%,0%)' - 1
local CSS_HSLA_FN_MINIMUM_LENGTH = #'hsla(0,0%,0%,0)' - 1
function css_fn.rgb(line, i)
if #line < i + css_rgb_fn_minimum_length then return end
if #line < i + CSS_RGB_FN_MINIMUM_LENGTH then return end
local r, g, b, match_end = line:sub(i):match("^rgb%(%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*(%d+%%?)%s*%)()")
if not match_end then return end
r = percent_or_hex(r) if not r then return end
g = percent_or_hex(g) if not g then return end
b = percent_or_hex(b) if not b then return end
local rgb_hex = ("%02x%02x%02x"):format(r,g,b)
if #rgb_hex ~= 6 then return end
local rgb_hex = tohex(bor(lshift(r, 16), lshift(g, 8), b), 6)
return match_end - 1, rgb_hex
end
function css_fn.hsl(line, i)
if #line < i + css_hsl_fn_minimum_length then return end
if #line < i + CSS_HSL_FN_MINIMUM_LENGTH then return end
local h, s, l, match_end = line:sub(i):match("^hsl%(%s*(%d+)%s*,%s*(%d+)%%%s*,%s*(%d+)%%%s*%)()")
if not match_end then return end
h = tonumber(h) if h > 360 then return end
@ -229,24 +252,22 @@ do
l = tonumber(l) if l > 100 then return end
local r, g, b = hsl_to_rgb(h/360, s/100, l/100)
if r == nil or g == nil or b == nil then return end
local rgb_hex = ("%02x%02x%02x"):format(floor(r), floor(g), floor(b))
if #rgb_hex ~= 6 then return end
local rgb_hex = tohex(bor(lshift(floor(r), 16), lshift(floor(g), 8), floor(b)), 6)
return match_end - 1, rgb_hex
end
function css_fn.rgba(line, i)
if #line < i + css_rgba_fn_minimum_length then return end
if #line < i + CSS_RGBA_FN_MINIMUM_LENGTH then return end
local r, g, b, a, match_end = line:sub(i):match("^rgba%(%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*([.%d]+)%s*%)()")
if not match_end then return end
a = tonumber(a) if not a or a > 1 then return end
r = percent_or_hex(r) if not r then return end
g = percent_or_hex(g) if not g then return end
b = percent_or_hex(b) if not b then return end
local rgb_hex = ("%02x%02x%02x"):format(floor(r*a), floor(g*a), floor(b*a))
if #rgb_hex ~= 6 then return end
local rgb_hex = tohex(bor(lshift(floor(r*a), 16), lshift(floor(g*a), 8), floor(b*a)), 6)
return match_end - 1, rgb_hex
end
function css_fn.hsla(line, i)
if #line < i + css_hsla_fn_minimum_length then return end
if #line < i + CSS_HSLA_FN_MINIMUM_LENGTH then return end
local h, s, l, a, match_end = line:sub(i):match("^hsla%(%s*(%d+)%s*,%s*(%d+)%%%s*,%s*(%d+)%%%s*,%s*([.%d]+)%s*%)()")
if not match_end then return end
a = tonumber(a) if not a or a > 1 then return end
@ -255,8 +276,7 @@ do
l = tonumber(l) if l > 100 then return end
local r, g, b = hsl_to_rgb(h/360, s/100, l/100)
if r == nil or g == nil or b == nil then return end
local rgb_hex = ("%02x%02x%02x"):format(floor(r*a), floor(g*a), floor(b*a))
if #rgb_hex ~= 6 then return end
local rgb_hex = tohex(bor(lshift(floor(r*a), 16), lshift(floor(g*a), 8), floor(b*a)), 6)
return match_end - 1, rgb_hex
end
end
@ -377,15 +397,15 @@ local function make_matcher(options)
local loop_matchers = {}
if enable_names then
table.insert(loop_matchers, name_parser)
table.insert(loop_matchers, color_name_parser)
end
do
local valid_lengths = {[3] = enable_RGB, [6] = enable_RRGGBB, [8] = enable_RRGGBBAA}
local minlen, maxlen
for k, v in pairs(valid_lengths) do
if v then
minlen = math.min(k, minlen or 99)
maxlen = math.max(k, maxlen or 0)
minlen = minlen and min(k, minlen) or k
maxlen = maxlen and max(k, maxlen) or k
end
end
if minlen then
@ -535,19 +555,25 @@ end
-- @param[opt={'*'}] filetypes A table/array of filetypes to selectively enable and/or customize. By default, enables all filetypes.
-- @tparam[opt] {[string]=string} default_options Default options to apply for the filetypes enable.
-- @usage require'colorizer'.setup()
local function setup(filetypes, default_options)
local function setup(filetypes, user_default_options, global_configuration)
if not nvim.o.termguicolors then
nvim.err_writeln("&termguicolors must be set")
return
end
initialize_trie()
FILETYPE_OPTIONS = {}
SETUP_SETTINGS = {
exclusions = {};
default_options = merge(DEFAULT_OPTIONS, default_options or {});
default_options = merge(DEFAULT_OPTIONS, user_default_options or {});
}
-- This is just in case I accidentally reference the wrong thing here.
default_options = SETUP_SETTINGS.default_options
global_configuration = global_configuration or {
lowercase = false;
on_enter = false;
}
if type(global_configuration.lowercase) == 'boolean' then
COLOR_NAME_SETTINGS.lowercase = global_configuration.lowercase
end
-- Initialize this AFTER setting COLOR_NAME_SETTINGS
initialize_trie()
function COLORIZER_SETUP_HOOK()
local filetype = nvim.bo.filetype
if SETUP_SETTINGS.exclusions[filetype] then
@ -558,7 +584,9 @@ local function setup(filetypes, default_options)
end
nvim.ex.augroup("ColorizerSetup")
nvim.ex.autocmd_()
-- nvim.ex.autocmd("VimEnter * lua COLORIZER_SETUP_HOOK()")
if global_configuration.on_enter then
nvim.ex.autocmd("VimEnter * lua COLORIZER_SETUP_HOOK()")
end
if not filetypes then
nvim.ex.autocmd("FileType * lua COLORIZER_SETUP_HOOK()")
else

View file

@ -34,6 +34,19 @@ local function trie_create()
return ffi.cast(Trie_ptr_t, ptr)
end
local function trie_destroy(trie)
if trie == nil then
return
end
for i = 0, 61 do
local child = trie.character[i]
if child ~= nil then
trie_destroy(child)
end
end
ffi.C.free(trie)
end
local INDEX_LOOKUP_TABLE = ffi.new 'uint8_t[256]'
local CHAR_LOOKUP_TABLE = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
do
@ -149,7 +162,7 @@ local function trie_as_table(trie)
}
end
local function print_trie_table(s)
local function print_trie_table(s, thicc)
local mark
if not s then
return {'nil'}
@ -168,46 +181,48 @@ local function print_trie_table(s)
end
local lines = {}
for _, child in ipairs(s.children) do
local child_lines = print_trie_table(child)
local child_lines = print_trie_table(child, thicc)
for _, child_line in ipairs(child_lines) do
table.insert(lines, child_line)
end
end
local child_count = 0
for i, v in ipairs(lines) do
if v:match("^[%w%d]") then
for i, line in ipairs(lines) do
local line_parts = {}
-- if line[1] and line[1]:match("^%w") then
if line:match("^%w") then
child_count = child_count + 1
if i == 1 then
lines[i] = mark..""..v
line_parts = {mark}
elseif i == #lines or child_count == #s.children then
lines[i] = "└──"..v
line_parts = {"└─"}
else
lines[i] = "├──"..v
line_parts = {"├─"}
end
if thicc then table.insert(line_parts, "") end
else
if i == 1 then
lines[i] = mark..""..v
line_parts = {mark}
if thicc then table.insert(line_parts, "") end
elseif #s.children > 1 and child_count ~= #s.children then
lines[i] = ""..v
line_parts = {thicc and "" or ""}
else
lines[i] = " "..v
line_parts = {thicc and " " or " "}
end
end
table.insert(line_parts, line)
-- lines[i] = vim.tbl_flatten(line_parts)
lines[i] = table.concat(line_parts)
end
return lines
end
local function trie_destroy(trie)
local function trie_to_string(trie, thicc)
if trie == nil then
return
return 'nil'
end
for i = 0, 61 do
local child = trie.character[i]
if child ~= nil then
trie_destroy(child)
end
end
ffi.C.free(trie)
local as_table = trie_as_table(trie)
return table.concat(print_trie_table(as_table, thicc), '\n')
end
local Trie_mt = {
@ -223,13 +238,9 @@ local Trie_mt = {
search = trie_search;
longest_prefix = trie_longest_prefix;
extend = trie_extend;
to_string = trie_to_string;
};
__tostring = function(trie)
if trie == nil then
return 'nil'
end
return table.concat(print_trie_table(trie_as_table(trie)), '\n')
end;
__tostring = trie_to_string;
__gc = trie_destroy;
}

View file

@ -8,9 +8,9 @@ require'colorizer'.attach_to_buffer(0, {css=true})
#F0F
#FF00FF
#FFF00F8F
#F0F 1
#FF00FF 1
#FFF00F8F 1
#F0F #F00
#FF00FF #F00
#FFF00F8F #F00
Blue Gray LightBlue Gray100 White
White
#def
@ -22,6 +22,7 @@ hsl(300,50%,50%)
hsla(300,50%,50%,0.5)
hsla(300,50%,50%,1.0000000000000001)
hsla(360,50%,50%,1.0000000000000001)
blue gray lightblue gray100 white gold blue
]]
--[[ FAIL

36
test/print-trie.lua Normal file
View file

@ -0,0 +1,36 @@
-- TODO this is kinda shitty
local function dirname(str,sep)
sep=sep or'/'
return str:match("(.*"..sep..")")
end
local script_dir = dirname(arg[0])
package.path = script_dir.."/../lua/?.lua;"..package.path
local Trie = require 'trie'
local nvim = require 'nvim'
local tohex = bit.tohex
local min, max = math.min, math.max
local COLOR_NAME_SETTINGS = {
lowercase = false;
strip_digits = true;
}
COLOR_MAP = {}
COLOR_TRIE = Trie()
for k, v in pairs(nvim.get_color_map()) do
if not (COLOR_NAME_SETTINGS.strip_digits and k:match("%d+$")) then
COLOR_NAME_MINLEN = COLOR_NAME_MINLEN and min(#k, COLOR_NAME_MINLEN) or #k
COLOR_NAME_MAXLEN = COLOR_NAME_MAXLEN and max(#k, COLOR_NAME_MAXLEN) or #k
COLOR_MAP[k] = tohex(v, 6)
COLOR_TRIE:insert(k)
if COLOR_NAME_SETTINGS.lowercase then
local lowercase = k:lower()
COLOR_MAP[lowercase] = tohex(v, 6)
COLOR_TRIE:insert(lowercase)
end
end
end
print(COLOR_TRIE)