Neovim から Copilot Chat を使う

Vim から Copilot を使いたい場合、 `copilot.vim` を使われていると思います。

https://github.com/github/copilot.vim

ただ残念なことに `copilot.vim` は現状 Copilot Chat 機能に対応していません。

https://github.com/orgs/community/discussions/50939

この問題を解決できるのが `CopilotChat.nvim` になります。Neovim stable (0.9.5) か nightly で動作するようです。スターの数も順調に増えているようです。

https://github.com/CopilotC-Nvim/CopilotChat.nvim

ということで私も `CopilotChat.nvim` を導入してみました。備忘録として設定した内容をこの記事に書いておきます。

## 導入手順

基本的には[公式](https://github.com/CopilotC-Nvim/CopilotChat.nvim)の内容に従って設定すればあっけなく使えます。
ただ[Lazy.nvim](https://github.com/folke/lazy.nvim)を使っている前提の記述となっている箇所も多いので改めて自分の環境の場合の設定方法を載せておきます。 

### 私の環境

OS: Windows 11 Pro 23H2
Neovim: v0.9.5
プラグインマネージャー: [dein.vim](https://github.com/Shougo/dein.vim)

### プラグインマネージャーを使って `CopilotChat.nvim` をインストールする

Lazy.nvim, Vim-Plug を使ったインストールの場合は[公式の Installation](https://github.com/CopilotC-Nvim/CopilotChat.nvim?tab=readme-ov-file#installation)を参照ください。
私は dein.vim を使っています。[公式の Installation](https://github.com/CopilotC-Nvim/CopilotChat.nvim?tab=readme-ov-file#installation)の内容を参考にして dein.toml ファイルに下記内容を追加しました。

```toml
[[plugins]] # GitHub Copilot（すでに入っている場合は不要。zbirenbaum/copilot.lua でも良いそうです。）
repo = 'github/copilot.vim'

[[plugins]] # CopilotChat (for curl, log wrapper) に必要
repo = 'nvim-lua/plenary.nvim'

[[plugins]] # CopilotChat
repo = 'CopilotC-Nvim/CopilotChat.nvim'
depends = ['github/copilot.vim', 'nvim-lua/plenary.nvim']
branch = 'canary'
```

続いて init ファイルの設定を行います。
私は init ファイルを一部 lua 化していて、init.vim から各プラグインの設定ファイルを読み込むようにしています。フォルダ構成はこんな感じです。
```bash
C:.
│  coc-settings.json
│  init.vim
│
└─lua
    ├─copilot_chat_config <-- 今回新しく作成
    │      init.lua       <-- 今回新しく作成
    │
    ├─mason_nvim_config
    │      init.lua
    │
    └─telescope_config
            init.lua
```
今回は `CopilotChat.nvim` の設定ファイル `init.lua` を用意します。

```init.lua
require("CopilotChat").setup {
  debug = true, -- Enable debugging
}
```

次に今回追加した `init.lua` ファイルを読み込むように `init.vim` 側に設定を書きます。

```init.vim
" --------------------------------
" lua ファイルから設定を読み込み
" --------------------------------
:lua require('copilot_chat_config.init')
```

ここまでの設定で Copilot Chat が使えるはずです。
`:CopilotChat` コマンドを実行して Copilot と Neovim 上でやり取りできることを確認できました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/efe98876-addd-d805-66aa-916918401587.png)

日本語でも大丈夫です。
![image (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/4fe2587e-701d-5a5a-687a-bca3c812ed2d.png)

欲を言えばチャット文を送信のタイミングで IME が OFF になると嬉しいんですが…設定できるかな？と思って調べてみましたが良い方法は見つからず。

## カスタマイズ

とりあえず動くようにはなりました。[公式には Tips](https://github.com/CopilotC-Nvim/CopilotChat.nvim?tab=readme-ov-file#tips) が用意されています。その中で下記の2つを自分の環境でも設定してみました。

* バッファの内容全部を考慮して Copilot とチャットする
* Telescope と連携してプロンプトアクションの一覧を表示して実行する

### バッファの内容全部を考慮して Copilot とチャットする

Tips の内容は lazy.nvim での記載方法が書かれていました。私は `CopilotChat.nvim` の設定ファイル `init.lua` に下記の内容を追記して設定しました。

```init.lua
-- バッファの内容全体を使って Copilot とチャットする
function CopilotChatBuffer()
  local input = vim.fn.input("Quick Chat: ")
  if input ~= "" then
    require("CopilotChat").ask(input, { selection = require("CopilotChat.select").buffer })
  end
end

-- <leader>ccq (Copilot Chat Quick) で Copilot とチャットする
vim.api.nvim_set_keymap("n", "<leader>ccq", "<cmd>lua CopilotChatBuffer()<cr>", { noremap = true, silent = true })
```
ためしに `CopilotChat.nvim` の設定ファイル `init.lua` を開いて、「このファイルは何のファイルですか？」と聞いたところ、ファイル内容を網羅した回答がちゃんと返ってきました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/a0aabb5f-072f-379c-75a5-9373bf8558b1.png)

### Telescope と連携してプロンプトアクションの一覧を表示して実行する

こちらもlazy.nvim での記載方法が書かれていましたので `CopilotChat.nvim` の設定ファイル `init.lua` に下記の内容を追記して設定しました。

```init.lua
-- telescope を使ってアクションプロンプトを表示する
function ShowCopilotChatActionPrompt()
  local actions = require("CopilotChat.actions")
  require("CopilotChat.integrations.telescope").pick(actions.prompt_actions())
end

-- キーマッピング
-- <leader>ccp (Copilot Chat Prompt の略) でアクションプロンプトを表示する
vim.api.nvim_set_keymap("n", "<leader>ccp", "<cmd>lua ShowCopilotChatActionPrompt()<cr>", { noremap = true, silent = true })
```

`<leader>ccp` を実行すると telescope に Copilot Chat Prompt Actions が一覧で表示されました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/ad870f38-31d2-7fe3-f5d4-cb712e9b2801.png)

試しに Explain を実行してみました。カーソルが載っている箇所についてのコードの説明をしてくれました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/f788cfd4-a478-fb45-adf5-01241d83d661.png)

### Prompt Actions の質問文を日本語化する

さて Prompt Actions は便利なんですが英語で質問しているので回答も英語で返ってきます。日本語のほうが嬉しいので日本語化します。

正しいやり方なのか謎なのですが、prompt の内容をオーバーライドする手段で対応しました。ちなみに私は Commit 関連のコマンドは使わないつもりなので必要なものだけ日本語化しました。
`CopilotChat.nvim` の設定ファイル `init.lua` の内容を修正します。ちなみに日本語は元の英語を DeepL で翻訳したものを、ちょろっと自分でいじったものです。

```init.lua
local select = require('CopilotChat.select')

require("CopilotChat").setup {
  debug = true, -- Enable debugging

  -- プロンプトの設定
  -- デフォルトは英語なので日本語でオーバーライドしています
  prompts = {
    Explain = {
      prompt = '/COPILOT_EXPLAIN カーソル上のコードの説明を段落をつけて書いてください。',
    },
    Tests = {
      prompt = '/COPILOT_TESTS カーソル上のコードの詳細な単体テスト関数を書いてください。',
    },
    Fix = {
      prompt = '/COPILOT_FIX このコードには問題があります。バグを修正したコードに書き換えてください。',
    },
    Optimize = {
      prompt = '/COPILOT_REFACTOR 選択したコードを最適化し、パフォーマンスと可読性を向上させてください。',
    },
    Docs = {
      prompt = '/COPILOT_REFACTOR 選択したコードのドキュメントを書いてください。ドキュメントをコメントとして追加した元のコードを含むコードブロックで回答してください。使用するプログラミング言語に最も適したドキュメントスタイルを使用してください（例：JavaScriptのJSDoc、Pythonのdocstringsなど）',
    },
    FixDiagnostic = {
      prompt = 'ファイル内の次のような診断上の問題を解決してください：',
      selection = select.diagnostics,
    }
  }
}
```

この設定で、質問が日本語になって回答も日本語で返ってくるようになりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/eb789b69-c1a6-7b32-e898-e09961f8b638.png)

回答はこんな感じになりました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2960076/49bc7c52-3f98-9a1d-054c-2aa2090bdce9.png)

## 最後に

私は Visual Studio をデバッグモードで動作確認するときにしか使っていませんでした…。なので今までは Copilot Chat は全く使っていませんでした。今回の設定で Neovim から Copilot Chat を使えるようになったので Copilot Chat を活用していきたいと思います。活用していくうちに今後設定の見直しをしたくなるかもしれません。その際はまた記事にしますのでまた読んであげて下さい。