TOP

■Schemeリファレンス|OEdit独自関数■

【OEdit版Scheme|予備知識】

・文字コード/改行コードに関する扱い。(↓図参照)
 ・OEditは、ファイルを読み込む際に、文字/改行コード変換を行い、
  内部テキストを『Unicode+LF(\n)』に統一して保持する。
  書き出す際は、現在の文字/改行コード情報が参照され、それに基いて変換出力される。
 ・Schemeは、文字/改行コードを『Unicode+LF(\n)』として扱う。
  @テキスト操作を行う際。
   →『Unicode+LF(\n)』ルールに従っていれば、後はOEditが良きに計らう。
    (テキストから取得する改行は『\n』になるし、テキストへ『\n』を書き込めば改行になる)
   →『\n』が改行として扱われる都合上(?)、『\r』はテキスト上のどこにもマッチしない。
   →テキストへの書き込み時には、『\r』『\r\n』も『1つの改行』として通るようだ。(救済措置?)
   →↓2関数は例外的に、返り値に現在の改行コードを反映させる。(文字コードは反映しない。常にUnicode換算)
    editor-get-selected-string-bytes
    editor-get-selected-string-chars
  Aファイル操作を行う際。
   →原則として、バイナリとして読み書きされる。(BOMも改行コードも文字コードも、単にバイトとして同列に扱われる)
   →ただ、利便の為、読み込み時には、一部文字コード変換が行われる。(その対象は↓2つ)
    この例外的文字コード変換が行われるのが、OEdit6系との大きな違いである。
    ・Unicode→Unicode(no signature)に文字コード変換される。
     (=単に、先頭2バイトのBOM(FF FE)が破棄されるだけ)
    ・SJIS→Unicode(no signature)に文字コード変換される。
    ●EUC/JIS/UTF-8については、自動変換の対象とならず、生のままバイナリとして読み込まれる。
   →ちなみに、書き込み時には、文字コード変換は一切行われない。
    『メモリイメージそのまま』(=バイナリ)出力されるので、BOM無しのUnicode
    即ち、Unicode(no signature)で、書き出される。
    ファイル先頭に、明示的にU+FEFF(UTF-16LEのBOM)を書き込む事で、BOM有Unicodeでのファイル出力も可能。
   →OEditのUnicodeはUTF-16LEなので、書き出したデータは、『下位バイト→上位バイト』な並びとなる事に注意。
    例.文字列『ABCあい』をファイル出力した場合。
     →ファイル上『41 00 42 00 43 00 42 30 44 30』
     →『41 00』が半角『A』(U+0041)
     →『44 30』が全角『い』(U+3044)
  Bダイアログへの出力。
   →テキスト操作を行う場合と同じ扱い。

・位置に関する表現。
 ▼OEdit対人インタフェースと異なる点▼
  ・行/桁を0からカウントする。(OEdit対人インタフェースは、1からカウントする)
  ・1文字を1桁としてカウントする。
   →半角も全角もタブ、例外なく『1文字=1桁』にてカウントする。
   →この仕様は、エディタテキストから文字列取得した際に、
    『その文字インデックスと桁位置が1対1で可逆対応する』
    という、恩恵をもたらす。
    (例えば、半角/全角が混在しているテキスト中でも、現在位置の右隣/左隣の文字を簡単に取得可能)
   →反面、取得した座標(row, col)から算出した座標(row+1, col)が、次行の真下桁位置を意味するとは限らない
    という弊害ももたらす。(例えば、↓局面とか)
     『注目している行が半角10文字で構成され、次行が全角10文字で構成されてる場合』
   →ただ、OEditでは、この縦横移動に専用関数が用意されており、
    またOEdit7.0.5.1〜では『論理上の桁⇔画面表示上の桁』変換を行うcaret-col系関数も用意されたので、
    それらを併用する事で、仕様上の不利の大部分は吸収可能かと思われる。
▼5桁目にカーソルを置いた例▼
●文字インデックス的には、常に6番目を指すが、表示上の桁位置は全/半角に依存する。
あいうえおKL  ◆(0 5)
あいうえIJKL  ◆(1 5)
あいうGHIJKL  ◆(2 5)
あいEFGIIJKL  ◆(3 5)
あCDEFGHIJKL  ◆(4 5)
ABCDEFGHIJKL  ◆(5 5)
▼表示上の4桁目にカーソルを置いた例▼
●表示上の桁位置は揃っているが、各々別の桁位置となっている。
>   EFG       ◆(0 1)(>はタブ記号)
A>  EFG       ◆(1 2)
AB> EFG       ◆(2 3)
ABC>EFG       ◆(3 4)
ABCDEFG       ◆(4 4)
 ▼OEdit対人インタフェースと同じ点▼   ・行は、論理行単位でカウントする。(折り返し表示等による外見上の変化には惑わされない) ・想定外の位置指定をエラーとしない。(良きに計らう)  位置指定については、↓正規化が行われた上で正常処理される。  @編集中テキストの範囲外位置を指定した場合。   1)負の桁     →行頭。   2)行末を超える桁 →行末。(=改行やEOFに相当する位置)   3)負の行     →先頭行。   4)最終行を超える行→末尾行。 ・選択範囲に関する表現。  ・選択範囲は、範囲始点座標と範囲終点座標の計2つの位置で表される。  ・但し、範囲終点に位置する文字は範囲に含まれない事に注意する必要。   (正確には『範囲終点』と言うより『範囲右下点』。右から囲う場合とか)   例えば『ABCD』テキストに対して『(開始行0,開始桁0)〜(終了行0,終了桁3)』範囲選択を行うと、   範囲は『ABC』となる。(0,3)に位置する文字『D』は範囲には含まれない。   →これは、カーソルが文字の左側に位置する事に起因する仕様かと思われます。    実際のOEdit上のビジュアルと照らし合わせると、違和感なく覚えられそうです。   →ついでに言えば、上記例で『(開始行0,開始桁0)〜(終了行0,終了桁0)』範囲選択した場合は、    『選択範囲が無い』と見なされます。   →例外的に『複数行に対する選択で且つ0巾である矩形選択』については、    実質何も囲っていないにも関わらず、『選択範囲が有る』と見なされます。 ・OEdit独自関数には、editor系とapp系の2系統が存在します。(それぞれの位置付けは↓な感じ)  editor系…現在開かれているテキストに対する操作。       テキストが未だ開かれていない状態(例えば『oedit.scm』中とか)で実行すると#fが返ります。       『#t…成功、#f…失敗』型の返り値を取るものは、その様な特殊なケースでしか#fが返り得ないので、       実用上判別処理は省いて構いません。  app系  …OEdit全般に対する操作。       テキストが未だ開かれていない状態でも、正常動作するものが殆どです。


◆エディタ操作|ファイル名取得◆

editor-get-filename
【書式】 (editor-get-filename)
【引数】 なし
【返り値】現在編集中のファイル名文字列。(フルパス)
     ""…現在編集中のファイルが『無題ドキュメント』の場合。
     #f…取得失敗。

●返り値が#fでない事を一応チェックした方が無難です。
 例えば、on-close-fileイベント捕捉時に(特定のタイミングで)#fが返り得ます。
現在編集中のファイル名(フルパス)を文字列で取得する。
;■現在編集中のファイル名(フルパス)を表示するマクロ。(実験用)■
(define fn (editor-get-filename))  ;◆現在編集中のテキストのファイルパス
(if (and fn (string>? fn ""))      ;◆それが、#fでなく且つ""でもないなら、
  (app-msg-box "ファイル名 …" fn)
  (app-msg-box "ファイル名 … 未定")
)

◆エディタ操作|座標制御◆

editor-get-cur-row
【書式】 (editor-get-cur-row)
【引数】 なし
【返り値】カーソルの現在行。(0〜)
現在のカーソル位置(行)を取得する。
・存在し得ない値を取得する事は決してない。
editor-get-cur-col
【書式】 (editor-get-cur-col)
【引数】 なし
【返り値】カーソルの現在桁。(0〜)
現在のカーソル位置(桁)を取得する。
・存在し得ない値を取得する事は決してない。
editor-set-row-col
【書式】 (editor-set-row-col row col)
【引数】 row…行、col…桁
【返り値】#t…成功、#f…失敗。
指定した位置にカーソルをセットする。
・カーソルセットの際に、(元からあった)範囲は解除される。
editor-get-caret-col
【書式】 (editor-get-caret-col row col)
【引数】 row…行、col…(標準座標換算での)桁
【返り値】(表示座標換算での)桁。(0〜)
『標準座標系→表示座標系』に換算した桁位置を返す。
・全/半角を区別し、タブストップも考慮に入れた、真に画面表示上の桁位置を取得する。
・存在し得ない値を取得する事は決してない。(OEdit7.0.5.3〜)
;■現カーソル位置を『標準座標系』『表示座標系』の2種で表示するハンドラ。(実験用)■
(app-set-event-handler "on-cursor-moved" (lambda ()
  (define row       (editor-get-cur-row))
  (define col       (editor-get-cur-col))
  (define caret-col (editor-get-caret-col row col))
  (define c (editor-get-row-col-char row col))
  (define cs (cond
    ((eof-object? c)  "[EOF]")
    ((eqv? c #\tab)     "\\t")
    ((eqv? c #\newline) "\\n")
    (else (string c))
  ))
  (app-status-bar-msg cs "標準座標系(" row col ")、表示座標系(" row caret-col ")")
))
editor-get-col-from-caret-col
【書式】 (editor-get-col-from-caret-col row caret-col)
【引数】 row…行、caret-col…(表示座標換算での)桁
【返り値】(標準座標換算での)桁。(0〜)
『表示座標系→標準座標系』に換算した桁位置を返す。
・全角内部やタブ巾内部の中途桁位置を指定した場合は、右隣文字の桁位置を返す。(=切り上げ扱い)
 →例えば『あいうえ』テキストに対して『い』の内部桁(caret-col=3)を指定すると、
  切り上げて『う』の開始桁位置(caret-col=4)に換算され処理される。
 →OEdit6系では、中途桁は切り下げ扱いだったので、その点には留意する必要がある。
・存在し得ない値を取得する事は決してない。
;■表示座標系ライブラリっぽいもの■
(define (rc-get-cur-col)
  (editor-get-caret-col (editor-get-cur-row) (editor-get-cur-col))
)
(define (rc-set-row-col row caret-col)
  (editor-set-row-col row (editor-get-col-from-caret-col row caret-col))
)
(define (rc-get-row-col-char row caret-col)
  (editor-get-row-col-char row (editor-get-col-from-caret-col row caret-col))
)
(define (rc-get-selected-area)
  (define area (editor-get-selected-area))
  (and area (begin
    (define row1 (list-ref area 0))
    (define col1 (list-ref area 1))
    (define row2 (list-ref area 2))
    (define col2 (list-ref area 3))
    (list row1 (editor-get-caret-col row1 col1) row2 (editor-get-caret-col row2 col2))
  ))
)
(define (rc-set-select-area row1 caret-col1 row2 caret-col2)
  (editor-set-select-area
    row1
    (editor-get-col-from-caret-col row1 caret-col1)
    row2
    (editor-get-col-from-caret-col row2 caret-col2)
  )
)

◆エディタ操作|範囲制御◆

editor-get-selected-area
【書式】 (editor-get-selected-area)
【引数】 なし
【返り値】選択範囲リスト…(row1 col1 row2 col2)形
     #f      …選択範囲なし
選択範囲を取得する。(範囲の座標情報を取得するのであって、選択範囲の文字列を取得するのではない)
・取得された(row1 col1 row2 col2)値の並びには、一定のルールが存在する。
 →そのルールは、『範囲情報が囲みの向きに左右されない様にする』為のもので、
  経験上、次の様なものと推測される。
  @(row1 col1)(row2 col2)は、選択範囲の始点又は終点を表す座標である。
  A始点/終点の内、早い行成分を持つ方の座標が(row1 col1)となる。遅い方は(row2 col2)となる。
  B行成分が双方同じ場合は、桁成分の早い方が(row1 col1)となる。
 →これらのルールにより、次の事が導かれる。
  ・(row1 col1)位置は、必ずしも始点ではない。
   →例えば、『左上←右下』へ囲う場合は、終点が(row1 col1)となる。
  ・矩形範囲の場合は、必ずしも、始点/終点が左上又は右下とならない。
   →例えば、『左下→右上』『左下←右上』方向に囲う場合は、始点/終点は左下又は右上となる。
・選択範囲が矩形か否か(の情報)は取得しない。
 →矩形か否かを判別したいなら、editor-box-select?関数を用いる必要。
・矩形選択の場合は、実際には存在し得ない座標を取得し得る。
 →例えば、空行にカーソルを置いて、Ctrl+マウスドラッグにて右方向へ囲うと、
  範囲終端として、行末を超えた桁値を取得可能。
;▼選択範囲(とカーソル位置)に関する各種情報を取得するマクロ(実験用)▼
(app-set-key "F12" (lambda ()
  (define cur-pos (list (editor-get-cur-row) (editor-get-cur-col)))
  (define area  (editor-get-selected-area))
  (define exist (if area "範囲(有)" "範囲(無)"))
  (define shape (if (editor-box-select?) "矩形" "通常"))
  (display (string-append shape exist "…"))(write (editor-get-selected-string))(newline)
  (display "範囲…")(write area)(newline)
  (display "カーソル位置…")(write cur-pos)
))
editor-set-select-area
【書式】 (editor-set-select-area row1 col1 row2 col2)
【引数】 row1…始点(行)、col1…始点(桁)、row2…終点(行)、col2…終点(桁)
【返り値】#t…成功、#f…失敗。
選択範囲を設定する。(=指定した範囲を囲う)
・『矩形選択』モード時に実行すると、矩形範囲となる。(その他の場合は、通常範囲となる)
 →『矩形選択』モードへは、editor-set-box-select関数にて移行可能。
・範囲設定後は、元の選択範囲は失われる。
・範囲設定後は、元のカーソル位置は失われる。
 →範囲選択後のカーソル位置は、(row2,col2)位置となる。
1行0巾範囲を指定した場合は、そこにカーソルをセットしたのと同義。(範囲無し扱い)
 →その際のeditor-get-selected-areaの返り値は、#fとなる。
・矩形選択時は、複数行0巾範囲が許容される。
 →その際のeditor-get-selected-areaの返り値は、(row1 col row2 col)形となる。
 →矩形選択時でも、1行0巾範囲は許容されない。(=範囲無し扱い)
;▼setした範囲とgetした範囲を比較するマクロ(実験用)▼
(app-set-key "F12" (let ((prev-area "row1 col1 row2 col2")) (lambda ()
  (set! prev-area (app-input-box "範囲を入力して下さい。" prev-area))
  (if prev-area (begin
    (define set-area (read (open-input-string (string-append "(" prev-area ")"))))
    (apply editor-set-select-area set-area)
    (define get-area (editor-get-selected-area))
    (app-status-bar-msg "set…" set-area ", get…" get-area)
  ))
)))
editor-set-box-select
【書式】 (editor-set-box-select)
【引数】 なし
【返り値】#t…成功、#f…失敗。
次に行う範囲選択を矩形選択にする。(=矩形選択モードに入る。OEditのCtrl+B相当?)
・この関数は、選択範囲が無い場合にのみ機能する。(範囲が有る場合は、#fが返って失敗)
・矩形選択モード中は、全ての範囲選択操作が矩形操作となる。
・但し、モードの持続期間は1範囲選択に限られる。(次の範囲選択へは持ち越せない)
 @editor-set-select-area関数へ繋げた場合は、その終了時に効果が切れる。
 Aeditor-forward-char等の4種へ繋げた場合は、範囲を変化させ得るこの系統以外の関数が
  実行された直後に効果が切れる。(例↓)
  →(editor-forward-char 1 #t)を連続させても期限は切れない。
  →(editor-paste-string 〜)すると期限が切れる。(editor-paste-stringは範囲を解除するので)
  →(editor-forward-char 1 #f)した場合も期限が切れる。(editor-forward-charの#f型は範囲を解除するので)
;▼指定した範囲を矩形選択するマクロ(実験用)▼
(app-set-key "F12" (let ((prev-area "row1 col1 row2 col2")) (lambda ()
  (set! prev-area (app-input-box "範囲を入力して下さい。" prev-area))
  (if prev-area (begin
    (define set-area (read (open-input-string (string-append "(" prev-area ")"))))
    (edx-unset-select-area) ;◆範囲を解除。(editor-set-box-selectは、範囲があると機能しないので)
    (editor-set-box-select)(apply editor-set-select-area set-area)
    (app-status-bar-msg "矩形選択…" set-area)
  ))
)))
(define (edx-unset-select-area) ;◆選択範囲の解除。(=範囲を現カーソル位置に縮退させる)
  (define row (editor-get-cur-row))
  (define col (editor-get-cur-col))
  (editor-set-select-area row col row col)
)
editor-box-select?
【書式】 (editor-box-select?)
【引数】 なし
【返り値】#t…矩形範囲が有る場合
     #f…矩形範囲が無い場合。(=通常範囲が有る場合、又は範囲が無い場合)
選択範囲が矩形選択である場合に真を返す。
;▼選択範囲が通常選択である場合に真を返す関数▼
(define (edx-normal-select?)
  (and (editor-get-selected-area) (not (editor-box-select?)))
)

◆エディタ操作|テキスト取得◆

editor-get-selected-string
【書式】 (editor-get-selected-string)
【引数】 なし
【返り値】選択範囲の文字列(改行含む)
選択範囲の文字列を取得する。
・範囲が無い場合は、""が返る。
・改行文字は常に『\n』として取得する。(現在編集中の改行コードは加味されない)
・矩形範囲の場合は、矩形右端が必ず改行文字(\n)となる様に取得される。
 →どうやら範囲右端が改行でない場合は、自動で改行文字(\n)が補填される様です。
 →例外が2つ。
  @0巾矩形の場合は改行文字(\n)が補填されません。(""を取得)
  A1行矩形の場合は改行文字(\n)が補填されません。
editor-get-selected-area項にある例題で色々試してみて下さい。
editor-get-selected-string-chars
【書式】 (editor-get-selected-string-chars)
【引数】 なし
【返り値】選択範囲の文字数(改行文字もカウントする)
選択範囲の文字数を取得する。
・範囲が無い場合は、0が返る。
・改行文字は1〜2文字とカウントする。(現在の改行コード次第)
 CR(\r)   …1文字。
 LF(\n)   …1文字。
 CR+LF(\r\n)…2文字。
・矩形範囲に対する挙動は、editor-get-selected-stringとほぼ同様です。
 (矩形右端が改行文字となる様に補正された文字列に対して文字数を算出します)
 →ただ、補填される改行文字は『現在の改行コード』となります。(『\n』とは限らない)
 →また、0巾矩形の場合も改行文字が補填されます。
editor-get-selected-string-bytes
【書式】 (editor-get-selected-string-bytes)
【引数】 なし
【返り値】選択範囲のバイト数(改行文字もカウントする)
選択範囲のバイト数を取得する。
・editor-get-selected-string-charsをバイト数に換算したものと等価です。(多分)
・現在の改行コードが加味されます。
・現在の文字コードは加味されません。
 →常にUnicodeにてバイト計算。
  (Unicodeは、半角も全角も原則2バイト。稀に拡張分の文字セットが4バイトとなるらしい)
;▼選択範囲の文字列長と文字数とバイト数を比較するマクロ(実験用)▼
(app-set-key "F12" (lambda ()
  (define str-len   (string-length (editor-get-selected-string)))
  (define str-chars (editor-get-selected-string-chars))
  (define str-bytes (editor-get-selected-string-bytes))
  (app-status-bar-msg "文字列長…" str-len "  文字数…" str-chars "  バイト数…" str-bytes)
))
editor-get-all-string
【書式】 (editor-get-all-string)
【引数】 なし
【返り値】エディタ内の全文字列
エディタ内の全文字列を取得する。
・選択範囲の有無に関係なく、全文字列を取得。
 →取得する文字列は、『すべて選択』状態でeditor-get-selected-stringした場合と等価です。
 →なので、行末改行も含めて取得されます
  (後述のeditor-get-row-string関数との違いに注意)
editor-get-row-string
【書式】 (editor-get-row-string row)
【引数】 row…行番号。(0〜)
【返り値】指定した行の文字列(改行含まず)
指定した行の文字列を取得する。(但し、行末の改行は取得しません)
;▼カーソルが位置する行のテキストを取得するマクロ(実験用)▼
;●editor-get-row-string関数にて取得するので、行末改行は含まれない。
(app-set-key "F12" (lambda ()
  (define row (editor-get-cur-row))
  (define s (editor-get-row-string row))
  (app-status-bar-msg (xstring s))
))
(define (xstring x) ;◆オブジェクトの外部表現(文字列)を返す関数
  (define op (open-output-string))
    (write x op) (define ans (get-output-string op))
  (close-output-port op)
  ans
)
editor-get-row-cnt
【書式】 (editor-get-row-cnt)
【引数】 なし
【返り値】現在編集中のテキストの総行数
現在編集中のテキストの総行数を取得する。
・定量的に表現するなら、『テキストの行末改行の個数+1』の値が返ります。
editor-get-row-col-char
【書式】 (editor-get-row-col-char row col)
【引数】 row…行、col…桁
【返り値】指定した位置の文字
     #\newline…改行位置を指定した場合。
     #<eof>   …文末位置を指定した場合。
指定した行/列に位置する文字を取得する。
・全角文字も取得可能。
・行末にある改行コードも取得可能。(改行コードは常に#\newlineとして取得)
・文末にあるEOFコードも取得可能。(EOFコードは、#<eof>オブジェクトとして取得)
 →これはeof-object?関数により判別可能。
・想定外範囲(負の桁とか)もエラーを出さずに処理する。(想定内最外縁値と見なして処理される)
・また、この関数の使用後に、範囲が解けたり、カーソル位置が変化する様な事はありません。
 →範囲があっても、カーソル位置がどこでも、それらに影響を与えずに取得します。
;▼現カーソル位置の文字(&その左右の文字)を取得するマクロ(実験用)▼
(app-set-key "F12" (lambda ()
  (define row (editor-get-cur-row))
  (define col (editor-get-cur-col))
  (define c  (editor-get-row-col-char row col)) ;◆現カーソル位置の文字
  (define cl (editor-get-row-col-char row (- col 1 ))) ;◆左の文字
  (define cr (editor-get-row-col-char row (+ col 1))) ;◆右の文字
  (app-status-bar-msg "位置(" row "," col ")  文字…"
    "左"   (xstring cl)
    "|現" (xstring c )
    "|右" (xstring cr)
  )
))
(define (xstring x) ;◆オブジェクトの外部表現(文字列)を返す関数
  (define op (open-output-string))
    (write x op) (define ans (get-output-string op))
  (close-output-port op)
  ans
)

◆エディタ操作|テキスト編集◆

editor-paste-string
【書式】 (editor-paste-string str)
【引数】 str…文字列
【返り値】#t…成功、#f…失敗。
エディタに文字列を貼り付ける。
・挙動は、原則OEdit対人インタフェースと同じ。
・範囲が無い場合はカーソル位置に挿入貼り付け、範囲が有る場合は範囲を上書き貼り付けする。
・貼り付け処理後に、
 1)範囲選択は、解除される。
 2)カーソル位置は、『貼り付けた文字列末尾の次文字』位置に移る。
・範囲なしで空文字列""を貼り付けた場合は、何も行われない。(=当該行に編集マークも付かない)
・矩形関連。
 ・矩形範囲から取得した文字列をただ単に貼り付けても、矩形貼り付けとはならない。
 ・矩形貼り付けしたいなら、何か文字列を『矩形範囲に対して』貼り付ければ良い。
  →これは、OEdit対人にて『矩形コピー+矩形範囲に対して貼り付け』したのと同等。
   また、『0巾矩形範囲に対して』貼り付ければ、
    OEdit対人による『矩形コピー+貼り付け』と同等になる。
   (若干の差異として、OEdit対人の場合はコピー末尾の改行が貼り付け後カーソル位置に反映される)
  →もし、貼り付け文字列の行数が、矩形範囲の行数を超えていた場合は、超えた分については、
   矩形左端が0巾状態で次行以降にずっと続いているイメージで貼り付け処理される。
   『次行の最終桁<矩形範囲左端桁位置』の場合は、不足分は半角空白で補填される。
   (これは、OEdit対人インタフェースと同じ仕様)
  →ちなみに矩形範囲は、↓の何れの手段によるものであっても構わない。
   ・OEdit対人で手動選択した矩形範囲。
   ・Schemeマクロで生成した矩形範囲。
;▼選択範囲の文字列を、全角⇒半角変換するマクロ(実験用)▼
(app-set-key "F12" (lambda ()
  (define s (editor-get-selected-string))
  (editor-paste-string (string-halfcase s))
))

(define (hash-table type . pairs) ;◆拡張ハッシュテーブル操作ライブラリ|テーブル生成&初期化
  (let ( (h (make-hash-table type)) )
    (for-each (lambda (pair) (hash-table-put! h (car pair) (cdr pair))) pairs)
    h
  )
)
(define char-halfcase (let ;◆全角⇒半角変換(文字版)
  ((h (apply hash-table 'eqv? (map cons
    (string->list (string-append
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      "abcdefghijklmnopqrstuvwxyz"
      "0123456789"))
    (string->list (string-append
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      "abcdefghijklmnopqrstuvwxyz"
      "0123456789"))
  ))))
  (lambda (c)
    (hash-table-get h c c)
  )
))
(define (string-halfcase s) ;◆全角⇒半角変換(文字列版)
  (regexp-replace-all #/./ s (lambda (m)
    (char-halfcase (string-ref (rxmatch-substring m) 0))))
)
editor-delete-selected-string
【書式】 (editor-delete-selected-string)
【引数】 なし
【返り値】#t…成功、#f…失敗。
選択範囲のテキストを削除する。
・範囲なしの場合は、何もしない。(その場合でも、返り値は#tとなる)

◆エディタ操作|検索/置換◆

editor-search-string
editor-search-string-ci
【書式】 (editor-search-string    regex-str)
     (editor-search-string-ci regex-str)
【引数】 regex-str…検索文字列  (必ず、正規表現として解釈される)
【返り値】0… マッチ時
     1…非マッチ時
テキスト全体に対して、正規表現検索を行う。
・OEdit対人の正規表現検索インタフェースを関数化したもの。(ci版の方は、英大小文字を区別しない)
・検索は、現カーソル位置から開始され、最初にマッチしたワードが選択状態となる。
・検索結果は、OEditテキスト上で全て強調表示される。
 →強調表示の後、続けて、OEdit対人インタフェースの『次を検索』『前を検索』へ繋げる事も可能。(OEdit7.0.5.3〜)
・当関数を使用すると、OEdit対人インタフェースの検索/置換窓の状態が上書きされる。
 →例えば、検索/置換窓が↓状態になる。
  ・editor-search-string  を使った後…『大文字・小文字を区別する…○』『正規表現…○』
  ・editor-search-string-ciを使った後…『大文字・小文字を区別する…×』『正規表現…○』
 →検索履歴には影響を与えない風。
;▼テキスト内を指定した正規表現ワードで検索するマクロ(実験用)▼
(app-set-key "F12" (let ((prev-str "")) (lambda ()
  (define reg-str (app-input-box "検索ワードを入力して下さい" prev-str)) ;◆前回入力を初期値として使用。
  (if reg-str (begin
    (set! prev-str reg-str)
    (editor-search-string-ci reg-str)
  ))
)))
editor-replace-selected-string
editor-replace-selected-string-ci
【書式】 (editor-replace-selected-string    regex-str rep)
     (editor-replace-selected-string-ci regex-str rep)
【引数】 regex-str…検索文字列  (必ず、正規表現として解釈される)
     rep      …置換後の文字列
【返り値】置換件数…置換成功時。
     #f   …置換失敗時。(テキストが開かれていない状態等でのみ返り得る。気にしなくて良い)
選択範囲のテキストに対して、正規表現置換を行う。
・OEdit対人の正規表現置換インタフェースを関数化したもの。(ci版の方は、英大小文字を区別しない)
・0件マッチの場合は、0を返す。(#fではない)(その際は、テキストに対して何も行わない)
・選択範囲は、置換後も解除されずに残る。
・検索には正規表現を用いるが、引数として送る際は、文字列形態で与える必要がある。
 →文字列中では『\』を『\\』記述する都合上、正規表現のものと併せてエスケープが
  2重に掛かる形となり、非常にややこしい。
  (また、文字列形態の場合は『"』を『\"』エスケープする必要もある)
  例1.#/\d+/を意図するなら、"\\d+"と書く。
  例2.#/href="top\.htm"/を意図するなら、"href=\"top\\.htm\""と書く。
  例3.#/<\/span>/を意図するなら、"<\\/span>"と書く。
  例4.#/C:\\Scheme\\oedit\.ini/を意図するなら、"C:\\\\Scheme\\\\oedit\\.ini"と書く。
 →ちなみに、幾つかの文字については、もう少し簡潔にも書ける。例えば、
  ・上記例3は"</span>"でも通る。
   →正規表現定数中で『\/』エスケープが必要であったのはSchemeパーサの都合であって、
    正規表現パーサの都合ではない。だから、文字列形態を取るなら、端から『\/』エスケープは不要。
  ・上記例2を"href=\"top.htm\""するのはダメ。
   →正規表現パーサは『.』と誤認しない様、『\.』エスケープを必要とする。だから省略できない。
  ・『\t』『\n』については、『\\t』『\\n』エスケープしてもしなくても良い。
   →単に、"\\t"は#/\t/に解釈され、"\t"は#/>/に解釈される。そういう事。(>は生タブ文字)
 →慣れない内は、↓手順で確認しつつ進めるのが良いかもしれない。(後述のエスケープマクロ利用でも良い)
  @検索表現を、先ず正規表現定数で記述してみる。
  Aそれをregexp->string関数にて文字列に変換する。
  Bそれをwrite関数で表示させてメモる
・置換後の文字列には、後方参照(\1〜\9他)が使えます。
 →『\t』『\n』も使えます。
  検索文字列の場合と同じ理屈で、"\\t"と"\t"の両方が通ります。
 →後方参照に用いる記号を『記号そのもの』として扱いたい場合は、
  例えば『$1』なら、『$』をエスケープして"\\$1"と書けば良い。
  "\$1"だと"$1"と書いたのと等価だからダメです。(後方参照として機能してしまう)
・後方参照『\1』『$1』等は、editor-replace-selected-string関数へ送られた後で内部的に展開される
 →だから、置換後の文字列として『(char-upcase (string-ref $1 0))』風に利用するのは無理。
・方法は限られますが、範囲外のテキストを参照する事も可能です。
 @ゼロ巾表現(『先読み』『後読み』とか)の利用。
  →例えば『ブログ、プログラム、3キログラム』テキストについて、
   『ログ、プログラム』部分を範囲選択した状態で、
   (editor-replace-selected-string "(?<!プ)ログ" "LOG")すると、
   選択部分が『LOG、プログラム』に置換されます。
   ちゃんと、『ブログ』の『ブ』の部分が見えてるって事です。(利用価値ありそう)
 A後方参照『$`』『$'』の利用。
  →範囲外の部分も、ちゃんと取得されます。

■『regexp-replace-all + editor-paste-string』置換手法と比べた場合の長所/短所■
【長所】
 ◎置換後に、範囲が維持される。(連続した置換に向いている)
 ◎置換された行のみに編集マークが付く。
  →regexp-replace-all型では、置換件数0であっても、全行に編集マークが付く。
 ○英大小文字を区別しない関数(ci系)が用意されている。
  →regexp-replace-allには、regexp-replace-all-ci的な派生関数は用意されていません。
   こちらの関数で英大小文字を区別しない為には、↓何れかの手法をとる必要。
   @正規表現定数へのiオプション指定。(『#/〜/i』形)
   Aパターン修飾子の利用。(『#/(?i)〜/』)
    例えば、『#/a\d+/』はA12にマッチしないけど、『#/(?i)a\d+/』ならマッチする。
 ○置換件数を得られる。
 ○範囲外への参照が可能。
 ○範囲に対するマッチ表現『\=A』『\=z』『\=^』『\=$』が利用可能。(大したメリットではない)
  →regexp-replace-all関数は、それらがなくても『\A』『\z』『^』『$』にて代替処理可能です。
【短所】
 △可読性にやや難。(検索表現を文字列で与える都合上)
 △巨大な文書(1万行とか)だと、置換に手間取る。(数秒とか。選択範囲自体は小さいのに・謎)
 ×マッチ文字列を変形する様な置換処理が苦手。(全角⇒半角変換とか。力技になったり不可能だったり)
 ×改行コードの変換に使えない。(OEditに各種改行を書き込むと、内部的に『\n』に一本化されるので)
;▼選択範囲の文字列をエスケープ化するマクロ(検索文字列作成用)▼
(app-set-key "F12" (lambda ()
  (editor-paste-string (escape (editor-get-selected-string)))
))
(define (escape s)
  (define op (open-output-string))
  (write s op) ;◆エスケープ化。
  (define ans  ;◆クォート外し。
    (regexp-replace #/^"(.*)"$/ (get-output-string op) "$1"))
  (close-output-port op)
  ans
)
;▼選択範囲を行コメント化するマクロ(Scheme専用版)▼
;・矩形範囲にも対応。
;・『範囲行頭=範囲末尾』な行はコメント化しない。
(app-set-key "F12" (lambda ()
  (define cnt (editor-replace-selected-string "\\=^(?!\\=z)" ";"))
  (app-status-bar-msg cnt "件置換しました")
))
;▼選択範囲の行コメントを解除するマクロ(Scheme専用版)▼
(app-set-key "Shift+F12" (lambda ()
  (define cnt (editor-replace-selected-string "\\=^(\\s*);" "$1"))
  (app-status-bar-msg cnt "件置換しました")
))
;▼選択範囲の文字列を、全角⇒半角変換するマクロ(実験用)▼
;●regexp-replace-all+editor-paste-string手法版と比較してみて下さい。
; こっちのがやや高速だけど、実現方法は力技です。(苦笑)
(app-set-key "F12" (lambda ()
  (define cnt (+
    (tr "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
    (tr "abcdefghijklmnopqrstuvwxyz" "abcdefghijklmnopqrstuvwxyz")
    (tr "0123456789" "0123456789")
  ))
  (app-status-bar-msg cnt "件置換しました")
))
;▼選択範囲中の文字について、文字グループmgs⇒文字グループrgs置換を行い、総置換件数を返す▼
(define (tr mgs rgs)
  (apply + (map editor-replace-selected-string
    (map string (string->list mgs))
    (map string (string->list rgs))
  ))
)
editor-replace-string
editor-replace-string-ci
【書式】 (editor-replace-string    regex-str rep)
     (editor-replace-string-ci regex-str rep)
【引数】 regex-str…検索文字列  (必ず、正規表現として解釈される)
     rep      …置換後の文字列
【返り値】置換件数…置換成功時。
     #f   …置換失敗時。(テキストが開かれていない状態等でのみ返り得る。気にしなくて良い)
テキスト全体に対して、正規表現置換を行う。
・OEdit対人の正規表現置換インタフェースを関数化したもの。(ci版の方は、英大小文字を区別しない)

◆エディタ操作|カーソル移動◆

editor-forward-char
editor-backward-char
editor-next-line
editor-previous-line
【書式】 (editor-forward-char  n select) ◆カーソル→移動
     (editor-backward-char n select) ◆カーソル←移動
     (editor-next-line     n select) ◆カーソル↓移動
     (editor-previous-line n select) ◆カーソル↑移動
【引数】 n     …移動量(文字単位 又は 行単位)(ゼロや負の数値も指定可能)
     select…#t(選択モード)、#f(移動モード)
【返り値】#t…成功、#f…失敗。
指定した移動量だけカーソルを移動させる。
・移動モード時は、OEdit対人インタフェースにおける『単なるカーソル移動』と同じ挙動。
・選択モード時は、OEdit対人インタフェースにおける『Shift+カーソル移動』と同じ挙動。
・移動モードにて移動を行うと、範囲は解除される。
移動量は、文字単位(又は行単位)で指定する。(≠桁単位)
 →ちなみに、カーソル移動については、1文字移動毎に小さなwaitが入る感じです。
  リピート時に『瞬きする間に画面の端から端』(汗)とかならない為のwaitだと思うんですが、
  (editor-forward-char 1000 #t)で1秒以上と、もどかしい局面がややありました。
  追加引数にてwait制御とか出来たら、もっと幸せになれそうな気がしました。
・移動量には、ゼロや負の数値も指定可能。
 →ゼロなら『移動しない』、負の数値なら『逆方向に移動』。
・移動は、論理上の行桁ではなく、スクリーン上(見た目)の行桁に従う。
 @折り返し時の縦移動にも正しく対応。
  →例えば、『画面巾100桁+右端で折り返す』設定の時に、非常に長い行上でカーソル下方向移動を試みると、
   次の論理行ではなく、当論理行上の+100桁位置へと移動する。
   (即ちこれは、論理上の縦移動ではなく、見た目上の縦移動と言える)
 Aタブ巾にも正しく対応。
  →タブを1文字とカウントせずにちゃんとタブ巾換算にて上下移動する。
  →例えば、『タブ巾8』設定時に、タブ2個のみの行の行末から上下の行へ移動させると、
   ちゃんと、上下行の桁16位置へ移動する。
   (editor-set-row-col (+ row 1) col)とかだと、桁2位置へ移動してしまいます。
 B画面左右端が前後行と繋がっている。
  →画面左右端を超える移動の際は、各々前行の行末/次行の行頭へ移動する。
  →『テキスト全体が(改行も含めて)行連結された1つの文字列』みたいなイメージ。
  →当然、文頭より前へは進めないし、文末より後ろへも進めない。
・表示上のカーソル位置とは別に、独自の桁位置バッファを所持しており、次の様な移動を意識せず行える。
 例.editor-next-line関数にて↓移動した際の、各行でのカーソル位置の遷移。
あいうえお ◆『か』から↓移動開始!(は行末改行)
あいう
あいうえ
あいうえお  ◆途中でズレたカーソル桁位置が、ちゃんと復元されてる!
;▼選択範囲を1単語右へ伸ばすマクロ。(空白類のみを区切りと認識)(かなり手抜き)▼
(app-set-key "Alt+RIGHT" (letrec (
  (skip-sp   (lambda () (if (char-whitespace? (edx-get-current-char))
    (begin (editor-forward-char 1 #t) (skip-sp)))))
  (skip-word (lambda () (if (not (char-whitespace? (edx-get-current-char)))
    (begin (editor-forward-char 1 #t) (skip-word)))))
  )
  (lambda ()
    (if (char-whitespace? (edx-get-current-char)) (skip-sp))
    (skip-word) ;◆単語の右端迄移動
  )
))
(define (edx-get-current-char) ;◆現カーソル位置の文字を取得。
  (editor-get-row-col-char (editor-get-cur-row) (editor-get-cur-col))
)

◆イベント制御◆

app-set-event-handler
【書式】 (app-set-event-handler eventname proc)
【引数】 eventname…イベント名(文字列)
           "on-open-file"     ◆ファイルを開いた直後
           "on-close-file"     ◆ファイルを閉じる直前
           "on-file-saved"     ◆ファイルが保存された直後
           "on-activate-app"    ◆アプリケーション(自OEdit)がアクティブになった時
           "on-selection-changed" ◆選択範囲が変化した時(内容でなく範囲そのものの変化)
           "on-cursor-moved"    ◆カーソルが移動した時
     proc     …引数なしの手続き(イベント捕捉時に処理したい内容を書く)
【返り値】常に#t ◆#fは多分返らない。
指定したイベントに対するハンドラを設定する。
・設定したハンドラは、該当するイベントが発生した際に呼び出されて実行される。
・1種類のイベントに対しては、1つのハンドラしか設定できない。
 →1種類のイベントに対し複数回ハンドラを設定しても、有効となるのは、最後に設定したハンドラのみ。
 →『複数の処理内容を1ハンドラ内に列挙する』等の工夫を行えば、実質的に複数ハンドラを設定可能。

■on-open-file■
 ・ファイルが開かれた直後に、(基本的には)トリガされる。
 ・例外的に、『無題ドキュメント』や『現在編集中のファイル』(=読み直し)が開かれた際には、トリガされない。
 ・詳細なタイミングについては、OEdit起動シーケンス図を参照。
 ・例。
【OEdit起動時】
 @トリガされない…『ファイル名指定…無』
 Aトリガされない…『ファイル名指定…有』+『そのファイルが(別OEditプロセスで)既に開かれている』
 Bトリガされる …『ファイル名指定…有』+『そのファイルが未だ開かれていない』

■on-close-file■
 ・ファイルが閉じられる直前に、必ずトリガされる。
 ・『無題ドキュメント』が閉じられる際にも、トリガされる。
 ・意味的にのみ存在する(?)起動時空ファイルが閉じられる際にも、トリガされる。
 ・詳細なタイミングについては、OEdit起動シーケンス図を参照。
 ・例。
【OEdit起動時】
 @トリガされない…『ファイル名指定…無』
 Aトリガされない…『ファイル名指定…有』+『そのファイルが(別OEditプロセスで)既に開かれている』
 Bトリガされる …『ファイル名指定…有』+『そのファイルが未だ開かれていない』
【OEdit終了時】
 @トリガされる …常に。
;▼on-close-fileイベントを捕捉して、状況に応じたメッセージを表示するマクロ(実験用)▼
(app-set-event-handler "on-close-file" (lambda ()
  (define fn (editor-get-filename))
  (if fn
    (if (string=? fn "")
      (app-msg-box "無題ドキュメントを閉じます。")
      (app-msg-box fn "を閉じます。")
    )
    (app-msg-box "(仮の)空ファイルを閉じます。")
  )
))

■on-file-saved■
 ・ファイルが保存された直後に、トリガされる。
 ・トリガの対象となるのは自プロセスでの保存時のみ。
  →現在編集中のファイルを他プロセス(外部アプリとかOEditの別プロセス)でも開いていて、
   それが他プロセス側で保存されたとしても、自プロセス上ではトリガされない。
  →どうやら、ファイルそのものの更新を監視するのでなく、自OEditプロセス上での保存アクションを検知
   しているだけの様だ。

■on-activate-app■
 ・自OEditプロセス(oedit.exe)がアクティブになった直後に、トリガされる。
  →アクティブになったと判断されるのは、↓2つかな。(自分が実験した限りでは)
   @『非アクティブ⇒アクティブ』になった直後。(アクティブなままだとトリガされない)
   A『OEdit起動』直後。
  →APIの事はさっぱり解らないのだけど、必ずしも、
   『自OEditプロセスがアクティブになる』=『自OEditのメインウィンドウがアクティブになる』
   とは言えない様だ。
  →具体的には、OEdit上で子ウィンドウ(検索窓とか)を開いて閉じる際に、OEditメインウィンドウは
   一時的に非アクティブになって、またアクティブに戻るわけだけど、こういった局面では
   on-activate-appイベントは発生しない。
  →結局、素人な自分が理解する分には、↓な感じのイメージで十分だろうと思う。
   『自OEditのメインウィンドウがアクティブになった際にトリガされる』(←別OEditの事は関知しない)
   『けど、無駄にトリガされるのも迷惑なので、子ウィンドウ開閉によるアクティブは無視される』

■on-selection-changed■
 ・範囲が変化する際に、トリガされる。
  →内容ではなく、範囲そのものの変化に対してトリガされる。
  →『範囲有り』の状態から『範囲なし』の状態に変化する際にもトリガされる。
 ・トリガの厳密なタイミングについては、操作の系列により、多少異なる。
@カーソル移動系…必ず、範囲変化の『直後に』トリガされる様です。
A検索系    …範囲が移った『直後に』トリガされる。
         (厳密には、遷移の過程に一瞬の範囲なしが介在する為、都合2回トリガされてる)
B置換系    …置換の結果、範囲が変化しても、全くトリガされない様です。
C編集系    …各々操作毎に挙動が異なります。
         『直後』『直前』『全くトリガされない』等、挙動は様々です。
         1回の操作で、(段階的に)複数回トリガされるものも多いです。
         (全くトリガされないものとしては、『重複行を削除』『半角−全角変換』が挙げられます)
;▼on-selection-changedイベントを捕捉して、累積検知回数と位置と範囲を表示するマクロ(実験用)▼
(app-set-event-handler "on-selection-changed" (let ((counter 0)) (lambda ()
  (set! counter (+ 1 counter))
  (define row (editor-get-cur-row))
  (define col (editor-get-cur-col))
  (define area (editor-get-selected-area))
  (app-status-bar-msg "cnt=" counter "(" row col ")" area)
)))

■on-cursor-moved■
 ・カーソル移動時に、トリガされる。
  →文字入力や範囲選択動作中、マクロによるカーソル移動も含まれる。
  →メイン画面以外(オプションウィンドウ中とか検索窓中)でのカーソル移動には反応しない風。
 ・カーソル出現時/消滅時にも、トリガされる感じ。
  →『カーソル出現時/消滅』は、例えば↓状況で発生します。
   @OEditメイン画面のアクティブ/非アクティブ切り替え時。
   A表示(disiplay関数等)ダイアログの開閉時。
   BOEditのオプションウィンドウ等の開閉時。
  →表示ダイアログやOEditのオプションウィンドウを出した際に、
   エディタ上からは、カーソルが一時的に消滅する。(→この際に、1回トリガされる)
  →表示ダイアログやOEditのオプションウィンドウから復帰した際に、
   エディタ上に、カーソルが再び出現する。(→この際に、1回トリガされる)
  →だから、on-cursor-movedイベントを捕捉してダイアログ表示を行うと、無限ループに陥る。
   基本的には、表示はapp-status-bar-msg関数を使うしかないと思う。(多分)
   また、on-cursor-moved用のハンドラを作成する際にバグると、エラーダイアログの無限ループに陥るので、
   デバッグには、事前にソース保存するなりの対処が必須と思われる。
 ・on-selection-changedイベントと干渉する場合がある。
  →例えば範囲選択時に、両イベントがほぼ同時にトリガされる。感覚的には、↓な順でトリガされてる?
   @キー操作による範囲選択時 …on-selection-changed→on-cursor-movedの順で発生?
   Aマウス操作による範囲選択時…on-cursor-moved→on-selection-changedの順で発生?
   ●共存させると、局面次第ではステータスバー表示を上書きし合うので、あまり使い心地は良くない。
  →on-cursor-movedとon-selection-changedを共存させるのは簡単でないが、
   『範囲変化のある場合は、大抵カーソル移動を伴う』という傾向を利用して、
   on-cursor-moved一本に処理を統合する手もある。(かなり容易でお薦め!)
;▼on-cursor-movedイベントを検知して、ステータスバー上にカウント表示するマクロ(実験用)▼
(let ((counter 0))
  (app-set-event-handler "on-cursor-moved" (lambda ()
    (set! counter (+ 1 counter))
    (app-status-bar-msg "counter =" counter)
  ))
)
;■ステータスバー上に、常に現カーソル位置の文字コードを表示するマクロ■
(app-set-event-handler "on-cursor-moved" (lambda ()
  (define row (editor-get-cur-row))
  (define col (editor-get-cur-col))
  (define c (editor-get-row-col-char row col))
  (define c-code (char->hex c '(4 4 6 8))) ;◆【補足】第2引数を'(2 4 6 8)に変えるか外すと、
                                           ; 1バイトコードを16進2桁表記。
  (define cs (cond
    ((eof-object? c)  "[EOF]")
    ((eqv? c #\tab)     "\\t")
    ((eqv? c #\newline) "\\n")
    (else (string c))
  ))
  (app-status-bar-msg cs (string-append "(U+" c-code ")"))
))
;▼サブ関数(char->hex c [w-list])▼
;・文字cを16進文字列にして返す。
;・16進桁数は、文字コードのバイト数に応じた桁巾に自動調節される。
;・オプション引数w-listにて、16進桁数をユーザ側で指定する事も可能。
; →w-list未指定時は、初期値として'(2 4 6 8)値が適用される。
; →w-listは、4要素の数値リストで、各々要素は、1〜4バイトコードに対応する桁巾を表す。
(define (char->hex c . parm)
  (define w-list (if (pair? parm) (car parm) '(2 4 6 8))) ;◆バイト数毎の16進桁数をリストで指定。
  (define x (if (eof-object? c) 0 (char->integer c)))
  (to-hex x (cond
    ((<= x 255)      (list-ref w-list 0))
    ((<= x 65535)    (list-ref w-list 1))
    ((<= x 16777215) (list-ref w-list 2))
    (else            (list-ref w-list 3))
  ))
)
;▼サブ関数(to-hex x [min-w] [prefix] [postfix])▼
;・数値xを16進文字列にして返す。
;・次の追加オプションを指定可能。
; @min-w  …最小桁巾。(不足分の桁は0で埋めます)(0を指定する事で最小桁巾の縛りを擬似的に回避可)
; Aprefix …接頭辞。(16進文字列の先頭に、指定した接頭辞を付加して返す)
; Bpostfix…接尾辞。(16進文字列の末尾に、指定した接尾辞を付加して返す)
(define (to-hex x . parm)
  ;◆初期値…min-w(0埋めなし)、prefix(接頭辞なし)、postfix(接頭辞なし)
  (define min-w   (if (pair? parm) (car parm) 0))
  (define prefix  (if (and (pair? parm) (pair? (cdr parm))) (cadr parm) ""))
  (define postfix (if (and (pair? parm) (pair? (cdr parm)) (pair? (cddr parm))) (caddr parm) ""))
  (define hex (to-radix 16 x))
  (string-append
    prefix  ;◆接頭辞
    (let ((pw (- min-w (string-length hex)))) (if (<= pw 0) "" (make-string pw #\0))) ;◆補填用0列
    hex     ;◆16進文字列
    postfix ;◆接尾辞
  )
)
;▼サブ関数(to-radix n x)▼
;・数値xをn進文字列にして返す。
;・制限事項。
; @負の数値、及び7FFFFFFFより大きい数値には非対応。(エラーとなる)
; A2〜32進数のみ対応。(それより小さくても大きくてもエラー)
(define to-radix (let
  ((number->char (lambda (i) (string-ref "0123456789ABCDEFGHIJKLMNOPQRSTUV" i))))
  (lambda (n x)
    (if (< x 0) (error "to-radix : positive number(0-7FFFFFFF) required:" x))
    (if (or (< n 2) (> n 32)) (error "to-radix : 2-32 radix required:" n))
    (define op (open-output-string))
    (let loop ((i x))
      (define quo (/ i n))         ;◆商
      (define mod (remainder i n)) ;◆余り
      (if (> quo 0) (loop quo))
      (write-char (number->char mod) op)
    )
    (define ans (get-output-string op))
    (close-output-port op)
    ans
  )
))
;■『文字コード+選択文字数+選択バイト数』をまとめて表示する版■
(app-set-event-handler "on-cursor-moved" (lambda ()
  (define row (editor-get-cur-row))
  (define col (editor-get-cur-col))
  (define c (editor-get-row-col-char row col))
  (define c-code (char->hex c '(4 4 6 8)))
    ;▲【補足】第2引数を'(2 4 6 8)に変えるか外すと、1バイトコードを16進2桁表記可能。
  (define cs (cond
    ((eof-object? c)  "[EOF]")
    ((eqv? c #\tab)     "\\t")
    ((eqv? c #\newline) "\\n")
    (else (string c))
  ))
  (define bytes (editor-get-selected-string-bytes))
  (define chars (editor-get-selected-string-chars))
  (if (editor-get-selected-area) ;◆選択範囲が有るか否かで仕分け。
    (app-status-bar-msg cs (string-append "(U+" c-code ") ") bytes "bytes," chars "chars")
    (app-status-bar-msg cs (string-append "(U+" c-code ")"))
  )
))
app-get-event-handler
【書式】 (app-get-event-handler eventname)
【引数】 eventname…イベント名(文字列)
           "on-open-file"     ◆ファイルを開いた直後
           "on-close-file"     ◆ファイルを閉じる直前
           "on-file-saved"     ◆ファイルが保存された直後
           "on-activate-app"    ◆アプリケーション(自OEdit)がアクティブになった時
           "on-selection-changed" ◆選択範囲が変化した時(内容でなく範囲そのものの変化)
           "on-cursor-moved"    ◆カーソルが移動した時
【返り値】イベントハンドラ…イベントハンドラが設定されている場合。
     空リスト()   …イベントハンドラが設定されていない場合。
指定したイベントに対して現在設定されているハンドラを返す。
・ハンドラが設定されていなければ、空リスト()が返る。
;■全イベントのハンドラ設定状況を簡易一覧するマクロ■
(define events '(
  "on-open-file"
  "on-close-file"
  "on-file-saved"
  "on-activate-app"
  "on-selection-changed"
  "on-cursor-moved"
))
(define (check-handler event)
  (define status (if (null? (app-get-event-handler event)) "未設定" "設定中"))
  (display (string-append event "…" status "\n"))
)
(for-each check-handler events)
app-clear-event-handler
【書式】 (app-clear-event-handler eventname)
【引数】 eventname…イベント名(文字列)
           "on-open-file"     ◆ファイルを開いた直後
           "on-close-file"     ◆ファイルを閉じる直前
           "on-file-saved"     ◆ファイルが保存された直後
           "on-activate-app"    ◆アプリケーション(自OEdit)がアクティブになった時
           "on-selection-changed" ◆選択範囲が変化した時(内容でなく範囲そのものの変化)
           "on-cursor-moved"    ◆カーソルが移動した時
【返り値】#t…成功。
     #f…失敗。 ◆実質的には、ハンドラが設定されてなかった場合にこうなる。
指定したイベントに対するハンドラ設定を解除する。(=何も設定されてない状態にする)
・ハンドラが設定されていなかった場合は、#fが返り、何も行われない。
app-set-timer
【書式】 (app-set-timer timer-id elapse proc)
【引数】 timer-id …タイマーイベントを識別する番号。(一意の16bit整数値:0〜65535)
     elapse  …タイマーイベントの起動間隔。[ミリ秒]
     proc   …引数なしの手続き(イベント捕捉時に処理したい内容を書く)
【返り値】#t…成功。
     #f…失敗。 ◆失敗するのは、『oedit.scm』中で実行した場合のみ。(恐らく)
タイマーで起動するイベントを登録する。
・タイマーイベントは、同時に複数個設定可能。(=共存可能)
 →試しに、33個同時設定してみたけど、ちゃんと動作しました。(個数制限があるのかは不明)
・タイマーイベントは、時間が来れば、どんな局面でも発生する。
 ・タイマーイベント処理中でも。
 ・OEditプロセスが非アクティブでも。
 ・OEdit対人インタフェースで、ダイアログとか出してる最中でも。
 →起動間隔を1ミリ秒とかに設定してダイアログ表示とかさせると、
  1秒間に1000枚のペースでダイアログ表示されて、恐ろしい事になる筈。(要注意!)
 →累積リスクを避ける為に、現在タイマ処理中である事を示すフラグを用意すると良いかも知れない。
・timer-idは、
 ・同一idを使って登録すると、処理が上書きされる。(エラーにはならない)
 ・idに32bit整数値を指定した場合は、原則としてその下位16bit分の値として処理される(エラーにはならない)
  が、ややこしい事になるので、idは素直に16bit整数値を用いるのが無難。
  →どうややこしいかというと、(あくまで推測ですが)
   @Scheme上ではtimer-idはちゃんと32bit整数値として管理されてる。
    →だから、131071(0x1FFFF)番と65535(0xFFFF)番のタイマーが、ちゃんと両方登録される。
    →そして、タイマーはちゃんと2個分動作する。
   A一方で、timerの実装レベルでは、idの下位16bit分しか見てない。
    →だから、131071(0x1FFFF)番と65535(0xFFFF)番は実質同じものであり、両方登録しようとしたなら、
     先に登録したハンドラは、後に登録したハンドラで上書きされる。
    →その結果@A間に矛盾が生じ、タイマーは2個分動作するが、両方とも後者で登録したハンドラ
     の方が処理される。(苦笑)
  →要は、idに16bit整数値を使ってる分には問題ないから、気にする必要はないという事。
『oedit.scm』中では、タイマーイベントを登録できない。
 →作者さまによると『timerはメイン画面が初期化される前は使用できません』との事。
 →こちらで実験してみた所、どうやらここの図中のDにて初期化がなされる模様。
  即ち、図中のDの直後から、タイマーイベントが登録可能になります。
 →D〜F間には、最大で3個(on-close-file、on-open-file、on-activate-app)のイベントが発生し得るので、
  この何れかを捕捉して、その中でapp-set-timerする様に記述すれば、何とかタイマーイベントを
  登録できそうです。
 →サンプルとして、on-activate-app初弾を捕捉する手法を、↓の4番目の例に掲載しておきます。
  タイマーを使った経験がまるでないので、↓例は安定動作する自信がありません。(汗)
  実用レベルに持って行くには、タイマーの累積抑止など、細々としたチューニングを
  各自で行う必要があるでしょう。
;■5秒毎にメッセージをダイアログ表示。(放置するとダイアログが累積する版)■
(app-set-timer 1 5000 (lambda ()
  (app-msg-box "時間です。")
))
;■5秒毎にメッセージをダイアログ表示。(ダイアログが累積しない版)■
(let (($timer-running #f))
  (app-set-timer 1 5000 (lambda () (call/cc (lambda (return)
    (if $timer-running (return)) ;◆タイマーイベント処理中は、新たな処理を控える。(累積対策)
    (set! $timer-running #t)     ;◆本処理前に、フラグを立てる。(=タイマ抑止)
    (app-msg-box "時間です。")
    (set! $timer-running #f)     ;◆本処理後に、フラグを寝かす。(=タイマ抑止の解除)
  ))))
)
;■現在編集中のファイルの更新を検知して『読み直し』を促すサンプル(『oedit.scm』外で使う版)■
;●簡便の為に、累積抑止処理は省いてます。
(let ( ;◆ver1.01
  ($h (make-hash-table 'string=?)) ;◆更新日時管理用ハッシュテーブル。(ファイルパス, 更新日時)
  ($timer-id 1)
  ($interval 10000)                ;◆ファイルの更新チェックの間隔。[ミリ秒]
  )
  ;▼サブ関数|更新日時をハッシュテーブルに登録/更新▼
  (define (update-mt)
    (define fn (editor-get-filename))             ;◆現在編集中のファイルのパス
    (if (and fn (file-exists? fn)) (begin
      (define mt (slot-ref (sys-stat fn) 'mtime)) ;◆現在編集中のファイルの更新日時
      (hash-table-put! $h fn mt)                  ;◆ハッシュテーブルに登録/更新
    ))
  )
  ;▼サブ関数|更新のチェックと通知▼
  (define (monitor-mt)
    (define fn (editor-get-filename))                  ;◆現在編集中のファイルのパス
    (define mt-hash (hash-table-get $h fn #f))         ;◆更新日時(hashに記録されてる分)
    (if (and mt-hash fn (file-exists? fn)) (begin
      (define mt-now  (slot-ref (sys-stat fn) 'mtime)) ;◆更新日時(最新の分)
      (if (> mt-now mt-hash) ;◆更新されていたら通知する。
        (app-msg-box
          "更新日時が変更されています。\n"
          "必要なら手動で読み直して下さい。\n"
          "(読み直したら直ぐに、ハッシュ更新の為、F12を押下する必要)"
        )
      )
    ))
  )
  ;▼各種ハンドラをセット▼
  (app-set-event-handler "on-open-file"  update-mt) ;◆ファイルを開いた際に、更新日時をハッシュに記録
  (app-set-event-handler "on-file-saved" update-mt) ;◆ファイル保存に伴い、ハッシュを更新
  (app-set-key "F12" update-mt)                     ;◆ファイル読み直しに伴い、ハッシュを更新
                                                    ; (on-reopen-file不在をF12キー手動押下で代用)
  (app-set-key "Shift+F12" (lambda ()               ;◆タイマー切断。(通知がウザい時の退避手段)
    (app-clear-timer $timer-id)
    (app-clear-event-handler "on-open-file")
    (app-clear-event-handler "on-file-saved")
  ))
  (app-set-timer $timer-id $interval monitor-mt)    ;◆タイマーによる更新日時の監視
)
;■現在編集中のファイルの更新を検知して『読み直し』を促すサンプル(『oedit.scm』内で使う版)■
;●簡便の為に、累積抑止処理は省いてます。
(let ( ;◆ver1.21
  ($h (make-hash-table 'string=?)) ;◆更新日時管理用ハッシュテーブル。(ファイルパス, 更新日時)
  ($timer-id 1)
  ($interval 10000)                ;◆ファイルの更新チェックの間隔。[ミリ秒]
  )
  ;▼サブ関数|更新日時をハッシュテーブルに登録/更新▼
  (define (update-mt)
    (define fn (editor-get-filename))             ;◆現在編集中のファイルのパス
    (if (and fn (file-exists? fn)) (begin
      (define mt (slot-ref (sys-stat fn) 'mtime)) ;◆現在編集中のファイルの更新日時
      (hash-table-put! $h fn mt)                  ;◆ハッシュテーブルに登録/更新
    ))
  )
  ;▼サブ関数|更新のチェックと通知▼
  (define (monitor-mt)
    (define fn (editor-get-filename))                  ;◆現在編集中のファイルのパス
    (define mt-hash (hash-table-get $h fn #f))         ;◆更新日時(hashに記録されてる分)
    (if (and mt-hash fn (file-exists? fn)) (begin
      (define mt-now  (slot-ref (sys-stat fn) 'mtime)) ;◆更新日時(最新の分)
      (if (> mt-now mt-hash) ;◆更新されていたら通知する。
        (app-msg-box
          "更新日時が変更されています。\n"
          "必要なら手動で読み直して下さい。\n"
          "(読み直したら直ぐに、ハッシュ更新の為、F12を押下する必要)"
        )
      )
    ))
  )
  ;▼各種ハンドラをセット▼
  (app-set-event-handler "on-open-file"  update-mt)   ;◆ファイルを開いた際に、更新日時をハッシュに記録
  (app-set-event-handler "on-file-saved" update-mt)   ;◆ファイル保存に伴い、ハッシュを更新
  (app-set-key "F12" update-mt)                       ;◆ファイル読み直しに伴い、ハッシュを更新
                                                      ; (on-reopen-file不在をF12キー手動押下で代用)
  (app-set-key "Shift+F12" (lambda ()                 ;◆タイマー切断。(通知がウザい時の退避手段)
    (app-clear-timer $timer-id)
    (app-clear-event-handler "on-open-file")
    (app-clear-event-handler "on-file-saved")
    (app-clear-event-handler "on-activate-app")
  ))
  (app-set-event-handler "on-activate-app" (let ((counter 0)) (lambda ()
    (set! counter (+ 1 counter))
    (if (= counter 1)                                 ;◆OEdit起動直後のアクティブ時に1度切り、
      (app-set-timer $timer-id $interval monitor-mt)) ;◆タイマーイベントを登録。
  )))
)
app-clear-timer
【書式】 (app-clear-timer timer-id)
【引数】 timer-id …タイマーイベントを識別する番号。(一意の16bit整数値:0〜65535)
【返り値】#t…成功。
     #f…失敗。 ◆失敗するのは、『oedit.scm』中で実行した場合のみ。(恐らく)
タイマーイベントを削除する。
・登録してないtimer-idが指定された場合は、何もしない。(エラーにはならない)
・timer-idに32bit整数値を指定した場合は、その下位16bit分の値として処理される。(エラーにはならない)
 →例えば、131071(0x1FFFF)を指定した場合、65535(0xFFFF)として処理される。
・まとめて全部のタイマーイベントを削除する事はできない。(1個ずつ削除する必要)
・『oedit.scm』中では、使えない。

◆キー割り当て◆

app-set-key
【書式】 (app-set-key keyname proc [comment])
【引数】 keyname…キー名(文字列)
     proc  …引数なしの手続き(キー押下時に処理したい内容を書く)
     comment…コメント(文字列)(省略可) ◆省略時は""指定として扱われる。
【返り値】常に#t ◆#fは多分返らない。
キー割り当てを行う。
・キー名の表記は、OEditの『ツール→キー割り当て』の所と完全に互換。
 →ただ、管理テーブルは『ツール→キー割り当て』のものとは別個に用意されてます。(同じキーを双方に登録可)
・キー割り当てが他とバッティングした際の優先順位。(推定)(上から順に優先)
 @Windows標準のショートカットキー設定。(現ウィンドウ外へ影響の及ぶもの)
  →『Alt+TAB』『Ctrl+ESCAPE』等。
 @IMEの設定。
  →『F8』(半角に変換)等。
 Aapp-set-keyの設定、又は、app-set-cancel-keyの設定。
 BOEditの『ツール→キー割り当て』設定。
 CWindows標準のショートカットキー設定。(現ウィンドウ内での操作)
  →『Alt+F4』『Shift+F10』等。
・キーの組み合わせには、多少制約がある様です。(こちらを参照)
・不正なキー名を指定した場合は、(基本的には)エラーとなります。
 →キー名は、英大小文字を厳密に区別します。
  例.『Ctrl+A』『F12』…○、『ctrl+A』『f12』…×。
 →例外として、英字に関しては、大/小文字どちらで表記しても構いません。
  例.『Ctrl+A』…○、『Ctrl+a』…○。(どちらの表記も等価)
 →エラーとならない場合について。(バグ??)
  ・キー名に『〜+〜+…+○』指定した際に、『〜+〜+…+』部が不正でもエラーとならない。
   →『○』部が不正ならエラーとなる。
  ・またその際のキー割り当ては『〜+〜+…+○』のまま行われるが、実際に捕捉可能なキーは『△+○』である。
   →上記の△は『〜+〜+…』の内の不正でない部分です。
    例えば、『XYZ+Alt+012+Y』設定だと『Alt+Y』押下に、『ctrl+a』設定だと『a』押下に、反応する。
・『Alt+』『Ctrl+』『Shift+』は、併用専用です。単体では使えません。
 →『+』を除いて、『Alt』『Ctrl』『Shift』表記するのも無効です。
 →『Alt+』『Ctrl+』『Shift+』を互いに併用する場合は、どの順序で並べても構いません。
  また、併用専用キーを末尾に配置する事はできません。
  例.『Alt+Ctrl+Shift+A』と『Ctrl+Alt+Shift+A』は等価。
  例.『A+Ctrl』『A+Ctrl+』とかは、エラー。
・割り当てに対しては、コメント(説明とか)を付記できます。
 →付記したコメントは、(キー割り当ての)管理テーブルに一緒に記録されます。
 →app-get-all-keys関数でキーリストを取得した際に、コメントも取得されます。
  キーヘルプを出力する際に使えます。それ以外に、特に使い途はないです。
・ちなみに、割り当てを解除する手段は用意されてません。
 →『(app-set-key "キー名" (lambda ()))』な感じで擬似的に解除する事は可能です。
 →ただ、↑手法は所詮気休めなので、
  『(app-set-key "A" (lambda () (editor-paste-string "B")))』してしまった状態で
  『(app-set-key "A" (lambda ()))』としても、Aキー本来の挙動は戻りません。
  ●OEditを再起動すれば、元に戻せます。(『oedit.scm』中でのキー定義なら、それを修正の上でOEdit再起動)
;■(実質的)空行を除去■
;・選択範囲中の、空白類のみで構成された行(=実質的空行)を除去する。
;・対象となる空白類は、半角空白/タブ/全角空白の3つ。(変更可)
(app-set-key "F12" (lambda ()
  (define $spaces " \t ") ;◆対象となる空白類
  (define cnt (editor-replace-selected-string
    (string-append "^[" $spaces "]*$\n?")
    ""
  ))
  (app-status-bar-msg cnt "件除去しました")
) "(実質的)空行を除去")
;■条件付ソート■
;・選択範囲について、条件付で行ソートを行う。
;・条件は、取り敢えず『行を行末文字から比較』『英大/小文字を区別せずに比較』の2つ。
; (初期状態では、ソート条件は『行を行末文字から比較』の方になってる)
;・一部関数↓は、『xlib-base.scm』中に収録されてる分を利用。
; string-reverse-ex、string-split、string-join、list-sort!、等…
;・list-sort!関数は、要素の替わりに(要素から算出した)キーを用いて比較を行う。
; だから、キー算出式を取り替える事で、簡単にソートルールを変更可能。
; 例.英大/小文字を区別せずにソートしたいなら、キー算出式を↓に取り替えればOK。
;   (define (key s) (string-upcase s))
(app-set-key "F12" (lambda ()
  ;▼config的なもの▼
  (define cmp string<?)                  ;◆ソート順 |昇順(string<?)、降順(string>?)
  (define (key s) (string-reverse-ex s)) ;◆キー算出式|行の内容を逆順に比較用
  ;(define (key s) (string-upcase s))    ;◆キー算出式|英/大小文字を区別せずに比較用
  
  ;▼ソート処理本体▼
  (define s (editor-get-selected-string))
  (if (rxmatch #/\n\z/ s) ;◆文末改行の有無で処理を振り分け。(OEdit版ソートの挙動に近付ける為)
    (begin (set! s (string-chomp s)) (define mode 'suffix))
    (define mode 'infix)
  )
  (define sl (string-split s #\newline))
  (editor-paste-string
    (string-join (list-sort! sl cmp key) "\n" mode))
) "末尾からのソート")

;▼サブ関数|文末改行の除去▼
(define (string-chomp s) (regexp-replace #/\n\z/ s ""))
;▼サブ関数|英小文字⇒大文字変換▼
(define (string-upcase s)
  (regexp-replace-all #/[a-z]/ s (lambda (m)
    (char-upcase (string-ref (rxmatch-substring m) 0))))
)
;■自動インデント■
;・全角空白もインデントとして認識する。(認識する文字は、自由に変更可)
;・OEdit標準の自動インデントは、ONでもOFFでも構わない。(こっちの割り当てが優先される)
(app-set-key "ENTER" (lambda () ;◆ver1.10
  ;▼config的なもの▼
  (define $indent-chars " \t ") ;◆インデントとして認識する文字
  (define $erase-spaces " \t")   ;◆無用先頭空白除去の対象文字
  
  ;▼通常範囲がある場合は、『左上←右下』向きに囲い直す▼
  ; (Undo時のカーソル位置挙動をOEdit版に近付ける為)
  (define area (editor-get-selected-area))
  (if (and area (not (editor-box-select?)))
    (editor-set-select-area
      (list-ref area 2) (list-ref area 3) (list-ref area 0) (list-ref area 1))
  )
  
  (editor-delete-selected-string) ;◆矩形範囲への貼り付け挙動が対人とSchemeで異なるのを回避する為
  (editor-paste-string "\n")
  
  ;▼インデントの算出▼
  (define row (editor-get-cur-row))
  (define s (editor-get-row-string (- row 1)))
  (define m (rxmatch
    (string->regexp (string-append "^([" $indent-chars "]*)(.*)$"))
    s
  ))
  (define s-indent  (rxmatch-substring m 1)) ;◆前行のインデント
  (define s-content (rxmatch-substring m 2)) ;◆前行の行内容(インデント除く)
  
  ;▼行分割時の無用先頭空白除去▼
  (if (string>? s-content "") (begin ;◆前行の行内容が空でない場合のみ、除去処理を行う
    (define s (editor-get-row-string row))
    (define m (rxmatch
      (string->regexp (string-append "^[" $erase-spaces "]*"))
      s
    ))
    (define col-es (rxmatch-end m)) ;◆無用先頭空白塊の終端桁
    (editor-set-select-area row 0 row col-es)
  ))
  ;▼インデントの付与▼
  (editor-paste-string s-indent)
) "自動インデント")
app-get-all-keys
【書式】 (app-get-all-keys)
【引数】 
【返り値】((keyname proc comment) …)形式 ◆個々のキー割り当て情報を要素とするリスト
     ●keyname proc commentは、それぞれapp-set-keyで指定した値に1対1で対応する。
全てのキー割り当て情報を取得する。
・取得する情報は、app-set-keyで登録したもののみ。(app-set-cancel-keyで登録した分は取得されない)
・キー割り当てが無い場合は、空リスト()が返る。
・キーリストの並び順は、登録順でもキー名昇順でもない。(ハッシュテーブルを全取得した際に似ている)
・取得した情報は、全て新規生成値なので、自由に書き換えて構わない。
 →書き換えても、キー割り当てに影響を与える事はない。
;▼簡易キーヘルプ▼
;●コメント設定がないと、寂しい結果となる。(苦笑)
(app-set-key "Ctrl+H" (let
  ((key-help-format (lambda (l) (string-append (car l) "\t: " (caddr l) "\n"))))
  (lambda ()
    (define kl (app-get-all-keys))
    (display (apply string-append (map key-help-format kl)))
  )
) "キーヘルプ")
app-set-cancel-key
【書式】 (app-set-cancel-key keyname)
【引数】 keyname…キー名(文字列)
【返り値】常に#t ◆#fは多分返らない。
マクロの実行を中断するキーを設定する。
・不正なキー名を指定した場合は、(基本的には)エラーとなる。
 →エラーとならない特殊パターンは、app-set-key関数の場合と同様。
・デフォルトでは、Ctrl+Pause/Breakキーに設定されている。
 →別のキーに変更した後に、元に戻したくなったら『(app-set-cancel-key "Ctrl+CANCEL")』でOK。
 →『実際のキー』と『キー名』が一致しないパターンが稀にあります。(↑は典型的例)
 →『実際のキー』と『キー名』の対応は、こちらを参照して下さい。
・キー割り当ての管理テーブルは、app-set-keyとapp-set-cancel-keyで別個に存在する。
 →だから、同じキーをapp-set-keyとapp-set-cancel-keyの両方に割り当てる事が許容される。
 →両方で割り当てられたキーについては、押下時にどちらかが機能する風。
  経験的には、マクロ実行中にはキャンセルが、それ以外では通常割り当てが効く風です。
・app-set-key関数は、捕捉したキー入力を最後に破棄するが、app-set-cancel-key関数は
 キー入力を後ろに流す。
 →『(app-set-key "A" (lambda () (display "*"))』『(app-set-cancel-key "A")』をそれぞれ試した場合、
  前者…A押下時に『*がダイアログ表示されるだけ』
  後者…A押下時に『キャンセル処理後にAが画面上に現れる』

◆表示◆

app-msg-box
【書式】 (app-msg-box 式1 …)
【引数】 1個以上の式を列挙
【返り値】#t…成功、#f…失敗。
メッセージダイアログに表示。
・式は、表示の前に評価されます。
・式を複数列挙した場合は、間に半角空白を挟んで表示されます。
・display表現で表示されるようです。
 →display表現だと、制御文字(タブや改行)は機能として働きます。
 →display表現では、型による修飾みたいなのは、省いて表示されます。
  例えば、『7』『#\7』『"7"』は、何れも『7』と表示されます。
・メッセージダイアログ表示中は、マクロの中断(キャンセル)キーが効きません。
 →キャンセルキー押下は、OKボタン押下(=ダイアログ閉じ)と扱われます。
・各種表示関数の機能の違いは、こちらを参照。
;▼使用例▼
(app-msg-box '(a . b) #\newline "ABC\n" (- 100 3) #t)
app-status-bar-msg
【書式】 (app-status-bar-msg 式1 …)
【引数】 1個以上の式を列挙
【返り値】#t…成功、#f…失敗。
ステータスバーに表示。
・app-msg-box関数のステータスバー表示版的位置付けです。
・各種表示関数の機能の違いは、こちらを参照。
app-input-box
【書式】 (app-input-box [title [value]])
【引数】 title…ダイアログのタイトル(文字列)(省略可)
     value…入力欄の初期値(文字列)(省略可)
【返り値】入力文字列…OKボタン又はEnterキー押下時
     #f    …CANCELボタン又はESCキー押下時
文字列入力用のインプットダイアログを表示。
・入力した文字列は、返り値として受け取れます。
 →何も入力せずにOK押下なら、""空文字列が返ります。
 →何か入力してもCANCEL押下なら、#fが返ります。
・入力欄への文字数制限は、特にないようです。
 →1万文字で確認済み。(但し、1万文字だと、こちらの環境では文字がブレて見えました)
・入力欄中では、エスケープ記法は使えません。
 →入力した文字は、文字そのものとして扱われます
 →『\』も『"』も、エスケープを必要としません。
 →例.『\』 …『\』な計1文字の文字列として取得します。(こんな感じ→"\\")
  例.『"』 …『"』な計1文字の文字列として取得します。(こんな感じ→"\"")
  例.『\n』…『\』『n』の計2文字の文字列として取得します。(こんな感じ→"\\n")
・入力可能なのは、通常文字だけです。
 →制御文字(タブ/改行)は入力できません。
  例えば、Tabキー押下では何も起こりません。Enterキー押下ではOK押下と扱われます。
 →但し、初期文字列中になら、制御文字を含められます。
  入力欄中に記述できたなら、それが例え制御文字であってもOK押下で受け取れます。
・入力ダイアログ表示中は、マクロの中断(キャンセル)キーが効きません。
 →キャンセルキー押下は、CANCELボタン押下と扱われます。
;▼ダイアログ入力した文字列をそのまま解釈するマクロ(実験用)▼
;・\記法を解釈しません。(\tや\nを含めた文字列を入力してみて下さい)
(app-set-key "F12" (lambda ()
  (write (app-input-box))
))
;▼ダイアログ入力した文字列を\記法で解釈するマクロ(実験用)▼
;・\記法を解釈します。
(app-set-key "Shift+F12" (lambda ()
  (write (escape (app-input-box)))
))
(define (escape s) ;◆文字列を\記法で解釈し直して返す。
  (and s (read (open-input-string (string-append "\"" s "\"")))) ;◆(and s 〜)はsが#fの時への対処
)

◆環境◆

app-get-tool-dir
【書式】 (app-get-tool-dir)
【引数】 なし
【返り値】oedit.exeが置いてあるディレクトリへの絶対パス(文字列)
oedit.exeが置いてあるディレクトリへの絶対パス(文字列)を取得する。
・より正確には、取得するパスは『今起動しているoedit.exeプロセス』のものとなります。(当然だけど)
・パスの末尾には、必ず『\』が付く。(例えば、"D:\\oedit\\"とか)
;■現在編集中のテキストを(該当するファイルを経由して)Grep検索するマクロ■
;・実行前に事前の下準備が必要です。
; @oedit.exeの所在フォルダ直下に『@』名の新フォルダを作成。
; A@フォルダ直下に、『ogrep.exe』と『oedit.ini』をコピる。
;  →『oedit.ini』はフォントや背景色設定用として、OGrepから流用されます。
;  →『ogrep.ini』は、マクロが実行毎に自動生成します。(コピる必要はありません)
;   ogrep.exeは起動オプションを持たないので、iniファイル経由でオプションを渡してます。
;・『ogrep.exe』をわざわざコピるのは、既存の『ogrep.ini』が破壊されぬ為の配慮です。
;・ファイルパス直指定により(擬似的に)テキストのGrepを実現しています。
; →だから、Grepする直前に手動で現在編集中のテキストを保存する必要があります。
; →でないと、最新でない検索結果が得られます。
; →また必然的に、無題ドキュメント上では使えません。(ファイルとして存在しないので)
(app-set-key "F12" (let* (
  (grep-dir (string-append (app-get-tool-dir) "@\\")) ;◆今回用意した別働ogrep.exeの在り処
  (exe-path (string-append grep-dir "ogrep.exe"))
  (ini-path (string-append grep-dir "ogrep.ini"))
  )
  (lambda ()
    (define fn (editor-get-filename)) ;◆現在編集中のテキストのファイルパス
    (if (and fn (string>? fn "")) (begin
      ;▼『ogrep.ini』の自動生成▼
      (call-with-output-file ini-path (lambda (ouf)
        (display (string-append
          "[GREP]\nDISTINCT_LWR_UPR=0\nREGEXP=1\nSEARCH_SUB_FOLDER=0\n"
            ;◆↑大小文字を区別…×、正規表現…○、サブフォルダも検索…×
          "[search_folder]\n0=" (File.dirname  fn) "\n" ;◆検索フォルダ
          "[file_type]\n0="     (File.basename fn) "\n" ;◆ファイルの種類
          ) ouf)
      ))
      ;▼『ogrep.exe』の呼び出し▼
      (win-exec exe-path)
    ))
  )
) "編集中のテキストをGrep検索")

(define (File.dirname fn)  ;◆絶対ファイルパス(文字列)からディレクトリ部を抽出して返す。
  (or (rxmatch-substring (rxmatch #/^.*\\/ fn)) "")
)
(define (File.basename fn) ;◆絶対ファイルパス(文字列)からファイル名部を抽出して返す。
  (rxmatch-substring (rxmatch #/[^\\]*$/ fn))
)
app-get-command-line-option
【書式】 (app-get-command-line-option)
【引数】 なし
【返り値】起動時のコマンドラインオプション(文字列)
OEdit起動時のコマンドラインオプション(文字列)を取得する。
取得するのは、引数部分のみです。
 →例えば、『oedit.exe test.scm /r10』起動されたなら、"test.scm /r10"文字列を取得。
取得するのは、クォート解釈等が行われる前の生コマンドライン文字列です。
 @例えば、クォートは起動した時のまま。↓
  『oedit "abc.txt"』起動したものなら、"\"abc.txt\""文字列を取得。
  『oedit abc.txt』起動したものなら、"abc.txt"文字列を取得。
 Aスペースの個数とかも起動した時のまま維持される。↓
  『oedit a.txt   /r20  』起動なら、"a.txt   /r20  "として取得。
 Bワイルドカードもそのまま。
  『oedit *.txt』起動なら、"*.txt"文字列を取得。
  →ちなみに、開かれるのは、最初にマッチしたファイル(1つ)のみ。
 Cファイル名がフルパスに自動補完されたりはしない。(入力したままの形で受け取る)
  →ただ、エクスプローラからファイルを開く際は、
   『エクスプローラが、ファイルをフルパスに補完した上でOEditを起動してる』
   みたいで、取得したら必ずフルパスになってます。(詳しくは勉強不足で分かりません)
 Dファイル名なしで起動なら、ファイル名なしで取得。
  『oedit.exe』(引数なし)で起動なら、""空文字列を取得。
  『oedit.exe /xc5-7』(オプションのみ)で起動なら、"/xc5-7"を取得。
app-get-command-line-optionの取得値は、途中で更新されたりしない
 →『起動時のもの』を(当該OEditプロセスが)後生大事に保持し続ける様です。
・特殊な局面での取得値。
 @OEdit上から『ファイル→新しいウィンドウ』した先のウィンドウ上での取得値。
  →""空文字列。(『ファイル名未指定+起動オプションなし』にてOEditを新規起動したのと多分、等価)
 AOEdit上で読み直したり、別のファイルを開いたりした際の取得値。
  →元のまま。(変化なし)
 B現在編集中のファイルに対し、別の起動オプションにてコマンドライン起動した際の取得値。
  →元のまま。(変化なし)
  →新起動オプション自体は、ちゃんと働くので取得値に反映されないのは不思議。
 Cファイル名複数指定にて起動された際。
  1)先頭OEditプロセス上。(先頭ファイルが開いている)
   取得値は、『起動時に指定した起動オプションそのまま』(複数ファイル名をそのまま含んでる)。
  2)後続OEditプロセス上。(後続ファイルが、それぞれ開いている)
   取得値は、『"自ファイル名フルパス"』(クォート含む)。
   →強制クォートされていたり、フルパスに自動補完されてる点にも注目。

関連リンク『OEditの起動シーケンス』『OEditの起動オプション処理』『Schemeで自作起動オプション処理に挑戦』。