diff options
Diffstat (limited to 'lua')
-rw-r--r-- | lua/colorizer.lua | 42 | ||||
-rw-r--r-- | lua/colorizer/buffer_utils.lua | 49 | ||||
-rw-r--r-- | lua/colorizer/color_utils.lua | 306 | ||||
-rw-r--r-- | lua/colorizer/matcher_utils.lua | 18 | ||||
-rw-r--r-- | lua/colorizer/utils.lua | 66 |
5 files changed, 454 insertions, 27 deletions
diff --git a/lua/colorizer.lua b/lua/colorizer.lua index 4af765c..90b964a 100644 --- a/lua/colorizer.lua +++ b/lua/colorizer.lua @@ -108,7 +108,9 @@ local BUFFER_LOCAL = {} -- mode = "background", -- Set the display mode. -- -- Available methods are false / true / "normal" / "lsp" / "both" -- -- True is same as normal --- tailwind = false -- Enable tailwind colors +-- tailwind = false, -- Enable tailwind colors +-- -- parsers can contain values used in |user_default_options| +-- sass = { enable = false, parsers = { css }, }, -- Enable sass colors -- virtualtext = "■", -- } --</pre> @@ -124,6 +126,7 @@ local BUFFER_LOCAL = {} --@field css_fn boolean --@field mode string --@field tailwind boolean|string +--@field sass table --@field virtualtext string local USER_DEFAULT_OPTIONS = { RGB = true, @@ -137,6 +140,7 @@ local USER_DEFAULT_OPTIONS = { css_fn = false, mode = "background", tailwind = false, + sass = { enable = false, parsers = { css = true } }, virtualtext = "■", } @@ -198,10 +202,13 @@ local function detach_from_buffer(buf, ns) clear_namespace(buf, ns or DEFAULT_NAMESPACE, 0, -1) if BUFFER_LOCAL[buf] then - if BUFFER_LOCAL[buf].__tailwind_ns then - clear_namespace(buf, BUFFER_LOCAL[buf].__tailwind_ns, 0, -1) - if type(BUFFER_LOCAL[buf].__tailwind_detach) == "function" then - BUFFER_LOCAL[buf].__tailwind_detach() + for _, namespace in pairs(BUFFER_LOCAL[buf].__detach.ns) do + clear_namespace(buf, namespace, 0, -1) + end + + for _, f in pairs(BUFFER_LOCAL[buf].__detach.functions) do + if type(f) == "function" then + f(buf) end end @@ -210,6 +217,7 @@ local function detach_from_buffer(buf, ns) end BUFFER_LOCAL[buf].__autocmds = nil + BUFFER_LOCAL[buf].__detach = nil end -- because now the buffer is not visible, so delete its information BUFFER_OPTIONS[buf] = nil @@ -253,9 +261,13 @@ local function attach_to_buffer(buf, options, typ) BUFFER_OPTIONS[buf] = options BUFFER_LOCAL[buf] = BUFFER_LOCAL[buf] or {} - local tailwind_ns, tailwind_detach = rehighlight_buffer(buf, options) - BUFFER_LOCAL[buf].__tailwind_ns = BUFFER_LOCAL[buf].__tailwind_ns or tailwind_ns - BUFFER_LOCAL[buf].__tailwind_detach = BUFFER_LOCAL[buf].__tailwind_detach or tailwind_detach + local highlighted, returns = rehighlight_buffer(buf, options) + + if not highlighted then + return + end + + BUFFER_LOCAL[buf].__detach = BUFFER_LOCAL[buf].__detach or returns.detach BUFFER_LOCAL[buf].__init = true @@ -266,13 +278,20 @@ local function attach_to_buffer(buf, options, typ) local autocmds = {} local au_group_id = AUGROUP_ID - autocmds[#autocmds + 1] = autocmd({ "TextChanged", "TextChangedI", "TextChangedP" }, { + local text_changed_au = { "TextChanged", "TextChangedI", "TextChangedP" } + -- only enable InsertLeave in sass, rest don't require it + if options.sass and options.sass.enable then + table.insert(text_changed_au, "InsertLeave") + end + + autocmds[#autocmds + 1] = autocmd(text_changed_au, { group = au_group_id, buffer = buf, callback = function(args) -- only reload if it was not disabled using detach_from_buffer if BUFFER_OPTIONS[buf] then - if args.event == "TextChanged" then + BUFFER_LOCAL[buf].__event = args.event + if args.event == "TextChanged" or args.event == "InsertLeave" then rehighlight_buffer(buf, options, BUFFER_LOCAL[buf]) else local pos = vim.fn.getpos "." @@ -287,9 +306,10 @@ local function attach_to_buffer(buf, options, typ) autocmds[#autocmds + 1] = autocmd({ "WinScrolled" }, { group = au_group_id, buffer = buf, - callback = function() + callback = function(args) -- only reload if it was not disabled using detach_from_buffer if BUFFER_OPTIONS[buf] then + BUFFER_LOCAL[buf].__event = args.event rehighlight_buffer(buf, options, BUFFER_LOCAL[buf]) end end, diff --git a/lua/colorizer/buffer_utils.lua b/lua/colorizer/buffer_utils.lua index a567d19..7801d0c 100644 --- a/lua/colorizer/buffer_utils.lua +++ b/lua/colorizer/buffer_utils.lua @@ -9,9 +9,10 @@ local set_highlight = api.nvim_set_hl local color_utils = require "colorizer.color_utils" local color_is_bright = color_utils.color_is_bright +local sass_update_variables = color_utils.sass_update_variables +local sass_cleanup = color_utils.sass_cleanup -local matcher_utils = require "colorizer.matcher_utils" -local make_matcher = matcher_utils.make_matcher +local make_matcher = require("colorizer.matcher_utils").make_matcher local highlight_buffer, rehighlight_buffer local BUFFER_LINES = {} @@ -159,8 +160,9 @@ local TW_LSP_CLIENT = {} ---@param line_end number: Last line to highlight ---@param options table: Configuration options as described in `setup` ---@param options_local table: Buffer local variables ----@return nil|boolean|number,function|nil +---@return nil|boolean|number,table function highlight_buffer(buf, ns, lines, line_start, line_end, options, options_local) + local returns = { detach = { ns = {}, functions = {} } } if buf == 0 or buf == nil then buf = api.nvim_get_current_buf() end @@ -168,7 +170,13 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options ns = ns or DEFAULT_NAMESPACE local loop_parse_fn = make_matcher(options) if not loop_parse_fn then - return false + return false, returns + end + + -- only update sass varibled when text is changed + if options_local.__event ~= "WinScrolled" and options.sass and options.sass.enable then + table.insert(returns.detach.functions, sass_cleanup) + sass_update_variables(buf, 0, -1, nil, make_matcher(options.sass.parsers or { css = true }), options, options_local) end local data = {} @@ -178,7 +186,7 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options -- Upvalues are options and current_linenum local i = 1 while i < #line do - local length, rgb_hex = loop_parse_fn(line, i) + local length, rgb_hex = loop_parse_fn(line, i, buf) if length and rgb_hex then local name = create_highlight(rgb_hex, mode) local d = data[current_linenum] or {} @@ -193,7 +201,7 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options add_highlight(options, buf, ns, data, line_start, line_end) if not options.tailwind or (options.tailwind ~= "lsp" and options.tailwind ~= "both") then - return + return true, returns end if not TW_LSP_CLIENT[buf] or TW_LSP_CLIENT[buf].is_stopped() then @@ -246,11 +254,15 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options end) tailwind_client = ok and tailwind_client or {} if vim.tbl_isempty(tailwind_client) then - return DEFAULT_NAMESPACE_TAILWIND, del_tailwind_stuff + table.insert(returns.detach.functions, del_tailwind_stuff) + table.insert(returns.detach.ns, DEFAULT_NAMESPACE_TAILWIND) + return true, returns end if not tailwind_client[1] or not tailwind_client[1].supports_method "textDocument/documentColor" then - return DEFAULT_NAMESPACE_TAILWIND, del_tailwind_stuff + table.insert(returns.detach.functions, del_tailwind_stuff) + table.insert(returns.detach.ns, DEFAULT_NAMESPACE_TAILWIND) + return true, returns end TW_LSP_CLIENT[buf] = tailwind_client[1] @@ -260,13 +272,18 @@ function highlight_buffer(buf, ns, lines, line_start, line_end, options, options highlight_buffer_tailwind(buf, DEFAULT_NAMESPACE_TAILWIND, mode, options) end, 100) - return DEFAULT_NAMESPACE_TAILWIND, del_tailwind_stuff + table.insert(returns.detach.functions, del_tailwind_stuff) + table.insert(returns.detach.ns, DEFAULT_NAMESPACE_TAILWIND) + + return true, returns end -- only try to do tailwindcss highlight if lsp is attached if TW_LSP_CLIENT[buf] then highlight_buffer_tailwind(buf, DEFAULT_NAMESPACE_TAILWIND, mode, options) end + + return true, returns end -- get the amount lines to highlight @@ -288,7 +305,7 @@ local function getrow(buf) if old_min and old_max then -- Triggered for TextChanged autocmds -- TODO: Find a way to just apply highlight to changed text lines - if old_max == new_max then + if (old_max == new_max) or (old_min == new_min) then min, max = new_min, new_max -- Triggered for WinScrolled autocmd - Scroll Down elseif old_max < new_max then @@ -318,7 +335,7 @@ end ---@param options table: Buffer options ---@param options_local table|nil: Buffer local variables ---@param use_local_lines boolean|nil Whether to use lines num range from options_local ----@return nil|boolean|number,function|nil +---@return nil|boolean|number,table function rehighlight_buffer(buf, options, options_local, use_local_lines) if buf == 0 or buf == nil then buf = api.nvim_get_current_buf() @@ -328,13 +345,17 @@ function rehighlight_buffer(buf, options, options_local, use_local_lines) local min, max if use_local_lines and options_local then - min, max = options_local.__startline, options_local.__endline + min, max = options_local.__startline or 0, options_local.__endline or -1 else min, max = getrow(buf) end - local lines = buf_get_lines(buf, min, max, false) - return highlight_buffer(buf, ns, lines, min, max, options, options_local or {}) + + local bool, returns = highlight_buffer(buf, ns, lines, min, max, options, options_local or {}) + table.insert(returns.detach.functions, function() + BUFFER_LINES[buf] = nil + end) + return bool, returns end --- @export diff --git a/lua/colorizer/color_utils.lua b/lua/colorizer/color_utils.lua index 34a04f5..adb3f19 100644 --- a/lua/colorizer/color_utils.lua +++ b/lua/colorizer/color_utils.lua @@ -7,6 +7,10 @@ local byte_is_alphanumeric = utils.byte_is_alphanumeric local byte_is_hex = utils.byte_is_hex local parse_hex = utils.parse_hex local percent_or_hex = utils.percent_or_hex +local get_last_modified = utils.get_last_modified +local watch_file = utils.watch_file + +local uv = vim.loop local bit = require "bit" local floor, min, max = math.floor, math.min, math.max @@ -102,6 +106,7 @@ local function color_name_parser(line, i, opts) end TAILWIND_ENABLED = opts.tailwind end + if #line < i + COLOR_NAME_MINLEN - 1 then return end @@ -123,6 +128,304 @@ local function color_name_parser(line, i, opts) end end +local SASS = {} +--- Cleanup sass variables +---@param buf number +local function sass_cleanup(buf) + SASS[buf] = nil +end + +local dollar_hash = ("$"):byte() +local at_hash = ("@"):byte() +local colon_hash = (";"):byte() + +-- Helper function for sass_update_variables +local function sass_parse_lines(buf, line_start, content, name) + SASS[buf].DEFINITIONS_ALL = SASS[buf].DEFINITIONS_ALL or {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT = SASS[buf].DEFINITIONS_RECURSIVE_CURRENT or {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE = SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE or {} + + SASS[buf].DEFINITIONS_LINEWISE[name] = SASS[buf].DEFINITIONS_LINEWISE[name] or {} + SASS[buf].DEFINITIONS[name] = SASS[buf].DEFINITIONS[name] or {} + SASS[buf].IMPORTS[name] = SASS[buf].IMPORTS[name] or {} + SASS[buf].WATCH_IMPORTS[name] = SASS[buf].WATCH_IMPORTS[name] or {} + SASS[buf].CURRENT_IMPORTS[name] = {} + + local import_find_colon = false + for i, line in ipairs(content) do + local linenum = i - 1 + line_start + -- Invalidate any existing definitions for the lines we are processing. + if not vim.tbl_isempty(SASS[buf].DEFINITIONS_LINEWISE[name][linenum] or {}) then + for v, _ in pairs(SASS[buf].DEFINITIONS_LINEWISE[name][linenum]) do + SASS[buf].DEFINITIONS[name][v] = nil + end + SASS[buf].DEFINITIONS_LINEWISE[name][linenum] = {} + else + SASS[buf].DEFINITIONS_LINEWISE[name][linenum] = {} + end + + local index = 1 + while index < #line do + -- ignore comments + if line:match("^//", index) then + index = #line + -- line starting with variables $var + elseif not import_find_colon and line:byte(index) == dollar_hash then + local variable_name, variable_value = line:match("^%$([%w_-]+)%s*:%s*(.+)%s*", index) + -- Check if we got a variable definition + if variable_name and variable_value then + -- Check for a recursive variable definition. + if variable_value:byte() == dollar_hash then + local target_variable_name, len = variable_value:match "^%$([%w_-]+)()" + if target_variable_name then + -- Update the value. + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[variable_name] = target_variable_name + SASS[buf].DEFINITIONS_LINEWISE[name][linenum][variable_name] = true + index = index + len + end + index = index + 1 + else + -- Check for a recursive variable definition. + -- If it's not recursive, then just update the value. + if SASS[buf].COLOR_PARSER then + local length, rgb_hex = SASS[buf].COLOR_PARSER(variable_value, 1) + if length and rgb_hex then + SASS[buf].DEFINITIONS[name][variable_name] = rgb_hex + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[variable_name] = rgb_hex + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE[variable_name] = rgb_hex + SASS[buf].DEFINITIONS_LINEWISE[name][linenum][variable_name] = true + -- added 3 because the color parsers returns 3 less + -- todo: need to fix + index = index + length + 3 + end + end + end + index = index + #variable_name + end + -- color ( ; ) found + elseif import_find_colon and line:byte(index) == colon_hash then + import_find_colon, index = false, index + 1 + -- imports @import 'somefile' + elseif line:byte(index) == at_hash or import_find_colon then + local variable_value, colon, import_kw + if import_find_colon then + variable_value, colon = line:match("%s*(.*[^;])%s*([;]?)", index) + else + import_kw, variable_value, colon = line:match("@(%a+)%s+(.+[^;])%s*([;]?)", index) + import_kw = (import_kw == "import" or import_kw == "use") + end + + if not colon or colon == "" then + -- now loop until ; is found + import_find_colon = true + else + import_find_colon = false + end + + -- if import/use key word is found along with file name + if import_kw and variable_value then + local files = {} + -- grab files to be imported + for s, a in variable_value:gmatch "['\"](.-)()['\"]" do + local folder_path, file_name = vim.fn.fnamemodify(s, ":h"), vim.fn.fnamemodify(s, ":t") + if file_name ~= "" then + -- get the root directory of the file + local parent_dir = vim.fn.fnamemodify(name, ":h") + parent_dir = (parent_dir ~= "") and parent_dir .. "/" or "" + folder_path = vim.fn.fnamemodify(parent_dir .. folder_path, ":p") + file_name = file_name + files = { + folder_path .. file_name .. ".scss", + folder_path .. "_" .. file_name .. ".scss", + folder_path .. file_name .. ".sass", + folder_path .. "_" .. file_name .. ".sass", + } + end + -- why 2 * a ? I don't know + index = index + 2 * a + end + + -- process imported files + for _, v in ipairs(files) do + -- parse the sass files + local last_modified = get_last_modified(v) + if last_modified then + -- grab the full path + v = uv.fs_realpath(v) + SASS[buf].CURRENT_IMPORTS[name][v] = true + + if not SASS[buf].WATCH_IMPORTS[name][v] then + SASS[buf].IMPORTS[name][v] = last_modified + local c, ind = {}, 0 + for l in io.lines(v) do + ind = ind + 1 + c[ind] = l + end + sass_parse_lines(buf, 0, c, v) + c = nil + + local function watch_callback() + local dimen = vim.api.nvim_buf_call(buf, function() + return { vim.fn.line "w0", vim.fn.line "w$", vim.fn.line "$", vim.api.nvim_win_get_height(0) } + end) + -- todo: Improve this to only refresh highlight for visible lines + -- can't find out how to get visible rows from another window + -- probably a neovim bug, it is returning 1 and 1 or 1 and 5 + if + dimen[1] ~= dimen[2] + and ((dimen[3] > dimen[4] and dimen[2] > dimen[4]) or (dimen[2] >= dimen[3])) + then + SASS[buf].LOCAL_OPTIONS.__startline = dimen[1] + SASS[buf].LOCAL_OPTIONS.__endline = dimen[2] + end + SASS[buf].LOCAL_OPTIONS.__event = "" + + local lastm = get_last_modified(v) + if lastm then + SASS[buf].IMPORTS[name][v] = lastm + local cc, inde = {}, 0 + for l in io.lines(v) do + inde = inde + 1 + cc[inde] = l + end + sass_parse_lines(buf, 0, cc, v) + cc = nil + end + + require("colorizer.buffer_utils").rehighlight_buffer( + buf, + SASS[buf].OPTIONS, + SASS[buf].LOCAL_OPTIONS, + true + ) + end + SASS[buf].WATCH_IMPORTS[name][v] = watch_file(v, watch_callback) + end + else + -- if file does not exists then remove related variables + SASS[buf].IMPORTS[name][v] = nil + pcall(uv.fs_event_stop, SASS[buf].WATCH_IMPORTS[name][v]) + SASS[buf].WATCH_IMPORTS[name][v] = nil + end + end -- process imported files + end + end -- parse lines + index = index + 1 + end -- while loop end + end -- for loop end + + local function remove_unused_imports(import_name) + if type(SASS[buf].IMPORTS[import_name]) == "table" then + for file, _ in pairs(SASS[buf].IMPORTS[import_name]) do + remove_unused_imports(file) + end + end + SASS[buf].DEFINITIONS[import_name] = nil + SASS[buf].DEFINITIONS_LINEWISE[import_name] = nil + SASS[buf].IMPORTS[import_name] = nil + -- stop the watch handler + pcall(uv.fs_event_stop, SASS[buf].WATCH_IMPORTS[import_name]) + SASS[buf].WATCH_IMPORTS[import_name] = nil + end + + -- remove definitions of files which are not imported now + for file, _ in pairs(SASS[buf].IMPORTS[name]) do + if not SASS[buf].CURRENT_IMPORTS[name][file] then + remove_unused_imports(name) + end + end +end -- sass_parse_lines end + +--- Parse the given lines for sass variabled and add to SASS[buf].DEFINITIONS_ALL. +-- which is then used in |sass_name_parser| +-- If lines are not given, then fetch the lines with line_start and line_end +---@param buf number +---@param line_start number +---@param line_end number +---@param lines table|nil +---@param color_parser function|boolean +---@param options table: Buffer options +---@param options_local table|nil: Buffer local variables +local function sass_update_variables(buf, line_start, line_end, lines, color_parser, options, options_local) + lines = lines or vim.api.nvim_buf_get_lines(buf, line_start, line_end, false) + + if not SASS[buf] then + SASS[buf] = { + DEFINITIONS_ALL = {}, + DEFINITIONS = {}, + IMPORTS = {}, + WATCH_IMPORTS = {}, + CURRENT_IMPORTS = {}, + DEFINITIONS_LINEWISE = {}, + OPTIONS = options, + LOCAL_OPTIONS = options_local, + } + end + + SASS[buf].COLOR_PARSER = color_parser + SASS[buf].DEFINITIONS_ALL = {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT = {} + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE = {} + + sass_parse_lines(buf, line_start, lines, api.nvim_buf_get_name(buf)) + + -- add non-recursive def to DEFINITIONS_ALL + for _, color_table in pairs(SASS[buf].DEFINITIONS) do + for color_name, color in pairs(color_table) do + SASS[buf].DEFINITIONS_ALL[color_name] = color + end + end + + -- normally this is just a wasted step as all the values here are + -- already present in SASS[buf].DEFINITIONS + -- but when undoing a pasted text, it acts as a backup + for name, color in pairs(SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE) do + SASS[buf].DEFINITIONS_ALL[name] = color + end + + -- try to find the absolute color value for the given name + -- use tail call recursion + -- https://www.lua.org/pil/6.3.html + local function find_absolute_value(name, color_name) + return SASS[buf].DEFINITIONS_ALL[color_name] + or ( + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[color_name] + and find_absolute_value(name, SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[color_name]) + ) + end + + local function set_color_value(name, color_name) + local value = find_absolute_value(name, color_name) + if value then + SASS[buf].DEFINITIONS_ALL[name] = value + end + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT[name] = nil + end + + for name, color_name in pairs(SASS[buf].DEFINITIONS_RECURSIVE_CURRENT) do + set_color_value(name, color_name) + end + + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT = nil + SASS[buf].DEFINITIONS_RECURSIVE_CURRENT_ABSOLUTE = nil +end + +--- Parse the given line for sass color names +-- check for value in SASS[buf].DEFINITIONS_ALL +---@param line string: Line to parse +---@param i number: Index of line from where to start parsing +---@param buf number +---@return number|nil, string|nil +local function sass_name_parser(line, i, buf) + local variable_name = line:sub(i):match "^%$([%w_-]+)" + if variable_name then + local rgb_hex = SASS[buf].DEFINITIONS_ALL[variable_name] + if rgb_hex then + return #variable_name + 1, rgb_hex + end + end +end + --- Converts an HSL color value to RGB. ---@param h number: Hue ---@param s number: Saturation @@ -407,4 +710,7 @@ return { rgba_function_parser = rgba_function_parser, hsl_function_parser = hsl_function_parser, hsla_function_parser = hsla_function_parser, + sass_name_parser = sass_name_parser, + sass_cleanup = sass_cleanup, + sass_update_variables = sass_update_variables, } diff --git a/lua/colorizer/matcher_utils.lua b/lua/colorizer/matcher_utils.lua index 29cd5e9..7803eac 100644 --- a/lua/colorizer/matcher_utils.lua +++ b/lua/colorizer/matcher_utils.lua @@ -5,6 +5,7 @@ local min, max = math.min, math.max local color_utils = require "colorizer.color_utils" local color_name_parser = color_utils.color_name_parser +local sass_name_parser = color_utils.sass_name_parser local rgba_hex_parser = color_utils.rgba_hex_parser local parser = {} @@ -13,6 +14,7 @@ parser["_rgb"] = color_utils.rgb_function_parser parser["_rgba"] = color_utils.rgba_function_parser parser["_hsl"] = color_utils.hsl_function_parser parser["_hsla"] = color_utils.hsla_function_parser +local b_hash, dollar_hash = ("#"):byte(), ("$"):byte() ---Form a trie stuct with the given prefixes ---@param matchers table: List of prefixes, {"rgb", "hsl"} @@ -21,8 +23,7 @@ parser["_hsla"] = color_utils.hsla_function_parser local function compile_matcher(matchers, matchers_trie) local trie = Trie(matchers_trie) - local b_hash = ("#"):byte() - local function parse_fn(line, i) + local function parse_fn(line, i, buf) -- prefix # if matchers.rgba_hex_parser then if line:byte(i) == b_hash then @@ -30,6 +31,13 @@ local function compile_matcher(matchers, matchers_trie) end end + -- prefix $, SASS Colour names + if matchers.sass_name_parser then + if line:byte(i) == dollar_hash then + return sass_name_parser(line, i, buf) + end + end + -- Prefix 0x, rgba, rgb, hsla, hsl local prefix = trie:longest_prefix(line, i) if prefix then @@ -55,6 +63,7 @@ local MATCHER_CACHE = {} ---@return function|boolean: function which will just parse the line for enabled parsers local function make_matcher(options) local enable_names = options.css or options.names + local enable_sass = options.sass and options.sass.enable local enable_tailwind = options.tailwind local enable_RGB = options.css or options.RGB local enable_RRGGBB = options.css or options.RRGGBB @@ -74,6 +83,7 @@ local function make_matcher(options) + ((enable_tailwind == true or enable_tailwind == "normal") and 1 or 7) + (enable_tailwind == "lsp" and 1 or 8) + (enable_tailwind == "both" and 1 or 9) + + (enable_sass and 1 or 10) if matcher_key == 0 then return false @@ -92,6 +102,10 @@ local function make_matcher(options) matchers.color_name_parser = { tailwind = options.tailwind } end + if enable_sass then + matchers.sass_name_parser = true + end + local valid_lengths = { [3] = enable_RGB, [6] = enable_RRGGBB, [8] = enable_RRGGBBAA } local minlen, maxlen for k, v in pairs(valid_lengths) do diff --git a/lua/colorizer/utils.lua b/lua/colorizer/utils.lua index bfffb60..9dad9d8 100644 --- a/lua/colorizer/utils.lua +++ b/lua/colorizer/utils.lua @@ -3,6 +3,8 @@ local bit, ffi = require "bit", require "ffi" local band, bor, rshift, lshift = bit.band, bit.bor, bit.rshift, bit.lshift +local uv = vim.loop + -- -- TODO use rgb as the return value from the matcher functions -- -- instead of the rgb_hex. Can be the highlight key as well -- -- when you shift it left 8 bits. Use the lower 8 bits for @@ -101,6 +103,68 @@ local function percent_or_hex(v) return x end +--- Get last modified time of a file +---@param path string: file path +---@return number|nil: modified time +local function get_last_modified(path) + local fd = uv.fs_open(path, "r", 438) + if not fd then + return + end + + local stat = uv.fs_fstat(fd) + uv.fs_close(fd) + if stat then + return stat.mtime.nsec + else + return + end +end + +--- Watch a file for changes and execute callback +---@param path string: File path +---@param callback function: Callback to execute +---@param ... array: params for callback +---@return function|nil +local function watch_file(path, callback, ...) + if not path or type(callback) ~= "function" then + return + end + + local fullpath = uv.fs_realpath(path) + if not fullpath then + return + end + + local start + local args = { ... } + + local handle = uv.new_fs_event() + local function on_change(err, filename, _) + -- Do work... + callback(filename, unpack(args)) + -- Debounce: stop/start. + handle:stop() + if not err or not get_last_modified(filename) then + start() + end + end + + function start() + uv.fs_event_start( + handle, + fullpath, + {}, + vim.schedule_wrap(function(...) + on_change(...) + end) + ) + end + + start() + return handle +end + --- @export return { byte_is_alphanumeric = byte_is_alphanumeric, @@ -108,4 +172,6 @@ return { merge = merge, parse_hex = parse_hex, percent_or_hex = percent_or_hex, + get_last_modified = get_last_modified, + watch_file = watch_file, } |