setlocal wrap nowrapとは

Vimで, setlocal wrap nowrapとsetlocal nowrapは違います.
setlocal wrap nowrapは上手く使えばいい副作用をもたらしてくれます.

Vimで日の丸

Vimで, 日の丸を描くプラグインを書いてみます.

command! Flag call s:flag()

function! s:flag()
  tabnew
  setlocal nowrap nonumber filetype=flag buftype=nofile nocursorline cursorcolumn
  call s:redraw()
  syntax match FlagBG '.*' contains=FlagFG,FlagBD
  syntax match FlagFG 'X' contained
  syntax match FlagBD 'Y' contained
  highlight FlagBG ctermfg=15 ctermbg=15 guifg=#ffffff guibg=#ffffff
  highlight FlagFG ctermfg=196 ctermbg=196 guifg=#ff0000 guibg=#ff0000
  highlight FlagBD ctermfg=210 ctermbg=210 guifg=#ff8787 guibg=#ff8787
endfunction

function! s:redraw()
  let [h, w] = [winheight(0), winwidth(0)]
  let r = min([2 * h, w]) * 0.3
  let s = []
  for i in range(h)
    let a = max([float2nr(sqrt(r * r - (h * 1.0 - i * 2.0) * (h * 1.0 - i * 2.0))), 0])
    let b = max([float2nr(sqrt((r + 1) * (r + 1) - (h * 1.0 - i * 2.0) * (h * 1.0 - i * 2.0))) - a, 0])
    let as = repeat('X', 2 * a)
    let bs = repeat('Y', b)
    call extend(s, [(repeat(' ', w / 2 - a - b).bs.as.bs.repeat(' ', w - a - b - w / 2))])
  endfor
  silent % delete _
  call setline(1, s)
endfunction

cursorcolumnを設定しているのは, 見やすさのためです.
このスクリプトをplugin/flag.vimに保存し, :Flagで実行します.

画像を見て分かるように, vnewとかnewした時に, 半分が隠れてしまいます.
これを改善したスクリプトは以下のようになります.

command! Flag call s:flag()

function! s:flag()
  tabnew
  setlocal nowrap nonumber filetype=flag buftype=nofile nocursorline cursorcolumn
  call s:redraw()
  augroup FlagAutoUpdate
    autocmd!
    autocmd BufEnter,VimResized * call s:update(expand('<abuf>'))
  augroup END
  augroup Flag
    autocmd WinEnter,BufEnter,VimResized <buffer> call s:redraw()
  augroup END
  syntax match FlagBG '.*' contains=FlagFG,FlagBD
  syntax match FlagFG 'X' contained
  syntax match FlagBD 'Y' contained
  highlight FlagBG ctermfg=15 ctermbg=15 guifg=#ffffff guibg=#ffffff
  highlight FlagFG ctermfg=196 ctermbg=196 guifg=#ff0000 guibg=#ff0000
  highlight FlagBD ctermfg=210 ctermbg=210 guifg=#ff8787 guibg=#ff8787
endfunction

function! s:redraw()
  let [h, w] = [winheight(0), winwidth(0)]
  let r = min([2 * h, w]) * 0.3
  let s = []
  for i in range(h)
    let a = max([float2nr(sqrt(r * r - (h * 1.0 - i * 2.0) * (h * 1.0 - i * 2.0))), 0])
    let b = max([float2nr(sqrt((r + 1) * (r + 1) - (h * 1.0 - i * 2.0) * (h * 1.0 - i * 2.0))) - a, 0])
    let as = repeat('X', 2 * a)
    let bs = repeat('Y', b)
    call extend(s, [(repeat(' ', w / 2 - a - b).bs.as.bs.repeat(' ', w - a - b - w / 2))])
  endfor
  silent % delete _
  call setline(1, s)
endfunction

function! s:update(bufnr)
  let nr = -1
  let newnr = str2nr(a:bufnr)
  for buf in tabpagebuflist()
    if getbufvar(buf, '&filetype') == 'flag' && buf != newnr
      let nr = buf
      break
    endif
  endfor
  if nr == -1 | return | endif
  let winnr = bufwinnr(nr)
  let newbuf = bufwinnr(str2nr(a:bufnr))
  let currentbuf = bufwinnr(bufnr('%'))
  execute winnr 'wincmd w'
  call s:redraw()
  if winnr != newbuf && newbuf != -1
    execute newbuf 'wincmd w'
  elseif winnr != currentbuf && currentbuf != -1
    execute currentbuf 'wincmd w'
  endif
endfunction


...
上手く動いているように見えます...


しかし, カーソルを動かすと...



実は, こうなってしまうのは, nowrapのせいです.
解決法は, 2つあります.

  • setlocal wrap にする
  • setlocal wrap nowrap を使う


一つ目の解決法は, 結構危険です.
なんかの拍子に行が折り返してしまうかもしれません.
つまり, 完璧に列数をコントロールしなければなりません.
非常に神経が磨り減ります.


そこで, 便利なのが二つ目の解決法です.
書き換える関数s:redrawを, 以下のように変更します.

function! s:redraw()
  ...
  silent % delete _
  call setline(1, s)
  setlocal wrap nowrap
endfunction

つまり, バッファーを書き換えた直後に setlocal wrap nowrap を入れるのです!
すると...

うまくいきました!!!
setlocal wrap nowrap凄い!!!

まとめ

ビジュアルが重要なプラグインで, 見た目が「常に」良くなるように見せるためには, 他のバッファーに入ったり削除したりした時にもプラグインのバッファーに戻って書き直しなければなりません.
しかも, そういうプラグインでは, おそらくsetlocal nowrapを使っていると思います.
$:vnewしてみて下さい.
ここに書いたような問題が発生するはずです.


そういう時は, setlocal wrap nowrapを使ってみて下さい.
あと, setlocal wrap nowrap と setlocal nowrap は違います.
意図的ですので, ダブってるよ!って突っ込まないで下さい.