Hidemaru Q and A

第III部〜秀丸マクロのいろはにほへと
 秀丸マクロで文字列を編集する


●秀丸マクロで文字列を編集する

ここでは、秀丸マクロで文字列を編集するときによく使うコマンドの基本的な解説と、実際にコマンドを使う上でいくつか注意すべき点をまとめました。
秀丸マクロで文字列を編集するには、大きく分けて、秀丸ウィンドウ上でカーソル移動したり範囲選択したり、直接文字列を挿入したりして処理する方法と、文字列変数に取り込んで変数の中で処理する方法があります。それらの処理を行うにあたって、範囲選択の方法や、文字列変数に取り込む方法、2バイト文字の扱いなどについて主に説明します。

【1】改行を挿入するには(insertreturn; とinsert "\n";)
【2】カーソル位置の文字が何かを知るには(code キーワード)
【3】任意の座標にある文字が何かを判断するには(moveto,カーソル移動系コマンド)
【4】範囲選択するには
【5】範囲選択を解除するには
【6】文字列型変数に取り込むには:その1(gettext文)
【7】範囲選択されているかどうか判断するには(selectingキーワード)
【8】文字列型変数に取り込むには:その2(クリップボード経由)
【9】変数内の文字が何かを知るには(ascii関数)
【10】文字列中の先頭以外の文字が1バイト文字か2バイト文字かを区別するには(midstr文等への注意事項)

【1】改行を挿入するには(insertreturn; とinsert "\n";)

秀丸マクロで改行を挿入するには、どうすればよいでしょうか?。

その名もずばり、insertreturnという命令があります。
insertreturn文は、「改行キーを押したのと全く同じ働き」をします。
秀丸ヘルプの「挿入系文」にも説明されているように、insertreturn文は現在のモードが上書きモードか挿入モードかによって動作が異なります。
上書きモードの場合、「動作環境」−「カーソル」−「高度なカーソル」の「上書きモード時のリターン」の設定によって動作が変わります。「カーソル移動する」が選択されていると、改行は挿入されず、カーソルが次行行頭に移動します。
また「ファイルタイプ別の設定」−「全般」の、自動インデントに関する設定に従った動作を行います。

「改行キーを押したのと全く同じ働き」をさせたい場合は、insertreturn文が便利ですが、上書きモードでも挿入モードでも必ず改行を挿入したい場合はどうするのが良いでしょうか?。
insert文で改行文字を挿入するのが簡単です。

(例) 改行を挿入するには

insert "\n";

 または

insert "\x0a";

または、上書きモードの時、一旦、挿入モードに変えて、insertreturnしてまた上書きモードに戻してあげる方法もあります。
この場合、インデントの設定に従った動作を行います。

if(overwrite){
overwriteswitch;
insertreturn;
overwriteswitch;
} else {
insertreturn;
}

【2】カーソル位置の文字が何かを知るには(code キーワード)

現在のカーソル位置にある文字が、1バイト文字か、2バイト文字かを調べるにはどうすれば良いでしょうか?。

codeというキーワードがあります。
codeキーワードは、カーソル位置の文字コードを表します。
このキーワードは2バイト文字に対応しており、2バイト文字の場合は2バイトのコードを返します。例えば全角空白の場合は0x8140になります。
※実際に編集中のテキストの文字コードには関係なく、Shift-JIS の文字コードを返します。

(例) 現在のカーソル位置にある文字が、1バイト文字か、2バイト文字かをメッセージボックスで表示する

if (code > 0xff) {
message "2バイト文字です";
} else {
message "1バイト文字です";
}

秀丸マクロの数値型は4バイトであり、0x8000以降も正の値となるので、上記の書き方でOKです。また16bit版秀丸でも同じです。

(例) または、以下でもOK

if (code && 0xff00 != 0) {
message "2バイト文字です";
} else {
message "1バイト文字です";
}

ここで他の特殊文字の例も見ましょう。

(例) 改行文字を判定する

if (code == 0x0d) {
message "改行文字です";
}

以下の書き方では改行文字と判定できません。insert文で改行を挿入する場合との違いに注意しましょう。

if (code == 0x0a) {
if (code == '\n') {
if (code == ascii("\n")) {
if (code == ascii("\r")) { // これもなぜか ascii("\r") == 10 となり駄目

※秀丸マクロでは、\rも 0x0a として処理されます。

(例) 文末を判定する
eofキーワードが文末を示します。

if (code == eof) {
message "文末です";
}

(例) タブ文字の判定

if (code == 0x09) {
message "タブ文字です";
}

【3】任意の座標にある文字が何かを判断するには(moveto,カーソル移動系コマンド)

任意の座標にある文字が何かを判断するにはどうすれば良いでしょうか。
簡単な方法としては、カーソル移動系コマンドのmovetoでカーソルを移動してから、codeキーワードを使います。

ここで疑問が出てきます。movetoで2バイト文字の真ん中の座標に移動するとどうなるでしょうか?。
答え:2バイト文字の先頭にカーソルが移動します。

逆に、上記の性質を使うと、movetoで指定した座標値と、実際に移動した先の座標値(キーワード x と y で取得出来る)を比較することで、その座標にある文字が、1バイト文字か、2バイト文字の1バイト目か2バイト目かを区別することができます。

movetolineno文の場合、移動指定先が、2 バイトコードの 2 バイト目に来る場合、2 バイト目の最後、すなわち次の文字の先頭にカーソルが移動します。

※タブ文字の上に移動する場合も、2バイト文字と同様の動作になります。
「動作環境」−「高度なカーソル」の「タブ文字の上のカーソル移動した時」の設定は、moveto, movetolineno 文の動作には影響しません。

【4】範囲選択するには

後で文字列変数に取り込んだり、秀丸の編集系コマンド(例えば、tohankakuなど)を使ったりする場合には、範囲選択されている必要があります。
秀丸マクロで範囲選択するにはいくつかの方法があります。

【5】範囲選択を解除するには

範囲選択を解除するにはescape文を使用します。
例えばマクロ文で選択を行う場合、既に範囲選択されていると不都合がある場合があります。そのようなときに利用できます。
ちなみにbeginsel等で範囲選択モードになっているときに範囲選択モードを解除する(選択範囲は残す)場合に使うのはendsel文です。

【6】文字列型変数に取り込むには:その1(gettext文)

ユーザーの操作または上記のマクロ文で範囲選択した後、その選択された文字列を文字列型変数に取り込むにはどうすれば良いでしょうか。

典型的には、以下のようなコーディングを見ることが多くなるでしょう。

$s = gettext(seltopx,seltopy,selendx,selendy);

秀丸マクロでは、範囲選択された文字列を直接文字変数に取り込むマクロ文は有りません。画面上の任意の範囲の文字列を文字変数に取り込むgettext文と、現在の選択範囲の座標値を示すseltopxselendyキーワード(範囲選択関連キーワード)を併用します。

但し注意が必要なのは、seltopx〜selendyキーワードに値が入っているといって、その値どおり範囲選択されているとは限らないことです。
マクロヘルプの「範囲選択関連キーワード」の説明にもあるように、範囲選択が解除された後も、以前の値を保持しているためです。
ということは、上記のコーディングで文字列型変数に取得する場合は、その前に、現在本当に範囲選択されているかを確認する必要があります。

ちなみに、gettext文を実行すると、(gettext文での選択範囲が、秀丸ウィンドウ上で範囲選択されている範囲と重なっているか否かに関わらず)範囲選択は解除されます。

【7】範囲選択されているかどうか判断するには(selectingキーワード)

秀丸マクロから、現在範囲選択されているかどうか判断するには、selectingキーワードを使います。

(例) 範囲選択された場所を文字列変数に取得する

if(selecting) {
$s = gettext(seltopx,seltopy,selendx,selendy);
message "選択範囲は、" + $s + "です";
} else {
message "範囲選択されていません";
}

【8】文字列型変数に取り込むには:その2(クリップボード経由)

便利なgettext文も、文字列の長さが長すぎるとエラーになってしまいます。
長い文字列を取得して処理する必要がある場合は、クリップボードを経由して行うことができます。大まかには、以下の流れになります。
  1. あらかじめ範囲選択する
  2. cutで切り取り、またはcopyでコピーする
  3. beginclipboardread文で、クリップボードからの読み込み開始を宣言する
  4. getclipboard文で、クリップボードから1行(改行文字で区切られた範囲)を読み込む。
使い方の詳細は、秀丸マクロヘルプの「クリップボードと変数のやりとり」を参照してください。

gettext文とクリップボード系文との利点と弱点を比べて、自分が行う処理に合ったものを使いましょう。

【9】変数内の文字が何かを知るには(ascii関数)

変数内の文字のコードを調べるために、ascii関数が用意されています。これは引数として与えた文字列の最初の文字のコードを返します。
また2バイト文字を引数に与えられていると、2バイトのコードが返る便利な関数ですが、逆にそれゆえの落とし穴もあります。
ascii関数を2バイト文字に対して適用した場合、2バイト分をまとめて 16bit整数として返す、という仕様ですが、以下のようにわざと2バイト文字の1バイト目(コード=0x??)を切り出した(1文字からなる)文字列に適用すると、なんと0x??00 という値が返ってきます!

$a = midstr("あ", 0, 1); // 2バイト文字の1バイト目を切り出す
message str(ascii($a)); // == 33280 == 0x8200 が返る

というわけで、2バイト文字の1バイト目かどうかを判断するのに

if (ascii(midstr($s, #i, 1)) & 0x80) {
// 2バイト文字の1バイト目か、半角カタカナだった
}

というようなかっこいい(笑)コードはNGです。
ascii文の仕様からすると、midstr文などで切り出す場合は、2文字切り出すのが良いです。(1文字目が1バイト文字でも次の文字はascii文は無視してくれます。)
というわけで、以下の例のようにしましょう。

if (ascii(midstr($s, #i, 2)) > 0xff) {
// 2バイト文字
}

または

if (ascii(midstr($s, #i, 2)) && 0xff00 != 0) {
// 2バイト文字
}

【10】文字列中の先頭以外の文字が1バイト文字か2バイト文字かを区別するには(midstr文等への注意事項)

前項で、midstr文を使って、なにげなく文字列変数の中から一部を切り出しましたが、実はこれらの文はmoveto文等とは違って、切り出す範囲の先頭が、2バイト文字の2バイト目かどうかは判断してくれません。
何も考えずにmidstr等を使うと、切り出した結果の文字列の先頭が2バイト文字の2バイト目だった場合、別の1バイト文字に化けたり、ゴミになったり(表示させると半角の中点になる)していまいます。

また、切り出す範囲の先頭の1バイト分の文字コードだけを見て、そこが2バイト文字の2バイト目かどうかは、一意に決められません。
秀丸に限らず一般にShift-JISのコード体系では、「2バイト文字の2バイト目」になり得るコードの中には、「2バイト文字の1バイト目」になり得るものが有るためです。

(例) 以下の例では、2バイト文字の2バイト目から始まる2バイトを、別の2バイト文字と誤判断してしまいます。
※例示のため、機種依存文字を使用しています。ご容赦ください。m(_ _)m

$s = "これは@";
message char(ascii(midstr($s, 6, 2))); // @が表示される
$s = "1回一括A";
message char(ascii(midstr($s, 6, 2))); // が表示される!!
//("括"==0x8a87、"A"==0x8741、""==0x8787)

""は0x8787と、1バイト目と2バイト目が同じ値で、いかにも意地悪な例ではありますが、各バイト値だけでは判断できないことがわかると思います。

では、文字列中の任意の位置の文字が1バイト文字か2バイト文字かを区別するには、どうすればよいのでしょうか。
実現方法はいくつか考えられますが、文字列の先頭から順番に確認していくのが基本です。
一例としては、[HMM0074A] ●文字列の後方から文字列を検索 を参照してください。


※この内容は秀丸エディタ ver.3.15の内容を元に書かれています。
今後のバージョンアップによっては、上記の内容が必ずしも当てはまらない場合もあるかもしれません。


目次に戻る