Quantcast
Channel: 永遠に未完成
Viewing all articles
Browse latest Browse all 102

Vim 8.0 Advent Calendar

$
0
0

この記事は 2016 年 12 月に Qiita 上で行われた Vim 8.0 Advent Calendarを 1 つにまとめたものです。

目次


前書き

2016年9月、Vimの新しいメジャーバージョンである Vim 8.0 がリリースされました。 このアドベントカレンダーでは Vim 8.0 に含まれる新しい機能や変更などを紹介していきます。

注意:

  • 正確には Vim 7.4.0 以降に追加された機能になるので、リリースから時間が経っている機能もあります。
    • この Advent Calendar では便宜上、Vim 7.4 から Vim 8.0 の間に入った機能を Vim 8.0 の新機能として紹介します。ご了承ください。
  • 全ての新機能を紹介するものではありません。

Vim 8.0 Advent Calendar 1 日目 関数機能の強化

Vim 8.0 では Vim script の関数機能が強化されました。この記事では Partials とラムダを紹介します。

Partials

これまでの Vim script では function()関数で関数参照(Funcref)を作成できました。これにより、関数を変数に入れ、直接呼び出すことができます。

letFoo=function('strftime')echo Foo('%Y-%m-%d')
" => 2016-12-01echo Foo('%Y-%m-%d', 1482634800)
" => 2016-12-25

これに加え、function()関数に事前に引数を渡すことで、引数部分をバインドした関数参照を作れるようになりました。これを Partial と呼びます。

letFoo=function('strftime', ['%Y-%m-%d'])echo Foo()
" => 2016-12-01echo Foo(1482634800)
" => 2016-12-25

function()の第2引数以降に辞書を渡すことで、self をバインドすることも可能です。

function! Value() dict
  return self.value
endfunctionletdict={'value': 10}letFoo=function('Value', dict)echo Foo()
" => 10

辞書から辞書関数の値を参照すると、自動的に辞書をバインドした関数参照が得られます。

letdict={'value': 20}function! dict.get_value() dict
  return self.value
endfunctionletFoo= dict.get_value

echo Foo()
" => 20

Partial から、バインドしている引数や辞書を得るには get()を使います。

letdict={'value': 20}function! AddN(n) dict
  returna:n+ self.value
endfunctionletAdd30=function('AddN', [30], dict)echo Add30()
" => 50echoget(Add30, 'name')
" => AddNechostring(get(Add30, 'func'))
" => function('AddN')echoget(Add30, 'dict')
" => {'value': 20}echoget(Add30, 'args')
" => [30]

ラムダ

これまでは関数を作るためには :function Ex コマンドを使って、複数行に渡ってコードを書く必要がありました。 そのため、sort()のような一部の関数を受け取る関数の実装が面倒でした。

function! MyCompare(i1, i2)returna:i1-a:i2endfunctionechosort([3, 5, 4, 1, 2], 'MyCompare')
" => [1, 2, 3, 4, 5]

そこで新しくラムダ構文が追加されました。{args -> expr}という形式で、本文には式のみが書けます。

echosort([3, 5, 4, 1, 2], { i1, i2 -> i1 - i2 })
" => [1, 2, 3, 4, 5]

また、map()filter()は今まで文字列で式を渡していましたが、関数参照を渡せるようになりました。 これらの関数は、配列の添字の {index}もしくは辞書のキー {key}と、各要素の値である {val}の 2 つを引数に取ります。間違いやすいので注意してください。

echomap(range(10), { index, val -> val * 2 })
" => [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]

substitute()関数も同様に {sub}に関数を渡せるようになりました。渡した関数は、引数としてマッチした対象を配列で受け取ります。配列の 0 番目はマッチした対象の全体、1 番目以降はパターン内のグループです。

echosubstitute('char count sample', '\w\+', { m -> m[0] . '(' . len(m[0]) . ')' }, 'g')
" => char(4) count(5) sample(6)

クロージャ

関数内で更に関数を定義した際に、クロージャを作れるようになりました。これはどういうことかと言うと、ネストした関数の内側から、外側の関数のローカルスコープを参照できるということです。 クロージャを使うには :function Ex コマンドに closureフラグを渡します。

function! Counter()letc=0function! s:count() closure
    letc+=1returncendfunctionreturn funcref('s:count')endfunctionletC1= Counter()letC2= Counter()echo C1()
" => 1echo C2()
" => 1echo C1()
" => 2echo C2()
" => 2

関数のスコープに注意してください。関数内であろうとも、関数自体のスコープは関数ローカルにはなりません。グローバル関数を定義すれば、それはグローバル関数になります。恐らくこれは歴史的な理由によるものだと思われます。

もしくはラムダを使うことでもクロージャを作成できます。ラムダを使う場合、ラムダ内の式が静的にパースされて、外側の変数を参照していた場合にクロージャと判定されます。よって、例えば :executeeval()などで動的に参照されるだけの場合、クロージャにはならず外側の変数は参照できません。

function! OuterFunc(arg)letvar=0" これはクロージャになります。letclosure={-> var }" これはクロージャにはなりません。letnot_closure={->eval('var')}" これは a:arg の参照によりクロージャになり、var も参照できます。letclosure={-> [a:arg, eval('var')][-1] }endfunction

2種類の関数参照

関数参照は function()関数で生成できますが、これによって生成される関数参照は名前参照になります。これはつまり、関数が同名で再定義された場合、参照先の関数も新しい関数になってしまうということです。 一方で先ほどのクロージャは何度も再定義されることが多く、問題になる場合があります。そこで、関数が上書きされても元の値を参照し続ける新しい参照を作るために、funcref()関数が追加されました。 funcref()関数の引数は function()関数と全く同じで、Partial も作成可能です。違いは、funcref()呼び出し時点での関数自体への参照が得られる点です。これにより参照先の関数が後で上書きされても、元の関数を参照し続けることができます。 注意点として、組み込み関数に対しては使えません。組み込み関数の関数参照を作る場合には function()を使う必要があります。

Vim 8.0 Advent Calendar 2 日目 チャンネル

Vim 8.0 では、外部リソースとのやりとりを行う機能としてチャンネルが追加されました。 本記事では、チャンネルの基本的な使い方として、ソケット通信を行う方法について簡単に説明します。 詳細については Vim付属の help を参照してください。

チャンネルを使う

この例では、ローカルホストの HTTP サーバに対して ch_sendraw()関数を使ってリクエストを送り、結果をハンドラで受け取って表示しています。

" リモートからのレスポンスがあった際に呼ばれるハンドラ関数を定義しますfunction! s:handle(ch, msg) abort
" レスポンスを表示します。実際にはタイミング次第ではレスポンスが分割される可能性もあり得ますechoa:msg" ch_close() 関数でチャンネルを閉じることができます" リモートから切断された場合は自動的に閉じられますif ch_status(a:ch)!=#'closed'call ch_close(a:ch)endifecho ch_status(s:ch)
" => closedendfunction" チャンネルを開く際に渡すオプションを用意します" ここではモードを "raw"、コールバックに先ほど定義したハンドラ関数の参照を指定していますlets:options={'mode': 'raw', 'callback': function('s:handle')}" ch_open() でチャンネルを開きますlets:ch= ch_open('localhost:80', s:options)" ここで s:ch 変数は channel 型の値になります。channel 型は、チャンネル機能のために新しく追加された型ですecho ch_status(s:ch)
" => open" 生の HTTP を叩きますcall ch_sendraw(s:ch, "GET / HTTP/1.0\r\n\r\n")

ここで、s:handle()関数は非同期に呼び出されます。つまり、サーバからのレスポンスがあるまでの間、ユーザーは編集を続けることができます。 ただし、マルチスレッドではない点に注意してください。s:handle()が呼び出され、実行されている間はユーザーは Vimの操作ができません。重い処理はしないように注意が必要です。

チャンネルのモード

先ほどの例では raw モードでチャンネルを使用しました。チャンネルには他にも多くのメッセージの送信/受信の方法があります。

チャンネルで行う通信には 4 つのモードがあります。モードによって Vimはメッセージを解釈し、コールバックをメッセージ単位で呼び出してくれたり、メッセージのエンコード/デコードを行ってくれます。

  • JSONJSON単位でメッセージをやりとりします。 メッセージには JSONVimのオブジェクトに変換したものが渡されます。

  • JS JSON に似ていますが、JavaScriptのオブジェクトを使ってメッセージをやりとりします。 オブジェクトのキーの ""が省略されたり、配列に空の要素が許可されたりなどの違いがあります。

  • NL 行単位でメッセージをやりとりします。 メッセージには末尾の改行を取り除いたものが渡されます。

  • RAW Vim はメッセージを解釈しません。そのままのデータが使われます。

その他のメッセージの読み書きの方法

例では ch_sendraw()と、ハンドラを使った読み書きを行いました。他にもいくつか紹介します。

ch_sendexpr()ch_sendraw()

チャンネルに対してメッセージを送ります。 JSONモードや JSモードの際は ch_sendexpr()を使い、Vimのオブジェクトを渡すことでエンコードされた値が送られます。 NLモードや RAWモードの際は ch_sendraw()を使い、文字列を渡すことで生のデータを送れます。

ch_read()ch_readraw()

チャンネルのバッファにあるメッセージを読みます。読みとったメッセージはバッファから取り除かれます。また、ハンドラに処理されたメッセージもバッファからすでにないため、読めません。 JSONモードや JSモードの際は ch_read()を使い、デコードされた値が得られます。 NLモードや RAWモードの際は ch_readraw()を使い、生のデータを得られます。NLモードの場合は、改行単位でメッセージを読み取ります。

ch_evalexpr()ch_evalraw()

チャンネルに対してメッセージを送り、そのレスポンスを待って、レスポンスを返します。send と read を一度にやるものです。使い分けについても前述のものと同じです。

JSON/JSNL/RAW
送信ch_sendexpr()ch_sendraw()
受信ch_read()ch_readraw()
送受信ch_evalexpr()ch_evalraw()

チャンネルには他にもまだまだ機能やオプションがあります。詳細は :help channelを参照してみてください。

Vim 8.0 Advent Calendar 3 日目 ジョブ

ジョブ機能を使うことで、外部プロセスを非同期で実行することができます。

ジョブを使う

この例では、ジョブを使って外部コマンド git grep -n wordを実行し、結果を 1 行ずつ非同期で処理し、quickfix に追加しています。

function! s:handler(ch, msg) abort
  caddexpra:msgcwindowendfunctioncallsetqflist([])lets:job= job_start(\   ['git', 'grep', '-n', 'word'],
\{'out_cb': function('s:handler')})

このように、ジョブを使うことで外部プロセスをバックグラウンドで実行し、チャンネルとコールバック関数を使うことで結果を非同期に処理できます。処理に時間のかかるソースコードのチェック処理などを裏で実行し、結果が出たら表示するといったことが可能になります。

ジョブのオプション

ジョブで指定できるオプションについて簡単に紹介します。ここで紹介しているものが全てではありません。詳細については help を参照してください。

letjob_options={}
モード

ジョブと接続される、標準入力、標準出力、標準エラー出力の各チャンネルのモードを指定します。デフォルトは nlで、改行を 1 つのメッセージとします。

" 標準入力のモードです。letjob_options.in_mode ='nl'" 標準出力のモードです。letjob_options.out_mode ='nl'" 標準エラー出力のモードです。letjob_options.err_mode ='nl'
標準入出力の接続先

標準入出力についての細かい指定が行えます。

" 標準入力を使いません。letjob_options.in_io ='null'" 標準入力をチャンネルに接続します。デフォルトです。letjob_options.in_io ='pipe'" 標準入力をファイルから読み込みます。letjob_options.in_io ='file'" 標準入力をファイルから読み込む際のファイルへのパスです。letjob_options.in_name ='/path/file'" 標準入力をバッファから読み込みます。letjob_options.in_io ='buffer'" 標準入力をバッファから読み取る際の読み取るバッファのバッファ番号です。letjob_options.in_buf =1" 標準入力をバッファから読み取る際のバッファの読み取り範囲の先頭行です。デフォルトは 1 です。letjob_options.in_top =1" 標準入力をバッファから読み取る際のバッファの読み取り範囲の最終行です。デフォルトはバッファの最終行です。letjob_options.in_bot =9999" 注意: 最終行にしたい場合はこの値は指定してはいけません。" 標準出力を使いません。letjob_options.out_io ='null'" 標準出力をチャンネルに接続します。デフォルトです。letjob_options.out_io ='pipe'" 標準出力をファイルに出力します。letjob_options.out_io ='file'" 標準出力をファイルに出力する際のファイルへのパスです。letjob_options.out_name ='/path/file'" 標準出力をバッファに出力します。letjob_options.out_io ='buffer'" 標準出力をバッファに出力する際のバッファ番号です。" 指定がない場合や、存在しないバッファだった場合は、新しくバッファが作成されます。letjob_options.out_buf =1" 出力先がバッファの場合に 0 を指定すると、出力先バッファの 'modifiable'オプションをオフにします。" 出力は行われますが、ユーザーはバッファを変更できなくなります。letjob_options.out_modifiable =0" 出力先がバッファの場合に 1 を指定すると、新しく作られたバッファの 1 行目にメッセージを出力します。" メッセージは "Reading from channel output..."のようなものです。letjob_options.out_msg =1

標準エラー出力は、標準出力のオプションのキーの outerrに変えたものが同じように用意されています。

コールバック

ジョブ側で何かが起きた際に呼び出される関数を指定します。指定しない場合は特に何も呼び出されません。

" 標準出力もしくは標準エラー出力から何か読み出せるようになった際に呼び出されます。" 下記 2 つとは併用できません。letjob_options.callback ={ ch, msg => [] }" 標準出力から何か読み出せるようになった際に呼び出されます。letjob_options.out_cb ={ ch, msg => [] }" 標準エラー出力から何か読み出せるようになった際に呼び出されます。letjob_options.err_cb ={ ch, msg => [] }" チャンネルが閉じられた際に呼び出されます。letjob_options.close_cb ={ ch => [] }" ジョブ(外部プロセス)が終了した際に呼び出されます。letjob_options.exit_cb ={ job, exit_status => [] }
その他
" ch_evalexpr() などでデータを読み取る際のタイムアウト時間(ミリ秒)です。letjob_options.timeout =2000" 標準出力を読み取る際のタイムアウト時間(ミリ秒)です。"timeout" を上書きします。letjob_options.out_timeout =2000" 標準エラー出力を読み取る際のタイムアウト時間(ミリ秒)です。"timeout" を上書きします。letjob_options.err_timeout =2000" Vim 終了時に、ジョブに対して job_stop() を呼び出します。" デフォルトは 'term'で、Vim 終了時にジョブを停止します。" 空文字列を指定すると、何もしません。letjob_options.stoponexit ='term'

ジョブを制御する

ジョブを停止する

job_stop()関数でジョブを終了させることができます。実際にはシグナルを送信したりします。 実際に何が起きるかは OS 依存です。

" ジョブを停止します。" 第2引数には他に 'hup''quit''int''kill'や、シグナルの数値などが指定できます。" 省略時は 'term'になります。call job_stop(job, 'term')
ジョブの状態や情報を得る

job_status()関数や job_info()関数で、ジョブの状態や情報を取得できます。

echo job_status(job)
" => 'run' (ジョブが実行中の場合)" => 'fail' (ジョブの開始に失敗した場合)" => 'dead' (ジョブの実行が終了している場合)" ジョブに紐付けられたチャンネルです。letch= job_getchannel(job)" 様々な情報を辞書で取得します。echo job_info(job)
" 'status'     job_status() の戻り値と同じです。" 'channel'    job_getchannel() の戻り値と同じです。" 'exitval'終了コードです。'status'が 'dead'の場合のみ参照できます。" 'exit_cb'    exit_cb オプションに指定された関数参照の値です。" 'stoponexit' stoponexit オプションの値です。

Vim 8.0 Advent Calendar 4 日目 JSONサポート

チャンネルやジョブが追加されたのに合わせて、外部と JSONでのやり取りを行うことを想定して、JSONサポートが追加されました。

エンコード/デコードする

json_encode()json_decode()を使うことで、Vimの内部データと JSON文字列を相互に変換できます。

letobj={'users': [{'name': 'thinca', 'lang': 'vim'}]}letjson= json_encode(obj)echojson" => {"users":[{"lang":"vim","name":"thinca"}]}echo json_decode(json)
" => {'users': [{'lang': 'vim', 'name': 'thinca'}]}

追加された値

Vim script には、true/falseなどの bool 値や、nullなどの値は存在しませんでした。 このままだと JSONと相互に変換するのに支障が出るため、新しくこれらを表す値が追加されました。

  • v:false
  • v:true
  • v:null
  • v:none

これらによって、bool 値や null を含む JSONも正しく相互変換されます。

letjson='{"is_vimmer":true,"has_free_time":false,"future":null}'letobj= json_decode(json)echoobj" => {'future': v:null, 'is_vimmer': v:true, 'has_free_time': v:false}echo json_encode(obj)
" => {"future":null,"is_vimmer":true,"has_free_time":false}

js_encode()js_decode()

チャンネルには JSONモードの他に JS モードがありました。これらに対応する js_encode()js_decode()があります。これらは JavaScriptのオブジェクトのような形式を扱います。

letjs='{vimmers:["thinca",,],}'letobj= js_decode(js)echoobj" => {'vimmers': ['thinca', v:none]}echo js_encode(obj)
" => {vimmers:["thinca",,]}
  • オブジェクトのキーは必要がなければダブルクォートで囲われません。
  • 末尾カンマを許容します。
  • 配列内の空の要素(連続するカンマ)を許容し、この場合は v:noneが使われます。

かなり特殊な値になるので、通常は JSONを使えばよいでしょう。

Vim 8.0 Advent Calendar 5 日目 タイマー

Vim 8.0 は新しくタイマー機能が追加されました。これにより、指定時間後に関数を呼び出すことができます。

タイマーを開始する

以下の例では 1 秒毎に関数を呼び出し、その度にカウントダウンを行い、最後に BOMB!と表示して終了します。

letdict={'count': 10}function! dict.countdown(timer) abort
  letself.count -=1if self.count
    echoself.countelseecho'BOMB!'call timer_stop(a:timer)endifendfunctionlettimer= timer_start(1000, dict.countdown, {'repeat': -1})

タイマーが起動した後、タイマーによって関数が実行されている間以外は、ユーザーは編集を続けることができます。 例によって Vimはシングルスレッドですので、タイマーによって Vim script が実行されている間はユーザーは操作ができません。

関数の解説

timer_start({time}, {callback}, [, {options}])

タイマーを開始します。{time}ミリ秒後に {callback}関数を呼び出します。 関数はタイマー ID を返します。この ID を使ってタイマーの操作ができます。また、{callback}関数も引数にこの ID を受け取ります。 {options}には辞書でオプションを渡せます。今のところ有効なオプションは以下のものです。

  • "repeat"{callback}を繰り返し呼び出す回数を指定します。 正数を指定すると、{time} ミリ秒毎に指定した回数だけ {callback}が呼び出されます。 -1 を指定すると、制限なく呼び出され続けます。 指定しなかった場合は 1 回だけ呼び出されます。
timer_stop({timer})

指定したタイマーを停止します。{callback}関数は呼び出されなくなります。

timer_pause({timer}, {paused})

タイマーを一時停止したり再開したりします。{paused}が TRUE の場合は一時停止、FALSE の場合は再開になります。

timer_info([{timer}])

タイマーの情報を返します。{timer}引数を渡すと指定したタイマーの情報を、引数を省略した場合は全てのタイマーの情報を配列で返します。 情報は辞書で、ID や残り時間、呼び出される関数など一通りの情報が得られます。

timer_stopall()

タイマーは一歩間違えると暴発し、一切の操作ができなくなるような事態も起き得ます。timer_stopall()を呼び出すことで、全てのタイマーを停止することができます。

Vim 8.0 Advent Calendar 6 日目 パッケージ

Vimのパッケージ機能を使うことで、簡単なプラグインの管理を行うことができます。

パッケージとは

まず、パッケージ機能におけるパッケージとはどんなものかについて説明します。 1 つのパッケージは、複数のプラグインを含んでいます。また、プラグインはそれぞれ、Vim起動時に読み込まれるか、あとから指定して読み込まれるかに分けられます。 パッケージは以下のようなディレクトリ構造になっています。

package/
|- start/
|  |- plugin1/
|  |- plugin2/
|  `- plugin3/
`- opt/
   |- plugin4/
   |- plugin5/
   `- plugin6/

start/ディレクトリ以下にあるものが Vim起動時に読み込まれるプラグインで、opt/ディレクトリ以下にあるものがあとから指定して読み込まれるプラグインです。 plugin1plugin2などがプラグイン名になります。この名前は後から読み込む際に指定する名前になります。

パッケージを配置する

パッケージは、'packpath'オプションで指定されたディレクトリの中の pack/ディレクトリ内から探されます。 'packpath'の初期値は 'runtimepath'の初期値と同じです。

具体例を挙げて見ていきます。 ~/.vimなどが 'packpath'の 1 つとして登録されています。 この中の pack/ディレクトリ内の、パッケージ名のディレクトリにパッケージを配置します。 つまり、パッケージ名を pack1pack2とすると、以下のようになります。

~/.vim/
|- pack/
|  |- pack1/
|  |  |- start/
|  |  `- opt/
|  |- pack2/
|  |  |- start/
|  |  `- opt/
:  :

このようにパッケージ、およびその中のプラグインを配置しておくことで、Vimは起動時、vimrc ロード後のプラグイン読み込み前に、各パッケージの start/ディレクトリ内のプラグイン'runtimepath'に自動的に追加し、その後プラグインを読み込みます。

オプショナルなプラグインのロード

opt/以下のプラグインは、:packadd Ex コマンドを使うことでロードできます。

:packadd plugin4

プラグイン名(=プラグインディレクトリ名)を指定することで、未ロードだった場合はロードされます。プラグイン'runtimepath'に追加され、プラグイン内の plugin/**/*.vimファイルがロードされます。

また、vimrc 内からロードする場合は :packadd!を使います。!を付けると、'runtimepath'への追加のみが行われ、ロードはスキップされます。vimrc 内の場合、その後で別途ロード処理があるため、その場ではロードしないようにこちらを使用します。

パッケージの用途について

パッケージは Vimに標準で入った簡易プラグイン管理システムです。標準であることが強みでしょう。あまり多くのプラグインを使っていない人などは、この機能で十分な人もいるでしょう。 また、パッケージは見た限りだと、パッケージ単位でのプラグインの配布なども想定されていそうです。ディストリビューションなどでの配布や、関連したプラグインをまとめたものをパッケージにする用途が考えられます。

一方で、パッケージ機能はロード周りの世話はしてくれますが、昨今のプラグインマネージャプラグインが行ってくれるような、プラグイン自体のインストールや更新は行ってくれません。このレベルでプラグインを管理したい人は、やはりプラグインマネージャプラグインを利用するのが良いと思います。

Vim 8.0 Advent Calendar 7 日目 ウィンドウ ID

Windows ID を使うことで、特定のウィンドウの追跡が容易になります。

ウィンドウ ID がなかった時代

ウィンドウの指定はウィンドウ番号で行っていました。これはウィンドウの位置に対応して左上から順に振られます。

+-------------------------------+
|               |               |
|               |       2       |
|               |               |
|       1       |---------------|
|               |               |
|               |       3       |
|               |               |
+-------------------------------+

ウィンドウへ移動したり、ウィンドウに紐付けられた変数にアクセスする際は、このウィンドウ番号を使っていました。 しかし、このウィンドウ番号はウィンドウを移動すると変わってしまいます。例えば上の例で、ウィンドウ番号 1 の場所で <C-w>Lを行うと、以下のようになります(括弧内は元のウィンドウ番号です)。

+-------------------------------+
|               |               |
|    (2->)1     |               |
|               |               |
|---------------|    (1->)3     |
|               |               |
|    (3->)2     |               |
|               |               |
+-------------------------------+

これだと困る、ということで埋まれたのがウィンドウ ID です。

ウィンドウ ID とは

ウィンドウ ID は全てのウィンドウに振られる ID です。 ウィンドウ番号と違い、ウィンドウを移動しても変わりません。

ウィンドウ ID の取得
" 現在アクティブなウィンドウのウィンドウ ID を取得しますletwin_id= win_getid()" 現在タブページからウィンドウ番号を指定してウィンドウ ID を取得しますletwin_id= win_getid(winnr)" タブページとウィンドウ番号を指定してウィンドウ ID を取得しますletwin_id= win_getid(winnr, tabnr)" バッファ名やバッファ番号から最初に見付かったバッファが" 表示されているウィンドウのウィンドウ ID を取得しますletwin_id= bufwinid(buf)" 指定したバッファ番号のバッファを表示している" ウィンドウのウィンドウ ID を配列で全て取得しますletwin_ids= win_findbuf(bufnr)
ウィンドウ ID の使用
" 現在のタブページからウィンドウ ID のウィンドウを探してウィンドウ番号を返します" 見付からなかった場合は 0 を返しますletwinnr= win_id2win(win_id)" ウィンドウ ID のウィンドウを探して、" そのタブページ番号とウィンドウ番号を要素 2 の配列で返します" 見付からなかった場合は [0, 0] を返しますlet [tabnr, winnr] = win_id2tabwin(win_id)" 指定のウィンドウ ID のウィンドウに移動します。成功したら TRUE を返しますletsucceed= win_gotoid(win_id)

ウィンドウ番号とウィンドウ ID の併用

ウィンドウ ID は 1000 から振られます。これにより、1000 以上の場合はウィンドウ ID、1000 未満の場合はウィンドウ番号と仮定することで、既存の関数でウィンドウ番号を渡していた箇所でウィンドウ ID を渡せるようになっています。以下の関数で利用できます。

  • arglistid()
  • getcwd()
  • getloclist()
  • gettabwinvar()
  • haslocaldir()
  • setloclist()
  • settabwinvar()
  • winheight()
  • winwidth()

Vim 8.0 Advent Calendar 8 日目 defaults.vim

今回は新しく Vimに追加された defaults.vimという機構について解説します。

背景

Vimには今でも多くの新機能が追加され便利になっていっていますが、一方で互換性も重視しています。 中には、シンタックスハイライトなど明らかに便利であるにも関わらず、デフォルトでは有効になっていない機能があります。 新しく Vimを使い始めるユーザーにとって、互換性が理由で便利な機能がすぐに使えないことはあまり嬉しくはないでしょう。 そこで追加されたのが defaults.vimです。あくまで Vim自体のデフォルト値は変えずに、ユーザーに便利な設定を提供します。

defaults.vimの読み込み

ユーザーの vimrcファイルが存在しない場合、$VIMRUNTIME/defaults.vimファイルは自動で読み込まれます。 読み込みたくない場合は、vimrc を読み込まない場合と同様に vim -u NONEvim -u NORCなどを指定して Vimを起動します。 また、システム管理者が defaults.vimを読み込ませたくない場合、システムワイドの vimrc にて let g:skip_defaults_vim = 1を行うことで defaults.vimの設定は適用されなくなります。

なお、この defaults.vimの追加によって、Vimは通常の起動でコンパチブルモードで起動することがなくなりました。このことは非互換の変更として help にも記載されています。

defaults.vimの内容

例えば以下のような設定があります。

  • Vi 互換モードをオフにする (set nocompatible)
  • filetype の検出やプラグインなどを有効にする
  • シンタックスハイライトを有効にする
  • 'nrformats'オプションの値から octalを取り除く
  • 間違えて <C-u>したときに undo できるようにする
  • :DiffOrig Ex コマンドの定義 (:help :DiffOrig)

詳細は実際にファイルの中身を見てみてください。:e $VIMRUNTIME/defaults.vimで開けます。

defaults.vimから始める vimrc

defaults.vimは最初に使い始める vimrc の叩き台としても機能します。 新しく自分用の vimrc を書き始めたいと思ったとき、defaults.vimの内容を継承したい場合は、defaults.vimファイルを vimrcファイルにコピーするか、もしくは vimrcの先頭に以下のように書きます。

source$VIMRUNTIME/defaults.vim

ここから、自分の気に入らない部分をちょっとずつ手を加えていくとよいでしょう。

逆に言うと、これらを行わずにユーザーの vimrcファイルを作成すると、defaults.vimが読み込まれなくなることから、挙動が変わってしまうことに注意してください。

Vim 8.0 Advent Calendar 9 日目 2 進数のサポート

Vim 8.0 では 2 進数のサポートが強化されています。

2 進数の数値リテラル

0bもしくは 0Bで始まる 2 進数リテラルが追加されました。

echo 0b1010 == 10
" => 1

<C-a><C-x>の 2 進数サポート

'nrformats'オプションに指定できる値に binが追加されました。 これはデフォルトで含まれているため、特に設定せずに利用可能です。 0b0Bで始まる 2 進数の数値の上で <C-a><C-x>を実行すると、数値を増減できます。

0b1000
↓<C-x>
0b0111

printf() の 2 進数サポート

printf()関数のフォーマットに %bが追加されました。 これを使うことで、数値を 2 進数の文字列に変換できます。

echoprintf('0b%b', 10)
" => 0b1010

もちろんフラグにも対応しています。8 桁の 0 埋めパディングをするには以下のようにします。

echoprintf('0b%08b', 10)
" => 0b00001010

Vim 8.0 Advent Calendar 10 日目 quickfix に追加された機能

Vim 8.0 では quickfix 周りに便利な機能が追加されました。

quickfix の各項目の場所で Ex コマンドを実行する

quickfix の各項目に対して Ex コマンドを実行する :cdo Ex コマンドが追加されました。この Ex コマンドを使うことで、quickfix に対する柔軟な操作が可能になります。

例えば、プロジェクトの中から単語 fooを探し、それらを全て barに書き換えるには以下のようにします。

" 単語 foo を探します。結果は quickfix に入ります。
:vimgrep/\<foo\>/ **/*
" 検索結果の各行にて、置換を行い、バッファを保存します。
:cdo s/\<foo\>/bar/g | update

quickfix 内の各ファイルで Ex コマンドを実行する

上記の例において、検索結果の各ファイルについて複数の結果のデータがある場合に、各ファイルについて何か処理がしたい場合があるかもしれません。この場合に :cdoを使ってしまうと、各ファイルにて結果のデータの数だけ処理が実行されてしまいます。 こういった場合のために、cfdo Ex コマンドも追加されています。こちらの Ex コマンドは、quickfix に存在している各ファイルについて繰り返しが行えます。 各ファイルが開かれた際のカーソル位置は、ファイル内の最初の quickfix の項目の位置になります。

quickfix の一番下にスクロールする

Vimの非同期機能が強化されたため、今後は quickfix の内容が非同期で更新されることもあるでしょう。そういった場合に、常に quickfix の一番下の内容が見たい場合もあります。 新しく追加された :cbottom Ex コマンドを実行すると、quickfix ウィンドウへ移動しなくても、quickfix ウィンドウの内容を一番下までスクロールさせることができます。

quickfix の履歴を参照する

quickfix の内容は、最新の 10 個まで履歴が保存されていて、以前の内容に戻すことができます。詳細は :help quickfix-error-listsに記載されています。 この機能は、以前までは実際に :colderなどを実行して前に戻ってみないと古いものが存在するかどうかがわかりませんでした。 そこで追加されたのが :chistory Ex コマンドです。この Ex コマンドを実行すると、quickfix にどのような履歴があり、現在どれが参照されているのかが以下のように表示されます(以下は help からの抜粋です)。

  error list 1 of 3; 43 errors
> error list 2 of 3; 0 errors
  error list 3 of 3; 15 errors

ロケーションリスト版

上記の Ex コマンド :cdo:cfdo:cbottom:chistoryはそれぞれ、ロケーションリスト版として :ldo:lfdo:lbottom:lhistoryが用意されています。

Vim 8.0 Advent Calendar 11 日目 タイムスタンプで管理されるようになった viminfo ファイル

今回は、ユーザーの作業が記録されている viminfo ファイルについてです。

viminfo ファイルの概要

viminfo ファイルは、ユーザーが行った様々な操作を記録しておくファイルです。例えば、レジスタの内容、コマンドラインの履歴、検索文字列の履歴や、ジャンプリストなど、様々な情報が記録されます。これらをファイルに記録することで、次回 Vimを使った際にも、前回の履歴を引き継いで使うことができます。 viminfo ファイルは、基本的には起動時に読み込まれ、終了時にファイルに書き出されます。

viminfo ファイルのマージ

1 つの viminfo ファイルを複数の Vimのセッションで使った場合、viminfo ファイルはマージされます。

どのようにマージが起きるか、例を挙げます。 同時に 2 つの Vimを立ち上げて、それぞれ A B とします。起動時には viminfo は空で、どちらも最初は履歴がありません。 この時、

  1. A で Ex コマンド :echo 1を実行
  2. B で Ex コマンド :echo 2を実行
  3. A で Ex コマンド :echo 3を実行
  4. B で Ex コマンド :echo 4を実行
  5. B を終了
  6. A を終了

とします。

5 の時点で、B の履歴が viminfo ファイルに書き込まれ、viminfo ファイルには新しいものが上に来るように以下のような順番で記録されます(実際のファイルの形式とは異なります)。

:echo 2
:echo 4

次に 6 で A が終了する際、viminfo ファイルに更新があることを Vimが検出すると、一旦新しくなった viminfo を読み込みます。続いて A の Vimのセッションで記録された履歴を追記し、viminfo ファイルに書き出します。以下のようになります。

:echo 2
:echo 4
:echo 1
:echo 3

このように、コマンドラインヒストリがマージされます。

発生する問題

上記の例で、1 つ問題が発生します。ユーザーは複数の Vimを行き来し、:echo 1から :echo 4まで順番に実行したのにも関わらず、履歴の順番はぐちゃぐちゃになってしまっています。 これは特に Vimを長時間起動していた場合、つい先ほど実行したコマンドが履歴の奥深くに潜ってしまうことを意味します。例だと、ユーザーが最後に実行したのは :echo 4ですが、これは履歴の一番下から 3 番目に来てしまっています。 最近実行したものは、履歴の中でも最近に出てきてくれた方が嬉しいでしょう。

タイムスタンプを使った新しい viminfo ファイル

そこで Vim 8.0 では、コマンドなどの履歴を保存する際に、それらが実行された時間のタイムスタンプも一緒に保存するようになりました。 これによって履歴は読み込まれる時にタイムスタンプ順でソートされ、ユーザーが実行した順番で履歴を参照することができます。 以下のものがタイムスタンプで管理されるようになりました。

Vim 8.0 Advent Calendar 12 日目 連番の生成

Vimで連番と言えば今まででも、マクロを使う方法や Vim script を活用する方法などがありましたが、より手軽な方法が追加されました。

g<C-a>g<C-x>コマンド

今まではノーマルモードにて <C-a><C-x>を実行することで、カーソル位置の数値を増減できましたが、ビジュアルモード中でも同様の操作が可能になりました。

加えて、連番を作り出すための g<C-a>g<C-x>がビジュアルモードのコマンドに追加されました。

使用例

例えば以下のようなバッファがあった場合:

1
1
1
1
1

2 行目以降をビジュアルモードで選択して、g<C-a>を実行すると、以下のようになります。

1
2
3
4
5

連番を作ることができました。2 行目以降を選択する点に注意してください。

これは見付かった数値に対して、見付かった順に [見付かった回数×count] 分、数値を足します。 例えば 2g<C-a>を実行すると、以下のようになります。

1
3
5
7
9

この操作は行指向で、選択範囲の各行で最初に見付かった数値のみを増減させます。また、数値が見付からなかった行はスキップされます。

1. これは 1 つ目の項目です。
1. これは 2 つ目の項目であり、
   折り返しが含まれています。
1. この項目にも折り返しが含まれて
   います。3 つ目の項目です。
1. 4 つ目の項目です。

このテキストに対して、<C-v>で 2 行目から最終行まで矩形選択を行い、g<C-a>を実行すると、以下のようになります。

1. これは 1 つ目の項目です。
2. これは 2 つ目の項目であり、
   折り返しが含まれています。
3. この項目にも折り返しが含まれて
   います。3 つ目の項目です。
4. 4 つ目の項目です。

数値が存在しない行は飛ばされます。 ここで Vで行単位で選択をしてしまうと、3 つ目の項目の中身が含まれてしまってうまくいかなくなります。

1. これは 1 つ目の項目です。
2. これは 2 つ目の項目であり、
   折り返しが含まれています。
3. この項目にも折り返しが含まれて
   います。6 つ目の項目です。
5. 4 つ目の項目です。

'nrformats'オプションに alphaが含まれていれば、アルファベットの増減も可能です。

a. ...
a. ...
a. ...
a. ...
a. ...

a. ...
b. ...
c. ...
d. ...
e. ...

g<C-x>は数値を減らす点以外は g<C-a>と同様に動作します。

Vim 8.0 Advent Calendar 13 日目 undo を分割せずにカーソルを移動

普段はあまり気にしないかもしれませんが、undo の単位は重要です。この undo について、新しい機能が追加されました。

挿入モードでの操作の分割

挿入モードでの操作中に、<Left>などでカーソル位置を動かすと、undo 情報や .でのリピートが壊れてしまいます。 例えば以下のキーマッピングがあった場合、

inoremap ( ()<Left>

挿入モードで foo(barと入力することで foo(bar)が入力できます。

入力: afoo(bar<Esc>
↓
foo(bar)

しかし、ここで uコマンドで undo を行うとfoo()となってしまいます。また、.コマンドでリピートを実行すると、barが挿入されます。

foo(bar)
↓
入力: u
↓
foo()
foo(bar)
↓
入力: j.
↓
foo(bar)
bar

これは <Left>により入力情報が途切れてしまうためです。つまり、<Left>を行った時点で、挿入モードを一旦抜けて入り直したのと同じことになります。

挿入モードでの <C-g>Uコマンド

この問題を避けるために、<C-g>Uコマンドが追加されました。これはカーソルを動かすコマンドの直前で使用します。 このコマンドを使うために、キーマッピングを以下のように書き換えます。

inoremap ( ()<C-g>U<Left>

最初の例と同様に foo(barを入力し、その後それぞれ uコマンドや .コマンドを実行すると、入力された foo(bar)全体が 1 つの入力として動作します。

foo(bar)
↓
入力: u
↓
foo(bar)
↓
入力: j.
↓
foo(bar)
foo(bar)

この <C-g>Uコマンドは、カーソルが行を跨がない移動をする場合のみ有効です。

<C-g>Uコマンドは便利ですが、手で入力するにはちょっと複雑です。今回の例のように、キーマッピングで使うとよいでしょう。

Vim 8.0 Advent Calendar 14 日目 新しいオプション その 1

新オプション紹介その 1 です。追加されたものの中でも便利なオプションについて解説します。

'breakindent' (真偽値) 'breakindentopt' (カンマ区切り文字列)

オンにすると、折り返して表示される行がインデントされて表示されます。 つまり以下のようになります。左が 'breakindent'がオフ、右がオンです。また、'showbreak'オプションの値に >が設定されています。4 行目の折り返しに注目してください。 f:id:thinca:20161230184910p:plain

また、'breakindentopt'オプションで細かい挙動を制御できます。このオプションはカンマ区切りの文字列で、以下の要素を指定できます。

  • min:{n}
    • 深いインデントが短すぎる幅で折り返されないように、1 行の最小の幅を指定します。未指定の場合は 20 になります。
  • shift:{n}
    • 折り返された位置をずらします。正数で右に、負数で左にずらします。未指定の場合は 0 で、ずらしません。
  • sbr
    • インデントの左側に 'showbreak'を表示します。

例えば、shift:4,sbrを指定すると、以下のようになります。

f:id:thinca:20161230184911p:plain

'showbreak'である >記号が行頭に移動し、折り返しのインデントが ifのインデントに比べて右に 4 つずれています。

'fixendofline' (真偽値)

通常、Vimは保存時にファイルの末尾に必ず改行を入れます。これは POSIXにて、テキストは行の集合であり、行は必ず改行で終わるとされているからだと思われます。 このファイル末尾の改行の付与は、ファイル末尾に改行のないファイルを開いて編集し、保存した場合にも行われます。 これは特にチームで作業している場合に困る場合があります。また、一部の CSVの処理系など、ファイル末尾の改行の有無で意味が合わるファイルを扱う場合も困ります。

そこで 'fixendofline'オプションが追加されました。このオプションはデフォルトではオンで、保存時にファイル末尾に改行を追加します。 オフの場合、'endofline'オプションに従って改行を付与します。オンなら改行が付与され、オフなら改行は付与されません。 この 'endofline'オプションは、既存のファイルを開いた際にはファイルの末尾の改行を見て自動でオンオフされます。

つまりまとめると以下のようになります。

  • ファイル末尾の改行をいじって欲しくない場合は、set nofixendoflineを vimrc ファイルに書きます。
  • ファイル末尾の改行の有無を操作したい場合は、上記に加えて、都度 'endofline'オプションの値を変更します。

'belloff' (カンマ区切り文字列)

Vimはエラーが発生した際にベルを鳴らします。これを無効化したい場合、以前は以下のような設定を書いていました。

setvisualbellt_vb=

新しく追加された 'belloff'オプションを使うと、代わりに以下のように書けます。

set belloff=all

'belloff'オプションは、どんな時にベルを鳴らさないようにしたいのかが細かく指定できます。特定の場合のベルのみ無効にしたい!といったことも可能です。 どのような値が指定可能かは help を参照してください。

Vim 8.0 Advent Calendar 15 日目 新しいオプション その 2

新オプション紹介その 2 です。その 1 に比べると地味なオプション達を簡単に紹介します。それぞれ詳細は help を参照してください。

'renderoptions' (特殊形式文字列)

テキストレンダラの設定です。このオプションを設定することによって、WindowsではレンダリングDirectXを使えます。 また、DirectXに対して様々なオプションを設定できます。

'emoji' (真偽値)

オンにするとユニコード絵文字を全角とみなします。デフォルトはオフです。

'langremap' (真偽値)

元々 'langnoremap'オプションがありました。このオプションは真偽値のオプションであったため、これをオフにしようとすると以下のようになります。

set nolangnoremap

これは二重否定でわかりづらい、ということで追加されたのが 'langremap'オプションです。'langnoremap'オプションは今も互換性のために残されていて、この 2 つのオプションは常に逆の値を指すようになっています。

'signcolumn' (特定文字列)

sign の桁を表示するかどうかを設定します。デフォルトは autoで、sign が存在する場合のみ表示されます。 その他、yesnoで常にオン/オフが可能です。

sign は本来の目的以外でも、行全体をハイライトするために hack 的にプラグインから使われる場合があり、このような時に noを設定することで余計な sign カラムを非表示にすることができます。

'tagcase' (特定文字列)

タグファイル内を検索する際の大文字小文字の区別する方法を指定します。 元々はこれは 'ignorecase'オプションの値に依存していましたが、'ignorecase'インタラクティブな検索などで使われることもあり、オンにしている人が多いかと思います。一方、タグファイル内の検索はプログラミング言語の識別子などが入っていることもあり、大文字小文字は区別して欲しい場合が多いです。そこで、それぞれ独立して設定できるようにするために 'tagcase'オプションが追加されました。 デフォルト値は followicで、互換性を保つために 'ignorecase'に追従します。常に大文字小文字を区別して欲しい場合は、matchを設定します。他にもいくつか設定できる値があります。

'termguicolors' (真偽値)

オンにすると、ターミナル内でも GUI用の 24 ビットカラーのカラースキームが使用できます。ただし、ISO-8613-3 互換のターミナルが必要です。 対応していないターミナルでオンにすると残念なことになるので注意してください。

'luadll''perldll''pythondll''pythonthreedll''rubydll''tcldll' (文字列)

Vimには様々な言語のインターフェースがあり、ビルド時にこれらを指定できます。 ダイナミックリンクも可能でしたが、これまでは、その dll のファイル名はビルド時に指定したものに固定でした。 しかし、タイナミックリンクである以上、ファイル名は環境によって変わる場合があります。そこで、オプションによって dll のファイル名を指定できるようになりました。 'pythonthreedll'Python 3 のためのものです。本来 'python3dll'としたかったようですが、オプション名に数値が使えないという制約が存在したため、このようになっています。

Vim 8.0 Advent Calendar 16 日目 新しい Ex コマンド

Vim 8.0 で利用できる新しい Ex コマンドのうち、まだ紹介していないものを紹介します。

:filter[!] {pat} {command}

{command}の出力のうち、{pat}で指定した正規表現にマッチする行だけを表示します。[!]を指定すると、逆にマッチしない行だけを表示します。 {pat}/foo/のように /などの記号で囲われた形式です。ただし、パターンが記号などを含まない場合は /は省略できます。

以下に使用例を挙げます。

" マークを記録してあるファイルのうち、.txt で終わるものを表示します。
filter /\.txt$/oldfiles" 読み込まれた Vim script のうち、パスに vimrc を含むものを表示します。
filter vimrc scriptnames" 開かれているバッファのうち、.vim を含むものを表示します。
filter /\.vim/buffers" キーマッピングのどこかに <C-r> を含むものを表示します。execute"filter /\<C-r>/ map"" 現在のバッファから、foo を含む行を表示します。
filter foo %print" ↑の例は :global でも実現できます。global/foo/print

最後に注意点として、この Ex コマンドは全ての出力をフィルタするわけではありません。例えば、:echoの結果はフィルタされません。

:keeppatterns {command}

検索履歴に手を加えずに {command}を実行します。 :substitute (:s///) や :globalなどの一部の Ex コマンドは、通常は Vim script 中で使った場合でもパターンが検索履歴に追加されてしまいます。これはプラグインの中などで使う場合に問題になります。 そこでこの :keeppatterns Ex コマンドを使って :keeppatterns global/.../などのようにすることで、検索履歴が変更されてしまうのを防ぐことができます。

これは {command}の実行中はずっと手を加えないということではなく、直接指定した Ex コマンドが検索を使うものだった場合だけ有効です。つまり以下の場合は、検索履歴は変更されてしまいます。

" :execute を挟むkeeppatternsexecute"s/\<CR>//ge"" 関数経由function! s:work() abort
  s/\e//geendfunctionkeeppatternscall s:work()

:noswapfile {command}

{command}を実行します。このとき、新しいバッファが開かれた場合はスワップファイルを作成しません。 これはプラグインが仮想バッファを作成する際に便利です。

この Ex コマンドも :keeppatterns Ex コマンドと同様、バッファを開くコマンドを {command}に直接指定した場合のみ有効です。 ただし、:vertical Ex コマンドや :leftabove Ex コマンドなどの、ウィンドウを開く先を指定する修飾子コマンドは含まれていても問題ありません。

:clearjumps

現在のウィンドウのジャンプリストを空にします。

カーソルを大きく移動させるコマンドを実行したり、バッファを移動したりした場合、そのカーソルの移動はジャンプリストに記録され、あとから辿って移動することができます。 これは多くの場合便利ですが、例えばプラグインが仮想バッファを開いた際などに、戻れてしまうと不便な場合もあります。そういった場合にこの Ex コマンドが使えます。

:helpclose

現在のタブページにヘルプウィンドウがあれば、1 つだけ閉じます。 これは現在のウィンドウがヘルプウィンドウではない場合にも動作するので、離れた場所にあるヘルプウィンドウを閉じるのに便利です。 1 つもヘルプウィンドウがない場合は特にエラーにもならず、何も起きません。

Vim 8.0 Advent Calendar 17 日目 新しい関数 ~文字列操作編~

Vim 8.0 では新しく便利な組み込み関数が多数追加されています。今回はその中から、文字列操作に関連するものを紹介します。

matchstrpos({expr}, {pat}[, {start}[, {count}]])

Vim script には元々、指定した文字列から、正規表現にマッチした位置を取り出す match()関数と、マッチした文字列を取り出す matchstr()という関数があります。 これら関数は便利ですが、マッチした位置とマッチした文字列両方が欲しい場合には少し問題があります。この場合、それぞれの関数を呼び出すことになるのですが、関数を 2 回呼び出すのは手間がかかる上に、同じ正規表現マッチを 2 回行うのはパフォーマンス的にも無駄です。

そこで、matchstrpos()関数が追加されました。引数は match()関数や matchstr()関数と同じで、戻り値が違います。"マッチした文字列"、"マッチした先頭の位置"、"マッチした末尾の位置"の 3 要素の配列を返します。

echo matchstrpos('fizz buzz fizzbuzz', 'b\w\+')
" => ['buzz', 5, 9]

strcharpart({src}, {start}[, {len}])

Vim script には元々、文字列の一部を切り出す strpart()関数がありますが、これはバイト単位で動作するため、マルチバイト文字に対して使うと文字列のバイトの途中で切り取られてしまうという問題がありました。 そこで strcharpart()が追加されました。これはバイト単位ではなく、文字単位で文字列の一部を切り取ります。

echo strcharpart('あいうえお', 1, 3)
" => いうえ

strgetchar({str}, {index})

文字列内の指定した index の文字の文字コードを取得します。これはバイトではなく文字単位で動作します。文字数はマルチバイトで数えられ、結果はマルチバイト文字の 1 文字の文字コードになります。

letcode= strgetchar('あいうえお', 4)echoprintf('0x%x', code)
" => 0x304aechonr2char(code)
" => お

byteidxcomp({expr}, {nr})

Vim script には byteidx()という関数があります。これは、マルチバイト文字を考慮して、{expr}に与えた文字列の 0 オリジンで {nr}番目の文字が、文字列内の何バイト目かを返します。{nr}が文字列の文字数と同じ場合は文字列全体のバイト数を返し、{nr}がそれより大きい場合は -1 を返します。

echobyteidx('あいうえお', 2)
" => 6echobyteidx('あいうえお', 5)
" => 15echobyteidx('あいうえお', 6)
" => -1

新しく追加された byteidxcomp()は、合成文字を個別にカウントします。

lets='e'.nr2char(0x301)echos" => éechobyteidx(s, 1)
" => 3   (合成文字を 1 文字とみなし、文字列が 1 文字であるため文字列全体のバイト数を返します)echobyteidx(s, 2)
" => -1  (合成文字を 1 文字とみなし、{nr} が文字数より大きいため -1 を返します)echobyteidxcomp(s, 1)
" => 1   (合成文字を別々の文字とみなし、"e" までをカウントして 1 を返します)echobyteidxcomp(s, 2)
" => 3   (合成文字を別々の文字とみなし、文字列が 2 文字であるため文字列全体のバイト数を返します)

glob2regpat()

glob()関数などで使われる、いわゆるワイルドカードなどが含まれるファイルパターンを正規表現に変換します。

echo glob2regpat('*.vim')

今のところ、ワイルドカード***も、正規表現.*に変換されるようです。実際のワイルドカード*ディレクトリを辿らず、/などのディレクトリ区切り文字にはマッチしないため、若干挙動が異なってしまう点に注意してください。

Vim 8.0 Advent Calendar 18 日目 新しい関数 ~情報取得編~

今回は新しく追加された関数の中から、情報を取得するものを中心に紹介します。

wordcount()

現在バッファの統計情報を辞書で取得します。 この情報は g<C-g>コマンドで表示できるものですが、表示だけだとスクリプトから扱うのが困難であるため、関数が追加されました。

辞書には以下の情報が含まれます。

キー 説明
bytesバッファ内のバイト数です。
charsバッファ内の文字数です。
wordsバッファ内の単語数です。
cursor_bytesカーソル位置より前のバイト数です。ビジュアルモードでない場合のみ存在します。
cursor_charsカーソル位置より前の文字数です。ビジュアルモードでない場合のみ存在します。
cursor_wordsカーソル位置より前の単語数です。ビジュアルモードでない場合のみ存在します。
visual_bytesビジュアル選択領域内のバイト数です。ビジュアルモードの場合のみ存在します。
visual_charsビジュアル選択領域内の文字数です。ビジュアルモードの場合のみ存在します。
visual_wordsビジュアル選択領域内の単語数です。ビジュアルモードの場合のみ存在します。

getbufinfo([{expr}])getbufinfo([{dict}])

バッファの情報を辞書の配列で取得します。引数を与えない場合、全てのバッファの情報を取得します。 配列の各要素の辞書は、以下のエントリーを持っています。

キー 説明
bufnrバッファ番号です。
changedバッファが変更されているなら('modified'がオンなら) TRUE になります。
changedtickバッファが変更された回数(b:changedtick の値)です。
hidden隠れバッファであるなら('hidden'がオンなら) TRUE になります。
listedバッファがバッファリストに表示されるなら('buflisted'がオンなら) TRUE になります。
loadedバッファがロード済みなら TRUE になります。
nameバッファ名(バッファのファイルのフルパス)です。
signsサインの情報のリストです。リストの各要素は辞書で、以下の要素を持ちます。
キー説明
idサインの ID
lnum行番号
nameサインの名前
variablesバッファローカル変数を参照する辞書です。
windowsバッファを表示しているウィンドウのウィンドウ ID のリストです。

引数を渡した場合は、取得したいバッファの条件を指定することで絞り込みができます。詳細は help を参照してみてください。

getwininfo([{winid}])

ウィンドウの情報を辞書の配列で取得します。引数を与えない場合、全てのタブページのウィンドウの情報を取得します。 配列の各要素の辞書は、以下のエントリーを持っています。

キー 説明
bufnrウィンドウが開いているバッファのバッファ番号です。
heightウィンドウの高さです。
loclistこのウィンドウがロケーションリストだった場合は 1 です。
quickfixこのウィンドウが quickfix ウィンドウだった場合は 1 です。
tabnrウィンドウがあるタブページのタブページ番号です。
variablesウィンドウローカル変数を参照する辞書です。
widthウィンドウの幅です。
winidウィンドウ ID です。
winnrウィンドウ番号です。

引数にウィンドウ ID を渡した場合は、指定したウィンドウ ID の情報のみを含む配列を取得できます。

gettabinfo([{arg}])

タブページの情報を辞書の配列で取得します。引数を与えない場合、全てのタブページのウィンドウの情報を取得します。 配列の各要素の辞書は、以下のエントリーを持っています。

キー 説明
tabnrタブページ番号です。
variablesタブページローカル変数を参照する辞書です。
windowsタブページで表示されているウィンドウのウィンドウ ID のリストです。

引数にタブページ番号を渡した場合は、指定したタブページ番号の情報のみを含む配列を取得できます。

getcharsearch()setcharsearch({dict})

文字検索の情報を取得、設定できます。文字検索とは、fFtTで行う、指定した文字やその手前に飛ぶ機能のことです。 文字検索の情報を持つ辞書を取得、および設定できます。この辞書は以下の要素を持ちます。

key 説明
char検索文字です。空文字列にすると、文字検索を解除します。
forward検索方向です。1 ならば前方、0 ならば後方です。
untill検索の種類です。1 の場合は、文字の手前(tT)、0 の場合は文字自体(fF) の検索です。

getcmdwintype()

getcmdtype()コマンドラインウィンドウ版です。 q:q/q?コマンドラインウィンドウを開いている時に、現在のコマンドラインウィンドウがどのタイプかを返します。戻り値は :/?のいずれかで、コマンドラインウィンドウが開かれていない場合は空文字列を返します。

getcompletion({pat}, {type} [, {filtered}])

コマンドラインの補完の結果を取得できます。{type}は以下のうちのどれかです。

{type}説明
augroup autocmd のグループ名です。
bufferバッファ名です。
behave:behave Ex コマンドの引数です。
colorカラースキームです。
command Ex コマンドです。
compiler:compiler Ex コマンドの引数です。
cscope:cscope Ex コマンドの引数です。
dirディレクトリ名です。
environment環境変数です。
event autocmd のイベント名です。
expressionVimの式です。
fileファイル名とディレクトリ名です。
file_in_path'path'にあるファイル名とディレクトリ名です。
filetypeファイルタイプの名前です。
function関数名です。
help help の項目です。
highlightハイライトグループです。
history:history Ex コマンドの引数です。
localeロケールの名前(locale -aの出力)です。
mappingキーマッピングの名前です。
menuメニューです。
optionオプションです。
shellcmdシェルコマンドです。
sign:sign Ex コマンドの引数です。
syntax syntax ファイルのファイル名です。
syntime:syntime Ex コマンドの引数です。
tag tags ファイルから読み取れるタグです。
tag_listfilestagと同じです。
userユーザー名です。
varVim script の変数です。

{pat}候補を絞り込めます。コマンドラインに入力されている文字列を渡します。 {filtered}に 1 を渡すと、'wildignore'オプションを結果に適用します。

arglistid([{winnr} [, {tabnr}]])

Vimには引数リストという機能があります。これはグローバルなものが 1 つあり、それとは別にウィンドウ毎にローカルなものが作成できます。 引数リストの使い方についてはここでは省略しますが、この関数はこの引数リストの ID を取得できます。 指定したウィンドウにローカルな引数リストがなく、グローバルなものが使用されている場合は 0 を返します。引数が無効だった場合は -1 を返します。

Vim 8.0 Advent Calendar 19 日目 新しい関数 ~特殊操作編~

関数紹介編の最後です。特殊な操作をするものや、その他雑多な関数を紹介します。

uniq({list} [, {func} [, {dict}]])

配列内の連続する同じ要素を削除します。 全体から重複を削除したい場合は事前に sort()関数を使う必要があります。

letnew_uniq_list= uniq(sort(copy(list)))

比較にはデフォルトで文字列表現を使います。{func}{dict}を与えることで、sort()関数と同様に比較方法を指定することが可能です。

execute({command} [, {silent}])

Ex コマンド {command}を実行し、コマンドラインへの出力を結果として返します。 {command}は文字列か、文字列の配列です。

{silent}"""silent""silent!"のいずれかで、コマンドのプレフィックスのように使われます。デフォルトは "silent"です。

echofilter(split(execute('scriptnames'), "\n"), { i, line -> line =~# '^\s*1:' })
" =>   1: ~/.vim/vimrc

Ex コマンドの実行中に :redir Ex コマンドを使うことはできません。

matchaddpos({group}, {pos}[, {priority}[, {id}[, {dict}]]])

matchadd()関数のようにウィンドウ内にハイライトを追加しますが、matchadd()関数はパターンを指定するのに対し、matchaddpos()関数は位置を指定します。 {pos}には位置のリストを指定します。位置は以下のうちのいずれかです。

説明
数値 行番号です。行全体を強調表示します。
数値を 1 つ持ったリスト 行番号です。行全体を強調表示します。
数値を 2 つ持ったリスト 行番号と、その行の桁です。単位はバイトで、指定した文字を強調表示します。
数値を 3 つ持ったリスト 行番号、桁、文字列長(バイト単位)です。

一度に指定できる位置は 8 個までです。

setfperm({fname}, {mode})

ファイルのパーミッションを設定します。{mode}getfperm()関数の戻り値と同様のフォーマットである、"rwxrwxrwx"形式で指定します。

systemlist({expr} [, {input}])

system()関数と同様ですが、戻り値は行単位のリストになります。戻り値内の NULL 文字(\0)は改行文字に変換されます。 これにより、結果に NULL 文字が含まれてるコマンドの結果も取得できます。

perleval({expr})

if_perlを使って Perlの式を評価し、結果を Vimデータ形式に変換して返します。 これは pyeval()関数や luaeval()関数の Perl版です。

echo perleval('{"foo" => "bar"}')
" => {'foo': 'bar'}

exepath({expr})

実行コマンドのフルパスを取得します。絶対パス相対パス$PATHの中に存在するファイルが実行ファイルだった場合、そのフルパスを返します。

echo exepath('git')
" => /usr/bin/git

isnan({expr})

{expr}NaN値であるかを判定します。

echo isnan(0.0 / 0.0)
" => 1

reltimefloat({time})

reltime()関数の経過時間の戻り値を秒数の Float 値に変換します。

letstart=reltime()sleep1echo reltimefloat(reltime(start))
" => 1.000287

以前から経過時間を文字列に変換する reltimestr()関数がありましたが、こちらは文字列であり、かつ先頭に空白が含まれているため、表示以外の目的で使うには少々扱いづらいという問題がありました。reltimefloat()関数を使うことで直接 Float 値が得られるため、平均の計算などがやりやすくなります。

Vim 8.0 Advent Calendar 20 日目 新しいイベント

Vim 8.0 では autocmd イベントも新しく追加されています。

TabNew

新しくタブページが開かれた際に発生します。例えば :tabnew Ex コマンドを使うと、以下の順番でイベントが発生します。

  1. WinLeave
  2. TabLeave
  3. WinNew
  4. WinEnter
  5. TabNew
  6. TabEnter

TabClosed

タブページが閉じられた際に発生します。例えば、:tabclose Ex コマンドでカレントタブページを閉じると、以下の順番でイベントが発生します。

  1. BufLeave
  2. WinLeave
  3. TabLeave
  4. TabClosed
  5. WinEnter
  6. TabEnter
  7. BufEnter

WinNew

新しいウィンドウが作成された際に発生します。Vim起動時に開かれるウィンドウに対しては発生しません。

CmdUndefined

定義されていないユーザー定義コマンドを実行しようとした際に発生します。 パターンはコマンド名に対してマッチングが行われ、<amatch><afile>は実行しようとしたユーザー定義コマンド名に設定されます。しかし、<amatch>は正しく展開されないようなので、<afile>を使うのがよいでしょう。 イベントの実行中に存在しなかったコマンドを定義すれば、イベント終了後にコマンドが実行されます。

以下の例は、Fooで始まる未定義の Ex コマンドを実行すると、その場で自分自身のコマンド名を出力する Ex コマンドを定義します。

augroup example
  autocmd!
  autocmd CmdUndefined Foo* execute'command! -nargs=*'expand('<afile>')'echo'string(expand('<afile>'))augroup END
FooBar
" => FooBar

OptionSet

オプションが設定された際に発生します。 パターンは、常に短縮していないオプション名に対してマッチングが行われ、<amatch>にはオプション名が設定されます。 また、以下の組み込み変数に設定されたオプションの値の情報が格納されます。

組み込み変数名 説明
v:option_old変更前のオプションの値です。
v:option_new変更後のオプションの値です。
v:option_type設定された変数のスコープです。globallocalが入ります。

'key'オプションの場合は、セキュリティのためイベントは発生しません。

TextChanged

ノーマルモードでカレントバッファのテキストが変更された際に発生します。このとき、b:changedtickが更新されます。 このイベントはそれなりの頻度で発生します。重い処理を行う場合は十分注意すべきです。

TextChangedI

挿入モードでカレントバッファのテキストが変更された際に発生します。ただし、補完のポップアップメニューが表示されているときは発生しません。 このイベントは非常に頻繁に発生することに気を付けてください。重い処理を行うと、ユーザー体験が著しく損なわれます。

Vim 8.0 Advent Calendar 21 日目 新しい組み込み変数

今回は新しく追加された組み込み変数を紹介します。

タイプを表す定数

type()関数を使うと、変数のタイプを得ることができます。ここで得られる値は数値で、各タイプに数値が割り当てられています。 ある変数が特定のタイプであるかどうかを判定したい場合、今までは以下のようにしていました。

" 以下の例では変数 var が文字列かどうかを判定しています。" 文字列の type の値は 1 なので、これと比較して判定します。iftype(var)==1endif" マジックナンバーを避けるため、以下のようにすることが多いです。iftype(var)==type('')endif

この方法には、以下のような問題がありました。

  • 若干トリッキーで、慣れないと理解しづらいコードになります。
  • type()関数を余計に呼ぶため、オーバーヘッドがあります。
  • 関数参照の場合は type(function('type'))のようになり、長い上に function()関数に渡す関数名が人によってバラバラで統一感がありません。
  • 新しく追加された job型などの値は気軽に生成できません。

そこで、type()関数の戻り値を表す定数が新たに追加されました。以下の表が定数の一覧です。

定数 定数の値 型の値の例
数値 v:t_number 0 10
文字列 v:t_string 1 'foo'
関数参照 v:t_func 2 function('type')
リスト v:t_list 3 [0, 1, 2]
辞書 v:t_dict 4 {'one': 1}
浮動小数点数v:t_float 5 1.23
真偽値 v:t_bool 6 v:truev:false
特殊値 v:t_none 7 v:nullv:none
ジョブ v:t_job 8 job_start(cmd)
チャンネル v:t_channel 9 ch_open(host)

v:completed_item

補完された対象を表す変数です。 以前までは、CompleteDoneイベントにより補完の完了を知ることはできましたが、どの候補が選択されたかを知ることができませんでした。新しく追加されたこの変数を参照することで、どの候補が選択されたのかわかります。 選択された候補は辞書です。詳しい構造については :help complete-itemsで説明されています。補完に失敗した場合は空の辞書になります。

v:hlsearch

検索による強調表示が行われているかを表す変数です。 検索のハイライトは 'hlsearch'オプションをオンにして検索を行うことで行われますが、:nohlsearch Ex コマンドを使うことで、一時的にハイライトを無効にできます。このとき 'hlsearch'オプションの値はそのままなので、ハイライトが行われているのか、:nohlsearch Ex コマンドで消されているのかが今まではわかりませんでした。 v:hlsearch変数は、ハイライトが行われている時は 1、行われていない時は 0 になります。 また、値を書き換えることでハイライトの状態を変更できます。ただし、'hlsearch'オプションがオフの場合はハイライトを有効にできないため、1 を入れても値は 0 のままです。エラーも発生しません。 また、関数の呼び出しは、最後に使用された検索パターンを保存して呼び出し終了後にリストアします。つまり関数内でこの変数を書き換えても、関数の終了時に復元されてしまうので注意してください。

v:progpath

Vimを起動した際のコマンドを示す文字列です。つまり、Vimコマンド自身のコマンドライン引数の 0 番目です。 システムに複数の Vimがある場合に、どの Vimで起動されたかのヒントになります。Vim内から別の Vimを起動して処理を行いたい場合などに便利です。

echo exepath(v:progpath)

コマンドが相対パスでカレントディレクトリが移動した場合や、$PATHが変更された場合など、確実に起動した Vimが得られるわけではない点に注意してください。

v:vim_did_enter

Vimが起動して、VimEnterイベントが発生する直前までは 0 です。VimEnterイベントが発生する直前に 1 になります。 似たような値に has('vim_starting')があります。こちらの値は逆で、起動中は 1、起動後は 0 になります。値が変わるタイミングは v:vim_did_enterと同じです。両者は完全に代替可能です。 ではなぜこの変数が追加されたのかと言うと、実はこれを追加した際、Bram さんは has('vim_starting')の存在を完全に忘れていました。あとで指摘された際、この変数の追加をリバートすることも検討したようです。 しかし、has()関数は基本的に Vimコンパイル時に組み込まれている機能を調べるためのもので、一部の例外を除き Vim実行中に値が変化しないこと、そのような慣習のため、Vimが起動中かどうかを調べる方法が has()関数にあることはユーザーにとってわかりづらいことなどを Gary さんに指摘され、残すことになったようです。

Vim 8.0 Advent Calendar 22 日目 新しいスタイルのテスト

Vim 8.0 では、Vim本体のテストのスタイルが新しくなりました。

新しいテストのサンプル

新しいスタイルのテストは Vim本体のテストのために追加されたものですが、基本的に Vim script の機能であるため、プラグインのテストにも利用できます。 以下に、新しいスタイルで書かれた簡単なテストコードを示します。

" テスト対象の関数function! Add(a, b) abort
  returna:a+a:bendfunction" --------------------------function! Test_Add() abort
  call assert_equal(5, Add(2, 3))endfunctionfunction! s:run_test() abort
  letv:errors= []

  call Test_Add()ifempty(v:errors)echo'Test Passed!'elseecho'Test Failed!'for error inv:errorsechoerrorendforendifendfunctioncall s:run_test()

実行すると、以下のようにテストをパスします。

Test Passed!

テストに失敗する例も示します。Test_Add()関数を以下のように書き換えます。

function! Test_Add() abort
  call assert_equal(10, Add(2, 3))endfunction

実行すると、以下のようにテストに失敗します。

Test Failed!
function <SNR>1_run_test[3]..Test_Add line 1: Expected 10 but got 5

テストの仕組み

以上の例から見て取れるのは 2 点です。

  • 値のチェックに使っている assert_equal()関数
  • テストの結果のチェックに使っている v:errors組み込み変数

新しいテストでは、これらを使ってテストを書きます。

仕組みは単純です。v:errors組み込み変数は配列です。assert_で始まるアサート系の関数を呼び出し、アサートに失敗すると、この v:errorsに失敗のメッセージが追加されます。

例で行っているように、テスト開始前に v:errorsを空にし、いくつかのアサート系の呼び出したあと、最後に v:errorsの中身を確認することでテストを行います。

アサート系関数

追加されたアサート系の関数を紹介します。 ほとんどの関数は {msg}引数を持っており、これを渡すことで v:errorsに入るメッセージを指定できます。省略した場合は関数毎に用意されたメッセージが使用されます。

assert_equal({expected}, {actual} [, {msg}])

{actual}{expected}と等しい事をテストします。型の自動変換は行われません。

assert_notequal({expected}, {actual} [, {msg}])

{actual}{expected}と等しくない事をテストします。

assert_inrange({lower}, {upper}, {actual} [, {msg}])

{actual}{lower}以上 {upper}以下の数値である事をテストします。

assert_match({pattern}, {actual} [, {msg}])

{actual}正規表現{pattern}にマッチする事をテストします。

assert_notmatch({pattern}, {actual} [, {msg}])

{actual}正規表現{pattern}にマッチしない事をテストします。

assert_true({actual} [, {msg}])

{actual}が TRUE である事をテストします。ここでの TRUE は、非ゼロの数値か、v:trueです。それ以外の型や値の場合は失敗します。

assert_false({actual} [, {msg}])

{actual}が FALSE である事をテストします。ここでの FALSE は、ゼロの数値か、v:falseです。それ以外の型や値の場合は失敗します。

assert_exception({error} [, {msg}])

v:exceptionに文字列 {error}が含まれている事をテストします。

assert_fails({cmd} [, {error}])

{cmd}を実行した結果、エラーが発生する事をテストします。{error}が渡された場合、v:errmsgに格納されている発生したエラーメッセージに文字列 {error}が含まれている事をテストします。

その他のテスト用関数

assert 系以外で追加されたテストを補助する関数です。ただし、ほとんどの関数は Vim本体のテストのためのものです。簡単に紹介します。

テスト用関数 説明
test_alloc_fail({id}, {countdown}, {repeat})メモリの確保を強制的に失敗させます。
test_autochdir({expr})起動中に 'autochdir'を有効にします。
test_disable_char_avail() typeahead なしの状態でテストします。
test_garbagecollect_now()直ちにメモリを解放します。
test_null_channel() null のチャンネルを返します。
test_null_dict() null の辞書を返します。
test_null_job() null の Job を返します。
test_null_list() null のリストを返します。
test_null_partial() null の部分適用関数を返します。
test_null_string() null の文字列を返します。
test_settime({expr})Vimが使う内部時間を変更します。

Vim 8.0 Advent Calendar 23 日目 雑多な変更

今回は、今までの変更に収まらなかった細かい変更点について見ていきます。

GTK+ 3

GUIとして、GTK+ 3 に対応しました。GTK+ 2 と同じように使えます。

+num64

Vim script の整数の型が 64 bit になりました。 利用可能な環境であれば 64 bit になることになっていますが、基本的には一般的なほぼ全ての環境では利用可能かと思います。 has('num64')を使うことで、64 bit が有効かどうかを判定できます。

ifhas('num64')echo 1000000000000000000
" => 1000000000000000000endif

これにより、32 bit による桁溢れなどを利用している一部のプラグインがうまく動作しなくなる可能性があります。

ユーザー定義コマンドでの新しい置き換えテキスト <mods>

Vimの Ex コマンドの中には、他の Ex コマンドに前置することで他のコマンドを修飾するものがあります。:aboveleft:hideなどがそうで、これらはコマンド修飾子と呼ばれます。 これまでは、ユーザー定義コマンドに対してコマンド修飾子が指定されても、その存在を知ることはできませんでした。そこで追加されたのが <mods>です。

以下のように <mods>を使うことで、指定された修飾コマンドの存在を知ることができます。

command! ShowMods echo split(<q-mods>)

ShowMods
" => []aboveleft ShowMods
" => ['aboveleft']hideleftabove ShowMods
" => ['aboveleft', 'hide']

指定された順序に関係なく、決められた順序で <mods>に入ります。対応しているコマンド修飾子は以下です。

  • :aboveleft:leftabove
    • どちらも同じ意味のコマンドで、どちらが指定されても aboveleftが得られます。
  • :belowright:rightbelow
    • どちらも同じ意味のコマンドで、どちらが指定されても belowrightが得られます。
  • :botright
  • :browse
  • :confirm
  • :hide
  • :keepalt
  • :keepjumps
  • :keepmarks
  • :keeppatterns
  • :lockmarks
  • :noswapfile
  • :silent
  • :tab
  • :topleft
  • :verbose
  • :vertical

また、以下のコマンド修飾子には対応していません。

型チェックの廃止

以前のバージョンの Vimでは、変数に対して、元から入っている値の型に暗黙に変換不可能な型の値を代入しようとすると、エラーになっていました。

" Vim 7.4.1546 より前letvar=10letvar= []
" => E706: 変数の型が一致しません: var

これは元々意図的にチェックが行われエラーになっていたのですが、静的型付き言語ならともかく、動的型付き言語である Vim script でこれを行ってもわずらわしいだけであったため、このチェックはなくなりました。

" Vim 7.4.1546 以降letvar=10letvar= []
" => エラーなし

printf('%s')が全ての型を受け入れる

printf()関数のフォーマット文字列である %sは、文字列を表示します。以前のバージョンの Vimでは、文字列に暗黙に変換できない値を渡すとエラーになっていました。

" Vim 7.4.2220 より前echoprintf('%s', [1, 2, 3])
" => E730: リスト型を文字列として扱っています

しかしこれはあまり便利ではないため、文字列型でない場合は string()関数を適用した結果が使用されるように変更されました。

" Vim 7.4.2220 以降echoprintf('%s', [1, 2, 3])
" => [1, 2, 3]

辞書のキーに空文字列を使える

以前のバージョンの Vimでは、辞書のキーに空文字列が使えませんでした。

" Vim 7.4.1707 より前echo {'': 10}
" => E713: 辞書型に空のキーを使うことはできません

しかし、空文字列を許可しない積極的な理由はなく、空文字列が使えた方が便利であるため、キーに空文字列が使えるようになりました。

" Vim 7.4.1707 より前echo {'': 10}
" => {'': 10}

正規表現\%C

新しく追加された正規表現のアトム \%Cを使うと、合成文字をスキップすることができます。

lets='e'.nr2char(0x301)echos" => éechos =~# 'e'" => 0echos =~# 'e\%C'" => 1

ハイライトグループ EndOfBuffer

新しく追加されたハイライトグループ EndOfBufferは、ファイルの末尾以降に表示される ~の文字がある行の部分のハイライトになります。標準では NonTextと同じようにハイライトされます。 例えば以下のように背景色と前景色を同じにすることで、ファイル末尾以降を塗り潰す、といったことができます。

highlightEndOfBufferctermfg=DarkGray ctermbg=DarkGray guifg=DarkGrayguibg=DarkGray

サポートが終了した環境

以下の環境や機能は、使っている人がほぼいない、大きくなった Vimが動作するのにそぐわないなどの理由により、Vimのコードを綺麗に保つためにサポートが終了しました。

これらの環境や機能で Vimが使いたい場合は、古い Vimを使う必要があります。

Vim 8.0 Advent Calendar 24 日目 内部的な変更

今回は、利用者にはあまり影響がない Vimの開発側の変更についてです。

GitHubへ移行

移行当時、割と大きく取り上げられていたので、ご存知の方も多いでしょう。 それまで Vimは、Google Code で Mercurialを使って開発されていました。しかし Google Code のサービス終了に伴い、2015 年 8 月に VimリポジトリGitHubへ移行、リポジトリも Git に変更されました。

https://github.com/vim/vim

また、以前は Vimへのパッチの投稿は vim_dev のメーリングリスト上でのみ行われていましたが、現在では GitHub上の Pull Request でも受け付けています。貢献への敷居がかなり下がったと言えます。

大規模なソースコードの分割

Vimは長い歴史もあり、かなり巨大なプログラムです。ソースコードもかなりの量があり、eval.cなどは 2 万行を超えていました。 Vimの開発が GitHubに移ったりなどさまざまな変化がある中で、コードのテストカバレッジCoveralls で計測するようになりました。 しかし、この時にこの巨大なソースコードが問題になったようで、これを回避するためにソースコードの分割が行われました。 概ね、以下のような分割が行われました。

  • eval.c
    • eval.c
    • list.c
    • dict.c
    • evalfunc.c
    • userfunc.c
  • spell.c
    • spell.c
    • spellfile.c

ANSI-C スタイルの関数定義

Vim本体のソースコードは、以前までは K&R と呼ばれるスタイルで関数定義を行っていました。以下のような形式です。

/* * Add a watcher to a list. */void
list_add_watch(l, lw)
    list_T  *l;
    listwatch_T *lw;
{
    lw->lw_next = l->lv_watch;
    l->lv_watch = lw;
}

関数の引数部分の、型の宣言が括弧の外に書かれています。C 言語を知っている人の中でも、そもそもこのような書き方ができること自体知らない人もいるのではないでしょうか。 Vimの歴史は古く、そのためこの古い書き方がずっと使われてきました。 しかしこの度、現代で広く使われている ANSI-C スタイルの関数定義に書き換えられました。以下のような形式です。

/* * Add a watcher to a list. */void
list_add_watch(list_T *l, listwatch_T *lw)
{
    lw->lw_next = l->lv_watch;
    l->lv_watch = lw;
}

ビジュアルモードが常に有効

Vimには大きく分けて、Tiny、Small、Normal、Big、Huge の 5 つのビルドがあり、後になるほど多くの機能が含まれるようになっています。 ビジュアルモード(+visual機能)は、以前までは Small 以上の Vimに含まれる機能でしたが、7.4.200 からは Tiny も含む全ての Vimで有効になるようになりました。

Vim 8.0 Advent Calendar 25 日目 ユーザーをハッピーにする

長かったこの連載もついに最終回です。

Vimユーザーをハッピーにする」

2015 年の年末、Vimに以下のようなコミットが行われました。

Author: Bram Moolenaar <Bram@vim.org>
Date:   Thu Dec 31 16:10:23 2015 +0100

    patch 7.4.1005
    Problem:    Vim users are not always happy.
    Solution:   Make them happy.

Vimユーザーをハッピーにする、と書かれたこのコミットは、1 つの Ex コマンドを追加するものでした。

:smile

:help version8のページ内の :smile Ex コマンドの説明には、コミットメッセージと同様、以下のようにだけ書かれています。

|:smile|                make the user happy

この Ex コマンドを実行しても、何かとても便利なことが起きるわけではありません。しかし、これこそが Vimの願いであると感じます。

一部の例外はあるかもしれませんが、ソフトウェアはユーザーをハッピーにするために存在するものだと思います。少なくとも私はそうであると信じていますし、Vimはそのためにあると思っています。

今後も、Vimはユーザーをハッピーにするために進化を続けます。そして Vimを使った多くのユーザーがハッピーになることを願っています。

f:id:thinca:20161230184917p:plain

Happy Vimming!


Viewing all articles
Browse latest Browse all 102

Trending Articles