Skip to content

Commit faa4bad

Browse files
committed
fix(winhl): sync inactive highlights for all windows
1 parent 1c91a20 commit faa4bad

File tree

3 files changed

+64
-66
lines changed

3 files changed

+64
-66
lines changed

lua/reactive/commands.lua

+19-12
Original file line numberDiff line numberDiff line change
@@ -66,31 +66,38 @@ function M:init()
6666
group = group,
6767
pattern = '*:*',
6868
desc = 'Reactive: watches for mode changes to update highlights and run callbacks',
69-
callback = function(opts)
70-
local from, to = vim.v.event.old_mode, vim.v.event.new_mode
71-
72-
Snapshot:set_modes(from, to)
69+
callback = function()
70+
Snapshot:set_modes(vim.v.event.old_mode, vim.v.event.new_mode)
7371
local snap = Snapshot:gen { callbacks = true }
7472

7573
Highlight:apply {
7674
hl = snap.hl,
77-
winhl = snap.winhl,
75+
winhl = snap.winhl.current,
7876
winid = api.nvim_get_current_win(),
7977
}
8078
end,
8179
})
8280

83-
-- We need BufWinEnter event to successfully handle cases where you open a window
84-
-- then through telescope go to another one and then go back through Ctrl + o
85-
aucmd({ 'WinEnter', 'BufWinEnter', 'WinLeave' }, {
81+
aucmd('WinEnter', {
8682
group = group,
8783
desc = 'Reactive: applies active/inactive window highlights',
88-
callback = function(opts)
89-
local snap = Snapshot:gen { inactive_win = opts.event == 'WinLeave' }
84+
callback = function()
85+
Highlight:sync()
86+
end,
87+
})
9088

89+
-- We use this autocmd to fix the bug when after entering a file through a telescope/fzf/whatever
90+
-- and then jumping back through ctrl+o mapping we could get highlights for noncurrent windows or
91+
-- highlights for a different mode, for example having a highlights for insert mode while being in normal
92+
-- it may be a neovim issue (or feature?), because I don't see any reasons for it to happen
93+
aucmd('BufWinEnter', {
94+
group = group,
95+
desc = 'Reactive: applies active/inactive window highlights',
96+
callback = function()
97+
local snap = Snapshot:gen()
9198
Highlight:apply {
9299
hl = snap.hl,
93-
winhl = snap.winhl,
100+
winhl = snap.winhl.current,
94101
winid = api.nvim_get_current_win(),
95102
}
96103
end,
@@ -107,7 +114,7 @@ function M:init()
107114
end,
108115
})
109116

110-
Highlight:sync()
117+
Highlight:sync(true)
111118
end
112119

113120
local function stop_plugin()

lua/reactive/highlight.lua

+20-21
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,16 @@ local M = {
33
prev_highlights = {},
44
}
55

6-
---@param opts { winid: number, winhl?: table<string, string>, hl?: table<string, table<string, any>>, forced?: boolean }
6+
---@param opts { winid: number, winhl?: table<string, string>, hl?: table<string, table<string, any>> }
77
function M:apply(opts)
88
local skip_winhl = self:apply_winhl(opts.winhl, opts.winid)
9-
local skip_hl = self:apply_hl(opts.hl, opts.forced)
9+
local skip_hl = self:apply_hl(opts.hl)
1010

1111
-- redraw pending screen updates
1212
-- without this command some highlights won't be applied immediately
1313
-- or till the next "redraw" caused by nvim itself
1414
-- but we won't redraw if it's not forced or there're no highlights applied or changed
15-
if not skip_winhl or not skip_hl or opts.forced then
15+
if not skip_winhl or not skip_hl then
1616
vim.cmd.redraw()
1717
end
1818
end
@@ -101,32 +101,31 @@ function M:apply_hl(highlights, forced)
101101
return false
102102
end
103103

104-
function M:sync()
104+
---@param forced boolean? forcely apply highlights
105+
function M:sync(forced)
105106
local Util = require 'reactive.util'
106-
local Snapshot = require 'reactive.snapshot'
107-
local windows = vim.api.nvim_list_wins()
107+
local snapshot = require('reactive.snapshot'):gen()
108108
local current_win = vim.api.nvim_get_current_win()
109109

110-
Util.eachi(windows, function(win)
111-
if win == current_win then
110+
-- we'll only apply global highlights once as it makes no sense to do it
111+
-- several times
112+
local skip_hl = self:apply_hl(snapshot.hl, forced)
113+
local skip_winhl = true
114+
115+
Util.eachi(vim.api.nvim_list_wins(), function(win)
116+
-- we don't apply colours to non-focusable windows
117+
if not vim.api.nvim_win_get_config(win).focusable then
112118
return
113119
end
114120

115-
local snap = Snapshot:gen { inactive_win = true }
116-
self:apply_winhl(snap.winhl, win)
121+
if not self:apply_winhl(win == current_win and snapshot.winhl.current or snapshot.winhl.noncurrent, win) then
122+
skip_winhl = false
123+
end
117124
end)
118125

119-
local current_win_snap = Snapshot:gen()
120-
-- we'll only apply global highlights once as it makes no sense to do it
121-
-- several times
122-
self:apply {
123-
hl = current_win_snap.hl,
124-
winhl = current_win_snap.winhl,
125-
winid = current_win,
126-
-- whenever we 'sync' colors, we should forcely apply new highlights even though there
127-
-- could be the same highlight groups that had been applied before
128-
forced = true,
129-
}
126+
if not skip_hl or not skip_winhl then
127+
vim.cmd.redraw()
128+
end
130129
end
131130

132131
return M

lua/reactive/snapshot.lua

+25-33
Original file line numberDiff line numberDiff line change
@@ -32,36 +32,14 @@ function M:set_opfunc(opfunc)
3232
end
3333

3434
---@param opts? { inactive_win?: boolean, callbacks?: boolean }
35-
---@return { winhl: table<string, string>, hl: table<string, table> }
35+
---@return { winhl: { current: table<string, string>, noncurrent: table<string, string> }, hl: table<string, table> }
3636
function M:gen(opts)
3737
local State = require 'reactive.state'
3838

39-
self.snapshot = { winhl = {}, hl = {} }
40-
41-
-- if we're leaving a window, then we just color that window with `inactive` colors, if presented
42-
if opts and opts.inactive_win then
43-
State:iterate_presets(function(preset)
44-
local constraints = {}
45-
46-
if
47-
preset.static
48-
and not vim.tbl_isempty(preset.static)
49-
and not self:process_constraints(preset.skip, constraints)
50-
then
51-
self:form {
52-
preset_name = preset.name,
53-
highlights = {
54-
winhl = preset.static.winhl and preset.static.winhl.inactive,
55-
hl = preset.static.hl,
56-
},
57-
scope = '@static.inactive',
58-
constraints = constraints,
59-
}
60-
end
61-
end)
62-
63-
return self.snapshot
64-
end
39+
self.snapshot = {
40+
winhl = { current = {}, noncurrent = {} },
41+
hl = {},
42+
}
6543

6644
local mode = self.to
6745
local mode_len = #mode
@@ -196,9 +174,22 @@ function M:gen(opts)
196174
if preset.static and not vim.tbl_isempty(preset.static) then
197175
self:form {
198176
preset_name = preset.name,
199-
highlights = { winhl = preset.static.winhl and preset.static.winhl.active, hl = preset.static.hl },
200-
scope = '@static.active',
177+
highlights = {
178+
winhl = preset.static.winhl and preset.static.winhl.active,
179+
hl = preset.static.hl,
180+
},
181+
scope = '@static.current',
182+
constraints = scope[preset.name] and scope[preset.name].constraints or {},
183+
}
184+
185+
self:form {
186+
preset_name = preset.name,
187+
highlights = {
188+
winhl = preset.static.winhl and preset.static.winhl.inactive,
189+
},
190+
scope = '@static.noncurrent',
201191
constraints = scope[preset.name] and scope[preset.name].constraints or {},
192+
window_scope = 'noncurrent',
202193
}
203194
end
204195

@@ -278,7 +269,7 @@ local merge_handlers = {
278269
Util.each(highlights, function(hl_group, hl_val)
279270
-- if a group is already applied, then we won't overwrite it
280271
-- meaning that it had a higher priority
281-
if M.snapshot.winhl[hl_group] then
272+
if M.snapshot.winhl[opts.window_scope][hl_group] then
282273
return
283274
end
284275

@@ -297,9 +288,9 @@ local merge_handlers = {
297288
M.cache.transformed_winhl[key] = rhs
298289
end
299290

300-
M.snapshot.winhl[hl_group] = rhs
291+
M.snapshot.winhl[opts.window_scope][hl_group] = rhs
301292
else
302-
M.snapshot.winhl[hl_group] = cached_hl
293+
M.snapshot.winhl[opts.window_scope][hl_group] = cached_hl
303294
end
304295
end)
305296
end,
@@ -315,11 +306,12 @@ local merge_handlers = {
315306
end,
316307
}
317308

318-
---@param opts { preset_name: string, highlights: table<string, table | fun(): table>, scope: string, constraints: TriggerConstraints }
309+
---@param opts { preset_name: string, highlights: table<string, table | fun(): table>, scope: string, constraints: TriggerConstraints, window_scope?: 'current' | 'noncurrent' }
319310
function M:form(opts)
320311
local handler_options = {
321312
scope = string.format('@preset.%s.%s', opts.preset_name, opts.scope),
322313
preset_name = opts.preset_name,
314+
window_scope = opts.window_scope or 'current',
323315
}
324316

325317
Util.each(merge_handlers, function(value)

0 commit comments

Comments
 (0)