VSCodeをVimmer好みにする

皆さんはVSCodeを使用していますでしょうか。 私は普段Vim(Neovim)ですが、誰かと共通の何かを作成するときはVSCodeを使用しています。 また、初心者の方に何かを教えるときは同じエディタを使わないと伝わりが良くないので、みんなが使ってるエディタを勧めることが多く、その上教える際も初心者に方にはこのボタンを押してみましょうみたいに教えるのでこうなってしまいますね。

そして、画面共有をしながらプログラミングして見せたりするのにコーディングをするわけですが、残念ながらVSCodeはそのままだと使い物になりません。 とはいえNeovimの拡張機能を入れてinit.luaを共通で使うというのもあんまりしたくありませんので、私はVSCodeの設定を別で管理することにしています。 今回はよく使う機能をショートカットに設定して、vimっぽく使おうということでお話していきます。

拡張機能を入れる

まずは拡張機能vimを入れます。これがないと始まりません。

marketplace.visualstudio.com

これでキーバインドVimになって使いやすくなるのですが、これだけだと悲しい事件が起きてしまいます。 なので色々設定していきましょう。

設定 (settings.json)

まず、settings.jsonを開いて以下の設定を入れます。 leaderの設定とクリップボード共有の設定は入れておくといいでしょう。 また、Cursorにあるようなインラインなチャット入力の呼び出しもここで設定できます。(GitHub Copilot) (書き換えてDiffとってAcceptしたら書き換えてくれるやつのこと)

Macではこれを設定しなくてもCommand + iで呼べますが、Ubuntuの場合は設定しないとショートカットに割り当てられていません。今回は<C-i>に割り当てています。 他にもundoとredovscode由来のものを使用するように変更しています。これをしないとundo/redoしただけなのに未保存扱いになってしまい面倒です。 他にも色々書いていますが、興味があればKeyboard Shortcutsを開いてcommandsの部分を検索してみるとなんのことか解ると思います。

以下設定例です。

    "vim.useSystemClipboard": true,
    "vim.hlsearch": true,
    "vim.incsearch": true,
    "vim.leader": ",",
    "vim.useCtrlKeys": true,
    // normal mode
    "vim.normalModeKeyBindingsNonRecursive": [
        {
            "before": [
                "<C-i>"
            ],
            // CopilotChat inline editor
            "commands": [
                "interactiveEditor.start"
            ]
        },{
            "before": [
                "<leader>",
                "c"
            ],
            // CopilotChat
            "commands": [
                "workbench.panel.chat.view.copilot.focus"
            ]
        },{
            "before": [
                "<leader>",
                "e"
            ],
            // open explorer
            "commands": [
                "workbench.view.explorer"
            ]
        },{
            "before": [
                "<leader>",
                "u"
            ],
            // timeline
            "commands": [
                "timeline.focus"
            ]
        },{
            "before": [
                "<leader>",
                "s",
            ],
            // find in files
            "commands": [
                "workbench.action.findInFiles"
            ]
        },{
            "before": [
                "<leader>",
                "g",
            ],
            // open source control
            "commands": [
                "workbench.view.scm"
            ]
        },{
            "before": [
                "<leader>",
                " ",
            ],
            // search highlight
            "commands": [
                ":noh"
            ]
        },{
            "before": [
                "u",
            ],
            "commands": [
                "undo"
            ]
        },{
            "before": [
                "<C-r>",
            ],
            "commands": [
                "redo"
            ]
        },{
            "before": [
                "<leader>",
                "r",
            ],
            // search and replace
            "commands": [
                "editor.action.startFindReplaceAction"
            ]
        },{
            "before": [
                "<leader>",
                "d",
            ],
            // git diff on current file
            "commands": [
                "git.openChange"
            ]
        },{
            "before": [
                "<leader>",
                "f",
            ],
            // format document
            "commands": [
                "editor.action.formatDocument"
            ]
        },{
            "before": [
                "K",
            ],
            // definition preview
            "commands": [
                "editor.action.showDefinitionPreviewHover"
            ]
        },{
            "before": [
                "<C-t>",
            ],
            // jump to definition
            "commands": [
                "editor.action.revealDefinition"
            ]
        }
    ],
    // visual mode
    "vim.visualModeKeyBindingsNonRecursive": [
        {
            "before": [
                "<C-i>"
            ],
            // CopilotChat inline editor
            "commands": [
                "interactiveEditor.start"
            ]
        },{
            "before": [
                "<leader>",
                "c"
            ],
            // CopilotChat inline editor
            "commands": [
                "workbench.panel.chat.view.copilot.focus"
            ]
        },{
            "before": [
                "<leader>",
                "e"
            ],
            // open explorer
            "commands": [
                "workbench.view.explorer"
            ]
        },{
            "before": [
                "<leader>",
                "u"
            ],
            // timeline
            "commands": [
                "timeline.focus"
            ]
        },{
            "before": [
                ">",
            ],
            "commands": [
                "editor.action.indentLines"
            ]
        },{
            "before": [
                "<",
            ],
            "commands": [
                "editor.action.outdentLines"
            ]
        }
    ],
    // insert mode
    "vim.insertModeKeyBindingsNonRecursive": [
        {
            "before": [
                "<C-i>"
            ],
            // CopilotChat inline editor
            "commands": [
                "interactiveEditor.start"
            ]
        },{
            "before": [
                "<C-j>"
            ],
            // Copilot suggestion(next)
            "commands": [
                "editor.action.inlineSuggest.showNext"
            ]
        },{
            "before": [
                "<C-k>"
            ],
            // Copilot suggestion(previous)
            "commands": [
                "editor.action.inlineSuggest.showPrevious"
            ]
        }
    ],

あとはショートカットの設定を別途好みのものにすれば良いと思います。 これで結構使える感じになるのではないでしょうか。

おすすめの設定

ファイル名検索

開いているフォルダの中のファイルからファイル名検索

        },{
            "before": [
                "<leader>",
                "s",
                "f",
            ],
            "commands": [
                "workbench.action.quickOpen"
            ]
        },{

grep

開いているフォルダの中のファイルの中身をgrep

        },{
            "before": [
                "<leader>",
                "g",
                "r",
            ],
            "commands": [
                "workbench.action.findInFiles"
            ]
        },{

検索ハイライトを解除

        },{
            "before": [
                "<leader>",
                " ",
            ],
            "commands": [
                ":noh"
            ]
        },{

開いているバッファを表示(の代わり)

拡張機能vimで:buffersをしようとするとPRヨロと言われるのでとりあえずこれで代用

        },{
            "before": [
                "<leader>",
                "l",
                "s",
            ],
            "commands": [
                "workbench.files.action.focusOpenEditorsView"
            ]
        },{

コメントイン/アウト

コレがないと生きられない

        },{
            "before": [
                "g",
                "c",
                "c",
            ],
            commands": [
                "editor.action.commentLine"
            ]
        },{

quick fix(Copilot)

次のエラーに飛んでインラインチャットでエラーメッセージを拾う

            "before": [
                "<leader>",
                "q",
                "f",
            ],
            "commands": [
                "editor.action.marker.nextInFiles",
                "interactiveEditor.start",
            ]

ショートカット設定(keybindings.json)

併せてこちらも設定しておくといい感じになります。

Enterをctrl+m、ESCをctrl+[で出来るようにする

    {
        "key": "ctrl+m",
        "command": "type",
        "args": {
            "text": "\n"
        },
        "when": "editorTextFocus"
    },
    {
        "key": "ctrl+[",
        "command": "extension.vim_escape",
    },

Buffresの代わりに開いたOPEN EDITORSの選択したファイルを閉じる

    {
        "key": "alt+x",
        "command": "workbench.action.closeActiveEditor"
    },

nvim-treeみたいにファイルの作成や削除をキー入力で出来るようにする(Explorerにフォーカスしているとき)

    // create new file
    {
        "key": "a",
        "command": "explorer.newFile",
        "when": "filesExplorerFocus && foldersViewVisible && !explorerResourceIsRoot && !explorerResourceReadonly && !inputFocus"
    },
    // create new dir
    {
        "key": "shift+a",
        "command": "explorer.newFolder",
        "when": "filesExplorerFocus && foldersViewVisible && !explorerResourceIsRoot && !explorerResourceReadonly && !inputFocus"
    },
    // rename
    {
        "key": "r",
        "command": "renameFile",
        "when": "filesExplorerFocus && foldersViewVisible && !explorerResourceIsRoot && !explorerResourceReadonly && !inputFocus"
    },
    // open with vertical split window
    {
        "key": "ctrl+v",
        "command": "explorer.openToSide",
        "when": "explorerViewletFocus && foldersViewVisible && !inputFocus"
    },
    // delete file
    {
        "key": "d",
        "command": "moveFileToTrash",
        "when": "explorerResourceMoveableToTrash && filesExplorerFocus && foldersViewVisible && !explorerResourceReadonly && !inputFocus"
    },

ちなみに

NeovimでCopilotChat使いたいんだがって方はこちらを参照してください。

github.com

環境によっては「copilot.luaとか見つからないんだが?」って怒られる方もいると思います。 その場合はcopilot.luaとplugin.luaimport osの直下に

import sys
sys.path.append(os.path.dirname(os.path.abspath(__file__)))

を追記すると使えるようになります。 copolot.luaとplugin.lua

~/.local/share/nvim/lazy/CopilotChat.nvim/rplugin/python3

にあると思います。

また、根本解決したい場合は以下を実行するとよいです。

pip install pynvim==0.5.0

余談ですがnvimでcopilot chatを使用したquick fix(っぽいこと)をしたい場合はこんな感じになります。

local function quick_fix_next_error_with_ai()
  if vim.diagnostic.get(0, {severity = vim.diagnostic.severity.ERROR})[1] == nil then
    print("no error")
    return
  end
  -- jump to next error/warn
  vim.diagnostic.goto_next({severity = vim.diagnostic.severity.ERROR})
  -- fix with Copilot
  -- copy diagnostic message and current line
  local diagnostic_message = vim.diagnostic.get(0, {severity = vim.diagnostic.severity.ERROR})[1].message
  local current_line_text = vim.api.nvim_get_current_line()
  -- open Copilot chat window
  vim.cmd("vertical rightbelow new")
  vim.cmd("setlocal filetype=markdown")
  vim.cmd("CopilotChat ".. "error message : " .. diagnostic_message .. " | current line text : " .. current_line_text .. " | your job : how to fix it?")
end
vim.keymap.set("n", "<leader>xn", vim.diagnostic.goto_next, {desc="Jump to Next Error/Warn"})
vim.keymap.set("n", "<leader>qf", quick_fix_next_error_with_ai, {desc="Jump to Next Error and fix with Copilot Chat"})