mirror of
https://github.com/norcalli/nvim-colorizer.lua.git
synced 2026-09-10 07:06:14 -04:00
Update color functions (#13)
- Add hsl, hsla, rgba, RRGGBBAA - Redo the parser for rgb,rgba,hsl,hsla codes. - Fix the trie implementation. - Bugfix and refactor alpha/error handling
This commit is contained in:
parent
f661199197
commit
6c3d4e4b4c
19
README.md
19
README.md
|
|
@ -36,15 +36,16 @@ Additionally, having a Lua API that's available means users can use this as a li
|
|||
|
||||
```lua
|
||||
DEFAULT_OPTIONS = {
|
||||
RGB = true; -- #RGB hex codes
|
||||
RRGGBB = true; -- #RRGGBB hex codes
|
||||
names = true; -- "Name" codes like Blue
|
||||
rgb_fn = false; -- CSS rgb() and rgba() functions
|
||||
hsl_fn = false; -- CSS hsl() and hsla() functions
|
||||
css = false; -- Enable all features above.
|
||||
css_fn = false; -- Enable all CSS *functions*: rgb_fn, hsl_fn
|
||||
-- Available modes: foreground, background
|
||||
mode = 'background'; -- Set the display mode.
|
||||
RGB = true; -- #RGB hex codes
|
||||
RRGGBB = true; -- #RRGGBB hex codes
|
||||
names = true; -- "Name" codes like Blue
|
||||
RRGGBBAA = false; -- #RRGGBBAA hex codes
|
||||
rgb_fn = false; -- CSS rgb() and rgba() functions
|
||||
hsl_fn = false; -- CSS hsl() and hsla() functions
|
||||
css = false; -- Enable all CSS features: rgb_fn, hsl_fn, names, RGB, RRGGBB
|
||||
css_fn = false; -- Enable all CSS *functions*: rgb_fn, hsl_fn
|
||||
-- Available modes: foreground, background
|
||||
mode = 'background'; -- Set the display mode.
|
||||
}
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -50,15 +50,16 @@ Assumes the module is imported as `colorizer`
|
|||
|
||||
>
|
||||
DEFAULT_OPTIONS = {
|
||||
RGB = true; -- #RGB hex codes
|
||||
RRGGBB = true; -- #RRGGBB hex codes
|
||||
names = true; -- "Name" codes like Blue
|
||||
rgb_fn = false; -- CSS rgb() and rgba() functions
|
||||
hsl_fn = false; -- CSS hsl() and hsla() functions
|
||||
css = false; -- Enable all features above.
|
||||
css_fn = false; -- Enable all CSS *functions*: rgb_fn, hsl_fn
|
||||
RGB = true; -- #RGB hex codes
|
||||
RRGGBB = true; -- #RRGGBB hex codes
|
||||
names = true; -- "Name" codes like Blue
|
||||
RRGGBBAA = false; -- #RRGGBBAA hex codes
|
||||
rgb_fn = false; -- CSS rgb() and rgba() functions
|
||||
hsl_fn = false; -- CSS hsl() and hsla() functions
|
||||
css = false; -- Enable all CSS features: rgb_fn, hsl_fn, names, RGB, RRGGBB
|
||||
css_fn = false; -- Enable all CSS *functions*: rgb_fn, hsl_fn
|
||||
-- Available modes: foreground, background
|
||||
mode = 'background'; -- Set the display mode.
|
||||
mode = 'background'; -- Set the display mode.
|
||||
}
|
||||
<
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ local DEFAULT_NAMESPACE = nvim.create_namespace 'colorizer'
|
|||
|
||||
local COLOR_MAP
|
||||
local COLOR_TRIE
|
||||
local CSS_FUNCTION_TRIE = Trie()
|
||||
for _, v in ipairs{'rgb', 'rgba', 'hsl', 'hsla'} do CSS_FUNCTION_TRIE:insert(v) end
|
||||
local RGB_FUNCTION_TRIE = Trie()
|
||||
for _, v in ipairs{'rgb', 'rgba'} do RGB_FUNCTION_TRIE:insert(v) end
|
||||
local HSL_FUNCTION_TRIE = Trie()
|
||||
for _, v in ipairs{'hsl', 'hsla'} do HSL_FUNCTION_TRIE:insert(v) end
|
||||
|
||||
--- Setup the COLOR_MAP and COLOR_TRIE
|
||||
local function initialize_trie()
|
||||
|
|
@ -54,15 +60,16 @@ local function color_is_bright(r, g, b)
|
|||
end
|
||||
|
||||
local DEFAULT_OPTIONS = {
|
||||
RGB = true; -- #RGB hex codes
|
||||
RRGGBB = true; -- #RRGGBB hex codes
|
||||
names = true; -- "Name" codes like Blue
|
||||
rgb_fn = false; -- CSS rgb() and rgba() functions
|
||||
hsl_fn = false; -- CSS hsl() and hsla() functions
|
||||
css = false; -- Enable all CSS features: rgb_fn, hsl_fn, names, RGB, RRGGBB
|
||||
css_fn = false; -- Enable all CSS *functions*: rgb_fn, hsl_fn
|
||||
RGB = true; -- #RGB hex codes
|
||||
RRGGBB = true; -- #RRGGBB hex codes
|
||||
names = true; -- "Name" codes like Blue
|
||||
RRGGBBAA = false; -- #RRGGBBAA hex codes
|
||||
rgb_fn = false; -- CSS rgb() and rgba() functions
|
||||
hsl_fn = false; -- CSS hsl() and hsla() functions
|
||||
css = false; -- Enable all CSS features: rgb_fn, hsl_fn, names, RGB, RRGGBB
|
||||
css_fn = false; -- Enable all CSS *functions*: rgb_fn, hsl_fn
|
||||
-- Available modes: foreground, background
|
||||
mode = 'background'; -- Set the display mode.
|
||||
mode = 'background'; -- Set the display mode.
|
||||
}
|
||||
|
||||
local HIGHLIGHT_NAME_PREFIX = "colorizer"
|
||||
|
|
@ -118,6 +125,158 @@ local SETUP_SETTINGS = {
|
|||
default_options = DEFAULT_OPTIONS;
|
||||
}
|
||||
|
||||
local function name_parser(line, i)
|
||||
local prefix = COLOR_TRIE:longest_prefix(line:sub(i))
|
||||
if prefix then
|
||||
local rgb = COLOR_MAP[prefix]
|
||||
local rgb_hex = bit.tohex(rgb):sub(-6)
|
||||
return #prefix, rgb_hex
|
||||
end
|
||||
end
|
||||
|
||||
local css_fn = {}
|
||||
|
||||
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 function percent_or_hex(v)
|
||||
if v:sub(-1,-1) == "%" then
|
||||
return tonumber(v:sub(1,-2))/100*255
|
||||
end
|
||||
local x = tonumber(v)
|
||||
if x > 255 then return end
|
||||
return x
|
||||
end
|
||||
|
||||
function css_fn.rgb(line, i)
|
||||
if #line < i + css_rgb_fn_minimum_length then return end
|
||||
-- TODO this might be able to be improved.
|
||||
local r, g, b, match_end = line:sub(i):match("^rgb%(%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*(%d+%%?)%s*%)()")
|
||||
if not r 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
|
||||
return match_end - 1, rgb_hex
|
||||
end
|
||||
|
||||
-- Pattern for rgba() functions from CSS
|
||||
function css_fn.rgba(line, i)
|
||||
if #line < i + css_rgba_fn_minimum_length then return end
|
||||
-- TODO this might be able to be improved.
|
||||
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 r 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
|
||||
-- TODO this might not be the best approach to alpha channel.
|
||||
-- Things like pumblend might be useful here.
|
||||
r, g, b = r*a, g*a, b*a
|
||||
r, g, b = math.floor(r), math.floor(g), math.floor(b)
|
||||
local rgb_hex = ("%02x%02x%02x"):format(r,g,b)
|
||||
if #rgb_hex ~= 6 then
|
||||
return
|
||||
end
|
||||
return match_end - 1, rgb_hex
|
||||
end
|
||||
|
||||
-- https://gist.github.com/mjackson/5311256
|
||||
local function hue_to_rgb(p, q, t)
|
||||
if t < 0 then t = t + 1 end
|
||||
if t > 1 then t = t - 1 end
|
||||
if t < 1/6 then return p + (q - p) * 6 * t end
|
||||
if t < 1/2 then return q end
|
||||
if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end
|
||||
return p
|
||||
end
|
||||
|
||||
local function hsl_to_rgb(h, s, l)
|
||||
if h > 1 or s > 1 or l > 1 then return end
|
||||
if s == 0 then
|
||||
local r = l * 255
|
||||
return r, r, r
|
||||
end
|
||||
|
||||
local q
|
||||
if l < 0.5 then
|
||||
q = l * (1 + s)
|
||||
else
|
||||
q = l + s - l * s
|
||||
end
|
||||
local p = 2 * l - q
|
||||
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
|
||||
|
||||
function css_fn.hsl(line, i)
|
||||
if #line < i + css_hsl_fn_minimum_length then return end
|
||||
-- TODO this might be able to be improved.
|
||||
local h, s, l, match_end = line:sub(i):match("^hsl%(%s*(%d+)%s*,%s*(%d+)%%%s*,%s*(%d+)%%%s*%)()")
|
||||
if not h then return end
|
||||
h = tonumber(h) if h > 360 then return end
|
||||
s = tonumber(s) if s > 100 then return end
|
||||
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(math.floor(r), math.floor(g), math.floor(b))
|
||||
if #rgb_hex ~= 6 then return end
|
||||
return match_end-1, rgb_hex
|
||||
end
|
||||
|
||||
function css_fn.hsla(line, i)
|
||||
if #line < i + css_hsla_fn_minimum_length then return end
|
||||
-- TODO this might be able to be improved.
|
||||
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 h then return end
|
||||
a = tonumber(a) if not a or a > 1 then return end
|
||||
h = tonumber(h) if h > 360 then return end
|
||||
s = tonumber(s) if s > 100 then return end
|
||||
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(math.floor(r*a), math.floor(g*a), math.floor(b*a))
|
||||
if #rgb_hex ~= 6 then return end
|
||||
return match_end-1, rgb_hex
|
||||
end
|
||||
|
||||
local function css_function_parser(line, i)
|
||||
local prefix = CSS_FUNCTION_TRIE:longest_prefix(line:sub(i))
|
||||
if prefix then
|
||||
return css_fn[prefix](line, i)
|
||||
end
|
||||
end
|
||||
|
||||
local function rgb_function_parser(line, i)
|
||||
local prefix = RGB_FUNCTION_TRIE:longest_prefix(line:sub(i))
|
||||
if prefix then
|
||||
return css_fn[prefix](line, i)
|
||||
end
|
||||
end
|
||||
|
||||
local function hsl_function_parser(line, i)
|
||||
local prefix = HSL_FUNCTION_TRIE:longest_prefix(line:sub(i))
|
||||
if prefix then
|
||||
return css_fn[prefix](line, i)
|
||||
end
|
||||
end
|
||||
|
||||
local function compile_matcher(matchers)
|
||||
local parse_fn = matchers[1]
|
||||
for j = 2, #matchers do
|
||||
local old_parse_fn = parse_fn
|
||||
local new_parse_fn = matchers[j]
|
||||
parse_fn = function(line, i)
|
||||
local length, rgb_hex = new_parse_fn(line, i)
|
||||
if length then return length, rgb_hex end
|
||||
return old_parse_fn(line, i)
|
||||
end
|
||||
end
|
||||
return parse_fn
|
||||
end
|
||||
|
||||
--[[-- Highlight the buffer region.
|
||||
Highlight starting from `line_start` (0-indexed) for each line described by `lines` in the
|
||||
buffer `buf` and attach it to the namespace `ns`.
|
||||
|
|
@ -130,13 +289,26 @@ buffer `buf` and attach it to the namespace `ns`.
|
|||
@see setup
|
||||
]]
|
||||
local function highlight_buffer(buf, ns, lines, line_start, options)
|
||||
local enable_names = options.names
|
||||
local enable_RGB = options.css or options.RGB
|
||||
local enable_RRGGBB = options.css or options.RRGGBB
|
||||
local enable_rgb = options.css or options.css_fns or options.rgb_fn
|
||||
local enable_rgba = options.css or options.css_fns or options.rgb_fn
|
||||
local enable_hsl = options.css or options.css_fns or options.hsl_fn
|
||||
local enable_hsla = options.css or options.css_fns or options.hsl_fn
|
||||
local enable_names = options.names
|
||||
local enable_RGB = options.css or options.RGB
|
||||
local enable_RRGGBB = options.css or options.RRGGBB
|
||||
local enable_RRGGBBAA = options.css or options.RRGGBBAA
|
||||
local enable_rgb = options.css or options.css_fns or options.rgb_fn
|
||||
local enable_hsl = options.css or options.css_fns or options.hsl_fn
|
||||
|
||||
local loop_parse_fn
|
||||
local loop_matchers = {}
|
||||
if enable_names then table.insert(loop_matchers, name_parser) end
|
||||
if enable_rgb and enable_hsl then
|
||||
table.insert(loop_matchers, css_function_parser)
|
||||
elseif enable_rgb then
|
||||
table.insert(loop_matchers, rgb_function_parser)
|
||||
elseif enable_hsl then
|
||||
table.insert(loop_matchers, hsl_function_parser)
|
||||
end
|
||||
if #loop_matchers > 0 then
|
||||
loop_parse_fn = compile_matcher(loop_matchers)
|
||||
end
|
||||
-- TODO do I have to put this here?
|
||||
initialize_trie()
|
||||
ns = ns or DEFAULT_NAMESPACE
|
||||
|
|
@ -147,21 +319,6 @@ local function highlight_buffer(buf, ns, lines, line_start, options)
|
|||
local highlight_name = create_highlight(rgb_hex, options)
|
||||
nvim_buf_add_highlight(buf, ns, highlight_name, current_linenum, match_start-1, match_end-1)
|
||||
end
|
||||
if enable_rgb then
|
||||
-- TODO this can have improved performance by either reusing my trie or
|
||||
-- doing a byte comp.
|
||||
-- Pattern for rgb() functions from CSS
|
||||
line:gsub("()rgb%(%s*(%d+%%?)%s*,%s*(%d+%%?)%s*,%s*(%d+%%?)%s*%)()", function(match_start, r,g,b, match_end)
|
||||
if r:sub(-1,-1) == "%" then r = math.floor(r:sub(1,-2)/100*255) end
|
||||
if g:sub(-1,-1) == "%" then g = math.floor(g:sub(1,-2)/100*255) end
|
||||
if b:sub(-1,-1) == "%" then b = math.floor(b:sub(1,-2)/100*255) end
|
||||
local rgb_hex = ("%02x%02x%02x"):format(r,g,b)
|
||||
if #rgb_hex ~= 6 then
|
||||
return
|
||||
end
|
||||
highlight_line_rgb_hex(match_start, rgb_hex, match_end)
|
||||
end)
|
||||
end
|
||||
if enable_RGB then
|
||||
-- Pattern for #RGB, part 1. No trailing characters allowed
|
||||
line:gsub("()#([%da-fA-F][%da-fA-F][%da-fA-F])()%W", highlight_line_rgb_hex)
|
||||
|
|
@ -172,17 +329,26 @@ local function highlight_buffer(buf, ns, lines, line_start, options)
|
|||
-- Pattern for #RRGGBB
|
||||
line:gsub("()#([%da-fA-F][%da-fA-F][%da-fA-F][%da-fA-F][%da-fA-F][%da-fA-F])()", highlight_line_rgb_hex)
|
||||
end
|
||||
if enable_names then
|
||||
if enable_RRGGBBAA then
|
||||
-- Pattern for #RRGGBB
|
||||
line:gsub("()#([%da-fA-F][%da-fA-F])([%da-fA-F][%da-fA-F])([%da-fA-F][%da-fA-F])([%da-fA-F][%da-fA-F])()", function(match_start, r, g, b, a, match_end)
|
||||
a = tonumber(a, 16) if a > 255 then return end
|
||||
r = tonumber(r, 16) if r > 255 then return end
|
||||
g = tonumber(g, 16) if g > 255 then return end
|
||||
b = tonumber(b, 16) if b > 255 then return end
|
||||
a = a / 255
|
||||
local rgb_hex = ("%02x%02x%02x"):format(math.floor(r*a), math.floor(g*a), math.floor(b*a))
|
||||
if #rgb_hex ~= 6 then return end
|
||||
highlight_line_rgb_hex(match_start, rgb_hex, match_end)
|
||||
end)
|
||||
end
|
||||
if loop_parse_fn then
|
||||
local i = 1
|
||||
while i < #line do
|
||||
-- TODO skip if the remaining length is less than the shortest length
|
||||
-- of an entry in our trie.
|
||||
local prefix = COLOR_TRIE:longest_prefix(line:sub(i))
|
||||
if prefix then
|
||||
local rgb = COLOR_MAP[prefix]
|
||||
local rgb_hex = bit.tohex(rgb):sub(-6)
|
||||
highlight_line_rgb_hex(i, rgb_hex, i+#prefix)
|
||||
i = i + #prefix
|
||||
local length, rgb_hex = loop_parse_fn(line, i)
|
||||
if length then
|
||||
highlight_line_rgb_hex(i, rgb_hex, i+length)
|
||||
i = i + length
|
||||
else
|
||||
i = i + 1
|
||||
end
|
||||
|
|
@ -238,7 +404,6 @@ local function attach_to_buffer(buf, options)
|
|||
nvim_buf_clear_namespace(buf, ns, firstline, new_lastline)
|
||||
local lines = nvim_buf_get_lines(buf, firstline, new_lastline, true)
|
||||
highlight_buffer(buf, ns, lines, firstline, BUFFER_OPTIONS[buf])
|
||||
-- highlight_buffer(buf, ns, lines, firstline, BUFFER_OPTIONS[buf] or options)
|
||||
end;
|
||||
on_detach = function()
|
||||
BUFFER_OPTIONS[buf] = nil
|
||||
|
|
@ -356,6 +521,5 @@ return {
|
|||
highlight_buffer = highlight_buffer;
|
||||
reload_all_buffers = reload_all_buffers;
|
||||
get_buffer_options = get_buffer_options;
|
||||
-- initialize = initialize_trie;
|
||||
}
|
||||
|
||||
|
|
|
|||
40
lua/trie.lua
40
lua/trie.lua
|
|
@ -30,6 +30,7 @@ void free(void *ptr);
|
|||
|
||||
local Trie_t = ffi.typeof('struct Trie')
|
||||
local Trie_ptr_t = ffi.typeof('$ *', Trie_t)
|
||||
local Trie_size = ffi.sizeof(Trie_t)
|
||||
|
||||
local function byte_to_index(b)
|
||||
-- 0-9 starts at string.byte('0') == 0x30 == 48 == 0b0011_0000
|
||||
|
|
@ -75,8 +76,6 @@ local function verify_byte_to_index()
|
|||
end
|
||||
end
|
||||
|
||||
local Trie_size = ffi.sizeof(Trie_t)
|
||||
|
||||
local function new_trie()
|
||||
local ptr = ffi.C.malloc(Trie_size)
|
||||
ffi.fill(ptr, Trie_size)
|
||||
|
|
@ -237,6 +236,9 @@ local function print_structure(s)
|
|||
end
|
||||
|
||||
local function free_trie(trie)
|
||||
if trie == nil then
|
||||
return
|
||||
end
|
||||
for i = 0, 61 do
|
||||
local child = trie.character[i]
|
||||
if child ~= nil then
|
||||
|
|
@ -247,24 +249,24 @@ local function free_trie(trie)
|
|||
end
|
||||
|
||||
local Trie_mt = {
|
||||
__index = {
|
||||
insert = insert;
|
||||
search = search;
|
||||
longest_prefix = longest_prefix;
|
||||
};
|
||||
__tostring = function(trie)
|
||||
local structure = trie_structure(trie)
|
||||
if structure then
|
||||
return table.concat(print_structure(structure), '\n')
|
||||
else
|
||||
return 'nil'
|
||||
end
|
||||
end;
|
||||
__gc = free_trie;
|
||||
}
|
||||
local Trie = ffi.metatype(Trie_t, Trie_mt)
|
||||
__new = new_trie;
|
||||
__index = {
|
||||
insert = insert;
|
||||
search = search;
|
||||
longest_prefix = longest_prefix;
|
||||
};
|
||||
__tostring = function(trie)
|
||||
local structure = trie_structure(trie)
|
||||
if structure then
|
||||
return table.concat(print_structure(structure), '\n')
|
||||
else
|
||||
return 'nil'
|
||||
end
|
||||
end;
|
||||
__gc = free_trie;
|
||||
}
|
||||
|
||||
return Trie
|
||||
return ffi.metatype('struct Trie', Trie_mt)
|
||||
|
||||
-- local tests = {
|
||||
-- "cat";
|
||||
|
|
|
|||
Loading…
Reference in a new issue