如何让nvim-cmp自动补全按Tab时选中建议项?
关于nvim-cmp Tab键选中补全建议的配置调整
现有配置
return { 'hrsh7th/nvim-cmp', event = 'InsertEnter', dependencies = { 'hrsh7th/cmp-buffer', 'hrsh7th/cmp-path', 'hrsh7th/cmp-nvim-lsp', { 'L3MON4D3/LuaSnip', version = 'v2.*', build = 'make install_jsregexp', }, 'saadparwaiz1/cmp_luasnip', 'rafamadriz/friendly-snippets', 'onsails/lspkind.nvim', }, config = function() local cmp = require('cmp') local luasnip = require('luasnip') local lspkind = require('lspkind') require('luasnip.loaders.from_vscode').lazy_load() -- Disable preselection at LSP capabilities level local capabilities = require('cmp_nvim_lsp').default_capabilities() capabilities.completion.completionItem.preselectSupported = false cmp.setup({ preselect = cmp.PreselectMode.None, completion = { completeopt = 'menu,menuone,preview,noselect,noinsert' }, snippet = { expand = function(args) luasnip.lsp_expand(args.body) end, }, mapping = { ['<C-n>'] = cmp.mapping.select_next_item( { behavior = cmp.SelectBehavior.Select }, { desc = 'Select next item' } ), ['<C-p>'] = cmp.mapping.select_prev_item( { behavior = cmp.SelectBehavior.Select }, { desc = 'Select previous item' } ), ['<C-b>'] = cmp.mapping.scroll_docs(-4, { desc = 'Scroll docs up' }), ['<C-f>'] = cmp.mapping.scroll_docs(4, { desc = 'Scroll docs down' }), ['<C-Space>'] = cmp.mapping.complete({ desc = 'Trigger completion' }), ['<C-e>'] = cmp.mapping.abort({ desc = 'Abort completion' }), ['<CR>'] = cmp.mapping(function(fallback) if cmp.visible() and cmp.get_selected_entry() then cmp.confirm({ select = false }) else fallback() end end, { 'i', 's', desc = 'Confirm selection' }), ['<Tab>'] = cmp.mapping(function(fallback) if cmp.visible() then cmp.select_next_item() else fallback() end end, { 'i', 's', desc = 'Next item or indent' }), ['<S-Tab>'] = cmp.mapping(function(fallback) if cmp.visible() then cmp.select_prev_item() else fallback() end end, { 'i', 's', desc = 'Previous item or dedent' }), }, sources = cmp.config.sources({ { name = 'nvim_lsp' }, { name = 'luasnip' }, { name = 'buffer' }, { name = 'path' }, }), formatting = { format = lspkind.cmp_format({ maxwidth = 50, ellipsis_char = '...', }), }, }) -- Automatically deselect first item when menu opens cmp.event:on('menu_opened', function() vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Up>', true, true, true), 'n', true) end) -- Pass capabilities to your LSP servers (add this to your lspconfig setup) -- Example: require("lspconfig").lua_ls.setup({ capabilities = capabilities }) end, }
已尝试操作
- 在
completion配置中添加noinsert参数 - 自定义回车键映射规则
- 绑定
menu_opened事件,自动按<Up>取消初始预选
需求
实现自动补全菜单弹出时,按Tab(或其他按键)选中补全建议项,支持切换选择。
调整方案
方案1:保留无预选初始状态,Tab优先选中第一个项
修改<Tab>和<S-Tab>的映射逻辑,当补全菜单可见但无选中项时,直接选中第一个/最后一个项,同时兼容LuaSnip的展开/跳转:
['<Tab>'] = cmp.mapping(function(fallback) if cmp.visible() then -- 无选中项时先选中第一个,否则切换到下一个 if not cmp.get_selected_entry() then cmp.select_next_item({ behavior = cmp.SelectBehavior.Select }) else cmp.select_next_item({ behavior = cmp.SelectBehavior.Select }) end elseif luasnip.expand_or_jumpable() then luasnip.expand_or_jump() else fallback() end end, { 'i', 's', desc = 'Select next item or expand snippet' }), ['<S-Tab>'] = cmp.mapping(function(fallback) if cmp.visible() then -- 无选中项时先选中最后一个,否则切换到上一个 if not cmp.get_selected_entry() then cmp.select_prev_item({ behavior = cmp.SelectBehavior.Select }) else cmp.select_prev_item({ behavior = cmp.SelectBehavior.Select }) end elseif luasnip.jumpable(-1) then luasnip.jump(-1) else fallback() end end, { 'i', 's', desc = 'Select previous item or jump back snippet' }),
方案2:移除自动取消预选逻辑,Tab直接从首个项切换
如果不需要初始无预选的状态,可删除menu_opened事件的绑定代码:
-- 删除这段代码 -- cmp.event:on('menu_opened', function() -- vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Up>', true, true, true), 'n', true) -- end)
此时补全菜单弹出时默认选中第一个项,按Tab直接切换到下一个,<S-Tab>切换到上一个。
额外优化:选中时自动插入文本
若希望选中补全项时直接将内容插入缓冲区,可将cmp.SelectBehavior.Select改为cmp.SelectBehavior.Insert:
cmp.select_next_item({ behavior = cmp.SelectBehavior.Insert })
最终完整配置(方案1示例)
return { 'hrsh7th/nvim-cmp', event = 'InsertEnter', dependencies = { 'hrsh7th/cmp-buffer', 'hrsh7th/cmp-path', 'hrsh7th/cmp-nvim-lsp', { 'L3MON4D3/LuaSnip', version = 'v2.*', build = 'make install_jsregexp', }, 'saadparwaiz1/cmp_luasnip', 'rafamadriz/friendly-snippets', 'onsails/lspkind.nvim', }, config = function() local cmp = require('cmp') local luasnip = require('luasnip') local lspkind = require('lspkind') require('luasnip.loaders.from_vscode').lazy_load() -- Disable preselection at LSP capabilities level local capabilities = require('cmp_nvim_lsp').default_capabilities() capabilities.completion.completionItem.preselectSupported = false cmp.setup({ preselect = cmp.PreselectMode.None, completion = { completeopt = 'menu,menuone,preview,noselect,noinsert' }, snippet = { expand = function(args) luasnip.lsp_expand(args.body) end, }, mapping = { ['<C-n>'] = cmp.mapping.select_next_item( { behavior = cmp.SelectBehavior.Select }, { desc = 'Select next item' } ), ['<C-p>'] = cmp.mapping.select_prev_item( { behavior = cmp.SelectBehavior.Select }, { desc = 'Select previous item' } ), ['<C-b>'] = cmp.mapping.scroll_docs(-4, { desc = 'Scroll docs up' }), ['<C-f>'] = cmp.mapping.scroll_docs(4, { desc = 'Scroll docs down' }), ['<C-Space>'] = cmp.mapping.complete({ desc = 'Trigger completion' }), ['<C-e>'] = cmp.mapping.abort({ desc = 'Abort completion' }), ['<CR>'] = cmp.mapping(function(fallback) if cmp.visible() and cmp.get_selected_entry() then cmp.confirm({ select = false }) else fallback() end end, { 'i', 's', desc = 'Confirm selection' }), ['<Tab>'] = cmp.mapping(function(fallback) if cmp.visible() then if not cmp.get_selected_entry() then cmp.select_next_item({ behavior = cmp.SelectBehavior.Select }) else cmp.select_next_item({ behavior = cmp.SelectBehavior.Select }) end elseif luasnip.expand_or_jumpable() then luasnip.expand_or_jump() else fallback() end end, { 'i', 's', desc = 'Select next item or expand snippet' }), ['<S-Tab>'] = cmp.mapping(function(fallback) if cmp.visible() then if not cmp.get_selected_entry() then cmp.select_prev_item({ behavior = cmp.SelectBehavior.Select }) else cmp.select_prev_item({ behavior = cmp.SelectBehavior.Select }) end elseif luasnip.jumpable(-1) then luasnip.jump(-1) else fallback() end end, { 'i', 's', desc = 'Select previous item or jump back snippet' }), }, sources = cmp.config.sources({ { name = 'nvim_lsp' }, { name = 'luasnip' }, { name = 'buffer' }, { name = 'path' }, }), formatting = { format = lspkind.cmp_format({ maxwidth = 50, ellipsis_char = '...', }), }, }) -- Automatically deselect first item when menu opens cmp.event:on('menu_opened', function() vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes('<Up>', true, true, true), 'n', true) end) -- Pass capabilities to your LSP servers (add this to your lspconfig setup) -- Example: require("lspconfig").lua_ls.setup({ capabilities = capabilities }) end, }
内容的提问来源于stack exchange,提问作者Bruce
相关产品推荐
相关产品推荐

