VimをCUIコマンドに組み込む/dotfiles晒し

# はじめに

こいついつも.vimrc弄ってんなと言われてはや数年、今年も弄ってます。

今回は二本立てになっています、ちなみにWindowsでKaoriya版のGVimを使ってますので、ご自身の環境への読み替えをお願いします。

## VimをCUIコマンドに組み込む

僕は開発中のビルドにQuickRunの他に素のコマンドプロンプトもよく利用しているのですが、といいますかまずはコマンドプロンプトでVimに依存しない開発環境を整えて、それをVimの開発に落とし込むという手順を踏んでいるのですが、そうするとコマンドプロンプトとVimの連携もしたくなってくるかと思いますので、対応できるようにしました。

成果物はこちらです、コマンドプロンプト上で各種言語のLinterやビルドのエラーメッセージにVimのerrorformatを適用してフォーマットを統一し、fzf(pecoの親戚みたいなの)でエラー箇所を選択し、GVimで開くためのツールキットです。
[wordijp/mycommands - (GitHub)](https://github.com/wordijp/mycommands)

この中で言語別のerrorformatの適用にVimをCUIコマンドとして呼び出しています。

### デモ

![demo.gif](https://qiita-image-store.s3.amazonaws.com/0/51728/a3f0aeeb-7da6-1cc7-a077-6fe6ce1298eb.gif)

### CUIコマンドとして呼び出している部分

この呼び出し部分もCUIコマンドにしています、batファイルで書かれていますが、Windows上の色々不便な部分[^1]を解消するためにRustで書かれたコマンド経由で呼び出すようにしています。
~~こんな事するんなら最初からLinux環境使えという言葉はやめてくださいしんでしまいます~~

[^1]: batファイルの「バッチ ジョブを終了しますか (Y/N)?」を出さなくしたり、SIGPIPEに疑似的に対応したり。

下記がそのソースです。

```bat
@rem internal\vim-loclist.bat

@echo off
@rem エラー一覧をQuickFixロケーションリストへ変換する

if "%1" == "" (
  echo "usage) vim-loclist <filetype>"
  exit /B 0
)

vim - -es +"source $VIM/vimrc" +"set nonumber" +"set filetype=%1" +":lgetbuffer" +":lopen" +:%%p +:q! +:q! | tail +2 | grep -v -E "^\|\|\s*$"
```

エラーメッセージをパイプ経由でこのコマンドに渡す事を前提としており、QuickFix経由で言語別のerrorformatを適用し標準出力に流します、errorformatは言語別に設定が違い自動判別も難しいため、filetypeだけは引数で任意に指定する必要があります。

こうすると、PHPの場合は

```
php -l | vim-loclist php | fzf(or peco)
```

C++の場合は

```
make 2>&1 1>nul | vim-loclist cpp | fzf(or peco)
```

という風にerrorformat適用後の一覧の選択ができ、さらに後ろにGVimで開くコマンドを追加すればコマンドプロンプトでのエラー結果をGVimで開けます。

ちなみにエラー結果をGVimの起動引数に変換するのもCUIコマンドにしています。
※shはMSYS2で対応しています。

```sh
# internal/vim-arg.sh
cat - | \
	sed -u -E 's/([^|]+)\|([0-9]+)( col [0-9]+)?\|.+$/+\2 \1/'
```

### 参考

- [vimをパイプにする](http://auewe.hatenablog.com/entry/2016/12/03/001000)



## dotfiles晒し

今年、Language Server Protocol(以下LSP)が登場し、コーディングツールキットに革命の波が押し寄せてきました、なのでLSPに対応した.vimrc含めdotfilesを晒していこうかと思います。

[wordijp/dotfiles-win - (GitHub)](https://github.com/wordijp/dotfiles-win)
※PHP用のQuickRun設定では一つ目に紹介したmycommands内のコマンドを呼び出しています。

Kaoriya版Vim用の.vimrcですが、ファイル群を%USERPROFILE%(C:/Users/user)に置けば済むようにしています~~、makeは[Make for Windows](http://gnuwin32.sourceforge.net/packages/make.htm)前提です~~。

```
> make install
```

この設定は下記の各種言語にて統一された操作感を持つ事を目的としています、頻繁に言語をまたいで開発しているためVimが手放せません。[^2]

[^2]: 流石にPHPに絞ってPHP Storm使えと言われるとグゥの音も出ないです。

- C/C++
- Rust
- PHP
- Ruby
- Python
- Go
- JavaScript

なお、各種言語で静的構文解析ジャンプ・コード補完・Linter/ビルドのために、LSPの他に[Valloric/YouCompleteMe - (GitHub)](https://github.com/Valloric/YouCompleteMe)(以下YCM)、[w0rp/ale - (GitHub)](https://github.com/w0rp/ale)、[fatih/vim-go - (GitHub)](https://github.com/fatih/vim-go)、[racer-rust/vim-racer - (GitHub)](https://github.com/racer-rust/vim-racer)なども利用しています、将来的にはLSPに一本化されるとは思うのですが、まだまだ発展途上のため各種プラグインを併用した状態になっています。

例えば、静的構文解析ジャンプは次のようにしてます、めんどくさい。

```vim
" .vimrc
" 静的構文解析ジャンプ
let g:go_def_mapping_enabled = 0 "自前でマッピング
nnoremap <F12> :call <SID>defJump()<CR>
nnoremap <C-]> :call <SID>defJump()<CR>
nnoremap <C-h> :vsp<CR>:call <SID>defJump()<CR>
nnoremap <C-k> :split<CR>:call <SID>defJump()<CR>
function s:defJump()
  if &ft == 'go'
    :GoDef
  elseif &ft ==# 'cpp' || &ft ==# 'php' || &ft ==# 'ruby'
    " 実装へジャンプ
    :call LanguageClient#textDocument_definition()
  elseif &ft ==# 'rust'
    :execute "normal \<Plug>(rust-def)"
  elseif &ft ==# 'javascript' || &ft ==# 'javascript.jsx' || &ft ==# 'typescript' || &ft ==# 'python'
    :YcmCompleter GoToDefinition
  else
    :exe("tjump ".expand('<cword>'))
  end
endfunction
```

### PHPのLSP対応

WindowsでPHPのLSPを動かすには[felixfbecker/php-language-server - (GitHub)](https://github.com/felixfbecker/php-language-server)を使うのですが、こちらは[Windowsでは標準入出力経由では動きません](https://github.com/felixfbecker/php-language-server#command-line-arguments)、Tcp経由にする必要があります。
VSCodeのプラグインはそのようにしてるのですがVim用は探す限り見つかりませんでした、なのでTcp経由で接続するVim用プラグインを自作しています。
[wordijp/LanguageServer-php-tcp-neovim - (GitHub)](https://github.com/wordijp/LanguageServer-php-tcp-neovim)

```vimscript
" .vimrc
" Tcp経由のphp-language-serverインストール
Plug 'wordijp/LanguageServer-php-tcp-neovim', {
  \ 'do': 'bash ./install.sh && composer install && composer run-script parse-stubs'
  \ }
```

上記の制約により標準入出力のみの[prabirshrestha/vim-lsp - (GitHub)](https://github.com/prabirshrestha/vim-lsp)は使うことが出来なくなります、今のところは[autozimu/LanguageClient-neovim - (GitHub)](https://github.com/autozimu/LanguageClient-neovim)しかないかと思います。

### denite

大きそうな設定なのでこちらも記載。
速度と利便性を兼ね合いした結果、ファイル検索はfindより高速と評判の[sharkdp/fd - (GitHub)](https://github.com/sharkdp/fd)を、grepはUTF-8・Shift_JIS両対応で速度のバランスが良い[monochromegane/the_platinum_searcher(通称PT) - (GitHub)](https://github.com/monochromegane/the_platinum_searcher)を利用しています。
除外は検索時点で省くのが最速だろうって事で直に設定しています。

```vim
" .vimrc
" denite
" find source
" NOTE: 除外もこっち
call denite#custom#var('file_rec', 'command',
  \ ['fd', '.', '-HI', '--type', 'f',
  \ '-E', '.git',
  \ '-E', 'vendor',
  \ '-E', 'node_modules',
  \ '-E', 'target',
  \
  \ '-E', '*.bak',
  \ '-E', '*.o',
  \ '-E', '*.obj',
  \ '-E', '*.pdb',
  \ '-E', '*.exe',
  \ '-E', '*.bin',
  \ '-E', '*.dll',
  \ '-E', '*.a',
  \ '-E', '*.lib',
  \ '-E', '.gitignore',
  \ '-E', '.*.*',
  \ ])
call denite#custom#source('file_rec', 'matchers', ['matcher_fuzzy'])

" grep source
" NOTE: ptでutf8、sjis両対応
call denite#custom#var('grep', 'command',
  \ ['pt', '--nogroup', '--nocolor', '--smart-case', '--hidden',
  \ '--ignore', '.git',
  \ '--ignore', 'vendor',
  \ '--ignore', 'node_modules',
  \ '--ignore', 'target',
  \
  \ '--ignore', '*.bak',
  \ '--ignore', '*.o',
  \ '--ignore', '*.obj',
  \ '--ignore', '*.pdb',
  \ '--ignore', '*.exe',
  \ '--ignore', '*.bin',
  \ '--ignore', '*.dll',
  \ '--ignore', '*.a',
  \ '--ignore', '*.lib',
  \ '--ignore', '.gitignore',
  \ '--ignore', '.*.*',
  \ ])
call denite#custom#var('grep', 'default_opts', [])
call denite#custom#var('grep', 'recursive_opts', [])
call denite#custom#source('grep', 'matchers', ['matcher_fuzzy'])
```

ショートカットキーは

```vim
" .vimrc
" ctrlp like
nnoremap <silent> <C-p> :<C-u>Denite file_rec<CR>
" grep(選択単語)
nnoremap + :<C-u>Denite -buffer-name=search -no-empty grep -input=<C-R><C-W>
" grep
nnoremap <silent> ;g :<C-u>Denite -buffer-name=search -no-empty grep<CR>
" 検索
nnoremap <silent> ;/ :<C-u>Denite -buffer-name=search -auto-resize line<CR>
" 閉じたバッファをまた開く
nnoremap <silent> ;r :<C-u>Denite -buffer-name=search -resume<CR>
" 次へ
nnoremap <silent> ;n :<C-u>Denite -buffer-name=search -resume -immediately -select=+1<CR>
" 前へ
nnoremap <silent> ;p :<C-u>Denite -buffer-name=search -resume -immediately -select=-1<CR>
```

## 終わりに

凄く属人的な気がしますが助力になれば幸いです。