@@ -2,23 +2,47 @@ local globals = require 'libmodal.globals'
22local ParseTable = require ' libmodal.collections.ParseTable'
33local utils = require ' libmodal.utils' --- @type libmodal.utils
44
5+ --- @alias CursorPosition { [1] : integer , [2] : integer } see ` nvim_win_get_cursor`
6+
57--- @class libmodal.Mode
8+ --- @field private cursor CursorPosition
69--- @field private flush_input_timer unknown
710--- @field private help ? libmodal.utils.Help
8- --- @field private input libmodal.utils.Var[number ]
9- --- @field private input_bytes ? number [] local ` input` history
11+ --- @field private input libmodal.utils.Var[integer ]
12+ --- @field private input_bytes ? integer [] local ` input` history
1013--- @field private instruction fun ()|{ [string] : fun ()| string }
1114--- @field private mappings libmodal.collections.ParseTable
1215--- @field private modeline string[][]
1316--- @field private name string
14- --- @field private ns number the namespace where cursor highlights are drawn on
1517--- @field private popups libmodal.collections.Stack
1618--- @field private supress_exit boolean
17- --- @field public count libmodal.utils.Var[number]
19+ --- @field private virtual_cursor_autocmd integer
20+ --- @field public count libmodal.utils.Var[integer]
1821--- @field public exit libmodal.utils.Var[boolean]
1922--- @field public timeouts ? libmodal.utils.Var[boolean]
2023local Mode = utils .classes .new ()
2124
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 ,
32+ },
33+
34+ CursorMovedI = {
35+ i = true ,
36+ R = true ,
37+ },
38+ }
39+
40+ --- The namespaces used by modes
41+ local NS = {
42+ --- The virtual cursor namespace. Used to workaround neovim/neovim#20793
43+ CURSOR = vim .api .nvim_create_namespace (' libmodal-mode-virtual_cursor' ),
44+ }
45+
2246local HELP_CHAR = ' ?'
2347local TIMEOUT =
2448{
@@ -45,7 +69,7 @@ function Mode:execute_instruction(instruction)
4569 end
4670
4771 self .count :set (0 )
48- self :redraw_virtual_cursor ( )
72+ self :render_virtual_cursor ( 0 , true )
4973end
5074
5175--- check the user's input against the `self.instruction` mappings to see if there is anything to execute.
@@ -105,9 +129,11 @@ function Mode:check_input_for_mapping()
105129end
106130
107131--- clears the virtual cursor from the screen
132+ --- @param bufnr integer to clear the cursor on
108133--- @private
109- function Mode :clear_virt_cursor ()
110- vim .api .nvim_buf_clear_namespace (0 , self .ns , 0 , - 1 );
134+ function Mode :clear_virtual_cursor (bufnr )
135+ vim .api .nvim_buf_clear_namespace (bufnr , NS .CURSOR , 0 , - 1 );
136+
111137end
112138
113139--- enter this mode.
@@ -122,10 +148,22 @@ function Mode:enter()
122148 self .count :set (0 )
123149 self .exit :set (false )
124150
125- --- HACK: https://github.com/ neovim/neovim/issues/ 20793
151+ --- HACK: neovim/neovim# 20793
126152 vim .api .nvim_command ' highlight Cursor blend=100'
127153 vim .schedule (function () vim .opt .guicursor :append { ' a:Cursor/lCursor' } end )
128- self :render_virt_cursor ()
154+ self .cursor = self :cursor_in (0 )
155+ self :render_virtual_cursor (0 )
156+
157+ do
158+ 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+ })
166+ end
129167
130168 self .previous_mode_name = vim .g .libmodalActiveModeName
131169 vim .g .libmodalActiveModeName = self .name
@@ -194,19 +232,37 @@ function Mode:get_user_input()
194232 end
195233end
196234
197- --- clears and then renders the virtual cursor
198- --- @private
199- function Mode :redraw_virtual_cursor ()
200- self :clear_virt_cursor ()
201- self :render_virt_cursor ()
235+ --- @param winid integer
236+ --- @return CursorPosition line_and_col
237+ function Mode :cursor_in (winid )
238+ local cursor = vim .api .nvim_win_get_cursor (winid )
239+ cursor [1 ] = cursor [1 ] - 1 -- win_get_cursor returns +1 for our purpose
240+ return cursor
202241end
203242
204243--- render the virtual cursor using extmarks
244+ --- @param winid integer
245+ --- @param clear ? boolean if true , clear other virtual cursors before rendering the new one
205246--- @private
206- function Mode :render_virt_cursor ()
207- local line_nr , col_nr = unpack (vim .api .nvim_win_get_cursor (0 ))
208- line_nr = line_nr - 1 -- win_get_cursor returns +1 for our purpose
209- vim .highlight .range (0 , self .ns , ' Cursor' , { line_nr , col_nr }, { line_nr , col_nr + 1 }, {})
247+ function Mode :render_virtual_cursor (winid , clear )
248+ local bufnr = vim .api .nvim_win_get_buf (winid )
249+ if clear then
250+ self :clear_virtual_cursor (bufnr )
251+ end
252+
253+ local cursor = self :cursor_in (winid )
254+ vim .highlight .range (bufnr , NS .CURSOR , ' Cursor' , cursor , cursor , { inclusive = true })
255+
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
210266end
211267
212268--- show the mode indicator, if it is enabled
@@ -234,18 +290,20 @@ end
234290--- @private
235291--- @return nil
236292function Mode :tear_down ()
293+ --- HACK: neovim/neovim#20793
294+ self :clear_virtual_cursor (0 )
295+ vim .schedule (function () vim .opt .guicursor :remove { ' a:Cursor/lCursor' } end )
296+ vim .api .nvim_command ' highlight Cursor blend=0'
297+ vim .api .nvim_del_autocmd (self .virtual_cursor_autocmd )
298+ self .cursor = nil
299+
237300 if type (self .instruction ) == ' table' then
238301 self .flush_input_timer :stop ()
239302 self .input_bytes = nil
240303
241304 self .popups :pop ():close ()
242305 end
243306
244- --- HACK: https://github.com/neovim/neovim/issues/20793
245- self :clear_virt_cursor ()
246- vim .schedule (function () vim .opt .guicursor :remove { ' a:Cursor/lCursor' } end )
247- vim .api .nvim_command ' highlight Cursor blend=0'
248-
249307 if self .previous_mode_name and # vim .trim (self .previous_mode_name ) < 1 then
250308 vim .g .libmodalActiveModeName = nil
251309 else
@@ -272,7 +330,6 @@ function Mode.new(name, instruction, supress_exit)
272330 input = utils .Var .new (name , ' input' ),
273331 instruction = instruction ,
274332 name = name ,
275- ns = vim .api .nvim_create_namespace (' libmodal' .. name ),
276333 modeline = {{' -- ' .. name .. ' --' , ' LibmodalPrompt' }},
277334 },
278335 Mode
0 commit comments