@ -2,23 +2,47 @@ local globals = require 'libmodal.globals'
local ParseTable = require ' libmodal.collections.ParseTable '
local utils = require ' libmodal.utils ' --- @type libmodal.utils
--- @alias CursorPosition {[1]: integer, [2]: integer} see `nvim_win_get_cursor`
--- @class libmodal.Mode
--- @field private cursor CursorPosition
--- @field private flush_input_timer unknown
--- @field private help? libmodal.utils.Help
--- @field private input libmodal.utils.Var[ numb er]
--- @field private input_bytes? numb er[] local `input` history
--- @field private input libmodal.utils.Var[ integ er]
--- @field private input_bytes? integ er[] local `input` history
--- @field private instruction fun()|{[string]: fun()|string}
--- @field private mappings libmodal.collections.ParseTable
--- @field private modeline string[][]
--- @field private name string
--- @field private ns number the namespace where cursor highlights are drawn on
--- @field private popups libmodal.collections.Stack
--- @field private supress_exit boolean
--- @field public count libmodal.utils.Var[number]
--- @field private virtual_cursor_autocmd integer
--- @field public count libmodal.utils.Var[integer]
--- @field public exit libmodal.utils.Var[boolean]
--- @field public timeouts? libmodal.utils.Var[boolean]
local Mode = utils.classes . new ( )
--- Cursor events triggered by which modes
local CURSOR_EVENTS_BY_MODE = {
CursorMoved = {
n = true ,
V = true ,
v = true ,
[ utils.api . replace_termcodes ' <C-v> ' ] = true ,
} ,
CursorMovedI = {
i = true ,
R = true ,
} ,
}
--- The namespaces used by modes
local NS = {
--- The virtual cursor namespace. Used to workaround neovim/neovim#20793
CURSOR = vim.api . nvim_create_namespace ( ' libmodal-mode-virtual_cursor ' ) ,
}
local HELP_CHAR = ' ? '
local TIMEOUT =
{
@ -45,7 +69,7 @@ function Mode:execute_instruction(instruction)
end
self.count : set ( 0 )
self : re draw _virtual_cursor( )
self : re n de r_virtual_cursor( 0 , true )
end
--- 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()
end
--- clears the virtual cursor from the screen
--- @param bufnr integer to clear the cursor on
--- @private
function Mode : clear_virt_cursor ( )
vim.api . nvim_buf_clear_namespace ( 0 , self.ns , 0 , - 1 ) ;
function Mode : clear_virtual_cursor ( bufnr )
vim.api . nvim_buf_clear_namespace ( bufnr , NS.CURSOR , 0 , - 1 ) ;
end
--- enter this mode.
@ -122,10 +148,22 @@ function Mode:enter()
self.count : set ( 0 )
self.exit : set ( false )
--- HACK: https://github.com/neovim/neovim/issues/ 20793
--- HACK: neovim/neovim# 20793
vim.api . nvim_command ' highlight Cursor blend=100 '
vim.schedule ( function ( ) vim.opt . guicursor : append { ' a:Cursor/lCursor ' } end )
self : render_virt_cursor ( )
self.cursor = self : cursor_in ( 0 )
self : render_virtual_cursor ( 0 )
do
local augroup = vim.api . nvim_create_augroup ( ' libmodal-mode- ' .. self.name , { clear = false } )
self.virtual_cursor_autocmd = vim.api . nvim_create_autocmd ( ' BufLeave ' , {
callback = function ( ev )
local bufnr = ev.buf
self : clear_virtual_cursor ( bufnr )
end ,
group = augroup ,
} )
end
self.previous_mode_name = vim.g . libmodalActiveModeName
vim.g . libmodalActiveModeName = self.name
@ -194,19 +232,37 @@ function Mode:get_user_input()
end
end
--- clears and then renders the virtual cursor
--- @private
function Mode : redraw_virtual_cursor ( )
self : clear_virt_cursor ( )
self : render_virt_cursor ( )
--- @param winid integer
--- @return CursorPosition line_and_col
function Mode : cursor_in ( winid )
local cursor = vim.api . nvim_win_get_cursor ( winid )
cursor [ 1 ] = cursor [ 1 ] - 1 -- win_get_cursor returns +1 for our purpose
return cursor
end
--- render the virtual cursor using extmarks
--- @param winid integer
--- @param clear? boolean if true, clear other virtual cursors before rendering the new one
--- @private
function Mode : render_virt_cursor ( )
local line_nr , col_nr = unpack ( vim.api . nvim_win_get_cursor ( 0 ) )
line_nr = line_nr - 1 -- win_get_cursor returns +1 for our purpose
vim.highlight . range ( 0 , self.ns , ' Cursor ' , { line_nr , col_nr } , { line_nr , col_nr + 1 } , { } )
function Mode : render_virtual_cursor ( winid , clear )
local bufnr = vim.api . nvim_win_get_buf ( winid )
if clear then
self : clear_virtual_cursor ( bufnr )
end
local cursor = self : cursor_in ( winid )
vim.highlight . range ( bufnr , NS.CURSOR , ' Cursor ' , cursor , cursor , { inclusive = true } )
if not vim.deep_equal ( self.cursor , cursor ) then
local mode = vim.api . nvim_get_mode ( ) . mode
if CURSOR_EVENTS_BY_MODE.CursorMoved [ mode ] then
vim.api . nvim_exec_autocmds ( ' CursorMoved ' , { } )
elseif CURSOR_EVENTS_BY_MODE.CursorMovedI [ mode ] then
vim.api . nvim_exec_autocmds ( ' CursorMovedI ' , { } )
end
self.cursor = cursor
end
end
--- show the mode indicator, if it is enabled
@ -234,6 +290,13 @@ end
--- @private
--- @return nil
function Mode : tear_down ( )
--- HACK: neovim/neovim#20793
self : clear_virtual_cursor ( 0 )
vim.schedule ( function ( ) vim.opt . guicursor : remove { ' a:Cursor/lCursor ' } end )
vim.api . nvim_command ' highlight Cursor blend=0 '
vim.api . nvim_del_autocmd ( self.virtual_cursor_autocmd )
self.cursor = nil
if type ( self.instruction ) == ' table ' then
self.flush_input_timer : stop ( )
self.input_bytes = nil
@ -241,11 +304,6 @@ function Mode:tear_down()
self.popups : pop ( ) : close ( )
end
--- HACK: https://github.com/neovim/neovim/issues/20793
self : clear_virt_cursor ( )
vim.schedule ( function ( ) vim.opt . guicursor : remove { ' a:Cursor/lCursor ' } end )
vim.api . nvim_command ' highlight Cursor blend=0 '
if self.previous_mode_name and # vim.trim ( self.previous_mode_name ) < 1 then
vim.g . libmodalActiveModeName = nil
else
@ -272,7 +330,6 @@ function Mode.new(name, instruction, supress_exit)
input = utils.Var . new ( name , ' input ' ) ,
instruction = instruction ,
name = name ,
ns = vim.api . nvim_create_namespace ( ' libmodal ' .. name ) ,
modeline = { { ' -- ' .. name .. ' -- ' , ' LibmodalPrompt ' } } ,
} ,
Mode