fix: virtual cursor edge cases (#32)

* perf(Mode): use dedicated virtual cursor namespace

* fix(Mode): virtual cursor stays in inactive buffers when exiting

* fix(Mode): `CursorMoved` not fired as expected
pull/38/head
Iron-E 2 months ago committed by GitHub
parent 3cb0a3817f
commit 8366119447
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -21,6 +21,9 @@ local fooModeKeymaps =
zfc = 'q',
zff = split_twice,
zfo = 'vsplit',
e = 'edit foo',
p = 'bp',
o = 'norm o',
}
-- enter the mode using the keymaps

@ -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[number]
--- @field private input_bytes? number[] local `input` history
--- @field private input libmodal.utils.Var[integer]
--- @field private input_bytes? integer[] 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:redraw_virtual_cursor()
self:render_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

Loading…
Cancel
Save