VimScript入門

コマンド&スクリプト

VimScriptの過去記事まとめです。まとめ中。

Contents

スポンサーリンク

VimScript 入門編

VimScript echo

Vimスクリプト学習始めます。

WindowsでGvimを起動。

いつもkaoriyaさんを使ってます。

ってことで、Hello Worldにあたるであろう、echoから。

:echo "HELLO"


Enter押すまでは実行しません。


Enterを押すと、HELLOが表示されます。
しかし、いったいechoって何に使えるのでしょう。。。
計算でしょうか。

:echo 30+40


Enter実行。

電卓使えるでしょうか。

VimScript setline

setline関数でバッファに文字を書いてみます。


関数を使うには、:callで、関数を指定します。

:call setline(".","HELLO")

setlineは実行すると現在行に指定した文字列を書き込みます。

新規のバッファなので1行目に文字列を書き込みました。
続けて10行目を指定して、setlineを実行してみます。

指定した行が存在しないとsetlineを実行しても失敗します。

ではバッファの行を追加しておきます。

:norm 10o


空10行追加して全11行になりました。

:call setline("10","HELLO2")

バッファを全クリアしておきます。

ggdG

前回はcallでsetlineを実行しましたが、
callをつけないとどうなるのでしょう。

:setline(".","HELLO")

エラーになりました。

:で入力するのは、exコマンドですので、setline関数を直接書いても、当然コマンドがないって怒られますね。
callやechoコマンドを使うことで、関数が実行できます。

バッファをクリアして、空行を10行追加。全11行にしておきます。

:ggdG
:norm 10o

1行目~5行目まで、AAAという文字列で埋めます。

:1,5call setline(".","AAA")

次は3行目を指定してsetlineを実行。

:call setline("3","BBBBB")


setlineは、指定した行の文字列を入れ替えるだけなので、3行目を指定すると、元々のAAAは削除されて、BBBBBになります。

VimScript line

lineを使ってみます。lineの戻り値は行番号です。

:echo line(".")

新規のバッファでこれを実行すると、行番号は1から始まるので1が戻ってきます。

10行空行を追加します。

:norm 10o


11行目にカーソルがある状態でlineを使って行番号を使ってみます。

:echo line(".")

VimScript getline

今回はgetline関数を使います。
getlineは、指定した行のテキストを取得する関数です。

:echo getline("3")


イージーですね。

次は複数行を取得してみます。
2行目から4行目のテキストを取得。

:echo getline("2","4")


戻り値はリストで返ります。

getlineの戻り値を変数に格納してみます。

:let aaa=getline("2","4")

行範囲をgetlineの第1、第2引数で指定します。
変数へはletコマンドで変数名を指定して代入。
これで代入完了です。

変数に指定した行範囲のテキストが代入されたか確認します。
リストの添え字は0始まりです。

:echo aaa[1]


ちゃんと格納されてますね。

VimScript eval

関数evalを使ってみます。
evalは、文字列を評価してくれる関数です。

:echo eval("10 + 20")


簡単ですね。
次は、Vimのバッファにテキストを入力してみます。計算式です。

10 + 20 + 30 * 4


カーソル位置のテキストをgetlineで読み取って、evalしてみましょう。

:call setline(".",eval(getline(".")))


計算式が答えに置き換わりました。

VimScript join

今回はjoinです。

vimを開いたら適当にテキストを作成。

:0r!wsl seq 10

この10行をjoinを使って結合してみます。

:echo join(getline("1","10"))

joinの第2引数に何も指定しないと、各文字列は空白で結合されます。
次は+で結合してevalに渡してみましょう。

:echo eval(join(getline("1","10"),"+"))

各行の合計が計算できました。

join続きです。

前回はgetlineで1行目から10行目を指定して結合しましたが、
バッファ全範囲を指定して結合してみます。

:0r!wsl seq 20

:echo join(getline("w0","w$"))

次は、2つマークを付けて、マーク間の文字列を結合してみます。

ノーマルモードで、5行目にカーソルを移動して、ma
10行目にカーソルを移動させて、mb
と入力しておきます。

マークの確認をしてみましょう。

マークaは5行目、マークbは10行目に設定されています。

では、マーク間の文字列を連結。

:echo join(getline("'a","'b"))


連結できました。

VimScript str2float

関数で計算をしてみます。


5行目から10行目を選択して、

:'<,'>call setline(".",pow(getline("."),2))

エラーが出てしまいました。
どうやら、文字列を直接pow関数に渡すのはNGなようです。

str2float関数を使って、getlineで得た文字列を数値に変換します。

:'<,'>call setline(".",pow(str2float(getline(".")),2))

まだエラーになりますね。
setlineへ渡すときの値を、文字列にしていませんでした。

stringを使って、powで得た計算結果を文字列に変換します。

:'<,'>call setline(".",string(pow(str2float(getline(".")),2)))

今度はうまくいきました。

VimScript split

split関数を使います。

:0r!wsl seq 100 | wsl pr -t10J

入力テキストとして、10×10のマトリクスを作成。

5行目を、splitを使って、行をタブで分割してみます。

:call setline(".",split(getline("."),"\t"))

下の方が上書きされてしまいましたが、一応できました。

VimScript append

append関数を使います。
元のデータは前回使ったテキストです。

5行目と6行目の間に、テキストを追加します。

:call append("5","AAA")

次は削除です。deletebuflineを使います。
deletebuflineは第1引数にバッファを指定する必要があります。
カレントバッファの6行目を消してみます。

:call deletebufline("%","6")


名前が覚えにくい関数です。

VimScript add

テキストを二次元配列(リストのリスト)に格納します。
使用するテキストは前回使ったテキストです。

:let arr=split(getline("w0","w$"),"\t")

エラーになりました。
getlineで複数行指定しているので、splitに渡すときにはリストの形になってしまっていました。

add関数で、arr変数にsplitの結果を渡すようにすればよいですね。

:let arr=[]
:call add(arr,split(getline("."),"\t"))

echo arr

を実行すると変数の内容が確認できます。


全値が格納されましたね。

今回はリストの要素の追加です。
ずっと使ってるaddですね。

変数arrを初期化後、値10を追加します。

:let arr=[]
:call add(arr,10)
:echo arr


次は20を追加する。

:call add(arr,20)
:echo arr

一気に2個追加できるでしょうか。

:call add(arr,[30,40])
:echo arr

二つの要素の追加ではなく、リストとしての一つの要素の追加となりました。
addでは一つだけしか要素の追加はできないようです。

VimScript len

:let arr=[]
:call add(arr,split(getline("."),"\t"))

前回このようにして二次元配列として変数にデータを格納しました。

中身を確認してみます。2行目。

:echo arr[1]


3行目の5列目。

:echo arr[2][4]

変数の個数を確認。

:echo len(arr)

100ではなくて10と表示されました。
つまり二次元配列の行数の取得ですね。

VimScript remove

リスト(二次元配列)の要素の削除をしてみます。

:let arr2=remove(arr,0)

これでarrの要素の先頭、つまり二次元配列の1行目が削除されました。ハズです。
確認してみます。

:echo arr2

削除された1行目は、arr2に移りました。

arrも確認してみます。

:echo arr

一行目削除されましたね。

リスト(二次元配列)の要素の複数削除です。
現在の変数の内容はこのようになってます。

:echo arr

removeをつかって複数の要素を削除してみます。
1行目から7行目の削除です。

:let arr2=remove(arr,0,6)

:echo arr

8行削除して残り1行になりました。

VimScript reverse

reverseを使います。

変数arrを初期化して、新しいデータを設定します。

:let arr=[]
:1call add(arr,split(getline("."),"\t"))

:echo arr

reverseでリストを反転させてみます。

:echo reverse(arr)

あれ、変わってません。
よく見たらarrって二次元配列ですね。

1行目を指定して反転させてみます。

:echo reverse(arr[0])

反転しました。

VimScript insert

insertでリストの先頭に要素を追加してみます。
まずは変数を初期化してリストを作ります。

:let arr=[1,2,3,4]
:echo arr

insertでリストの先頭に数値10を追加してみます。

:call insert(arr,10)
:echo arr


数値だけでなく文字列を追加してみます。

:call insert(arr,"HELLO")


insertだけでなく、addでも数値、文字列、リストが追加できます。

insertは先頭だけでなく、その名前の通り要素の途中でも追加できます。

:let arr=[1,2,3,4]
;call insert(arr,100,2)


2と3の間に追加されました。

存在しない位置を指定するとどうなるでしょうか。

;call insert(arr,200,10)

範囲外のエラーとなりました。

VimScript listのコピー

リストのコピーです。

:let a=[1,2,3,4]

まず、リストaを作ります。次にaをbに代入して、bの内容を確認すると、

:let b = a
:echo b


bの内容は、aと同じです。
次に、aの内容を変更した後、bを確認します。

:let a[0]=100
:echo b

aの内容を変更すると、bの内容も変更されました。単に変数の代入では、リストのコピーにならないようです。

VimScript copy

内容のコピーです。

リストaを作成し、copy関数で、リストbにコピーします。

:let a=[1,2,3,4]
:let b=copy(a)

:echo b


これで別々のリストが出来上がりました。リストaとリストbは、別のリストなので、今はaとbの内容が同じに見えていますが、片方のリストの値を変更しても、もう片方には影響しません。
aの内容を変更してみます。

:let a[0]=200
:echo a b


変更したa[0]の位置にあたるb[0]は元の値のままですね。

VimScript deepcopy

copyをつかってリストをコピーしましたが、実はリスト中のリストの実体はコピーされてません。

:let a=[1,[2,3]]
:let b=copy(a)
:let a[0]=100
:let a[1][0]=200
:echo a b

左がaで、右がbですが、a[1]とb[1]の値が同じになってますね。

リストのリストをコピーするには、deepcopyを使います。

:let a=[1,[2,3]]
:let b=deepcopy(a)
:let a[0]=100
:let a[1][0]=200
:echo a b

リストaとリストbは、これで別物になりました。
さらにリストの中にリストがある場合はどうでしょう。

:let a=[1,[2,[3]]]
:let b=deepcopy(a)
:let a[0]=100
:let a[1][0]=200
:let a[1][1][0]=300
:echo a b


deepcopyは再帰的にリストの値のコピーをしてくれるので、完全に別物になったようです。

VimScript list unpack

リストの各要素を別々の変数に分けて代入してみます。
いわゆるアンパックです。

:let a=[10,20,30]
:let [a1,a2,a3] = a
:echo a1 a2 a3


簡単ですね。

展開先の変数の数が足りない場合は

:let [b1,b2] = a

:echo b1 b2

さすが。エラーになりました。
エラーになった場合は、変数に格納されるのでしょうか。

変数には格納されていませんね。

次。代入先が元のリストより多い場合はどうなるでしょうか。

:let [c1,c2,c3,c4] = a

ちゃんとエラーになりました。

VimScript list 範囲アクセス

リストの要素のアクセスはインデックスを指定すれば良かったですね。

:let a=[10,20,30,40,50]
:echo a[2]

範囲を指定するには、:(コロン)で範囲の先頭と末尾を区切って指定します。

:echo a[1:3]

範囲を指定してリストをコピーしてみます。

:let b=a[1:3]
:echo a b

各要素もコピーされていることを確認します。

:let a[2]=100
:echo a b


aとbは別リストですので、aを変更しても、bの要素には影響ありませんでした。

VimScript list append

今まで学習したリスト操作を使って、テキストの編集をしてみます。

元のテキストを適当に書きます。

:let a = getline("w0","w$")
:echo a


テキストを変数aに格納しました。
2行目と3行目を抽出します。

:let b = a[1:2]

最後の行に、リストbを追加してみます。

:call append("w$",b)


リストさえ使いこなせば、テキストの編集なんてお茶の子さいさいですね。

VimScript list 上下反転

リスト操作を使って、テキストを上下反転してみます。

:0r!wsl seq 10

このテキストをaに格納します。

:let a=getline("w0","w$")

reverseで取得したテキストを上下反転させます。

:let b=reverse(a)
:echo b


反転した変数を今のバッファと置き換えます。

:call setline("w0,w$",b)


tacコマンドなど、外部コマンドが使えないときでも、関数で代用できそうですね。

VimScript list 左右反転

前回はテキスト上下反転を行いましたが、今回は左右反転です。
土台のテキストを作成。

:r0!wsl seq 100 | wsl pr -t10J

このテキストを変数に格納します。

:let a=getline("w0","w$")
:echo a

3行目を1文字毎に区切り、別変数に格納します。

:let b=split(a[2],'\zs')
:echo b

別変数に格納した文字のリストを反転、結合し、一行の文字列にします。¥

:let a[2]=join(reverse(b),"")
:echo a[2]

最後に、テキストを入れ替えます。

:call setline("w0w$",a)

VimScript function

関数を作ります。
:(コロン)に続いてfunctionと関数名()を入力してEnterを押すと、関数が作れます。

:function Test()

関数の中身には、文字列を返すだけの処理を書いてみます。

:  return "HELLO"
:  endfunction

これで関数Testが作成されました。
実行して確認してみましょう。

:echo Test()

HELLOと表示されました。関数定義できたようです。

VimScript delfunction

繰り返し処理を書いてみます。

前回Test()という関数を定義したので、Test()は使えません。

:function Test()

delfunctionを使って、Test()を削除します。()は付けずに指定します。

:delfunction Test

では新しく定義するTest()で、forを使ってみます。

:function Test()
:  for i in range(1,5)
:    call append(".",i)
:  endfor
:endfunction

使ってみます。

:call Test()

1~5がバッファに書き込まれました。

VimScript map

各リストを一括で処理するため、map関数を覚えます。
mapの第1引数に、処理対象のリスト、
第2引数に評価式を書きます。
前回使ったテキストを使います。

:call map(a,'"ABCDE " . v:val')
:echo a

v:valには、リストaの各要素が入ります。

リストaの内容は書き換えられていますので、このままsetlineしてテキストを置き換えます。

:call setline("w0w$",a)

置き換わりました。

次は先ほどとは逆に、各行頭につけた”ABCDE “を、substituteを使って削除してみます。

:call map(a,'substitute(v:val,"ABCDE ","","")')
:echo a

リストaが置換されたので、バッファ上のテキストと置き換えます。

:call setline("w0w$",a)

元に戻りました。

mapを使って、計算をしてみます。
ベースのテキストは1~10の数列にしておきます。

:0r!wsl seq 10

テキストをリストaに格納し、mapで各要素を2倍の数にします。

:let a=getline("w0","w$")
:call map(a,'v:val * 2')
:echo a

空行を数値に変換すると0になるので、リストの最後の要素が0になっていますね。
次は、文字列と計算結果の混合で処理してみます。

:let a=getline("w0","w$")
:call map(a,'v:val . " * 2 = " . v:val * 2')
:echo a

テキストと置き換えます。

mapと自作関数を組み合わせて、バッファの全テキストを左右反転する処理をしてみます。

まずはベースのテキストを作成。

:0r!wsl seq 64 | wsl pr -t8J

各行を処理をする関数を作ります。

:function Revline(mystr)
:  return join(reverse(split(a:mystr,'\zs')),"")
:endfunction

試しにechoしてみます。

:echo Revline("HELLO")

大丈夫そうです。

テキストをリストに格納します。

:let a=getline("w0","w$")
:call map(a,'Revline(v:val)')
:echo a

ではバッファを置き換えます。

:call setline("w0w$",a)

全行左右反転できました。

さきほどは自作の関数をmapで実行しましたが、一行の関数だったので、わざわざ関数にする必要もありません。mapだけで同じ処理をしてみます。

:let a=getline("w0","w$")
:echo a

‘で囲んだ中でさらに、’が登場するので、エスケープする必要があります。
‘をエスケープするには、2個重ねればよいです。

:call map(a,'join(reverse(split(v:val,''\zs'')),"")')
:echo a

‘で囲むと文字そのもの。”で囲むと、\は\記号ではなく、エスケープシーケンスになるので、
‘の代わりに”を使って、\記号をエスケープしておいても良いですね。

:call map(a,'join(reverse(split(v:val,"\\zs")),"")')

では変換した変数をバッファのテキストと置き換えます。

:call setline("w0w$",a)

VimScript for

mapの代わりにforを内部で使った関数でリスト処理を作ってみます。
書いてみます。
いつも使ってる1~10の数列をバッファに書いておきます。

:0r!wsl seq 10

リストの各要素に3を掛ける処理を行う関数を作ります。
引数の変数にアクセスには、a:を付けます。

:function Times3(mylist)
:  let b=[]
:  for m in a:mylist
:    call add(b,m * 3)
:  endfor
:  return b
:endfunction

関数が作成できたので、バッファ上の数値を変数aに取り込んで、リスト処理してみます。

:let a = getline("w0","w$")
:let result = Times3(a)
:call setline("w0w$",result)

各行が3倍になってますね。一番下の行は空行なので結果は0です。

VimScript dictionary

連想配列使います。辞書ともいいますね。

連想配列の前に、普通の配列。

:let arr = [10,20,30]
:echo arr[0]


各要素にアクセスするには、要素のインデックスを0始まりで指定すればよかったです。
連想配列は、インデックスで指定するのではなく、文字列で指定します。
では使ってみます。
初期化は{}を使います。

:let dict={"HELLO":10, "TARO":20, "A":30}

定義するときは、{}を使いましたが、使用するときは配列と同じです。

:echo dict["TARO"]

文字列を指定する必要があるので、””で囲まないと、エラーになります。

:echo dict[TARO]

さきほどはこんな連想配列を作りました。

:let dict={"HELLO":10, "TARO":20, "A":30}

echoで確認してみます。

:echo dict

あれ、要素の順番が初期化の時と違いますね。

forでキーと値を一つずつ取り出して確認してみます。

:for [a, b] in items(dict)
:   echo a . "=" . b
:endfor


やはり、入れ替わってます。

キーでソートしたい場合は、sort関数を使えばよいです。

:for a in sort(keys(dict))
:  echo a
:endfor

なので初期化時に書いた通りに並べ替えたい場合は、キー名の前に数字等をつけて、並び替えできるようにしておけばよいですね。

:let dict2={"1 HELLO":10, "2 TARO":20, "3 A":30}
:echo dict2

:for a in sort(keys(dict2))
:  echo a
:endfor

連想配列をクリアするには、要素のない初期化をしてやればよいです。

:let dict={}
:echo dict

この初期化された変数に、要素を追加してみます。

:let dict["HELLO"]=100
:echo dict


このように代入するだけで要素の追加ができます。
代入するだけで要素追加できてしまうということなら、初期化していない変数に、連想配列の要素追加をするとどうなるでしょうか。

:let nondict["HELLO"]=100


未定義の変数って怒られました。
つまり、さきほど要素を追加したdictも、unletしてしまえば、要素の追加はできなくなります。

:unlet dict
:let dict["HELLO"]=100

連想配列の学習がまだまだ続きます。

連想配列も要素数は、lenで取得できます。

:let dict={"AA":10,"BB":20,"CC":30}
:echo len(dict)


リストも同じだったか確認。

:let lista=[100,200,300]
:echo len(lista)


リストも連想配列も同じようにlenで要素数取得できますね。

次は、連想配列の要素を追加して、要素数の確認をしてみます。

:let dict["DD"]=40
:echo len(dict)

要素の削除もしてみます。

:call remove(dict,"AA")
:echo dict

当然要素数も削除に合わせて変化してます。

次は、空要素で初期化後、要素を追加。

:let dict={}
:let dict["AA"]=100
:echo dict

連想配列は、[]使う方式以外に、.(ドット)を使う方法でもアクセスできます。

:let dict.AA = 200
:echo dict

.(ドット)を使う方式だと”も[]も入力しなくて済むので4文字楽できますね。

同じ方法で、要素の追加もできます。

:let dict.BB = 300
:echo dict

次は連想配列の要素削除です。
まず初期化。

:let dict={"AA":100,"BB":200,"CC":300}
:echo dict

以前使ったのは、removeを使った削除ですが、
.(ドット)を使ったアクセス方法で、dict[“BB”]を削除します。

:unlet dict.BB
:echo dict


問題なく削除されました。

存在しないキーを指定すると、

:unlet dict.DD
:echo dict

エラーになります。

VimScript blob

Blobを使います。いわゆるBinary Large OBjectですね。

まずは変数の初期化から。
16進2Byteのデータ0x31と0x32を、変数dataに格納してみます。

:let data = 0z3132

echoで変数を確認してみます。

:echo data

そのままですね。変数の内容をそのまま参照しても、代入したときと同じように表示されます。一見、有用性が見られませんが、Blobはその独特なアクセス方法に意味があります。
Blobのデータのアクセスは、Byte単位で、配列と同じように[]を使ってアクセスできます。

:echo data[0] . "と" . data[1]

49、50という数字は、それぞれ0x31、0x32の10進数です。
このようにBlobを使えば、バイナリデータを意図も簡単に作れてしまいます。
さて、Blobの初期化ですが、Byte単位の16進数である必要があるので、偶数単位で記述します。
試しに奇数の初期化をしてみましょう。

:let data = 0z12345

エラーになりました。

さきほどは2Byteのデータでしたが、もう少し大きなデータを作ってみます。

:let data=0z555555555555555555555555


これでdataに代入できましたが、5がたくさん並んでいて、何Byteあるのかわかりません。ただ、エラーは出なかったので、偶数であることは確かです。
echoしてみます。

:echo data

echoの結果が、.(ドット)区切りで表示されました。
データ大きいと、単なる数字の羅列では数えられないので、見やすくなるように配慮されていますね。
この.(ドット)を使ったリテラルは、代入時にも使えます。

:let data=0z33333333.33333333.33333333
:echo data

ドットで区切る位置は、偶数単位であれば、どこで区切ってもよいです。

:let data=0z12345567890.1234567890.1234.12
:echo data

偶数単位で区切らないともちろん、エラーになります。

:let data=0z123.4567.89

次です。初期化します。

:let data=0z11.22.33.44.55
:echo data

先頭の1Byteにアクセスするには、[0]を使います。

:echo data[0]

データの末尾にアクセスするには、[-1]。

:echo data[-1]

範囲アクセスは、リストと同じく、:(コロン)で区切ります。

:echo data[1:3]

バイナリデータの変更してみます。
まず初期化。

:let data=0z010203040506070809
:echo data

3Byte目のデータ03からFFに変更します。

:let data[2]=0xFF
:echo data

次は、5~8Byte目をすべてAAに変更します。
気をつけないといけないのが、複数Byteなので、16進数値0zではなく、Blobリテラル0zを使います。

:let data[4:7]=0zAA.AA.AA.AA
:echo data

次はバイナリデータを1Byteずつ処理してみます。
初期化。

:let data=0z01.02.03.04.05.06.07.08.09
:echo data

whileを使って繰り返し処理します。

:let i=0
:while i < 9
:  let data[i] = data[i] * data[i]
:  let i = i + 1
:  endwhile
:echo data

16進なので分かりにくいですが、各データが2倍の値になってますね。

変数dataに対して、破壊的置換をしましたが、今度は別の変数に結果を格納してみます。
結果が格納される変数はリストにします。

:let data = 0z01020304.05060708.09
:let out=[]
:for b in data
:  call add(out,b * b)
:  endfor
:echo out

リストで表示された方が見やすいです。

さきほどはforを使ってBlobを処理した結果をリストに格納しました。
これを応用すると、Blobからリストに変換処理が作れます。

:let data=0z010203040506070809
:let out=[]
:for b in data
:  call add(out,b)
:endfor

要するに、これを関数化すればよいです。
関数にしてみます。

:function Blob2list(bin)
:  let l=[]
:  for bdata in a:bin
:    call add(l,bdata)
:  endfor
:  return l
:endfunction

使ってみます。

:echo Blob2list(0z010203)

リスト表記はやはり見やすいですねえ。

Blobをリストに変換する関数を作りましたが、そもそもそんな関数使うのでしょうか。
Blobはリストと同じアクセスができるのだから必要ないですよね。
echoで表示するときに、カンマで区切ってくれて見やすいぐらいでしょうか。
リストと同じってことはmap処理もできるはずです。for文を使う必要もないんですね。
Blobをmapで処理してみます。

let data=0z010203040506070809
:echo map(data,'v:val * v:val')


ものすごく簡単ですね。

リストと同じと考えればよいとすると、変数の代入だと内部はコピーされません。copyを使用する必要があります。
代入の場合。

:let data=0z010203
:let out = data
:let data[0] = 0xFF
:echo out

dataを変更したら、outも変更されました。要するに変数の代入をしただけでは、中身はコピーされていません。リストと同じ性質ですね。

次はcopyを使ってみます。

:let data=0z010203
:let out = copy(data)
:let data[0] = 0xFF
:echo out


値の変化がないってことは、中身もコピーされたってことですね。リストと同じです。

Blobの連結を使ってみます。

Blobの連結には、 + 記号を使いますが、まずは1普通の足し算を確認してみます。

:let a = 1030
:let b = 2040
:echo a + b


300が表示されました。+は足し算なので当然ですね。

では、Blobの結合です。

:let a = 0z1030
:let b = 0z2040
:echo a + b


連結されました。文字列の連結に似てますね。
では文字列の連結の記号.(ドット)は使えるのでしょうか。

:let a . b

エラーになりました。

Blobデータをファイルへ書き込んでみます。
まず、ファイルを書き込むディレクトリを作成します。

:!mkdir c:\tmp
:cd c:\tmp

次に、writefileにBlobデータと、ファイル名を指定して、ファイル書き込みします。

:call writefile(0z010203,"test.bin")

ファイルは生成されたでしょうか。

:r!dir

3 Byteのファイルができてます。
データを確認してみます。

r!wsl od -tx1 test.bin

ファイルをBlobデータに読み込みます。
さきほど書き込んだファイルを使います。

:let data = readfile('c:\tmp\test.bin','B')

格納されたデータを確認してみます。

:echo data

Blobなので、そのままByte毎の処理ができますね。

:for b in data
:  call append(".",b)
:endfor

バッファ上のテキストを、バイナリデータに変換後、ファイル書き込みしてみます。
最初に、適当にテキストファイルを作成。

:0r!wsl seq 100 | wsl pr -t5J

全行をリストとして取り込みます。

:let a = getline("w0","w$")
:echo a

タブ区切りのデータなので、タブでsplit。

:call map(a,'split(v:val,"\t")')
:echo a

outという変数名でBlobを作ります。

:let out=0z
:for arr in a
:  for data in arr
:    let out = add(out,data)
:  endfor
:endfor
:echo out

Blobデータができたので、後はファイルへ書き込むだけ。

:!mkdir "c:\tmp"
:call writefile(out,'c:\tmp\test.bin')

書き込んだファイルを確認します。

:cd c:\tmp
:new
:0r!wsl od -Ax -tx1 test.bin

書き込まれた内容も合ってそうですね。

VimScript extend

毎度リストを使っていて、リストの中のリストの要素を抽出するために、for文を入れ子にするのが、すごく面倒に感じます。
入れ子のないリストにすることはできないでしょうか。

let a=[[1,2],[3,4],[5,6]]

というリストを、

let a=[1,2,3,4,5,6]

こうしてくれる関数などないかなあと、vimのヘルプを探してみましたが、見つかりませんでした。(あるのかもしれませんが)

ってことで作ってみます。リストを指定したときに、そのリストをグローバル変数にaddする関数を作ればよいです。リストの連結をするには、extendを使います。

:function Addmylist(l)
:  call extend(g:mylist,a:l)
:endfunction

使ってみます。

:let a=[[1,2],[3,4],[5,6]]
:let mylist=[]
:call map(a,'Addmylist(v:val)')

前回リストを平坦にする関数を作りましたが、中でextendやってるだけなので、関数にする必要はないですね。
自作関数使わず、直接mapでextendを使ってみます。

:let in_list=[[1,2],[3,4],[5,6]]
:let out=[]
:call map(g:in_list,'extend(g:out,v:val)')

ずいぶんすっきりしました。
これでもいけるはず。

:echo out

ぜんぜん大丈夫ですね。
では、バッファに適当なテキストを作って、それをリストに変換してみます。

:0r!wsl seq 100 | wsl pr -t5J

テキスト取り込み。

:let in=getline("w0","w$")
:call map(in,'split(v:val,"\t")')
:let out=[]
:call map(in,'extend(g:out,v:val)')

outリストに平坦なデータが格納されました。

:echo out

VimScript type

type()関数使ってみます。
typeの戻り値は、ヘルプを見ると、こんな感じ。

0:数値
1:文字列
2:Funcref
3:リスト
4:辞書
5:浮動小数点数
6:真偽値
7"特殊値
8"ジョブ
9:チャネル
10:Blob

思ったよりたくさんの型があるんですね。
今まで使ったことなる型について確認してみます。
まずは数値。

:let a=123
:echo type(a)

次は文字列。

:let a="123"
:echo type(a)

リストです。

:let a=[1,2,3]
:echo type(a)

辞書。

:let a={1:'ONE', 2:'TWO', 3:'THREE'}
:echo type(a)

最後はBlobです。

:let a=0z010203
:echo type(a)

VimScript sort

リストの要素をソートします。

:let a = [5,3,7,2,9,0]
:echo a

sort関数でソートしてみます。

:echo sort(a)

バッファ上のテキストの数列もソートしてみます。
いつものようにサンプル作成。

:0r!wsl seq 100 | wsl shuf | wsl pr -t5J

1から100までをランダムに並べてみました。
前回まで学習したmapとextendを組み合わせて、変数に取り込んだ後、sortします。

:let a = getline("w0","w$")
:call map(a,'split(v:val,"\t")')
:let b=[]
:call map(a,'extend(g:b,v:val)')
:echo b


変数に取り込めたので、sortしてみます。

:echo sort(b)

VimScript uniq

sortの次は、uniqです。重複している要素を削除します。

:let a = [5,3,7,2,9,0,3,5,2]
:echo sort(a)

2,3,5が重複していますね。uniq関数を実行してみます。

:echo uniq(sort(a))

Vimバッファ上のテキストでやってみます。
0~9のランダムの数値を100個ならべて、

:0r!wsl for i in `seq 100`;do expr $RANDOM \% 10;done | wsl pr -t5J


これをVimスクリプトで、sortしてuniqします。
まずは、取り込み。

:let a=getline("w0","w$")
:let out=[]
:call map(a,'extend(g:out,split(v:val,"\t"))')
:echo out

uniqは親切にも破壊的置換をしてくれるので、コピーしたリストの方をuniqします。

:let b = copy(out)
:call uniq(sort(b))
:echo b

VimScript bufname

今回はbufnameとbufnrを使います。バッファ名、番号を取得する関数です。
使い方はヘルプにあります。

:h bufname
bufname({expr})
		戻り値はバッファの名前。バッファ名はコマンド ":ls" で表示され
		るものと同様。
		{expr}が数値ならば、その番号のバッファ名が返される。0は現在の
		ウィンドウの代替バッファを意味する。{expr}が文字列ならば、バッ
		ファ名に対してファイル名マッチング |file-pattern| を行うパター
		ンとなる。このマッチングは常に、'magic' をセットし 'cpoptions'
		を空にした状態で行われる。複数マッチしてしまった場合には空文字
		列が返される。
		"" や "%" は現在のバッファを意味し、"#" は代替バッファを意味す
		る。

		~省略~

ふむふむ。では:lsを確認してみます。

%が現在のバッファってことなので、現在表示されているバッファは、”[無名]”ですね。
bufnameは現在のバッファの番号を指定する必要があるっぽいので、
さきに、bufnrを使ってみます。
ヘルプの確認。

:h bufnr
bufnr({expr} [, {create}])
  結果はバッファの番号。バッファ番号はコマンド ":ls" で表示され
  るものと同様。{expr}の使い方は前述の |bufname()| を参照。バッ
  ファが存在しない場合-1が返される。ただし、{create}が与えられて
  0でないときは、バッファリストに載せない新しいバッファを作成し
  その番号を返す。
  bufnr("$")は最後のバッファを意味する: >

bufnrに”[無名]”を指定して、バッファ番号を確認してみます。

:echo bufnr("[無名]")

-1が返ってきてしまいました。指定の仕方が違うんでしょう。

%が現在のバッファを表すので、%で確認してみます。

:echo bufnr("%")

1が返ってきました。

ということで、bufnameに戻ります。
bufnameで1を指定して、バッファの名前を取得してみましょう。

:echo bufname(1)

何も表示されませんでした。

bufnameのヘルプの最後に書いてありますが、バッファが名前をもってないので何も表示されなかったようです。

bufname({expr})
~省略~
  バッファが存在しないか名前を持っていない場合には、空文字列が返
  される。

では、無名バッファではなく、新規ファイルを作成して、名前のついたバッファとして開いてみます。

:!mkdir c:\tmp
:cd c:\tmp
:e test.txt

では、:lsでバッファ番号を確認。

:ls

test.txtのバッファ番号は、1でした。
1を指定したらbufnameの戻り値はどうなるでしょうか。

:echo bufname(1)

バッファに名前があるので、バッファ名、ちゃんと返ってきました。

VimScript bufexists

今回はbufexistsを使ってみます。

ヘルプから。

:h bufexists

長いので下の方は省略してます。

bufexists({expr})
		結果は数値で、{expr}と呼ばれるバッファが存在すれば|TRUE|とな
		る。
		{expr}が数値の場合、バッファ番号とみなされる。数値の 0 はカレ
		ントウィンドウの代替バッファである。

		{expr}が文字列の場合、バッファ名に正確にマッチしなければな
		らない。名前として以下のものが許される:
		- カレントディレクトリからの相対パス。
		- フルパス。
		- 'buftype' が "nofile" であるバッファの名前
		- URL名。
		バッファリストにないバッファも検索される。

ちょうど今test.txtというファイルを開いてますので、バッファ名に”test.txt”を指定してbufexistsを実行してみます。

:echo bufexists("test.txt")


1が返りました。0がFALSEなので、0以外ということで、TRUEですね。
“test.txt”という名前のバッファは存在するってことです。
存在しないバッファ名を指定してみます。

:echo bufexists("hello.txt")


0が返りました。FALSE、つまり”hello”という名前のバッファは存在しない、です。

バッファ番号でも指定してみます。

:echo bufexists(1)

1番のバッファは存在するので、TRUEが返りました。

画面スプリットしてbufexists使ってみます。

:new

上が無名バッファで、下がtest.txtのバッファです。

lsで各バッファの番号を確認します。

:ls

1番がtest.txt、2番が無名、両方とも存在するバッファなので、bufexistsは、TRUEを返すはずです。

:echo bufexists(1) . "," . bufexisxts(2)

両方ともTRUEが返りました。

次は、分割された上の画面だけ残して、下の画面を隠してみます。
カーソルが上にある状態で、

<C-W>o

1画面になったので、もう一度1番、2番を指定してbufexistsを実行。

:echo bufexists(1) . "," . bufexisxts(2)

あれ、結果は同じです。共にTRUEですね。

lsを確認してみます。

:ls

test.txtバッファは画面には表示されていませんが、1番のバッファとして存在してますね。

バッファは画面に表示されていなくても、存在していれば、bufexistsはTRUEが返るってことは確認できました。今度は、バッファを閉じた後に、bufexistsがFALSEになることを確認してみます。

:ls

1番のバッファをクローズします。

:bd 1
:ls


lsの表示は、2番のバッファのみになりました。
この状態で、1番のバッファが存在するかbufexistsしてみます。

:echo bufexists(1)

あれ、TRUEが返りました。

bdコマンドのヘルプを見てみます。

:bd[elete][!] [N]
		バッファ[N](デフォルト: カレントバッファ)をメモリから取り除
		き、バッファリストから削除する。バッファが編集中の場合はこのコ
		マンドは失敗する ([!] が与えられた場合は成功する。そのとき変更
		は破棄される)。ファイルには影響はない。このバッファを表示して
		いる全てのウィンドウは閉じられる。バッファ[N]がカレントバッ
		ファの場合は、他のバッファが代わりに表示される。このバッファに
		は、ジャンプリストの中のメモリ上にロードされているバッファを指
		し示している最も最近のエントリが使用される。
		実際は、バッファは完全に削除されていない。

		~省略~

バッファは完全に削除されていない!?
bdではバッファは消えたように見えるだけなので、bufexistsでTRUEになってしまうのでしょうか。

VimScript :ls

Windowsの調子が悪くなったので、MacのVimで学習を進めます。
使うバージョンはコレです。

英語版を確認するのにちょうど良い機会なので、lsの表示から見てみます。

:ls

日本語では無名となっていたバッファ名が、[No Name]と表示されています。
bufnrの結果が取得できるかも確認します。

:echo bufnr("[No Name]")

-1が返ってきました。

日本語の時もそうでしたが、そもそも指定するバッファ名がないのだから、””を指定しないとだめなのかも。
今度は””で指定。

:echo bufnr("")


無名バッファの番号である1が返ってきました。

VimScript :bdelete

~/tmp $ vim

Vimを起動してすぐに:new

:new

上下に画面をスプリットしましたが、 カーソルが上のバッファにあるので
:newで追加になったのは上のバッファですね。
lsも確認してみます。

:ls

lsの表示は、下にある2番のバッファが、追加したバッファなので少々ややこしいです。

さらに:newします。

:new
:ls

一番上の画面が、ls表示の3番のバッファです。
%が付いているので、カレントバッファの意味ですね。

もう一画面追加してみます。

:new
:ls

#がついているバッファは、alternateバッファで、前回カレントバッファだったバッファ
を表しているようです。
あと、全部にaの表記がありますが、activeを表しています。

次は、1番のバッファを削除してみます。

:bd 1
:ls

一番最初のバッファ、1番のバッファが削除されました。

次に、再度newをしてみます。1番のバッファが削除された状態ですが、
次のバッファは、1番になるでしょうか、5番になるでしょうか。

:new
:ls

5番になりました。
次は、5番のバッファを削除後、:newしてみます。

:bd 5
:new
:ls

まあ予想はしていましたが、6番になりました。

以前削除したバッファ番号についてbufexistsを使ってみたら、TRUEが返ってきたことがありましたが、何か関連しているのでしょうか。
内部でバッファ存在している気がしてなりません。
そんなことを考えながら、ヘルプを探していると、とあるコマンドを見つけました。
:bwipeout
名前から想像するに、バッファをぬぐいとってくれるのでは?

使ってみます。省略形で:bwで良いです。

:bw 1
:new
:ls

変わりませんでした。

VimScript window

今回も、バッファについて学習。
前回、:newや、:bd、:bwを使いながらバッファの理解を試みましたが、基本が分かってないので、どうにもこうにも理解が進みません。ってことで、基礎から勉強することにしました。
バッファとは何か。ヘルプに書いてあります。

:h window
1. Introduction                                 *windows-intro* *window*

Summary:
   A buffer is the in-memory text of a file.
   A window is a viewport on a buffer.
   A tab page is a collection of windows.

A window is a viewport onto a buffer.  You can use multiple windows on one
buffer, or several windows on different buffers.

A buffer is a file loaded into memory for editing.  The original file remains
unchanged until you write the buffer to the file.

A buffer can be in one of three states:

ふむふむ。つまりこういうことなんでしょう。
ファイル(file)があります。
バッファ(buffer)は、ファイルのテキストのこと(in-memory)
そして、ウィンドウ(window)は、バッファを覗くための窓のこと。

windowは、一つのバッファに幾つあっても良いですね。

buffer/file
 +--------+
 |        |
+----------+
||        || window
+----------+
 |        |
 |        |
 |        |
+----------+
||        || window
+----------+
 |        |
 |        |
 +--------+

でも、バッファとファイルの関係は、一対一ですね。

では確認。Vimを起動します。

~/tmp $ vim

起動後、新規ファイル名を指定してバッファを開きます。

:e file1.txt


この時点のバッファを確認。

:ls


新規ファイル名を指定してバッファを開きます。

:new file2.txt
:ls


各バッファにHELLOって書き込みます。

:windo norm 0iHELLO


両方のバッファにHELLOと書かれました。
次に2番のバッファをdeleteします。
HELLOを書き込んでからファイルへ保存してないので、!をつけないとエラー出ます。

:bd! 2
:ls

file1.txtしか、もうバッファは残っていないように見えます。
しかし、bufexistsで確認すれば分かりますが、2番は存在するんですよね。

:echo bufexists(2)


ということで、思い切って、2番を指定してバッファを開いてみます。

:sb 2
:ls


なんと! 上の画面に、file2.txtというファイル名でバッファが現れました。

続きです。

前回削除したバッファを開いたら、最初に開いた時のファイル名file2.txtでバッファが現れました。
しかし、lsの表示では、1番のバッファしか存在してません。
上のバッファに書き込みができるか試してみます。

書き込めました。lsも見てみます。

:ls


やはり1番しかリストに表示されていません。
次は、上のバッファをfile2.txtに保存してみたいと思います。

:!ls file2.txt


ファイル保存はできてそうです。
今まで:bdeleteでバッファ削除してましたが、:bwipeoutを使ってみます。

:wipeout 2
:echo bufexists(2)


これで2番のバッファが完全に消えたようです。
2番を指定してバッファは開けないはずです。

:sb 2


予想通り、開くことはできませんでした。

VimScript :bwipeout

:bwipeoutについてもうすこし詳しく学習します。

:h :bw
:[N]bw[ipeout][!]                       *:bw* *:bwipe* *:bwipeout* *E517*
:bw[ipeout][!] {bufname}
:N,Mbw[ipeout][!]
:bw[ipeout][!] N1 N2 ...
                Like |:bdelete|, but really delete the buffer.  Everything
                related to the buffer is lost.  All marks in this buffer
                become invalid, option settings are lost, etc.  Don't use this
                unless you know what you are doing. Examples:
                    :.+,$bwipeout   " wipe out all buffers after the current
                                    " one
                    :%bwipeout      " wipe out all buffers

自分が何をしているか分からないなら、使っちゃダメって書いてありますね。
:%bipeoutなんて実行する人は、分かってない人確定なんですが、私はよく分かってませ>ん。使ってみたいと思います。

~/tmp $ vim

無名で起動した後に、上下スプリットしておいて、ls確認。

:new
:ls

〜からの、ワイプアウト!

:%bwipeout

上下スプリットが消えてしまいました。2バッファ消えたようです。
lsを確認してみます。

なぜか2番が残ってます。

無名バッファだと動きがよく分かりません。ファイルを2つ作成して、Vimで2つを開い>てからワイプアウトしてみます。

~/tmp $ echo HELLO1>file1.txt
~/tmp $ echo HELLO2>file2.txt
~/tmp $ vim -o file*.txt

見るまでもないですが、ls。

:ls

では、ここでワイプアウト!

:%bwipeout

ワイプアウトした後の画面。

file1.txt、file2.txtのバッファは消えてますね。
lsも確認。

:ls

新たに3番のバッファが追加されて、無名バッファになっています。ここでもう一度、ワイプアウト。

:%bwipeout
:ls

3番のままです。

無名のバッファがない場合は、ワイプアウトした後は、バッファリストから削除されますが、Vimのスクリーンには無名バッファを表示しないといけないので、新たにバッファ番号が追加されるようです。 しかし、無名バッファが未編集で残っている場合は、そのまま残すようなのでバッファ番号が追加されないのでしょう。
確認してみます。

~/tmp $ vim -c ls


未編集で、:%bw

:%bw


1番のままです。
次はこのバッファに書き込んで、:%bw

:ls


1番のバッファは削除され、新規に2番の無名バッファが作成されました。

VimScript getbufinfo

バッファの情報を得る関数 getbufinfoを使ってみます。

/tmp $ vim -o file*.txt

:echo getbufinfo(1)

連想配列になってますね。

ヘルプを見ると、キーはこれだけあるようです。

bufnr           buffer number.
changed         TRUE if the buffer is modified.
changedtick     number of changes made to the buffer.
hidden          TRUE if the buffer is hidden.
listed          TRUE if the buffer is listed.
lnum            current line number in buffer.
loaded          TRUE if the buffer is loaded.
name            full path to the file in the buffer.
signs           list of signs placed in the buffer.
                Each list item is a dictionary with
                the following fields:
                    id    sign identifier
                    lnum  line number
                    name  sign name
variables       a reference to the dictionary with
                buffer-local variables.
windows         list of |window-ID|s that display this
                buffer
popups          list of popup |window-ID|s that
                display this buffer

nameだけ取り出してみましょう。

:echo a[0].name

複数のバッファの情報が取り出せるようにリストで返ってくるんですね。

VimScript arc

argc関数使います。
ヘルプから。

argc([{winid}])
    The result is the number of files in the argument list.  See
    |arglist|.
    If {winid} is not supplied, the argument list of the current
    window is used.
    If {winid} is -1, the global argument list is used.
    Otherwise {winid} specifies the window of which the argument
    list is used: either the window number or the window ID.
    Returns -1 if the {winid} argument is invalid.

引数で指定したファイル数が分かるんですね。
早速ファイル名を指定して起動。

/tmp $ vim file1.txt


argcを実行してみます。

:echo argc()


引数で指定したファイルは1個だったので、1が返ってきました。
Vimを終了して、今度は複数ファイルで起動してみます。

/tmp $ touch test_{1..100}.txt
/tmp $ vim test_*.txt


Vimの最下段の通知を見ると、ファイルが1つしか開いてないように見えますが、
タイトルバーにファイルがたくさん並んでるので、ファイルを全部開いているのでしょう。
argcを実行する前に、lsを見てみます。

:ls


多すぎて表示し切れてないですね。
では、argc実行。

:echo argc

argcが起動した時の引数なら、bdeleteした後も、数は変わらないハズですが、確認してみましょう。

:echo argc()


1番のバッファをbdeleteします。

:bd 1


argcを確認。

:echo argc()


100のままですね。
次は、bwipeoutしてみます。

:bw 2

argcを確認。

:echo argc()


想定通り、100のままでした。
タイトルバーの表示も起動時から変わってないですね。

VimScript argidx

argidx関数を使ってみます。
ヘルプから。

argidx()        The result is the current index in the argument list.  0 is
                the first file.  argc() - 1 is the last one.  See |arglist|.

現在開いているファイルのインデックス番号ですね。
最初のファイルなら0。最後のファイルは、argc-1になるようです。 ファイルを100個開いて確認してみます。

/tmp $ touch test_{1..100}.txt
/tmp $ vim test_*.txt


起動直後なので、インデックス0のファイルを開いているハズです。

:echo argidx()


0ですね。
次のバッファへ切り替えます。

:next


バッファとファイルが紐ついているので、次のファイルに切り替わりました。
では、argidxを確認します。

:echo argidx()


インデックスは1が返ってきました。

argidxは起動時に指定したファイルのインデックスであることは分かりました。
やはりこれもバッファ削除するとどうなるか気になるので確認しておきます。

起動方法は同じ。

/tmp $ touch test_{1..100}.txt
/tmp $ vim test_*.txt


1から10のバッファをbdelete。

:bdelete 1 2 3 4 5 6 7 8 9 10


10バッファ削除したので、argidxを確認してみましょう。

:echo argidx()


え!?
0のままです。どういうことでしょう? lsを確認。

:ls


11番のバッファ(test_18.txt)がカレントバッファになってます。
想定ではargidxの結果は10になって欲しいところです。
バッファリストと引数リストは別物なんですね。
この状態で、次のバッファにしてみます。

:next


test_10.txtになったってことを、おそらくargidxは1が戻ります。

:echo argidx()


まあよくよく考えると、前々回に、バッファ削除後のargcを確認しましたが、引数の数は
変化しなかったので、当然の結果ですが、バッファ削除を使うのが怖くなりました。

VimScript :args

lsでバッファリストの確認ができますが、

:ls


バッファリストだけでなく、引数リストも確認してみましょう。
argsです。

:args


[]次のファイルが、カレントファイルということですね。
画面スプリットするとどうなるでしょうか。

:split
:next


このようにスプリットした状態で、argc確認。

:args

カレントバッファのみが[]が付くようです。

arglistを選択し直すには、args ファイルリスト を使います。
特定のファイルを指定してVimを起動してみます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_2.txt


arglistを確認。

:args


当然一つだけですね。では、arglistを再度設定します。

:args test_*.txt


arglistを確認。

:args


argsを使いこなしてVim編集能力を向上したいです。

VimScript :argdelete

バッファ削除があるなら、引数削除もありますね。
argdeleteを使ってみます。
現在のargsの状態。

:args


ここからtest_1.txtを削除してみます。

:argdelete test_1.txt


argdeleteしても、画面ではtest_1.txtが、見えてますね。
argsを確認。

:args


引数リストからは削除されました。
バッファリストと引数リストがリンクしていないので、ものすごくややこしいです。
リンクさせない理由があるんでしょうけど。

/tmp $ touch test_{1..100}.txt
/tmp $ vim test_*.txt

最初にargs確認。

:args

全てのパターンを指定してargdeleteを実行します。

:argdelete *

特に何も変わってないように見えます。
argsを見てみます。

:args

全部消えてますね。

argcの数も0になっているでしょうか。

:echo argc()

パターンを指定して
バッチリ0になってます。少しずつですが、引数リストについて分かってきました。

VimScript arglist

bufferとargについてもう少し学習します。
引数のリスト、arglistを表示にするには、:argsを使います。
:argsで表示されるリストは、vim起動時の引数、つまり編集しているファイルのリストが表示されます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

bufferのリストは:lsで表示します。

:ls

argリストとbufferリスト同じ数ですね。

では、新規バッファを作成してみます。バッファの名前はtest_6.txtにします。

:new test_6.txt

スプリットされた上に配置されました。
bufferリストを確認してみます。

:ls

bufferリストは、5つから1つ増えて6つになってます。

次は、arglist。

:args

arglistの方は、5つのままです。

では、新規に追加したtest_6.txtを保存するとどうなるでしょう。

:write

保存後のarglistを確認。

:args


変化なしですね。

さきほど5つのファイルを引数に指定してVimを起動した後、新たにbufferを追加しました。

test_6.txtは、保存済みなので、このまま閉じます。
ZZ
画面が一つになったところで、現在のbufferを確認します。

:ls


%がtest_1.txtになってます。argsを見てみます。

:args


test_1.txtが括弧で括られているので、lsで選択されているbuffer名と同じですね。

:nextを実行してみます。

:next
:args


括弧が次のファイルに移動しました。
test_5.txtまで進めてみます。

:3next
:args


括弧がtest_5.txtに移動しました。
では現在のlsを確認。

:ls


%がtest_5.txtを指しているので、argsでの選択ファイルと一致しています。
では次。さらにnextするとどうなるか。

:next


エラーになりました。最後のファイルを超えて進めないようです。

arglistのヘルプを見てみます。

:h arglist

3. The argument list                            *argument-list* *arglist*

If you give more than one file name when starting Vim, this list is remembered
as the argument list.  You can jump to each file in this list.

Do not confuse this with the buffer list, which you can see with the
|:buffers| command.  The argument list was already present in Vi, the buffer
list is new in Vim.  Every file name in the argument list will also be present
in the buffer list (unless it was deleted with |:bdel| or |:bwipe|).  But it's
common that names in the buffer list are not in the argument list.

This subject is introduced in section |07.2| of the user manual.

There is one global argument list, which is used for all windows by default.
It is possible to create a new argument list local to a window, see
|:arglocal|.

You can use the argument list with the following commands, and with the
expression functions |argc()| and |argv()|.  These all work on the argument
list of the current window.

以前も使いましたが、arglistはargc()や、argv()といった関数が使えるんですね。

では適当にファイルを引数に指定してVim起動。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

arglist確認。

:args


関数でarglistを取得してみます。

:echo argv()


この時のarglistの数は、

:echo argc()

VimScript :next

:nextコマンドのヘルプを見てみます。

:h next
:n[ext] [++opt] [+cmd] {arglist}                        *:next_f*
                        Same as |:args_f|.

:n[ext]! [++opt] [+cmd] {arglist}
                        Same as |:args_f!|.

arglistが対象であることがわかるので、バッファが存在していても、arglistに存在して
いないファイルへは移動できないようです。

では、argsになくて、lsに存在しているbufferへはどう移動すれば良いのでしょうか。

答えは簡単。bnextを使えば良いです。ただのnextだとarglist内の移動、bnextだとbufferlist内の移動ができます。

:bnext

bufferリストを確認。

:ls

では、この時のargsはどうなっているのか。

:args


なんと、test_5.txtが選択されたままです。ややこしいです。
argsとlsがずれたままVimを使うのは頭こんがらがります。

VimScript :buffer-list

bufferとargについて、各コマンドが見やすく一覧できるヘルプがあるので確認してみま>しょう。

:h buffer-list
7. Argument and buffer list commands                    *buffer-list*

      args list                buffer list         meaning
1. :[N]argument [N]     11. :[N]buffer [N]      to arg/buf N
2. :[N]next [file ..]   12. :[N]bnext [N]       to Nth next arg/buf
3. :[N]Next [N]         13. :[N]bNext [N]       to Nth previous arg/buf
4. :[N]previous [N]     14. :[N]bprevious [N]   to Nth previous arg/buf
5. :rewind / :first     15. :brewind / :bfirst  to first arg/buf
6. :last                16. :blast              to last arg/buf
7. :all                 17. :ball               edit all args/buffers
                        18. :unhide             edit all loaded buffers
                        19. :[N]bmod [N]        to Nth modified buf

  split & args list       split & buffer list      meaning
21. :[N]sargument [N]   31. :[N]sbuffer [N]     split + to arg/buf N
22. :[N]snext [file ..] 32. :[N]sbnext [N]      split + to Nth next arg/buf
23. :[N]sNext [N]       33. :[N]sbNext [N]      split + to Nth previous arg/buf
24. :[N]sprevious [N]   34. :[N]sbprevious [N]  split + to Nth previous arg/buf
25. :srewind / :sfirst  35. :sbrewind / :sbfirst split + to first arg/buf
26. :slast              36. :sblast             split + to last arg/buf
27. :sall               37. :sball              edit all args/buffers
                        38. :sunhide            edit all loaded buffers
                        39. :[N]sbmod [N]       split + to Nth modified buf

40. :args               list of arguments
41. :buffers            list of buffers

The meaning of [N] depends on the command:
 [N] is the number of buffers to go forward/backward on 2/12/22/32,
     3/13/23/33, and 4/14/24/34
 [N] is an argument number, defaulting to current argument, for 1 and 21
 [N] is a buffer number, defaulting to current buffer, for 11 and 31
 [N] is a count for 19 and 39

Note: ":next" is an exception, because it must accept a list of file names
for compatibility with Vi.

40のargsに対して、41にbuffersがありますが、lsと同じです。

7番のallを使ってみます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

:all

VimScript :argadd

arglistとbuffer-listが一致しないままVimを使用するのはまだ不安なので、両者を一致>させる方法を考えてみます。

今起動しているVimは、このように両リストは不一致しています。

:ls
:args

arglistに、test_6.txtが不足しているので、arglistにtest_6.txtを追加するだけですね
。 おそらくargをaddするコマンドがあると思います。ヘルプで見てみます。

:h argadd
:[count]arga[dd] {name} ..                      *:arga* *:argadd* *E479*
:[count]arga[dd]
                        Add the {name}s to the argument list.  When {name} is
                        omitted add the current buffer name to the argument
                        list.
                        If [count] is omitted, the {name}s are added just
                        after the current entry in the argument list.
                        Otherwise they are added after the [count]'th file.
                        If the argument list is "a b c", and "b" is the
                        current argument, then these commands result in:
                                command         new argument list
                                :argadd x       a b x c
                                :0argadd x      x a b c
                                :1argadd x      a x b c
                                :$argadd x      a b c x
                        And after the last one:
                                :+2argadd y     a b c x y
                        There is no check for duplicates, it is possible to
                        add a file to the argument list twice.
                        The currently edited file is not changed.
                        Note: you can also use this method:
                                :args ## x
                        This will add the "x" item and sort the new list.

使ってみます。

:argadd test_6.txt

順番は違いますが、リストの数は一致しました。

VimScript :first

arglistの先頭のファイルを選択するときに使います。
いつものように、適当に複数ファイルを起動。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

arglist中の他のファイルを選択します。先頭以外ならどれでも良いです。

:3next

現在のarglistを確認。

:args

では:firstを実行してみます。

:first

先頭のファイルに切り替わったようです。arglistを確認してみます。

:args

:rewindってコマンドも:firstと同じです。
先頭以外のファイルに切り替えてから、

:2next
:args

:rewindを実行。

:rewind
:args


先頭ファイルになりました。

VimScript :last

複数ファイルを起動。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

適当に最後以外のファイルに切り替えて、
(最初のファイルが選択されているのでいるので良いです)

:2next
:args

:last起動。

:last

arglistも最後のファイルが選択されています。

:args

VimScript :wnext

:nextコマンドで、arglistの次のファイルにバッファを切り替えすることができましたが、:wnextを使うと、保存してから切り替えることができます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

いつものように複数起動。

:args


現在のバッファには、最初のファイルが選択されています。
テキストに適当に書き込みます。
:wnextを実行。

:wnext


arglistを確認。

:args

保存してから、前のファイルに切り替えるには、:wNext(:wN)です。

:wNext

:wNextは、:wprevious(:wp)と同じです。Shift押さないで済むので、私は、こちらの方が
使いやすいです。

:last
:wprevious

VimScript execute

:から始まるExコマンドについても学習してきましたが、Exコマンドをスクリプトから実行するにはexexute関数を使います。

ヘルプを確認。

:h extecute(
execute({command} [, {silent}])                                 *execute()*
                Execute an Ex command or commands and return the output as a
                string.
                {command} can be a string or a List.  In case of a List the
                lines are executed one by one.
                This is equivalent to:
                        redir => var
                        {command}
                        redir END

                The optional {silent} argument can have these values:
                        ""              no `:silent` used
                        "silent"        `:silent` used
                        "silent!"       `:silent!` used
                The default is "silent".  Note that with "silent!", unlike
                `:redir`, error messages are dropped.  When using an external
                command the screen may be messed up, use `system()` instead.
                                                        *E930*
                It is not possible to use `:redir` anywhere in {command}.

                To get a list of lines use |split()| on the result:
                        split(execute('args'), "\n")

                To execute a command in another window than the current one
                use `win_execute()`.

                When used recursively the output of the recursive call is not
                included in the output of the higher level call.

使ってみます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

extecuteの結果をechoで表示

:echo execute("args")

execute関数を使う練習をしてみます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt

現在のarglistを確認。

:args


arglistを変数に格納。

:let arr=execute("args")

変数の中身を確認してみます。

:echo arr


ではarrをforで回してみます。

:for f in arr


なぬっ、こんな使い方はできない模様。
そもそも:argsで返ってくるのは、リストではないようです。なので、arglistを得たい時は、素直にargv()を使えばいいですね。

:let arr=argv()
:echo arr


あ、これ、これです。リスト形式。
でもこれではexecuteの学習にはならないので、executeでリストを得る方法を探してみます。まあ探すも何も、ヘルプに書いてあります。

:h execute(
                To get a list of lines use |split()| on the result:
                        split(execute('args'), "\n")

使ってみます。

:let arr=split(execute('args'), "\n")
:echo arr


いや、これ違いますね。一個のリストになってます。空白でsplitする必要がありますので、

:let arr=split(execute('args'), " ")

こうですね。

:echo arr


リストになりました。

splitとexecuteを組み合わせてarglistを得る方法では、カレントファイルが[]付の名前>になってしまうので、それを外す処理が面倒です。そもそもargv()でarglistは取得できるので、argv()が常に現在のarglistについて結果を返してくれるなら、わざわざexecuteを使う必要がないですね。
:argsでリストを変更した後に、argv()がちゃんと変化するのか確認してみます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt
echo argv()

:args test_2.txt
echo argv()


起動直後のarglistではなく、現在のarglistの結果が返ってきました。ってことでargv()を使えばいいので、executeでarglistを取得する必要はなさそうです。では、buffer-listの方はどうでしょう。
:lsの結果を変数に格納する場合は、executeが使えるのでは。

:let arr=execute('ls')
:echo arr


あ、splitが必要そうです。

:let arr=split(execute('ls'),"\n")
:echo arr

:lsの結果を変数に格納すると、ファイル名以外の情報も格納されてしまいます。
ファイル名のみ抽出してみます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt
:let arr=split(execute('ls'),"\n")
:echo arr

:let arr=split(execute('ls'),"\n")
:echo arr
:call map(arr,'split(v:val,''"'')')
:echo arr


さらにmap。

:call map(arr,'v:val[1]')
:echo arr

VimScript filter

filterを使います。

前回は:lsの結果をmapを2回繰り返して、buffer-listのファイル名を取得しましたが、
もう少し簡単に抽出してみます。

/tmp $ touch test_{1..5}.txt
/tmp $ vim test_*.txt
:let arr=split(execute('ls'),'"')
:echo arr

あとは、配列の奇数番目を抽出すれば良いですね。

:let i=0
:while i < len(arr)
:  if i % 2 == 1
:    echo arr[i]
:    endif
:  let i=i+1
:  endwhile

これをfilterを使って、もっとシンプルにしてみます。

filterのヘルプを確認。

:h filter(
filter({expr1}, {expr2})                                *filter()*
                {expr1} must be a |List| or a |Dictionary|.
                For each item in {expr1} evaluate {expr2} and when the result
                is zero remove the item from the |List| or |Dictionary|.
                {expr2} must be a |string| or |Funcref|.

〜省略〜

                Example that keeps the odd items of a list:
                        func Odd(idx, val)
                          return a:idx % 2 == 1
                        endfunc
                        call filter(mylist, function('Odd'))
                It is shorter when using a |lambda|:
                        call filter(myList, {idx, val -> idx * val <= 42})
                If you do not use "val" you can leave it out:
                        call filter(myList, {idx -> idx % 2 == 1})

ちょうど奇数抽出のことがExampleで述べられています。
これを使ってみます。

:let a=filter(arr,{idx -> idx % 2 == 1})
:echo a

filter続きです。

テキストのフィルタにも使えますね。

/tmp $ vim
:0r!seq 20

:let arr=getline("w0","w$")
:echo arr

3つおきに抽出。

:let a=filter(arr,{idx -> idx % 3 == 0})
:echo a

テキストを置き換えます。

:call setline("w0w$",a)

下の方が削除されてません。
どうやら、全部削除してからsetlineする必要がありそうです。

バッファを全削除します。

:0,$d

再度setline。3行おきに抽出したテキストに置き換えます。

:call setline("w0",a)

VimScript :buffer

:ls


バッファを切り替えるには、:bufferコマンド(:b)を使います。2番目のバッファに切り替えてみます。

:b2


バッファ番号は、先に指定しても大丈夫です。

:4b


:(コロン)さえ入力するのが面倒な場合は、数字を押してから(CtrlとShiftを押しながら6)でもバッファが切り替わります。

2<C-^>

VimScript :bfirst :blast

arglistの先頭のファイルを選択するのに:firstを使いましたが、buffer-listの先頭を選択するには、:bfirstを使います。bが先頭につくだけですね。ですので最後のファイルを選択するコマンドは:blastになります。
:bfirst :blast使ってみます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt
:ls


カレントバッファは、buffer-listの最初にあります。カレントバッファを最後のバッファにしてみます。

:blast


次は最初のバッファ。

:bfirst

VimScript :bmodified

:bmodified使います。

:bmodifiedは、変更したバッファをカレントバッファにしてくれる使いこなすと便利なコマンドです。

まずは複数ファイルを起動。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

今、最初のバッファがカレントバッファになっています。
まだ変更していませんので、:bmodifiedを使ってもカレントバッファは切り替わりません。
実行してみます。

:bmodified

エラーメッセージが表示されました。

では、3番のバッファに切り替えてからテキストを変更してみます。

:b3

テキストを編集しました。保存はしてません。

この状態で、:bfirstします。

:bfirst

保存していないので、バッファが切り替えできませんでした。
!をつけて強制的に切り替えます。

:bfirst!


最初のバッファに移動しました。
では、:bmodifiedを使います。

:bmodified


変更したバッファに切り替わりました。

先ほど変更バッファを保存せずに、:bmodifiedを使って、変更バッファまで移動しましたが、
保存後のバッファも:modifiedの切り替え対象として認識してくれるのでしょうか。
確認してみます。

前回の変更バッファにて、:writeを実行して保存します。

:write

最初のバッファに切り替えます。

:bfirst

では、:bmodifiedで切り替わるでしょうか。

:bmodified


エラーになりました。

:bmodifiedを使いこなすには、こまめに:writeする癖を直す必要がありそうです。

arglist用のコマンドも存在するでしょうか。
buffer-list、arglistに対応するコマンドは、ヘルプで確認できます。以前もやりましたね。

:h buffer-list
7. Argument and buffer list commands                    *buffer-list*

      args list                buffer list         meaning
1. :[N]argument [N]     11. :[N]buffer [N]      to arg/buf N
2. :[N]next [file ..]   12. :[N]bnext [N]       to Nth next arg/buf
3. :[N]Next [N]         13. :[N]bNext [N]       to Nth previous arg/buf
4. :[N]previous [N]     14. :[N]bprevious [N]   to Nth previous arg/buf
5. :rewind / :first     15. :brewind / :bfirst  to first arg/buf
6. :last                16. :blast              to last arg/buf
7. :all                 17. :ball               edit all args/buffers
                        18. :unhide             edit all loaded buffers
                        19. :[N]bmod [N]        to Nth modified buf
  
〜後は省略〜

:bmodfiedは、19番なので、相当するarglistのコマンドは、左へたどると空白になってますので、存在しませんね。

じゃあarglistから変更中のファイルを探すにはどうするって話ですね。
変更中のバッファがある場合に、arglistを選択し直すとどうなるか確認してみます。

まずは複数ファイルを用意しておいて、その中から1つだけVimで起動。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_3.txt

このファイルを編集します。

保存はしません。
次に、argsで先ほど作成したファイル全てを選択。

:args test_*.txt

あ、!を付けるのを忘れてました。

:args! test_*.txt

buffer-listを確認。

:ls


では、:bmodifiedを使ってみます。

:bmodified


なんと!
先ほどの編集はなきものにされました。

複数のファイルの変更後に、:bmodifiedを使ってみます。

ファイル5個新規作成して、Vimで開きます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

2番目のバッファを開く。

:b2

以下のように編集。保存はしません。

次に4番目のバッファを開きます。先ほどのバッファは保存していないためエラーになるので、!をつけて実行します。

:b!4

では、1番目のバッファに戻ってみます。

:b!1

:bmodified、1回目実行。

:bmodified

:bmodified、2回目実行。

:bmodified

エラーになりました。 !が必要でしたね。

:bmodified!

4番目のバッファが表示されました。

もう一度:modified!を実行すると、どうなるでしょうか。

:bmodified!


2番目のバッファが再度表示されました。
最後の編集ファイルまで表示した後は、最初に戻って再度編集ファイルを見つけて表示してくれるようです。

VimScript glob

glob関数で、ワイルドカードが指定できます。

 
:echo glob("test_*.txt")

VimScript :badd

:badd使います。

複数ファイルを作成しておいて、一つだけVimで開きます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_1.txt

:baddでファイルを指定します。

:badd test_2.txt


あれ!? 何か変わったのでしょうか。
buffer-listを確認します。

:ls


カレントバッファは変更されずに、buffer-listにバッファが追加されました。
:baddのヘルプを見てみます。

:bad[d] [+lnum] {fname}
                Add file name {fname} to the buffer list, without loading it.
                If "lnum" is specified, the cursor will be positioned at that
                line when the buffer is first entered.  Note that other
                commands after the + will be ignored.

buffer-list上に既に存在するファイル名を指定すると、どうなるでしょうか。

:badd test_1.txt


特にエラーは出ませんね。
buffer-listは、

:ls


特に変わりません。
今buffer-listに入っているバッファのことは気にせずに、:baddが使えるってことですね。

:baddで複数ファイルを指定してみます。

前回と同様で、複数ファイルを作成して、一つだけVimで開きます。

                                         
/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_1.txt   

複数ファイル名を指定してbaddしてみます。

:badd test_*.txt


エラーになりました。

ファイル名が多すぎるようです。
前回見ましたが、もう一回ヘルプを確認。

 
:h :badd
:bad[d] [+lnum] {fname}

〜省略〜

ファイル名、一つのようですね。

次は複数ファイルを:baddします。
globでファイル名を取得できるので、ファイル名をリストに変換して格納します。

                 
:let arr = split(glob("test_*.txt"),"\n")
:echo arr

リストになってますね。

このリストを使ってforで回してbaddします。

 
:for fname in arr
:  badd fname
:  endfor
:ls


え!? バッファにファイル名が追加されてません。fnameって名前が追加されています。
なぜでしょう。baddの行をもう一度確認。

:  badd fname 

そりゃそうですよね。fnameを追加していますから。
変数の中身の名前を指定しなければなりません。

:for fname in arr
:  execute "badd" fname
:  endfor 
:ls


追加されました(fnameっていうバッファが残ってますが)。

VimScript :bufdo

buffer-listの操作はだいたい分かりました。
次は、各バッファに一括編集、bufdoを学習します。

まずはヘルプ確認。

:[range]bufdo[!] {cmd}  Execute {cmd} in each buffer in the buffer list or if
                        [range] is given only for buffers for which their
                        buffer number is in the [range].  It works like doing
                        this:
                                :bfirst
                                :{cmd}
                                :bnext
                                :{cmd}
                                etc.

buffer-listの中の全てのバッファへの編集だけでなく、rangeを指定することで、特定のバッファのみに絞り込んで編集ができるようです。
使ってみます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

上記のように起動した後、lsを確認。

:ls

:ballで全てのバッファをスプリット表示しておきます。

:ball

:bufdoを使って、”HELLO”を”HELL “に置換してみます。

:bufdo s/HELLO/HELL /


エラーになりました。毎度!をつけるのを忘れていますね。

!をつけて、もう一度トライ。

:bufdo! s/HELLO/HELL /


見事に全バッファ置換されました。

bufdoで置換を実施しただけなので、各バッファはファイルへの保存はしてない状態のはずです。
:bmodifiedで編集中のファイルへ切り替わるか確認してみます。

:bmodified


一番上の窓のバッファがtest_1.txtに変更されました。編集中のバッファのうち先頭のバッファ、つまりtest_1.txtに
切り替わったってことですね。

もう一度、実行してみます。

:bmodified


またエラー出ました。!つけ忘れです。

:bmodified!

一番上の窓が、test_2.txtに切り替わりました。
まあ、全ファイルが保存されていない状態だと思いますので、手取り早く終了して再起動してみます。

:qa!
/tmp $ vim test_*.txt

ballします。

:ball

次は範囲を指定して:bufdoを使ってみます。

まず複数ファイル作成してVim起動。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

buffer-listを確認。

:ls

2と3のバッファを置換してみます。

:2,3bufdo! s/HELLO/HELL /

全バッファ見てみます。

:ball


2と3のバッファが置換されてますね。

VimScript :argdo

:bufdoを実行した直後に、:argdoを実行してみます。

前回:2,3bufo s/HELLO/HELL/ を実行したところからの続きです。

ではargdo実行。

:3,4argdo! s/LL/OO/

Enterを押してメッセージを消すと。

さらに:ball

:ball


bufdoで置換した後、バッファの保存は実行していませんが、argdoでバッファが初期状態に戻ることなく置換されました。

VimScript :windo

:argdoの次は:windoです。

いつものようにまずは複数ファイルを作成してVim起動。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

buffer-listを確認。

:ls

画面スプリットでtest_3.txtも開いておきます。

:vs test_3.txt

では、:windoを使って置換。

:windo! s/HELLO/ABCD /

windoの場合は、!は不要なようです。

:windo! s/HELLO/ABCD /

置換できたようですね。

全バッファ見てみます。

:ball

VimScript :tabnew

:tabnew使ってみます。

Vimを起動します。

/tmp $ vim

tabnew実行。

:tabnew

もう一度tabnew実行。

:tabnew

画面上部にNo Nameが3つ表示されて、一番右のNo Nameが黒背景に白文字になっていますが、これが現在選択中のタブ>を表しているようです。

次のタブへ切り替えるには、:tabn(ext)を使います。

:tabn

前のタブへ戻すには、:tabNです。

:tabN

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_1.txt

ファイルを指定してtabnewします。

:tabnew test_2.txt

さらにもう一回。

:tabnew test_3.txt

3つのファイルがそれぞれタブに割り当たりました。
buffer-listを確認してみます。

:ls


タブを追加したタイミングでバッファを追加されているようです。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

buffer-list確認。

:ls

全部バッファをタブで開きます。

:bufdo tabnew

Enterを押します。
いちばん最後に追加したNo Nameのタブを削除します。

:quit


出来上がり。

VimScript :tabmove

:tabmoveを使ってみます。
ヘルプを確認。

:h tabmove
:tabm[ove] [N]                                          *:tabm* *:tabmove*
:[N]tabm[ove]
                Move the current tab page to after tab page N.  Use zero to
                make the current tab page the first one.  N is counted before
                the move, thus if the second tab is the current one,
                `:tabmove 1` and `:tabmove 2`  have no effect.
                Without N the tab page is made the last one.
                    :.tabmove   " do nothing
                    :-tabmove   " move the tab page to the left
                    :+tabmove   " move the tab page to the right
                    :0tabmove   " move the tab page to the beginning of the tab
                                " list
                    :tabmove 0  " as above
                    :tabmove    " move the tab page to the last
                    :$tabmove   " as above
                    :tabmove $  " as above

移動したいタブ番号を指定すれば良いようです。
ではいつもの起動。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

そして、それぞれのバッファでタブを追加。

:bufdo tabnew


No Nameのタブを移動してみます。

:tabmove 1


2番目に移動しました。
ってことは、先頭に移動したい場合は、0を指定するんですね。

:tabmove 0


最後に移動するには、単にtabmoveだけですね。

:tabmove

VimScript :tabclose

タブを閉じる:tabcloseを使ってみます。
ヘルプを確認。

                                                        *:tabc* *:tabclose*
:tabc[lose][!]  Close current tab page.
                This command fails when:
                - There is only one tab page on the screen.             *E784*
                - When 'hidden' is not set, [!] is not used, a buffer has
                  changes, and there is no other window on this buffer.
                Changes to the buffer are not written and won't get lost, so
                this is a "safe" command.
                    :tabclose       " close the current tab page

カレントタブを消すだけですね。
ファイルを複数起動して:tabcloseを使ってみます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

全バッファをタブで開きます。

:bufdo tabnew

カレントタブはNo Nameです。このタブで、tabclose使ってみます。

:tabclose

No Nameのタブは消えて、test_5.txtのタブが、カレントタブになりました。

:ls

次は、:tabcloseではなく、:quitで閉じてみます。

:quit

test_5.txtのタブは消えて、test_4.txtのタブが、カレントタブになりました。
buffer-listを確認。

:ls

:quitで同じことができるなら、:quitでいい気がします。

VimScript :tabdo

:tabdo使います。

ヘルプを確認。

                                                        *:tabd* *:tabdo*
:[range]tabd[o] {cmd}
                Execute {cmd} in each tab page or if [range] is given only in
                tab pages which tab page number is in the [range].  It works
                like doing this:
                        :tabfirst
                        :{cmd}
                        :tabnext
                        :{cmd}
                        etc.
                This only operates in the current window of each tab page.
                When an error is detected on one tab page, further tab pages
                will not be visited.
                The last tab page (or where an error occurred) becomes the
                current tab page.
                {cmd} can contain '|' to concatenate several commands.
                {cmd} must not open or close tab pages or reorder them.
                Also see |:windo|, |:argdo|, |:bufdo|, |:cdo|, |:ldo|, |:cfdo|
                and |:lfdo|

:bufdoや:argdoと同じく、リストの順番に実行するだけっぽいですね。

では、使ってみましょう。

:tabdo s/HELLO/HELL /

test_4.txtのタブは置換されたようですが、他のタブはどうでしょうか。

:tabnext

test_1.txtのタブも置換されてますね。

VimScript :tab

:tabを使います。
ヘルプ。

:[count]tab {cmd}                                       *:tab*
                Execute {cmd} and when it opens a new window open a new tab
                page instead.  Doesn't work for |:diffsplit|, |:diffpatch|,
                |:execute| and |:normal|.
                If [count] is given the new tab page appears after the tab
                page [count] otherwise the new tab page will appear after the
                current one.
                Examples:
                    :tab split      " opens current buffer in new tab page
                    :tab help gt    " opens tab page with help for "gt"
                    :.tab help gt   " as above
                    :+tab help      " opens tab page with help after the next
                                    " tab page
                    :-tab help      " opens tab page with help before the
                                    " current one
                    :0tab help      " opens tab page with help before the
                                    " first one
                    :$tab help      " opens tab page with help after the last
                                    " one

タブを新規に開いて、コマンドを実行してくれるコマンドのようです。
使い道は、helpを別タブで開きたいとき、しか思いつきません。

使ってみます。

/tmp $ vim test_1.txt

:tab help

helpは省略して書けるので、:tab hでも同じことができますね。

:tab h :tab

VimScript :tabonly

:tabonly使ってみます。
ヘルプを確認。

                                                        *:tabo* *:tabonly*
:tabo[nly][!]   Close all other tab pages.
                When the 'hidden' option is set, all buffers in closed windows
                become hidden.
                When 'hidden' is not set, and the 'autowrite' option is set,
                modified buffers are written.  Otherwise, windows that have
                buffers that are modified are not removed, unless the [!] is
                given, then they become hidden.  But modified buffers are
                never abandoned, so changes cannot get lost.
                    :tabonly        " close all tab pages except the current
                                    " one

複数ファイルを開きます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt
:bufdo tabnew

カレントタブは、No Nameです。ここで:tabonlyを実行してみます。

:tabonly

タブが全て消えました。
buffer-listを確認。

:ls


カレントバッファは、No Nameのままですね。

Vim起動時にタブ

タブのことが少しずつ分かってきました。
Vim起動時からタブが表示されていると便利そうです。
起動オプションを確認してみます。

/tmp $ vim --help

〜省略〜

   -p[N]                Open N tab pages (default: one for each file)
   -o[N]                Open N windows (default: one for each file)
   -O[N]                Like -o but split vertically

〜省略〜

-pですね。
使ってみましょう。

/tmp $ vim -p test_*.txt

Vim内のヘルプで-pオプションの説明を見てみます。

:h -p
                                                        *-p*
-p[N]           Open N tab pages.  If [N] is not given, one tab page is opened
                for every file given as argument.  The maximum is set with
                'tabpagemax' pages (default 10).  If there are more tab pages
                than arguments, the last few tab pages will be editing an
                empty file.  Also see |tabpage|.

数字が指定できますね。3と指定してみます。

/tmp $ vim -p3 test_*.txt


タブが3つだけ表示されました。
buffer-listはどうなってるでしょう。

:ls


タブの数と一致するわけではないんですね。

VimScript :find

:find使います。
ヘルプの確認。

                                                        *:fin* *:find*
:fin[d][!] [++opt] [+cmd] {file}
                        Find {file} in 'path' and then |:edit| it.
                        {not available when the |+file_in_path| feature was
                        disabled at compile time}

ファイルを見つけてくれるコマンドですね。
複数ファイルを開いて:findを使ってみます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

test_3.txtを探して開いてみます。

:find test_3.txt

test_3.txtが開いたようです。
buffer-listはどうなってるでしょう。

:ls

カレントバッファが、test_3.txtになってますね。
次は、バッファに存在しないファイルを指定するとどうなるか確認してみます。

/tmp $ vim


test_3.txtを探してみます。

:find test_3.txt


ファイルを見つけて開くことができました。

VimScript :tabfind

:findで、ファイル名を指定して開くことができましたが、
今回は、:tabfindを使ってみます。

:[count]tabf[ind] [++opt] [+cmd] {file}                 *:tabf* *:tabfind*
                Open a new tab page and edit {file} in 'path', like with
                |:find|.  For [count] see |:tabnew| above.
                {not available when the |+file_in_path| feature was disabled
                at compile time}
/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim -p test_*.txt

test_3.txtを指定して:tabfindを実行します。

:tabfind test_3.txt

今表示されているタブから探すのではなくて、ファイルを探して見つけた後、新規タブを起動するようです。
ん〜。ちょっとイメージが違いました。
今表示されているタブから探すには、どうすれば良いでしょうか。

gettabinfoを使ってみます。

gettabinfo([{arg}])                                     *gettabinfo()*
                If {arg} is not specified, then information about all the tab
                pages is returned as a List. Each List item is a Dictionary.
                Otherwise, {arg} specifies the tab page number and information
                about that one is returned.  If the tab page does not exist an
                empty List is returned.

                Each List item is a Dictionary with the following entries:
                        tabnr           tab page number.
                        variables       a reference to the dictionary with
                                        tabpage-local variables
                        windows         List of |window-ID|s in the tab page.

この関数で、タブの一覧が取得できそうです。使ってみます。

:echo gettabinfo()

VimScript :vimgrep

:vimgrep使います。
ヘルプから。

                                        *:vim* *:vimgrep* *E682* *E683*
:vim[grep][!] /{pattern}/[g][j] {file} ...
                        Search for {pattern} in the files {file} ... and set
                        the error list to the matches.  Files matching
                        'wildignore' are ignored; files in 'suffixes' are
                        searched last.
                        Without the 'g' flag each line is added only once.
                        With 'g' every match is added.

                        {pattern} is a Vim search pattern.  Instead of
                        enclosing it in / any non-ID character (see
                        |'isident'|) can be used, so long as it does not
                        appear in {pattern}.
                        'ignorecase' applies.  To overrule it put |/\c| in the
                        pattern to ignore case or |/\C| to match case.
                        'smartcase' is not used.
                        If {pattern} is empty (e.g. // is specified), the last
                        used search pattern is used. |last-pattern|

grepと使いかたは同じですね。
ではファイルを作成。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim

ファイルは作成しますが、指定せずにVimを起動します。

:vimgrepを使って、文字列を検索してみましょう。

:vimgrep /HELLO/ *.txt

最初に見つかった文字列が存在するファイルが開いたようです。

検索結果一覧を表示します。

:copen

下の画面の検索結果からファイルを選択してEnterを押すと、上画面に選択したファイルが開きます。

次は、数字を指定してvimgrepを使ってみます。
サンプルは同じです。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim
:3vimgrep HELLO *.txt

3番目に見つかった場所を開くかと思いきや、そうではなく、最大3個見つけくれたようです。
ヘルプを確認してみます。

:{count}vim[grep] ...
                        When a number is put before the command this is used
                        as the maximum number of matches to find.  Use
                        ":1vimgrep pattern file" to find only the first.
                        Useful if you only want to check if there is a match
                        and quit quickly when it's found.

                        Without the 'j' flag Vim jumps to the first match.
                        With 'j' only the quickfix list is updated.
                        With the [!] any changes in the current buffer are
                        abandoned.

                        Every second or so the searched file name is displayed
                        to give you an idea of the progress made.
                        Examples:
                                :vimgrep /an error/ *.c
                                :vimgrep /\<FileName\>/ *.h include/*
                                :vimgrep /myfunc/ **/*.c
                        For the use of "**" see |starstar-wildcard|.

ファイル数を増やして確認してみます。

/tmp $ for i in {1..100};do echo HELLO$i>test_$i.txt;done
/tmp $ vim
:99vimgrep HELLO *.txt


(1 of 99) とあるので、最大99箇所ですね。
この状態で、copenすると、

Quickfix Listにたくさん表示されました。

:copenした時に表示されるQuickfix Listとは何でしょう。調べてみます。

どんな単語でヘルプを見れば良いか分からないので、とりあえずquickfixで。

:h quickfix
1. Using QuickFix commands                      *quickfix* *Quickfix* *E42*

Vim has a special mode to speedup the edit-compile-edit cycle.  This is
inspired by the quickfix option of the Manx's Aztec C compiler on the Amiga.
The idea is to save the error messages from the compiler in a file and use Vim
to jump to the errors one by one.  You can examine each problem and fix it,
without having to remember all the error messages.

In Vim the quickfix commands are used more generally to find a list of
positions in files.  For example, |:vimgrep| finds pattern matches.  You can
use the positions in a script with the |getqflist()| function.  Thus you can
do a lot more than the edit/compile/fix cycle!

QuickFixって言葉の意味は分かってきました。編集しながら、早くコンパイルする、つまり、早くプログラムを直す目的からきてるんですね。
:vimgrepの結果もQuickfix Listに格納されてて、getqflist()関数でリストが取得可能なようです。

使ってみます。

/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim
:vimgrep HELLO *.txt

:echo getqflist()


たくさん情報が載ってますね。


次は、変数に格納して各パラメータを見てみます。

let a=getqflist()
:echo a

1行目だけ確認。

:echo a[0]


各メンバを整理すると、

a[0].lnum: 1
a[0].bufnr: 2
a[0].col: 1
a[0].valid: 1
a[0].vcol: 0
a[0].nr: 0
a[0].type: x
a[0].pattern:
a[0].text: HELLO1

ん? quickfix listの中に、ファイル名は入ってないんですね。

getqflist()のヘルプを確認。

getqflist([{what}])                                     *getqflist()*
                Returns a list with all the current quickfix errors.  Each
                list item is a dictionary with these entries:
                        bufnr   number of buffer that has the file name, use
                                bufname() to get the name
                        module  module name
                        lnum    line number in the buffer (first line is 1)
                        col     column number (first column is 1)
                        vcol    |TRUE|: "col" is visual column
                                |FALSE|: "col" is byte index
                        nr      error number
                        pattern search pattern used to locate the error
                        text    description of the error
                        type    type of the error, 'E', '1', etc.
                        valid   |TRUE|: recognized error message

バッファ番号から、bufname()でファイル名を引けば良さそうです。

:echo bufname(2)

getqflistの結果を使ってみます。

まず、バッファ番号を全取得

:for a in getqflist()
:   echo a.bufnr
:endfor

先ほどの処理をベースにして、バッファ名を一覧。

:for a in getqflist()
:   echo bufname(a.bufnr)
:endfor

次は、各バッファ名をタブに割り当てる処理に変更し、これを関数化してみます。

:function! Quicklist2Tab()
:  for a in getqflist()
:    tabnew bufname(a.bufnr)
:  endfor
:endfunciton

実行。

:call Quicklist2Tab()

あ、失敗しました。executeを挟まないとダメでした。

:function! Quicklist2Tab()
:  for a in getqflist()
:    execute "tabnew" bufname(a.bufnr)
:  endfor
:endfunciton

実行。

:call Quicklist2Tab()

サンプルが単純なので検索結果もシンプルになってしまい、:vimgrepやgetqflistのありがたさがわかりません。

もう少し複雑なサンプルにしてみます。

/tmp $ rm test*.txt
/tmp $ for i in {1..100};do echo HELLO$i>>test.txt;done

100行のファイルをシャッフル。

/tmp $ sort -R test.txt > base.txt
/tmp $ rm test.txt
/tmp $ split -l20 base.txt test_
/tmp $ ls test*
test_aa test_ab test_ac test_ad test_ae
/tmp $

ファイル5個作成しました。内容はこれです。

Vimを起動後、:vimgrepで検索。キーワードは3にしました。

:vimgrep 3 test*

:copenでヒットした箇所一覧を確認。

:copen

getqflist()の結果をみてみます。

:echo getqflist()


19行あるので、そのままじゃ見渡せないですね。変数に格納します。

:let a=getqflist()
:echo map(a,'bufname(v:val.bufnr)')


ファイル名一覧ができたので、各ファイルにタブを割り当ててみます。

:for bname in a
:  execute "tabnew" bufname(bname.bufnr)
:  endfor

:hlsearchへ続きます。

VimScript :hlsearch

どのタブも色が黒に反転してないので、どこのタブにいるのか分かりません。
先頭タブへ切り替えてみます。

:tabfirst


一番左のタブが黒抜きになりました。これが現在のタブですね。
次のタブへ切り替えます。

:tabn


ん〜。vimgrepで見つかったファイルをタブにするだけでは、どの行を見つけたのか分からないので、
copenと比較するまでもなく、このままでは使えませんね。
検索文字列をハイライトしてみます。

:set hls


検索文字列が黄色に光って見やすくなりました。

uniq sortへ続きます。

VimScript uniq sort


検索結果に対して、タブが多すぎですので、重複しているタブを削除するため、一旦、現在のタブを全て削除。

:tabnew
:tabonly

検索結果を今一度確認。

:echo a

別の空リストを用意します。

:let b=[]

ファイル名のみをbリストに登録。

:for bname in a
:  call add(b,bufname(bname.bufnr))
:  endfor
:echo b

次に、重複しているファイルを削除するため、sortしてuniqします。

:let c=uniq(sort(b))
:echo c

ファイル名一覧ができたので、各ファイルにタブを割り当ててみます。

:for bname in c
:  execute "tabnew" bname
:  endfor


だいぶスッキリしました。

VimScript :lvimgrep

次はlvimgrepです。
ヘルプを見てみます。。

:h lvimgrep
                                                        *:lv* *:lvimgrep*
:lv[imgrep][!] /{pattern}/[g][j] {file} ...
:lv[imgrep][!] {pattern} {file} ...
                        Same as ":vimgrep", except the location list for the
                        current window is used instead of the quickfix list.

quickfix listの代わりに、カレントウィンドウのlocation listを使うことを除けば、:vimgrepと同じのようですね。

では実験用のファイルを作成。

/tmp $ rm test*
/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_1.txt test_2.txt

test_1.txtとtest_2.txtのみをVimで開きました。
test_1.txtにはHELLO1、test_2.txtにはHELLO2が格納されています。

lvimgrepでHELLOを検索してみます。

:lvimgrep HELLO test*

あれ、どうしたことでしょうか。1 of 5とあるので、5個見つかっています。
test_1.txtとtest_2.txtしか開いていないはずなのに。

location-listのヘルプを確認してみます。

:h location
                                                *location-list* *E776*
A location list is a window-local quickfix list. You get one after commands
like `:lvimgrep`, `:lgrep`, `:lhelpgrep`, `:lmake`, etc., which create a
location list instead of a quickfix list as the corresponding `:vimgrep`,
`:grep`, `:helpgrep`, `:make` do.
                                                *location-list-file-window*
A location list is associated with a window and each window can have a
separate location list.  A location list can be associated with only one
window.  The location list is independent of the quickfix list.

When a window with a location list is split, the new window gets a copy of the
location list.  When there are no longer any references to a location list,
the location list is destroyed.

location-listに関して、誤解してました。基本的に、quickfix listと同じで、ウィンドウローカルなだけなんですね。

:lvimgrepの使いかたがなんとなく分かってきました。
まず、無名バッファを開く。

/tmp $ vim

検索します。

:lvimgrep HELLO test*


:copenの代わりに:lopenを使います。

:lcopen

次に新規tabを開きます。無名バッファです。

:tabnew

このタブでも、lvimgrepを使ってみましょう。
HELLO[34]を検索します。 HELLO3とHELLO4がマッチします。

:lvimgrep HELLO[34]

:lopenします。

:lopen

さて、各タブの検索結果は、別々の一覧のままになっているでしょうか。
最初のタブに切り替えてみます。

:tabfirst


元のタブの:lopenの一覧は、そのままです。ウィンドウ毎にlocation-listが存在していることがわかりました。

VimScript :vimgrepadd

:vimgrepadd使います。
ヘルプ。

:h vimgrepadd
                                                *:vimgrepa* *:vimgrepadd*
:vimgrepa[dd][!] /{pattern}/[g][j] {file} ...
:vimgrepa[dd][!] {pattern} {file} ...
                        Just like ":vimgrep", but instead of making a new list
                        of errors the matches are appended to the current
                        list.

検索結果に追加するだけっぽいです。

使ってみます。

/tmp $ rm test*
/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim

まずは、ただの:vimgrepで、1と2を検索します。

:vimgrep '[12]' test*


結果一覧。

:copen

では:vimgrepadd使ってみます。

:vimgrepadd '[34]' test*


検索結果の一覧に追加されました。

:vimgrepaddは、単純にquickfix listに追加するだけの機能のように見えます。
同じ検索を2回行うと、どうなるでしょうか。

/tmp $ vim

Vimを起動したら、:vimgrep

:vimgrep HELLO test*
:copen

続いて:vimgrepaddで同じ検索を行います。

:vimgrepadd HELLO test*


やはり、ただ、検索結果を追加するだけですね。使う時は以前検索したキーワードと重複しないか注意しないといけないですね。

おそらく、検索キーワードは同じにして、検索したいファイルを追加するときに重宝しそうです。

VimScript win_getid

win_getidを使います。ヘルプから。

:h win_getid
win_getid([{win} [, {tab}]])                            *win_getid()*
                Get the |window-ID| for the specified window.
                When {win} is missing use the current window.
                With {win} this is the window number.  The top window has
                number 1.
                Without {tab} use the current tab, otherwise the tab with
                number {tab}.  The first tab has number one.
                Return zero if the window cannot be found.

引数は省略するとカレントウィンドウのIDを返してくるようです。

では使ってみます。何もファイルを指定せずVimを起動します。

/tmp $ vim

win_getidの戻り値をechoで表示。

:echo win_getid()

1000番が返ってきました。

スプリットした場合のウィンドウIDはどうなるでしょうか。

:vnew
:echo win_getid()

lで、スプリットの右側へカレントバッファを切り替えし、ウィンドウIDを確認。

:echo win_getid()


最初のバッファは1000番のままですね。

別バッファでも別ウィンドウIDが振られていることを理解しました。
バッファ別に振られるなら、別タブでも当然、別ウィンドウIDになるはずですが、一応確認してみます。

何もファイルを指定せずVimを起動します。

/tmp $ vim

ウィンドウIDを確認。

:echo win_getid()


1000番が割り振られています。

次に:tabnewして、win_getidします。

:tabnew
:echo win_getid()


1001番です。ウィンドウが増えるたびに増えていくのでしょう。
タブを削除(バッファを削除)した場合はどうなるでしょうか。
:bwipeoutしてから再度:tabnewしてみます。

:bwipeout
:tabnew
:echo win_getid()


1002番が割り振られました。
バッファ番号と同じように、同じ番号は割り振られないようですね。

複数バッファを開いたときに、それぞれのウィンドウIDがどのように割り振られるのか確認してみます。

/tmp $ rm test*
/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt

ウィンドウは一つだけですね。ってことは、1000番が割り振られているはずです。

:echo win_getid()

1000番です。

次は、画面スプリットして別バッファを開いてみます。
test_3.txtを開きます。

:vs test_3.txt
:echo win_getid()

スプリットして開いたバッファを閉じます。

:quit

再度、画面スプリットして別バッファを開きます。
test_2.txtにしましょう。

:vs test_2.txt
:echo win_getid()

1002です。

さらに、画面スプリットしてみます。
test_3.txt

:vs test_3.txt
:echo win_getid()


一度割り振られたウィンドウIDは使われないみたいですね。

VimScript win_gotoid

ウィンドウIDが理解できてきたので、次はwin_gotoid使います。
ヘルプです。

win_gotoid({expr})                                      *win_gotoid()*
                Go to window with ID {expr}.  This may also change the current
                tabpage.
                Return 1 if successful, 0 if the window cannot be found.

成功すると1、失敗すると0が返るんですね。
使ってみます。

/tmp $ rm test*
/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim test_*.txt
:echo win_getid()

win_gotoで今見えているウィンドウのID、1000を指定してみましょう。
戻り値を見るためにechoを使います。

:echo win_gotoid(1000)

1が表示されたので、成功ですね。

次は、存在しないウィンドウIDを指定してみます。1005を指定してみます。

:echo win_gotoid(1005)


0が表示されたので、失敗です。

win_gotoidを使ってタブを切り替えてみます。

/tmp $ rm test*
/tmp $ for i in {1..5};do echo HELLO$i>test_$i.txt;done
/tmp $ vim -p test_*.txt
:echo win_getid()

win_gotoidで他タブに切り替えることができるでしょうか。

:call win_gotoid(1001)

見事にタブが切り替わりました。
ウインドウIDを確認します。

:echo win_getid()

VimScript wincol

wincol使います。
現在のカーソルのカラム位置を返してくれます。
ヘルプです。

                                                        *wincol()*
wincol()        The result is a Number, which is the virtual column of the
                cursor in the window.  This is counting screen cells from the
                left side of the window.  The leftmost column is one.

引数を指定せずにVim起動。

/tmp $ vim

適当に文字列を作ります。

:0r!seq 10 | pr -t10

カーソルは、行頭にあります。wincolを実行すると何が返ってくるでしょうか。

:echo wincol()


1が返ってきました。

wを押して、カーソルを移動し、wincolを実行してみます。

:echo wincol()


8が返ってきました。

続きです。

上下画面スプリットして、wincol使ってみます。

:sp
:echo win_getid()

カーソルを6が書いてある位置まで移動して、wincol実行。

:echo wincol()


カレントウィンドウのカーソルのカラム番号が返るようです。

別タブも開いてみます。

:tabnew
:0r!seq 5 | pr -t5

3の位置にカーソル移動して、wincol実行。

:echo wincol()


別タブでも同じくカレントウィンドウのカーソル位置が返るようですね。

VimScript col

前回と前々回でwincolを使いましたが、colという関数もあります。
これを使ってもカーソルのカラム位置を取得することができます。
ヘルプです。

                                                        *col()*
col({expr})     The result is a Number, which is the byte index of the column
                position given with {expr}.  The accepted positions are:
                    .       the cursor position
                    $       the end of the cursor line (the result is the
                            number of bytes in the cursor line plus one)
                    'x      position of mark x (if the mark is not set, 0 is
                            returned)
                    v       In Visual mode: the start of the Visual area (the
                            cursor is the end).  When not in Visual mode
                            returns the cursor position.  Differs from |'<| in
                            that it's updated right away.

カーソル位置を取得するには、”.”を指定すれば良いですね。

使ってみましょう。 引数を指定せずにVim起動。

/tmp $ vim

適当に文字列を作ります。

:0r!seq 10 | pr -t10

そのままのカーソル位置で、col実行。

:echo col(".")


1ですね。

wを5回押して6の位置にカーソルを移動して、col実行。

:echo col(".")


カーソル位置が返ってきました。

colの”.”以外を使った動きも確認していきます。

引数を指定せずにVim起動。

/tmp $ vim

適当に文字列を作ります。

:0r!seq 100 | pr -t10

“$”を使ってみます。

:echo col("$")

57と表示されました。

これって、改行も含めた位置でしょうか。
確認するため、 $キーで、カーソルを行末へ移動。

:echo col(".")


56が返りました。
ヘルプにあるcursor line plus oneは、改行のことだったんですね。

    $       the end of the cursor line (the result is the
            number of bytes in the cursor line plus one)

続きです。

col(“‘x”)を使ってみます。
colのヘルプにはこうあります。

    'x      position of mark x (if the mark is not set, 0 is
            returned)

ではいつものように、Vimを起動して、適当なテキストを作成。

/tmp $ vim
:0r!seq 100 | pr -t10

マークがない状態で、col(“‘x”)を使ってみます。

:echo col("'x")

0が返ってきました。

マークを設定します。カーソルはそのままに、mx(カーソル位置をxレジスタに記憶)と入力。
そうしたら、カーソルを他へ移動してみましょう。

4行目の数字の塊の4列目と5列目の間にいますね。
ここで、col(“‘x”)

:echo col("'x")


1が返りました。先ほどxにマークしたカーソルのカラムは1列目なので、合ってそうですが、他の位置でも確認してみます。
加えて、colで指定するマークxは、xじゃないとダメってわけではなく、何かのレジスタって意味なので、他のレジスタでも確認してみます。
では現在のカラム位置を確認。

:echo col(".")

現在カーソル位置を、yにマークします。my入力。次にカーソルを他へ移動。先頭行の行頭にしました。
ではマークyのカラムを表示します。

:echo col("'y")


先ほどカーソルがあった位置のカラム、26が表示されました。

col続きです。

    v       In Visual mode: the start of the Visual area (the
            cursor is the end).  When not in Visual mode
            returns the cursor position.  Differs from |'<| in
            that it's updated right away.

Visual modeでの引数のようです。使ってみます。

/tmp $ vim
:0r!seq 100 | pr -t10

適当にVisual modeで選択してみます。

選択していて、ふと気付きました。
いつもの確認のように、この状態からechoを使ってしまうと、選択されている行数分echoが実行されてしまいます。

でも、まあいいか、ってことでecho col(“v”)を実行してみます。

:'<,'>echo col("v")

No range allowedと表示されました。そもそも複数行で実行できないコマンドなんですね。

変数に格納してみます。 まずは配列初期化。

:let a[]

次に、Visual modeで選択して、配列にcol(“v”)の結果を追加してみます。

:'<,'>call add(a, col("v"))
:echo a


全部1が返ってきました。

よくわからんです。
Visual modeのことを学習した後で、学習した方が良い気がするので、colの学習はこれにて終了。

VimScript winline

前回までにwincol、colの確認をしました。winlineを使ってみます。
ヘルプです。

                                                        *winline()*
winline()       The result is a Number, which is the screen line of the cursor
                in the window.  This is counting screen lines from the top of
                the window.  The first line is one.
                If the cursor was moved the view on the file will be updated
                first, this may cause a scroll.

まずは起動。

/tmp $ vim
:0r!seq 10

カーソルを3行めに移動します。

:echo winline()


3が返りました。
wincol()と同じく、引数がなくて数字が返るだけなので、わかりやすいですね。

画面スプリットして、

:vs

カーソルを移動して、winline実行。

:echo winline()


8が返りました。
今フォーカスしているウィンドウのカーソル位置が返ります。

VimScript winheight

winheight使います。
ヘルプ

winheight({nr})                                         *winheight()*
                The result is a Number, which is the height of window {nr}.
                {nr} can be the window number or the |window-ID|.
                When {nr} is zero, the height of the current window is
                returned.  When window {nr} doesn't exist, -1 is returned.
                An existing window always has a height of zero or more.
                This excludes any window toolbar line.

まずは引数を指定せずに実行してみます。

/tmp $ vim
:echo winheight()


エラーになりました。

{nr}は省略できないんですね。0を指定すればcurrent windowになるようです。

:echo winheight(0)


23と返ってきましたが、合ってるでしょうか。
番号を表示してみます。

:0r!seq 100
:1

23行ですね。
次は、上下で画面スプリットしてみます。

:sp

それぞれwinheightを実行してみましょう。
上の画面から

:echo winheight(0)


11が返りました。
Ctrl-wwで下の画面にフォーカス。

:echo winheight(0)


10が返りました。

VimScript winwidth

今回はwinwidthです。

winwidth({nr})                                          *winwidth()*
                The result is a Number, which is the width of window {nr}.
                {nr} can be the window number or the |window-ID|.
                When {nr} is zero, the width of the current window is
                returned.  When window {nr} doesn't exist, -1 is returned.
                An existing window always has a width of zero or more.
/tmp $ vim
:echo winwidth(0)


80が返ってきましたが、タイトルバーにあるように80×24なので、widthは80文字ですね。

左右で画面スプリットします。

:vs

それぞれのwinwidthを確認。

:windo winwidth(win_getid())


それぞれのウィンドウ幅が返ってきました。

VimScript wincount

wordcount使います。

wordcount()                                             *wordcount()*
                The result is a dictionary of byte/chars/word statistics for
                the current buffer.  This is the same info as provided by
                |g_CTRL-G|
                The return value includes:
                        bytes           Number of bytes in the buffer
                        chars           Number of chars in the buffer
                        words           Number of words in the buffer
                        cursor_bytes    Number of bytes before cursor position
                                        (not in Visual mode)
                        cursor_chars    Number of chars before cursor position
                                        (not in Visual mode)
                        cursor_words    Number of words before cursor position
                                        (not in Visual mode)
                        visual_bytes    Number of bytes visually selected
                                        (only in Visual mode)
                        visual_chars    Number of chars visually selected
                                        (only in Visual mode)
                        visual_words    Number of words visually selected
                                        (only in Visual mode)

とりあえず無名バッファを起動して確認。

/tmp $ vim
:echo wordcount()

各項目全部0です。

HELLOと書いてみます。

:echo wordcount()

それらしい数値になりました。
数字ではどうでしょう。HELLOの代わりに12345に書き換えます。

:echo wordcount()


数字に変えても同じでした。

引数なしで起動。

/tmp $ vim

実験用テキストを作ります。

:0r!seq 100 | pr -t10

word数をカウントしてみます。
1から100までの数を並べただけなので、100になるはずです。

:echo wordcount().words

100と表示されました。

1個だけ消してみます。

:%s/45//

45を消してみました。

再度word数をカウントしてみます。

:echo wordcount().words

cursor_wordsです。

引数なしで起動して、実験用テキストを作ります。

/tmp $ vim
:0r!seq 100 | pr -t10

「:5」と入力して、5行目の行頭にカーソル移動後、cursor_wordsを表示してみます。

:echo wordcount().cursor_words


41と表示されました。

次にwキーを押して、カーソルを単語ひとつ右へ移動してみます。

再度cursor_wordsを表示してみます。

:echo wordcount().cursor_words


42になりました。

カーソル位置のwordを含んで、カーソル以前にどれだけwordがあるかカウントしてくれてるようです。

ggで、カーソルを、1行目の行頭に移動後、cursor_wordsを確認してみます。

:echo wordcount().cursor_words

1ですね。

次は、「:10」、$キーで、10行目の行末。

:echo wordcount().cursor_words

想定どおり100が返りました。

実験用テキストを作ります。

/tmp $ vim
:0r!seq 9

word数は、

:echo wordcount().words


当然9ですね。

byte数は、各行改行を入れて+1するので、9 x 2 = 18が返るはず。

:echo wordcount().bytes

あれ19になってます。

もしや、10行あるでしょうか。行番号表示してみます。

:set nu


10行目があります。

xxdを使って、メモリダンプを確認してみます。

:%!xxd


最後の改行を含んで、19byteのようです。

実験用テキストを作ります。

/tmp $ vim
:0r!seq 11 19

さらに10行目を削除

:10delete

byte数をカウントします。1行、改行を含めて3byteなので、3 x 9 = 27byteですね。

:echo wordcount().bytes

想定どおり27です。

空白はbyte数にカウントされるでしょうか。 数字の間に空白を入れてみます。

:%s/\v(.)/\1 /

見づらいのでハイライトはオフします。

:noh

では、byte数カウントします。

:echo wordcount().bytes


36と表示されました。空白もbyte数に含まれるってことですね。
ってことは、単にファイルサイズですね。

cursor_bytesを使います。
引数なしで起動して、実験用テキストを作ります。

/tmp $ vim
:0r!seq 11 19

さらに10行目を削除して、各行に空白を追加。

:10delete
:%s/\v(.)/\1 /

この状態でbytesを確認すると、

:echo wordcount().bytes


36ですね。

「gg」で先頭行の行頭にカーソルを移動します。そしてcursor_bytesを確認。

:echo wordcount().cursor_bytes


1ですね。

次に「3j」で4行目の行頭にカーソルを移動します。そしてcursor_bytesを確認。

:echo wordcount().cursor_bytes


13と表示されました。

1行が4byteで、カーソルの前に3行あるので、4 x 3 = 12Byte。
カーソルの下の文字を含めて+1。13Byteです。
「l」キーでカーソルを1文字右に移動して、再度cursor_bytesを確認。

:echo wordcount().cursor_bytes


14になりました。

VimScript toupper

tolower、toupper使ってみます。

引数なしでVim起動。

/tmp $ vim
:echo tolower("HELLO")


全部小文字になりました。

:echo toupper("hello")

全部大文字になりました。

バッファ内のテキストにtolower/touppwerを使ってみます。

適当な文字列を/usr/share/dict/wordsから持ってきます。

:0r!sort -R /usr/share/dict/words | head

テキストができたので、これを変数に格納。

:let a=getline("w0","w$")
:echo a

テキストを取り込んだ変数aをコピーします。

let b = deepcopy(a)
echo b

変数bに対してmapでtoupperを実行します。

:call map(b, 'toupper(v:val)')
echo a + b

次は、tolower。同じ要領で、bをcにコピー。

let b = deepcopy(a)
echo b

そしてtolower

:call map(c, 'tolower(v:val)')
:echo c


全部小文字になりました。

VimScript localtime

localtime使います。

ヘルプ。

localtime()                                             *localtime()*
                Return the current time, measured as seconds since 1st Jan
                1970.  See also |strftime()| and |getftime()|.

よくある1970年からの秒カウントですね。

現在の時刻を確認。

/tmp $ date
2019年 10月19日 土曜日 16時27分52秒 JST
/tmp $
/tmp $ vim -c "echo localtime()"

Vimを起動してlocaltime()を実行してみます。

左下に表示されているのがそうですね。
でも、これでは合っているかどうかわかりません。
strftime()を使って確認してみます。
使い方がわからないので、ヘルプを見てみます。

strftime({format} [, {time}])                           *strftime()*
                The result is a String, which is a formatted date and time, as
                specified by the {format} string.  The given {time} is used,
                or the current time if no time is given.  The accepted
                {format} depends on your system, thus this is not portable!
                See the manual page of the C function strftime() for the
                format.  The maximum length of the result is 80 characters.
                See also |localtime()| and |getftime()|.
                The language can be changed with the |:language| command.
                Examples:
                  :echo strftime("%c")             Sun Apr 27 11:49:23 1997
                  :echo strftime("%Y %b %d %X")    1997 Apr 27 11:53:25
                  :echo strftime("%y%m%d %T")      970427 11:53:55
                  :echo strftime("%H:%M")          11:55
                  :echo strftime("%c", getftime("file.c"))
                                                   Show mod time of file.c.
                Not available on all systems.  To check use:
                        :if exists("*strftime")
:echo strftime("%c",1571470172)


先ほどdateで確認した時間からちょっと経ってるので、正確にはわかりませんが、合ってそうです。

VimScript getftime

getftime使います。
ヘルプ。

getftime({fname})                                       *getftime()*
                The result is a Number, which is the last modification time of
                the given file {fname}.  The value is measured as seconds
                since 1st Jan 1970, and may be passed to strftime().  See also
                |localtime()| and |strftime()|.
                If the file {fname} can't be found -1 is returned.

ファイルのタイムスタンプを1970年からの秒カウントで返してくれるんですね。

まずは適当にファイルを作成。

/tmp $ touch test1.txt
/tmp $

getftimeを使ってみます。

/tmp $ vim -c 'getftime("test1.txt")'


エラーになりました。
echoつけるの忘れてました。

/tmp $ vim -c 'echo getftime("test1.txt")'


ちゃんと表示されました。
時間は多分合ってます。

VimScript getftype

getftype使います。

ヘルプ。

getftype({fname})                                       *getftype()*
                The result is a String, which is a description of the kind of
                file of the given file {fname}.
                If {fname} does not exist an empty string is returned.
                Here is a table over different kinds of files and their
                results:
                        Normal file             "file"
                        Directory               "dir"
                        Symbolic link           "link"
                        Block device            "bdev"
                        Character device        "cdev"
                        Socket                  "socket"
                        FIFO                    "fifo"
                        All other               "other"
                Example:
                        getftype("/home")
                Note that a type such as "link" will only be returned on
                systems that support it.  On some systems only "dir" and
                "file" are returned.  On MS-Windows a symbolic link to a
                directory returns "dir" instead of "link".

ファイルの種類が返るだけですね。

使ってみます。

/tmp $ touch test1.txt
/tmp $ vim -c 'echo getftype("test1.txt")'


fileと表示されました。

ディレクトリも確認してみます。

:echo getftype("/tmp")

linkと表示されています。ディレクトリではないんでしょうか。

/tmp $ ls -l /tmp
lrwxr-xr-x@ 1 root  admin  11 10 13 12:48 /tmp -> private/tmp
/tmp $


リンクのようです。
では、新規にディレクトリを作成して確認してみます。

/tmp $ mkdir test1
/tmp $ vim -c 'echo getftype("test1")'


dirと返ってきました。

VimScript getfsize

getfsize使います。

いつものヘルプ。

getfsize({fname})                                       *getfsize()*
                The result is a Number, which is the size in bytes of the
                given file {fname}.
                If {fname} is a directory, 0 is returned.
                If the file {fname} can't be found, -1 is returned.
                If the size of {fname} is too big to fit in a Number then -2
                is returned.

ふむふむ。ファイルサイズですね。

使ってみます。

/tmp $ echo -ne "12345" > test1.txt
/tmp $ wc -c test1.txt
       5 test1.txt
/tmp $

5が表示されました。

以前使ったwordcountではどうでしょう。

/tmp $ vim -c 'echo wordcount().bytes' test1.txt

6が返りました。勝手に改行が入るようです。
xxdで確認してみます。

:%!xxd


改行0aが入ってますね。
vimを終了して、odコマンドで確認してみると、

/tmp $ od -tx1 test1.txt


5バイトです。

VimScript getfperm

getfperm使います。

getfperm({fname})                                       *getfperm()*
                The result is a String, which is the read, write, and execute
                permissions of the given file {fname}.
                If {fname} does not exist or its directory cannot be read, an
                empty string is returned.
                The result is of the form "rwxrwxrwx", where each group of
                "rwx" flags represent, in turn, the permissions of the owner
                of the file, the group the file belongs to, and other users.
                If a user does not have a given permission the flag for this
                is replaced with the string "-".  Examples:
                        :echo getfperm("/etc/passwd")
                        :echo getfperm(expand("~/.vimrc"))
                This will hopefully (from a security point of view) display
                the string "rw-r--r--" or even "rw-------".

                For setting permissions use |setfperm()|.

/tmp $ ls -l test1.txt
-rw-r--r--  1 takk            wheel  5 10 19 20:23 test1.txt
/tmp $


一致しますね。

/tmp $ chmod 444 test1.txt
/tmp $ ls -l test1.txt
-r--r--r--  1 takk            wheel  5 10 19 20:23 test1.txt
/tmp $

リードオンリーに変更しました。

一致しました。
戻しておきます。

/tmp $ chmod 644 test1.txt
/tmp $ ls -l test1.txt
-rw-r--r--  1 takk            wheel  5 10 19 20:23 test1.txt
/tmp $

っていうか戻せるんですね。

VimScript empty

empty使います。
ヘルプの説明を読みます。

empty({expr})                                           *empty()*
                Return the Number 1 if {expr} is empty, zero otherwise.
                - A |List| or |Dictionary| is empty when it does not have any
                  items.
                - A |String| is empty when its length is zero.
                - A |Number| and |Float| are empty when their value is zero.
                - |v:false|, |v:none| and |v:null| are empty, |v:true| is not.
                - A |Job| is empty when it failed to start.
                - A |Channel| is empty when it is closed.
                - A |Blob| is empty when its length is zero.

                For a long |List| this is much faster than comparing the
                length with zero.

String、則ち文字列から確認します。
vimを起動して、

/tmp $ vim
:echo empty("HELLO")

0が返りました。
1が真なので、HELLOは空ではないから合ってますね。
空を指定してみます。

:echo empty("")

1になりました。空ってことですね。

変数を使って確認します。

:let a="HELLO"
:echo empty(a)


0が返りました。

空。

:let a=""
:echo empty(a)


1が返りました。

数字をemptyの引数に指定した時の結果を確認します。

:let a=100
:echo empty(a)

0なので空ではない意味です。

数字を0にすると、

:let a=0
:echo empty(a)


1が返りました。空と判定されたってことですね。

次は小数点付き。

:let a=100.5
:echo empty(a)

0なので空ではないですね。

次は1より小さい数。

:let a=0.5
:echo empty(a)


0ですね。空と判定されてません。

数字の0のみがemptyってことですね。

listのempty判定です。
起動から。

/tmp $ vim -c 'let a=[1,2,3]'
:echo a

emptyの結果は、

:echo empty(a)

リストに要素が詰まっているので、emptyの結果は、False(0)ですね。
次は、リストを空にしてみます。

:let a=[]
:echo a

emptyの結果は、

:echo empty(a)


True(1)になりました。空ですね。

Dictionaryです。

Vimを起動します。

/tmp $ vim -c "let a={1:'one',2:'two'}"

起動後、Dictionaryを確認。

:echo a.1

emptyを使います。

:echo empty(a)


0なので、空ではないです。

次は、Dictionaryを空にしてみます。

:let a={}
:echo a.1


エラーも出ているし、空ですね。

emptyで確認します。

:echo empty(a)

VimScript or ビット演算

orビット演算使います。

Vim起動。

/tmp $ vim

1 + 1は、(移行+は、論理和の意味)

:echo or(1,1)


1ですね。

1+2は、

:echo or(1,2)


3になりました。

31+3は、

:echo or(31,3)


31になりました。
31は、2進数で、

1111 1111

3は、

0000 0011

31と3を上下に並べると、

1111 1111
0000 0011

上段が全部1なので、論理和しても、全部1ですね。合ってます。

VimScript and ビット演算

orを学習したので、andです。
まずは、Vim起動。

/tmp $ vim

論理積1・1の計算をします。

:echo and(1,1)


当然1です。

次は、10・5

echo and(10,5)


0になりました。
10と5は、それぞれ2進数で表すと、

1010
0101

上下が1の列がないので、オール0で正解です。

:echo and(127,100)


100です。
2進数で確認してみます。

0111 1111
0110 0100

合ってますね。

VimScript invert ビット演算

OR ANDと学習してきたので、次はNOTかなと思ってヘルプを探しましたがみつかりません。
論理演算は、bitwise operationというらしいので、bitsizeでヘルプを探しました。

Other computation:                                      *bitwise-function*
        and()                   bitwise AND
        invert()                bitwise invert
        or()                    bitwise OR
        xor()                   bitwise XOR
        sha256()                SHA-256 hash

invert()、これっぽいですね。
使ってみます。

Vim起動して、

/tmp $ vim
:echo invert(1)

-2?
なぜに?

ヘルプを確認します。

invert({expr})                                          *invert()*
                Bitwise invert.  The argument is converted to a number.  A
                List, Dict or Float argument causes an error.  Example:
                        :let bits = invert(bits)

普通に使えると思いますが。。。

他の数字でもう一回。

:echo invert(100)


-101?
0から引いた後、マイナス1しているように見えます。

では、50を指定したら、-51になるでしょうか。

:echo invert(50)

-51になりましたが、ん〜どうにも理解できません。

変数に格納してからinvert()を実行してみます。

:let a=50
:echo invert(a)

-51。結果は同じですね。

次は、echoで直接結果を表示するのではなく、変数に結果を格納した後に、echoしてみます。

:let a=50
:let b=invert(a)
:echo b


-51。同じです。当然ですが。

やはり直接echoするのときっとかわりません。
次は、0を指定してみます。

:echo invert(0)


-1になりました。0から引いて-1してれば当然です。

次は負の数を指定してみます。-10を指定すれば理屈では9になります。

:echo invert(-10)


9になりました。

何か大きく間違っている気がしてきました。
invert()で数を指定すると、0から引いて-1するってのは、10進数でみたら、全然ビット演算に見えないけど、2進数で
みたらどうだろう。printfで2進に変換して確認してみます。

50を2進数で表示すると、

:let a=50
:echo printf("%b",a)


110010ですね。

これをinvert()で実行すると、

:echo printf("%b",invert(a))


1がたくさん並んでいますが、右の方をみると、
001101になってます。

並べて確認してみます。

110010
001101

何と! ビット反転できてますね。

今頃気づきましたが、ずっと答えは合っていたようです。

VimScript printf

今回はprintf使います。

/tmp $ vim

echo してみます。

:echo printf("HELLO")

なんだかprintfしてるのにechoもしてるって不思議な気がします。
printfだけとどうなるでしょう。

:printf("HELLO")

エラーになりました。
Vimスクリプトでは、printfはフォーマットを作るだけなんでしょう、きっと。

では、%で書式を指定していきます。まずは文字列から。
おそらく%sで指定すれば良いですね。

:let a = "HELLO"
:echo printf("%s", a)

表示されました。

%sを2回使うと、

:let a = "HELLO"
:echo printf("%s %s", a, a)

%sに文字数を指定できるでしょうか。

:let a="HELLO"
:echo printf("%10s")


エラーになりました。なぜでしょう。
ヘルプを確認します。

:h printf-s
                                                        *printf-s*
                s       The text of the String argument is used.  If a
                        precision is specified, no more bytes than the number
                        specified are used.
                        If the argument is not a String type, it is
                        automatically converted to text with the same format
                        as ":echo".

sの書式のことは書いてありますが、文字数のことはかいてません。
もっと基本のヘルプにあるのでしょうか。

ヘルプを上に辿っていくと、このサンプルが見つかりました。

                Often used items are:
                  %s    string
                  %6S   string right-aligned in 6 display cells
                  %6s   string right-aligned in 6 bytes
                  %.9s  string truncated to 9 bytes

サンプルにあるってことは文字数指定できるハズです。サンプルと同じように%6sを指定してみます。

:echo printf("%6s", a)


先ほど実行したコマンド、よくみたら、

:echo printf("%10s")

引数指定してませんでした。エラーは当たり前ですね。

パディングがわかるように目印と一緒に文字列を表示してみます。

:let a = "HELLO"
:echo printf("[%10s]", a)

わかりやすいですね。右詰で全体で10文字になるように表示されました。
ではマイナスをつけると、どうなるか。

:let a = "HELLO"
:echo printf("[%-10s]", a)

マイナスをつけると左詰になりました。

次はトランケットです。指定した文字数で切り詰めます。

:let a = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
:echo printf("[%.5s]", a)


なるほど、これは使えそうです。

printf %sはよくわかりましたので、次へ進みます。今回は%dを使います。

まずはヘルプ。

:h printf-d
                                *printf-d* *printf-b* *printf-B* *printf-o*
                                *printf-x* *printf-X*
                dbBoxX  The Number argument is converted to signed decimal
                        (d), unsigned binary (b and B), unsigned octal (o), or
                        unsigned hexadecimal (x and X) notation.  The letters
                        "abcdef" are used for x conversions; the letters
                        "ABCDEF" are used for X conversions.
                        The precision, if any, gives the minimum number of
                        digits that must appear; if the converted value
                        requires fewer digits, it is padded on the left with
                        zeros.
                        In no case does a non-existent or small field width
                        cause truncation of a numeric field; if the result of
                        a conversion is wider than the field width, the field
                        is expanded to contain the conversion result.
                        The 'h' modifier indicates the argument is 16 bits.
                        The 'l' modifier indicates the argument is 32 bits.
                        The 'L' modifier indicates the argument is 64 bits.
                        Generally, these modifiers are not useful. They are
                        ignored when type is known from the argument.

他の指定子のことも書かれてるので、 たくさん説明があります。

使ってみます。

:let a=100
:echo printf("[%d]",a)

まあ普通に表示されますね。

計算した結果はどうでしょうか。

:let a=100
:echo printf("[%d]",a * a)

桁指定してみます。右詰。

:let a=100
:echo printf("[%10d]", a)

左詰。

:let a=100
:echo printf("[%-10d]", a)

16進数の書式やります。

ヘルプ。

                  %x    hex number
                  %04x  hex number padded with zeros to at least 4 characters
                  %X    hex number using upper case letters

16進数から使ってみます。

:let a=100
:echo printf("[%x]", a)


100は16進数で64なので合ってますね。

わかりやすく0xや、Hをつけてみます。

:let a=100
:echo printf("[0x%x %xH]", a, a)

8桁にしてみます。

:let a=100
:echo printf("[%8xH]", a)

アドレス表示風に0埋めの8桁にしてみます。 8の前に0をつけるだけです。

:let a=100
:echo printf("[%08xH]", a)

大文字で表示。

:let a=100
:echo printf("[%08XH]", a)

次は、8進、2進の書式です。
ヘルプを見ると、こんなサンプル。

                  %o    octal number
                  %08b  binary number padded with zeros to at least 8 chars

使ってみます。8進数から。

:echo printf("%o %o %o %o %o", 5, 6, 7, 8, 9)

桁数指定。

:echo printf("[%03o]", 0644)

次は2進数。

:let a=9
:echo printf("[%04b]", a)

:let a=0xff5a
:echo printf("[%016b]", a)

floatです。

ヘルプ。

                                                        *printf-f* *E807*
                f F     The Float argument is converted into a string of the
                        form 123.456.  The precision specifies the number of
                        digits after the decimal point.  When the precision is
                        zero the decimal point is omitted.  When the precision
                        is not specified 6 is used.  A really big number
                        (out of range or dividing by zero) results in "inf"
                        or "-inf" with %f (INF or -INF with %F).
                        "0.0 / 0.0" results in "nan" with %f (NAN with %F).
                        Example:
                                echo printf("%.2f", 12.115)
                                12.12
                        Note that roundoff depends on the system libraries.
                        Use |round()| when in doubt.

整数を何桁、 小数を何桁で指定すればいいだけですね。

:let a=23.45
:echo printf("%3.5f", a)

変数を整数にすると、

:let a=23
:echo printf("%3.5f", a)

特に問題なく小数点入りで表示されますね。

文字列だとどうでしょうか。

:let a="HELLO"
:echo printf("%3.5f", a)

やはりエラーになりますね。

整数部は省略できるか。

:let a=23.45
:echo printf("%.5f", a)

VimScript filereadable

filereadable関数使います。

ヘルプから。

filereadable({file})                                    *filereadable()*
                The result is a Number, which is |TRUE| when a file with the
                name {file} exists, and can be read.  If {file} doesn't exist,
                or is a directory, the result is |FALSE|.  {file} is any
                expression, which is used as a String.
                If you don't care about the file being readable you can use
                |glob()|.
                                                        *file_readable()*
                Obsolete name: file_readable().

ファイルが読めるかチェックですね。

実験してみます。
まずファイルを作ります。

/tmp $ echo HELLO > file1.txt
/tmp $ ls -l file1.txt
-rw-r--r--  1 takk            wheel  6 11 15 02:01 file1.txt
/tmp $
/tmp $ vim -c 'echo filereadable("file1.txt")'

左下に1が表示されました。1はTRUEなので、ファイルが読み込み可能ってことです。

存在しないファイルを指定してみます。

:echo filereadable("aaa.txt")

0が表示されました。
以前使ったgetfpermと似てますね。

VimScript filewritable

前回filereadableを使ったので、今回はfilewritableです。

ヘルプ。

filewritable({file})                                    *filewritable()*
                The result is a Number, which is 1 when a file with the
                name {file} exists, and can be written.  If {file} doesn't
                exist, or is not writable, the result is 0.  If {file} is a
                directory, and we can write to it, the result is 2.

まあこれも権限チェックして結果を表示するだけの関数ですかね。
使ってみます。
前回使ったファイルを使います。

/tmp $ ls -l file1.txt
-rw-r--r--  1 takk            wheel  6 11 15 02:01 file1.txt
/tmp $
/tmp $ vim -c 'echo filewritable("file1.txt")'

1が表示されたのでTRUEですね。書き込み可能です。

次はもちろん。存在しないファイル。新規のファイルを作成することができるのかチェックなら、1が返るハズ。

:echo filewritable("aaa.txt")

0ですね。ファイル作成が可能かではなく、あくまでも存在するファイルが書き込み可能かどうかのチェックのようで>す。

次は存在している書き込み禁止のファイルを指定してみます。

/tmp $ ls -l /bin/ls
-rwxr-xr-x  1 root  wheel  51888 10 24 10:34 /bin/ls
/tmp $

これにします。

:echo filewritable("/bin/ls")


0が表示されました。

VimScript abs

計算用の関数も使ってみます。
absから。

abs({expr})                                                     *abs()*
 Return the absolute value of {expr}.  When {expr} evaluates to
 a |Float| abs() returns a |Float|.  When {expr} can be
 converted to a |Number| abs() returns a |Number|.  Otherwise
 abs() gives an error message and returns -1.
 Examples:
         echo abs(1.456)
         1.456
         echo abs(-5.456)
         5.456
         echo abs(-4)
         4
 {only available when compiled with the |+float| feature}

絶対値ですね。

使ってみます。

:echo abs(1.234)


1.234と表示されました。
そのままですね。

マイナス値を与えてみます。

:echo abs(-1.234)


マイナスが取れました。

変数で指定してみます。

:let a = -1.23456789


桁が多いので桁落ちして格納されたかもしれません。確認してみます。
-1.234568になってますね。
この変数を、absにかけてみます。

:echo abs(a)


マイナスが外れて、1.234568になりました。

VimScript acos

ヘルプのaから順番にさらってるので、absの次はacosです。
ヘルプの説明はこんなのです。

acos({expr})                                                    *acos()*
                Return the arc cosine of {expr} measured in radians, as a
                |Float| in the range of [0, pi].
                {expr} must evaluate to a |Float| or a |Number| in the range
                [-1, 1].
                Examples:
                        :echo acos(0)
                        1.570796
                        :echo acos(-0.5)
                        2.094395
                {only available when compiled with the |+float| feature}
:echo acos(-1)


3.141593が表示されました。桁落ちしてるんですね。
-1.0を指定してみます。

:echo acos(-1.0)


同じですね。

次は、1を指定。

:echo acos(1)


0.0が返りました。

VimScript asin

次はasin。

asin({expr})                                            *asin()*
                Return the arc sine of {expr} measured in radians, as a |Float|
                in the range of [-pi/2, pi/2].
                {expr} must evaluate to a |Float| or a |Number| in the range
                [-1, 1].
                Examples:
                        :echo asin(0.8)
                        0.927295
                        :echo asin(-0.5)
                        -0.523599
                {only available when compiled with the |+float| feature}

使ってみます。

:echo asin(0.0)


結果は0.0です。

:echo asin(0.5)


0.523599。

:echo asin(1.0)


1.570796が返りました。PI/2ですね。

:echo asin(-1.0)


-1.570796が返りました。

VimScript atan

atan({expr})                                            *atan()*
                Return the principal value of the arc tangent of {expr}, in
                the range [-pi/2, +pi/2] radians, as a |Float|.
                {expr} must evaluate to a |Float| or a |Number|.
                Examples:
                        :echo atan(100)
                        1.560797
                        :echo atan(-4.01)
                        -1.326405
                {only available when compiled with the |+float| feature}

:echo atan(0)


0.0が返りました。

:echo atan(0.5)


結果は0.463648。

:echo atan(1.0)


0.785398。

:echo atan(3.141592/2.0)


1.003885。

あれ1ちょうどじゃないんですね。atanってなんだったかな。

VimScript atan2

次はatan2。
ヘルプから。

atan2({expr1}, {expr2})                                 *atan2()*
                Return the arc tangent of {expr1} / {expr2}, measured in
                radians, as a |Float| in the range [-pi, pi].
                {expr1} and {expr2} must evaluate to a |Float| or a |Number|.
                Examples:
                        :echo atan2(-1, 1)
                        -0.785398
                        :echo atan2(1, -1)
                        2.356194
                {only available when compiled with the |+float| feature}
:echo atan2(0)


エラーになりました。引数は一つじゃなくて、二つですね。

:echo atan2(0,0)


結果は0.0。

:echo atan2(-1,0)


結果は-1.570796。

:echo atan2(1,-1)

ヘルプの説明どおり、2.356194が返りました。

コメント

タイトルとURLをコピーしました