Neovim補完プラグインblink.cmpの使い方とカスタマイズ

Neovimの補完プラグインblink.cmpの使い方や設定方法を解説します。

Blink.cmp

blink.cmpとは?

blink.cmpはNeovimの補完プラグインで、補完の表示や絞り込みが速いです。

追加のプラグイン無しで「LSP」「バッファ」「パス」「コマンドライン」「スニペット」に対する補完ができます。
プラグインを入れれば辞書やAIによる補完ソースを追加できます。さらに、nvim-cmpの補完ソースも使えます。

キーマップの設定も簡潔に書けますし、カスタマイズも可能です。

導入方法

リポジトリ名だけコピペしたい人向け
saghen/blink.cmp

インストール方法は公式ドキュメントに書いてあります。以下、日本人向けに情報を整理しました。

依存関係

blink.cmpではfuzzy mathcerのためにバイナリを使います。バイナリはダウンロードするか自分でビルドするかを選択できます。
そんなわけで必要なものは次のとおりです。

  • Neovim(0.10以上)
  • バイナリをダウンロードする場合
    • curl
    • Git
  • 自分でビルドする場合
    • Rustかrustup

インストール

ここではlazy.nvimの例を載せますが、ビルドコマンドなどは他のプラグインマネージャーでも参考になるはずです。

lazy.nvimの例
return {
'saghen/blink.cmp',
version = '*', -- バイナリをダウンロードする場合
opts = {
-- 次の項目で紹介
},
}

以下、ビルドする場合です。

ビルドする場合
return {
'saghen/blink.cmp',
version = '*',
build = 'cargo build --release',
-- build = 'nix run .#build-plugin',
-- ...
}
Nixユーザーの場合
return {
'saghen/blink.cmp',
version = '*',
build = 'nix run .#build-plugin',
-- ...
}

最低限の設定

最低限のプラグインの設定はsetupを呼び出すだけで完了です。

require('blink.cmp').setup({})

lazy.nvimの場合setupの中身はoptsに書くためこの記述は不要です。この書き方について知りたい人は別の記事「lazy.nvimの使い方から起動を爆速にする方法までを解説」を読んでください。

LSPの設定

Neovimのバージョンが0.11よりも低い場合、nvim-lspconfigに次のように設定してあげます。

  1. nvim-lspconfigの依存関係にblink.cmpを追加
  2. 各Language serverのcapabilitiesrequire('blink.cmp').get_lsp_capabilities()を追加

1ステップずつ解説します。まずは依存関係へ追加。

lazy.nvimでの依存関係の設定
return {
"neovim/nvim-lspconfig",
dependencies = {
"saghen/blink.cmp",
},
-- ...
}

続いて、各Language serverのcapabilitiesにblink.cmpのcapabilitiesを渡します。
以下、公式ドキュメントに書かれているものとは別に、筆者の例です。

local lspconfig = require("lspconfig")
local function server_register(server_name)
local opts = {}
local success, req_opts = pcall(require, "plugins.lsp.servers." .. server_name)
if success then
opts = req_opts
end
opts.capabilities = require("blink.cmp").get_lsp_capabilities(opts.capabilities)
lspconfig[server_name].setup(opts)
end
require("mason").setup()
local mason_lspconfig = require("mason-lspconfig")
mason_lspconfig.setup_handlers({ server_register })
local other_lsp = {
"biome",
"eslint",
"jsonls",
"markdown-language-server",
}
for _, server_name in pairs(other_lsp) do
server_register(server_name)
end

lazy.nvimでの遅延読み込み

lazy.nvimでの遅延読み込みの例も載せておきます。

return {
"saghen/blink.cmp",
event = { "InsertEnter", "CmdLineEnter" },
-- ...
}

使い方

Neovim初心者・デフォルトの設定にしている人向けに使い方を説明します。
呼吸するようにカスタマイズする皆さんは、この項目は読み飛ばしてください

挿入モードやコマンドラインモードで文字を入力後、次のデフォルトキーマップを使って候補を選択・決定します。Neovimのデフォルトの補完に似てますね(:help popupmenu-completionのやつ)。

キーマップ内容
<C-y>補完候補を決定
<C-e>キャンセル
<Up>前の補完候補を選択
<Down>次の補完候補を選択
<C-p>前の補完候補を選択
<C-n>次の補完候補を選択

他のキーマップについてはKeymapの”default”プリセットを読みましょう。

設定の書き方について補足

ここまでは最低限の設定のみでした。でも設定したいですよね

ここで、lazy.nvimでの設定する場合に1点補足です。次のようにopts_extendを書いてあげましょう。

lazy.nvimでの例
return {
'saghen/blink.cmp',
version = '*',
---@module 'blink.cmp'
---@type blink.cmp.Config
opts = {
-- ここに設定を書く
},
opts_extend = { "sources.default" },
}

opts_extendはlazy.nvimの機能で、opts_extendに指定したもの(プラグインで定義されているsources.default)をopts内で拡張できます。おまじないだと思って書いておきましょう。

余談:lazy.nvimの実装をgrepしてもopts_extendは出てきません。〇〇_extendという汎用的なマージ機能として実装されているからです。実装探検隊の方は注意しましょう。

設定例

公式ドキュメントに各設定の挙動が画像つきで載っています。

ここではよく設定されるであろう項目を解説します。opts = {}の形式で書きますが、lazy.nvim以外の人はrequire('blink.cmp').setup({})に読み替えてください。

キーマップ

キーマップのプリセットは4つ用意されています。

プリセット名特徴
noneキーマップは全部自分でセットする
default<C-y>で候補を決定
super-tab<Tab>で候補を決定かスニペットジャンプ
enter<CR>で候補を決定

プリセットの中身は公式ドキュメントのKeymapを読みましょう。

キーマップの書き方

筆者はプリセットをsuper-tabにして、ところどころ上書きしています。

opts = {
keymap = {
preset = "super-tab",
["<C-u>"] = { "scroll_documentation_up", "fallback" },
["<C-d>"] = { "scroll_documentation_down", "fallback" },
["<C-b>"] = {},
["<C-f>"] = {},
},
},

各キーマップは{ "command-foo", "command-bar" }のようにテーブルで定義します。テーブルの最初の要素が発動しなかったら次の要素へ、といった具合です。
使用できるコマンドの一覧はCommandsに書いてあります。

単純に無効化したいだけであれば{}を渡します。

キーマップに関数を渡す

キーマップの定義テーブルには、コマンド名だけではなく関数も渡せます。

たとえば、プリセットsuper-tab<Tab>は次のように定義されています。

実際のsuper-tabの定義
['<Tab>'] = {
function(cmp)
if cmp.snippet_active() then
return cmp.accept()
else
return cmp.select_and_accept()
end
end,
'snippet_forward',
'fallback',
},

コマンドラインだけ個別設定

コマンドラインやターミナルだけ個別に設定したいなら、cmdline.keymapのような場所に定義します。

筆者はコマンドラインで「<CR>で補完候補の決定とエンター」を設定しています。

opts = {
cmdline = {
keymap = {
preset = "super-tab",
["<CR>"] = { "accept_and_enter", "fallback" },
},
},
},

accept_and_enterは「補完候補の決定」である<CR>と「実行コマンドの決定」である<CR>を一括で入力できる、という補完コマンドです。

一方、「:LspRestart tailwindcss:LspRestartのような部分」の入力中は「補完候補の決定」だけしたくなります。そんなときはsuper-tab<Tab>を使っています。

補完ソースの設定

補完ソースはsourcesに書きます。

単純な例

まずはビルトインの補完ソースのみを使っている例を見てみましょう。

使いたいソースはdefaultに書きます。ファイルタイプ毎にソースを変えるならper_filetypeに書きます。

opts = {
sources = {
default = { "snippets", "lsp", "path", "buffer" },
per_filetype = {
markdown = { "snippets", "lsp", "path" },
mdx = { "snippets", "lsp", "path" },
},
}
}

筆者は上記のようにmarkdownmdxのときだけ「バッファ補完」を無効化しています。日本語の文章だと煩わしいからです。

スニペット

vim.snippetを使ったスニペット、LuaSnipmini.snippetsがビルトインでサポートされています。

これらのスニペットエンジンを使う場合、snippets.presetdefaultluasnipmini_snippetsのいずれかを指定します。

opts = {
snippets = { preset = "luasnip" },
}

この1行でキーマップをよしなに設定してくれます

LuaSnipの使い方の記事も書いたので良ければどうぞ。

プラグインマネージャーでインストールや依存関係に追加するのも忘れずに。

依存関係に追加
return {
"saghen/blink.cmp",
dependencies = {
"rafamadriz/friendly-snippets", -- スニペット集
"L3MON4D3/LuaSnip", -- スニペットエンジン
},
-- ...
}
サポートされていないスニペットエンジン

サポートされていなスニペットエンジンでも、nvim-cmpでのプラグインがあればblink.compat経由で使えそうです。
ただし、スニペットの「展開」「アクティブ判定」「ジャンプ」の関数を自分で定義する必要があります

こんな感じで定義
opts = {
snippets = {
expand = function(snippet)
foo.expand(snippet)
end,
active = function(filter)
return foo.available(filter)
end,
jump = function(direction)
foo.jump(direction)
end,
}
}

公式ドキュメント:Snippets | Blink Completion (blink.cmp)

ビルトインの補完ソースをカスタマイズ

ビルトインの補完ソースをカスタマイズする場合、providers.〇〇のような場所に書きます。

以下は、「パス補完」をカスタマイズしています。基準となるディレクトリを変更する調整です。

opts = {
sources = {
-- ...
providers = {
path = {
opts = {
get_cwd = function(context)
-- NOTE: ZLEのedit-command-lineで使いやすいようにcwdを変更
local dir_name = vim.fn.expand(("#%d:p:h"):format(context.bufnr))
if dir_name == "/tmp" then
return vim.fn.getcwd()
end
return dir_name
end,
},
},
}
}
}

別の記事で触れているZLEのedit-command-line機能での例です。
edit-command-lineではカレントディレクトリが/tmpになってしまうため、変更しています。

プラグインを使う例

続いて、プラグインによる補完ソースを使う設定方法の解説です。ここではblink-cmp-dictionaryを例にします。

まずはプラグインのインストールや依存関係を設定します。

依存関係に追加
return {
"saghen/blink.cmp",
dependencies = {
{
"Kaiser-Yang/blink-cmp-dictionary",
dependencies = { "nvim-lua/plenary.nvim" },
},
-- ...
},
-- ...
}

次に、sources.providersに定義を追加します。そして、sources.defaultsources.per_filetype.〇〇のテーブルにそのキー名を追加します。

opts = {
sources = {
default = { "snippets", "lsp", "path", "buffer", "dictionary" },
providers = {
dictionary = {
module = "blink-cmp-dictionary", -- 必須
name = "Dict", -- 必須
min_keyword_length = 3,
async = true,
score_offset = -1000,
max_items = 5,
opts = {
dictionary_files = { "/usr/share/dict/words" },
},
},
}
}
}

moduleが読み込まれるモジュールです。プラグインのREADMEに載っているものを記載しましょう。
その他はオプションです。

補完ソースのオプション

補完ソースのオプションには2つあります。optsがそれぞれの補完ソース固有、それ以外は共通のオプションです。

providers = {
dictionary = {
module = "blink-cmp-dictionary", -- 必須
name = "Dict", -- 必須
min_keyword_length = 3,
async = true,
score_offset = -1000,
max_items = 5,
opts = {
dictionary_files = { "/usr/share/dict/words" },
},
},
}

公式ドキュメント:Sources | Blink Completion (blink.cmp)

見た目の設定

見た目も細かく設定できます。よくありそうなやつをピックアップします。

ボーダー

よくありそうなのは「ウィンドウにボーダーを付けたい」といったところでしょうか。

次の設定は公式ドキュメントRecipesより引用です。

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

ドキュメントには記載されていませんがボーダーは現時点(2025年2月)では7種類あります。
インストール後ならlazydev.nvimでの補完が効くのでそれで見てください。あるいは、実装でblink.cmp.WindowBorder型を検索してください。

ドキュメントを表示

デフォルトでは、ドキュメントはキーマップで表示・非表示を切り替えます。
最初からドキュメントを表示したいなら次のように設定します。

次の設定は公式ドキュメントGeneralより引用です。

opts = {
completion = {
documentation = { auto_show = true, auto_show_delay_ms = 500 },
},
}
Blink.cmp

検索時には補完をオフにしたい

/?による検索のときには補完の表示をオフにすることも設定できます。

これも公式ドキュメントRecipesより引用です。

opts = {
completion = {
menu = {
auto_show = function(ctx)
return ctx.mode ~= "cmdline" or not vim.tbl_contains({ '/', '?' }, vim.fn.getcmdtype())
end,
},
}
}

この設定ではあくまでも「補完の自動開始をしない」という挙動です。補完したくなったら<C-Space>を入力すれば補完できます(キーマップのプリセットを使っている場合)。

カスタマイズ例

ここまではよくありそうな設定方法の解説でした。ここからは実際に筆者がやっているカスタマイズの紹介です。

キーマップのカスタマイズ

キーマップで関数を使ってカスタマイズしているものを挙げます。

スニペット変換の優先

補完を無視して「一番優先度が高いスニペット」を展開するキーマップです。

補完候補を見るまでもない、よく使うスニペットを展開するときに便利です。

opts = {
keymap = {
-- ...
["<C-y>"] = {
function(cmp)
if require("luasnip").expandable() then
cmp.hide()
vim.schedule(function()
require("luasnip").expand()
end)
return true
end
return false
end,
"fallback",
},
},
},

falseを返すとテーブルの次の要素(今回はfallback)の実行に移ります。

補完ワードの先頭の大文字・小文字を切り替え

候補を挿入しつつ、先頭を大文字にしたいことがあります。

例:fooという候補をFooにしたい。

そんなときに次のキーマップを使っています。

["<C-v>"] = {
function(cmp)
cmp.accept({
callback = function()
vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes("<Esc>mzBvg~`za", true, false, true),
"in",
false
)
end,
})
end,
},

Bvg~で先頭の文字へ移動して大文字・小文字を切り替えています。

直前の空白削除

getFoodefault_fooのような連続する単語を書くとき、Foofooの部分だけ補完を効かせたいことがあります。

そんなときに使うのが次のキーマップです。

-- 直前に入れた空白を削除する
-- `get Foo` -> `getFoo`
-- getFoo, default_foo のようなprefixがあるときに使う
["<C-g>"] = {
function()
vim.api.nvim_feedkeys(
vim.api.nvim_replace_termcodes('<Esc>mzF<Space>"_x`za', true, false, true),
"in",
false
)
end,
},

このキーマップは次のステップ4の部分を実現します。

  1. get_を入力
  2. get_:最後に空白を入れる
  3. get_ foo:補完する
  4. get_foo:空白を削除する

getFooのような場合は前述の<C-v>のキーマップと合わせて使ったりします。

別にblink.cmp経由で登録する必要はありませんが、近いタイミングで使うものなので。

コマンドラインの補完開始の条件

コマンドラインにて、:wqのようなコマンドまで補完候補に入ると煩わしいです。

公式ドキュメントでは、「補完開始の文字数条件をコマンドラインだけ2にする方法」を紹介しています。

ただこれだと:Lのときに:Lazyのようなコマンドが表示されません。
そこで筆者は次のように補完の開始条件を設定しました。

opts = {
sources = {
min_keyword_length = function(ctx)
-- :wq, :qa -> menu doesn't popup
-- :Lazy, :wqa -> menu popup
if ctx.mode == "cmdline" and ctx.line:find("^%l+$") ~= nil then
return 3
end
return 0
end,
}
}

小文字アルファベット2文字までは補完がオフになります。

特定のときに補完をオフにしたい

補完のオンオフ条件は、補完自体ならenabled、補完ソースごとならproviders.〇〇.enabledで指定できます。

以下は「markdownmdxでコードブロックの言語名を書くとき」に補完を無効化する例です。

opts ={
enabled = function()
return not (
vim.tbl_contains({ "markdown", "mdx" }, vim.bo.filetype)
and vim.api.nvim_get_current_line():match("^```[%a]*`")
)
end,
}

ファイルを分けてぇんだ

カスタマイズ大好き人間の場合、設定ファイルが長くて分割したくなるかもしれません。lazy.nvimの場合は以前書いた記事が参考になるでしょう。

ここではblink.cmpの筆者のディレクトリ構成と、設定を分割したい場合に使える型情報を記載しておきます。

ディレクトリ構成の例
.config/nvim/lua/plugins
├── completion
│ └── index.lua
│ └── luasnip.lua
│ └── mappings.lua
│ └── sources.lua
├── ...
.config/nvim/lua/plugins/completion/index.lua
---@module "lazy"
---@type LazyPluginSpec
return {
"saghen/blink.cmp",
dependencies = {
{ import = "plugins.completion.luasnip" },
-- ...
},
---@module "blink.cmp"
---@type blink.cmp.Config
opts = {
keymap = require("plugins.completion.mappings"),
sources = require("plugins.completion.sources"),
-- ...
},
opts_extend = { "sources.default" },
}
.config/nvim/lua/plugins/completion/luasnip.lua
---@module "lazy"
---@type LazyPluginSpec
return {
"L3MON4D3/LuaSnip",
dependencies = { "rafamadriz/friendly-snippets" },
-- ...
}
.config/nvim/lua/plugins/completion/mappings.lua
---@module "blink.cmp"
---@type blink.cmp.KeymapConfig
return {
preset = "super-tab",
["<C-u>"] = { "scroll_documentation_up", "fallback" },
-- ...
}
.config/nvim/lua/plugins/completion/sources.lua
---@module "blink.cmp"
---@type blink.cmp.SourceConfigPartial
return {
default = { "snippets", "lsp", "path", "buffer", "dictionary" },
-- ...
}

sourcesの部分の分割で使う型はblink.cmp.SourceConfigPartialですので注意を。

トラブルシューティング

筆者が遭遇したトラブルの解決方法です。

スニペットが表示されない

「公式ドキュメントどおりに設定しているのにスニペットが表示されない」
このトラブルは、単に補完候補の下の方に表示されているだけでした。優先度が低かったわけです。
score_offsetで優先度を設定できるようです。

providers = {
snippets = {
score_offset = 10,
},
},

筆者は上記の代わりに次のようにdefaultでスニペットを最初に追加することで対応しました。この方がスニペットの主張が強すぎないです。

opts = {
sources = {
default = { "snippets", "lsp", "path", "buffer" },
per_filetype = {
markdown = { "snippets", "lsp", "path" },
mdx = { "snippets", "lsp", "path" },
},
}
}

コマンドラインやターミナルだけキーマップがおかしい

コマンドライン・ターミナルだけ個別にキーマップを設定して、プリセットを追加し忘れていませんか?

opts = {
cmdline = {
keymap = {
preset = "super-tab",
["<CR>"] = { "accept_and_enter", "fallback" },
},
},
},

任意のタイミングで補完を開始したい

<C-space>を入力すれば、任意のタイミングで補完候補を表示できます。

['<C-space>'] = { 'show', 'show_documentation', 'hide_documentation' },

筆者の自作LSPでは、[[]]のような「括弧の中にカーソルがあるとき」に補完候補を返すように実装しています。
ですが、blimk.cmpでは表示されませんでした。nvim-cmpやVSCodeでは表示されたので、どうしてなのか調査中です。
ひとまずはこのキーマップで対応しています。


以上、blink.cmpの解説でした。この記事を書いている時点(2025年2月22日)ではまだβ版なのですが、細かくカスタマイズできて大変便利です。