|
1 |
| -local util = require('autocomplete.util') |
2 |
| - |
3 | 1 | local M = {}
|
4 | 2 |
|
5 |
| -local state = { |
6 |
| - entry = nil, |
7 |
| - ns = { |
8 |
| - selection = nil, |
9 |
| - directory = nil, |
10 |
| - }, |
11 |
| - completion = { |
12 |
| - current = 0, |
13 |
| - last = nil, |
14 |
| - skip_next = false, |
15 |
| - noselect = false, |
16 |
| - menuone = false, |
17 |
| - data = {}, |
18 |
| - }, |
19 |
| - window = { |
20 |
| - id = nil, |
21 |
| - bufnr = nil, |
22 |
| - }, |
23 |
| -} |
24 |
| - |
25 |
| -local function open_win(height) |
26 |
| - if not state.window.bufnr or not vim.api.nvim_buf_is_valid(state.window.bufnr) then |
27 |
| - state.window.bufnr = vim.api.nvim_create_buf(false, true) |
28 |
| - end |
29 |
| - |
30 |
| - if not state.window.id or not vim.api.nvim_win_is_valid(state.window.id) then |
31 |
| - state.window.id = vim.api.nvim_open_win(state.window.bufnr, false, { |
32 |
| - relative = 'editor', |
33 |
| - border = M.config.border, |
34 |
| - style = 'minimal', |
35 |
| - width = vim.o.columns, |
36 |
| - height = height, |
37 |
| - row = vim.o.lines - 2, |
38 |
| - col = 0, |
39 |
| - }) |
40 |
| - end |
41 |
| -end |
42 |
| - |
43 |
| -local function close_win() |
44 |
| - if state.window.id and vim.api.nvim_win_is_valid(state.window.id) then |
45 |
| - vim.api.nvim_win_close(state.window.id, true) |
46 |
| - state.window.id = nil |
47 |
| - end |
48 |
| -end |
49 |
| - |
50 |
| -local function is_cmdline() |
51 |
| - return vim.fn.getcmdwintype() == '' and vim.v.event.cmdtype == ':' and vim.fn.mode() == 'c' |
52 |
| -end |
53 |
| - |
54 |
| -local function highlight_selection() |
55 |
| - if state.completion.current < 1 or state.completion.current > #state.completion.data then |
56 |
| - return |
57 |
| - end |
58 |
| - |
59 |
| - vim.api.nvim_buf_clear_namespace(state.window.bufnr, state.ns.selection, 0, -1) |
60 |
| - vim.highlight.range( |
61 |
| - state.window.bufnr, |
62 |
| - state.ns.selection, |
63 |
| - 'PmenuSel', |
64 |
| - state.completion.data[state.completion.current].start, |
65 |
| - state.completion.data[state.completion.current].finish, |
66 |
| - {} |
67 |
| - ) |
68 |
| -end |
69 |
| - |
70 |
| -local function update_cmdline(accept, reset) |
71 |
| - if vim.tbl_isempty(state.completion.data) then |
72 |
| - return |
73 |
| - end |
74 |
| - |
75 |
| - if state.completion.current > #state.completion.data then |
76 |
| - state.completion.current = 1 |
77 |
| - end |
78 |
| - if state.completion.current < 1 then |
79 |
| - state.completion.current = #state.completion.data |
80 |
| - end |
81 |
| - |
82 |
| - if accept or reset then |
83 |
| - if M.config.close_on_done then |
84 |
| - state.completion.skip_next = true |
85 |
| - close_win() |
86 |
| - end |
87 |
| - else |
88 |
| - state.completion.skip_next = not accept |
89 |
| - end |
90 |
| - |
91 |
| - if reset then |
92 |
| - vim.fn.setcmdline(state.completion.last) |
93 |
| - return |
94 |
| - end |
95 |
| - |
96 |
| - highlight_selection() |
97 |
| - vim.cmd('redraw') |
98 |
| - |
99 |
| - local commands = vim.split(vim.fn.getcmdline(), ' ') |
100 |
| - table.remove(commands, #commands) |
101 |
| - local new_cmdline = table.concat(commands, ' ') |
102 |
| - .. ' ' |
103 |
| - .. state.completion.data[state.completion.current].completion |
104 |
| - new_cmdline = vim.trim(new_cmdline) |
105 |
| - if accept and not M.config.close_on_done then |
106 |
| - new_cmdline = new_cmdline .. (vim.endswith(new_cmdline, '/') and '' or ' ') |
107 |
| - end |
108 |
| - |
109 |
| - vim.fn.setcmdline(new_cmdline) |
110 |
| -end |
111 |
| - |
112 |
| -local function cmdline_changed() |
113 |
| - -- We are currently updating command line via next/prev, so do not refresh it |
114 |
| - if state.completion.skip_next then |
115 |
| - state.completion.skip_next = false |
116 |
| - return |
117 |
| - end |
118 |
| - |
119 |
| - local window_height = M.config.rows |
120 |
| - if window_height < 1 then |
121 |
| - window_height = math.floor(vim.o.lines * window_height) |
122 |
| - end |
123 |
| - |
124 |
| - local col_width = math.floor(vim.o.columns / M.config.columns) |
125 |
| - |
126 |
| - -- Recreate window if we closed it before |
127 |
| - open_win(window_height) |
128 |
| - |
129 |
| - -- Clear window |
130 |
| - local window_data = {} |
131 |
| - for _ = 0, window_height do |
132 |
| - window_data[#window_data + 1] = (' '):rep(vim.o.columns) |
133 |
| - end |
134 |
| - vim.api.nvim_buf_set_lines(state.window.bufnr, 0, window_height, false, window_data) |
135 |
| - |
136 |
| - -- Get completions |
137 |
| - local input = vim.fn.getcmdline() |
138 |
| - local completions = vim.fn.getcompletion(input, 'cmdline') |
139 |
| - |
140 |
| - state.completion.last = input |
141 |
| - state.completion.data = {} |
142 |
| - state.completion.current = state.completion.noselect and 0 or 1 |
143 |
| - |
144 |
| - if #completions <= (state.completion.menuone and 0 or 1) then |
145 |
| - close_win() |
146 |
| - return |
147 |
| - end |
148 |
| - |
149 |
| - local completion_data = {} |
150 |
| - |
151 |
| - local i = 1 |
152 |
| - for line = 0, window_height - 1 do |
153 |
| - for col = 0, math.floor(vim.o.columns / col_width) - 1 do |
154 |
| - if i > #completions then |
155 |
| - break |
156 |
| - end |
157 |
| - |
158 |
| - local completion = completions[i] |
159 |
| - local is_directory = vim.fn.isdirectory(vim.fn.fnamemodify(completion, ':p')) == 1 |
160 |
| - |
161 |
| - local shortened = vim.fn.fnamemodify(completion, ':p:t') |
162 |
| - if shortened == '' then |
163 |
| - shortened = vim.fn.fnamemodify(completion, ':p:h:t') |
164 |
| - end |
165 |
| - if is_directory then |
166 |
| - shortened = shortened .. '/' |
167 |
| - end |
168 |
| - shortened = ' ' .. shortened .. ' ' |
169 |
| - |
170 |
| - if string.len(shortened) >= col_width then |
171 |
| - shortened = string.sub(shortened, 1, col_width - 4) .. '...' |
172 |
| - end |
173 |
| - |
174 |
| - local end_col = col * col_width + string.len(shortened) |
175 |
| - if end_col > vim.o.columns then |
176 |
| - break |
177 |
| - end |
178 |
| - |
179 |
| - vim.api.nvim_buf_set_text( |
180 |
| - state.window.bufnr, |
181 |
| - line, |
182 |
| - col * col_width, |
183 |
| - line, |
184 |
| - end_col, |
185 |
| - { shortened } |
186 |
| - ) |
187 |
| - |
188 |
| - local data = { |
189 |
| - start = { line, col * col_width }, |
190 |
| - finish = { line, end_col }, |
191 |
| - completion = completion, |
192 |
| - } |
193 |
| - |
194 |
| - completion_data[i] = data |
195 |
| - if is_directory then |
196 |
| - vim.highlight.range( |
197 |
| - state.window.bufnr, |
198 |
| - state.ns.directory, |
199 |
| - 'Directory', |
200 |
| - data.start, |
201 |
| - data.finish, |
202 |
| - {} |
203 |
| - ) |
204 |
| - end |
205 |
| - |
206 |
| - i = i + 1 |
207 |
| - end |
208 |
| - end |
209 |
| - |
210 |
| - state.completion.data = completion_data |
211 |
| - |
212 |
| - vim.api.nvim_win_set_height( |
213 |
| - state.window.id, |
214 |
| - math.min(math.floor(#completions / (math.floor(vim.o.columns / col_width))), window_height) |
215 |
| - ) |
216 |
| - highlight_selection() |
217 |
| - vim.cmd('redraw') |
218 |
| -end |
219 |
| - |
220 |
| -local function changed_handler() |
221 |
| - if not is_cmdline() then |
222 |
| - close_win() |
223 |
| - return |
224 |
| - end |
225 |
| - |
226 |
| - util.debounce(state.entry, M.config.debounce_delay, cmdline_changed) |
227 |
| -end |
| 3 | +function M.setup() |
| 4 | + local term = vim.api.nvim_replace_termcodes('<C-z>', true, true, true) |
228 | 5 |
|
229 |
| -local function enter_handler() |
230 |
| - if not is_cmdline() then |
231 |
| - close_win() |
232 |
| - return |
233 |
| - end |
234 |
| - |
235 |
| - state.completion.menuone = string.find(vim.o.completeopt, 'menuone') |
236 |
| - state.completion.noselect = string.find(vim.o.completeopt, 'noselect') |
237 |
| - changed_handler() |
238 |
| -end |
239 |
| - |
240 |
| -local function leave_handler() |
241 |
| - util.stop(state.entry) |
242 |
| - state.completion.data = {} |
243 |
| - state.completion.last = nil |
244 |
| - close_win() |
245 |
| -end |
| 6 | + vim.opt.wildmenu = true |
| 7 | + vim.opt.wildmode = 'noselect:lastused,full' |
| 8 | + vim.opt.wildcharm = vim.fn.char2nr(term) |
246 | 9 |
|
247 |
| -M.config = { |
248 |
| - mappings = { |
249 |
| - accept = '<C-y>', |
250 |
| - reject = '<C-e>', |
251 |
| - complete = '<C-space>', |
252 |
| - next = '<C-n>', |
253 |
| - previous = '<C-p>', |
254 |
| - }, |
255 |
| - border = nil, -- Cmdline completion border style |
256 |
| - columns = 5, -- Number of columns per row |
257 |
| - rows = 0.3, -- Number of rows, if < 1 then its fraction of total vim lines, if > 1 then its absolute number |
258 |
| - close_on_done = true, -- Close completion window when done (accept/reject) |
259 |
| - debounce_delay = 100, |
260 |
| -} |
| 10 | + vim.keymap.set('c', '<Up>', '<End><C-U><Up>', { silent = true }) |
| 11 | + vim.keymap.set('c', '<Down>', '<End><C-U><Down>', { silent = true }) |
261 | 12 |
|
262 |
| -function M.setup(config) |
263 |
| - M.config = vim.tbl_deep_extend('force', M.config, config or {}) |
264 |
| - |
265 |
| - -- Wild menu needs to be always disabled otherwise its in background for no reason |
266 |
| - vim.o.wildmenu = false |
267 |
| - |
268 |
| - state.entry = util.entry() |
269 |
| - state.ns.selection = vim.api.nvim_create_namespace('CmdlineCompletionSelection') |
270 |
| - state.ns.directory = vim.api.nvim_create_namespace('CmdlineCompletionDirectory') |
271 |
| - |
272 |
| - if M.config.mappings.accept then |
273 |
| - vim.keymap.set('c', M.config.mappings.accept, function() |
274 |
| - update_cmdline(true) |
275 |
| - end, { desc = 'Accept cmdline completion' }) |
276 |
| - end |
277 |
| - if M.config.mappings.reject then |
278 |
| - vim.keymap.set('c', M.config.mappings.reject, function() |
279 |
| - update_cmdline(false, true) |
280 |
| - end, { desc = 'Reject cmdline completion' }) |
281 |
| - end |
282 |
| - if M.config.mappings.complete then |
283 |
| - vim.keymap.set('c', M.config.mappings.complete, function() |
284 |
| - cmdline_changed() |
285 |
| - end, { desc = 'Force complete cmdline completion' }) |
286 |
| - end |
287 |
| - if M.config.mappings.next then |
288 |
| - vim.keymap.set('c', M.config.mappings.next, function() |
289 |
| - state.completion.current = state.completion.current + 1 |
290 |
| - update_cmdline() |
291 |
| - end, { desc = 'Next cmdline completion' }) |
292 |
| - end |
293 |
| - if M.config.mappings.previous then |
294 |
| - vim.keymap.set('c', M.config.mappings.previous, function() |
295 |
| - state.completion.current = state.completion.current - 1 |
296 |
| - update_cmdline() |
297 |
| - end, { desc = 'Previous cmdline completion' }) |
298 |
| - end |
299 |
| - |
300 |
| - local group = vim.api.nvim_create_augroup('CmdlineCompletion', {}) |
301 |
| - vim.api.nvim_create_autocmd('CmdlineEnter', { |
302 |
| - desc = 'Setup cmdline completion handlers', |
303 |
| - group = group, |
304 |
| - callback = enter_handler, |
305 |
| - }) |
306 |
| - vim.api.nvim_create_autocmd({ 'CmdlineLeave', 'CmdWinEnter' }, { |
307 |
| - desc = 'Tear down cmdline completion handlers', |
308 |
| - group = group, |
309 |
| - callback = leave_handler, |
310 |
| - }) |
311 | 13 | vim.api.nvim_create_autocmd('CmdlineChanged', {
|
312 |
| - desc = 'Auto update cmdline completion', |
313 |
| - group = group, |
314 |
| - callback = changed_handler, |
| 14 | + pattern = ':', |
| 15 | + callback = function() |
| 16 | + local cmdline = vim.fn.getcmdline() |
| 17 | + local curpos = vim.fn.getcmdpos() |
| 18 | + local last_char = cmdline:sub(-1) |
| 19 | + |
| 20 | + if |
| 21 | + curpos == #cmdline + 1 |
| 22 | + and vim.fn.pumvisible() == 0 |
| 23 | + and last_char:match('[%w%/%: ]') |
| 24 | + and not cmdline:match('^%d+$') |
| 25 | + then |
| 26 | + vim.opt.eventignore:append('CmdlineChanged') |
| 27 | + vim.api.nvim_feedkeys(term, 'ti', false) |
| 28 | + vim.schedule(function() |
| 29 | + vim.opt.eventignore:remove('CmdlineChanged') |
| 30 | + end) |
| 31 | + end |
| 32 | + end, |
315 | 33 | })
|
316 | 34 | end
|
317 | 35 |
|
|
0 commit comments