Read-only mirror of https://codeberg.org/andyg/leap.nvim
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}