diff --git a/examples/lua/keymaps.lua b/examples/lua/keymaps.lua index 895527a..7f39edd 100644 --- a/examples/lua/keymaps.lua +++ b/examples/lua/keymaps.lua @@ -13,18 +13,31 @@ local fooModeKeymaps = j = 'norm j', k = 'norm k', l = 'norm l', + G = function(self) local count = self.count:get() vim.api.nvim_command('norm! ' .. count .. 'G') end, + + d = 'delete', + e = 'edit foo', + o = 'norm o', + p = 'bp', + zf = 'split', zfc = 'q', zff = split_twice, zfo = 'vsplit', - e = 'edit foo', - p = 'bp', - o = 'norm o', } +-- show that events work as expected +local id = vim.api.nvim_create_autocmd( + { 'CursorMoved', 'CursorMovedI', 'TextChanged', 'TextChangedI', 'TextChangedP', 'TextChangedT' }, + { callback = function(ev) vim.notify(vim.inspect(ev)) end } +) + -- enter the mode using the keymaps libmodal.mode.enter('FOO', fooModeKeymaps) + +-- remove setup +vim.api.nvim_del_autocmd(id) diff --git a/lua/libmodal/Mode.lua b/lua/libmodal/Mode.lua index bf97596..99e2e51 100644 --- a/lua/libmodal/Mode.lua +++ b/lua/libmodal/Mode.lua @@ -5,6 +5,8 @@ local utils = require 'libmodal.utils' --- @type libmodal.utils --- @alias CursorPosition {[1]: integer, [2]: integer} see `nvim_win_get_cursor` --- @class libmodal.Mode +--- @field private autocmds integer[] +--- @field private changedtick integer --- @field private cursor CursorPosition --- @field private flush_input_timer unknown --- @field private help? libmodal.utils.Help @@ -16,24 +18,69 @@ local utils = require 'libmodal.utils' --- @type libmodal.utils --- @field private name string --- @field private popups libmodal.collections.Stack --- @field private supress_exit boolean ---- @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 ''] = true, +local C_v = utils.api.replace_termcodes '' +local C_s = utils.api.replace_termcodes '' + +--- Event groups organized by modes +local EVENTS_BY_MODE = { + --- Cursor events triggered by which modes + CURSOR_MOVED = { + CursorMoved = { + n = true, + nt = true, + ntT = true, + s = true, + S = true, + [C_s] = true, + v = true, + V = true, + [C_v] = true, + vs = true, + Vs = true, + [C_v .. 's'] = true, + }, + + CursorMovedI = { + i = true, + niI = true, + niR = true, + R = true, + Rv = true, + }, }, - CursorMovedI = { - i = true, - R = true, + TEXT_CHANGED = { + TextChanged = { + n = true, + nt = true, + ntT = true, + }, + + TextChangedI = { + i = true, + niI = true, + niR = true, + R = true, + Rv = true, + }, + + TextChangedP = { + ic = true, + ix = true, + Rc = true, + Rvc = true, + Rx = true, + Rvx = true, + }, + + TextChangedT = { + t = true, + }, }, } @@ -57,6 +104,18 @@ local ZERO = string.byte(0) --- Byte for 9 local NINE = string.byte(9) +--- Execute events depending on current mode +--- @param events_by_mode {[string]: {[string]: true}} +local function execute_event_by_mode(events_by_mode) + local mode = vim.api.nvim_get_mode().mode + for event, modes in pairs(events_by_mode) do + if modes[mode] then + vim.api.nvim_exec_autocmds(event, {}) + break + end + end +end + --- execute the `instruction`. --- @private --- @param instruction fun(libmodal.Mode)|string a Lua function or Vimscript command. @@ -70,6 +129,7 @@ function Mode:execute_instruction(instruction) self.count:set(0) self:render_virtual_cursor(0, true) + self:execute_text_changed_events() end --- check the user's input against the `self.instruction` mappings to see if there is anything to execute. @@ -133,7 +193,24 @@ end --- @private function Mode:clear_virtual_cursor(bufnr) vim.api.nvim_buf_clear_namespace(bufnr, NS.CURSOR, 0, -1); +end +--- Runs CursorMoved* events, if applicable +--- @param cursor CursorPosition the current cursor position +function Mode:execute_cursor_moved_events(cursor) + if not vim.deep_equal(self.cursor, cursor) then + execute_event_by_mode(EVENTS_BY_MODE.CURSOR_MOVED) + self.cursor = cursor + end +end + +--- Runs TextChanged* events, if applicable +function Mode:execute_text_changed_events() + local changedtick = vim.api.nvim_buf_get_changedtick(0) + if self.changedtick ~= changedtick then + execute_event_by_mode(EVENTS_BY_MODE.TEXT_CHANGED) + self.changedtick = changedtick + end end --- enter this mode. @@ -156,15 +233,26 @@ function Mode:enter() 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, - }) + self.autocmds = { + vim.api.nvim_create_autocmd('BufLeave', { + callback = function(ev) + local bufnr = ev.buf + self:clear_virtual_cursor(bufnr) + end, + group = augroup, + }), + + vim.api.nvim_create_autocmd('BufEnter', { + callback = function(ev) + local bufnr = ev.buf + self.changedtick = vim.api.nvim_buf_get_changedtick(bufnr) + end, + }); + } end + self.changedtick = vim.api.nvim_buf_get_changedtick(0) + self.previous_mode_name = vim.g.libmodalActiveModeName vim.g.libmodalActiveModeName = self.name @@ -253,16 +341,7 @@ function Mode:render_virtual_cursor(winid, clear) 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 + self:execute_cursor_moved_events(cursor) end --- show the mode indicator, if it is enabled @@ -294,7 +373,9 @@ function Mode:tear_down() 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) + for _, autocmd in ipairs(self.autocmds) do + vim.api.nvim_del_autocmd(autocmd) + end self.cursor = nil if type(self.instruction) == 'table' then