本文へジャンプ

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。プロフィールで選択した情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

  • 閉じる [x]

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む


送信されたすべての情報は安全です。

  • 閉じる [x]

Vim エディターのスクリプトの作成: 第 1 回 変数、値、式

Vim スクリプトの基本要素の紹介

Damian Conway, Dr., CEO and Chief Trainer, Thoughtstream
author photo - damian conway
Damian Conway は、オーストラリア Monash University でコンピューター・サイエンスの Adjunct Associate Professor を務める傍ら、国際的 IT トレーニング会社、Thoughtstream の CEO でもあります。彼が日常的に vi を使うようになって 4 半世紀が過ぎた今、彼がこの依存症を克服する見込みはなさそうです。

概要: Vim スクリプトは、Vim エディターを新たに作り直して拡張するためのメカニズムです。スクリプトを組むことによって、新しいツールを作成したり、共通のタスクを単純化したり、さらにはエディターの既存の機能を再設計して置き換えることさえできます。連載 1 回目のこの記事では、Vim スクリプト・プログラミング言語の基本構成要素である値、変数、式、文、関数、コマンドについて紹介します。これらの機能を単純な一連の例で実際に使用しながら説明します。

このシリーズの他の記事を見る

日付:  2009年 5月 06日
レベル:  初級 この記事の原文:  英語
アクティビティー: 34000 ビュー
お気軽にご意見・ご感想をお寄せください: 


優れたテキスト・エディター

古い冗談にこうあります。まともなテキスト・エディターさえあったら Emacs は優れたオペレーティング・システムになっていただろうし、まともなオペレーティング・システムさえあったら vi は優れたテキスト・エディターになっていただろう。この冗談は、これまで常に Emacs が vi よりも戦略的に大きく勝っていた 1 つの点を表しています。それは、組み込みの拡張プログラミング言語です。Emacs のユーザーは腱鞘炎を起こしそうなほどの制御コードの入力を快く我慢し、進んで Lisp 言語を使って拡張機能を作成しているという事実が、まさに、組み込み拡張言語がいかに大きな利点であるかを証明しています。

しかし、vi プログラマーが今までのように Emacs の括弧で括られたスクリプト言語に羨望のまなざしを向ける必要はもうありません。我々のお気に入りの vi エディターも今やスクリプトで処理できるのです。しかもその方法は、Emacs より遙かに人間の作業に向いたものです。

この連載記事では、vi から派生して現在最もよく使われている Vim エディターを取り上げ、Vim が提供する単純ながらも極めて強力なスクリプト言語について検討します。第 1 回目のこの記事では、Vim スクリプトの基本構成要素である変数、値、式、単純なフロー制御、そして Vim が持つ数々のユーティリティー関数をいくつか抜粋して、その詳細を説明します。

連載では、読者が Vim を利用できること、そしてそのインタラクティブな機能について十分に理解していることを前提とします。この前提を満たしていない場合は、Vim が提供する Web サイトや各種のオンライン・リソースおよび書籍にまず目を通してください。あるいは Vim で単純に :help と入力して情報を入手することもできます。資料へのリンクは、「参考文献」セクション記載されています。

特に断りがない限り、この連載に記載するすべての例は、読者が Vim バージョン 7.2 以降を使用していることを前提とします。どのバージョンの Vim を使用しているのかを確認するには、以下のコマンドでエディターを起動してください。

vim --version

または、Vim に :version と入力して確認することもできます。バージョン 7.2 より前の Vim を使用している場合は、最新のリリースにアップグレードすることを是非ともお勧めします。以前のバージョンでは、これから説明する Vim スクリプトの機能の多くがサポートされていません。Vim をダウンロードおよびアップグレードするためのリンクは、「参考文献」セクションに記載されています。


Vimscript

Vim スクリプトとして知られる Vim のスクリプト言語は典型的な動的命令型言語で、一般的な言語機能のほとんどを提供します。具体的に言うと、変数、式、制御構造、組み込み関数、ユーザー定義関数、ファーストクラス文字列、ハイレベル・データ構造 (リストおよび辞書)、端末およびファイル入出力、正規表現パターン・マッチング、例外、そして統合デバッガーです。

組み込みヘルプ・システムを介して Vim 自体に用意されている Vim スクリプトのマニュアルを読むには、以下のように入力してください。

:help vim-script-intro

上記のコマンドを Vim セッションで実行するか、またはこの記事を読み進めてください。

Vim スクリプトの実行

Vim スクリプト・コマンドを実行する方法はいくつもありますが、そのうち最も簡単な方法は、スクリプト・コマンドを 1 つのファイル (通常は .vim 拡張子を持つファイル) にまとめて配置し、Vim セッションで以下のように :source を使ってそのファイルを実行するというものです。

:source /full/path/to/the/scriptfile.vim

あるいは、Vim コマンドラインでコロンの後にスクリプト・コマンドを直接入力するという方法もあります。以下はその一例です。

:call MyBackupFunc(expand('%'), { 'all':1, 'save':'recent'})

しかし、この方法を使う人はほとんどいません。突き詰めるところ、スクリプトを使用する本質は必要な入力作業を減らすことに尽きます。そのため、Vim スクリプトを起動する方法として最もよく使われているのは、以下のように新しいキーボード・マッピングを作成するという方法です。

:nmap ;s :source /full/path/to/the/scriptfile.vim<CR>

:nmap \b :call MyBackupFunc(expand('%'), { 'all': 1 })<CR>

上記のようなコマンドは、通常はホーム・ディレクトリーの .vimrc 初期化ファイル内に配置します。コマンドを配置した後は、通常モード (つまり、テキストを挿入しないこと) でキー・シーケンス ;s を入力すると指定のスクリプト・ファイルが実行され、\b を入力すると MyBackupFunc() 関数が呼び出されます (この関数も .vimrc 内に定義されているという前提です)。

この記事に記載する Vim スクリプトのすべての例では、さまざまな種類のキー・マッピングをトリガーとして使用しますが、以降の記事では他の 2 つの一般的な呼び出し手法として、Vim のコマンドラインからコロン・コマンドとしてスクリプトを実行する方法、そしてエディター・イベントを使用して自動的にスクリプトを起動する方法を紹介します。


構文の例

Vim には極めて高度な構文強調機能があり、この機能は組み込みコマンド :syntax enable で有効にし、:syntax off で無効にすることができます。

けれども構文強調機能のオン/オフを切り替えるたびに 10 文字あるいはそれ以上の文字を入力するのは煩わしいものです。そこで、.vimrc ファイルに以下の Vim スクリプトの行を配置するという方法を使うことができます。


リスト 1. 構文強調機能を切り替える
function! ToggleSyntax()
   if exists("g:syntax_on")
      syntax off
   else
      syntax enable
   endif
endfunction

nmap <silent>  ;s  :call ToggleSyntax()<CR>

上記のコードによって、通常モードで ;s シーケンスを入力するたびに構文強調機能のオン/オフが切り替わるようになります。以下に、このスクリプトで使用されているそれぞれの構成要素について説明します。

コードの最初のブロックは明らかに関数宣言で、ここでは引数を取らない ToggleSyntax() という名前の関数を定義しています。このユーザー定義関数は最初に Vim の組み込み関数 exists() を呼び出し、文字列を渡します。exists() 関数は、渡された文字列に指定されている名前の変数 (上記の例では、グローバル変数 g:syntax_on) が定義されているかどうかを判断します。

変数が定義されている場合は if 文が syntax off を実行し、定義されていなければ syntax enable を実行します。syntax enableg:syntax_on 変数を定義し、syntax off はこの定義を解除するため、ToggleSyntax() 関数を繰り返し呼び出すことで構文強調機能のオン/オフが切り替わるというわけです。

以下に示すコードの残りの部分は、ToggleSyntax() 関数を呼び出すキー・シーケンス (この例では ;s) を設定するためのものです。

nmap <silent> ;s :call ToggleSyntax()<CR>

nmap は「normal-mode key mapping (通常モードのキー・マッピング)」の略です。nmap に続く <silent> は、マッピングが実行中のコマンドをエコー出力しないようにするためのオプションで、これによって新しい ;s コマンドは見えないところでその作業を実行することになります。この作業とは、以下のコマンドを実行することです。

:call ToggleSyntax()<CR>

Vim スクリプトでは、上記の方法で関数を呼び出すことによって戻り値を無視することができます。

末尾にある <CR> は、<CR> という文字のリテラル・シーケンスであることに注意してください。Vim スクリプトはこのシーケンスをリテラル・キャリッジ・リターンとして解釈します。実際、Vim スクリプトが理解する出力不可能な文字はこの他にも多数あります。例えば、大抵の Web ブラウザーと同じくスペース・キーをページ・ダウン・キーとして機能させるキーボード・マッピングを作成するには、以下のようにします。

:nmap <Space> <PageDown>

これらの特殊な記号を網羅したリストを確認するには、Vim で :help keycodes と入力してください。

ToggleSyntax() は組み込みコマンド syntax を直接呼び出すこともできます。これは、Vim の組み込みコロン・コマンドは、すなわち Vim スクリプトでの文でもあるからです。例えば Vim で作成した文書に、中央揃えしたタイトルを簡単に作成できるようにしたいとします。それには、以下のように現行の行で各単語の先頭文字を大文字にし、行全体を中央揃えしてから次の行に進むといった関数を作成することができます。


リスト 2. 中央揃えしたタイトルを作成する
function! CapitalizeCenterAndMoveDown()
   s/\<./\u&/g   "Built-in substitution capitalizes each word
   center        "Built-in center command centers entire line
   +1            "Built-in relative motion (+1 line down)
endfunction

nmap <silent>  \C  :call CapitalizeCenterAndMoveDown()<CR>

Vim スクリプトの文

これまでの例に示されているように、Vim スクリプトの文はすべて改行で終わります (シェル・スクリプトや Python の場合と同様)。複数の行にまたがる文を実行しなければならない場合には、行を続けるためのマーカーとして単一のバックスラッシュを使用します。一般的な使い方とは若干違い、バックスラッシュは続きのある行の終わりにではなく、その後に続く行の先頭に配置します。


リスト 3. バックスラッシュを使用して行を継続する
call SetName(
\             first_name,
\             middle_initial,
\             family_name
\           )

また、垂直バーで区切ることによって複数の文を 1 行に記述することもできます。

echo "Starting..." | call Phase(1) | call Phase(2) | echo "Done"

つまり、Vim スクリプトでの垂直バーは、他のほとんどのプログラミング言語でのセミコロンに相当するということです。あいにく、Vim ではセミコロンを使用することができません。コマンドの先頭にあるセミコロンには、他の言語のセミコロンとは別の意味があるためです (具体的には、セミコロンはコマンドの行の範囲として「現在の行から~まで)」という意味で使用されます)。

コメント

文の区切り文字としての垂直バーは、コメントを追加する際に重要な役割を果たします。Vim スクリプトのコメントは、以下のように二重引用符で始まり、行の終わりまで続きます。


リスト 4. Vim スクリプトでのコメント
if exists("g:syntax_on")
   syntax off      "Not 'syntax clear' (which does something else)
else
   syntax enable   "Not 'syntax on' (which overrides colorscheme)
endif

しかし Vim スクリプトの文字列も同じく二重引用符で開始することができ、しかも文字列は常にコメントよりも優先されます。これはつまり、以下のように文字列が想定される場所にコメントを配置すると、コメントが文字列として解釈されてしまうということです。

echo "> " "Print generic prompt

echo コマンドは 1 つまたは複数の文字列があることを想定しているため、上記の行では、(Vim が文字列と見なす) 2 番目の文字列の終わりに引用符がないことを示すエラーが発生します。

しかし、コメントは文の先頭から始めることができるので、コメントを始める前に垂直バーを使用して明示的に新しい文を開始すれば上記の問題を解決することができます。したがって、以下のように修正します。

echo "> " |"Print generic prompt

値と変数

Vim スクリプトで変数を割り当てるには、特殊なキーワード、let が必要です。


リスト 5. let キーワードの使用
let name = "Damian"

let height = 165

let interests = [ 'Cinema', 'Literature', 'World Domination', 101 ]

let phone     = { 'cell':5551017346, 'home':5558038728, 'work':'?' }

文字列を指定する場合には、二重引用符または単一引用符のいずれかを区切り文字として使用できることに注意してください。二重引用符で囲まれた文字列は特殊な「エスケープ・シーケンス」に従います。例えば、"\n" (改行)、"\t" (タブ)、"\u263A" (Unicode の顔文字)、"\<ESC>" (エスケープ文字) などです。これとは対照的に、単一引用符付きの文字列はその区切り文字で囲まれた中身のすべてをリテラル文字として扱います。ただし例外として、連続する 2 つの単一引用符は 1 つのリテラル単一引用符として扱われます。

Vim スクリプトでの値は、通常以下の 3 つの型のいずれかとなります。

  • スカラー: 文字列や数値など、単一の値です。例: "Damian"165 など
  • リスト: 大括弧で囲まれた値の順序付きシーケンスで、それぞれの値にはゼロから始まる整数のインデックスが暗黙的に付けられます。例: ['Cinema', 'Literature', 'World Domination', 101]
  • 辞書: 中括弧で囲まれた順不同の一連の値で、それぞれの値が明示的なストリング・キーを持ちます。例: {'cell':5551017346, 'home':5558038728, 'work':'?'}

リストまたは辞書に含まれるすべての値が同じ型である必要はありません。文字列や数値を混在させることも、さらにはリストや辞書をネストさせることもできます。

値とは異なり、変数には固有の型がありません。代わりに、変数に割り当てられた最初の値の型が使用されます。したがって上記の例では、name 変数と height 変数はスカラーということになります (つまり、これらの変数に格納できるのは文字列または数値のみです)。一方、interests はリスト変数 (リストのみ保存可能) で、phone は辞書変数 (辞書のみ保存可能) です。変数にいったん割り当てられた型は永続的に維持され、実行時にはその型が厳格に適用されます (下記参照)。

let interests = 'unknown' " Error: variable type mismatch

デフォルトでは、変数のスコープはその変数が最初に割り当てられた関数に設定されます。最初の割り当てが関数の外部で行われた場合には、グローバル・スコープとなります。ただし、さまざまな接頭辞を使うことで、変数を明示的に他のスコープに属するものとして宣言することも可能です。表 1 に、これらの接頭辞を要約します。


表 1. Vim スクリプトの変数のスコープ指定
接頭辞意味
g:varname指定の変数をグローバル変数として指定
s:varname指定の変数をカレント・スクリプト・ファイルのローカル変数として指定
w:varname指定の変数をカレント・エディター・ウィンドウのローカル変数として指定
t:varname指定の変数をカレント・エディター・タブのローカル変数として指定
b:varname指定の変数をカレント・エディター・バッファーのローカル変数として指定
l:varname指定の変数を現行関数のローカル変数として指定
a:varname指定の変数を現行関数のパラメーターとして指定
v:varname指定の変数を Vim 事前定義変数として指定

この他、スクリプトが Vim で提供される別の型の値コンテナーにアクセスするために使用できる疑似変数もあります。表 2 にこれらの疑似変数を要約します。


表 2. Vim スクリプトの疑似変数
接頭辞意味
&varnameVim オプション (定義がある場合はローカル・オプション。そうでなければグローバル・オプション)
&l:varnameローカル Vim オプション
&g:varnameグローバル Vim オプション
@varnameVim レジスター
$varname環境変数

上記のうち、特に有用なのは「オプション」疑似変数です。例えば以下のように、現行のタブ間隔を大きくしたり小さくしたりする 2 つのキー・マッピングを設定することができます。

nmap <silent> ]] :let &tabstop += 1<CR>

nmap <silent> [[ :let &tabstop -= &tabstop > 1 ? 1 : 0<CR>

前の例での [[ キー・マッピングは、C のような「三項式」が含まれる式を使用していることに注目してください。

&tabstop > 1 ? 1 : 0

この式によって、キー・マッピングが現行のタブ間隔を最小値の 1 より小さくすることを防いでいます。この例からわかるように、Vim スクリプトでの式は、最近のほとんどのスクリプト言語で使用されている基本的な演算子を同じく使用し、その構文も通常は同じです。表 3 に、使用可能な演算子を要約します (優先順にグループ化しています)。


表 3. Vim スクリプトの演算子の優先順位表
演算演算子構文
割り当て
数値を加算して割り当て
数値を減算して割り当て
文字列を連結して割り当て
letvar=expr
let var+=expr
let var-=expr
let var.=expr
三項演算子bool?expr-if-true:expr-if-false
論理 ORbool||bool
論理 ANDbool&&bool
数値または文字列の等式
数値または文字列の不等式
数値または文字列の「より大きい」
数値または文字列の「以上」
数値または文字列の「未満」
数値または文字列の「以下」
expr==expr
expr!=expr
expr>expr
expr>=expr
expr<expr
expr<=expr
数値の加算
数値の減算
文字列の連結
num+num
num-num
str.str
数値の乗算
数値の除算
数値のモジュロ
num*num
num/num
num%num
数値への変換
数値の符合の反転
論理 NOT
+num
-num
!bool
括弧による優先(expr)

論理に関する注意

Vim スクリプトでは C の場合と同じく、ブール値のコンテキストで false となるのは数値のゼロのみです。ゼロ以外の数値は、正か負かに関わらず true と見なされます。ただし、すべての論理演算子および比較演算子が true として返す値は常に 1 です。

文字列がブール値として使用されている場合、文字列はまず整数に変換されてから true (ゼロ以外) または false (ゼロ) のどちらであるかが評価されます。これが何を意味するかと言うと、圧倒的多数の文字列は (空ではない文字列のほとんども含め) false として評価されるということです。そのため、空の文字列に対して以下のようにテストするという過ちを犯しがちです。


リスト 6. 空の文字列に対する誤ったテスト
let result_string = GetResult();

if !result_string
   echo "No result"
endif

このテストは result_string が空の文字列に割り当てられている場合には正常に機能しますが、問題は、result_string"I am NOT an empty string" のような文字列が含まれていても "No result" の結果になることです。なぜなら、含まれている文字列は最初に数値 (ゼロ) に変換されてからブール値 (false) に変換されるためです。

正しい解決策は、適切な組み込み関数を使って明示的に文字列が空であるかどうかをテストすることです。


リスト 7. 空の文字列に対する正しいテスト
if empty(result_string)
   echo "No result"
endif

コンパレーターに関する注意

Vim スクリプトでは、両方のオペランドが文字列でない限り、コンパレーターは常に数値を比較します。具体的に言うと、一方のオペランドが文字列で、もう一方のオペランドが数値の場合、文字列が数値に変換されてから 2 つの数値のオペランドが比較されます。そのため、以下のような式では文字列がゼロに変換されるため、結果は常に true となってしまいます。

let ident = 'Vim'

if ident == 0 "Always true (string 'Vim' converted to number 0)

このような場合は以下の方法を使うと、より確実な解決策となります。

if ident == '0'   "Uses string equality if ident contains string"but numeric equality 
if ident contains number

通常、文字列の比較は Vim の ignorecase オプションのローカル設定に従いますが、いずれの文字列コンパレーターにしても大文字と小文字を区別するか (# を付加)、または区別しないか (? を付加) を明示的に指定することができます。


リスト 8. 大/小文字の区別を指定した文字列コンパレーター
if name ==? 'Batman'         |"Equality always case insensitive
   echo "I'm Batman"
elseif name <# 'ee cummings' |"Less-than always case sensitive
   echo "the sky was can dy lu minous"
endif

文字列の比較では常に、「明示的に大/小文字の区別を指定」した演算子を使用することを強くお勧めします。それによって、ユーザーのオプション設定に依存しない信頼性の高いスクリプトの動作が確実になるからです。

演算に関する注意

演算式を使用する場合に注意しなければならないもう 1 つの点は、バージョン 7.2 より前の Vim では整数演算しかサポートされていなかったことです。そのため、以前のバージョンでは以下のような式を作成するという間違いがよくありました。


リスト 9. 整数演算での問題
"Step through each file...
for filenum in range(filecount)
   " Show progress...
   echo (filenum / filecount * 100) . '% done'" Make progress...
   call process_file(filenum)
endfor

この場合、filenum は常に filecount よりも小さくなるため、filenum/filecount という整数の除算をすると結果は一貫してゼロとなり、ループの繰り返し処理による各エコー出力は以下のようになります。

Now 0% done

バージョン 7.2 でもオペランドの一方が明示的な浮動小数点数であれば、Vim が行うのは浮動小数点演算のみとなります。

let filecount = 234

echo filecount/100   |" echoes 2
echo filecount/100.0 |" echoes 2.34


切り替えのもう 1 つの例

前に記載した構文切り替えスクリプトを応用すれば、簡単に別の有用なツールを作成することができます。例えば、頻繁にスペルを誤ったり、誤用したりする一連の単語があるとします。その場合、.vimrc に Vim のマッチング・メカニズムを起動するスクリプトを追加し、テキストを校正するときに問題のある単語を強調表示するという対策をとれます。

一例として、前のパラグラフのようなテキスト (以下に記載する原文のテキスト) を Vim 内に表示するキー・マッピング (例えば ;p) を作成します。

It's easy to adapt the syntax-toggling script shown earlier to create other useful tools. For example, if there is a set of words that you frequently misspell or misapply, you could add a script toyour .vimrc to activate Vim's match mechanism and highlight problematic words when you're proofreading text.

スクリプトは以下のようになります。


リスト 10. 誤用しがちな単語の強調表示
"Create a text highlighting style that always stands out...
highlight STANDOUT term=bold cterm=bold gui=bold

"List of troublesome words...
let s:words = [
             \ "it's",  "its",
             \ "your",  "you're",
             \ "were",  "we're",   "where",
             \ "their", "they're", "there",
             \ "to",    "too",     "two"
             \ ]

"Build a Vim command to match troublesome words...
let s:words_matcher
\ = 'match STANDOUT /\c\<\(' . join(s:words, '\|') . '\)\>/'

"Toggle word checking on or off...
function! WordCheck ()
   "Toggle the flag (or set it if it doesn't yet exist)...
   let w:check_words = exists('w:check_words') ? !w:check_words : 1

   "Turn match mechanism on/off, according to new state of flag...
   if w:check_words
      exec s:words_matcher
   else
      match none
   endif
endfunction

"Use ;p to toggle checking...
nmap <silent> ;p :call WordCheck()<CR>

単語チェックのオン/オフを切り替えるためには、変数 w:check_words をブール値フラグとして使用しています。WordCheck() 関数の最初の行はフラグが既に存在するかどうかを調べ、存在する場合には以下の割り当てによって変数のブール値を切り替えます。

let w:check_words = exists('w:check_words') ? !w:check_words : 1

w:check_words がまだ存在しない場合には、値 1 を割り当てて作成します。

let w:check_words = exists('w:check_words') ? !w:check_words : 1

上記で使用されている接頭辞 w: に注意してください。w: が使用されているということは、フラグ変数が常にカレント・ウィンドウのローカル変数であることを意味します。そのため、単語チェックはそれぞれのエディター・ウィンドウで個別に切り替えることが可能になります (これは、同じく常にカレント・ウィンドウにのみ作用する match コマンドの動作と一貫する設定です)。

単語チェックを有効にするには、Vim の match コマンドを設定します。match コマンドは、テキスト強調表示の指定 (この例では STANDOUT) とその後に続く強調表示対象のテキストを指定する正規表現を要求します。この例での正規表現は、スクリプトの s:words リスト変数 (つまり、join(s:words, '\|')) に指定されたすべての単語を OR で連結することによって構成されています。この選択肢のセットを、大文字小文字を区別しない単語境界線で囲むことによって (\c\<\(...\)\>)、大文字か小文字かに関わらず単語全体のみが突き合わせられるようにしています。

この突き合せのための文字列を使用した Vim コマンドを作成したら、WordCheck() 関数ではこのコマンドを実行 (exec s:words_matcher) してマッチング機能をオンにします。w:check_words がオフに切り替えられると、この関数は代わりに match none コマンドを実行して、この特殊なマッチング機能を停止します。


挿入モードでのスクリプト

Vim スクリプトは通常モードでしか使用できないわけではありません。テキストを挿入する際に使用できるキー・マッピングまたは略語を imap コマンドや iabbrev コマンドで設定することもできます。以下に例を示します。

imap <silent> <C-D><C-D> <C-R>=strftime("%e %b %Y")<CR>

imap <silent> <C-T><C-T> <C-R>=strftime("%l:%M %p")<CR>

これらのマッピングを .vimrc に設定すると、挿入モードで CTRL-D を 2 回入力することで Vim の組み込み関数 strftime() が呼び出されて日付が挿入され、CTRL-T を 2 回入力すると現在の時刻が挿入されることになります。

これと同じ汎用パターンを使用すれば、挿入マッピングや略語によって、スクリプト化可能なあらゆる操作を実行することができます。それには、適切な Vim スクリプトの式または関数呼び出しを最初の <C-R>= と最後の <CR> の間に挿入すればよいだけのことです (<C-R>= は Vim に対して、<C-R>= に続く式の評価結果を挿入するように指定し、<CR> は Vim に対して、その前の式を実際に評価するように指定します)。ただし、<C-R> (Vim での CTRL-R の省略形) は <CR> (Vim でのキャリッジ・リターンの省略形) と同じではないことを忘れないでください。

例えば Vim の組み込み関数 getcwd() を使用して、以下のように現行の作業ディレクトリーに対する略語を作成することができます。

iabbrev <silent> CWD <C-R>=getcwd()<CR>

あるいは、テキストの挿入中に CTRL-C と入力することで呼び出せる、単純な電卓を組み込むことも可能です。

imap <silent> <C-C> <C-R>=string(eval(input("Calculate: ")))<CR>

式の部分を以下に抜粋します。

string( eval( input("Calculate: ") ) )

この式はまず、組み込み関数 input() を呼び出してユーザーに計算を入力するように要求します。input() は入力された内容を文字列として返します。この入力文字列は組み込み関数 eval() に渡され、この関数が文字列を Vim スクリプトの式として評価して結果を返すと、次に組み込み関数 string() が数値の結果を変換して文字列に戻します。すると、キー・マッピングの <C-R>= シーケンスで文字列を挿入できるようになります。

さらに複雑な挿入モードのスクリプト

挿入マッピングでは、これまでの例よりかなり高度なスクリプトを呼び出すことができます。そのような場合は通常、コードをユーザー定義関数にリファクタリングし、キー・マッピングで呼び出せるようにするのが賢い方法です。

例えば、挿入時の CTRL-Y の動作は変更することができます。一般に、挿入モードで CTRL-Y が行うのは「縦方向のコピー」です。すなわち、カーソルがある行の直前の行内でカーソルと同じ列にある文字を、カーソルの位置にコピーするということです。以下の例で言うと、CTRL-Y によってカーソルの位置には「m」が挿入されることになります。

Glib jocks quiz nymph to vex dwarf
Glib jocks quiz ny_

その一方、縦方向のコピーに空の行を無視させて、挿入ポイントがある行より前の行で空行ではない最も近くの行内でカーソルと同じ列にある文字を、カーソルの位置にコピーしたいという場合もあります。例えば以下の場合、直前の行が空行であっても CTRL-Y によって「m」が挿入されるようにするということです。

Glib jocks quiz nymph to vex dwarf

Glib jocks quiz ny_

この拡張動作を実現するには、以下のコードを .vimrc ファイルに配置します。


リスト 11. 空行を無視するように改善した縦方向のコピー
"Locate and return character "above" current cursor position...
function! LookUpwards()
   "Locate current column and preceding line from which to copy...
   let column_num      = virtcol('.')
   let target_pattern  = '\%' . column_num . 'v.'
   let target_line_num = search(target_pattern . '*\S', 'bnW')

   "If target line found, return vertically copied character...
if !target_line_num return "" else return matchstr(getline(target_line_num), target_pattern) endif endfunction "Reimplement CTRL-Y within insert mode...
imap <silent> <C-Y> <C-R><C-R>=LookUpwards()<CR>

LookUpwards() 関数はまず、組み込み関数 virtcol() を使って現在挿入ポイントがあるスクリーン上の列 (つまり「縦の列」) を判断します。'.' 引数は、現在のカーソル位置の列番号が必要であることを指定します。

let column_num = virtcol('.')

LookUpwards() は次に組み込み関数 search() を使って、カーソル位置から上に向かってファイルを調べます。

let target_pattern = '\%' . column_num . 'v.'
let target_line_num = search(target_pattern . '*\S', 'bnW')

検索では特殊なターゲット・パターン ( \%column_numv.*\S) を使用して、先行する行のなかで空白でない文字を持つ最も近くの行をカーソル列 (\%column_numv) の位置 (\S) またはその後 (.*) に配置します。search() への 2 番目の引数は、逆方向に検索を行う一方、カーソルを動かしたり、検索をラップしたりしないよう、関数に指示する構成文字列 bnW です。検索が正常に完了すると、search() は先行する行のなかから該当する行の行番号を返します。検索に失敗した場合はゼロを返します。

続いて if 文が、挿入ポイントにコピーする文字 (存在する場合) を判断します。該当する行が見つからなかった場合には、target_line_num にゼロが割り当てられるはずなので、最初の return 文が実行されると、「何も挿入しない」ことを示す空の文字列が返されます。

一方、該当する行が特定された場合には、2 番目の return 文が代わりに実行されます。この文は最初にその該当する行のコピーを現行のエディター・バッファーから取得します。

return matchstr(getline(target_line_num), target_pattern)

次に、前の search() 呼び出しで一致した 1 文字からなる文字列を見つけて返します。

return matchstr(getline(target_line_num), target_pattern)

この新しい縦方向コピーの動作を LookUpwards() 内に配置すれば、後は imap を使って挿入モードの標準 CTRL-Y コマンドを上書きすればよいだけとなります。

imap <silent> <C-Y> <C-R><C-R>=LookUpwards()<CR>

注意する点として、以前の imap の例ではいずれも <C-R>= を使って Vim スクリプトの関数を呼び出していましたが、この例では代わりに <C-R><C-R>= を使用します。単一の CTRL-R は次に続く式の結果を、それが直接入力されたかのように挿入します。つまり、結果に含まれる特殊文字はその特殊な意味と動作をそのまま維持するということです。一方、CTRL-R が 2 回繰り返された形では、結果は処理されずに、そのままのテキストとして挿入されます。

この例での目的は、カーソルの前方にあるテキストをそのままコピーすることなので、テキストを文字通りに挿入したほうが適切です。キー・マッピングで <C-R>= を使用したとすると、前の行からリテラル・エスケープ文字をコピーすると、エスケープ文字を入力したのと同じことになるため、エディターが一瞬にして挿入モードから切り替わってしまいます。

Vim の組み込み関数について学ぶには

これまでのそれぞれの例からおわかりのように、Vim スクリプトが持つ能力のほとんどは、200 を超えるその広範な組み込み関数によるものです。これらの組み込み関数について学ぶには、以下を入力してください。

:help functions

または、以下を入力して (さらに有益な) カテゴリー別のリストにアクセスすることもできます。

:help function-list


これからの展望

Vim スクリプトは、Vim エディターを新たに作り直して拡張するためのメカニズムです。スクリプトを組むことによって、新しいツールを作成したり (問題のある単語を強調表示するツールなど)、共通タスクを単純化したり (タブ間隔の変更や日時情報の挿入、構文強調表示の切り替えなど)、さらには既存のエディター機能を完全に再設計することさえできます (CTRL-Y の「前の行のコピー」動作を拡張するなど)。

多くの人々にとって新しい言語を習得する一番の近道は、サンプルを見て学ぶことです。そのために Vim Tips ウィキでは大量のサンプル Vim スクリプトを提供しています (その大多数は、それ自体だけでも役に立つツールです)。さらに広範な Vim スクリプトの例を調べるには、Vim スクリプト・アーカイブに収録されている 2000 を超えるプロジェクトを参照してください。以下の「参考文献」セクションに、この両方のリンクが記載されています。

Perl、Python、Ruby、PHP、Lua、Awk、Tcl、あるいはいずれかのシェル言語を使い慣れている方は、Vim スクリプトはその一般的な手法と概念という点で馴染み深い一方で、構文の特異性という点では苛立つほどに勝手が違うと思うことでしょう。その食い違いを乗り越えて Vim スクリプトをマスターするには、時間をかけてこの言語を試し、詳しく調べ、いろいろと使ってみる必要があります。そのためにも、現状での Vim の機能に関して最も不満に思う部分について、それを改善するスクリプトを自分で作成できるかどうかを試してみてください。

この記事で説明したのは、Vim スクリプトの基本的な変数、値、式、関数についてのみです。このほんのわずかな構成要素で作成できる「より良いソリューション」の範囲は、当然かなり限られます。そこで、今後の記事ではより高度な Vim スクリプトのツールおよび手法として、データ構造、フロー制御、ユーザー定義コマンド、イベント駆動型スクリプト、Vim モジュールの作成、そして他のスクリプト言語を使った Vim の拡張について取り上げる予定です。具体的に言うと、連載の次回の記事では Vim スクリプトのユーザー定義関数の機能に注目し、ユーザー定義関数を使用して Vim のエクスペリエンスを改善するさまざまな方法を調べます。


参考文献

学ぶために

製品や技術を入手するために

議論するために

  • My developerWorks コミュニティーに加わってください。自分個人のプロファイルとカスタム・ホーム・ページを作成して、自分の興味に合わせて developerWorks をカスタマイズしたり、他の developerWorks ユーザーと対話したりすることができます。

著者について

author photo - damian conway

Damian Conway は、オーストラリア Monash University でコンピューター・サイエンスの Adjunct Associate Professor を務める傍ら、国際的 IT トレーニング会社、Thoughtstream の CEO でもあります。彼が日常的に vi を使うようになって 4 半世紀が過ぎた今、彼がこの依存症を克服する見込みはなさそうです。

不正使用の報告のヘルプ

不正使用の報告

ありがとうございます。 このエントリーは、モデレーターの注目フラグが設定されました。


不正使用の報告のヘルプ

不正使用の報告

不正使用の報告の送信に失敗しました。


developerWorks: サイン・イン


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


お客様が developerWorks に初めてサインインすると、プロフィールが作成されます。 プロフィールで選択した情報は公開されますが、いつでもその情報を編集できます。 お客様の姓名(非表示設定にしていない限り)とディスプレイ・ネームは、投稿するコンテンツと一緒に表示されます。

表示名をお選びください

developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

(半角英数字で3文字以上31文字以下にする必要があります)


「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 利用条件

 


この記事を評価する

コメント

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux
ArticleID=397773
ArticleTitle=Vim エディターのスクリプトの作成: 第 1 回 変数、値、式
publish-date=05062009
author1-email=damian@conway.org
author1-email-cc=