diff --git a/lua/colorizer.lua b/lua/colorizer.lua index 657afe7..14d9379 100644 --- a/lua/colorizer.lua +++ b/lua/colorizer.lua @@ -85,21 +85,22 @@ local CATEGORY_HEX = lshift(1, 2); local CATEGORY_ALPHANUM = bor(CATEGORY_ALPHA, CATEGORY_DIGIT) do local b = string.byte + local b_0, b_9, b_a, b_z, b_f = b'0', b'9', b'a', b'z', b'f' for i = 0, 255 do local v = 0 -- Digit is bit 1 - if i >= b'0' and i <= b'9' then + 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)) + 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 + if lowercase >= b_a and lowercase <= b_z then v = bor(v, lshift(1, 1)) - if lowercase <= b'f' then + if lowercase <= b_f then v = bor(v, lshift(1, 2)) - v = bor(v, lshift(lowercase - b'a'+10, 4)) + v = bor(v, lshift(lowercase - b_a+10, 4)) end end BYTE_CATEGORY[i] = v @@ -119,8 +120,9 @@ local function parse_hex(b) return rshift(BYTE_CATEGORY[b], 4) end +local b_percent = string.byte("%") local function percent_or_hex(v) - if v:sub(-1,-1) == "%" then + if v:byte(-1) == b_percent then return tonumber(v:sub(1,-2))/100*255 end local x = tonumber(v) @@ -185,7 +187,7 @@ local function color_name_parser(line, i) end end -local b_hash = ("#"):byte() +local b_hash = string.byte("#") local function rgb_hex_parser(line, i, minlen, maxlen) if i > 1 and byte_is_alphanumeric(line:byte(i-1)) then return @@ -286,19 +288,19 @@ do 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)) + local prefix = CSS_FUNCTION_TRIE:longest_prefix(line, 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)) + local prefix = RGB_FUNCTION_TRIE:longest_prefix(line, 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)) + local prefix = HSL_FUNCTION_TRIE:longest_prefix(line, i) if prefix then return css_fn[prefix](line, i) end @@ -445,6 +447,9 @@ local function highlight_buffer(buf, ns, lines, line_start, options) initialize_trie() ns = ns or DEFAULT_NAMESPACE local loop_parse_fn = make_matcher(options) + if options.custom_matcher then + loop_parse_fn = compile_matcher {loop_parse_fn, options.custom_matcher} + end for current_linenum, line in ipairs(lines) do current_linenum = current_linenum - 1 + line_start -- Upvalues are options and current_linenum @@ -505,7 +510,7 @@ local function attach_to_buffer(buf, options) BUFFER_OPTIONS[buf] = options rehighlight_buffer(buf, options) if already_attached then - return + return false end -- send_buffer: true doesn't actually do anything in Lua (yet) nvim.buf_attach(buf, false, { @@ -522,6 +527,7 @@ local function attach_to_buffer(buf, options) BUFFER_OPTIONS[buf] = nil end; }) + return true end --- Stop highlighting the current buffer. @@ -620,7 +626,18 @@ local function get_buffer_options(buf) if buf == 0 or buf == nil then buf = nvim_get_current_buf() end - return merge({}, BUFFER_OPTIONS[buf]) + if BUFFER_OPTIONS[buf] then + return merge({}, BUFFER_OPTIONS[buf]) + end +end + +--- Return the currently active buffer options. +-- @tparam[opt=0|nil] integer buf A value of 0 or nil implies the current buffer. +local function is_buffer_attached(buf) + if buf == 0 or buf == nil then + buf = nvim_get_current_buf() + end + return BUFFER_OPTIONS[buf] ~= nil end --- @export @@ -632,5 +649,15 @@ return { highlight_buffer = highlight_buffer; reload_all_buffers = reload_all_buffers; get_buffer_options = get_buffer_options; + is_buffer_attached = is_buffer_attached; + parsers = { + compile = compile_matcher; + color_name_parser = color_name_parser; + css_fn = css_fn; + css_function_parser = css_function_parser; + hsl_function_parser = hsl_function_parser; + rgb_function_parser = rgb_function_parser; + rgb_hex_parser = rgb_hex_parser; + } } diff --git a/lua/colorizer/sass.lua b/lua/colorizer/sass.lua new file mode 100644 index 0000000..4d033ec --- /dev/null +++ b/lua/colorizer/sass.lua @@ -0,0 +1,123 @@ +local colorizer = require 'colorizer' +local nvim = require 'colorizer/nvim' + +local nvim_buf_get_lines = vim.api.nvim_buf_get_lines +local nvim_get_current_buf = vim.api.nvim_get_current_buf + + +local M = {} + +local LINE_DEFINITIONS = {} +local VARIABLE_DEFINITIONS = {} +local RECURSIVE_VARIABLES = {} + +local function variable_matcher(line, i) + local variable_name = line:sub(i):match("^%$([%w_]+)") + if variable_name then + local rgb_hex = VARIABLE_DEFINITIONS[variable_name] + if rgb_hex then + return #variable_name + 1, rgb_hex + end + end +end + +local VALUE_PARSER = colorizer.parsers.compile { + function(line,i) return colorizer.parsers.rgb_hex_parser(line,i,3,8) end; + colorizer.parsers.color_name_parser; + colorizer.parsers.css_function_parser; +} + +local function update_from_lines(buf, buffer_variable_definitions, line_start, line_end) + local variable_definitions_changed = false + local lines = nvim_buf_get_lines(buf, line_start, line_end, false) + for linenum = line_start, line_start + #lines - 1 do + local existing_variable_name = buffer_variable_definitions[linenum] + -- Invalidate any existing definitions for the lines we are processing. + if existing_variable_name then + VARIABLE_DEFINITIONS[existing_variable_name] = nil + RECURSIVE_VARIABLES[existing_variable_name] = nil + variable_definitions_changed = true + buffer_variable_definitions[linenum] = nil + end + end + for i, line in ipairs(lines) do + local linenum = i + line_start - 1 + local variable_name, variable_value = line:match("^%s*%$([%w_]+)%s*:%s*(%S+)%s*$") + -- Check if we got a variable definition + if variable_name then + -- Check for a recursive variable definition. + local target_variable_name = variable_value:match("%$([%w_]+)") + if target_variable_name then + -- Update the recursive variable definition + RECURSIVE_VARIABLES[variable_name] = target_variable_name + -- Update the value. + VARIABLE_DEFINITIONS[variable_name] = VARIABLE_DEFINITIONS[target_variable_name] + variable_definitions_changed = true + else + -- If it's not recursive, then just update the value. + local length, rgb_hex = VALUE_PARSER(variable_value, 1) + if length then + variable_definitions_changed = true + buffer_variable_definitions[linenum] = variable_name + VARIABLE_DEFINITIONS[variable_name] = rgb_hex + end + end + -- Propagate changes to recursive dependents. + -- TODO this isn't recursive, obviously. Only works for 1 depth. + for varn, varv in pairs(RECURSIVE_VARIABLES) do + if varv == variable_name then + VARIABLE_DEFINITIONS[varn] = VARIABLE_DEFINITIONS[varv] + end + end + end + end + return variable_definitions_changed +end + +local function rehighlight_attached_buffers() + -- Rehighlight all buffers + for bufnr in pairs(LINE_DEFINITIONS) do + colorizer.attach_to_buffer(bufnr) + end +end + + +--- Attach to a buffer and continuously highlight changes. +-- @tparam[opt=0|nil] integer buf A value of 0 implies the current buffer. +-- @param[opt] options Configuration options as described in `setup` +-- @see setup +function M.attach_to_buffer(buf) +--function M.attach_to_buffer(buf, options) + if buf == 0 or buf == nil then + buf = nvim_get_current_buf() + end + + LINE_DEFINITIONS[buf] = {} + local buffer_variable_definitions = LINE_DEFINITIONS[buf] + + -- Parse the whole buffer to start. + update_from_lines(buf, buffer_variable_definitions, 0, -1) + rehighlight_attached_buffers() + + -- send_buffer: true doesn't actually do anything in Lua (yet) + nvim.buf_attach(buf, false, { + on_lines = function(event_type, buf, changed_tick, firstline, lastline, new_lastline) + -- This is used to signal stopping the handler highlights + if not colorizer.is_buffer_attached(buf) then + return true + end + local variable_definitions_changed = update_from_lines(buf, buffer_variable_definitions, firstline, new_lastline) + -- If the variable_definitions_changed then rehighlight all watched buffers. + if variable_definitions_changed then + rehighlight_attached_buffers() + end + end; + on_detach = function() + LINE_DEFINITIONS[buf] = nil + end; + }) +end + +M.variable_matcher = variable_matcher + +return M