aboutsummaryrefslogtreecommitdiff
path: root/lua
diff options
context:
space:
mode:
Diffstat (limited to 'lua')
-rw-r--r--lua/colorizer.lua42
-rw-r--r--lua/colorizer/buffer_utils.lua49
-rw-r--r--lua/colorizer/color_utils.lua306
-rw-r--r--lua/colorizer/matcher_utils.lua18
-rw-r--r--lua/colorizer/utils.lua66
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,
}