Vim で表示しているテキストを外部プログラムで編集する

この記事は [Vim Advent Calendar 2019](https://qiita.com/advent-calendar/2019/vim) の 13 日目の記事です。

Vim は選択範囲であったり開いているテキスト全体であったりに対して、外部プログラムで編集することができます。
ここでは、 `:{range}!{filter} [arg]` を題材にして動きを確認していきます。

なお、詳細については `:help complex-change` もしくは `:help filter` に記載があります。

## tate コマンドで縦書きに変換する

tate コマンドは、テキストを縦書きに変換するプログラムです。

```
$ type sample.txt | iconv -f utf-8 -t cp932
Vim で表示している

テキストを

外部プログラムで

編集する
```

↑ のような sample.txt に対して、 tate コマンドを使うと縦書きになります。

```
$ type sample.txt | tate
編　外　テ　Ｖ
集　部　キ　ｉ
す　プ　ス　ｍ
る　ロ　ト　　
　　グ　を　で
　　ラ　　　表
　　ム　　　示
　　で　　　し
　　　　　　て
　　　　　　い
　　　　　　る
```

この動作を Vim 上で直接再現するのが filter コマンドになります。
ここでは、 `:%!tate` というコマンドで、テキスト全体を tate コマンドで処理してみます。

![tate.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/63649/e37d0517-b591-8dd7-9011-27224565c850.gif)


大体想定通りだと思いますが、 range で指定した範囲のテキストを filter で指定したプログラムの標準入力に渡し、プログラムの標準出力で出力されたテキストで置き換えることができます。


ちなみに、 tate コマンドは以下になります。

tate
https://github.com/mattn/tate

## filter として使いやすいプログラム

sed / awk / Perl あたりのワンライナーで書きやすいプログラムが使いやすいです。
自分は、 Perl を良く使っていますので、いくつか例を示します。

### サンプル 1 : すべて大文字化

```
A filter is a program that accepts text at standard input, changes it in some
way, and sends it to standard output.  You can use the commands below to send
some text through a filter, so that it is replaced by the filter output.
Examples of filters are "sort", which sorts lines alphabetically, and
"indent", which formats C program files (you need a version of indent that
works like a filter; not all versions do).  The 'shell' option specifies the
shell Vim uses to execute the filter command (See also the 'shelltype'
option).  You can repeat filter commands with ".".  Vim does not recognize a
comment (starting with '"') after the `:!` command.
```

↓ `:%!perl -anE "print uc($_)"`

```
A FILTER IS A PROGRAM THAT ACCEPTS TEXT AT STANDARD INPUT, CHANGES IT IN SOME
WAY, AND SENDS IT TO STANDARD OUTPUT.  YOU CAN USE THE COMMANDS BELOW TO SEND
SOME TEXT THROUGH A FILTER, SO THAT IT IS REPLACED BY THE FILTER OUTPUT.
EXAMPLES OF FILTERS ARE "SORT", WHICH SORTS LINES ALPHABETICALLY, AND
"INDENT", WHICH FORMATS C PROGRAM FILES (YOU NEED A VERSION OF INDENT THAT
WORKS LIKE A FILTER; NOT ALL VERSIONS DO).  THE 'SHELL' OPTION SPECIFIES THE
SHELL VIM USES TO EXECUTE THE FILTER COMMAND (SEE ALSO THE 'SHELLTYPE'
OPTION).  YOU CAN REPEAT FILTER COMMANDS WITH ".".  VIM DOES NOT RECOGNIZE A
COMMENT (STARTING WITH '"') AFTER THE `:!` COMMAND.
```

### サンプル 2 : SREC の S0 レコードをデコード

`443A5C446174615C5358313231325F4150495C4150495C4150495F636F64655C` が ASCII の 16 進数表記になっているので変換するという例。
Vim のコマンドラインモードでは、 `%` がファイル名に変換されてしまうので `\%` と書く必要があることに注意。

```
S02B0000443A5C446174615C5358313231325F4150495C4150495C4150495F636F64655C7475746F7269616C44
```

↓ `:%!perl -ape "s/^(S0......)((..)+)(..$)/sprintf qq'\%s\%s\%s', $1, pack('H*', $2), $3/e"`

```
S02B0000D:\Data\SX1212_API\API\API_code\tutorial6C
```


### サンプル 3 : 前の行のタイムスタンプとの差分を追加しつつ、 ID (3 カラム目) 毎の差分も出す

```
4.303006 1  1               Rx   d 8 00 00 00 00 00 00 00 00
4.413445 1  1               Rx   d 8 00 00 00 00 00 00 00 00
4.480696 1  2               Rx   d 8 00 57 00 00 00 00 00 00
4.482131 1  4               Rx   d 8 00 57 00 00 00 00 00 00
4.523533 1  1               Rx   d 8 00 00 00 00 00 00 00 00
4.634201 1  1               Rx   d 8 00 00 00 00 00 00 00 00
4.743692 1  1               Rx   d 8 00 00 00 00 00 00 00 00
4.780966 1  2               Rx   d 8 00 57 00 00 00 00 00 00
4.782392 1  4               Rx   d 8 00 57 00 00 00 00 00 00
4.853831 1  1               Rx   d 8 00 00 00 00 00 00 00 00
4.963971 1  1               Rx   d 8 00 00 00 00 00 00 00 00
```

↓ `:%!perl -ape "s/^/sprintf '\%.6f \%.6f ', $F[0]-$x{$F[2]}, $F[0]-$y/e; $x{$F[2]}=$F[0]; $y=$F[0]"`

```
// 左から `ID 毎差分`、 `差分`、 `元のテキスト` を表示
4.303006   4.303006   4.303006 1  1               Rx   d 8 00 00 00 00 00 00 00 00
0.110439   0.110439   4.413445 1  1               Rx   d 8 00 00 00 00 00 00 00 00
4.480696   0.067251   4.480696 1  2               Rx   d 8 00 57 00 00 00 00 00 00
4.482131   0.001435   4.482131 1  4               Rx   d 8 00 57 00 00 00 00 00 00
0.110088   0.041402   4.523533 1  1               Rx   d 8 00 00 00 00 00 00 00 00
0.110668   0.110668   4.634201 1  1               Rx   d 8 00 00 00 00 00 00 00 00
0.109491   0.109491   4.743692 1  1               Rx   d 8 00 00 00 00 00 00 00 00
0.300270   0.037274   4.780966 1  2               Rx   d 8 00 57 00 00 00 00 00 00
0.300261   0.001426   4.782392 1  4               Rx   d 8 00 57 00 00 00 00 00 00
0.110139   0.071439   4.853831 1  1               Rx   d 8 00 00 00 00 00 00 00 00
0.110140   0.110140   4.963971 1  1               Rx   d 8 00 00 00 00 00 00 00 00
```

## まとめ

以上のように、外部プログラムを使って簡単なテキスト編集ができました。
今回紹介したサンプルは Perl 中心ですが、他のプログラムでも同様の事が可能です。
使い慣れたプログラムでフィルタ処理をして、テキスト編集を快適にしていきましょう。