【キラーアプリ】VSCodeの新たなVim拡張はNeoVimがおすすめ!

【キラーアプリ】VSCodeの新たなVim拡張はNeoVimがおすすめ!

VSCodeのVim拡張と言えば、青色のロゴのvscodevimが定番でした。そんな中でVim拡張で下剋上を成し遂げるほどのポテンシャルで成長しているNeo Vim拡張を紹介したいと思います。

VSCode Vimには問題が多かった


いち早くVSCodeでVimエミュレータとして登場して活躍していたVSCode Vim拡張機能ですが、もっさりしていたり、応答が返ってこない、アップデートすると不具合がぽろぽろ出るなど、安定しているとは言えませんでした。
また、VSCode上でVimを再現しているためVimの設定ファイル .vimrcやvim用に作成されたプラグインは使えませんでした。

Neo Vim (VSCode Neovim)

Neovimはプロジェクトの思想からも、外部プロセスからNeovimへアクセスするためのAPIが豊富に揃っています。
VSCodeのNeo Vim拡張は、VSCode上でVimをエミュレートするのではなくNeovimを直接参照することで高速・安定・豊富なプラグインをそのまま使用してVSCode上でVimを実現する拡張機能です。

1年以上VSCodeVimを使用している筆者が1日で乗り換えを決める程、圧倒的なポテンシャルを秘めています。
ただし、InsertModeやWindow操作などの一部はVSCode上で実装されるなど、100%互換があるとは言えません。
実行にも別途Neovimをセットアップする必要があり、プラグイン等も通常のVimと同じようにPackageManagerでセットアップする必要があるので、環境構築に少々時間が掛かります。

VSCode Neo Vim インストール

拡張機能からインストール

拡張機能 (Ctrl+Shift+X) からNeo Vimをインストールします。
vscode-neovim

Neovimをインストール

Neovimの0.5.0 nightly以上が必要なので、ダウンロードして配置します。
安定版ではなくプレリリースのnightlyが必要なので、よく確認しましょう。

neovim Release
https://github.com/neovim/neovim/releases

※パッケージ管理ツールのscoopでも公開されています。

scoop bucket add versions
scoop install neovim-nightly

VSCode Neovimのパスをセット

基本設定 (Ctrl+,)からNeovimのnvim.exeのパスをセットします。

"vscode-neovim.neovimExecutablePaths.win32": "C:\\Users\\****\\scoop\\shims\\nvim.exe"

※WindowsOSの場合の設定例です。OS種ごとに設定値が分かれています。
※パスはNeovimのインストールディレクトリに書き換えてください

ここまで終えれば、動作に必要な最低限の設定は完了です。
VSCodeを再起動するとお馴染みのNormal, Insert, VisualといったVimの入力モードで編集が行える状態となります。

設定ファイル init.vim (.vimrc)

neovimの設定ファイルinit.vimを作成します。
vimで言うと.vimrcのことです。

ファイルの作成場所は以下のユーザーディレクトリにinit.vimを作成します。

C:\Users\****\AppData\Local\nvim\init.vim

パスのデフォルトはAppData以下ですが、環境変数$XDG_CONFIG_HOMEでセットしたパスの以下の構成で参照されるため、環境変数を書き換えることでディレクトリは移動できます。

$XDG_CONFIG_HOME/nvim/init.vim

インサートモードを抜けたら日本語IMEをオフにする

インサートモートでIMEを日本語入力に変更した状態でノーマルモードへ戻ると、「っっっっ」などと日本語入力になるため手作業で毎回IMEを英数に切り替えなければなりません。
gvimでは<Ctrl-^>で入力切替が出来たりしましたが、NeoVimでは対応されていないので、インサートモードを抜けるタイミングで別アプリをキックしてIMEを制御します。

zenhanをインストール

Windows向けになりますが、c++で実装されたアプリケーションで、
引数に0を与えるとIMEが無効
引数に1を与えるとIMEが有効になります。

元々VSCode VimのimSelectの代替として用意されたものですが、IME制御という点で問題ないのでそのまま使用させて頂きます。
ダウンロードして任意の場所に保存し、環境変数からexeがある場所のパスを追加します。

zenhan
https://github.com/iuchim/zenhan

scoopからもダウンロード可能です。

scoop install zenhan

ターミナルでzenhan 0と入力するとIMEがオフ、zenhan 1と入力するとIMEがオンになることを確認します。

init.vimにInsertLeave時に実行処理を定義

init.vimにインサートモードから抜けるタイミングで任意のコマンドを自動的に実行するようにセットします。
InsertLeaveイベント発生時にzenhanを呼ぶことで、インサートモードからノーマルモードへ移行したタイミングでIMEが自動的にオフになります。
ついでにCmdLineLeaveイベント発生時にも呼んでおくと、Search後にIMEがオフになるので便利です。

" ime off
if executable('zenhan')
autocmd InsertLeave * :call system('zenhan 0')
autocmd CmdlineLeave * :call system('zenhan 0')
endif

プラグインの追加

プラグイン管理ツールのvim-plugを使用する方法です。

Windowsの場合はPowerShellから以下を実行します。
vim-plugがNeoVimのautoloadディレクトリにダウンロードされます。

iwr -useb https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim |`
ni "$env:LOCALAPPDATA/nvim-data/site/autoload/plug.vim" -Force

vim-plugの使い方

有名な文字列を囲うsurroundを使用する場合の手順です。

init.vimにプラグインの開始(begin)から終了(end)を宣言し、間にインストールするプラグインを指定します。
指定方法はgithubのURLや短縮してユーザー/リポジトリだけでも可能です。

call plug#begin('~/.vim/plugged')
Plug 'tpope/vim-surround'
call plug#end()
PlugInstall

init.vimに使用するプラグインを記載しただけでは使用できません。
初回にPlugInstallからプラグインをインストールする必要があります。

PlugInstallはEXコマンドに入力して実行しますが、VSCode NeoVimでは正しく実行されないようなので、NeoVimを直接起動して実行します。

プラグインのインストールが開始されます。
完了後にVSCodeを再起動してプラグインが有効になっていることを確認します。

VSCode NeoVimではNeoVimと100%互換がある訳ではないため、正常に動かないプラグインもあります。
EasyMotion等も正常に動かないため、VSCode NeoVim作者自身がEasyMotionをForkして動作するように変更したものをリリースしています。

jkでインサートモードを抜ける

VSCodeのキーボードショートカットの設定に以下のように記載します。

{
    "command": "vscode-neovim.compositeEscape1",
    "key": "j",
    "when": "neovim.mode == insert && editorTextFocus",
    "args": "j"
},
{
    "command": "vscode-neovim.compositeEscape2",
    "key": "k",
    "when": "neovim.mode == insert && editorTextFocus",
    "args": "k"
}

インサートモードでCtrl+oを使うには


公式で対応されたため、特別な設定は不要になりました。
設定が残っていると正しく動作しないため、削除してください。

1コマンドだけ実行して直ぐにインサートモードへ戻るCtrl+oは拡張機能で未実装です。
既にIssueも挙がっており、現状は以下のキーバインドを追加することで使用できるようになります。
ただ、一部実行するコマンドによっては正確に動きません。

[
    {
        "key": "ctrl+o",
        "command": "vscode-neovim.send",
        "args": "",
        "when": "editorTextFocus && neovim.mode == insert"
    },
]

VSCodeのコマンドをマップするには

VSCodeの各種コマンドをcallすることでマッピング可能です。

nnoremap :call VSCodeNotify('workbench.action.closeOtherEditors')
nnoremap :call VSCodeNotify('workbench.action.toggleSidebarVisibility')

ノーマルモードでカーソル位置の単語をハイライトするには

VSCodeでは標準でカーソル位置の単語は全てハイライト表示されますが、VSCode Neovimでは2020年12月現在ハイライトされません。
VSCode側のAPIのバグじゃないかとIssueで話されていますが、とはいえ修正されるまでは不便なので以下の設定を追加することで、思い通りの動きになります。
カーソル移動イベントに対してVSCodeのカーソル位置の単語をハイライトするコマンドをキックします。

autocmd CursorMoved * :call VSCodeNotify('editor.action.wordHighlight.trigger')

init.vim設定例

私のinit.vimを記載しておきます。
(US配列のキーボード向けのマッピングです)

" vim-plugin manager
call plug#begin(stdpath('data') . '/vscode_plugged')
Plug 'asvetliakov/vim-easymotion'
Plug 'bkad/CamelCaseMotion'
Plug 'haya14busa/vim-edgemotion'
Plug 'junegunn/vim-easy-align'
Plug 'justinmk/vim-sneak'
Plug 'machakann/vim-highlightedyank'
Plug 'machakann/vim-sandwich'
Plug 'markonm/traces.vim'
Plug 'thinca/vim-visualstar'
Plug 'tpope/vim-commentary'
Plug 'vim-jp/vimdoc-ja'
Plug 'wellle/targets.vim'
Plug 'vim-scripts/ReplaceWithRegister'
Plug 'haya14busa/vim-asterisk'
" Plug 'rhysd/clever-f.vim'
call plug#end()

" ime off
if executable('fcitx')
   autocmd InsertLeave * :call system('fcitx-remote -c')
   autocmd CmdlineLeave * :call system('fcitx-remote -c')
endif

" define
let mapleader = "\<space>"
let g:highlightedyank_highlight_duration = 200

" sandwich
" Sneakとマッピングが被るのでsurroundのマッピングへ変更
let g:sandwich_no_default_key_mappings = 1
let g:operator_sandwich_no_default_key_mappings = 1
nmap ys <Plug>(operator-sandwich-add)
nmap <silent>ds <Plug>(operator-sandwich-delete)<Plug>(operator-sandwich-release-count)<Plug>(textobj-sandwich-query-a)
nmap <silent>cs <Plug>(operator-sandwich-replace)<Plug>(operator-sandwich-release-count)<Plug>(textobj-sandwich-query-a)

" vim-asterisk
map g* <Plug>(asterisk-z*)
map g# <Plug>(asterisk-z#)
map * <Plug>(asterisk-gz*)
let g:asterisk#keeppos = 1

" clever-f
" let g:clever_f_use_migemo = 1
" let g:clever_f_chars_match_any_signs = ';'
" map ; <Plug>(clever-f-repeat-forward)
" map , <Plug>(clever-f-repeat-back)

" set
set clipboard+=unnamedplus
set ignorecase
set smartcase
set autoindent

" keymap
map f <Plug>Sneak_f
map F <Plug>Sneak_F
map t <Plug>Sneak_t
map T <Plug>Sneak_T
map <Leader>f <Plug>(easymotion-bd-f)
map <Leader>s <Plug>(easymotion-bd-f2)
" map <Leader>k <Plug>(easymotion-k)
" map <Leader>j <Plug>(easymotion-j)

map <Leader>w <Plug>CamelCaseMotion_w
map <Leader>b <Plug>CamelCaseMotion_b
map <Leader>e <Plug>CamelCaseMotion_e
map <Leader>ge <Plug>CamelCaseMotion_ge
nmap <Leader>w :w<CR>
nmap <Leader>q :q<CR>
nmap <Leader>Q :q!<CR>
nmap <Leader>x :x<CR>
nmap <Leader>E :call VSCodeNotify('workbench.files.action.focusOpenEditorsView')<CR>
nmap <Leader>p :call VSCodeNotify('workbench.action.showCommands')<CR>
nmap <Leader>o :call VSCodeNotify('workbench.action.gotoSymbol')<CR>
nmap <Leader>t :call VSCodeNotify('workbench.action.showAllSymbols')<CR>
nmap <Leader>g :call VSCodeNotify('workbench.view.scm')<CR>
nmap <Leader>> :call VSCodeNotify('workbench.action.moveEditorRightInGroup')<CR>
nmap <Leader><lt> :call VSCodeNotify('workbench.action.moveEditorLeftInGroup')<CR>
nmap <C-H> gT
nmap <C-L> gt
nmap <C-w><C-o> :Tabonly<CR>
nmap <Leader>n :tabnew<CR>
nmap ga <Plug>(EasyAlign)
" nmap <C-o> :call VSCodeNotify('workbench.action.navigateBack')<CR>
" nmap <C-i> :call VSCodeNotify('workbench.action.navigateForward')<CR>

noremap J 5j
noremap K 5k
nmap <Leader>j <Plug>(edgemotion-j)
nmap <Leader>k <Plug>(edgemotion-k)
vmap <Leader>j <Plug>(edgemotion-j)
vmap <Leader>k <Plug>(edgemotion-k)
nmap <Leader>e :call VSCodeNotify('workbench.view.explorer')<CR>

noremap 0 ^
noremap ^ 0
nnoremap \ :noh<CR>
nnoremap Y y$
nnoremap <C-w><C-o> :call VSCodeNotify('workbench.action.closeOtherEditors')<CR>
nnoremap <enter> :call VSCodeNotify('workbench.action.toggleSidebarVisibility')<CR>

vnoremap 0 ^
vnoremap ^ 0
vnoremap J 5j
vnoremap K 5k

xmap gc  <Plug>VSCodeCommentary
nmap gc  <Plug>VSCodeCommentary
omap gc  <Plug>VSCodeCommentary
nmap gcc <Plug>VSCodeCommentaryLine

xmap ga <Plug>(EasyAlign)

" ------- FIX: Keep autoindent enabled for all filetypes all the time ----- "
autocmd BufEnter * silent! set autoindent smartindent

" ------- Stop vscode-neovim from loading remainder of file ----- "
if exists('g:vscode') 
   finish
endif

 

ファイルツリー(エクスプローラー)をVim風に移動する

VSCode Vim拡張はファイルツリーをお馴染みのHJKLで移動できましたが、VSCode Neovim拡張では自分でショートカットを割り当てる必要があります。
ショートカットはinit.vimではなく、VSCodeのキーコンフィグに設定します。

keybindings.json

{
  // エクスプローラー
  {
    "key": "j",
    "command": "list.focusDown",
    "when": "listFocus && !inputFocus"
  },
  {
    "key": "k",
    "command": "list.focusUp",
    "when": "listFocus && !inputFocus"
  },
  {
    "key": "h",
    "command": "list.collapse",
    "when": "listFocus && !inputFocus"
  },
  {
    "key": "l",
    "command": "list.select",
    "when": "listFocus && !inputFocus"
  },
  // エクスプローラーから1番目のエディタグループへ移動
  {
    "key": "ctrl+w l",
    "command": "workbench.action.focusFirstEditorGroup",
    "when": "listFocus && !inputFocus"
  },
}

 

プログラミングカテゴリの最新記事