VZのブロックモードは,次の数種類があります。
mbの値 | モード略称 | 選択範囲 |
---|---|---|
0 | 非ブロック | 範囲指定を顕在化させていない |
1 | 行ブロック | 行を単位とした範囲指定 |
2 | 文字ブロック | 文字を単位とした範囲指定 |
4〜 | 矩形ブロック | 複数行同一桁範囲指定 |
現在のブロックモードを変更するには2通りの方法があります。編集コマンドを使用する方法と,変数を操作する方法です。前者は編集操作のシミュレートのようなもので,後者はマクロならではの手法です。ただし,マクロの中では状況に応じて適切な方法を選択することになります。エディタのマクロの柔軟性ということでしょうか。
まず,編集コマンドを使用する方法です。次の例は,複数の表示行からなる長い論理行を行ブロックとして選択するというものです。最初から行ブロックするのは難しいので,必然的にこのようなコードになるでしょう。
(mb)?. ;非ブロックモードで起動
#< #b #> ;論理行全体を文字ブロック
#x ;行ブロックに変更
なお,テキストスタックに保存済のブロック範囲のモードを強制的に変更することもできますが,あくまでも「やってできないことはない」という力技に類するものになります。これについては次項を参照してください。
テキストスタックに保存されたブロックは,そのタイプとサイズが情報として記録されています。ブロックタイプとは,どのブロックモードで保存されたものかということで,ペーストする際にタイプ情報に応じてブロックが再現されます。文字ブロックであればカーソル位置にそのまま挿入され,行ブロックであれば現在カーソルのある行と前の行との間に挿入されます。
テキストスタックは次のような構造になっています。
┌──────────────────────┐ │セグメント kz の終端 │↑上位アドレス │ │ │ ここは空きってこと │ │ │ │ │ │テキストスタックエンドポインタ kp │ ├──────────────────────┤ │ │ ├ブロックサイズ情報 kp..-1 (2バイト) ────┤ │ │ ├──────────────────────┤ │ブロックタイプ情報 kp.-3 │ ├──────────────────────┤ │ │ │ │ │ブロック文字列 (最後に記憶したブロック) │ │ │ │ │ ├──────────────────────┤ │ │ ├ブロックサイズ情報 kp..-1 (2バイト) ────┤ │ │ ├──────────────────────┤ │ブロックタイプ情報 kp.-3 │ ├──────────────────────┤ │ │ │ │ │ブロック文字列 (1回前に記憶したブロック) │ │ │ │ │ ├──────────────────────┤ │ │ 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 │ │ ├──────────────────────┤ │ │ ├ブロックサイズ情報 kp..-1 (2バイト) ────┤ │ │ ├──────────────────────┤ │ブロックタイプ情報 kp.-3 │ ├──────────────────────┤ │ │ │ブロック文字列 (最初に記憶した,あるいは, │ │ とにかく先頭にあるブロック) │ │ │ │セグメント kz の先頭 │↓下位アドレス └──────────────────────┘
ここで問題となるのは,kp.-3
に格納されているブロックタイプ情報です。この情報を変更してやれば,記憶されたときと異なるブロックタイプでペーストできるということになります。なお,無理に変更しているわけですから,ペースト後の状況は無保証です。無保証と言っても,テキストがぐちゃぐちゃに壊れることはないようですが。
さて,ブロックタイプ情報の kp.-3
は,まっとうな方法で確認することができます。関数 &Bm
はテキストスタックに最後に格納されたブロックのブロックタイプを返します。
返り値(r)の値 | ブロックタイプ |
---|---|
0 | 行(範囲指定しなかった場合の1論理行記憶) |
1 | 行ブロック |
2 | 文字ブロック |
上記以外 | 矩形ブロック(使用した矩形ブロックマクロに依存) |
つまり,kp.-3
に2を代入すれば行ブロックとして記憶されたブロックも,文字ブロックとしてペーストされるということになります。ただしkpというポインタは,VZのデータセグメントとは別のセグメントにありますので,一時的にVZが扱うセグメントを変更しなければなりません。
1 ^\ "最後に記憶されたブロックを文字ブロックに変更"
?. &Bm
dz[=kz,
(r-2)?{ kp.-3=2, }
dz],
&m("ブロックタイプを変更しました") .
行ブロックを文字ブロックに変更した場合,カーソル位置に挿入されるというだけの意味しかありませんので,含まれている改行もそのまま出力されます。
これだけでは単なるお遊びに過ぎませんので,少しは実用性を考えてみましょうか。
矩形ブロックとして記憶されたブロックを,ばらばらの文字ブロックにして個別にペーストできるようにしたら便利なことがあるかもしれない,…というかなり無理のある実用性ですが,わたしは矩形ブロックマクロに次の小物を付加して結構使っています。あ,でも本来の目的よりも,不要になった矩形ブロックを無効にするのに使うことの方が多いかなぁ。(^^;
= kb_chang = by H_ear
* m
84 "スタック先頭のブロックタイプ変更"
?.
&Bm (r<3)?. ;矩形ブロックのみ対象
dz[=kz, ;セグメント変更
k=kp, ;テキストスタック終端から開始
:L (k.-3>2)?{ ;矩形ブロックならループ
k.-3=2, ;★タイプ変更(文字ブロックへ)
l=k..-1, ;ブロックのサイズ
k-=3+l, ;+サイズ情報分遡及する
>L
}
dz], .
*
ブロック選択で範囲指定した文字列を検索文字列として取得するコマンドが標準で用意されています。このコマンドが#G
(#59
)です。製品版標準サンプルのVZ.DEFでは文字列の連続取得マクロとして機能拡張されていますが,選択部分を取得するのは単体のコマンド#G
だけで実行できます。
この#G
で取得された文字列が格納されるバッファは,検索文字列バッファです。検索文字列バッファは,変数psが指すアドレスを先頭とし,Hsで指定された大きさを持つヒストリーバッファです。このコマンドによる文字列の格納場所を変更するには,psの指すアドレスを一時的に別のアドレスにすり替えてやればよいということになります。
既存のヒストリーバッファ,例えばpwやpaとして用意されているバッファに格納したい場合は,単にpsの示すアドレスをpwやpaに変更してやればよいのです。ただし,psの指すアドレスを変更したら確実に元に戻しておかなければなりません。さもないとVZのハングアップを招くことになります。
(mb!=2)?. ;文字ブロックでなければならない
ps[, ;変数psの値を保存 ; この2行はVZ v1.6以降
ps=pa, ;psの値をpaと同じにする ; ps[=pa,と短縮できます
#G ;文字列を取得/取得された文字列はpsに擬態したpaに格納
ps], ;★変数psの値を復帰
注意すべき点は,コマンド#G
は改行を含めた取得ができないということで,改行を越えて範囲選択している場合は,改行の手前までが取得されます。
それでは,改行を含めた文字列の取得をどうするかというと,一旦テキストスタックに取得してから,改行を置き換えつつバッファに転送するということになるでしょう。ただし,テキストスタックは通常のバッファとは別のセグメントに存在するため,文字列関数等を使って直接データを転送することはできません。いわゆる「セグメントをまたぐ転送」といういささか厄介な手順を踏まなければなりません。
= ex#59cr = by H_ear
#if 0
psに改行を含む文字列を取得
#endif
* mad for vz!
1 ^\ "" ?.
(mb!=2)?. ;文字ブロックのみ
#k ;スタックに取得
dz[=kz, ;セグメント変更
l=kp..-1, dz], ;[l]ength:文字列長
(l>=Hs || l>=256)?. ;大きすぎれば終了(256:tmpbuf4:pq..149)
h=kp-l+3, ;[h]ead:文字列先頭
t=pq..149, ;[t]mpbuf
i-,
:l ; ---- loop ----
dz[=kz, p=h..i, dz], ;[p]acket 2bytes
t..i=p, (i++*2<l)?>l
3(h) ;スタックには残さない
((t+l).0-, ;文字列終端ヌル
r=t,
:n ; ---- $0d0a → \n ----
&SF(r,$0d)
(r)?{ r..0='n\', >n } ;改行を\nに変換
&Sh(ps,t) vp..39-, ;tmpbuf4 → ps
:z ; ---- exit ----
mp=2, &m("%s",ps) ;#Gに似せた振る舞い
&d #] .
*
[Variables]
l [l]ength
h [h]ead
t [t]mpbuf
i counter
p [p]acket
[Buffers]
ps(Hs)
tmpbuf4(pq..149:256)
[History]
2005/07/28 0.10 初版
#G
の拡張として改造したものである。テキストスタックの大きさは64KBありますが,常にそのような大きさのブロックを格納できるわけではありません。また,選択範囲を切り出して加工するようなマクロを書いた場合,選択範囲がテキストスタックに納まることを前提にしてしまうと,マクロの使用者側に制約がでてしまいます。これは極力避けたいところです。
さて,巨大なブロックに対してカットコマンド#y
を実行すると,先頭からの部分がカットされ,「ブロックが大きすぎます」
というメッセージが出て,カットできなかった残りの部分はブロック選択状態のまま残ります。ブロックモードが解除されていない状態です。このときブロックモードを示す変数mbは真の値となっていますが,他のテキストは特別な影響を受けるわけではありません。mac16.docではグローバル変数として扱われていますが,テキスト構造体内wa.5
に値を持つ編集テキスト変数とも考えられます。したがってカット後もブロックモードであれば,別のテキストにスタックの内容をはき出して戻り,残りのブロックに対して#y
を実行することができます。
a=id,
#R "nul" #m (s)?'y' b=id,
&#T(-a)
(-1){
#y
&#T(-b) #i
&#T(-a) (mb)??{} ;巨大ブロック対応移動
}
テキストの切替は,一応安全策を採ってテキストidを使っていますが,通常は次のように単純な表裏テキスト切替でも大丈夫かもしれません。
#R "nul" #m (s)?'y' #W
(-1){
#y
#W #i
#W (mb)??{} ;巨大ブロック対応移動
}
コマンド#A
(#76
:ブロック範囲の既存ファイルへの追加)が用意されていますので,このコマンドを使用するだけです。ただし,マクロの中で使用する場合には,あらかじめ追加先のファイル名を用意しておかなければなりません。
#Aの動作は次のようになっています。
「…は見つかりません. 新規ファイルですか?」と確認メッセージが表示されて応答待ちになる。
範囲指定がなければ強制保存されるわけですが,この意味は今ひとつわかりません。追加されるのはディスク上のファイルではなくて,編集テキストだからです。(セーブ後に何か入力してから修正フラグを強制的にオフにして#Aを実行しても,書き加えた部分は追加先ファイルにも反映されます。)
ファイル名入力窓で使用されるヒストリーバッファはpf(ファイル名履歴)です。同じファイルに追加を行う場合,前項の巨大ブロックを複写・貼付けする手法を用いなくても,大きなブロック範囲を別のファイルに複写できるということになるので,条件さえ合えば速度面で有利でしょう。
1 ^\ "" ?.
&Sc(pf,"hoge") ;既存ファイル指定
#A #e #m ;履歴を呼び出す
次のようなこともできます。
1 ^\ "" ?.
mx+, ly[, ln[, mb-,
&Sc(pf,wa+146)
(10){
#A #e #m
#L #e #m
&m("%d",mg..4)
}
mx-, &#J(r]) ky], &d
*
こんなひねった話がFAQかという問題はありますが,誰でも一度は考えることかと思います。ブロック範囲をブロックした(笑)全体に対する置換というわけですね。
= blockout.def = by H_ear
ブロック範囲外のみ置換処理対象とするサンプル
* mad for vz!
1 ^^ "" ?. (mb-1||vw-1)?. &s ;行ブロック かつ vwxありなら実行
#b &02 ;ブロックを消してもブロック範囲は記憶
#B &02 ;ブロックの反対側へ
&d #] .
2: #56 "\x ^$$" #m #m ;Thanks to H-oka(__)
(xc>0)?"fy" ?"by" ;fore or back
*
ブロック範囲は再度設定しない限り有効ですので,ブロックの先頭から後方置換,ブロックの末尾から前方置換を実行するという手順です。
置換対象文字列の設定を繰り返すのが無駄とも思えますが、#57
(直前の置換の再実行) を使うには工夫が必要なので,結局この方法がpsをいじったりするよりも楽なようです。なお,検索方向や一括/逐次確認といった置換モードの値はvp..38
にありますので,この値をいじることによっても可能です。
#56 | All | Forward | Backward |
Yes | 256($100) | 257($101) | 258($102) |
No | 0($0) | 1($1) | 2($2) |
ただし決定的に問題なのは、ブロック範囲の大きさによってはxcの判定が混乱する可能性があるということです。xcの値を決定するのはcp(カーソル位置のオフセット)とブロックマーク位置の比較であるため,符号が反転するポインタをまたいでxcが0という判定になる可能性があるため,それこそ万にひとつの可能性とは言え見過ごせないと考える人もいらっしゃるでしょう。
簡単な解決方法は,少々泥臭いのですが,ブロック範囲を作業テキストに切り出しておいて,残りのテキストに対して一括置換を実行し,置換後は元の位置にカーソルが戻りますので,その位置に切り出しておいた保護テキストを挿入するというところでしょうか。
ブロックの挿入や貼り付けを行った後で,そのブロックの範囲を再現させたいときがあります。ブロック操作の直後であれば,次のマクロで再現できます。
1 ^\ "" ?. mb=r, &d
しかし,ブロック操作の直後にちょっとでもカーソルを動かすなどしてしまうと,返り値rは直前のコマンドの返り値に置き換わってしまって,カーソルを元の位置に戻したにもかかわらずブロックを再現できないという状況になります。
この問題に対処したものが,上村郁夫氏の新「Reblock」です。
1 ^B "Reblock[\]" ;Ikuo
?. (ks&1)??{ #b. }
&s #B n=ln, #B (ln==n) mb+, ? mb=2, &d
ブロックマークの先頭と末尾を行き来して,同一の論理行中に納まっていれば文字ブロックと判定しています。これなら多少カーソルを動かして範囲を調整することも可能です。
別の問題として,複数の行をまたいだ文字ブロックを貼り付けた直後などは,所期のブロック範囲を再現することはできません。この問題を解決するには,ブロックの挿入(#i
)ではなく貼り付け(#p
)を行って,次のマクロを実行するという手もあります。このマクロはH岡教授によるもので,本来の用途はブロック記憶直後にブロック範囲を再現させるものです。
1 ^\ "" ;H-oka
?. dz[=kz, mb=kp.-3, dz], &d
ブロック再現は,なかなか完璧に動作させるのは難しいようですが,Ek(ブロック挿入後,カーソルをブロックの最後へ移動する)が有効の場合に限れば,行ブロックを挿入するとカーソルが行頭に移ることに着目することにより,次のような判定を加えることで行またぎ文字ブロックにもそこそこ対応できるようです。
1 ^B "Reblock[\]" ;Ikuo + H_ear(lx+lh) (Ek+)
(ks&1)??{ #b. }
&s #B n=ln, #B (ln==n || lx+lh) mb+, ? mb=2, &d