Skip to content

Commit 86c9a6a

Browse files
authored
Fix/132 multiple virtualtext (#133)
* fix: clear all namespaces when detaching from buffer * chore: removes unused tailwind_names namespace * fix: when tailwind = "both", reapply default highlights while avoiding collisions * chore: creates message logger to use vim.api.nvim_echo for nvim >= .11
1 parent fb8296c commit 86c9a6a

File tree

10 files changed

+103
-47
lines changed

10 files changed

+103
-47
lines changed

Makefile

+6-4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ SCRIPTS_DIR=scripts
33
TRIE_TEST_SCRIPT=$(SCRIPTS_DIR)/trie-test.sh
44
TRIE_BENCHMARK_SCRIPT=$(SCRIPTS_DIR)/trie-benchmark.sh
55
MINIMAL_SCRIPT=$(SCRIPTS_DIR)/minimal-colorizer.sh
6+
MINIMAL_COLORIZER=colorizer_minimal
7+
MINIMAL_TRIE=colorizer_trie
68

79
help:
810
@echo "Available targets:"
@@ -27,9 +29,9 @@ minimal:
2729
@bash $(MINIMAL_SCRIPT)
2830

2931
clean:
30-
@echo "Removing test/colorizer_repro"
31-
@rm -rf test/colorizer_repro
32-
@echo "Removing test/trie/colorizer_trie"
33-
@rm -rf test/trie/colorizer_trie
32+
@echo "Removing test/"$(MINIMAL_COLORIZER)
33+
@rm -rf test/$(MINIMAL_COLORIZER)
34+
@echo "Removing test/trie/"$(MINIMAL_TRIE)
35+
@rm -rf test/trie/$(MINIMAL_TRIE)
3436

3537
.PHONY: help trie trie-test trie-benchmark minimal clean

lua/colorizer.lua

+3
Original file line numberDiff line numberDiff line change
@@ -405,6 +405,9 @@ function M.detach_from_buffer(bufnr)
405405
if bufnr < 0 then
406406
return -1
407407
end
408+
for _, ns_id in pairs(const.namespace) do
409+
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, 0, -1)
410+
end
408411
vim.api.nvim_buf_clear_namespace(bufnr, const.namespace.default, 0, -1)
409412
if colorizer_state.buffer_local[bufnr] then
410413
for _, namespace in pairs(colorizer_state.buffer_local[bufnr].__detach.ns_id) do

lua/colorizer/buffer.lua

+78-30
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ local function create_highlight(rgb_hex, mode)
5959
if mode == "foreground" then
6060
vim.api.nvim_set_hl(0, highlight_name, { fg = "#" .. rgb_hex })
6161
else
62-
-- TODO: 2025-01-11 - Should this check for background or virtualtext
6362
local rr, gg, bb = rgb_hex:sub(1, 2), rgb_hex:sub(3, 4), rgb_hex:sub(5, 6)
6463
local r, g, b = tonumber(rr, 16), tonumber(gg, 16), tonumber(bb, 16)
6564
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)
7776
return string.sub(lines[1], start_col + 1, end_col)
7877
end
7978

79+
--- Add low priority highlights. Trims highlight ranges to avoid collisions.
80+
---@param bufnr number: Buffer number
81+
---@param extmarks table: List of low priority extmarks to reapply
82+
---@param priority_ns_id number: Namespace id for priority highlights
83+
---@param linenr number: Line number
84+
local function add_low_priority_highlights(bufnr, extmarks, priority_ns_id, linenr)
85+
local priority_marks = vim.api.nvim_buf_get_extmarks(
86+
bufnr,
87+
priority_ns_id,
88+
{ linenr, 0 },
89+
{ linenr + 1, 0 },
90+
{ details = true }
91+
)
92+
for _, default_mark in ipairs(extmarks) do
93+
local default_start = default_mark[3] -- Start column
94+
local default_end = default_mark[4].end_col
95+
local hl_group = default_mark[4].hl_group
96+
local non_overlapping_ranges = { { default_start, default_end } }
97+
for _, lsp_mark in ipairs(priority_marks) do
98+
local lsp_start = lsp_mark[3]
99+
local lsp_end = lsp_mark[4].end_col
100+
-- Adjust ranges to avoid collisions
101+
local new_ranges = {}
102+
for _, range in ipairs(non_overlapping_ranges) do
103+
local start, end_ = range[1], range[2]
104+
if lsp_start <= end_ and lsp_end >= start then
105+
-- Collision detected, split range
106+
if start < lsp_start then
107+
table.insert(new_ranges, { start, lsp_start })
108+
end
109+
if lsp_end < end_ then
110+
table.insert(new_ranges, { lsp_end, end_ })
111+
end
112+
else
113+
-- No collision, keep range
114+
table.insert(new_ranges, { start, end_ })
115+
end
116+
end
117+
non_overlapping_ranges = new_ranges
118+
end
119+
for _, range in ipairs(non_overlapping_ranges) do
120+
vim.api.nvim_buf_add_highlight(
121+
bufnr,
122+
default_mark[4].ns_id, -- Original namespace
123+
hl_group,
124+
linenr,
125+
range[1],
126+
range[2]
127+
)
128+
end
129+
end
130+
end
131+
80132
--- Create highlight and set highlights
81133
---@param bufnr number: Buffer number (0 for current)
82134
---@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
93145
hl_opts = hl_opts or {}
94146
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, line_start, line_end)
95147
if ud_opts.mode == "background" or ud_opts.mode == "foreground" then
148+
local tw_both = ud_opts.tailwind == "both" and hl_opts.tailwind_lsp
96149
for linenr, hls in pairs(data) do
150+
local marks
151+
if tw_both then
152+
marks = vim.api.nvim_buf_get_extmarks(
153+
bufnr,
154+
const.namespace.default,
155+
{ linenr, 0 },
156+
{ linenr + 1, 0 },
157+
{ details = true }
158+
)
159+
-- clear default namespace to apply LSP highlights, then rehighlight non-overlapping default highlights
160+
-- Fixes: https://github.com/catgoose/nvim-colorizer.lua/issues/61
161+
vim.api.nvim_buf_clear_namespace(bufnr, const.namespace.default, linenr, linenr + 1)
162+
end
97163
for _, hl in ipairs(hls) do
98-
if ud_opts.tailwind == "both" and hl_opts.tailwind_lsp then
99-
vim.api.nvim_buf_clear_namespace(
100-
bufnr,
101-
const.namespace.tailwind_names,
102-
linenr,
103-
linenr + 1
104-
)
105-
if ud_opts.tailwind_opts.update_names then
106-
local txt = slice_line(bufnr, linenr, hl.range[1], hl.range[2])
107-
if txt and not hl_state.updated_colors[txt] then
108-
hl_state.updated_colors[txt] = true
109-
names.update_color(txt, hl.rgb_hex)
110-
end
164+
if tw_both and ud_opts.tailwind_opts.update_names then
165+
local txt = slice_line(bufnr, linenr, hl.range[1], hl.range[2])
166+
if txt and not hl_state.updated_colors[txt] then
167+
hl_state.updated_colors[txt] = true
168+
names.update_color(txt, hl.rgb_hex)
111169
end
112170
end
113171
local hlname = create_highlight(hl.rgb_hex, ud_opts.mode)
114172
vim.api.nvim_buf_add_highlight(bufnr, ns_id, hlname, linenr, hl.range[1], hl.range[2])
115173
end
174+
if tw_both then
175+
add_low_priority_highlights(bufnr, marks, ns_id, linenr)
176+
end
116177
end
117178
elseif ud_opts.mode == "virtualtext" then
118179
for linenr, hls in pairs(data) do
119180
for _, hl in ipairs(hls) do
120181
if ud_opts.tailwind == "both" and hl_opts.tailwind_lsp then
121-
vim.api.nvim_buf_clear_namespace(
122-
bufnr,
123-
const.namespace.tailwind_names,
124-
linenr,
125-
linenr + 1
126-
)
182+
vim.api.nvim_buf_clear_namespace(bufnr, ns_id, linenr, linenr + 1)
127183
if ud_opts.tailwind_opts.update_names then
128184
local txt = slice_line(bufnr, linenr, hl.range[1], hl.range[2])
129185
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
204260
-- Parse lines from matcher
205261
local data = M.parse_lines(bufnr, lines, line_start, ud_opts) or {}
206262
M.add_highlight(bufnr, ns_id, line_start, line_end, data, ud_opts)
207-
-- Tailwind parsing
208-
if ud_opts.tailwind == "normal" or ud_opts.tailwind == "both" then
209-
local tw_data = M.parse_lines(bufnr, lines, line_start, ud_opts, { tailwind = true }) or {}
210-
M.add_highlight(bufnr, const.namespace.tailwind_names, line_start, line_end, tw_data, ud_opts)
211-
end
212263
if ud_opts.tailwind == "lsp" or ud_opts.tailwind == "both" then
213264
tailwind.lsp_highlight(
214265
bufnr,
@@ -230,11 +281,8 @@ end
230281
---@param lines table: Table of lines to parse
231282
---@param line_start number: Buffer line number to start highlighting
232283
---@param ud_opts table: `user_default_options`
233-
---@param parse_opts table|nil: Parsing options
234-
--- - tailwind boolean|nil: use tailwind_names parser
235284
---@return table|nil
236-
function M.parse_lines(bufnr, lines, line_start, ud_opts, parse_opts)
237-
parse_opts = parse_opts or {}
285+
function M.parse_lines(bufnr, lines, line_start, ud_opts)
238286
local loop_parse_fn = matcher.make(ud_opts)
239287
if not loop_parse_fn then
240288
return
@@ -247,7 +295,7 @@ function M.parse_lines(bufnr, lines, line_start, ud_opts, parse_opts)
247295
while i < #line do
248296
local length, rgb_hex = loop_parse_fn(line, i, bufnr)
249297
if length and not rgb_hex then
250-
vim.api.nvim_err_writeln(
298+
utils.log_message(
251299
string.format(
252300
"Colorizer: Error parsing line %d, index %d. Please report this issue.",
253301
line_nr,

lua/colorizer/constants.lua

-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ M.plugin = {
1212
-- - tailwind - Namespace used for creating extmarks to prevent tailwind name parsing from overwriting tailwind lsp highlights
1313
M.namespace = {
1414
default = vim.api.nvim_create_namespace(M.plugin.name),
15-
tailwind_names = vim.api.nvim_create_namespace(M.plugin.name .. "_tailwind_names"),
1615
tailwind_lsp = vim.api.nvim_create_namespace(M.plugin.name .. "_tailwind_lsp"),
1716
}
1817

lua/colorizer/matcher.lua

-2
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ parsers.prefix = {
2727
["_hsla"] = parsers.hsl_function,
2828
}
2929

30-
-- TODO: 2024-12-31 - Return multiple parse_fn for tailwind parser?
31-
3230
---Form a trie stuct with the given prefixes
3331
---@param matchers table: List of prefixes, {"rgb", "hsl"}
3432
---@param matchers_trie table: Table containing information regarding non-trie based parsers

lua/colorizer/parser/names.lua

+4-6
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,7 @@ local function handle_names_custom(names_custom)
7777
if status and type(result) == "table" then
7878
names = result
7979
else
80-
vim.api.nvim_err_writeln(
81-
"Error in names_custom function: " .. (result or "Invalid return value")
82-
)
80+
utils.log_message("Error in names_custom function: " .. (result or "Invalid return value"))
8381
return
8482
end
8583
end
@@ -92,11 +90,11 @@ local function handle_names_custom(names_custom)
9290
if normalized_hex:match("^%x%x%x%x%x%x$") then
9391
add_color(name, normalized_hex)
9492
else
95-
vim.api.nvim_err_writeln("Invalid hex code for '" .. name .. "': " .. normalized_hex)
93+
utils.log_message(string.format("Invalid hex code for '%s': %s", name, normalized_hex))
9694
end
9795
else
98-
vim.api.nvim_err_writeln(
99-
"Invalid value for '" .. name .. "': Expected string, got " .. type(hex)
96+
utils.log_message(
97+
string.format("Invalid value for '%s': Expected string, got %s", name, type(hex))
10098
)
10199
end
102100
end

lua/colorizer/tailwind.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ local function highlight(bufnr, ud_opts, add_highlight)
3737
lsp_cache[bufnr].document_params,
3838
function(err, results, _, _)
3939
if err ~= nil then
40-
vim.api.nvim_err_writeln("tailwind.highlight: Error: " .. err)
40+
utils.log_message("tailwind.highlight: Error: " .. err)
4141
end
4242
if err == nil and results ~= nil then
4343
local data, line_start, line_end = {}, nil, nil

lua/colorizer/utils.lua

+8
Original file line numberDiff line numberDiff line change
@@ -224,4 +224,12 @@ function M.visible_line_range(bufnr)
224224
return range[1] - 1, range[2]
225225
end
226226

227+
function M.log_message(message)
228+
if vim.version().minor >= 11 then
229+
vim.api.nvim_echo({ { message, "ErrorMsg" } }, true, {})
230+
else
231+
vim.api.nvim_err_writeln(message)
232+
end
233+
end
234+
227235
return M

test/.gitignore

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
1-
colorizer_repro/
1+
colorizer_minimal/
22
colorizer_trie/

test/minimal-colorizer.lua

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
-- Run this file as `nvim --clean -u minimal-colorizer.lua`
22

33
local settings = {
4-
use_remote = false, -- Use colorizer master or local git directory
5-
base_dir = "colorizer_repro", -- Directory to clone lazy.nvim
4+
use_remote = true, -- Use colorizer master or local git directory
5+
base_dir = "colorizer_minimal", -- Directory to clone lazy.nvim
66
local_plugin_dir = os.getenv("HOME") .. "/git/nvim-colorizer.lua", -- Local git directory for colorizer. Used if use_remote is false
77
expect = "expect.lua",
88
plugins = { -- add any additional plugins

0 commit comments

Comments
 (0)