Read-only mirror of https://codeberg.org/andyg/leap.nvim
at main 201 lines 7.1 kB view raw
1-- User-space convenience functions that rely only on the public API. 2 3--- Returns a table that can be used as or merged with `opts`, 4--- with `keys.next_target` and `keys.prev_target` set appropriately. 5local function with_traversal_keys(fwd_key, bwd_key, opts) 6 local function with(key, t) 7 return { type(t) == 'table' and t[1] or t, key } 8 end 9 10 local function without(key, t) -- `t` must be a table now 11 -- Iterate backwards so that we can safely remove the item while looping. 12 for i = #t, 1, -1 do 13 if vim.keycode(t[i]) == vim.keycode(key) then 14 table.remove(t, i) 15 end 16 end 17 return t 18 end 19 20 local keys = vim.deepcopy(require('leap').opts.keys) 21 22 return vim.tbl_deep_extend('error', opts or {}, { 23 keys = { 24 next_target = without(bwd_key, with(fwd_key, keys.next_target)), 25 prev_target = without(fwd_key, with(bwd_key, keys.prev_target)), 26 } 27 }) 28end 29 30--- @deprecated 31local function set_repeat_keys(fwd_key, bwd_key, kwargs) 32 kwargs = kwargs or {} 33 local modes = kwargs.modes or { 'n', 'x', 'o' } 34 local relative_dir = kwargs.relative_directions 35 36 local function leap_repeat(backward_invoc) 37 local leap = require('leap') 38 local backward = backward_invoc 39 if relative_dir then 40 if backward_invoc then 41 backward = not leap.state['repeat'].backward 42 else 43 backward = leap.state['repeat'].backward 44 end 45 end 46 local opts = { 47 -- Just overwrite the fields, one wouldn't want to switch to 48 -- another key after starting with one. 49 keys = vim.tbl_extend('force', leap.opts.keys, { 50 next_target = backward_invoc and bwd_key or fwd_key, 51 prev_target = backward_invoc and fwd_key or bwd_key, 52 }) 53 } 54 leap.leap { ['repeat'] = true, backward = backward, opts = opts } 55 end 56 57 vim.keymap.set(modes, fwd_key, function() leap_repeat(false) end, { 58 silent = true, 59 desc = 'Repeat leap ' 60 .. (relative_dir and 'in the previous direction' or 'forward') 61 }) 62 vim.keymap.set(modes, bwd_key, function() leap_repeat(true) end, { 63 silent = true, 64 desc = 'Repeat leap ' 65 .. (relative_dir and 'in the opposite direction' or 'backward') 66 }) 67end 68 69local function get_enterable_windows() 70 return vim.iter(vim.api.nvim_tabpage_list_wins(0)) 71 :filter(function(win) 72 local config = vim.api.nvim_win_get_config(win) 73 return config.focusable 74 -- Exclude auto-closing hover popups, e.g. diagnostics (#137). 75 and config.relative == "" 76 and win ~= vim.api.nvim_get_current_win() 77 end):totable() 78end 79 80local function get_focusable_windows() 81 return { vim.api.nvim_get_current_win(), unpack(get_enterable_windows()) } 82end 83 84local set_backdrop_highlight 85do 86 local function highlight_ranges_for_redraw_cycle(hl_group, ranges) 87 local ns = vim.api.nvim_create_namespace('') 88 89 for buf, range in pairs(ranges) do 90 vim.hl.range(buf, ns, hl_group, range[1], range[2]) 91 end 92 93 -- Set auto-cleanup. 94 vim.api.nvim_create_autocmd('User', { 95 pattern = { 'LeapRedraw', 'LeapLeave' }, 96 once = true, 97 callback = function() 98 for buf, range in pairs(ranges) do 99 if vim.api.nvim_buf_is_valid(buf) then 100 vim.api.nvim_buf_clear_namespace(buf, ns, range[1][1], range[2][1]) 101 end 102 -- Safety measure for scrolloff > 0: we always clean up 103 -- the current view too. 104 vim.api.nvim_buf_clear_namespace( 105 0, ns, vim.fn.line('w0') - 1, vim.fn.line('w$') 106 ) 107 end 108 end, 109 }) 110 -- When used as an autocmd callback, a truthy return value would 111 -- remove the autocommand (:h nvim_create_autocmd). 112 return nil 113 end 114 115 local function get_search_ranges() 116 local ranges = {} 117 local args = require('leap').state.args 118 local windows = args.windows or args.target_windows -- deprecated 119 120 if windows then 121 for _, win in ipairs(windows) do 122 local wininfo = vim.fn.getwininfo(win)[1] 123 local buf = wininfo.bufnr 124 ranges[buf] = { { wininfo.topline - 1, 0 }, { wininfo.botline - 1, -1 } } 125 end 126 else 127 local wininfo = vim.fn.getwininfo(vim.fn.win_getid())[1] 128 local buf = wininfo.bufnr 129 local curline = vim.fn.line('.') - 1 130 local curcol = vim.fn.col('.') - 1 131 if args.backward then 132 ranges[buf] = { { wininfo.topline - 1, 0 }, { curline, curcol } } 133 else 134 ranges[buf] = { { curline, curcol + 1 }, { wininfo.botline - 1, -1 } } 135 end 136 end 137 138 return ranges 139 end 140 141 --- Applies `hl_group` to all search ranges. Disabled on color scheme change. 142 set_backdrop_highlight = function(hl_group) 143 local group = vim.api.nvim_create_augroup('LeapBackdrop', {}) 144 local id = vim.api.nvim_create_autocmd('User', { 145 pattern = 'LeapRedraw', 146 group = group, 147 callback = function() 148 highlight_ranges_for_redraw_cycle(hl_group, get_search_ranges()) 149 end, 150 }) 151 vim.api.nvim_create_autocmd('ColorScheme', { 152 once = true, 153 group = group, 154 callback = function() vim.api.nvim_del_autocmd(id) end, 155 }) 156 end 157end 158 159--- @deprecated 160local function add_default_mappings(force) 161 for _, t in ipairs { 162 { { 'n', 'x', 'o' }, 's', '<Plug>(leap-forward-to)', 'Leap forward to' }, 163 { { 'n', 'x', 'o' }, 'S', '<Plug>(leap-backward-to)', 'Leap backward to' }, 164 { { 'x', 'o' }, 'x', '<Plug>(leap-forward-till)', 'Leap forward till' }, 165 { { 'x', 'o' }, 'X', '<Plug>(leap-backward-till)', 'Leap backward till' }, 166 { { 'n', 'x', 'o' }, 'gs', '<Plug>(leap-from-window)', 'Leap from window' }, 167 { { 'n', 'x', 'o' }, 'gs', '<Plug>(leap-cross-window)', 'Leap from window' }, 168 } do 169 local modes, lhs, rhs, desc = unpack(t) 170 for _, mode in ipairs(modes) do 171 -- If not forced, only set the keymaps if: 172 -- 1. A keyseq starting with `lhs` is not already mapped to 173 -- something else. 174 -- 2. There is no existing mapping to the <Plug> key. 175 if force or ( 176 vim.fn.mapcheck(lhs, mode) == '' and vim.fn.hasmapto(rhs, mode) == 0 177 ) then 178 vim.keymap.set(mode, lhs, rhs, { silent = true, desc = desc }) 179 end 180 end 181 end 182end 183 184local function setup(user_opts) 185 local opts = require('leap.opts').default 186 for k, v in pairs(user_opts) do 187 opts[k] = v 188 end 189end 190 191return { 192 with_traversal_keys = with_traversal_keys, 193 get_enterable_windows = get_enterable_windows, 194 get_focusable_windows = get_focusable_windows, 195 set_backdrop_highlight = set_backdrop_highlight, 196 -- deprecated -- 197 set_repeat_keys = set_repeat_keys, 198 add_repeat_mappings = set_repeat_keys, 199 add_default_mappings = add_default_mappings, 200 setup = setup, 201}