aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lua/colorizer.lua525
-rw-r--r--lua/trie.lua96
-rw-r--r--test/expectation.txt45
3 files changed, 405 insertions, 261 deletions
diff --git a/lua/colorizer.lua b/lua/colorizer.lua
index 22e28c8..f127b19 100644
--- a/lua/colorizer.lua
+++ b/lua/colorizer.lua
@@ -2,26 +2,19 @@
-- @module colorizer
local nvim = require 'nvim'
local Trie = require 'trie'
+local bit = require 'bit'
+local ffi = require 'ffi'
local nvim_buf_add_highlight = vim.api.nvim_buf_add_highlight
-local nvim_get_current_buf = vim.api.nvim_get_current_buf
-local nvim_buf_get_lines = vim.api.nvim_buf_get_lines
local nvim_buf_clear_namespace = vim.api.nvim_buf_clear_namespace
-
---- Default namespace used in `highlight_buffer` and `attach_to_buffer`.
--- The name is "terminal_highlight"
--- @see highlight_buffer
--- @see attach_to_buffer
-local DEFAULT_NAMESPACE = nvim.create_namespace 'colorizer'
+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 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()
@@ -29,7 +22,8 @@ local function initialize_trie()
COLOR_MAP = nvim.get_color_map()
COLOR_TRIE = Trie()
- for k in pairs(COLOR_MAP) do
+ for k, v in pairs(COLOR_MAP) do
+ COLOR_MAP[k] = tohex(v, 6)
COLOR_TRIE:insert(k)
end
end
@@ -46,19 +40,6 @@ local function merge(...)
return res
end
---- Determine whether to use black or white text
--- Ref: https://stackoverflow.com/a/1855903/837964
--- https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
-local function color_is_bright(r, g, b)
- -- Counting the perceptive luminance - human eye favors green color
- local luminance = (0.299*r + 0.587*g + 0.114*b)/255
- if luminance > 0.5 then
- return true -- Bright colors, black font
- else
- return false -- Dark colors, white font
- end
-end
-
local DEFAULT_OPTIONS = {
RGB = true; -- #RGB hex codes
RRGGBB = true; -- #RRGGBB hex codes
@@ -72,74 +53,48 @@ local DEFAULT_OPTIONS = {
mode = 'background'; -- Set the display mode.
}
-local HIGHLIGHT_NAME_PREFIX = "colorizer"
-local MODE_NAMES = {
- background = 'mb';
- foreground = 'mf';
-}
-
-local HIGHLIGHT_CACHE = {}
-
---- Make a deterministic name for a highlight given these attributes
-local function make_highlight_name(rgb, mode)
- return table.concat({HIGHLIGHT_NAME_PREFIX, MODE_NAMES[mode], rgb}, '_')
-end
-
-local function create_highlight(rgb_hex, options)
- local mode = options.mode or 'background'
- -- TODO validate rgb format?
- rgb_hex = rgb_hex:lower()
- local cache_key = table.concat({MODE_NAMES[mode], rgb_hex}, "_")
- local highlight_name = HIGHLIGHT_CACHE[cache_key]
- -- Look up in our cache.
- if not highlight_name then
- if #rgb_hex == 3 then
- rgb_hex = table.concat {
- rgb_hex:sub(1,1):rep(2);
- rgb_hex:sub(2,2):rep(2);
- rgb_hex:sub(3,3):rep(2);
- }
+-- 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]'
+local CATEGORY_DIGIT = lshift(1, 0);
+local CATEGORY_ALPHA = lshift(1, 1);
+local CATEGORY_HEX = lshift(1, 2);
+local CATEGORY_ALPHANUM = bor(CATEGORY_ALPHA, CATEGORY_DIGIT)
+do
+ local b = string.byte
+ for i = 0, 255 do
+ local v = 0
+ -- Digit is bit 1
+ if i >= b'0' and i <= b'9' then
+ v = bor(v, lshift(1, 0))
+ v = bor(v, lshift(1, 2))
+ v = bor(v, lshift(i - b'0', 4))
end
- -- Create the highlight
- highlight_name = make_highlight_name(rgb_hex, mode)
- if mode == 'foreground' then
- nvim.ex.highlight(highlight_name, "guifg=#"..rgb_hex)
- else
- local r, g, b = rgb_hex:sub(1,2), rgb_hex:sub(3,4), rgb_hex:sub(5,6)
- r, g, b = tonumber(r,16), tonumber(g,16), tonumber(b,16)
- local fg_color
- if color_is_bright(r,g,b) then
- fg_color = "Black"
- else
- fg_color = "White"
+ local lowercase = bor(i, 0x20)
+ -- Alpha is bit 2
+ if lowercase >= b'a' and lowercase <= b'z' then
+ v = bor(v, lshift(1, 1))
+ if lowercase <= b'f' then
+ v = bor(v, lshift(1, 2))
+ v = bor(v, lshift(lowercase - b'a'+10, 4))
end
- nvim.ex.highlight(highlight_name, "guifg="..fg_color, "guibg=#"..rgb_hex)
end
- HIGHLIGHT_CACHE[cache_key] = highlight_name
+ BYTE_CATEGORY[i] = v
end
- return highlight_name
end
-local SETUP_SETTINGS = {
- exclusions = {};
- 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
+local function byte_is_hex(byte)
+ return band(BYTE_CATEGORY[byte], CATEGORY_HEX) ~= 0
end
-local css_fn = {}
+local function byte_is_alphanumeric(byte)
+ local category = BYTE_CATEGORY[byte]
+ return band(category, CATEGORY_ALPHANUM) ~= 0
+end
-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 parse_hex(b)
+ return rshift(BYTE_CATEGORY[b], 4)
+end
local function percent_or_hex(v)
if v:sub(-1,-1) == "%" then
@@ -150,38 +105,17 @@ local function percent_or_hex(v)
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
+--- Determine whether to use black or white text
+-- Ref: https://stackoverflow.com/a/1855903/837964
+-- https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
+local function color_is_bright(r, g, b)
+ -- Counting the perceptive luminance - human eye favors green color
+ local luminance = (0.299*r + 0.587*g + 0.114*b)/255
+ if luminance > 0.5 then
+ return true -- Bright colors, black font
+ else
+ return false -- Dark colors, white font
end
- return match_end - 1, rgb_hex
end
-- https://gist.github.com/mjackson/5311256
@@ -200,7 +134,6 @@ local function hsl_to_rgb(h, s, l)
local r = l * 255
return r, r, r
end
-
local q
if l < 0.5 then
q = l * (1 + s)
@@ -211,55 +144,144 @@ 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
-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))
+local function 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 prefix then
- return css_fn[prefix](line, i)
+ -- Check if there is a letter here so as to disallow matching here.
+ -- Take the Blue out of Blueberry
+ -- Line end or non-letter.
+ local next_byte_index = i + #prefix
+ if #line >= next_byte_index and byte_is_alphanumeric(line:byte(next_byte_index)) then
+ return
+ end
+ return #prefix, COLOR_MAP[prefix]
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)
+local b_hash = ("#"):byte()
+local function rgb_hex_parser(line, i, minlen, maxlen)
+ if i > 1 and byte_is_alphanumeric(line:byte(i-1)) then
+ return
end
+ if line:byte(i) ~= b_hash then
+ return
+ end
+ local j = i + 1
+ if #line < j + minlen - 1 then return end
+ local n = j + maxlen
+ local alpha
+ local v = 0
+ while j <= math.min(n, #line) do
+ local b = line:byte(j)
+ if not byte_is_hex(b) then break end
+ if j - i >= 7 then
+ alpha = parse_hex(b) + lshift(alpha or 0, 4)
+ else
+ v = parse_hex(b) + lshift(v, 4)
+ end
+ j = j + 1
+ end
+ if #line >= j and byte_is_alphanumeric(line:byte(j)) then
+ return
+ end
+ local length = j - i
+ if length ~= 4 and length ~= 7 and length ~= 9 then return end
+ if alpha then
+ alpha = tonumber(alpha)/255
+ local r = floor(band(v, 0xFF)*alpha)
+ local g = floor(band(rshift(v, 8), 0xFF)*alpha)
+ local b = floor(band(rshift(v, 16), 0xFF)*alpha)
+ v = bor(lshift(r, 16), lshift(g, 8), b)
+ return 9, tohex(v, 6)
+ end
+ return length, line:sub(i+1, i+length-1)
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)
+-- TODO consider removing the regexes here
+-- TODO this might not be the best approach to alpha channel.
+-- 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
+ function css_fn.rgb(line, i)
+ 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
+ return match_end - 1, rgb_hex
+ end
+ function css_fn.hsl(line, i)
+ 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
+ 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(floor(r), floor(g), floor(b))
+ if #rgb_hex ~= 6 then return end
+ return match_end - 1, rgb_hex
+ end
+ function css_fn.rgba(line, i)
+ 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
+ return match_end - 1, rgb_hex
+ end
+ function css_fn.hsla(line, i)
+ 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
+ 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(floor(r*a), floor(g*a), floor(b*a))
+ if #rgb_hex ~= 6 then return end
+ return match_end - 1, rgb_hex
+ end
+end
+local css_function_parser, rgb_function_parser, hsl_function_parser
+do
+ local CSS_FUNCTION_TRIE = Trie {'rgb', 'rgba', 'hsl', 'hsla'}
+ local RGB_FUNCTION_TRIE = Trie {'rgb', 'rgba'}
+ local HSL_FUNCTION_TRIE = Trie {'hsl', 'hsla'}
+ css_function_parser = function(line, i)
+ local prefix = CSS_FUNCTION_TRIE:longest_prefix(line:sub(i))
+ if prefix then
+ return css_fn[prefix](line, i)
+ end
+ end
+ rgb_function_parser = function(line, i)
+ local prefix = RGB_FUNCTION_TRIE:longest_prefix(line:sub(i))
+ if prefix then
+ return css_fn[prefix](line, i)
+ end
+ end
+ hsl_function_parser = function(line, i)
+ local prefix = HSL_FUNCTION_TRIE:longest_prefix(line:sub(i))
+ if prefix then
+ return css_fn[prefix](line, i)
+ end
end
end
@@ -277,28 +299,104 @@ local function compile_matcher(matchers)
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`.
+--- Default namespace used in `highlight_buffer` and `attach_to_buffer`.
+-- The name is "terminal_highlight"
+-- @see highlight_buffer
+-- @see attach_to_buffer
+local DEFAULT_NAMESPACE = nvim.create_namespace "colorizer"
+local HIGHLIGHT_NAME_PREFIX = "colorizer"
+local HIGHLIGHT_MODE_NAMES = {
+ background = "mb";
+ foreground = "mf";
+}
+local HIGHLIGHT_CACHE = {}
-@tparam integer buf buffer id.
-@tparam[opt=DEFAULT_NAMESPACE] integer ns the namespace id. Create it with `vim.api.create_namespace`
-@tparam {string,...} lines the lines to highlight from the buffer.
-@tparam integer line_start should be 0-indexed
-@param options Configuration options as described in `setup`
-@see setup
-]]
-local function highlight_buffer(buf, ns, lines, line_start, options)
- local enable_names = options.names
+--- Make a deterministic name for a highlight given these attributes
+local function make_highlight_name(rgb, mode)
+ return table.concat({HIGHLIGHT_NAME_PREFIX, HIGHLIGHT_MODE_NAMES[mode], rgb}, '_')
+end
+
+local function create_highlight(rgb_hex, options)
+ local mode = options.mode or 'background'
+ -- TODO validate rgb format?
+ rgb_hex = rgb_hex:lower()
+ local cache_key = table.concat({HIGHLIGHT_MODE_NAMES[mode], rgb_hex}, "_")
+ local highlight_name = HIGHLIGHT_CACHE[cache_key]
+ -- Look up in our cache.
+ if not highlight_name then
+ if #rgb_hex == 3 then
+ rgb_hex = table.concat {
+ rgb_hex:sub(1,1):rep(2);
+ rgb_hex:sub(2,2):rep(2);
+ rgb_hex:sub(3,3):rep(2);
+ }
+ end
+ -- Create the highlight
+ highlight_name = make_highlight_name(rgb_hex, mode)
+ if mode == 'foreground' then
+ nvim.ex.highlight(highlight_name, "guifg=#"..rgb_hex)
+ else
+ local r, g, b = rgb_hex:sub(1,2), rgb_hex:sub(3,4), rgb_hex:sub(5,6)
+ r, g, b = tonumber(r,16), tonumber(g,16), tonumber(b,16)
+ local fg_color
+ if color_is_bright(r,g,b) then
+ fg_color = "Black"
+ else
+ fg_color = "White"
+ end
+ nvim.ex.highlight(highlight_name, "guifg="..fg_color, "guibg=#"..rgb_hex)
+ end
+ HIGHLIGHT_CACHE[cache_key] = highlight_name
+ end
+ return highlight_name
+end
+
+local MATCHER_CACHE = {}
+local function make_matcher(options)
+ local enable_names = options.css or 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 matcher_key = bor(
+ lshift(enable_names and 1 or 0, 0),
+ lshift(enable_RGB and 1 or 0, 1),
+ lshift(enable_RRGGBB and 1 or 0, 2),
+ lshift(enable_RRGGBBAA and 1 or 0, 3),
+ lshift(enable_rgb and 1 or 0, 4),
+ lshift(enable_hsl and 1 or 0, 5))
+
+ if matcher_key == 0 then return end
+
+ local loop_parse_fn = MATCHER_CACHE[matcher_key]
+ if loop_parse_fn then
+ return loop_parse_fn
+ end
+
local loop_matchers = {}
- if enable_names then table.insert(loop_matchers, name_parser) end
+ if enable_names then
+ table.insert(loop_matchers, 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)
+ end
+ end
+ if minlen then
+ table.insert(loop_matchers, function(line, i)
+ local length, rgb_hex = rgb_hex_parser(line, i, minlen, maxlen)
+ if length and valid_lengths[length-1] then
+ return length, rgb_hex
+ end
+ end)
+ end
+ end
if enable_rgb and enable_hsl then
table.insert(loop_matchers, css_function_parser)
elseif enable_rgb then
@@ -306,57 +404,52 @@ local function highlight_buffer(buf, ns, lines, line_start, options)
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
+ loop_parse_fn = compile_matcher(loop_matchers)
+ MATCHER_CACHE[matcher_key] = loop_parse_fn
+ return loop_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`.
+
+@tparam integer buf buffer id.
+@tparam[opt=DEFAULT_NAMESPACE] integer ns the namespace id. Create it with `vim.api.create_namespace`
+@tparam {string,...} lines the lines to highlight from the buffer.
+@tparam integer line_start should be 0-indexed
+@param options Configuration options as described in `setup`
+@see setup
+]]
+local function highlight_buffer(buf, ns, lines, line_start, options)
-- TODO do I have to put this here?
initialize_trie()
ns = ns or DEFAULT_NAMESPACE
+ local loop_parse_fn = make_matcher(options)
for current_linenum, line in ipairs(lines) do
current_linenum = current_linenum - 1 + line_start
-- Upvalues are options and current_linenum
- local function highlight_line_rgb_hex(match_start, rgb_hex, match_end)
- 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
- -- 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)
- -- Pattern for #RGB, part 2. Ending code.
- line:gsub("()#([%da-fA-F][%da-fA-F][%da-fA-F])()$", highlight_line_rgb_hex)
- end
- if enable_RRGGBB then
- -- 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_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
- 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
+ local i = 1
+ while i < #line do
+ local length, rgb_hex = loop_parse_fn(line, i)
+ if length then
+ local highlight_name = create_highlight(rgb_hex, options)
+ nvim_buf_add_highlight(buf, ns, highlight_name, current_linenum, i-1, i+length-1)
+ i = i + length
+ else
+ i = i + 1
end
end
end
end
+---
+-- USER FACING FUNCTIONALITY
+---
+
+local SETUP_SETTINGS = {
+ exclusions = {};
+ default_options = DEFAULT_OPTIONS;
+}
local BUFFER_OPTIONS = {}
local FILETYPE_OPTIONS = {}
@@ -478,7 +571,7 @@ local function setup(filetypes, default_options)
nvim.err_writeln("colorizer: Invalid option type for filetype "..filetype)
else
options = merge(SETUP_SETTINGS.default_options, v)
- assert(MODE_NAMES[options.mode or 'background'], "colorizer: Invalid mode: "..tostring(options.mode))
+ assert(HIGHLIGHT_MODE_NAMES[options.mode or 'background'], "colorizer: Invalid mode: "..tostring(options.mode))
end
else
filetype = v
diff --git a/lua/trie.lua b/lua/trie.lua
index e74d9eb..9d84487 100644
--- a/lua/trie.lua
+++ b/lua/trie.lua
@@ -14,6 +14,7 @@
-- You should have received a copy of the GNU General Public License
-- along with this program. If not, see <http://www.gnu.org/licenses/>.
local ffi = require 'ffi'
+local bit = require 'bit'
local bnot = bit.bnot
local band, bor, bxor = bit.band, bit.bor, bit.bxor
@@ -76,32 +77,30 @@ local function verify_byte_to_index()
end
end
-local function new_trie()
+local function trie_create()
local ptr = ffi.C.malloc(Trie_size)
ffi.fill(ptr, Trie_size)
return ffi.cast(Trie_ptr_t, ptr)
end
-local INDEX_LOOKUP_TABLE = ffi.new('uint8_t[256]')
-local b_a = string.byte('a')
-local b_z = string.byte('z')
-local b_A = string.byte('A')
-local b_Z = string.byte('Z')
-local b_0 = string.byte('0')
-local b_9 = string.byte('9')
-for i = 0, 255 do
- if i >= b_0 and i <= b_9 then
- INDEX_LOOKUP_TABLE[i] = i - b_0
- elseif i >= b_A and i <= b_Z then
- INDEX_LOOKUP_TABLE[i] = i - b_A + 10
- elseif i >= b_a and i <= b_z then
- INDEX_LOOKUP_TABLE[i] = i - b_a + 10 + 26
- else
- INDEX_LOOKUP_TABLE[i] = 255
+local INDEX_LOOKUP_TABLE = ffi.new 'uint8_t[256]'
+local CHAR_LOOKUP_TABLE = ffi.new('char[62]', '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')
+do
+ local b = string.byte
+ for i = 0, 255 do
+ if i >= b'0' and i <= b'9' then
+ INDEX_LOOKUP_TABLE[i] = i - b'0'
+ elseif i >= b'A' and i <= b'Z' then
+ INDEX_LOOKUP_TABLE[i] = i - b'A' + 10
+ elseif i >= b'a' and i <= b'z' then
+ INDEX_LOOKUP_TABLE[i] = i - b'a' + 10 + 26
+ else
+ INDEX_LOOKUP_TABLE[i] = 255
+ end
end
end
-local function insert(trie, value)
+local function trie_insert(trie, value)
if trie == nil then return false end
local node = trie
for i = 1, #value do
@@ -110,7 +109,7 @@ local function insert(trie, value)
return false
end
if node.character[index] == nil then
- node.character[index] = new_trie()
+ node.character[index] = trie_create()
end
node = node.character[index]
end
@@ -118,7 +117,7 @@ local function insert(trie, value)
return node, trie
end
-local function search(trie, value)
+local function trie_search(trie, value)
if trie == nil then return false end
local node = trie
for i = 1, #value do
@@ -135,7 +134,7 @@ local function search(trie, value)
return node.is_leaf
end
-local function longest_prefix(trie, value)
+local function trie_longest_prefix(trie, value)
if trie == nil then return false end
local node = trie
local last_i = nil
@@ -158,19 +157,21 @@ local function longest_prefix(trie, value)
end
end
+local function trie_extend(trie, t)
+ assert(type(t) == 'table')
+ for _, v in ipairs(t) do
+ trie_insert(trie, v)
+ end
+end
+
--- Printing utilities
local function index_to_char(index)
- if index < 10 then
- return string.char(index + b_0)
- elseif index < 36 then
- return string.char(index - 10 + b_A)
- else
- return string.char(index - 26 - 10 + b_a)
- end
+ if index < 0 or index > 61 then return end
+ return CHAR_LOOKUP_TABLE[index]
end
-local function trie_structure(trie)
+local function trie_as_table(trie)
if trie == nil then
return nil
end
@@ -178,7 +179,7 @@ local function trie_structure(trie)
for i = 0, 61 do
local child = trie.character[i]
if child ~= nil then
- local child_table = trie_structure(child)
+ local child_table = trie_as_table(child)
child_table.c = index_to_char(i)
table.insert(children, child_table)
end
@@ -189,10 +190,10 @@ local function trie_structure(trie)
}
end
-local function print_structure(s)
+local function print_trie_table(s)
local mark
if not s then
- return nil
+ return {'nil'}
end
if s.c then
if s.is_leaf then
@@ -208,7 +209,7 @@ local function print_structure(s)
end
local lines = {}
for _, child in ipairs(s.children) do
- local child_lines = print_structure(child)
+ local child_lines = print_trie_table(child)
for _, child_line in ipairs(child_lines) do
table.insert(lines, child_line)
end
@@ -235,35 +236,40 @@ local function print_structure(s)
return lines
end
-local function free_trie(trie)
+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
- free_trie(child)
+ trie_destroy(child)
end
end
ffi.C.free(trie)
end
local Trie_mt = {
- __new = new_trie;
+ __new = function(_, init)
+ local trie = trie_create()
+ if type(init) == 'table' then
+ trie_extend(trie, init)
+ end
+ return trie
+ end;
__index = {
- insert = insert;
- search = search;
- longest_prefix = longest_prefix;
+ insert = trie_insert;
+ search = trie_search;
+ longest_prefix = trie_longest_prefix;
+ extend = trie_extend;
};
__tostring = function(trie)
- local structure = trie_structure(trie)
- if structure then
- return table.concat(print_structure(structure), '\n')
- else
+ if trie == nil then
return 'nil'
end
+ return table.concat(print_trie_table(trie_as_table(trie)), '\n')
end;
- __gc = free_trie;
+ __gc = trie_destroy;
}
return ffi.metatype('struct Trie', Trie_mt)
@@ -305,4 +311,4 @@ return ffi.metatype('struct Trie', Trie_mt)
-- end
-- print(os.clock() - start)
--- print(table.concat(print_structure(trie_structure(trie)), '\n'))
+-- print(table.concat(print_trie_table(trie_as_table(trie)), '\n'))
diff --git a/test/expectation.txt b/test/expectation.txt
new file mode 100644
index 0000000..915d4de
--- /dev/null
+++ b/test/expectation.txt
@@ -0,0 +1,45 @@
+-- vim:ft=lua
+require'colorizer'.attach_to_buffer(0, {css=true})
+
+--[[ SUCCESS
+#F0F
+#FF00FF
+#FFF00F8F
+ #F0F
+ #FF00FF
+ #FFF00F8F
+ #F0F 1
+ #FF00FF 1
+ #FFF00F8F 1
+Blue Gray LightBlue Gray100 White
+White
+#def
+#deadbeef
+rgba(0,0,0,0)
+rgb(0,0,0)
+rgb(10, 100 , 100)
+hsl(300,50%,50%)
+hsla(300,50%,50%,0.5)
+hsla(300,50%,50%,1.0000000000000001)
+hsla(360,50%,50%,1.0000000000000001)
+]]
+
+--[[ FAIL
+#F0FF
+#F0FFF
+#F0FFF0F
+#F0FFF0FFF
+Blueberry Gray1000 BlueGree BlueGray
+#define
+#def0
+matcher#add
+rgb(10,256,100)
+rgb (10,255,100)
+rgb(10, 1 00 , 100)
+hsla(300,50%,50%,05)
+hsla(300,50%,50%,1.000000000000001)
+hsla(300,50%,50,1.0000000000000001)
+hsla(300,50,50,1.0000000000000001)
+hsla(361,50,50,1.0000000000000001)
+]]
+