Improve algorithm performance and correctness. (#15)

- Disable highlighting for HEX codes and NAME codes when
preceded by alphanumeric codes.
- Hand written parser with 1 pass parsing.
- Update Trie to be more ergonomic.
- Add test file with expected outputs for edge cases.
- Cache matcher creation and refactor.
- Refactor

Faster than ever baby
This commit is contained in:
Ashkan Kiani 2019-10-18 21:51:56 -07:00 committed by GitHub
parent f44f651adf
commit 83ddc656b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 431 additions and 287 deletions

View file

@ -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,24 +53,274 @@ local DEFAULT_OPTIONS = {
mode = 'background'; -- Set the display mode.
}
local HIGHLIGHT_NAME_PREFIX = "colorizer"
local MODE_NAMES = {
background = 'mb';
foreground = 'mf';
}
-- 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
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
end
BYTE_CATEGORY[i] = v
end
end
local function byte_is_hex(byte)
return band(BYTE_CATEGORY[byte], CATEGORY_HEX) ~= 0
end
local function byte_is_alphanumeric(byte)
local category = BYTE_CATEGORY[byte]
return band(category, CATEGORY_ALPHANUM) ~= 0
end
local function parse_hex(b)
return rshift(BYTE_CATEGORY[b], 4)
end
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
--- 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
-- 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
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
-- 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 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
-- 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
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
--- 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 = {}
--- 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}, '_')
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({MODE_NAMES[mode], rgb_hex}, "_")
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
@ -120,161 +351,62 @@ local function create_highlight(rgb_hex, options)
return highlight_name
end
local SETUP_SETTINGS = {
exclusions = {};
default_options = DEFAULT_OPTIONS;
}
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 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 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))
local css_fn = {}
if matcher_key == 0 then return 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 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
local loop_parse_fn = MATCHER_CACHE[matcher_key]
if loop_parse_fn then
return loop_parse_fn
end
local q
if l < 0.5 then
q = l * (1 + s)
else
q = l + s - l * s
local loop_matchers = {}
if enable_names then
table.insert(loop_matchers, name_parser)
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)
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
return parse_fn
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
loop_parse_fn = compile_matcher(loop_matchers)
MATCHER_CACHE[matcher_key] = loop_parse_fn
return loop_parse_fn
end
--[[-- Highlight the buffer region.
@ -289,74 +421,35 @@ 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_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
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

View file

@ -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
--- 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)
local function trie_extend(trie, t)
assert(type(t) == 'table')
for _, v in ipairs(t) do
trie_insert(trie, v)
end
end
local function trie_structure(trie)
--- Printing utilities
local function index_to_char(index)
if index < 0 or index > 61 then return end
return CHAR_LOOKUP_TABLE[index]
end
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'))

45
test/expectation.txt Normal file
View file

@ -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)
]]