diff --git a/Makefile b/Makefile index 0da0129..131898a 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,8 @@ SCRIPTS_DIR=scripts TRIE_TEST_SCRIPT=$(SCRIPTS_DIR)/trie-test.sh TRIE_BENCHMARK_SCRIPT=$(SCRIPTS_DIR)/trie-benchmark.sh MINIMAL_SCRIPT=$(SCRIPTS_DIR)/minimal-colorizer.sh +MINIMAL_COLORIZER=colorizer_minimal +MINIMAL_TRIE=colorizer_trie help: @echo "Available targets:" @@ -27,9 +29,9 @@ minimal: @bash $(MINIMAL_SCRIPT) clean: - @echo "Removing test/colorizer_repro" - @rm -rf test/colorizer_repro - @echo "Removing test/trie/colorizer_trie" - @rm -rf test/trie/colorizer_trie + @echo "Removing test/"$(MINIMAL_COLORIZER) + @rm -rf test/$(MINIMAL_COLORIZER) + @echo "Removing test/trie/"$(MINIMAL_TRIE) + @rm -rf test/trie/$(MINIMAL_TRIE) .PHONY: help trie trie-test trie-benchmark minimal clean diff --git a/lua/colorizer.lua b/lua/colorizer.lua index a5056af..0f3635c 100644 --- a/lua/colorizer.lua +++ b/lua/colorizer.lua @@ -405,6 +405,9 @@ function M.detach_from_buffer(bufnr) if bufnr < 0 then return -1 end + for _, ns_id in pairs(const.namespace) do + vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1) + end vim.api.nvim_buf_clear_namespace(bufnr, const.namespace.default, 0, -1) if colorizer_state.buffer_local[bufnr] then for _, namespace in pairs(colorizer_state.buffer_local[bufnr].__detach.ns_id) do diff --git a/lua/colorizer/buffer.lua b/lua/colorizer/buffer.lua index f52add2..f73c1fd 100644 --- a/lua/colorizer/buffer.lua +++ b/lua/colorizer/buffer.lua @@ -59,7 +59,6 @@ local function create_highlight(rgb_hex, mode) if mode == "foreground" then vim.api.nvim_set_hl(0, highlight_name, { fg = "#" .. rgb_hex }) else - -- TODO: 2025-01-11 - Should this check for background or virtualtext local rr, gg, bb = rgb_hex:sub(1, 2), rgb_hex:sub(3, 4), rgb_hex:sub(5, 6) local r, g, b = tonumber(rr, 16), tonumber(gg, 16), tonumber(bb, 16) local fg_color = color.is_bright(r, g, b) and "Black" or "White" @@ -77,6 +76,59 @@ local function slice_line(bufnr, line, start_col, end_col) return string.sub(lines[1], start_col + 1, end_col) end +--- Add low priority highlights. Trims highlight ranges to avoid collisions. +---@param bufnr number: Buffer number +---@param extmarks table: List of low priority extmarks to reapply +---@param priority_ns_id number: Namespace id for priority highlights +---@param linenr number: Line number +local function add_low_priority_highlights(bufnr, extmarks, priority_ns_id, linenr) + local priority_marks = vim.api.nvim_buf_get_extmarks( + bufnr, + priority_ns_id, + { linenr, 0 }, + { linenr + 1, 0 }, + { details = true } + ) + for _, default_mark in ipairs(extmarks) do + local default_start = default_mark[3] -- Start column + local default_end = default_mark[4].end_col + local hl_group = default_mark[4].hl_group + local non_overlapping_ranges = { { default_start, default_end } } + for _, lsp_mark in ipairs(priority_marks) do + local lsp_start = lsp_mark[3] + local lsp_end = lsp_mark[4].end_col + -- Adjust ranges to avoid collisions + local new_ranges = {} + for _, range in ipairs(non_overlapping_ranges) do + local start, end_ = range[1], range[2] + if lsp_start <= end_ and lsp_end >= start then + -- Collision detected, split range + if start < lsp_start then + table.insert(new_ranges, { start, lsp_start }) + end + if lsp_end < end_ then + table.insert(new_ranges, { lsp_end, end_ }) + end + else + -- No collision, keep range + table.insert(new_ranges, { start, end_ }) + end + end + non_overlapping_ranges = new_ranges + end + for _, range in ipairs(non_overlapping_ranges) do + vim.api.nvim_buf_add_highlight( + bufnr, + default_mark[4].ns_id, -- Original namespace + hl_group, + linenr, + range[1], + range[2] + ) + end + end +end + --- Create highlight and set highlights ---@param bufnr number: Buffer number (0 for current) ---@param ns_id number: Namespace id for which to create highlights @@ -93,37 +145,41 @@ function M.add_highlight(bufnr, ns_id, line_start, line_end, data, ud_opts, hl_o hl_opts = hl_opts or {} vim.api.nvim_buf_clear_namespace(bufnr, ns_id, line_start, line_end) if ud_opts.mode == "background" or ud_opts.mode == "foreground" then + local tw_both = ud_opts.tailwind == "both" and hl_opts.tailwind_lsp for linenr, hls in pairs(data) do + local marks + if tw_both then + marks = vim.api.nvim_buf_get_extmarks( + bufnr, + const.namespace.default, + { linenr, 0 }, + { linenr + 1, 0 }, + { details = true } + ) + -- clear default namespace to apply LSP highlights, then rehighlight non-overlapping default highlights + -- Fixes: https://github.com/catgoose/nvim-colorizer.lua/issues/61 + vim.api.nvim_buf_clear_namespace(bufnr, const.namespace.default, linenr, linenr + 1) + end for _, hl in ipairs(hls) do - if ud_opts.tailwind == "both" and hl_opts.tailwind_lsp then - vim.api.nvim_buf_clear_namespace( - bufnr, - const.namespace.tailwind_names, - linenr, - linenr + 1 - ) - if ud_opts.tailwind_opts.update_names then - local txt = slice_line(bufnr, linenr, hl.range[1], hl.range[2]) - if txt and not hl_state.updated_colors[txt] then - hl_state.updated_colors[txt] = true - names.update_color(txt, hl.rgb_hex) - end + if tw_both and ud_opts.tailwind_opts.update_names then + local txt = slice_line(bufnr, linenr, hl.range[1], hl.range[2]) + if txt and not hl_state.updated_colors[txt] then + hl_state.updated_colors[txt] = true + names.update_color(txt, hl.rgb_hex) end end local hlname = create_highlight(hl.rgb_hex, ud_opts.mode) vim.api.nvim_buf_add_highlight(bufnr, ns_id, hlname, linenr, hl.range[1], hl.range[2]) end + if tw_both then + add_low_priority_highlights(bufnr, marks, ns_id, linenr) + end end elseif ud_opts.mode == "virtualtext" then for linenr, hls in pairs(data) do for _, hl in ipairs(hls) do if ud_opts.tailwind == "both" and hl_opts.tailwind_lsp then - vim.api.nvim_buf_clear_namespace( - bufnr, - const.namespace.tailwind_names, - linenr, - linenr + 1 - ) + vim.api.nvim_buf_clear_namespace(bufnr, ns_id, linenr, linenr + 1) if ud_opts.tailwind_opts.update_names then local txt = slice_line(bufnr, linenr, hl.range[1], hl.range[2]) if txt and not hl_state.updated_colors[txt] then @@ -204,11 +260,6 @@ function M.highlight(bufnr, ns_id, line_start, line_end, ud_opts, buf_local_opts -- Parse lines from matcher local data = M.parse_lines(bufnr, lines, line_start, ud_opts) or {} M.add_highlight(bufnr, ns_id, line_start, line_end, data, ud_opts) - -- Tailwind parsing - if ud_opts.tailwind == "normal" or ud_opts.tailwind == "both" then - local tw_data = M.parse_lines(bufnr, lines, line_start, ud_opts, { tailwind = true }) or {} - M.add_highlight(bufnr, const.namespace.tailwind_names, line_start, line_end, tw_data, ud_opts) - end if ud_opts.tailwind == "lsp" or ud_opts.tailwind == "both" then tailwind.lsp_highlight( bufnr, @@ -230,11 +281,8 @@ end ---@param lines table: Table of lines to parse ---@param line_start number: Buffer line number to start highlighting ---@param ud_opts table: `user_default_options` ----@param parse_opts table|nil: Parsing options ---- - tailwind boolean|nil: use tailwind_names parser ---@return table|nil -function M.parse_lines(bufnr, lines, line_start, ud_opts, parse_opts) - parse_opts = parse_opts or {} +function M.parse_lines(bufnr, lines, line_start, ud_opts) local loop_parse_fn = matcher.make(ud_opts) if not loop_parse_fn then return @@ -247,7 +295,7 @@ function M.parse_lines(bufnr, lines, line_start, ud_opts, parse_opts) while i < #line do local length, rgb_hex = loop_parse_fn(line, i, bufnr) if length and not rgb_hex then - vim.api.nvim_err_writeln( + utils.log_message( string.format( "Colorizer: Error parsing line %d, index %d. Please report this issue.", line_nr, diff --git a/lua/colorizer/constants.lua b/lua/colorizer/constants.lua index 4665d9e..74a1fc4 100644 --- a/lua/colorizer/constants.lua +++ b/lua/colorizer/constants.lua @@ -12,7 +12,6 @@ M.plugin = { -- - tailwind - Namespace used for creating extmarks to prevent tailwind name parsing from overwriting tailwind lsp highlights M.namespace = { default = vim.api.nvim_create_namespace(M.plugin.name), - tailwind_names = vim.api.nvim_create_namespace(M.plugin.name .. "_tailwind_names"), tailwind_lsp = vim.api.nvim_create_namespace(M.plugin.name .. "_tailwind_lsp"), } diff --git a/lua/colorizer/matcher.lua b/lua/colorizer/matcher.lua index f48f4d4..79c04f2 100644 --- a/lua/colorizer/matcher.lua +++ b/lua/colorizer/matcher.lua @@ -27,8 +27,6 @@ parsers.prefix = { ["_hsla"] = parsers.hsl_function, } --- TODO: 2024-12-31 - Return multiple parse_fn for tailwind parser? - ---Form a trie stuct with the given prefixes ---@param matchers table: List of prefixes, {"rgb", "hsl"} ---@param matchers_trie table: Table containing information regarding non-trie based parsers diff --git a/lua/colorizer/parser/names.lua b/lua/colorizer/parser/names.lua index bb68796..f451c1c 100644 --- a/lua/colorizer/parser/names.lua +++ b/lua/colorizer/parser/names.lua @@ -77,9 +77,7 @@ local function handle_names_custom(names_custom) if status and type(result) == "table" then names = result else - vim.api.nvim_err_writeln( - "Error in names_custom function: " .. (result or "Invalid return value") - ) + utils.log_message("Error in names_custom function: " .. (result or "Invalid return value")) return end end @@ -92,11 +90,11 @@ local function handle_names_custom(names_custom) if normalized_hex:match("^%x%x%x%x%x%x$") then add_color(name, normalized_hex) else - vim.api.nvim_err_writeln("Invalid hex code for '" .. name .. "': " .. normalized_hex) + utils.log_message(string.format("Invalid hex code for '%s': %s", name, normalized_hex)) end else - vim.api.nvim_err_writeln( - "Invalid value for '" .. name .. "': Expected string, got " .. type(hex) + utils.log_message( + string.format("Invalid value for '%s': Expected string, got %s", name, type(hex)) ) end end diff --git a/lua/colorizer/tailwind.lua b/lua/colorizer/tailwind.lua index 8aa7c79..6e0bd56 100644 --- a/lua/colorizer/tailwind.lua +++ b/lua/colorizer/tailwind.lua @@ -37,7 +37,7 @@ local function highlight(bufnr, ud_opts, add_highlight) lsp_cache[bufnr].document_params, function(err, results, _, _) if err ~= nil then - vim.api.nvim_err_writeln("tailwind.highlight: Error: " .. err) + utils.log_message("tailwind.highlight: Error: " .. err) end if err == nil and results ~= nil then local data, line_start, line_end = {}, nil, nil diff --git a/lua/colorizer/utils.lua b/lua/colorizer/utils.lua index 633f672..1e1cc50 100644 --- a/lua/colorizer/utils.lua +++ b/lua/colorizer/utils.lua @@ -224,4 +224,12 @@ function M.visible_line_range(bufnr) return range[1] - 1, range[2] end +function M.log_message(message) + if vim.version().minor >= 11 then + vim.api.nvim_echo({ { message, "ErrorMsg" } }, true, {}) + else + vim.api.nvim_err_writeln(message) + end +end + return M diff --git a/test/.gitignore b/test/.gitignore index 96f1a43..f76eac1 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -1,2 +1,2 @@ -colorizer_repro/ +colorizer_minimal/ colorizer_trie/ diff --git a/test/minimal-colorizer.lua b/test/minimal-colorizer.lua index 1945832..8027159 100644 --- a/test/minimal-colorizer.lua +++ b/test/minimal-colorizer.lua @@ -1,8 +1,8 @@ -- Run this file as `nvim --clean -u minimal-colorizer.lua` local settings = { - use_remote = false, -- Use colorizer master or local git directory - base_dir = "colorizer_repro", -- Directory to clone lazy.nvim + use_remote = true, -- Use colorizer master or local git directory + base_dir = "colorizer_minimal", -- Directory to clone lazy.nvim local_plugin_dir = os.getenv("HOME") .. "/git/nvim-colorizer.lua", -- Local git directory for colorizer. Used if use_remote is false expect = "expect.lua", plugins = { -- add any additional plugins