blink.cmp Recipes 实战指南:从按文件类型禁用补全到菜单绘制与排序调优
2026/9/17 22:43:50 网站建设 项目流程

blink.cmp Recipes 实战指南:从按文件类型禁用补全到菜单绘制与排序调优

【免费下载链接】blink.cmpPerformant, batteries-included completion plugin for Neovim项目地址: https://gitcode.com/GitHub_Trending/bl/blink.cmp

本文是 blink.cmp(Neovim 高性能补全插件)官方doc/recipes.md的深度解读与实战手册。文章围绕补全启用控制、触发行为、按键映射、模糊排序、源(Source)定制、菜单绘制与写作场景适配等七大类官方 Recipes,逐条给出可直接复制到配置中的代码示例,并结合仓库源码(如 menu.lua、trigger.lua、list.lua、presets.lua)讲解每个配置项的真实默认值与底层实现逻辑。读完你可以在自己的 blink.cmp 配置中按需组合这些方案,解决从"禁用补全"到"精确匹配优先"等一系列实际问题。

说明:本文所有配置片段默认位于require('blink.cmp').setup({ ... })的参数表中;文中代码来自 doc/recipes.md 原文档,并结合仓库源码补充默认值与实现依据。点击文末相关文件链接可跳转到仓库查看源码。

一、通用(General):控制补全的启用与显示时机

1.1 按文件类型 / 缓冲区禁用补全

blink.cmp 通过enabled配置项决定是否启用补全,它可以是一个返回布尔值的函数,任意返回false的分支都会关闭补全:

enabled = function() return not vim.tbl_contains({ "lua", "markdown" }, vim.bo.filetype) end,

上面的写法会在luamarkdown文件类型中完全禁用补全。除此之外,还可以通过设置缓冲区局部变量vim.b.completion = false实现更细粒度的控制:

-- 通过 autocmd 实现 vim.api.nvim_create_autocmd('BufEnter', { pattern = '*.lua', callback = function() vim.b.completion = false end, }) -- 或直接在 ftplugin/some-filetype.lua 中设置 vim.b.completion = false

vim.b.completion是 blink.cmp 读取的缓冲区级开关(变量名为b:completion),适合在特定文件类型、特定路径甚至特定缓冲区上关闭补全。两种方式可以混用:函数方式适合按文件类型统一控制,缓冲区变量适合精细到单个缓冲区的场景。

1.2 仅在 shell 命令模式下禁用 cmdline 补全

在 Windows 的 git bash 或 WSL 环境中,执行 shell 命令时可能会出现卡顿。下面的配置让 blink.cmp 只在执行 shell 命令(如:!:%!)时关闭 cmdline 补全,而保留::help/?等其他命令模式的补全能力:

sources = { providers = { cmdline = { -- 忽略执行 shell 命令时的 cmdline 补全 enabled = function() return vim.fn.getcmdtype() ~= ':' or not vim.fn.getcmdline():match("^[%%0-9,'<>%-]*!") end } } }

这段代码用vim.fn.getcmdtype()判断当前命令类型,用vim.fn.getcmdline()匹配以!(可带%、数字、'<'>,-等修饰)开头的命令行,从而精准跳过 shell 命令补全。从源码看,cmdline 源的定义位于 sources.lua 的默认 providers 中,enabled字段支持boolean | fun(): boolean两种取值(见 sources.lua 的类型定义)。

1.3 禁用或延迟自动弹出补全菜单

默认情况下,blink.cmp 在键入时会自动弹出补全菜单。通过completion.menu.auto_showcompletion.menu.auto_show_delay_ms可以禁用自动弹出或设置延迟:

completion = { menu = { -- 禁用自动弹出,改为手动按 <C-space>(默认键位)呼出 auto_show = false, -- 或按文件类型分别处理 auto_show = function(ctx, items) return vim.bo.filetype == 'markdown' end, -- 键入后延迟多少毫秒再弹出菜单 auto_show_delay_ms = 500, -- 或按文件类型分别处理 auto_show_delay_ms = function(ctx, items) return vim.bo.filetype == 'markdown' and 1000 or 0 end, } }

源码确认了这两个配置的默认值:auto_show = trueauto_show_delay_ms = 0(见 menu.lua),且两者都同时接受布尔/数值或函数形式(类型标注为boolean | fun(ctx, items): booleaninteger | fun(ctx, items): integer)。auto_show的函数形式可以拿到补全上下文ctx和候选列表items,因此可以实现"仅在 markdown 且候选数量达到阈值时才自动弹出"这类复杂逻辑。若设置延迟,菜单会在持续键入时按延迟节流,避免频繁弹窗。

1.4 Emacs 风格的 Tab 补全行为

想要 Emacs 编辑器那种"Tab 插入下一项候选、Shift+Tab 回退"的行为?官方 Recipes 提供了完整方案(讨论见仓库 issue #1367):

local has_words_before = function() local col = vim.api.nvim_win_get_cursor(0)[2] if col == 0 then return false end local line = vim.api.nvim_get_current_line() return line:sub(col, col):match("%s") == nil end -- 在 blink 配置中 keymap = { preset = 'none', -- 若补全尚未触发,插入第一个建议;否则循环到下一个建议 ['<Tab>'] = { function(cmp) if has_words_before() then return cmp.insert_next() end end, 'fallback', }, -- 回退到上一个建议;若当前是第一个则取消补全 ['<S-Tab>'] = { 'insert_prev' }, }, completion = { menu = { enabled = false }, list = { selection = { preselect = false }, cycle = { from_top = false } }, }

实现要点解读:

  • preset = 'none'清空默认键位,完全自定义映射。none预设的定义见 presets.lua。
  • cmp.insert_next()/cmp.insert_prev()auto_insert语义的命令:直接"插入"下一项/上一项候选而不弹出菜单。源码注释说明,insert_next在列表底部且completion.list.cycle.from_bottom == true时会循环到顶部,并且与select_next不同,它没有候选时也会触发补全而非 fallback(见 keymap.lua)。
  • completion.list.selection.preselect = false关闭默认的自动预选第一项(默认值为true,见 list.lua),这样 Tab 才能从"未选中"状态开始循环插入。
  • cycle.from_top = false关闭顶部循环(默认true),配合insert_prev在第一个候选时停止。

1.5 为所有浮动窗口设置边框

Neovim 0.11+ 提供了全局的vim.o.winborder选项,可为所有浮动窗口设置默认边框。blink.cmp 的菜单、文档窗口和签名帮助窗口都遵循该默认值,你也可以单独覆盖:

completion = { menu = { border = 'single' }, documentation = { window = { border = 'single' } }, }, signature = { window = { border = 'single' } },

从源码看,菜单的border字段支持table | string | nil三种取值(见 menu.lua),'single'即使用单线边框。值得注意的是,菜单的scrollbar配置注释明确指出:当border ~= 'none'时滚动条 gutter 会被禁用(见 menu.lua),因此设置了非'none'边框后菜单滚动条将不再显示。

1.6 直接选择列表中的第 N 项

基于官方 issue #382 的方案,用Alt+数字直接接受第 N 个候选:

keymap = { preset = 'default', ['<A-1>'] = { function(cmp) cmp.accept({ index = 1 }) end }, ['<A-2>'] = { function(cmp) cmp.accept({ index = 2 }) end }, ['<A-3>'] = { function(cmp) cmp.accept({ index = 3 }) end }, ['<A-4>'] = { function(cmp) cmp.accept({ index = 4 }) end }, ['<A-5>'] = { function(cmp) cmp.accept({ index = 5 }) end }, ['<A-6>'] = { function(cmp) cmp.accept({ index = 6 }) end }, ['<A-7>'] = { function(cmp) cmp.accept({ index = 7 }) end }, ['<A-8>'] = { function(cmp) cmp.accept({ index = 8 }) end }, ['<A-9>'] = { function(cmp) cmp.accept({ index = 9 }) end }, ['<A-0>'] = { function(cmp) cmp.accept({ index = 10 }) end }, }, completion = { menu = { draw = { columns = { { 'item_idx' }, { 'kind_icon' }, { 'label', 'label_description', gap = 1 } }, components = { item_idx = { text = function(ctx) return ctx.idx == 10 and '0' or ctx.idx >= 10 and ' ' or tostring(ctx.idx) end, highlight = 'BlinkCmpItemIdx' -- 可选:自定义该组件的颜色 } } } } }

这里用到了两个关键机制:

  1. cmp.accept({ index = n })accept命令的index选项指定接受第几个候选。源码中cmp.accept会优先取opts.index对应的completion_list.items[opts.index],否则回退到当前选中项(见 init.lua)。
  2. item_idx绘制组件:默认的菜单列并不包含序号列,这里通过completion.menu.draw.columns新增{ 'item_idx' }列,并用components.item_idx.text自定义渲染——第 10 项显示为0,超过 10 项显示空格(避免挤占布局),其余显示数字序号。draw.columns默认值为{ { 'kind_icon' }, { 'label', 'label_description', gap = 1 } },默认组件集可在 menu.lua 中查看(内置组件包括kind_iconkindlabellabel_descriptionsource_namesource_id等)。

1.7 无视觉反馈地接受补全(force选项)

默认情况下,accept只有在菜单或 ghost text 可见时才生效(源码中cmp.accept会先检查cmp.is_visible(),见 init.lua)。如果你希望在菜单和 ghost text 都不可见时也能直接接受候选(例如使用无菜单的 Emacs 风格工作流),可以给命令传入force = true。官方 Recipes 提供了三种写法(讨论见仓库 discussion #2304):

方案 1:先选中第一项(若未选中)再接受

['<C-y>'] = { function(cmp) return cmp.select_and_accept({ force = true }) end, 'fallback', }

方案 2:直接接受列表第一项

['<C-y>'] = { function(cmp) return cmp.accept({ index = 1, force = true }) end, 'fallback', }

方案 3:若只有一个候选则直接选中并接受,否则弹出菜单并选中第一项

['<C-y>'] = { function(cmp) return cmp.show_and_insert_or_accept_single({ force = true }) end, 'fallback', }

源码显示cmp.show_and_insert_or_accept_single的实现逻辑是:当候选列表被过滤到只剩一项(#list.items == 1)时直接list.accept({ index = 1 }),否则展示菜单(见 init.lua);而cmp.select_and_accept则在无选中项时先选中第一项再接受。原文档提示:如果你已经开启了completion.list.selection.preselect(默认即自动预选第一项),则方案 2 中的index选项可以省略。

1.8 弹出菜单时隐藏 Copilot 建议

如果同时使用 GitHub Copilot 的行内建议,可以在 blink.cmp 菜单打开/关闭时联动隐藏/恢复 Copilot 的 ghost text:

vim.api.nvim_create_autocmd('User', { pattern = 'BlinkCmpMenuOpen', callback = function() require("copilot.suggestion").dismiss() vim.b.copilot_suggestion_hidden = true end, }) vim.api.nvim_create_autocmd('User', { pattern = 'BlinkCmpMenuClose', callback = function() vim.b.copilot_suggestion_hidden = false end, })

blink.cmp 会在菜单打开/关闭时分别触发User事件BlinkCmpMenuOpenBlinkCmpMenuClose,这里利用 autocmd 监听这两个事件,配合copilot.suggestion的 Lua API 完成联动。注意require("copilot.suggestion")来自 Copilot 插件本身,需确认你安装的 Copilot 版本提供该 API。

1.9 避免多行 ghost text 与菜单重叠

开启completion.ghost_text.enabled = true后,ghost text 会预览将要插入的文本。当候选是包含换行的多行文本时,ghost text 可能和菜单发生重叠(效果可参考 nvim-cmp issue #1955 的示例)。官方 Recipes 提供了一段自定义completion.menu.direction_priority的解法:

completion = { menu = { direction_priority = function() local ctx = require('blink.cmp').get_context() local item = require('blink.cmp').get_selected_item() if ctx == nil or item == nil then return { 's', 'n' } end local item_text = item.textEdit ~= nil and item.textEdit.newText or item.insertText or item.label local is_multi_line = item_text:find('\n') ~= nil -- 菜单朝上弹出后,保持该方向直到菜单重新打开, -- 因此把上下文 id 存在全局变量中 if is_multi_line or vim.g.blink_cmp_upwards_ctx_id == ctx.id then vim.g.blink_cmp_upwards_ctx_id = ctx.id return { 'n', 's' } end return { 's', 'n' } end, }, },

原理说明:

  • direction_priority默认值为{ 's', 'n' }(先尝试在光标下方弹出,空间不足再弹到上方,见 menu.lua),同时支持函数形式动态返回方向优先级数组。
  • require('blink.cmp').get_context()get_selected_item()是 blink.cmp 的公开 API,分别返回当前补全上下文与选中项。
  • 当选中项文本包含\n(多行)时,把方向优先级改为{ 'n', 's' }(优先向上弹出),并用全局变量vim.g.blink_cmp_upwards_ctx_id记录上下文 id,确保菜单在向上弹出后、同一上下文内保持该方向,避免每次滚动都来回跳动。

1.10 在换行、Tab 和空格字符上触发补全

::: warning 该方案目前存在已知问题,表现不符合预期,详见仓库 issue #836。 :::

blink.cmp 默认会屏蔽(空格)、\n(换行)和\t(Tab)这三个触发字符——即在这些字符后不会自动弹出补全。默认的屏蔽列表show_on_blocked_trigger_characters = { ' ', '\n', '\t' }定义在 trigger.lua。如果需要在这些字符后也触发补全,可以这样配置:

-- 默认情况下 blink.cmp 会屏蔽换行、Tab 和空格触发字符,这里取消该行为 completion.trigger.show_on_blocked_trigger_characters = {} -- 将换行、Tab 和空格加入 LSP 源的触发字符 sources.providers.lsp.override.get_trigger_characters = function(self) local trigger_characters = self:get_trigger_characters() vim.list_extend(trigger_characters, { '\n', '\t', ' ' }) return trigger_characters end

官方特别提醒:由于 LSP 可能对这些字符不返回任何候选,你需要同时在其他源(如 buffer 源)上也做类似的override.get_trigger_characters,否则当 LSP 无结果时,即使按了这三个字符菜单也不会出现。sources.providers.<name>.override是 blink.cmp 提供的源函数覆盖机制(类型定义见 sources.lua),允许在不动源源码的前提下覆写其内部方法。

二、模糊匹配(Fuzzy):排序与过滤调优

2.1 始终优先精确匹配

blink.cmp 的模糊匹配器默认会给精确匹配额外 4 分的加成,但并非强制优先。若希望精确匹配无条件排在最前,可以在排序器中把'exact'提到最前面:

fuzzy = { sorts = { 'exact', -- 默认排序器 'score', 'sort_text', }, }

从 fuzzy.lua 可以看到,默认的sorts{ 'score', 'sort_text' },可用的内置排序器包括'label''sort_text''kind''score''exact'以及自定义函数。排序器依次执行,第一个返回非nil结果的决定顺序(见 sort.lua);sort.exact的实现即"两者 exact 标志不同则按 exact 优先"(见 sort.lua)。

2.2 降低特定 LSP 的优先级

可以传入自定义排序函数,把某些 LSP(如 Emmet Language Serveremmet_ls)的结果排到后面:

fuzzy = { sorts = { function(a, b) if (a.client_name == nil or b.client_name == nil) or (a.client_name == b.client_name) then return end return b.client_name == 'emmet_ls' end, -- 默认排序器 'score', 'sort_text', }, }

排序函数接收两个补全项ab,返回true(a 排在 b 前)、false(a 排在 b 后)或nil(交给下一个排序器决定)。这段逻辑是:当两者 client_name 不同且 b 是emmet_ls时返回true,即"任何非 emmet_ls 的项都排在 emmet_ls 之前"。sorts的类型定义("label" | "sort_text" | "kind" | "score" | "exact" | SortFunction)[]见 fuzzy.lua。

2.3 从补全结果中剔除关键字/常量

如果希望语言关键字(if、else、while 等)交给内置或自定义片段(snippet)来处理,而不是由 LSP 提供,可以通过transform_items过滤掉Keyword类型的候选:

sources = { providers = { lsp = { name = 'LSP', module = 'blink.cmp.sources.lsp', transform_items = function(_, items) return vim.tbl_filter(function(item) return item.kind ~= require('blink.cmp.types').CompletionItemKind.Keyword end, items) end, }, }, }

transform_items是源配置中用于在返回前改写候选列表的钩子(类型fun(ctx, items): items见 sources.lua)。require('blink.cmp.types').CompletionItemKind.Keyword对应 LSP 标准中Keyword种类。你可以按需扩展这个过滤条件(例如同时过滤ConstantEnum等)。

三、源(Sources):Provider 定制

3.1 buffer 源:从所有打开的缓冲区补全

默认情况下,buffer 源只从可见的"普通"缓冲区取词(例如不会包含 neo-tree 这类特殊缓冲区)。默认的get_bufnrs实现遍历所有窗口取缓冲区、并过滤掉buftype == 'nofile'的缓冲区(见 buffer/init.lua)。如果希望从所有缓冲区取词:

sources = { providers = { buffer = { opts = { -- 取所有缓冲区,包括 neo-tree 这类特殊缓冲区 get_bufnrs = vim.api.nvim_list_bufs -- 或(推荐)只过滤出"普通"缓冲区 get_bufnrs = function() return vim.tbl_filter(function(bufnr) return vim.bo[bufnr].buftype == '' end, vim.api.nvim_list_bufs()) end } } } }

需要注意:官方明确说明这种做法的性能影响未经测试——因为词量会显著增加,而 buffer 源受max_total_buffer_size(默认 500KB)等大小限制的约束(见 buffer/init.lua)。推荐使用第二种写法,通过vim.bo[bufnr].buftype == ''只保留常规文件缓冲区。

3.2 根据 Treesitter 节点 / 文件类型动态选择 Provider

blink.cmpsources.default既可以是一个 provider 名称列表,也可以是一个接收上下文的函数,从而根据当前代码环境动态决定启用哪些源。源码中甚至把该用法写进了类型注释作为示例(见 sources.lua):

sources.default = function(ctx) local success, node = pcall(vim.treesitter.get_node) if success and node and vim.tbl_contains({ 'comment', 'line_comment', 'block_comment' }, node:type()) then return { 'buffer' } elseif vim.bo.filetype == 'lua' then return { 'lsp', 'path' } else return { 'lsp', 'path', 'snippets', 'buffer' } end end

这段配置的效果:在注释节点中只启用 buffer 源(避免 LSP 在注释中打扰);在 Lua 文件中启用lsppath;其他文件类型使用默认的四源组合(lsppathsnippetsbuffer,见 sources.lua)。sources.default的类型定义为string[] | fun(): string[](见 sources.lua)。注意vim.treesitter.get_node在没有 treesitter 支持的文件中会报错,所以用pcall包裹。

3.3 触发字符后隐藏 snippets

触发字符由各源自行定义,例如 Lua 的触发字符是."'。若不想在触发字符出现时显示 snippet 候选(此时用户多半在访问成员或写字符串),可以用should_show_items控制:

sources.providers.snippets.should_show_items = function(ctx) return ctx.trigger.initial_kind ~= 'trigger_character' end

ctx.trigger.initial_kind表示本次补全的触发方式(如键入关键字触发、触发字符触发、手动触发等),should_show_items返回false时该 provider 的结果不显示(类型boolean | fun(ctx, items): boolean见 sources.lua)。

3.4 自定义源的图标与名称

第三方源(如 Copilot)默认可能没有合适的图标与名称,可以通过transform_items改写每个候选的kind_iconkind_name

sources.providers.copilot.transform_items = function(ctx, items) for _, item in ipairs(items) do item.kind_icon = '' item.kind_name = 'Copilot' end return items end

kind_iconkind_name是补全项渲染时使用的字段,配合菜单绘制组件kind_iconkind显示。注意是 GitHub 图标字形,需要 nerd font 支持(详见下文"禁用 nerd fonts"一节)。

3.5 禁用所有 snippets

完整的禁用方法见 snippets 配置文档 中的 "Disable all snippets" 小节,核心思路是将sources.providers.snippetsenabled设为false,或从sources.default列表中移除'snippets'

3.6 按文件类型设置最小关键字长度

min_keyword_length控制触发补全所需的最少字符数,支持按文件类型动态返回:

sources.min_keyword_length = function() return vim.bo.filetype == 'markdown' and 2 or 0 end

即 markdown 中至少输入 2 个字符才触发,其余文件类型 0 个字符即触发(默认值即0,见 sources.lua)。类型为integer | fun(ctx): integer。也可以在单个 provider 上设置min_keyword_length实现按源区分(见 sources.lua)。

3.7 path 源:从cwd而非当前缓冲区目录补全路径

path 源默认以当前缓冲区的父目录为基准生成相对路径(源码中默认get_cwd实现为vim.fn.expand(('#%d:p:h'):format(context.bufnr)),见 path/init.lua)。若你习惯在仓库根目录运行代码,可以改为基于当前工作目录:

sources = { providers = { path = { opts = { get_cwd = function(_) return vim.fn.getcwd() end, }, }, }, },

配置后,get_cwd返回vim.fn.getcwd()(当前工作目录),路径补全将以此为基准。这也让通过:cwd切换基准目录变得非常直观——get_cwd类型为fun(context: blink.cmp.Context): string(见 path/init.lua)。path 源的其他可选opts还包括show_hidden_files_by_default(默认false)、ignore_root_slash(默认false)、max_entries(默认 10000,见 path/init.lua)。

四、菜单绘制(Completion menu drawing)

4.1 禁用 nerd fonts(无图标方案)

如果终端没有安装 nerd font,图标会显示为乱码。下面的配置完全移除kind_icon列,改用文本形式的kind列并放在行尾:

completion = { menu = { draw = { columns = { { 'label', 'label_description', gap = 1 }, { 'kind' } } } } }

kind组件默认的text返回ctx.kind(种类文本),并设置width = { fill = true }使其填充剩余宽度(见 menu.lua)。这样每一行显示为"标签 + 描述 + 种类文本",完全不需要图标字体。

4.2 为种类图标添加背景色

kind_icon组件添加左右空格,再配合自定义的高亮组(BlinkCmpKindBlinkCmpKind<kind>),可以做出带背景色的方块图标效果:

completion = { menu = { draw = { padding = { 0, 1 }, -- 只在右侧留 padding components = { kind_icon = { text = function(ctx) return ' ' .. ctx.kind_icon .. ctx.icon_gap .. ' ' end } } } } }

你需要先在 colorscheme 中配置对应高亮组的背景色和前景色,例如:

vim.api.nvim_set_hl(0, 'BlinkCmpKindFunction', { bg = '#ff8800', fg = '#000000' })

draw.padding默认值为1(左右各 1),也可写成{ left, right }形式(见 menu.lua)。这里{ 0, 1 }表示左侧无 padding、右侧 1 个空格。

4.3 图标方案合集

kind_icon组件默认使用 blink.cmp 内置图标(基于ctx.kind_icon)。官方 Recipes 提供了四种主流图标库的接入方案(原文档以<details>折叠呈现,本文按方案列出):

方案 A:mini.icons(仅 LSP kinds)

讨论来源:

completion = { menu = { draw = { components = { kind_icon = { text = function(ctx) local kind_icon, _, _ = require('mini.icons').get('lsp', ctx.kind) return kind_icon end, -- (可选)使用 mini.icons 提供的高亮 highlight = function(ctx) local _, hl, _ = require('mini.icons').get('lsp', ctx.kind) return hl end, }, kind = { -- (可选)使用 mini.icons 提供的高亮 highlight = function(ctx) local _, hl, _ = require('mini.icons').get('lsp', ctx.kind) return hl end, } } } } }

方案 B:mini.icons+ 文件类型图标(Path 源)

讨论来源:Path 源的结果按文件类型显示对应图标,其余按 LSP kind 显示:

local function get_mini_icon(ctx) if ctx.source_name == "Path" then local is_unknown_type = vim.tbl_contains( { "link", "socket", "fifo", "char", "block", "unknown" }, ctx.item.data.type ) local mini_icon, mini_hl, _ = require("mini.icons").get( is_unknown_type and "os" or ctx.item.data.type, is_unknown_type and "" or ctx.label ) if mini_icon then return mini_icon, mini_hl end end local mini_icon, mini_hl, _ = require("mini.icons").get("lsp", ctx.kind) return mini_icon, mini_hl end completion = { menu = { draw = { components = { kind_icon = { text = function(ctx) local kind_icon, kind_hl = get_mini_icon(ctx) return kind_icon end, -- (可选)使用 mini.icons 提供的高亮 highlight = function(ctx) local _, hl = get_mini_icon(ctx) return hl end, }, kind = { -- (可选)使用 mini.icons 提供的高亮 highlight = function(ctx) local _, hl = get_mini_icon(ctx) return hl end, } } } } }

方案 C:nvim-web-devicons+lspkind

讨论来源:Path 源用nvim-web-devicons按文件名显示文件图标,其他源用lspkind的符号映射:

completion = { menu = { draw = { components = { kind_icon = { text = function(ctx) local icon = ctx.kind_icon if vim.tbl_contains({ "Path" }, ctx.source_name) then local dev_icon, _ = require("nvim-web-devicons").get_icon(ctx.label) if dev_icon then icon = dev_icon end else icon = require("lspkind").symbol_map[ctx.kind] or "" end return icon .. ctx.icon_gap end, -- (可选)使用 nvim-web-devicons 的高亮组 -- 如果想保持高亮与图标同步,也可以给 kind.highlight 添加同样的函数 highlight = function(ctx) local hl = ctx.kind_hl if vim.tbl_contains({ "Path" }, ctx.source_name) then local dev_icon, dev_hl = require("nvim-web-devicons").get_icon(ctx.label) if dev_icon then hl = dev_hl end end return hl end, } } } } }

方案 D:mini.icons+lspkind

mini.icons显示文件类型图标、lspkind显示 LSP kinds:

completion = { menu = { draw = { components = { kind_icon = { text = function(ctx) if ctx.source_name ~= "Path" then return require("lspkind").symbol_map[ctx.kind] or "" .. ctx.icon_gap end local is_unknown_type = vim.tbl_contains({ "link", "socket", "fifo", "char", "block", "unknown" }, ctx.item.data.type) local mini_icon, _ = require("mini.icons").get( is_unknown_type and "os" or ctx.item.data.type, is_unknown_type and "" or ctx.label ) return (mini_icon or ctx.kind_icon) .. ctx.icon_gap end, highlight = function(ctx) if ctx.source_name ~= "Path" then return ctx.kind_hl end local is_unknown_type = vim.tbl_contains({ "link", "socket", "fifo", "char", "block", "unknown" }, ctx.item.data.type) local mini_icon, mini_hl = require("mini.icons").get( is_unknown_type and "os" or ctx.item.data.type, is_unknown_type and "" or ctx.label ) return mini_icon ~= nil and mini_hl or ctx.kind_hl end, } } } } }

以上方案中的require('mini.icons')require('nvim-web-devicons')require('lspkind')均来自对应的第三方插件(mini.iconsnvim-web-deviconslspkind-nvim),需确保这些插件已安装。ctx.item.data.type是 Path 源候选项携带的文件类型元数据(如filedirectory等)。

五、为写作者(For writers):散文写作场景适配

写散文时,你可能希望补全行为与写代码时明显不同。官方 Recipes 专门开辟了这一节,欢迎贡献更多有趣的配置。

5.1 buffer 源:保留首字母大小写

默认的 buffer 源直接以原文单词作为候选。对于散文写作,英文句首单词通常是首字母大写的(如 "The"),此时若 buffer 里有小写形式 "the",补全插入后可能破坏句首大写。下面的transform_items会在补全候选与当前输入关键字大小写不一致时自动校正首字母,并去重:

sources = { providers = { buffer = { -- 保留首字符的大小写 transform_items = function (a, items) local keyword = a.get_keyword() local correct, case if keyword:match('^%l') then correct = '^%u%l+$' case = string.lower elseif keyword:match('^%u') then correct = '^%l+$' case = string.upper else return items end -- 避免校正产生的重复项 local seen = {} local out = {} for _, item in ipairs(items) do local raw = item.insertText if raw:match(correct) then local text = case(raw:sub(1,1)) .. raw:sub(2) item.insertText = text item.label = text end if not seen[item.insertText] then seen[item.insertText] = true table.insert(out, item) end end return out end } } }

逻辑拆解:

  • 若当前输入的关键字以小写字母开头(^%l),则把"全大写开头的单词"(匹配^%u%l+$)校正为小写首字母(string.lower);反之若关键字以大写开头(^%u),把全小写单词(^%l+$)校正为大写首字母(string.upper)。
  • 校正通过改写候选的insertTextlabel实现,并用seen表按insertText去重,避免校正前后重复项同时出现在列表里。
  • a.get_keyword()是源实例方法,返回当前补全的关键字。

六、小结与相关文档导航

本文覆盖了doc/recipes.md的全部 21 个 Recipes,分属五类场景:补全启用/触发控制(General)、模糊排序(Fuzzy)、源定制(Sources)、菜单绘制(Drawing)与写作适配(Writers)。每段配置都可在require('blink.cmp').setup({ ... })中按需组合使用,官方也欢迎大家提交自己的 Recipes(见 doc/recipes.md 开头的说明)。

如果某个 Recipe 涉及更深入的配置语义,可进一步阅读以下官方文档与源码:

  • 配置总览:doc/configuration/general.md、doc/configuration/completion.md
  • 模糊匹配详解:doc/configuration/fuzzy.md
  • 源(Provider)配置:doc/configuration/sources.md
  • 键位映射预设:lua/blink/cmp/keymap/presets.lua
  • 菜单绘制与组件定义:lua/blink/cmp/config/completion/menu.lua
  • 触发行为默认值:lua/blink/cmp/config/completion/trigger.lua
  • 排序器实现:lua/blink/cmp/fuzzy/sort.lua
  • buffer 源默认行为:lua/blink/cmp/sources/buffer/init.lua
  • path 源默认行为:lua/blink/cmp/sources/path/init.lua

【免费下载链接】blink.cmpPerformant, batteries-included completion plugin for Neovim项目地址: https://gitcode.com/GitHub_Trending/bl/blink.cmp

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询