Skip to content

編集関数の作成

Koichi Murase edited this page Apr 17, 2024 · 4 revisions

※ここに記述されている仕様は予告なく変更される可能性があります。

1. 編集関数の基本

ネイティブな編集関数 (ble/widget) の定義方法をここでは説明する (他に簡易な "ユーザ編集関数" の定義方法もあるが、それについてはいつか書く)。

1.1 編集関数の定義方法と名称

編集関数 MyWidgetble/widget/MyWidget という名前の関数として定義する。このとき ble-bind -f kspecs MyWidget としてキー列を束縛することができる。編集関数の名称 MyWidget は、衝突を避けるために 名前空間/名前 の形式であることが望ましい。名前空間 は、例えば自作のパッケージの名前、または自分の名前などを用いるのが良い。MyWidget には . を含めない。. を含む名称の関数は、隠し編集関数または編集関数の補助関数として用いる。

1.2 編集関数の終了ステータス

編集関数の終了ステータスは以下の通り。

  • 0 を返したとき、要求された操作が無事に実行されたことを表す。
  • 27 を返したとき、操作がユーザによってキャンセルされたことを表す。例えば、追加で受け取ったユーザの入力によりキャンセルが起こりうる。
  • 148 を返したとき、非同期の入力を受け取るために、一旦編集関数を抜けたことを表す。この時、何らかの hook に続きの動作が登録されていることが期待される。
  • 125 を返したとき、編集関数内部で更にディスパッチを行ったが、対応するハンドラが見つからなかったことを表す。これは、__defchar__ が束縛された編集関数において意味を持つ。
  • 1 を返したとき、(その他の原因で) 要求された操作が実行できなかったことを表す。

1.3 編集関数内部で参照できる変数

これらの変数は特に記述がない限りは読み取り専用である (内部コードで変更するために敢えて -r 属性はつけていないが、直接変更することは避ける)。

変数 説明
WIDGET キー入力により呼び出された編集関数の呼び出しに用いられたコマンド
配列 KEYS WIDGET 呼び出しを引き起こしたキー(整数値)の列
KEYMAP WIDGET 呼び出し時のキーマップ
_ble_decode_keymap 現在のキーマップ。編集関数内部での処理でキーマップが切り替わりうることに注意する。
_ble_edit_str 現在の編集内容
_ble_edit_ind 現在のカーソルの位置
_ble_edit_mark マーク(選択の端点)
_ble_edit_mark_active 選択の状態
_ble_edit_overwrite_mode 上書きモード

1.4 カーソルの移動

現在のカーソル位置 _ble_edit_ind およびマークの位置 _ble_edit_mark は直接変更可能である (#D0671 従来は関数 ble/widget/.goto-char index によって変更したがこの関数は廃止され直接代入することとなった)。この2つの変数の値は 0 以上 ${#_ble_edit_str[*]} 以下でなければならない。

1.5 編集内容の変更

編集内容の変更範囲 (dirty range) を追跡する必要があるので、編集内容 _ble_edit_str は直接変更してはならない。必ず、以下の何れかの関数を呼び出すことによって変更を行う。更に、変更に応じて _ble_edit_mark / _ble_edit_ind も範囲内に収まるように明示的に更新する必要がある。

  • ble-edit/content/replace beg end [ins [reason]]
  • ble-edit/content/reset [ins [reason]]
  • ble-edit/content/reset-and-check-dirty [ins [reason]]

1.6 ユーザの入力を受け取りたい時

標準入力から直接読み取ってはならない。ble.sh の中で既に標準入力から読み取って未だ処理されていない文字・キーがあるかもしれないからである。ユーザからの入力を読み取るためには、非同期の方法を用いる。

function ble/widget/MyWidget {

  ... # 処理1

  _ble_decode_char__hook=ble/widget/MyWidget.hook1 # 文字を受け取るとき
  return 148
}
function ble/widget/MyWidget.hook1 {
  local char=$1 # 受け取った文字 (整数値)

  ... # 処理2(つづきの処理)

  _ble_decode_key__hook=ble/widget/MyWidget.hook2 # キーを受け取るとき
  return 148
}
function ble/widget/MyWidget.hook2 {
  local key=$1 # 受け取ったキー (整数値)

  ... # 処理3(つづきの処理)
}

2. vi/vim-mode における編集関数

vi/vim-mode で使用する編集関数 (vi_cmap 以外) に対しては更に複雑な要求がある。

2.1 ARG, FLAG, REG

引数 (各コマンドの前に入力できる整数)、レジスタ ("x で指定される)、オペレータ (vi_omap に入るときに設定される) を参照するときは、ble/keymap:vi/get-arg 関数を呼び出す。例えば、移動コマンド (motion), 編集コマンド (edit) などを実装するときがこれに該当する。

local ARG FLAG REG; ble/keymap:vi/get-arg ARG既定値

関数を呼び出すと ARG には引数が指定される。引数が指定されなかった時 ARG には、ble/keymap:vi/get-arg に渡した第一引数 ARG既定値 が設定される。FLAG にはオペレータ名 (後述の MyOperator に該当) が設定される。REG にはレジスタの番号 (文字コード) が設定される。ble/keymap:vi/get-arg が呼び出されると、その時の引数・オペレータ・レジスタは消去され、次回以降のコマンドでは使われなくなる。

2.2 マーク `[`] (前回の編集範囲) を正しく設定するために

実際の編集を伴う編集関数では、ble/keymap:vi/mark/set-previous-edit-area beg end を呼び出して編集範囲を設定する。但し beg は編集範囲の開始位置で end は編集範囲の終端位置 (最後の文字の位置ではなく、その次の境界の位置) である。

複雑な編集について編集範囲が容易に決定できない場合には、ble/keymap:vi/mark/set-previous-edit-area を自分で呼び出す代わりに、 編集が行われるコード全体を ble/keymap:vi/mark/start-edit-areable/keymap:vi/mark/end-edit-area で囲めば良い。_ble_edit_str に対する変更を追跡して、自動的に編集範囲が算出される。もし、囲まれたコードの中で更に別の編集関数を呼び出す場合には、ローカル変数 _ble_keymap_vi_mark_suppress_edit に非空白の値を設定して、内部での編集範囲算出と干渉しないようにする必要がある。

ble/keymap:vi/mark/start-edit-area
local _ble_keymap_vi_mark_suppress_edit=1

... # 編集操作

unset -v _ble_keymap_vi_mark_suppress_edit
ble/keymap:vi/mark/end-edit-area

ble/keymap:vi/mark/{start,end}-edit-area に囲まれた部分で、_ble_edit_str に対して実際には変更が発生しないが、編集の範囲として追加したい領域があるときは ble/keymap:vi/mark/commit-edit-area beg end を呼び出す。

2.3 繰り返しコマンド . による繰り返しを正しく設定するために

繰り返しの対象となるコマンドは2種類ある。一つは編集を伴うコマンドで、もう一つは挿入モードに入るコマンドである。これらのコマンドによる操作が成功したとき、繰り返しを登録する。基本的には ble/keymap:vi/repeat/record を呼び出せば良い。何処に ble/keymap:vi/repeat/record の呼び出しを記述するかについては、以下のことを意識すると簡単である。

  1. 編集を伴うコマンド: ble/keymap:vi/mark/set-previous-edit-area または ble/keymap:vi/mark/end-edit-area を呼び出すときは、そこで1単位の編集が完了するということを表すので、大抵、対応して ble/keymap:vi/repeat/record も呼び出すと考えると良い。

  2. 挿入モードに入るコマンド: ble/widget/vi_nmap/.insert-mode を呼び出した後は、場合に応じて ble/keymap:vi/repeat/record または ble/keymap:vi/repeat/clear-insert を呼び出す。ble/keymap:vi/repeat/record は今回の挿入モード突入に至ったコマンドを . で再現したい時に使う。この関数は、呼び出した時のキーマップを参照するので、ble/keymap:vi/vi_nmap/.insert-mode よりも後で呼び出す必要があることに注意する。ble/keymap:vi/repeat/clear-insert は、挿入モードの途中で挿入操作をクリアする時などに使う。例えば、vi_imap<C-o>, 移動操作, コマンド実行などで使われている。

但しオペレータの中では、外側で自動的に ble/keymap:vi/repeat/record が呼び出されるので、これらは自分で呼び出さない。例えば operator:cble/keymap:vi/vi_nmap/.insert-mode を呼び出すが、自身では ble/keymap:vi/repeat/record を呼び出さない。但し、オペレータによる非同期読み取りによる継続の場合は、ble/keymap:vi/mark/set-previous-edit の時と同様に自分で呼び出す必要がある。

編集を行って更に挿入モードに入るコマンドの場合であっても、1回だけ ble/keymap:vi/repeat/record を呼び出せば問題ない。その時でもやはり ble/keymap:vi/vi_nmap/.insert-mode よりも後で ble/keymap:vi/repeat/record を呼び出すようにすること。

KEYMAP, KEYS, WIDGET, ARG, FLAG, REG の値は記録され、繰り返しの対象となる編集関数を呼び出すときに再現される。繰り返しの対象となる編集関数が、それ以外の "状態" に依存して振る舞いが変化し、その状態に依存した振る舞いを繰り返し再現したいときは、それを明示的に記録・再生する必要がある。これは ble/keymap:vi/repeat/record の呼び出しの後に、配列 _ble_keymap_vi_repeat (または挿入モードに突入したときは配列 _ble_keymap_vi_irepeat_repeat) を修正することによって行う。■各配列の詳細は省略■

2.5 vi_nmap からのコマンド呼び出し後に正しく状態遷移するために

移動コマンド及び vi_nmap / vi_omap で実行される編集関数を自分で定義する場合には、最終的に ble/keymap:vi/adjust-command-mode が呼び出されるようにしなければならない。ble/widget/vi-command/* 及び ble/widget/vi_nmap/* の関数を呼び出したときは、内部で最終的に ble/keymap:vi/adjust-command-mode が呼び出されるので自分で呼び出す必要がない。これらの既存の関数に頼らない場合は、最後に自分で ble/keymap:vi/adjust-command-mode を呼び出す必要がある。

ble/keymap:vi/adjust-command-mode は以下の状態遷移を担う。

  • 矩形ビジュアルモードで $ を押して行末まで選択している状態で、他の移動コマンドを実行した後に、行末までの選択から通常の選択に戻す。
  • オペレータ待機モードで、移動コマンドやオペレータなどを作用した後に、オペレータ待機モードを解除する。
  • /, ? で検索した範囲がハイライトされた状態で、他のコマンド (移動コマンドや他のコマンド) を実行した後に、選択を解除する。
  • 挿入モードから C-o で入った単一コマンドモードで、コマンドを一つ実行した後に、単一コマンドモードを解除する。

2.4 移動コマンド (motion) の定義方法

移動コマンドの性質 exclusive / inclusive / linewise に応じて以下の関数を呼び出せば良い。`[`] 編集範囲や . 繰り返しは、以下の関数の内部で自動的に設定されるので自分で処理する必要はない (ただし、非同期に呼び出す場合には繰り返しについて特別に処理しなければならないことに注意する)。

  • ble/widget/vi-command/exclusive-goto.impl index flag reg opts
  • ble/widget/vi-command/inclusive-goto.impl index flag reg opts
  • ble/widget/vi-command/linewise-goto.impl index flag reg opts

上のコマンドの引数 index には行き先の位置を渡す。flag には (omap にいる時) オペレータ名を渡す。reg にはレジスタが設定されているときその番号を渡す。opts については、keymap/vi.sh 中の当該関数の説明を参照のこと。以下に実装の雛形を示す。

function ble/widget/MyExclusiveMotion {
  local ARG FLAG REG; ble/keymap:vi/get-arg 1
  local index=行き先を計算
  ble/widget/vi-command/exclusive-goto.impl "$index" "$FLAG" "$REG" nobell
}
function ble/widget/MyInclusiveMotion {
  local ARG FLAG REG; ble/keymap:vi/get-arg 1
  local index=行き先を計算
  ble/widget/vi-command/inclusive-goto.impl "$index" "$FLAG" "$REG" nobell
}
function ble/widget/MyLinewiseMotion {
  local ARG FLAG REG; ble/keymap:vi/get-arg 1
  local index=行き先を計算
  ble/widget/vi-command/linewise-goto.impl "$index" "$FLAG" "$REG"
}

互換性: ble/widget/vi-command/{ex,in}clusive-goto.impl の第4引数は、ble-0.2 以前は整数値を指定し、それが0以外の時にエラー発生時のベルを抑制する。ble-0.3 以降は : 区切りのオプションで、例えば nobell を指定するとエラー発生時のベルを抑制する。

2.4.1 ジャンプコマンド

ジャンプコマンドを実装するときは、マーク `` (ジャンプ前の位置) を設定する必要がある。実際に移動を行う前に ble/keymap:vi/mark/set-jump を呼び出す。ただし、オペレータを伴うとき ($FLAG が空文字列でないとき) はジャンプにならないので、ble/keymap:vi/mark/set-jump は呼び出さない。他は通常の移動コマンドと同じ。以下のような形式になる。

function ble/widget/MyJump {
  local ARG FLAG REG; ble/keymap:vi/get-arg 1
  [[ $FLAG ]] || ble/keymap:vi/mark/set-jump

  # ... 処理
}

2.6 (内部仕様)

  • __before_widget__ の内部で ble/keymap:vi/repeat/record は呼び出さない。ble/keymap:vi/repeat/record は、その編集が起こった原因となるコマンドを変数 WIDGET によって特定する。しかし、__before_widget__WIDGET 経由で呼び出されないので、誤った復元をしてしまうことになる。

  • WIDGET を編集関数本体の中で変更してはならない。これは同様に . で繰り返すための情報を破壊してしまうことになるからである。__before_widget__ 内部でコマンドを変更する目的で書き換えることは可能である。

3. vi/vim-mode のオペレータ

オペレータ MyOperatorble/keymap:vi/operator:MyOperator という名前の関数として定義できる。MyOperator には :, ., / を含まない関数名として有効な文字列である。実際に使うには以下のようにキーマップに登録する。

ble-bind -m vi_nmap -f kspecs 'vi-command/operator MyOperator'
ble-bind -m vi_omap -f kspecs 'vi-command/operator MyOperator'
ble-bind -m vi_xmap -f kspecs 'vi-command/operator MyOperator'

オペレータ関数は以下の引数を受け取る。

function ble/keymap:vi/operator:MyOperator {
  local a=$1 b=$2 context=$3 count=$4 reg=$5

  ... # 処理

}
  • a, b は作用対象の範囲を表す。context=line の時は、それぞれ行頭または行末にあることが保証される。末端の改行も範囲に含まれる。
  • context は呼び出しの文脈を表す。char, line, block の何れかの値である。
  • count はオペレータに渡された引数を表す。オペレータに対する引数は vi_xmap において指定されうる。
  • reg"x によって指定されたレジスタの番号である。

オペレータ関数内から以下の変数を参照できる。

変数名 説明
beg, end 作用対象の範囲を表す。a, b と同じ値である。また、この変数の値を変更することによって、オペレータによって範囲の拡大が実行されたことを呼び出し元に通知する。多くの場合、呼び出し後の beg をカーソルの移動先とする。
ble_keymap_vi_operator_index 空文字列。処理後のカーソルの移動先を設定する。空文字列のまま返すとカーソルは beg へ移動する。
ble_keymap_vi_mark_active オペレータ呼び出し直前の _ble_edit_mark_active の値を保持する (vi_xmap からオペレータを呼び出すとき _ble_edit_mark_active は直前に解除されることに注意する)。
ble_keymap_vi_opmode オペレータ呼び出しの文脈を保持する。vi_line, vi_block, vi_char, 空文字列の何れかの値になる。
配列 sub_ranges context=block の時に定義される。矩形範囲を構成する各行の情報を持つ。
sub_x1, sub_x2 context=block の時にに定義される。矩形範囲の左端と右端の列を持つ。

終了ステータス 0 は正常終了を表す。終了ステータス 1 はオペレータの操作が何らかの理由で失敗したことを表す。終了ステータス 148 を返したとき、続く既定動作は省略される。これは非同期に続きの処理を行いたいときに使う。省略される既定動作には以下が含まれる。非同期の続きの処理ではこれらを手動で呼び出す必要がある。

  • ble/keymap:vi/mark/set-previous-edit-area (`[`] の記録)
  • ble/keymap:vi/repeat/record (. の記録)
  • ble/keymap:vi/adjust-command-mode (様々な自動の状態遷移)

それ以外の終了ステータスはオペレータの操作が、事前にオペレータの設計者が定義した理由で失敗したことを表す。

3.1 オペレータとマーク `[`]

オペレータについては外側で自動的に編集範囲が検知されるので、自身で ble/keymap:vi/mark/{set-previous,end}-edit-area を呼び出す必要は基本的にない。但し、文字列内容に変更は起こらないが範囲を設定したい場合には ble/keymap:vi/commit-edit-area を明示的に呼び出す。例えば ble/keymap:vi/operator:y がこれにより編集範囲を設定する。

3.2 オペレータと繰り返しコマンド .

既定でオペレータによる操作は繰り返しコマンドによる繰り返しの対象となる。もし、そのオペレータによる操作を繰り返しの対象として登録したくないときは、空の関数 ble/keymap:vi/operator:MyOperator.record を定義する。

function ble/keymap:vi/operator:MyOperator.record { return 0; }
Clone this wiki locally