vim-jp / vimdoc-ja / usr_52

usr_52 - Vim日本語ドキュメント

メインヘルプファイルに戻る English | 日本語 | 編集
usr_52.txt    For Vim バージョン 9.1.  Last change: 2024 Oct 07

                     VIM USER MANUAL - by Bram Moolenaar

                        大きなプラグインを作る


プラグインが単純なこと以上のことを行うと、大きくなる傾向があります。このファイ
ルは、それらが高速に読み込まれるようにする方法と、それらを小さなパーツに分割す
る方法を説明しています。

52.1  エクスポートとインポート
52.2  オートロード
52.3  インポート/エクスポートなしのオートロード
52.4  他に使えるメカニズム
52.5  旧来のスクリプトから Vim9 script を使う
52.6  Vim9 サンプル: comment および highlight-yank プラグイン

次章: usr_90.txt  Vim のインストール
前章: usr_51.txt  プラグインを作る
目次: usr_toc.txt

==============================================================================
52.1  エクスポートとインポート

Vim9 script は大きな Vim script を容易に書けるようにデザインされています。他の
スクリプト言語、特に TypeScript のようになっています。また、処理内の関数はコン
パイルされ素早く実行できます。これにより Vim9 script はとても速く、最大100倍に
なります。

基本的な考え方は、スクリプトファイルには、スクリプトファイル内でのみ使用される
プライベートな項目と、それらをインポートするスクリプトで使用できるエクスポート
される項目があるということです。これにより、何がどこで定義されているかが非常に
明確になります。

1つの関数をエクスポートし、1つのプライベート関数を持つスクリプトの例から始めま
しょう:

        vim9script

        export def GetMessage(count: string): string
           var nr = str2nr(count)
           var result = $'To {nr} we say '
           result ..= GetReply(nr)
           return result
        enddef

        def GetReply(nr: number): string
          if nr == 42
             return 'yes'
          elseif nr == 22
             return 'maybe'
          else
             return 'no'
          endif
        enddef

export は Vim9 script でのみ動くので vim9script コマンドが必要です。

export def GetMessage(... の行は export から始まっていて、この関数が他のス
クリプトから呼び出すことができることを意味します。def GetReply(... の行は
export で始まっておらず、これはスクリプトローカル関数であり、このスクリプト
ファイル内でのみ使用できます。

次にインポートされるスクリプトについて説明します。この例の中ではこの配置を使用
しており、これは "pack" ディレクトリ下のプラグインに適しています:
        .../plugin/theplugin.vim
        .../lib/getmessage.vim

"..." ディレクトリが 'runtimepath' に追加されていると仮定すると、Vim は
"plugin" ディレクトリからプラグインを探し "theplugin.vim" を読み込みます。Vim
は "lib" ディレクトリを認識しません、そこに任意のスクリプトを置くことができま
す。

上の GetMessage() をエクスポートしているスクリプトは lib/getmessage.vim 内に
あります。GetMessage() 関数は plugin/theplugin.vim 内にて使用されます:

        vim9script

        import "../lib/getmessage.vim"
        command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>)

import コマンドは "../" で始まる相対パスを使用しています。これは、1つ上のディ
レクトリに移動することを意味します。他の種類のパスについては、:import コマン
ドを参照してください。

プラグインが提供するコマンドを試す方法:
        ShowMessage 1
        To 1 we say no

        ShowMessage 22
        To 22 we say maybe

関数 GetMessage() には、インポートしたスクリプト名 "getmessage" がプリフィック
スとして付けられていることに注意してください。こうすることで、使用されるすべて
のインポートされた関数について、それがどのスクリプトからインポートされたかが分
かります。複数のスクリプトをインポートする場合、それぞれで GetMessage() 関数を
定義できます:

        vim9script

        import "../lib/getmessage.vim"
        import "../lib/getother.vim"
        command -nargs=1 ShowMessage echomsg getmessage.GetMessage(<f-args>)
        command -nargs=1 ShowOther echomsg getother.GetMessage(<f-args>)

インポートしたスクリプトの名前が長いあるいは色々な箇所で使う場合、引数に "as"
を追加することで短くできます:
        import "../lib/getmessage.vim" as msg
        command -nargs=1 ShowMessage echomsg msg.GetMessage(<f-args>)


再読み込み

1つ覚えておくべきこと: インポートされた "lib/getmessage.vim" スクリプトは1度だ
け読み込まれます。インポート済みで2度目に読み込む時はスキップされ、項目は最初
に作成された以降そのままです。これはインポートコマンドが他のスクリプト内にあ
るか、再読み込みされた同じスクリプト内にあるかは関係ありません。

これはプラグインを使用する場合は効率的ですが、プラグインを開発中の場合は、イン
ポート後に "lib/getmessage.vim" を変更しても効果がありません。Vim を終了し、再
起動する必要があります。(理論的根拠: スクリプトで定義された項目は、コンパイル
された関数で使用できます。スクリプトを再度読み込むと、これらの関数が壊れる可能
性があります)。


グローバルの使用

時には、グローバル変数や関数を使用して、どこでも使えるようにしたい場合がありま
す。プラグインに設定を渡すグローバル変数がその良い例です。他のスクリプトが同じ
名前を使うのを避けるために、他の場所で使われる可能性が非常に低いプリフィックス
を使用します。例えば、"mytags" プラグインがある場合、次のように使用します:

        g:mytags_location = '$HOME/project'
        g:mytags_style = 'fast'

==============================================================================
52.2  オートロード

大きなスクリプトを分割しても、スクリプトが使用された瞬間にすべての行がロードさ
れ、実行されます。import するたびに、インポートしたスクリプトをロードして、
そこで定義されている項目を探します。これはエラーを早期に発見するためには良いこ
とですが、同時に時間もかかります。そのため、その機能があまり使用されない場合
は、無駄な時間がかかってしまいます。

import でスクリプトをすぐにロードするのではなく、必要になるまで延期すること
ができます。上記の例を使用すると、plugin/theplugin.vim スクリプトで行う必要が
ある変更は1箇所だけです:
        import autoload "../lib/getmessage.vim"

スクリプトの残りの部分は何も変更する必要はありません。ただし、型はチェックされ
ません。GetMessage() 関数が使用されるまで、その存在すらチェックされません。ス
クリプトにとってどちらがより重要かを決定する必要があります: 高速な起動か早期に
エラーが分かるか。すべての動作を確認した後で、"autoload" 引数を追加することも
できます。


オートロードディレクトリ

もう1つの形式は絶対パスでも相対パスでもないスクリプト名で autoload を使用する
ことです:
        import autoload "monthlib.vim"

これはスクリプト "monthlib.vim" を 'runtimepath' のオートロードディレクトリの
中から検索します。Unix では、ディレクトリの1つが "~/.vim/autoload" であること
が多いです。これはまた 'packpath' の、その下の "start" からも検索します。

この方法の主要な利点として、このスクリプトを他のスクリプトと共有するのが容易で
あることです。Vim が 'runtimepath' 内の "autoload" ディレクトリから検索するた
め、スクリプト名は一意である必要があり、プラグインはいくつかのプラグインマネー
ジャーとともに利用される場合では、'runtimepath' にディレクトリを追加されること
があり、それぞれが "autoload" ディレクトリを持っているためです。

オートロードなしは:
        import "monthlib.vim"

Vim は 'runtimepath' のインポートディレクトリ内からスクリプト "monthlib.vim"
を検索します。Note この場合、"autoload" を追加または削除すると、スクリプトが見
つかる場所が変わります。相対パスまたは絶対パスでは、場所は変わりません。

==============================================================================
52.3  インポート/エクスポートなしのオートロード

                                                write-library-script
インポート/エクスポート以前のメカニズムは依然として使用でき一部ユーザーは多少
単純であると感じるかもしれません。それは特別な名前によって関数を呼び出すという
アイデアです。その関数はオートロードスクリプト内にあります。そのスクリプトのこ
とをライブラリスクリプトと呼びます。

オートロードのメカニズムは、関数名が "#" 文字を含んでいることが前提となってい
ます:

        mylib#myfunction(arg)

Vim は埋め込まれた "#" 文字によって関数名を認識し、まだ定義されていない場合は
"autoload/mylib.vim" というスクリプトを 'runtimepath' から探します。そのスクリ
プトは "mylib#myfunction()" 関数を定義している必要があります。言うまでもなく、
"mylib" という名前は "#" の前の部分であり、".vim" を追加してスクリプト名として
使用されます。

mylib.vim スクリプト内に他の多くの関数を置ことができ、ライブラリスクリプト内の
構成は自由です。ただし、'#' の前部分がスクリプト名に一致する関数名を使用する必
要があります。そうしないと、Vim はロードするスクリプトを認識できません。この点
がインポート/エクスポートのメカニズムとの違いになります。

本当に夢中になり多数のライブラリスクリプトを書くのであれば、サブディレクトリを
使用したいでしょう。例:

        netlib#ftp#read('somefile')

ここでは、スクリプト名は、関数名から最後の "#" までが取得されます。途中の "#"
はスラッシュで置き換えられ、最後の "#" は ".vim" で置き換えられます。したがっ
て、"netlib/ftp.vim" となります。Unixの場合、これに使われるライブラリスクリプ
トは次のようになります:

        ~/.vim/autoload/netlib/ftp.vim

ここで、関数はこのように定義されています:

        def netlib#ftp#read(fname: string)
                #  Read the file fname through ftp
        enddef

関数が定義されている名前は、関数を呼び出すときに使われる名前と全く同じであるこ
とに注意してください。また、最後の '#' の前の部分は、サブディレクトリとスクリ
プト名に完全に一致しています。

変数にも同じメカニズムが使用できます:

        var weekdays = dutch#weekdays

これによりスクリプト "autoload/dutch.vim" がロードされ、次のような内容が含ま
れる必要があります:

        var dutch#weekdays = ['zondag', 'maandag', 'dinsdag', 'woensdag',
                \ 'donderdag', 'vrijdag', 'zaterdag']

参考文献: autoload

==============================================================================
52.4  他に使えるメカニズム

複数のファイルを使用するのが面倒で、すべてを1つのスクリプトにまとめておくのを
好む人もいます。その結果として、起動が遅くなるのを回避するため、小さな部分のみ
を定義し、残りは実際に使用されるまで延期するメカニズムがあります。
                                                write-plugin-quickload

基本的な考え方は、プラグインは2回ロードされるということです。初回は、機能を提
供するためのユーザーコマンドとマッピングが定義されます。2回目は、その機能を実
装する関数が定義されます。

高速ロードがスクリプトを2回ロードすることを意味するのは意外に思われるかもしれ
ません。つまり、初回は素早く読み込み、2回目にスクリプトの大部分を読み込ませる
ということです。この機能は、実際に使用するときにのみ発生します。常に機能を使用
する場合、実際には遅くなります!

これは、FuncUndefined 自動コマンドを使用しています。上で説明した autoload 機
能とは異なる動作をします。

以下の例は、その方法を示しています:

        " Vim global plugin for demonstrating quick loading
        " Last Change:  2005 Feb 25
        " Maintainer:   Bram Moolenaar <Bram@vim.org>
        " License:      This file is placed in the public domain.

        if !exists("s:did_load")
                command -nargs=* BNRead  call BufNetRead(<f-args>)
                map <F19> :call BufNetWrite('something')<CR>

                let s:did_load = 1
                exe 'au FuncUndefined BufNet* source ' .. expand('<sfile>')
                finish
        endif

        function BufNetRead(...)
                echo 'BufNetRead(' .. string(a:000) .. ')'
                " read functionality here
        endfunction

        function BufNetWrite(...)
                echo 'BufNetWrite(' .. string(a:000) .. ')'
                " write functionality here
        endfunction

スクリプトの初回ロード時は "s:did_load" は未設定です。"if" と "endif" の間のコ
マンドが実行されます。これは :finish コマンドで終わり、残りのスクリプトは実
行されません。

スクリプトの2回目ロード時は "s:did_load" は存在し、"endif" 以降のコマンドが実
行されます。これは(長い可能性のある) BufNetRead() と BufNetWrite() 関数を定義
します。

あなたのプラグインディレクト内にスクリプトを置くと Vim は初期化時に実行します。
このような流れでイベントが発生します:

1. 起動時にスクリプトが読み込まれた時、"BNRead" コマンドが定義され、<F19> キー
   がマッピングされる。FuncUndefined 自動コマンドが定義される。":finish" コ
   マンドは、スクリプトを早期に終了させる。

2. ユーザーが BNRead コマンドをタイプするか <F19> キーを押す。BufNetRead()
   か BufNetWrite() 関数が呼ばれる。

3. Vim は関数を見つけることができず、FuncUndefined 自動コマンドイベントが発
   生する。パターン "BufNet*" は呼び出された関数と一致するため、コマンド
   "source fname" が実行される。"fname" は、"<sfile>" (expand() を参照) を展
   開したものであるため、スクリプトがどこにあるかに関係なく、スクリプトの名前
   と等しくなる。

4. スクリプトは再度読み込まれ、"s:did_load" 変数は存在し、関数が定義される。

ロードされた後の関数が FuncUndefined 自動コマンドのパターンにマッチすること
に注意してください。他のプラグインで定義するパターンにマッチする関数がないよう
にしないといけません。

==============================================================================
52.5  旧来のスクリプトから Vim9 script を使う         source-vim9-script

いくつかの場合として持っている旧来の Vim script にて Vim9 script のアイテムを
使いたいことがあります。例えばあなたの .vimrc がプラグインの初期化をする場合な
どです。最良の方法は :import を使うことです。例:

        import 'myNicePlugin.vim'
        call myNicePlugin.NiceInit('today')

これは、Vim9 script ファイル内でエクスポートされた関数 "NiceInit" を見つけ、ス
クリプトローカルな項目 "myNicePlugin.NiceInit" として使用できるようにします。
:import は、"s:" が指定されていない場合でも、常にスクリプトの名前空間を使用
します。"myNicePlugin.vim" がすでに読み込まれていた場合、再度読み込まれること
はありません。

その上アイテムがグローバルの名前空間に置かれるのを防ぎ (名前の衝突が不測の問題
になり得ます)、そしてたとえ何度もアイテムをインポートしても、スクリプトの読み
込みがただ一度であることを意味します。

いくつかの場合、例えばテストなどで、ただ Vim9 script を読み込みたいだけのこと
があります。大丈夫ですが、グローバルのアイテムだけが利用可能となります。Vim9
script ではこうしたグローバルのアイテムで確実にユニークな名前を使うようにしな
くてはなりません。 例:
        source ~/.vim/extra/myNicePlugin.vim
        call g:NicePluginTest()

==============================================================================
52.6  Vim9 サンプル: comment および highlight-yank プラグイン

comment パッケージ

Vim には、Vim9 script で記述されたコメントプラグインが付属しています。
comment-install
$VIMRUNTIME/pack/dist/opt/comment/ にあるパッケージを確認してください。

highlight-yank プラグイン

ヤンクされた領域をハイライトする例を以下に示します。これは、Vim 9.1.0446 以降
で使用可能な getregionpos() 関数を使用します。

以下の例を新しいファイルにコピーしてプラグインディレクトリに配置すると、次回
Vim を起動したときにアクティブになります。add-plugin:

        vim9script

        def HighlightedYank(hlgroup = 'IncSearch', duration = 300, in_visual = true)
          if v:event.operator ==? 'y'
            if !in_visual && visualmode() != null_string
              visualmode(1)
              return
            endif
            var [beg, end] = [getpos("'["), getpos("']")]
            var type = v:event.regtype ?? 'v'
            var pos = getregionpos(beg, end, {type: type})
            var end_offset = (type == 'V' || v:event.inclusive) ? 1 : 0
            var m = matchaddpos(hlgroup, pos->mapnew((_, v) => {
              var col_beg = v[0][2] + v[0][3]
              var col_end = v[1][2] + v[1][3] + end_offset
              return [v[0][1], col_beg, col_end - col_beg]
            }))
            var winid = win_getid()
            timer_start(duration, (_) => m->matchdelete(winid))
          endif
        enddef

        autocmd TextYankPost * HighlightedYank()

==============================================================================

次章: usr_90.txt  Vim のインストール


Copyright: see manual-copyright  vim:tw=78:ts=8:noet:ft=help:norl: