Skip to content

Commit 9fa22b0

Browse files
authored
fix(Mode): textchanged events (#34)
* ref(Mode): arbitrary number of per-instance autocmds * ref(Mode): check cursor events using loop * fix(Mode): handle textchanged events * docs(examples): test CursorMoved, TextChanged events
1 parent 8366119 commit 9fa22b0

File tree

2 files changed

+126
-32
lines changed

2 files changed

+126
-32
lines changed

examples/lua/keymaps.lua

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,31 @@ local fooModeKeymaps =
1313
j = 'norm j',
1414
k = 'norm k',
1515
l = 'norm l',
16+
1617
G = function(self)
1718
local count = self.count:get()
1819
vim.api.nvim_command('norm! ' .. count .. 'G')
1920
end,
21+
22+
d = 'delete',
23+
e = 'edit foo',
24+
o = 'norm o',
25+
p = 'bp',
26+
2027
zf = 'split',
2128
zfc = 'q',
2229
zff = split_twice,
2330
zfo = 'vsplit',
24-
e = 'edit foo',
25-
p = 'bp',
26-
o = 'norm o',
2731
}
2832

33+
-- show that events work as expected
34+
local id = vim.api.nvim_create_autocmd(
35+
{ 'CursorMoved', 'CursorMovedI', 'TextChanged', 'TextChangedI', 'TextChangedP', 'TextChangedT' },
36+
{ callback = function(ev) vim.notify(vim.inspect(ev)) end }
37+
)
38+
2939
-- enter the mode using the keymaps
3040
libmodal.mode.enter('FOO', fooModeKeymaps)
41+
42+
-- remove setup
43+
vim.api.nvim_del_autocmd(id)

lua/libmodal/Mode.lua

Lines changed: 110 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ local utils = require 'libmodal.utils' --- @type libmodal.utils
55
--- @alias CursorPosition {[1]: integer, [2]: integer} see `nvim_win_get_cursor`
66

77
--- @class libmodal.Mode
8+
--- @field private autocmds integer[]
9+
--- @field private changedtick integer
810
--- @field private cursor CursorPosition
911
--- @field private flush_input_timer unknown
1012
--- @field private help? libmodal.utils.Help
@@ -16,24 +18,69 @@ local utils = require 'libmodal.utils' --- @type libmodal.utils
1618
--- @field private name string
1719
--- @field private popups libmodal.collections.Stack
1820
--- @field private supress_exit boolean
19-
--- @field private virtual_cursor_autocmd integer
2021
--- @field public count libmodal.utils.Var[integer]
2122
--- @field public exit libmodal.utils.Var[boolean]
2223
--- @field public timeouts? libmodal.utils.Var[boolean]
2324
local Mode = utils.classes.new()
2425

25-
--- Cursor events triggered by which modes
26-
local CURSOR_EVENTS_BY_MODE = {
27-
CursorMoved = {
28-
n = true,
29-
V = true,
30-
v = true,
31-
[utils.api.replace_termcodes '<C-v>'] = true,
26+
local C_v = utils.api.replace_termcodes '<C-v>'
27+
local C_s = utils.api.replace_termcodes '<C-s>'
28+
29+
--- Event groups organized by modes
30+
local EVENTS_BY_MODE = {
31+
--- Cursor events triggered by which modes
32+
CURSOR_MOVED = {
33+
CursorMoved = {
34+
n = true,
35+
nt = true,
36+
ntT = true,
37+
s = true,
38+
S = true,
39+
[C_s] = true,
40+
v = true,
41+
V = true,
42+
[C_v] = true,
43+
vs = true,
44+
Vs = true,
45+
[C_v .. 's'] = true,
46+
},
47+
48+
CursorMovedI = {
49+
i = true,
50+
niI = true,
51+
niR = true,
52+
R = true,
53+
Rv = true,
54+
},
3255
},
3356

34-
CursorMovedI = {
35-
i = true,
36-
R = true,
57+
TEXT_CHANGED = {
58+
TextChanged = {
59+
n = true,
60+
nt = true,
61+
ntT = true,
62+
},
63+
64+
TextChangedI = {
65+
i = true,
66+
niI = true,
67+
niR = true,
68+
R = true,
69+
Rv = true,
70+
},
71+
72+
TextChangedP = {
73+
ic = true,
74+
ix = true,
75+
Rc = true,
76+
Rvc = true,
77+
Rx = true,
78+
Rvx = true,
79+
},
80+
81+
TextChangedT = {
82+
t = true,
83+
},
3784
},
3885
}
3986

@@ -57,6 +104,18 @@ local ZERO = string.byte(0)
57104
--- Byte for 9
58105
local NINE = string.byte(9)
59106

107+
--- Execute events depending on current mode
108+
--- @param events_by_mode {[string]: {[string]: true}}
109+
local function execute_event_by_mode(events_by_mode)
110+
local mode = vim.api.nvim_get_mode().mode
111+
for event, modes in pairs(events_by_mode) do
112+
if modes[mode] then
113+
vim.api.nvim_exec_autocmds(event, {})
114+
break
115+
end
116+
end
117+
end
118+
60119
--- execute the `instruction`.
61120
--- @private
62121
--- @param instruction fun(libmodal.Mode)|string a Lua function or Vimscript command.
@@ -70,6 +129,7 @@ function Mode:execute_instruction(instruction)
70129

71130
self.count:set(0)
72131
self:render_virtual_cursor(0, true)
132+
self:execute_text_changed_events()
73133
end
74134

75135
--- check the user's input against the `self.instruction` mappings to see if there is anything to execute.
@@ -133,7 +193,24 @@ end
133193
--- @private
134194
function Mode:clear_virtual_cursor(bufnr)
135195
vim.api.nvim_buf_clear_namespace(bufnr, NS.CURSOR, 0, -1);
196+
end
136197

198+
--- Runs CursorMoved* events, if applicable
199+
--- @param cursor CursorPosition the current cursor position
200+
function Mode:execute_cursor_moved_events(cursor)
201+
if not vim.deep_equal(self.cursor, cursor) then
202+
execute_event_by_mode(EVENTS_BY_MODE.CURSOR_MOVED)
203+
self.cursor = cursor
204+
end
205+
end
206+
207+
--- Runs TextChanged* events, if applicable
208+
function Mode:execute_text_changed_events()
209+
local changedtick = vim.api.nvim_buf_get_changedtick(0)
210+
if self.changedtick ~= changedtick then
211+
execute_event_by_mode(EVENTS_BY_MODE.TEXT_CHANGED)
212+
self.changedtick = changedtick
213+
end
137214
end
138215

139216
--- enter this mode.
@@ -156,15 +233,26 @@ function Mode:enter()
156233

157234
do
158235
local augroup = vim.api.nvim_create_augroup('libmodal-mode-' .. self.name, { clear = false })
159-
self.virtual_cursor_autocmd = vim.api.nvim_create_autocmd('BufLeave', {
160-
callback = function(ev)
161-
local bufnr = ev.buf
162-
self:clear_virtual_cursor(bufnr)
163-
end,
164-
group = augroup,
165-
})
236+
self.autocmds = {
237+
vim.api.nvim_create_autocmd('BufLeave', {
238+
callback = function(ev)
239+
local bufnr = ev.buf
240+
self:clear_virtual_cursor(bufnr)
241+
end,
242+
group = augroup,
243+
}),
244+
245+
vim.api.nvim_create_autocmd('BufEnter', {
246+
callback = function(ev)
247+
local bufnr = ev.buf
248+
self.changedtick = vim.api.nvim_buf_get_changedtick(bufnr)
249+
end,
250+
});
251+
}
166252
end
167253

254+
self.changedtick = vim.api.nvim_buf_get_changedtick(0)
255+
168256
self.previous_mode_name = vim.g.libmodalActiveModeName
169257
vim.g.libmodalActiveModeName = self.name
170258

@@ -253,16 +341,7 @@ function Mode:render_virtual_cursor(winid, clear)
253341
local cursor = self:cursor_in(winid)
254342
vim.highlight.range(bufnr, NS.CURSOR, 'Cursor', cursor, cursor, { inclusive = true })
255343

256-
if not vim.deep_equal(self.cursor, cursor) then
257-
local mode = vim.api.nvim_get_mode().mode
258-
if CURSOR_EVENTS_BY_MODE.CursorMoved[mode] then
259-
vim.api.nvim_exec_autocmds('CursorMoved', {})
260-
elseif CURSOR_EVENTS_BY_MODE.CursorMovedI[mode] then
261-
vim.api.nvim_exec_autocmds('CursorMovedI', {})
262-
end
263-
264-
self.cursor = cursor
265-
end
344+
self:execute_cursor_moved_events(cursor)
266345
end
267346

268347
--- show the mode indicator, if it is enabled
@@ -294,7 +373,9 @@ function Mode:tear_down()
294373
self:clear_virtual_cursor(0)
295374
vim.schedule(function() vim.opt.guicursor:remove { 'a:Cursor/lCursor' } end)
296375
vim.api.nvim_command 'highlight Cursor blend=0'
297-
vim.api.nvim_del_autocmd(self.virtual_cursor_autocmd)
376+
for _, autocmd in ipairs(self.autocmds) do
377+
vim.api.nvim_del_autocmd(autocmd)
378+
end
298379
self.cursor = nil
299380

300381
if type(self.instruction) == 'table' then

0 commit comments

Comments
 (0)