Vimでキャメルケースのスペルチェックをするプラグインを作った 〜 CCSpellCheck.vim 〜

# Vimでスペルチェック
Vimでもデフォルトで英単語のスペルチェックの機能は提供されています。

ただ、単にそれらを有効にするだけで、スネークケースのようにアンダースコア(_)で区切られた変数名はチェックできるのですが、キャメルケースのような区切り文字がないような変数名がチェックできないという問題がありました。

コードレビューでTypoの指摘をされるのを減らしたいのですが、やはりどんなに注意しててもTypoはあるものなので、凡人たる私は人力に頼らず自動でチェックしたいところです。

大抵、デフォルトに無い機能はVimのプラグインを探せば見つかる事が多いですが、今回は結構探したつもりなのですがそういったプラグインが無さそうなので、良い機会なので作ってみました。

## CCSpellCheck.vim 
今回作成したVimプラグインはこちらです。
https://github.com/kamykn/CCSpellCheck.vim

大文字で区切るような変数名や関数名などで使われている単語をチェックし、Typoがあればhighlightで目立たせます。

**利用イメージ：CaseをCeseとtypoした場合**
<img width="729" alt="スクリーンショット 2017-10-21 23.08.19.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/e32fd277-96ff-8690-4b72-3b76153e5fff.png">



## ざっくりとした仕様説明
1. Vimの[spell](http://vim-jp.org/vimdoc-ja/spell.html)を部分的に利用していますが、機能的には別のものになっています。
2. スペルチェックのタイミングはBufWinEnter,BufWritePostなので、基本的にはファイルを開いたタイミング、保存したタイミングになります。
3. 単語のチェックにspellbadword()を使っているので、VimのSpell機能の辞書、spellfile、internal-wordlistを利用しています。
4. spellfileに単語を登録するために、zgやzwを利用したコマンドを用意しています。
（詳しくはコマンドの項目参照）
5. spellsuggest()を利用した修正候補からのコードの修正ができます。


## インストール
NeoBundleならインストールは下記の通りです。

```vim
NeoBundle 'kamykn/CCSpellCheck.vim'
```


## セッティング
今のところ、設定項目は以下の通りです。
すべて設定しなくてもデフォルトの設定で動きます。

```vim
" スペルチェックを有効(1)/無効(0) 。(デフォルト 1)
let g:CCSpellCheckEnable = 1

" 何文字以上の文字数の単語を対象にするか。(デフォルト4)
" 例：4 なら getHogeIdという単語のうちの Hogeだけがチェックの対象になります。
let g:CCSpellCheckMinCharacterLength = 4

" 修正候補列挙時の列挙数 (デフォルト 50)
let g:CCSpellCheckMaxSuggestWords = 50


" highligheで利用できるグループ名の設定 (デフォルト 'CCSpellBad')
let g:CCSpellCheckMatchGroupName = 'CCSpellBad'

" highlightの設定の上書き設定 (デフォルト cterm=reverse ctermfg=yellow gui=reverse guifg=yellow)
highlight CCSpellBad cterm=reverse ctermfg=magenta gui=reverse guifg=magenta
```

## 利用できるコマンド
### 修正候補の選択
Normalモードで単語の上にカーソルをおき、下記のコマンドで一覧が開きます。
(Vimのspellsuggestのz=に相当)

```
Z=
```

<img width="438" alt="スクリーンショット 2017-10-21 23.28.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/a24ef52e-ce8e-6c55-e667-346462cf7b3d.png">

左端の番号を入力して選択します。(デフォルトのz=と一緒)

※ ハイライトされていない単語でもコマンドを実行すれば候補が表示されます。

### 単語の登録
上記の画像の例のようなStrlenは正しい単語としてほしいですので正しい単語としてvimに登録しましょう。
spellfileに単語を登録するために、zgやzwを利用したコマンドを用意しています。
いずれも、visualモードで登録したい単語を選択した状態で、次のコマンドを入力します。

```vim
" 正しい単語として登録
Zg

" 正しくない単語として登録
Zw

" 正しい単語リストから削除
# => zug
Zug

" 正しくない単語リストから削除
# => zuw
Zuw

" 以下、internal-wordlist(一時的に有効なリスト)に登録
" 正しい単語として一時的に有効なリスト登録
ZG

" 正しくない単語として一時的に有効なリスト登録
ZW

" 一時的に有効な正しい単語リストから削除
ZUG

" 一時的に有効な正しくない単語リストから削除
ZUW
```

**正しい単語として登録する例**
登録したい選択範囲をVisualモードで選択し、
<img width="441" alt="スクリーンショット 2017-10-21 23.03.39.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/d355a1aa-49b9-48b0-2465-c20a1c7868df.png">

Zgを押下
<img width="411" alt="スクリーンショット 2017-10-21 23.03.15.png" src="https://qiita-image-store.s3.amazonaws.com/0/73446/15e0f78a-cd9f-e269-3863-81cabc8235c7.png">


## プラグイン作成中の紆余曲折的なお話
### 単語チェックを何でチェックするか
最初にLinuxでスペルチェックをする方法をぐぐったところ、Aspellというのがあったので開発初期はAspellでチェックさせたりしてました。
確か、修正候補の単語リストがほしい形で取れなかったのと(うろ覚え)、Aspell用のチェック用単語リストが必要になること、そもそもAspellをインストールしなければいけないことが微妙だなと思い始めた矢先、Vimのデフォルトのスペルチェックでギリギリ実装可能っぽいことがわかり、Aspellでのチェックをやめました。

### スペルチェックのタイミング
今は基本的にファイルを開くタイミングと、保存するタイミングでチェックが走ります。
このタイミングで走らせるとなると、開いているバッファの単語をすべてチェックすることになるので、一見、筋が悪そうです。

画面がスクロールするタイミングで表示範囲だけチェックすれば対象となる単語数がかなり減りそうですが、チェックの処理が意外と時間がかかり、スクロールに支障がありました。多分、単語チェック度にspellfileのファイル読み込みがあるっぽいような（詳しく調べてない）。
(あと、spellfileに単語が多くなると、書き込みにも時間がかかるっぽいです。)

Vimでスクロールにストレスがあるのは致命的だなと思ったので、Vimの中では『待つタイミング』である開く/保存のタイミングに混ぜてしまったほうがストレスが自分としては少なかったので、これで採用しました。


## まとめ
今回、初めてVimscriptを一から書きました。
書く準備としては[Vim-script-テクニックバイブル-](https://www.amazon.co.jp/Vim-script-テクニックバイブル-Vim使いの魔法の杖-scriptサポーターズ/dp/4774166340/)を読みました。plugin作成するためのお作法とか書いてあったりして、大変お世話になりました。

Vimはやはり機能を自分で拡張しやすいのがいいですね。プラグイン作ることでvimの理解も深まりますし、プラグイン作成で得られた知識は.vimrcにちょっとしたこと書くときにも使えますし。

そして、しばらくテスト運用として仕事で使うVimにプラグインを入れて使っていたのですが、狙いである方のTypoは無事に減らすことが出来ました。また、意外とチームでtypoしているというのがよく見えるようになりました。元は自分がコードレビューで指摘されるのを減らす目的でしたが、やっぱり人力にはある程度限界があるなと思いました。

…あと、もしかしたら使ってくれる人がいたとして、誰かの.vimrcにkamyknの名前が記される事となることを考えると、ちょっとテンション上がりますね。いや、本当に使ってくれる人がいたらですけど。