Read-only mirror of https://codeberg.org/andyg/leap.nvim
at main 152 lines 5.5 kB view raw
1local api = vim.api 2 3local function action(kwargs) 4 kwargs = kwargs or {} 5 local jumper = kwargs.jumper 6 local input = kwargs.input 7 local use_count = kwargs.count ~= false 8 9 local state = { 10 args = kwargs, 11 -- `jumper` can mess with these below. 12 mode = vim.fn.mode(1), 13 count = vim.v.count, 14 register = vim.v.register 15 } 16 17 local src_win = vim.fn.win_getid() 18 local saved_view = vim.fn.winsaveview() 19 -- Set an extmark as an anchor, so that we can execute remote delete 20 -- commands in the backward direction, and move together with the text. 21 local anch_ns = api.nvim_create_namespace('') 22 local anch_id = api.nvim_buf_set_extmark( 23 0, anch_ns, saved_view.lnum - 1, saved_view.col, {} 24 ) 25 26 jumper = jumper or function() 27 -- We are back in Normal mode when this call is executed, so _we_ 28 -- should tell Leap whether it is OK to autojump. 29 -- If `input` is given, all bets are off - before moving on to a 30 -- labeled target, we would have to undo whatever action was taken 31 -- (practically impossible). 32 -- Visual and Operator-pending mode are also problematic, because 33 -- we could only re-trigger them inside the `leap()` call, with a 34 -- custom action, and that would prevent users from customizing 35 -- the jumper. 36 local no_autojump = (input and #input > 0) or state.mode ~= 'n' 37 require('leap').leap { 38 windows = require('leap.user').get_focusable_windows(), 39 opts = no_autojump and { safe_labels = '' } or nil, 40 } 41 end 42 43 local function to_normal_mode() 44 if state.mode:match('^[vV\22]') then 45 api.nvim_feedkeys(state.mode, 'n', false) 46 elseif state.mode:match('o') then 47 -- I'm just cargo-culting this TBH, but the combination of 48 -- the two indeed seems to work reliably. 49 api.nvim_feedkeys(vim.keycode('<C-\\><C-N>'), 'nx', false) 50 api.nvim_feedkeys(vim.keycode('<esc>'), 'n', false) 51 end 52 end 53 54 local function back_to_pending_action() 55 if state.mode:match('^[vV\22]') then 56 api.nvim_feedkeys(state.mode, 'n', false) 57 elseif state.mode:match('o') then 58 local count = (use_count and state.count > 0) and state.count or '' 59 local register = '"' .. state.register 60 local op = vim.v.operator 61 local force = state.mode:sub(3) 62 api.nvim_feedkeys(count .. register .. op .. force, 'n', false) 63 end 64 end 65 66 local function cursor_moved() 67 return vim.fn.win_getid() ~= src_win 68 or vim.fn.line('.') ~= saved_view.lnum 69 or vim.fn.col('.') ~= saved_view.col + 1 70 end 71 72 local function restore_cursor() 73 if vim.fn.win_getid() ~= src_win then 74 api.nvim_set_current_win(src_win) 75 end 76 vim.fn.winrestview(saved_view) 77 local anch_pos = api.nvim_buf_get_extmark_by_id(0, anch_ns, anch_id, {}) 78 api.nvim_win_set_cursor(0, { anch_pos[1] + 1, anch_pos[2] }) 79 api.nvim_buf_clear_namespace(0, anch_ns, 0, -1) 80 end 81 82 local function register_followup_actions() 83 local action_canceled = false 84 -- Register "cancel" keys. 85 local listener_id = vim.on_key(function(key, _) 86 if key == vim.keycode('<esc>') or key == vim.keycode('<c-c>') then 87 action_canceled = true 88 end 89 end) 90 api.nvim_create_autocmd('ModeChanged', { 91 pattern = '*:*', 92 once = true, 93 callback = function () 94 local is_change_op = vim.fn.mode(1):match('o') and (vim.v.operator == 'c') 95 api.nvim_create_autocmd('ModeChanged', { 96 pattern = is_change_op and 'i:n' or '*:n', 97 once = true, 98 callback = vim.schedule_wrap(function() 99 restore_cursor() 100 vim.on_key(nil, listener_id) -- remove listener 101 if not action_canceled then 102 api.nvim_exec_autocmds('User', { 103 pattern = 'RemoteOperationDone', 104 data = state 105 }) 106 end 107 end) 108 }) 109 end 110 }) 111 end 112 113 local function after_jump() 114 if not cursor_moved() then return end 115 -- Add target postion to jumplist. 116 vim.cmd('norm! m`') 117 back_to_pending_action() -- (feedkeys...) 118 -- No 'n' flag, custom mappings should work here. 119 if input then api.nvim_feedkeys(input, '', false) end 120 -- Wait for `feedkeys`. 121 vim.schedule(register_followup_actions) 122 end 123 124 -- Execute "spooky" action: jump - operate - restore. 125 126 to_normal_mode() -- (feedkeys...) 127 -- Wait for `feedkeys`. 128 vim.schedule(function() 129 if type(jumper) == 'function' then 130 jumper() 131 -- Wait for `jumper` to finish its business. 132 vim.schedule(function() after_jump() end) 133 elseif type(jumper) == 'string' then 134 -- API note: `jumper` could of course call `feedkeys` itself, 135 -- but then we would need an independent parameter that tells 136 -- whether to wait for `CmdlineLeave`. 137 api.nvim_feedkeys(jumper, 'n', false) 138 vim.schedule(function() 139 -- Wait for finishing the search command (autocmd), and then 140 -- for actually leaving the command line (schedule wrap). 141 api.nvim_create_autocmd('CmdlineLeave', { 142 once = true, 143 callback = vim.schedule_wrap(after_jump) 144 }) 145 end) 146 end 147 end) 148end 149 150return { 151 action = action 152}