【キラーアプリ】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して動作するように変更したものをリリースしています。

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

VSCode自体のコマンドはcall VSCodeNotify()からコールすることでマッピング可能です。

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

コマンドIDはキーボートショートカットの設定から右クリック「コマンドIDのコピー」から取得できます。

VSCodeに同等機能が実装されている場合はコールした方が良いです。
ファイル保存であれば2のように指定します。

1. nmap <leader>w :w<cr>
2. nmap <leader>w <cmd>call VSCodeNotify(‘workbench.action.files.save’)<cr>

一見、どちらも同じことをしているように見えますが、1の方法で実行していると稀に(1日1回ぐらい)VSCode Neovimがバグります。
何かキーを押すと勝手にEXコマンド入力欄が出たり、カーソルが動かないといった状態になり、VSCodeを再起動しないと直りません。

2の方法で実行すると理由は分かりませんが私の環境ではバグって変な動きになる事がほとんどなくなりました。
NeovimはVSCodeをキックするだけなので余計なトラブルが起きづらいのかもしれません。

init.vim設定例

私のinit.vimを記載しておきます。
Neovimで使用していたプラグインからLSPや補完、デバッグなどのVSCodeで賄えるものを除外すると半分以下になりました。

call plug#begin(stdpath('data') . '/plugged')

" ヤンクした箇所をハイライト
Plug 'machakann/vim-highlightedyank'
" vim-docを日本語化
Plug 'vim-jp/vimdoc-ja'
" コード内ジャンプ
Plug 'easymotion/vim-easymotion'
" 文字列を括弧等で囲む
Plug 'machakann/vim-sandwich'
" カーソル位置の単語で検索
Plug 'thinca/vim-visualstar'
" ヤンクしている内容で置換
Plug 'vim-scripts/ReplaceWithRegister'
" *モーション拡張
Plug 'haya14busa/vim-asterisk'
" テキストオブジェクト拡張
Plug 'wellle/targets.vim'
" カーソルをエッジへ移動
Plug 'haya14busa/vim-edgemotion'
" f拡張
Plug 'rhysd/clever-f.vim'

call plug#end()

" ====== key ======

let mapleader = "\<Space>"

" leave insert
inoremap jk <esc>
" focus explorer
nmap <leader>e <cmd>call VSCodeNotify('workbench.view.explorer')<cr>
nmap <leader>E <cmd>call VSCodeNotify('workbench.action.toggleSidebarVisibility')<cr>
" symbol
nmap <Leader>o <Cmd>call VSCodeNotify('workbench.action.gotoSymbol')<CR>
nmap <Leader>t <Cmd>call VSCodeNotify('workbench.action.showAllSymbols')<CR>
" tab move
nmap <Leader>> <Cmd>call VSCodeNotify('workbench.action.moveEditorRightInGroup')<CR>
nmap <Leader><lt> <Cmd>call VSCodeNotify('workbench.action.moveEditorLeftInGroup')<CR>
" diagnotics jump
nmap ]d <cmd>call VSCodeNotify('editor.action.marker.nextInFiles')<cr>
nmap [d <cmd>call VSCodeNotify('editor.action.marker.prevInFiles')<cr>
" search jump
nmap ]s <cmd>call VSCodeNotify('editor.action.nextMatchFindAction')<cr>
nmap [s <cmd>call VSCodeNotify('editor.action.previousMatchFindAction')<cr>
" change jump
nmap ]c <cmd>call VSCodeNotify('editor.action.dirtydiff.next')<cr>
nmap [c <cmd>call VSCodeNotify('editor.action.dirtydiff.previous')<cr>
" reference jump
nmap ]r <cmd>call VSCodeNotify('search.action.focusNextSearchResult')<cr>
nmap [r <cmd>call VSCodeNotify('search.action.focusPreviousSearchResult')<cr>

" save file
nmap <leader>w <cmd>call VSCodeNotify('workbench.action.files.save')<cr>
" close file
nmap <leader>d <cmd>call VSCodeNotify('workbench.action.closeActiveEditor')<cr>
" tab move
nmap <C-h> <cmd>call VSCodeNotify('workbench.action.previousEditor')<cr>
nmap <C-l> <cmd>call VSCodeNotify('workbench.action.nextEditor')<cr>
" tab only
nmap <C-w><C-o> <cmd>call VSCodeNotify('workbench.action.closeOtherEditors')<cr>

" cursor move
nnoremap <S-j> 5j
nnoremap <S-k> 5k
vnoremap <S-j> 5j
vnoremap <S-k> 5k
nnoremap 0 ^
nnoremap ^ 0
vnoremap 0 ^
vnoremap ^ 0

" highlight off
nnoremap gq <cmd>nohlsearch<cr>

" yank
nnoremap Y y$

" paste
nnoremap p ]p
xnoremap p ]p

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

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

" ====== autocmd ======"

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

" input method disable
if executable('zenhan')
   autocmd InsertLeave * :call system('zenhan 0')
   autocmd CmdlineLeave * :call system('zenhan 0')
endif

" ====== plugins setting ======"

" vim asterisk"
let g:asterisk#keeppos = 1

map g* <Plug>(asterisk-z*)
map g# <Plug>(asterisk-z#)
map * <Plug>(asterisk-gz*)

" easymotion
map <Leader>f <Plug>(easymotion-bd-f)
map <Leader>k <Plug>(easymotion-k)
map <Leader>j <Plug>(easymotion-j)

"edgemotion
nmap gj <Plug>(edgemotion-j)
nmap gk <Plug>(edgemotion-k)
vmap gj <Plug>(edgemotion-j)
vmap gk <Plug>(edgemotion-k)

"highlightedyank
let g:highlightedyank_highlight_duration = 150

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