summaryrefslogtreecommitdiffstats
path: root/plugin/50-autocompletion.lua
blob: 19608719e7410f2a7e8c1717d5492f3ce6f86f41 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
--[[ 50-autocompletion.lua — automatic completion for insert and command-line mode.

In both modes the menu shows up automatically and `<Tab>`/`<S-Tab>` select the next/previous item.
In command-line mode `<Tab>` accepts a lone match outright and `<C-y>` accepts the selection (or
dismisses the menu when nothing is selected).

Keymaps:
  `<Tab>` / `<S-Tab>`   (insert) select next / previous menu item
  `<Tab>`               (cmdline) accept a lone match, else select next
  `<C-y>`               (cmdline) accept the selection, else dismiss
]]

----------------------------------------------------------------------------------------------------
-- Insert mode -------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

local function i_select_next_menu_item()
	return vim.fn.pumvisible() == 1 and "<C-n>" or "<Tab>"
end

local function i_select_previous_menu_item()
	return vim.fn.pumvisible() == 1 and "<C-p>" or "<S-Tab>"
end

----------------------------------------------------------------------------------------------------
-- Command-line mode -------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

--[[ `<Space><BS>` is a self-cancelling edit: it closes the menu, then `CmdlineChanged` fires and
tries to perform completion on the text before the cursor. ]]
local function c_insert_unique_or_wildtrigger()
	local complete_info = vim.fn.cmdcomplete_info()
	if complete_info.pum_visible == 1 then
		return #complete_info.matches == 1 and "<C-n><Space><BS>" or "<C-n>"
	end
	-- After navigating the command-line history, it takes two `wildtrigger()` calls to show the
	-- menu. The first is performed by the `CmdlineChanged` autocommand, the second is performed here.
	vim.fn.wildtrigger()
	return nil
end

local function c_accept_selection_or_dismiss()
	local complete_info = vim.fn.cmdcomplete_info()
	if complete_info.pum_visible == 1 then
		return complete_info.selected ~= -1 and "<Space><BS>" or "<C-e>"
	end
	return "<C-y>"
end

-- Show the command-line completion menu automatically.
-- Except for `:grep` commands (`:grep`, `:vimgrep`, their `add` and location-list variants).
local function trigger_wildmenu()
	if vim.fn.getcmdtype() == ":" then
		-- A command word followed by a delimiter (space, `/`, …) means we are past it, into the args.
		local cmd = vim.fn.getcmdline():match("^%s*(%a+)%A")
		if cmd and vim.fn.fullcommand(cmd):find("grep", 1, true) then
			if vim.fn.cmdcomplete_info().pum_visible == 1 then
				vim.api.nvim_feedkeys(vim.keycode("<C-e>"), "n", false)
			end
			return
		end
	end
	vim.fn.wildtrigger()
end

----------------------------------------------------------------------------------------------------
-- Settings ----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- Insert mode.
vim.opt.autocomplete = true -- Show the completion menu automatically.
--[[ Completion menu behaviour: do not preselect an item (`noselect`), show a menu even for a single
match (`menuone`), show extra info in a popup window (`popup`), and match fuzzily (`fuzzy`). ]]
vim.opt.completeopt = { "noselect", "menuone", "popup", "fuzzy" }
-- Completion sources, in order of priority: `omnifunc` (`o`).
vim.opt.complete = { "o" }

-- Command-line mode.
vim.opt.wildmenu = true -- Show completions in a menu.
vim.opt.wildchar = 9 -- Char code (`<Tab>`) assigned to command-line wildcard expansion.
--[[ Wildmenu behaviour: discard regex artifacts in search-pattern completion (`exacttext`), use a
popup menu (`pum`), show tag kind and file (`tagfile`), and match fuzzily except for files/dirs,
which use patterns (`fuzzy`). ]]
vim.opt.wildoptions = { "exacttext", "pum", "tagfile", "fuzzy" }
--[[ Completion modes triggered in order by `wildtrigger()` or `wildchar`: list matches without
inserting (`noselect`), then list matches and insert the first full match (`full`). ]]
vim.opt.wildmode = { "noselect", "full" }
-- Search recursively under the current directory so `:find` matches files in any subdirectory.
-- `:find` globs the tree synchronously with no timeout, so cap the depth (`**4`): the default `**`
-- depth of 30 walks all of `$HOME` when run from there and hangs.
vim.opt.path:append("**4")

----------------------------------------------------------------------------------------------------
-- Autocommands ------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- The `pattern` matches `:` commands plus `/` and `?` searches, but not `=` and `@`.
vim.api.nvim_create_autocmd({ "CmdlineChanged", "CmdlineEnter" }, {
	desc = "Autocompletion",
	group = vim.g.dotfiles.augroup,
	pattern = "[:\\/\\?]",
	callback = trigger_wildmenu,
})

----------------------------------------------------------------------------------------------------
-- Keymaps -----------------------------------------------------------------------------------------
----------------------------------------------------------------------------------------------------

-- Convert the `vim.o.wildchar` char code into a key string usable by `vim.keymap.set()`.
local wildchar_key = string.format("%c", vim.o.wildchar)

vim.keymap.set("i", "<Tab>", i_select_next_menu_item, { expr = true })
vim.keymap.set("i", "<S-Tab>", i_select_previous_menu_item, { expr = true })
vim.keymap.set("c", wildchar_key, c_insert_unique_or_wildtrigger, { expr = true })
vim.keymap.set("c", "<C-y>", c_accept_selection_or_dismiss, { expr = true })