Neovim 0.12(開発版)ではLSPクライアント経由でGitHub Copilotを使えるぞ(脱copilot.vim)

by
カテゴリ:
タグ:

Vim駅伝2025-08-29の記事です。前回の記事はnil2さんによる左手デバイスからVimを操作するでした。マクロパッドらしい痒い操作の実現が可能になる良いアイデアですね。

さてさて、Neovim 0.12(開発版)にvim.lsp.inline_completionが実装されました。

これと言語サーバーのcopilot-language-serverを組み合わせると、GitHub Copilotによる補完を、LSPクライアント経由で利用できます。プラグインのcopilot.vimcopilot.luaがいらなくなるのです!

local function fizzbuzzと打つと以下のような内容がグレーアウトで補完されるやつですね。 copilot-language-serverに限らず、lsp-aiなどの他の言語サーバーもも使えるはずなので、ローカルLLMを使いたい人にも有用です。

local function fizzbuzz(n)
  if n % 15 == 0 then
    return "FizzBuzz"
  elseif n % 3 == 0 then
    return "Fizz"
  elseif n % 5 == 0 then
    return "Buzz"
  else
    return tostring(n)
  end
end

設定方法

  1. copilot-language-serverをインストールする
  2. Neovim 0.12(開発版)を設定する
    • nvim-lspconfigプラグインを導入

    • 以下のような設定を自身のLSP関連の設定に追記

      require("nvim-lspconfig") -- copilot language serverの設定を提供
      vim.lsp.enable("copilot")
      vim.lsp.inline_completion.enable(true)
      
      -- マッピングのLHSはお好みで
      vim.keymap.set('i', '<c-cr>', '<cmd>lua vim.lsp.inline_completion.get()<cr>', { silent = true })
  3. :LspCopilotSignInコマンドを実行して、認証する

Tip: インライン補完の候補の有無でマッピングの挙動を変える

vim.lsp.inline_completion.get()は補完する内容がない場合に、falseを返します。この挙動を利用して、<tab>を入力した時に、補完候補が表示されていれば採用し、そうでなければ通常の<tab>を入力するといった動作を実現できます。

vim.keymap.set('i', '<tab>', function()
  if not vim.lsp.inline_completion.get() then
    return '<tab>'
  end
end, { expr = true })

Tip: インライン補完の有効・無効を細かく制御する

先の例では言語サーバーによらずインライン補完を有効化していました。有効化に使うvim.lsp.inline_completion.enable関数の第二引数を使うと、有効・無効を細かく制御できます。

https://neovim.io/doc/user/lsp.html#vim.lsp.inline_completion.enable()

enable({enable}, {filter})                *vim.lsp.inline_completion.enable()*
    Enables or disables inline completion for the {filter}ed scope, inline
    completion will automatically be refreshed when you are in insert mode.

    To "toggle", pass the inverse of `is_enabled()`: >lua
        vim.lsp.inline_completion.enable(not vim.lsp.inline_completion.is_enabled())
<

    Parameters: ~
      • {enable}  (`boolean?`) true/nil to enable, false to disable
      • {filter}  (`table?`) Optional filters |kwargs|,
                  • {bufnr}? (`integer`, default: all) Buffer number, or 0 for
                    current buffer, or nil for all.
                  • {client_id}? (`integer`, default: all) Client ID, or nil
                    for all.

たとえばLSPクライアントがcopilotのときだけ有効にしたい場合は、以下のようにします。

vim.api.nvim_create_autocmd('LspAttach', {
  callback = function(ev)
    local client = vim.lsp.get_client_by_id(ev.data.client_id)
    if client.name == "copilot" then
      vim.lsp.inline_completion.enable(true, { client_id = client.id, bufnr = ev.buf })
    end
  end
})

Tip: nvim-lspconfigに依存しない方法

nvim-lspconfigプラグインに依存せず設定したい方もいるかもしれません。その場合

  1. copilot-language-serverの設定を自前で書く

  2. copilot-language-serverがカレントバッファにアタッチされている状態で以下を実行する

    := vim.lsp.get_clients({ name = "copilot" })[1].request_sync("signIn", vim.empty_dict(), 1000, 0)

といった流れになると思われます。

認証方法についてはvim-jp SlackでGen Fさんが教えてくれました。

ENJOY!!

数日使ってみた感じだと、補完の速度がcopilot.luaプラグインよりもかなり速い気がします。

ただし、現時点ではcopilot.luaプラグインと違って、補完を行単位や単語単位で確定できません。私は滅多に利用していなかったので、まあいいかなと思ってます。対応については以下のIssueで議論されているので、近く改善されるかもしれません。

ところでVim駅伝は執筆者を随時募集しています。なんと次回の2025-09-01の枠も空いています。自分なりのVimの使い方からVimとの出会い、Vim友達のこと、Vimにまつわることなら技術記事じゃなくてもOKです。ぜひ気軽に書いてみてください。まだ見ぬ記事と出会えるの楽しみにしてます!

https://github.com/neovim/neovim/issues/35485