3年間育てた自分のvimrcを解説

# はじめに
Vim歴多分3年ぐらいです。
育て上げたvimrcをざっくりと分割して解説してみるテスト。
良くない記述方法や改善点あれば是非コメントで教えてください。
このvimrcファイルは[こちら](https://github.com/subebe/dotfiles/blob/e5108e2a706eb84e23ad40b43b248243b55c6f82/.vim/vimrc)。


# 初期設定
## 初期設定 Part1
お決まりの設定。
`set nocompatible` は最近の環境では必要ないらしいが念のため。
`MyAugroup` はプラグイン設定でも使用している為最初の方に記述。

```vim
if 0 | endif
set encoding=utf-8
scriptencoding utf-8

if &compatible
  set nocompatible
endif

let s:is_windows = has('win16') || has('win32') || has('win64')
let s:is_mac = has('mac')

augroup MyAugroup
  autocmd!
augroup END
```

## 初期設定 Part2
vimで使うディレクトリやファイルの場所を設定。
Windows環境の時はファイルパスの `\` を `/` にする `set shellslash` を使用。
プラグインがうまく動かない場合があるが、今のところ大丈夫。

```vim
if s:is_windows
  set shellslash
  let s:vimfiles_dir = expand('~/vimfiles')
else
  let s:vimfiles_dir = expand('~/.vim')
endif

let s:vimcache_dir   = expand('~/.cache/vim')

let s:viminfo        = s:vimcache_dir.'/viminfo'
let s:backup_dir     = s:vimcache_dir.'/backup'
let s:swap_dir       = s:vimcache_dir.'/swap'
let s:undo_dir       = s:vimcache_dir.'/undo'
let s:view_dir       = s:vimcache_dir.'/view'

let s:dein_dir       = s:vimcache_dir.'/dein.vim'
let s:dein_toml      = s:vimfiles_dir.'/rc/dein.toml'
let s:dein_cache_dir = expand('~/.cache/dein')

let s:local_vimrc    = s:vimfiles_dir.'/local.vimrc'
```

## 初期設定 Part3
vimで使うディレクトリの作成とdeinのインストールを行う。

```vim
function! s:make_dir(dir) abort
  " silent! call mkdir(dir, 'p')
  if !isdirectory(a:dir)
    call mkdir(a:dir, 'p')
  endif
endfunction
call s:make_dir(s:vimcache_dir)
call s:make_dir(s:backup_dir)
call s:make_dir(s:swap_dir)
call s:make_dir(s:undo_dir)
call s:make_dir(s:view_dir)

if !isdirectory(s:dein_dir)
  echomsg 'dein.vim does not exists.'
  if executable('git')
    execute '!git clone https://github.com/Shougo/dein.vim' s:dein_dir
  else
    echomsg 'git does not exists.'
  endif
endif
```

## 初期設定 Part4
`<Leader>` のキーマップにに `,` を使用する。
以前は [clever-f.vim](https://github.com/rhysd/clever-f.vim) を使用していたので `,` は不要でしたが、消してしまったので `,` が使えなくて不便。

```vim
let g:mapleader = ','
```

## 初期設定 Part5
`guioption` はGVimをCLI風にする設定。
下の方の記述はGUIでない時の設定ははモードごとにカーソルを切り替える用。
フォントはRictyを使用。
`macmeta`はMacVim用。

```vim
if has('gui_running')
  set guioptions-=a guioptions-=i
  set guioptions-=r guioptions-=L
  set guioptions-=e guioptions-=m guioptions+=M guioptions-=t guioptions-=T guioptions-=g
  set guioptions+=c

  if s:is_windows
    set renderoptions=type:directx,renmode:5
    set guifont=Ricty_Diminished:h13.5:cSHIFTJIS,MS_Gothic:h13
  elseif s:is_mac
    set guifont=Ricty\ Regular:h17
    set macmeta
    set transparency=10
  endif
else
  let &t_ti .= "\e[1 q"
  let &t_SI .= "\e[5 q"
  let &t_EI .= "\e[1 q"
  let &t_te .= "\e[0 q"
endif
```

# dein.vim設定
deinの設定。
プラグインの設定内容はまた元気がある時にやるかもしれない。
toml読み込みは遅いらしいがdein.vimはキャッシュ機能があるので問題なし。
`call dein#call_hook('source')` は [vim-singleton](https://github.com/thinca/vim-singleton) を起動するための設定。
`silent! execute 'helptags' s:dein_dir . '/doc/'` はdein.vimのヘルプを生成するための行。

```vim
if isdirectory(s:dein_dir) && executable('git') && (executable('rsync') || executable('xcopy'))
  let g:dein#install_process_timeout = 600
  let g:dein#install_progress_type = 'tabline'
  if has('vim_starting')
    execute 'set runtimepath^='.s:dein_dir
  endif
  if dein#load_state(s:dein_dir)
    call dein#begin(s:dein_cache_dir)
    call dein#add(s:dein_dir, {'rtp': ''})

    call dein#load_toml(s:dein_toml)

    call dein#end()
    call dein#save_state()
  endif

  call dein#call_hook('source')

  if dein#check_install()
    call dein#install()
  endif
  " if dein#check_update()
  "   call dein#update()
  " endif

  silent! execute 'helptags' s:dein_dir . '/doc/'
endif
filetype plugin indent on
```

# Options
この辺りは好み。
知らないオプションが出てきたときは `:help 'オプション名'` で引くと良い。
`:option` コマンドや `:help options-list` でどんなオプションがあるか確認するのもオススメ。
optionは `:options` コマンドの順番通りに(多分)設定している。

## Options Part1
`set tags+=tags;` は親ディレクトリのタグファイルも検出してくれる用設定。
`listchars` で不可視文字をいい感じに表示するように。

```vim
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Options
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" important

" moving around, searching and patterns
set whichwrap+=b,s,h,l,<,>,~,[,]
set incsearch
set ignorecase
set smartcase

" tags
set tags+=tags;

" displaying text
set scrolloff=5
set breakindent
set showbreak=>\ 
set display+=lastline
set redrawtime=500
set list
set listchars=tab:￫\ ,eol:⏎,space:･,extends:>,precedes:<
set number
set conceallevel=2
set concealcursor=nc

" syntax, highlighting and spelling
set hlsearch
nohlsearch

" multiple windows
set laststatus=2
set statusline=%F%m%r
set statusline+=%=
set statusline+=\ \|\ %{&fileformat},%{&fileencoding}%{(&bomb?\"(bom)\":\"\")}
set statusline+=\ \|\ %Y
set statusline+=\ \|\ %l/%L,%c
" set noequalalways
set splitright
```

## Options Part2
tablineをいい感じに表示するvimscript。
書いた時の記憶があんまりないので内容は覚えてないけどTabの幅とかを自動で調節するので便利。
内容に意味は無いのでスキップ推奨->[スキップ](#options-part3)

```vim
" multiple tab pages
set showtabline=2
set tabline=%!MyTabLine()

function! MyTabLine()
  if !exists("s:tabline")
    let s:tabline = s:tab(16, 32, 8)
  endif
  return s:tabline.gen()
endfunction

function! s:tab(size, maxsize, minsize) abort
  let self = {}

  let self.size = a:size
  let self.maxsize = a:maxsize
  let self.minsize = a:minsize

  function! self.gen() abort dict
    let left = self.left()
    let right = self.right()
    let leftsize = strdisplaywidth(left)
    let rightsize = strdisplaywidth(right)

    let width = &columns
    let middlesize = width - leftsize - rightsize
    let middle = self.middle(middlesize)
    return left.middle.'%=%<'.right
  endfunction

  function! self.left() abort dict
    return ' '
  endfunction
  function! self.right() abort dict
    return ' '
  endfunction

  function! self.label(n) abort dict
    let buflist = tabpagebuflist(a:n)
    let curbuf = buflist[tabpagewinnr(a:n) - 1]
    let bufcount = len(buflist) > 1 ? len(buflist) : ''

    let tabmod = len(filter(copy(buflist), 'getbufvar(v:val, "&modified")')) ? '(+)' : ''
    let tabmod = getbufvar(curbuf, '&modified') ? '+' : tabmod

    let fname = fnamemodify(bufname(curbuf), ':t')
    let quickfix = getbufvar(curbuf, '&buftype') !~ 'quickfix' ? '' : '[Quickfix]'
    let nofile = getbufvar(curbuf, '&buftype') !~ 'nofile' ? '' : '[下書き]'

    let name = '[無題]'
    let name = len(quickfix) ? quickfix : name
    let name = len(nofile) ? nofile : name
    let name = len(fname) ? fname : name

    let label = ' '.a:n.tabmod.':'.bufcount.' '.name.' '
    return label
  endfunction

  function! self.middle(middlesize) abort dict
    let sep = '|'
    let labels = []
    for tabnr in range(1, tabpagenr('$'))
      let label = self.label(tabnr)
      let label = self.labelformat(label, sep, self.size, self.maxsize)
      call add(labels, label)
    endfor

    let maxsize = self.maxsize
    while a:middlesize < self.labelssize(labels, sep) && maxsize > self.size
      let maxsize = maxsize - 1
      let i = 0
      while i < len(labels)
        if maxsize < strdisplaywidth(labels[i].sep)
          let labels[i] = self.labelformat(labels[i], sep, self.size, maxsize)
        endif
        let i = i + 1
      endwhile
    endwhile

    if a:middlesize < self.labelssize(labels, sep)
      let persize = a:middlesize / tabpagenr('$')
      let persize = max([persize, self.minsize])
      let i = 0
      while i < len(labels)
        let labels[i] = self.labelformat(labels[i], sep, self.size, persize)
        let i = i + 1
      endwhile
    endif

    let i = 0
    let strlabels = ''
    while i < len(labels)
      let tabn = i + 1
      let hl = tabn is tabpagenr() ? '%#TabLineSel#' : '%#TabLine#'
      let hl .= '%'.tabn.'T'
      let strlabels .= hl.labels[i].'%#TabLineFill#'.sep
      let i = i + 1
    endwhile

    return strlabels
  endfunction

  function! self.labelssize(labels, sep) abort dict
    let sum = 0
    for label in a:labels
      let sum = sum + strdisplaywidth(label.a:sep)
    endfor
    return sum
  endfunction

  function! self.substring(src, start, end) abort dict
    let splitlabel = split(a:src, '\zs')
    return join(splitlabel[a:start : a:end], '')
  endfunction

  function! self.labelformat(label, sep, size, maxsize) abort dict
    let label = a:label
    let label .= repeat(' ', a:size - strdisplaywidth(a:label.a:sep))

    let i = strchars(label)
    while strdisplaywidth(label.a:sep) > a:maxsize
      let label = self.substring(label, 0, i).'> '
      let i -= 1
    endwhile

    return label
  endfunction

  return self
endfunction
```

## Options Part3
backup、swap、undo、viewファイルは邪魔にならない場所に生成しているので一応使用。
`keywordprg`は `<S-k>` 押した時にVimのヘルプを引く設定。

```vim
" terminal
" using the mouse
set mouse=a

" GUI
" printing
set printheader=%<%t%h%m%=%N

" messages and info
set novisualbell
set noerrorbells
set showcmd
set belloff=all
set helplang=ja,en

" selecting text
set clipboard+=unnamed

" editing text
set backspace=indent,eol,start
set pumheight=10
set matchtime=1
set matchpairs+=<:>
set nrformats=hex

" tabs and indenting
set tabstop=4 " tab width
set shiftwidth=2 " auto indent width
set softtabstop=2 " tabkey space
set expandtab " softtab
set autoindent
set smartindent

" folding
" diff mode
" mapping
" reading and writing files
set backup
execute 'set backupdir='.s:backup_dir

" the swap file
execute 'set directory='.s:swap_dir
set swapfile

" command line editing
set history=10000
set wildmode=full
set wildmenu
set undofile
execute 'set undodir='.s:undo_dir

" executing external commands
set keywordprg=:help

" running make and jumping to errors
" system specific
" language specific
set iminsert=0
set imsearch=0

" multi-byte characters
set fileencodings=utf8,cp932,eucjp
set ambiwidth=double

" various
set viewoptions-=options
execute 'set viewdir='.s:view_dir
execute 'set viminfo+=n'.s:viminfo

" php
let g:PHP_vintage_case_default_indent = 1
```

# AutoCommands
内容はざっくり以下の通り。

- viewファイルを作成、ロードする
- 自動で開いているファイルにcd(`set autochdir` で可能ですが、プラグインで不具合が出たのでautocmdで)
- vimgrepで一部の拡張子を除外
- QuickFixを自動で開く
- 各言語用インデント設定
- IMEの状態によってカーソルの色を変更
- QuickFixを見やすく

```vim
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" AutoCommands
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
augroup MyAugroup
  " view
  autocmd BufWritePost ?* if expand('%') != '' && &buftype !~ 'nofile' | mkview | endif
  autocmd BufWinEnter ?* if expand('%') != '' && &buftype !~ 'nofile' | silent loadview | endif

  " set autochdir
  autocmd BufEnter * execute 'silent! lcd '.expand('%:h')

  " auto jump
  autocmd BufReadPost * if line("'\"") > 1 && line("'\"") <= line('$') | execute 'normal! g`"' | endif

  " quickfix
  autocmd QuickFixCmdPre vimgrep setlocal wildignore=*.gif,*.png,*.jpg,*.swf,*.flv,*.mp4,*.pdf,*.ttf,*.mp3,*.wav,tags
  autocmd QuickFixCmdPost vimgrep setlocal wildignore<
  autocmd QuickFixCmdPost *grep* cwindow

  " ftplugin
  autocmd FileType php setlocal tabstop=4 shiftwidth=4 softtabstop=4
  autocmd FileType javascript setlocal tabstop=2 shiftwidth=2 softtabstop=2
  autocmd FileType json setlocal conceallevel=0
  autocmd FileType html setlocal tabstop=2 shiftwidth=2 softtabstop=2
  autocmd FileType qf call s:MyFTPluginQuickfix()

  " ftdetect
  autocmd BufNewFile,BufRead *.tpl set filetype=php

  " syntax
  autocmd VimEnter,ColorScheme * call s:MyHighlight()

augroup END

function! s:MyHighlight() abort
  if has('multi_byte_ime') || has('xim')
    " highlight Cursor guibg=DarkRed guifg=NONE
    highlight CursorIM guibg=Green guifg=NONE
  endif
endfunction

function! s:MyFTPluginQuickfix() abort
  setlocal statusline=%t%{exists('w:quickfix_title')?\ '\ '.w:quickfix_title\ :\ ''}\ %=%l/%L
  setlocal nowrap
  setlocal cursorline
endfunction
```

# Commands
内容は以下の通り。
いろいろな所から拝借。

command     | 内容
----------- | -----------
Wsource     | vimscriptファイルを読み込んで実行する<br>vimrc書いてるときとかに便利
CdCurrent   | 今開いているファイルのディレクトリにcdする
Scratch     | 保存しない一時ファイルを作成する
DiffOrig    | 最後に保存したファイルとdiffする
VimGrepFile | 開いているファイルでvimgrepする
VimGrepDir  | カレントディレクトリでvimgrepする

```vim
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Commands
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
if exists('$MYVIMRC')
  command! EditMyVimrc edit $MYVIMRC
endif
command! Wsource if &filetype == 'vim' | write | source % | else | echo 'this is not vimfile.' | endif
command! CdCurrent cd %:p:h
command! -bang Scratch execute '<bang>' == '!' ? 'tabedit' : 'new' | setlocal buftype=nofile noswapfile
command! DiffOrig vert new | set buftype=nofile | read ++edit # | 0delete_ | diffthis | wincmd p | diffthis
command! -nargs=1 VimGrepFile execute 'vimgrep /\v<args>/j %:p'
command! -nargs=1 VimGrepDir execute 'vimgrep /\v<args>/j **'
```


# Mapping
わかりにくそうなやつだけ以下にピックアップ。

- ビジュアルモードで行ごとに `.` , `@` を使えるように。
- `<S-カーソルキー>` でウィンドウの大きさを調節。連打できるので便利。
- `<Space>` をプレフィックスとしてよく使うコマンドのエイリアスにしてる。
- `-` はオプション切り替え系。

```vim
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Mappings
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
nnoremap <ESC><ESC> :<C-u>nohlsearch<CR><ESC>

nnoremap Y y$

nnoremap <expr> gj &wrap ? 'j' : 'gj'
nnoremap <expr> gk &wrap ? 'k' : 'gk'
nnoremap <expr> j &wrap ? 'gj' : 'j'
nnoremap <expr> k &wrap ? 'gk' : 'k'

vnoremap <expr> gj &wrap ? 'j' : 'gj'
vnoremap <expr> gk &wrap ? 'k' : 'gk'
vnoremap <expr> j &wrap ? 'gj' : 'j'
vnoremap <expr> k &wrap ? 'gk' : 'k'

nnoremap x "_x
vnoremap x "_x
nnoremap X "_X
vnoremap X "_X

" nnoremap s <Nop>
" vnoremap s <Nop>
" nnoremap S <Nop>
" vnoremap S <Nop>

cnoremap <C-p> <Up>
cnoremap <C-n> <Down>

nnoremap <C-n> :<C-u>tabnext<CR>
nnoremap <C-p> :<C-u>tabprevious<CR>

vnoremap . :normal .<CR>
vnoremap @ :<C-u>execute "'<,'>normal @".nr2char(getchar())<CR>

nnoremap <S-UP> <C-w>+
nnoremap <S-Down> <C-w>-
nnoremap <S-Left> <C-w><
nnoremap <S-Right> <C-w>>
nnoremap <C-UP> <C-w>K
nnoremap <C-Down> <C-w>J
nnoremap <C-Left> <C-w>H
nnoremap <C-Right> <C-w>L

noremap <Space> <Nop>
nnoremap <Space>j o<ESC>
nnoremap <Space>k O<ESC>
nnoremap <Space>w :<C-u>write<CR>
nnoremap <Space>q :<C-u>quit<CR>
nnoremap <Space>Q :<C-u>quit!<CR>
nnoremap <Space>bd :<C-u>bdelete<CR>
nnoremap <Space><Space> %
nnoremap <Space>* *N
nnoremap <Space>g* g*N
nnoremap <Space>n :<C-u>tabnext<CR>
nnoremap <Space>p :<C-u>tabprevious<CR>
nnoremap <Space>t :<C-u>tabedit<CR>

nnoremap - <Nop>
nnoremap -fh :<C-u>setfiletype html <Bar> set filetype?<CR>
nnoremap -fj :<C-u>setfiletype javascript <Bar> set filetype?<CR>
nnoremap -fp :<C-u>setfiletype php <Bar> set filetype?<CR>
nnoremap -fs :<C-u>setfiletype sql <Bar> set filetype?<CR>
nnoremap -fv :<C-u>setfiletype vim <Bar> set filetype?<CR>
nnoremap -fm :<C-u>setfiletype markdown <Bar> set filetype?<CR>
nnoremap -ft :<C-u>setfiletype text <Bar> set filetype?<CR>
nnoremap -eu :<C-u>setlocal fileencoding=utf8 fileencoding?<CR>
nnoremap -es :<C-u>setlocal fileencoding=sjis fileencoding?<CR>
nnoremap -w :<C-u>setlocal wrap! wrap?<CR>
nnoremap -c :<C-u>setlocal cursorline! cursorline?<CR>
nnoremap -n :<C-u>setlocal number! number?<CR>
nnoremap -t :<C-u>setlocal expandtab! expandtab?<CR>
nnoremap -i :<C-u>setlocal ignorecase! ignorecase?<CR>
nnoremap -h :<C-u>setlocal hlsearch hlsearch?<CR>

if exists('$MYVIMRC')
  nnoremap <F1> :<C-u>edit $MYVIMRC<CR>
  nnoremap <S-F1> :<C-u>tabedit $MYVIMRC<CR>
endif
execute 'nnoremap <F2> :<C-u>edit '.s:local_vimrc.'<CR>'
execute 'nnoremap <S-F2> :<C-u>tabedit '.s:local_vimrc.'<CR>'
execute 'nnoremap <F3> :<C-u>edit '.s:dein_toml.'<CR>'
execute 'nnoremap <S-F3> :<C-u>tabedit '.s:dein_toml.'<CR>'
```

# Colorscheme
最初に見つけたカラースキームを自動で読み込むやつ。
基本的に [vim-hybrid](https://github.com/w0ng/vim-hybrid) を使ってます。

```vim
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
" Colorscheme
""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""
function! s:find_colorscheme(colorschemes) abort
  for colorscheme in a:colorschemes
    let result = split(globpath(&runtimepath, 'colors/'.colorscheme['name'].'.vim'), '\n')
    if len(result)
      return colorscheme
    endif
  endfor
  return {'name': 'default', 'background': 'light'}
endfunction

if has('gui_running')
  let s:colorschemes = [
        \  {'name': 'hybrid', 'background': 'dark'},
        \  {'name': 'desert', 'background': 'dark'},
        \]
else
  let s:colorschemes = [
        \  {'name': 'desert', 'background': 'dark'},
        \]
endif
let s:colorscheme = s:find_colorscheme(s:colorschemes)
syntax enable
execute 'set background='.s:colorscheme['background']
execute 'colorscheme' s:colorscheme['name']
```

# local.vimrc読み込み
いろいろな環境でVimを使っているのでマシンごとの個別の設定を読み込んでいるファイルでやる。
例えばMacだとUSキーボードを使用しているのでコマンドを入力しやすくするために `;` と`:` を入れ替える設定をしています。

```vim
if filereadable(s:local_vimrc)
  execute 'source' s:local_vimrc
endif
```