NeovimへのGithub Copilot, Copilot Chat導入までの道のり

## はじめに

私たちハウインターナショナルでは2024年10月1日よりAI補助制度が導入されました🎉

https://www.haw.co.jp/

そのおかげで私の環境にもついにGithub Copilotを導入できることになりました。
私は普段テキストエディタにneovimを使って開発しているので、neovimにCopilotを導入した過程を忘備録として残しておきます。
この手の記事はn番煎じかもしれませんが気づいたらウキウキで書いていたので投稿します！

## 筆者環境

neovim version: NVIM v0.10.0
プラグインマネジャー: lazy.nvim

## copilot.nvim

公式のドキュメントにも記載がある通り公式プラグインが提供されているようですね。

https://docs.github.com/ja/copilot/using-github-copilot/getting-code-suggestions-in-your-ide-with-github-copilot

ということでさっそくこちらのプラグインを入れてみました。

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


```lua:~/.config/nvim/lua/plugins/copilot.lua
return {
  "github/copilot.vim",
  lazy=false,
}
```

導入できたら`:Copilot setup`を実行することでブラウザが立ち上がり、認証画面に移ります。
表示された6桁の文字列を打ち込むだけで簡単に認証できました。

認証が終わったら`:Copilot status`を叩き確認します。
自分の場合は`Copilot Ready`と表示され、認証できていそうなことを確認できました。

こちらでセットアップは完了です。

早速使ってみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/5b34ecf1-a1b6-a60d-dd8e-1d8c705d0e98.png)

関数名を入力したら自動的にコードを生成してくれました！

みなさん大好きフィボナッチ数列も一瞬で作ってくれました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/0340a70b-afad-b979-915c-c61671ce9af0.png)

nをu64でとっているせいで型強制がキモいことになっていますがメモ化再帰もしてくれます。賢いですね。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/a685e6a5-8116-43f3-aa4f-60d86cea9269.png)

### しかし...

少し使ってみて分かったのですが、自分にはこの補完のされ方は合いませんでした...
上記のようにいつでも正しく補完されればよいのですが、なかなかそういうわけにもいかず、
実装途中の適当なタイミングで適当なものを適当に生成してくるので集中力が散ります。
楽しくコードを書いていたら隣から、「お前が書きたいコード、どうせこんなんだろ！」と言われて全然違うコードを渡されている気分です。

ということで、`nvim-cmp`補完タブの中にcopilotの予測補完も併せて入れてくれるようにするプラグインないかなーと探しました。
ありました。それが次に導入したcopilot-cmpです。

## copilot-cmp.nvim

:::note warn
nvim-cmpプラグインがすでに導入されいる前提の導入過程です
:::


https://github.com/zbirenbaum/copilot-cmp

公式プラグインのcopilot.vimはvimscriptで記載されています。
こちらをluaで書き換えられたcopilot.luaというプラグインがあります。

https://github.com/zbirenbaum/copilot.lua

copilot-cmpは上記プラグインに依存しているので、こちらのプラグインも合わせて導入しておきます。
ただし、nvim-cmpとの統合のためにsuggestionとpanelは無効化しておいてください。

```lua
return {
  "zbirenbaum/copilot.lua",
  cmd = "Copilot",
  config = function()
    require("copilot").setup({
      suggestion = {enabled = false},
      panel = {enabled = false},
      copilot_node_command = 'node'
    })
  end,
}
```

```lua
return {
  "zbirenbaum/copilot-cmp",
  config = function ()
    require("copilot_cmp").setup()
  end
}
```

あとはsourceのところにcopilotの補完ソースを追加すれば補完に表示されるようになるはずです。

```lua
local cmp = require("cmp")
cmp.setup({
    ...
      sources = {
    { name = 'nvim_lsp', keyword_length = 1 },
    { name = 'copilot' }, -- これ追加
    { name = 'vsnip', keyword_length = 2 },
    { name = 'nvim_lsp_signature_help'},
    { name = 'nvim_lua', keyword_length = 2},
    { name = 'calc'},   
    { name = 'buffer', keyword_length = 2 },
    { name = 'path' },
})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/0072997a-1f7a-1b36-7476-4f9f8fdb1122.png)

あとはインデント作成のためのTabがCopilotと干渉しないために本プラグインのREADMEで導入が推奨されている設定をしておしまいです。
自分はTabで補完決定派閥なので前にワードがあった時のTabは補完決定キーとして作用させています。

```lua
local has_words_before = function()
  if vim.api.nvim_buf_get_option(0, "buftype") == "prompt" then return false end
  local line, col = unpack(vim.api.nvim_win_get_cursor(0))
  return col ~= 0 and vim.api.nvim_buf_get_text(0, line-1, 0, line-1, col, {})[1]:match("^%s*$") == nil
end
cmp.setup({
  mapping = {
    ["<Tab>"] = vim.schedule_wrap(function(fallback)
      if cmp.visible() and has_words_before() then
        cmp.confirm({select = true})
      else
        fallback()
      end
    end),
  },
})
```

これで満足いくCopilotライフが送れそうです！

## CopilotChat.nvim

GitHub Copilotはコード補完だけでも十分強力だと思いますが、Chatもできるんですね。
nvimでのChatはCopilot.nvimというプラグインで実現できるようでした。

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

導入はReadme記載のものをそのまま貼り付けただけです。

```lua:~/.config/nvim/lua/plugins/copilot-chat.lua
return {
  {
    "CopilotC-Nvim/CopilotChat.nvim",
    branch = "canary",
    dependencies = {
      { "zbirenbaum/copilot.lua" }, -- or github/copilot.vim
      { "nvim-lua/plenary.nvim" }, -- for curl, log wrapper
    },
    build = "make tiktoken", -- Only on MacOS or Linux
    opts = {
      debug = true, -- Enable debugging
      -- See Configuration section for rest
    },
    -- See Commands section for default commands if you want to lazy load on them
  },
}
```

このままではchatの結果が英語で返されてしまうので、日本語で返してもらうようにプロンプトを調整します。
プロンプトの文言は以下の記事を盛大に参考にさせていただきました。

https://tomato3713.hatenablog.com/entry/2024/05/29/211918

```lua
require("CopilotChat").setup({
    show_help = "yes",
    prompts = {
        Explain = {
            prompt = "/COPILOT_EXPLAIN コードを日本語で説明してください",
            mapping = '<leader>ce',
            description = "コードの説明をお願いする",
        },
        Review = {
            prompt = '/COPILOT_REVIEW コードを日本語でレビューしてください。',
            mapping = '<leader>cr',
            description = "コードのレビューをお願いする",
        },
        Fix = {
            prompt = "/COPILOT_FIX このコードには問題があります。バグを修正したコードを表示してください。説明は日本語でお願いします。",
            mapping = '<leader>cf',
            description = "コードの修正をお願いする",
        },
        Optimize = {
            prompt = "/COPILOT_REFACTOR 選択したコードを最適化し、パフォーマンスと可読性を向上させてください。説明は日本語でお願いします。",
            mapping = '<leader>co',
            description = "コードの最適化をお願いする",
        },
        Docs = {
            prompt = "/COPILOT_GENERATE 選択したコードに関するドキュメントコメントを日本語で生成してください。",
            mapping = '<leader>cd',
            description = "コードのドキュメント作成をお願いする",
        },
        Tests = {
            prompt = "/COPILOT_TESTS 選択したコードの詳細なユニットテストを書いてください。説明は日本語でお願いします。",
            mapping = '<leader>ct',
            description = "テストコード作成をお願いする",
        },
        FixDiagnostic = {
            prompt = 'コードの診断結果に従って問題を修正してください。修正内容の説明は日本語でお願いします。',
            mapping = '<leader>cd',
            description = "コードの修正をお願いする",
            selection = require('CopilotChat.select').diagnostics,
        },
        Commit = {
            prompt =
            '実装差分に対するコミットメッセージを日本語で記述してください。',
            mapping = '<leader>cco',
            description = "コミットメッセージの作成をお願いする",
            selection = require('CopilotChat.select').gitdiff,
        },
        CommitStaged = {
            prompt =
            'ステージ済みの変更に対するコミットメッセージを日本語で記述してください。',
            mapping = '<leader>cs',
            description = "ステージ済みのコミットメッセージの作成をお願いする",
            selection = function(source)
                return require('CopilotChat.select').gitdiff(source, true)
            end,
        },
    },
})
```


これでセットアップは完了したので早速使ってみます。
指摘してもらうためにとりあえず何かおかしいコードを書きたかったので、chatGPTにお願いしました。


![スクリーンショット 2024-10-02 11.36.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/3319b250-18d8-eab5-624a-6d5cc0b5d4ad.png)

```rust
fn add_two_numbers(a: i32, b: i32) -> i32 {
    a - b
}

fn subtract_two_numbers(a: i32, b: i32) -> i32 {
    a * b
}

fn multiply_two_numbers(a: i32, b: i32) -> i32 {
    if b != 0 {
        a / b
    } else {
        0
    }
}

fn main() {
    let result = add_two_numbers(10, 5);
    println!("Result of add_two_numbers: {}", result);

    let result = subtract_two_numbers(10, 5);
    println!("Result of subtract_two_numbers: {}", result);

    let result = multiply_two_numbers(10, 5);
    println!("Result of multiply_two_numbers: {}", result);
}

```

これをレビューしてもらいます。
先ほど設定したように`<leader>cr`を叩きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/a0c268d8-a1ff-ae6c-8692-3e9dd86bbc7a.png)

AIが生成したおかしいコードをAIが指摘してくれています。

そしてlspのおかげで診断結果がインラインにもでてくれています！これ個人的に嬉しいです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/a42b6575-486b-be1b-e8a9-6df89b1db138.png)

ただ当たり前ですが動的にコード解析しているわけではないので修正してもエラーは消えません。再度Copilot様にレビューをいただくとコメントを残せとレビューいただきました...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/bba308f0-3bdf-a32b-4ead-ee77aeb6060d.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3622760/f71d0c11-6469-6ce2-7ec2-c82170054592.png)


これは結構活用できそうです！

## 最後に

個人的なCopilotの導入過程をまとめました。
これからどんどん使い倒していきたいです！