読者です 読者をやめる 読者になる 読者になる

Λlisue's blog

つれづれなるままに更新されないブログ

あんまり見かけない気がする Vim の Tips 11 + 1 選

どうも、ご無沙汰しておりません Vimmer + Pythonista、 略して Vist... やめよう、こっちは縁起が悪い。 この記事は Vim Advent Calendar 2015 の 24日目の記事となります。

はじめに

自分が Mac OS X および Linux しか持っていないため、Windows で動かなかったらごめんなさい。 とりあえず書こうと思っていたものが未完成なので、他ではあまり見たことがない ~/.vimrc のお便利設定をまとめます。

不要なデフォルトプラグインを止める

しょっぱなからぶっ飛んだ設定ですが、僕は不要なデフォルトプラグインをすべて止めています。 以下設定

let g:loaded_gzip              = 1
let g:loaded_tar               = 1
let g:loaded_tarPlugin         = 1
let g:loaded_zip               = 1
let g:loaded_zipPlugin         = 1
let g:loaded_rrhelper          = 1
let g:loaded_2html_plugin      = 1
let g:loaded_vimball           = 1
let g:loaded_vimballPlugin     = 1
let g:loaded_getscript         = 1
let g:loaded_getscriptPlugin   = 1
let g:loaded_netrw             = 1
let g:loaded_netrwPlugin       = 1
let g:loaded_netrwSettings     = 1
let g:loaded_netrwFileHandlers = 1

これを ~/.vimrc の先頭に記載することで、以下のプラグインの読み込みが(完全には)行われません。

  • GZip ファイルの展開・閲覧
  • Tar ファイルの展開・閲覧
  • Zip ファイルの閲覧・展開
  • --remote-wait とか
  • TOhtml
  • Vimball の展開とかインストールとか
  • GetLatestVimScripts コマンドとか
  • Netrw 系全部

これらのプラグインは個人的には全く使わないため切ってあります。 「えーこんなの絶対使わないよ」という方は切ってもいいかも(デフォルトプラグインなのでサイドエフェクトがある可能性があります。バグっても自己責任で!)

GUI Vim 以外で matchparen をやめる

() の対応などを表示してくれる matchparen という機能ですが Terminal 上ではカーソルがとても見にくくなるため切ってあります。以下設定

if !has('gui_running')
  let g:loaded_matchparen = 1
endif

Vim 内部で利用する PATH の設定

「Terminal から起動した場合は動くけど GUI で起動した場合は動かない」などの現象はだいたいこれです。 僕は関数を定義して指定されたパスが存在し、 $PATH に存在していない場合のみ追加するようにしています。 これにより複数の OS でパスが異なる場合でもよしなに設定できます。

function! AddPath(pathlist) abort " {{{
  let pathlist = split($PATH, s:delimiter)
  for path in map(filter(a:pathlist, 'v:val'), 'expand(v:val)') 
    if isdirectory(path) && index(pathlist, path) == -1
      call insert(pathlist, path, 0)
    endif
  endfor
  let $PATH = join(pathlist, s:delimiter)
endfunction " }}}
call AddPath([
      \ '/usr/local/texlive/2013/bin/x86_64-linux',
      \ '/usr/local/texlive/2013/bin/x86_64-darwin',
      \ '~/.pyenv/bin',
      \ '~/.plenv/bin',
      \ '~/.rbenv/bin',
      \ '~/.ndenv/bin',
      \ '~/.pyenv/shims',
      \ '~/.plenv/shims',
      \ '~/.rbenv/shims',
      \ '~/.ndenv/shims',
      \ '~/.anyenv/envs/pyenv/bin',
      \ '~/.anyenv/envs/plenv/bin',
      \ '~/.anyenv/envs/rbenv/bin',
      \ '~/.anyenv/envs/ndenv/bin',
      \ '~/.anyenv/envs/pyenv/shims',
      \ '~/.anyenv/envs/plenv/shims',
      \ '~/.anyenv/envs/rbenv/shims',
      \ '~/.anyenv/envs/ndenv/shims',
      \ '~/.cabal/bin',
      \ '~/.vim/bundle/vim-themis/bin',
      \])

マウスのミドルクリックによる貼付けをやめる

たまにクリックしてしまってとても面倒なので完全に切ります

map <MiddleMouse>   <Nop>
map <2-MiddleMouse> <Nop>
map <3-MiddleMouse> <Nop>
map <4-MiddleMouse> <Nop>
imap <MiddleMouse>   <Nop>
imap <2-MiddleMouse> <Nop>
imap <3-MiddleMouse> <Nop>
imap <4-MiddleMouse> <Nop>

入力モードでは Emacs 的な移動を可能にする

大体 Normal モードで移動するように体が慣れていますが、ターミナルなどでは Emacs キーバインディングが一般的なので入力中でも Emacsキーバインドで移動できるようにしています。

inoremap <C-a> <C-o>^
inoremap <C-e> <C-o>$
inoremap <C-f> <C-o>w
inoremap <C-b> <C-o>b
inoremap <C-d> <C-o>x

" Emacs 的じゃないけど、これも
inoremap <C-h> <C-o>h
inoremap <C-j> <C-o>j
inoremap <C-k> <C-o>k
inoremap <C-l> <C-o>l

ウィンドウサイズを簡単に変更

デフォルトでは Ctrl-w < と、連続で押すのは大変なので Shift + めったに使わない矢印キーを割り当てています。

nnoremap <S-Left>  <C-w><<CR>
nnoremap <S-Right> <C-w>><CR>
nnoremap <S-Up>    <C-w>-<CR>
nnoremap <S-Down>  <C-w>+<CR>

トグル系マッピング

設定のトグル用に <Leader>s (Switch) を割り当てています。 ここで味噌なのは <Leader>s<Plug>(my-switch) をまず割り当てて、その後 <Plug>(my-switch) を使って実際のマッピングを割り当てているところです。こうすることにより :mapマッピングを表示した際にわかりやすくなります(あと prefix 変えたくなったら簡単に変えられる)。

nnoremap <Plug>(my-switch) <Nop>
nmap <Leader>s <Plug>(my-switch)
nnoremap <silent> <Plug>(my-switch)s :<C-u>setl spell! spell?<CR>
nnoremap <silent> <Plug>(my-switch)l :<C-u>setl list! list?<CR>
nnoremap <silent> <Plug>(my-switch)t :<C-u>setl expandtab! expandtab?<CR>
nnoremap <silent> <Plug>(my-switch)w :<C-u>setl wrap! wrap?<CR>
nnoremap <silent> <Plug>(my-switch)p :<C-u>setl paste! paste?<CR>
nnoremap <silent> <Plug>(my-switch)b :<C-u>setl scrollbind! scrollbind?<CR>
nnoremap <silent> <Plug>(my-switch)y :call <SID>toggle_syntax()<CR>
function! s:toggle_syntax() abort
  if exists('g:syntax_on')
    syntax off
    redraw
    echo 'syntax off'
  else
    syntax on
    redraw
    echo 'syntax on'
  endif
endfunction

w!! で sudo で保存

便利すぎるので、結構他でも見ますが紹介。僕の場合は sudo.vim で上書きしてしまいますが、プラグインをインストールしていない環境でも簡易的に使えるように設定してあります。

cabbr w!! w !sudo tee > /dev/null %

いい感じに Vim の mkview/loadview 機能を自動化する

Vim の mkview/loadview 機能を使うことで折りたたみのレベルやカーソル位置などを保持しておくことができ、上手に使うと便利なのですが、自動化すると誤爆が多いため手動で行っている方も多いと思います。 以下のように view が使えるか?使うべきか?みたいな関数を噛ませてやるといい感じに自動化出来ます。

function! s:is_view_available() abort " {{{
  if !&buflisted || &buftype !=# ''
    return 0
  elseif !filewritable(expand('%:p'))
    return 0
  endif
  return 1
endfunction " }}}
function! s:mkview() abort " {{{
  if s:is_view_available()
    silent! mkview
  endif
endfunction " }}}
function! s:loadview() abort " {{{
  if s:is_view_available()
    silent! loadview
  endif
endfunction " }}}
autocmd MyAutoCmd BufWinLeave ?* call s:mkview()
autocmd MyAutoCmd BufReadPost ?* call s:loadview()

設定ファイルのリロード

Vim は設定ファイルを書くために利用する時間が比較的に(飛躍的に)長いエディタなので、ささっと書いてささっと試せるようにリロードをマッピングで行えるようにしています。 リロード中に関数の上書きが出来ないので has('vim_starting') で囲っています。

if has('vim_starting')
  function s:reload_vimrc() abort
    execute printf('source %s', $MYVIMRC)
    if has('gui_running')
      execute printf('source %s', $MYGVIMRC)
    endif
    redraw
    echo printf('.vimrc/.gvimrc has reloaded (%s).', strftime('%c'))
  endfunction
endif
nmap <silent> <Plug>(my-reload-vimrc) :<C-u>call <SID>reload_vimrc()<CR>
nmap <Leader><Leader>r <Plug>(my-reload-vimrc)

特定ファイルの親ディレクトリーにワンステップで移動する

特定ファイルの親ディレクトリーに移動したいことって結構あると思います。 手で打ってもいいのですが、割と面倒なので関数化しています。 また my-workon-post というユーザー定義 autocmd を呼び出すことで任意の処理を実行できるようにしています(それを利用して Vimfiler のディレクトリを変更するようにしてる)。

function! s:workon(dir, bang) abort
  let dir = (a:dir ==# '' ? expand('%') : a:dir)
  " convert filename to directory if required
  if filereadable(dir)
    let dir = fnamemodify(expand(dir),':p:h')
  else
    let dir = fnamemodify(dir, ':p')
  endif
  " change directory to specified directory
  if isdirectory(dir)
    silent execute 'cd ' . fnameescape(dir)
    if a:bang ==# ''
      redraw | echo 'Working on: '.dir
      if v:version > 703 || (v:version == 703 && has('patch438'))
        doautocmd <nomodeline> MyAutoCmd User my-workon-post
      else
        doautocmd MyAutoCmd User my-workon-post
      endif
    endif
  endif
endfunction
autocmd MyAutoCmd VimEnter * call s:workon(expand('<afile>'), 1)
command! -nargs=? -complete=dir -bang Workon call s:workon('<args>', '<bang>')

これを記載すると autocmd MyAutoCmd VimEnter * ... の部分によって起動時にファイルが指定されていたら、そのファイルの親ディレクトリに自動的に移動します。また :Workon というコマンドを定義しているので編集中のバッファで :Workon とすると編集中のバッファの親ディレクトリに移動できます。

また、先にも述べましたが my-workon-post というユーザー適宜 autocmd を呼び出しているので、例えば下記のように利用することが出来ます。このあたりは s:workon() に書いても良いのですが、直交性を配慮した結果です。

" :Workon コマンド後などに Vimfiler が開いていた場合は :Workon で移動したディレクトリに
" Vimfiler も移動する
function! s:cd_all_vimfiler(path) abort
  let current_nr = winnr()
  try
    for winnr in filter(range(1, winnr('$')),
          \ "getwinvar(v:val, '&filetype') ==# 'vimfiler'")
      call vimfiler#util#winmove(winnr)
      call vimfiler#mappings#cd(a:path)
    endfor
  finally
    call vimfiler#util#winmove(current_nr)
  endtry
endfunction
autocmd MyAutoCmd User my-workon-post call s:cd_all_vimfiler(getcwd())

(番外編)Unite menu をむっちゃ便利にする

f:id:lambdalisue:20151224235138p:plain

Shougoware 代表作である unite.vim には固定メニューを表示するための menu ソースがあるのですが、ヘルプなどに書いてある方法はコマンドを直接実行する方法なので「あ、このファイル split で開きたい」とかに対応できません(ぜんぶ edit xxxxxx って書くから)。 そこで以下の様なヘルパー関数を定義します。

function! s:register_filemenu(name, description, precursors) abort " {{{
  " find the length of the longest name
  let max_length = max(map(
        \ filter(deepcopy(a:precursors), 'len(v:val) > 1'),
        \ 'len(v:val[0])'
        \))
  let format = printf('%%-%ds : %%s', max_length)
  let candidates = []
  for precursor in a:precursors
    if len(precursor) == 1
      call add(candidates, [
            \ precursor[0],
            \ '',
            \])
    elseif len(precursor) >= 2
      let name = precursor[0]
      let desc = precursor[1]
      let path = get(precursor, 2, '')
      let path = resolve(expand(empty(path) ? desc : path))
      let kind = isdirectory(path) ? 'directory' : 'file'
      call add(candidates, [
            \ printf(format, name, desc),
            \ path,
            \])
    else
      let msg = printf(
            \ 'A candidate precursor must has 1 or more than two terms : %s',
            \ string(precursor)
            \)
      call add(candidates, [
            \ 'ERROR : ' . msg,
            \ '',
            \])
    endif
  endfor

  let menu = {}
  let menu.candidates = candidates
  let menu.description = a:description
  let menu.separator_length = max(map(
        \ deepcopy(candidates),
        \ 'len(v:val[0])',
        \))
  if menu.separator_length % 2 != 0
    let menu.separator_length += 1
  endif
  function! menu.map(key, value) abort
    let word = a:value[0]
    let path = a:value[1]
    if empty(path)
      if word ==# '-'
        let word = repeat('-', self.separator_length)
      else
        let length = self.separator_length - (len(word) + 3)
        let word = printf('- %s %s', word, repeat('-', length))
      endif
      return {
            \ 'word': '',
            \ 'abbr': word,
            \ 'kind': 'common',
            \ 'is_dummy': 1,
            \}
    else
      let kind = isdirectory(path) ? 'directory' : 'file'
      let directory = isdirectory(path) ? path : fnamemodify(path, ':h')
      return {
            \ 'word': word,
            \ 'abbr': printf('[%s] %s', toupper(kind[0]), word),
            \ 'kind': kind,
            \ 'action__path': path,
            \ 'action__directory': directory,
            \}
    endif
  endfunction

  " register to 'g:unite_source_menu_menus'
  let g:unite_source_menu_menus = get(g:, 'unite_source_menu_menus', {})
  let g:unite_source_menu_menus[a:name] = menu
endfunction " }}}

" 使用例
call s:register_filemenu('shortcut', 'Shortcut menu', [
      \ ['vim'],
      \ [
      \   'vimrc',
      \   fnamemodify(resolve($MYVIMRC), ':~'),
      \ ],
      \ [
      \   'gvimrc',
      \   fnamemodify(resolve($MYGVIMRC), ':~'),
      \ ],
      \ [
      \   'vimshrc',
      \   '~/.vim/vimshrc',
      \ ],
      \ [
      \   'vim',
      \   '~/.vim',
      \ ],
      \ [
      \   'bundle',
      \   '~/.vim/bundle',
      \ ],
      \ [
      \   'ftplugin',
      \   '~/.vim/ftplugin',
      \ ],
      \ ['zsh'],
      \ [
      \   'zshrc',
      \   '~/.config/zsh/.zshrc',
      \ ],
      \ [
      \   'rc/theme.dust.zsh',
      \   '~/.config/zsh/rc/theme.dust.zsh',
      \ ],
      \ [
      \   'rc/configure.applications.zsh',
      \   '~/.config/zsh/rc/configure.applications.zsh',
      \ ],
      \ [
      \   'zsh',
      \   '~/.config/zsh',
      \ ],
      \ ['tmux'],
      \ [
      \   'tmux.conf',
      \   '~/.tmux.conf',
      \ ],
      \ [
      \   'tmux-powerlinerc',
      \   '~/.tmux-powerlinerc',
      \ ],
      \ [
      \   'tmux-powerline.conf',
      \   '~/.config/tmux/tmux-powerline.conf',
      \ ],
      \ [
      \   'tmux',
      \   '~/.config/tmux',
      \ ],
      \ ['others'],
      \ [
      \   'gitconfig',
      \   '~/.gitconfig',
      \ ],
      \ [
      \   'gitignore',
      \   '~/.gitignore',
      \ ],
      \ [
      \   'pymolrc',
      \   '~/.pymolrc',
      \ ],
      \ [
      \   'vimperatorrc',
      \   '~/.vimperatorrc',
      \ ],
      \ [
      \   'latexmkrc',
      \   '~/.latexmkrc',
      \ ],
      \ [
      \   'jupyter custom.css',
      \   '~/.jupyter/custom/custom.css',
      \ ],
      \ [
      \   'jupyter custom.js',
      \   '~/.jupyter/custom/custom.js',
      \ ],
      \])

このヘルパー関数を利用して作成した Unite menu では、Unite fileUnite directory のように right アクションなどが有効になります。Unite menu でファイルの開き方を指定したくて困っていた方は是非試してみてください。

おわりに

雑だ。だがあと 2 min しかない。仕方がない