目次
- 旧来のVim scriptとVim9 script
- Vim9 scriptの主な変更点のまとめ
- コンパイルを通すまでに必要な手順
- コンパイルエラーになった箇所
- Vim9 scriptで書き換えてみて
旧来のVim scriptとVim9 script
旧来のVim scriptはViと互換性を維持するため、古い仕様を変更できず、実行するたびに各行のパースが行われており、パフォーマンスが良いとは言えませんでした。
Vim9 scriptは下位互換性を一部捨てることで10倍から100倍実行速度を向上させました。
また旧来のVim script特有の文法ではなく、より一般的に使われる文法が使われるようになりました。
旧来のVim scriptとVim9 scriptは同時に利用することができるため、高速化したい関数から取り組むと良いと思います。(全て置き換えなくても問題なく動作します)
*私の場合はvimrcをまるっと全て書き換えたため、これから紹介する手順は全て置換する前提です。
Vim9 scriptの主な変更点のまとめ
- コメントの開始が
#
に変更 - 行継続文字(
\
)がほとんどの場合、不要 - 値の代入に
let
を使用できない - 変数の宣言には
var
を使用 final
,const
の使用- 関数内でネストして関数を定義できるように
- 関数の宣言に
def
を使用 def
で定義される関数はエラーが発生次第、実行を中断するように- 変数と関数のスコープが明示しない限りスクリプトローカルに
- 関数の引数と戻り値に型が必要に
- 関数の呼び出し時の
:call
, メソッド呼び出しの:eval
が不要に - 範囲指定付きのコマンドはコロンの前置きが必須
- 再読み込みで関数と変数がクリアされるように
- ラムダ式では
=>
を使用
などなど詳細はヘルプドキュメントを参照ください
コンパイルを通すまでに必要な手順
ここでは最低限コンパイルを通すまでに必要な手順を紹介します
そのため、 :call
や :eval
の削除等は含んでいません。
vim9script
をvimrcの先頭行に記述する- コメントを
"
から#
に置換する func[tion]
をdef
に置換しabort
を削除する
(endfunc[tion]
をenddef
にする)- 引数の型と戻り値の型を書く(一旦
any
にする) let
をvar
に置換する(代入のlet
は削除する)- プレフィックス
s:
と引数辞書a:
を削除する - 引数リスト
a:000
をlist
型に置換する - 文字列結合の
.
を..
に置換する - スペースが必要な箇所にスペースを入れる
defcompile
を最終行に記述して:source %
しコンパイルチェックを行う
1.
vim9script
をvimrcの先頭行に記述する
:0put ="vim9script"
行頭に必ず必要です
2. コメントを
"
から#
に置換する
:%s/"/#/gce
コメント以外のダブルクォーテーションに注意しながら置換していきます
3.
func[tion]
をdef
に置換しabort
を削除する
(endfunc[tion]
をenddef
にする)
:%s/\v\_^(function|func)(!)?\s*%(s:)?(g:)?(\w+)%(\s*)(\(.*\))/def\2 \3\u\4\5: any/ge |
%s/\v%(\s*)abort//ge |
%s/\v\_^end(function|func)/enddef/ge
スクリプトローカルの関数が大文字始まりになることも確認します
Vim9 scriptではエラーが発生次第中断するようになったため abort
は禁止です
4. 引数の型と戻り値の型を書く(一旦
any
にする)
:%s/\v(def!?\s)%(g:)?(\w+)\(%(a:)?(\w+)\)/\1\2(\3: any)/ge |
%s/\v(def!?\s)%(g:)?(\w+)\(%(a:)?(\w+)%(,\s*)%(a:)?(\w+)\)/\1\2(\3: any, \4: any)/ge |
%s/\v(def!?\s)%(g:)?(\w+)\(%(a:)?(\w+)%(,\s*)%(a:)?(\w+)%(,\s*)%(a:)?(\w+)\)/\1\2(\3: any, \4: any, \5: any)/ge
*上記のExコマンドは引数3つまでにしか対応していないので引数が4つ以上ある場合は拡張してください
引数と戻り値は必ず型宣言が必要ですが、分からない場合は any
にしておけばコンパイルは通ります(実行時に型チェックされます)
- func MyFunc(text)
+ def MyFunc(text: string): any
型がわからないときは typename()
を使用して型を調べます
5.
let
をvar
に置換する(代入のlet
は削除する)
:%s/\vlet\s*(b:|w:|g:|t:)/\1/ge | %s/\vlet\s*%(s:)?/var /ge
既に宣言されているローカル変数と b:
w:
g:
t:
変数の代入に使用される let
を削除する必要があります
*上記のExコマンドは既に宣言されているローカル変数に let
を使用していた場合にも var
に置き換えてしまうため、別途削除が必要です
- let lnum = 1
- let lnum += 3
- let b:result = 42
+ var lnum = 1
+ lnum += 3
+ b:result = 42
6. プレフィックス
s:
と引数辞書a:
を削除する
:%s/\v%(s:)(\w+)/\u\1/ge | %s/\va:(\w+)/\1/ge
プレフィックスのない関数と変数は全てスクリプトローカルになるため s:
を使用することはできません
引数辞書 a:
も使用できません
- return len(a:text)
+ return len(text)
7. 引数リスト
a:000
をlist
型に置換する
4. 引数の型と戻り値の型を書く
に示したExコマンドを使用した場合は引数が 000
に書き換わるため、コンパイルエラーが発生します
正規表現では直すのが難しいため、コンパイルエラーが発生したときに直します
引数リストも引数辞書同様に使用できません
最後の引数として名前と list
型で定義します
def MyFunc(...itemList: list<number>)
8. 文字列結合の
.
を..
に置換する
:%s/\s\.\s/ .. /gce
文字列の中に \s.\s
を含んでいる場合に注意する
このExコマンドは .
の前にスペースがあることを前提にしています
9. スペースが必要な箇所にスペースを入れる
:%s/\v(^[^#].*[^ ])#/\1 #/ge |
%s/\s*=\s*/ = /ge |
%s/\v\[%(\s*)(\w+)%(\s*)\:%(\s*)\]/[\1 :]/ge |
%s/\v\[%(\s*)\:%(\s*)(\w+)%(\s*)]/[:\1]/ge |
%s/\v\[\s*:\s*\]/[:]/ge |
:%s/\v\[\s*(\w+)\s*:\s*(\w+)\s*\]/[\1:\2]/ge
コマンドと #
, コメント文の間にはスペースが必要
var name = value # コメント
var name = value# エラー!
=
の前後にスペースが必要(大抵の演算子の周りで必須)
var name=234 # エラー!
var name= 234 # エラー!
var name =234 # エラー!
var name = 234 # OK
サブリストの :
の周りにスペースが必要
otherlist = mylist[v : count] # v:count は異なる意味を持つ
otherlist = mylist[:] # リストのコピーを作る
otherlist = mylist[v :]
otherlist = mylist[: v]
関数名と (
の間にスペースは禁止
3. func[tion]をdefに変換しabortを削除する
でExコマンドを実行した場合は、改行がない場合のみスペースが取り除かれています
Func (arg) # エラー!
Func
\ (arg) # エラー!
Func
(arg) # エラー!
Func(arg) # OK
Func(
arg) # OK
Func(
arg # OK
)
10.
defcompile
を最終行に記述して:source %
しコンパイルチェックを行う
:$put ='defcompile'
defcompile
を記述することでクラス内部で定義された関数以外をコンパイルすることができます。
一部の実行時エラーに実行前に気づくことができるので移行中は最終行に記述しておくのが良いと思います
コンパイルエラーになった箇所
おそらく先に示した手順だけではコンパイルエラーが発生すると思います
私が遭遇したエラーになるケースとその対応を取り上げてみます
E1267: Function name must start with a capital:
関数名はスクリプトローカルであっても大文字始まりにしなければなりません
E1052: Cannot declare an option: &t_SI =
E1016: Cannot declare a global variable:
E1016: Cannot declare an environment variable: $HOGE =
グローバル変数、Optionや環境変数はletが不要です
E1068: No white space allowed before ':':
dictionaryでkey, valueの間にスペースは禁止です
{key : value} # NG
{key: value} # OK
E1050: Colon required before a range:
Exコマンドに範囲を指定する場合はコロンが必要です
- let prevbuf = bufnr('\[update plugins\]')
- if prevbuf != -1
- execute prevbuf . 'bwipeout!'
- endif
+ var prevbuf: number = bufnr('\[update plugins\]')
+ if prevbuf != -1
+ # Needs `:` before Ex command with range.
+ execute ':' .. prevbuf .. 'bwipeout!'
+ endif
E1012: Type mismatch; expected number but got string
map()
に渡されたリストか辞書の要素の型は変更できません
var mylist: list<number> = [1, 2, 3] # 型推論させても同じエラーになるがanyを宣言すればエラーにならない.
- echo map(mylist, (i: number, _): string => 'item ' .. i)
+ echo mapnew(mylist, (i: number, _): string => 'item ' .. i)
+ # ['item 0', 'item 1', 'item 2']
E1091: Function is not compiled:
ユーザーコマンドが後で定義されている場合に発生します
先に定義するか、 execute('function')
を使用して間接的にコマンドを呼び出すことでエラーを回避することができます
command -nargs=1 MyCommand echom <q-args> # 先に定義する
def Works()
MyCommand 123
enddef
def Works()
command -nargs=1 MyCommand echom <q-args>
MyCommand 123 # エラー
execute 'MyCommand 123' # 間接的に呼び出す
enddef
他にも number
同士を比較するのに 同一インスタンスかどうか検証するis
を使っているためエラーが発生することもありました
Vim9 scriptで書き換えてみて
型付け、一般的なプログラミング言語の書き方、関数のスコープなど様々な変更によってとにかく書き心地と可読性が向上しました
またスペースなどフォーマットが揃うのも可読性に寄与しています
完全移行によりVim 9.0以降でないと動かなくなってしまったため、ポータビリティが下がってしまったのがデメリットです
こちらは仕方ないのですが、LSPのSyntaxが対応していないのもデメリットになるかもしれません
さて、もちろん起動時間が速くなると期待して置き換えたわけですが、有意な差が出ませんでした、残念!(そもそも重たい処理を行う関数がなかった)
実際のデータは下記のとおりです
全体では 1ms, vimrc単体だと0.5msほど速くなったみたいですね
$ vim-startuptime -script -count=1000
# Before
Total Average: 15.064100 msec
Total Max: 20.946000 msec
Total Min: 14.531000 msec
# After
Total Average: 14.783616 msec
Total Max: 18.224000 msec
Total Min: 14.130000 msec
AVERAGE MAX MIN
---------------------------
8.053200 11.622000 7.304000: $HOME/.vimrc # Before
7.694375 9.630000 7.338000: $HOME/.vimrc # After