--[[ 50-autocompletion.lua — automatic completion for insert and command-line mode. In both modes the menu shows up automatically and ``/`` select the next/previous item. In command-line mode `` accepts a lone match outright and `` accepts the selection (or dismisses the menu when nothing is selected). Keymaps: `` / `` (insert) select next / previous menu item `` (cmdline) accept a lone match, else select next `` (cmdline) accept the selection, else dismiss ]] ---------------------------------------------------------------------------------------------------- -- Insert mode ------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- local function i_select_next_menu_item() return vim.fn.pumvisible() == 1 and "" or "" end local function i_select_previous_menu_item() return vim.fn.pumvisible() == 1 and "" or "" end ---------------------------------------------------------------------------------------------------- -- Command-line mode ------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------------- --[[ `` 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 "" or "" 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 "" or "" end return "" 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(""), "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 (``) 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", "", i_select_next_menu_item, { expr = true }) vim.keymap.set("i", "", 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_accept_selection_or_dismiss, { expr = true })