aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAshkan Kiani <ashkan.k.kiani@gmail.com>2019-10-18 14:27:02 -0700
committerGitHub <noreply@github.com>2019-10-18 14:27:02 -0700
commit6c3d4e4b4ce8a4eb0b5ac012e38ca1f66af4b077 (patch)
treea13787a3bbac64a82ee95ba14fcc75ff2cad4a69
parentAllow excluding files from highlight with '*' (#11) (diff)
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
-rw-r--r--README.md19
-rw-r--r--doc/colorizer-lua.txt17
-rw-r--r--lua/colorizer.lua246
-rw-r--r--lua/trie.lua40
4 files changed, 245 insertions, 77 deletions
diff --git a/README.md b/README.md
index cf25c87..a0f499d 100644
--- a/README.md
+++ b/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.
}
```
diff --git a/doc/colorizer-lua.txt b/doc/colorizer-lua.txt
index 9f2afda..39a97a2 100644
--- a/doc/colorizer-lua.txt
+++ b/doc/colorizer-lua.txt
@@ -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.
}
<
diff --git a/lua/colorizer.lua b/lua/colorizer.lua
index 32717e5..22e28c8 100644
--- a/lua/colorizer.lua
+++ b/lua/colorizer.lua
@@ -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;
}
diff --git a/lua/trie.lua b/lua/trie.lua
index b0751aa..e74d9eb 100644
--- a/lua/trie.lua
+++ b/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";