Vim script から標準出力に出す

この記事は [Vim script Advent Calendar 2015](http://qiita.com/advent-calendar/2015/vim-script) の 16日目の記事です。

## Shell から特定の `target.vim` を実行した時に何かしらの出力を得たい。

実行自体は次のようにすることで可能です。

```sh
$ vim -S target.vim -c 'qa!'
```

あるいは、

```sh
$ vim -c 'so target.vim | qa!'
```

`-N` と `-u NONE` が必要かは場合によります。
詳細はヘルプを見ましょう。(`--cmd` という選択肢もある [^rbtnn_2015])

```vim
:h -N
:h -u
:h -S
:h -c
:h --cmd
:h :so
:h :bar
```

さて、おもむろに次のヘルプを見ましょう。

```vim
:h Q
:h gQ
:h -E
:h -s-ex
:h :put
:h :print
:h [range]
```

簡単に説明すると、`Q`, `gQ` というのは `Ex モード` に入るキーバインドです。あまり使う機会がないので別のマッピングをしている人も多いと思います[^check_mapping]。そのような場合は `vim -E` などで試すことができます(`-s` をつけると試す分には辛い)。

![Vim_exmode.png](https://qiita-image-store.s3.amazonaws.com/0/35327/bf07ebe5-3576-8b53-8d55-220c6f42326e.png)

編集用のコマンドは　`:h inserting-ex`, `:h change.txt` などで `/^:` を探すのが参考になります。
なお、途中で `:redraw` を用いることでバッファの表示を更新することができます。

さて、上記のスクリーンショットでは `Vim` のコマンドラインウィンドウによる対話環境で実行されていますが `-s` オプションをつけると `Shell` で実行されるようになります。その際に `Vim` は標準出力を用いるわけです。

## 実践

つまり、こうです。

```vim:hello.vim
put ='Hello, world!'
```

```shell-session
watiko@local $ vim -NEsS hello.vim -c '2,$print | qa!'
Hello, world!
```

これを次のスクリプトで試してみます。


```vim:fib.vim
let s:fib = {
\   'memo' : [0, 1]
\ }

fu! s:fib.calc(to)
  let l:from = len(self.memo)
  for i in range(l:from, a:to)
    let l:next = self.memo[i - 1] + self.memo[i - 2]
    call add(self.memo, l:next)
  endfor
endfu

fu! s:fib.get(n)
  let l:last_index = len(self.memo) - 1
  if l:last_index < a:n
    call s:fib.calc(a:n)
  endif
  return self.memo[a:n]
endfu

put =s:fib.get(10)
put =s:fib.get(45)
put =s:fib.get(46)
put =s:fib.get(47)
```

```shell-session
watiko@local $ vim -NEsS fib.vim -c '2,$print | qa!'
55
1134903170
1836311903
-1323752223
```

## ?![^Number]

```vim:fib_big.vim
let s:save_cpo = &cpo
let v:errors = []
set cpo&vim

let s:BASE = 10

fu! s:add_positives(r0, r1)
  let l:i = len(a:r0) - 1
  let l:j = len(a:r1) - 1

  let l:carry = 0
  let l:r2 = ''

  while l:i >= 0
    if l:j < 0
      let l:sum = a:r0[l:i] + l:carry
    else
      let l:sum = a:r0[l:i] + a:r1[l:j] + l:carry
    endif

    if l:sum >= s:BASE
      let l:carry = 1
      let l:sum -= s:BASE
    else
      let l:carry = 0
    endif

    let l:r2 = l:sum . l:r2

    let l:i -= 1
    let l:j -= 1
  endw

  if l:carry != 1
    return l:r2
  else
    return l:carry . l:r2
  endif
endfu

fu! s:fib(n)
  let l:a = '0'
  let l:b = '1'

  let l:count = 0

  while l:count < a:n
    let l:count += 1
    let l:tmp = s:add_positives(l:b, l:a)
    let l:a = l:b
    let l:b = l:tmp
  endw

  return l:a
endfu

call assert_equal('200', s:add_positives('191', '9'))
call assert_equal('55', s:fib(10))

put =string(v:errors)
put =s:fib(45)
put =s:fib(46)
put =s:fib(47)
put =s:fib(1000)

let &cpo = s:save_cpo
```

```shell-session
watiko@local $ vim -NEsS fib_big.vim -c '2,$print | qa!'
[]
1134903170
1836311903
2971215073
43466557686937456435688527675040625802564660517371780402481729089536555417949051890403879840079255169295922593080322634775209689623239873322471161642996440906533187938298969649928516003704476137795166849228875
```

注: 普段は QuickRun を使っています。技術的興味があっただけで、あまりおすすめしません(面倒なので)。

[^rbtnn_2015]: http://www.slideshare.net/rbtnn/vimconf2015-55367256
[^check_mapping]: `:verbose map Q`, `:verbose map gQ`
[^Number]: `:h Number` 手元の環境では `:echo 0xffffffff`(32bit) が `-1` だった。びっくりした勢いのまま書いた。