// 引用符を選択するマクロ // quoteselect101beta.0211.mac // 1.01beta // 2005.3.3 // siteCTS // 秀丸専用 // このマクロは、ひとつで " と ' とエスケープにまで対応させてる都合上、操作にかなり癖があります。 // よくわからなければ、「開始引用符の上にカーソルを乗せて」実行してください。 // やることのわりに複雑な構造をしてますが、ファイル編集もループも一切ないので、未知のバグがあっても // 範囲選択を間違えるだけです。 // ■注意 // すべてのパターンで完璧に動作しません。"\\" など、一部の文字列を認識できません。 // カーソル位置からファイル終端までの間に引用符がない場合、重ねて実行した時に不可解な範囲選択をします。 // 引用符が行頭にある時は認識できません。 // はっきり言ってバグですが、実用上で困るパターンはないと思うので放っといてます。 // ■■ 状態による挙動の変化 ■■ // // 最初のうちは苛立つほど不可解な動きをしますが、仕組みがわかれば便利に使える…かも。 // 見つからないからといって、すぐにエラー吐いて終わるんでなく、次の候補を探すなどの努力をします。 // multilineについての説明は下記です。 // ■基本的な動き1(multiline無効時:デフォルト) // 最初に左へ行って、引用符を探します。(改行を越えない) // 行頭までに見つからなかったら、今度は文末方向に引用符を探しに行きます。(改行を越える) // 見つかったら、右へ行って対応する引用符を探します。(改行を越えない) // その間を範囲選択します。(引用符自体は含まない) // 行末までの間に対応する引用符が見つからなかったらエラーを吐いて終わります。 // ■基本的な動き2(multiline有効時) // 最初に文頭方向へ引用符を探しに行きます。(改行を越える) // 文頭までに見つからなかったら、今度は文末方向に引用符を探しに行きます。(改行を越える) // 見つかったら、右へ行って対応する引用符を探します。(改行を越える) // その間を範囲選択します。(引用符自体は含ない) // 文末までの間に対応する引用符が見つからなかったらエラーを吐いて終わります。 // ■重ねて実行 // 上記1も2も、そのまま重ねて実行すると、引用符も含んで範囲選択します。 // さらに重ねて実行すると、次の候補を範囲選択します。(引用符自体は含まない) // そのまま重ねて実行すると、引用符も含んで範囲選択します。 // 以降、実行するたびにだんだん後ろにずれていきます。 // 1の時は行末まできたらエラーを吐いて終わります。 // ■■ 詳細な動作 ■ // // ■引用符が入れ子になってる時 // " を優先します。' は上にカーソルが乗ってる時のみ対応します。 // ■適当に範囲選択して実行した時 // →範囲選択を解除し、次の「範囲選択しないで実行した時」に準じます。 // ■範囲選択しないで実行した時 // 1. カーソルが ' の上にある時 // →次の ' までを選択 // 2. カーソルが ' の上にあり、直前の文字が \ の時(エスケープされている) // →次の \' までを選択 // 3. カーソルが " の上にあり、直前の文字が \ の時(エスケープされている) // →次の \" までを選択 // 4. それ以外 // →カーソルより前の " を探し、それに対応する引用符までを選択 // 例外: 2・3の時に、引用符の前が \\" か \\' だった時 // →エスケープされてないものとして扱う(1. か 4. になる) // ※選択範囲に引用符自体は含まれません。 // ※さらに、これに加えて #multiline の設定(下記)が影響します。 // ※対応がずれているなどして見つからなかった場合、いったん行頭に戻って「最初に見つかった // 適当な引用符」でやり直します。それでも見つからない場合は警告して終了します。 // ■このマクロで範囲選択してから、さらに重ねて実行した時 // 1. 引用符自体も含めて範囲選択 // 2. さらに実行すると、次の引用符の内容を選択(引用符自体は含まない) // 3. さらに実行すると、2. の引用符自体を含んで選択 // 4. さらに実行するたび、後ろにずれていく // ※ 下記の設定で multiline を無効にしてる時は次の行には行きません initialize: //引用符の内容に改行が含まれることを許可するなら1 //HTMLではありえる #multiline = 0; //この設定を変えると、範囲指定してない時に挙動が少し変わります。 // 0 = 開始引用符を「行頭」まで探し、見つからない時は後方へ引き返して「改行まで」探す // 1 = 開始引用符を「文頭」まで探し、見つからない時は後方へ引き返して「文末まで」探す //設定はこれだけ //以下マクロ本文 #currentx = x; #currenty = y; $searchbuffer = searchbuffer; #searchoption = searchoption; disabledraw; disableinvert; quoteselect: // 自動範囲選択 // if (!selecting) { $$code = char(code); //カーソルの文字 // カーソルが引用符の上にあったらエスケープされているか調べる // if ($$code == "'" || $$code == "\"") { left; //左に行ってみる if (char(code) == "\\") { left; //\だったらもうひとつ左へ行ってみる if (char(code) != "\\") { //2連続\ではない(引用符がエスケープされている) $$quotereg = "[^\\\\]\\\\" + $$code; //検索する文字を [^\\]\\" に変更 $$quotestr = "\\" + $$code; // \\" か \\' } else { $$quotereg = "[^\\\\]" + $$code; //検索する文字を [^\\]" に設定 $$quotestr = $$code; // " か ' } right 3; //引用符の右へ行く } else { //エスケープされてない $$quotereg = "[^\\\\]" + $$code; //検索する文字を [^\\]" に設定 $$quotestr = $$code; // " か ' right 2; //引用符の右へ行く } // カーソルが引用符の上にない時 // } else { $$quotereg = "[^\\\\]\""; // [^\\]" に設定 $$quotestr = "\""; // " のみ ##x = column +1; ##y = lineno; searchup $$quotereg, regular; //開始引用符を探す(文頭側) //(ない || (改行を許可しない && 前の行に見つかっている)) → 開始引用符を再検索(文末側) if (result == 0 || (#multiline == 0 && ##y != lineno)) { movetolineno 1, ##y; //元の行の先頭に移動 left; //カーソルの直後はヒットしないので左へ行く //文末に向かって引用符を探す(エスケープも含む) searchdown "([^\\\\]\\\\|[^\\\\])[\"']", regular; //→ ([^\\]\\|[^\\])["'] if (!result) { //それでもない #currentx = x; #currenty = y; call error "このカーソルより後ろに引用符がありません"; } call getquote; //ペアマッチで拾われた先頭の無駄文字を削除 $$quotereg = "([^\\\\]\\\\|[^\\\\])\\" + $$return; //→ ([^\\]\\|[^\\])\' || ([^\\]\\|[^\\])\\' moveto selendx, selendy; //right strlen($$quotestr); //引用符の右へ移動 //(ある && (改行を許可する || (改行を許可しない && 同じ行に見つかっている))) } else moveto selendx, selendy; } escape; //この時点で開始引用符の右にカーソルがある // 引用符の内容を選択 // ##y = lineno; //行をまたがったか判定するためのバッファ beginsel; //選択開始 searchdown2 $$quotereg, regular; //対となる引用符まで選択 right; //正規表現のペアマッチで必ず1文字足りないので選択範囲を拡張する endsel; // 対応する引用符が見つからない // if (!result) { #currentx = x -1; #currenty = y; //カーソルを問題位置へ移動させる call error "対となる引用符 " + $$quotestr + " が見つかりません。\nここのカーソル位置より後ろに " + $$quotestr + " がある必要があります。"; // 改行を許可しない && 複数行選択になっている // → 開始引用符に戻って次の引用符を検索 } else if (#multiline == 0 && ##y != lineno) { escape; gosearchstarted; #retry = #retry +1; if (#retry) { #currentx = x -1; #currenty = y; call error "カーソル位置の " + $$quotestr + " から改行までの間に " + $$quotestr + " がありません。"; call exit; } searchdown2 "([^\\\\]\\\\|[^\\\\])[\"']", regular; //→ ([^\\]\\|[^\\])["'] moveto selendx -1, selendy; //ひとつ戻って引用符の上に乗る call quoteselect; //再帰 } //→exit; // 選択中に実行 // } else { ##stx = seltopx; ##sty = seltopy; ##sex = selendx; ##sey = selendy; escape; // 引用符を含んでない時(奇数回目) // → 引用符を含んで選択しなおす call inquote ##stx, ##sty, ##sex, ##sey; //選択範囲が引用符の中に入っていれば真 if (##return) { moveto ##stx -1, ##sty; //カーソルを引用符の上に移動 call getescape; //エスケープされてたら \ を返す $$quote = $$return + char(code); left strlen($$quote) -1; //引用符の上に移動する時に-1したので移動量も-1する beginsel; moveto ##sex, ##sey; right strlen($$quote); endsel; goto exit; } // 引用符を含んでいる時(偶数回目) // → 次の引用符を選択 call outquote ##stx, ##sty, ##sex, ##sey; //選択範囲内の両端が引用符なら文字数を返す(\"なら2) if (##return) { moveto ##stx + ##return, ##sty; searchdown2 "([^\\\\]\\\\|[^\\\\])[\"']", regular; //→ ([^\\]\\|[^\\])["'] moveto selendx, selendy; left; } escape; call quoteselect; //再帰 } goto exit; // サブ関数群 // //##1 = seltopx, ##2 = seltopy, ##3 = selendx, ##4 = selendy inquote: moveto ##1, ##2; left; if (char(code) == "\"" || char(code) == "'") { ##code = code; moveto ##3, ##4; if (char(code) == "\\") right; if (code == ##code) //同じ引用符か? ##n = 1; moveto ##1, ##2; } else right; return ##n; outquote: moveto ##1, ##2; if (char(code) == "\\") { ##escape = 1; right; } if (char(code) == "\"" || char(code) == "'") { ##code = code; moveto ##3, ##4; left; if (code == ##code) //同じ引用符か? ##n = 1 + ##escape; moveto ##1, ##2; } else right; return ##n; getquote: $$1 = gettext(seltopx, seltopy, selendx, selendy); $$1 = rightstr($$1, strlen($$1) -1); return $$1; getescape: ##x = x; ##y = y; left; if (char(code) == "\\") { left; if (char(code) != "\\") $$escape = "\\"; right; } right; return $$escape; altquote: if ($$1 == "\"") return "'"; else if ($$2 == "'") return "\""; message "error!"; goto exit; error: enabledraw; message $$1; disabledraw; endsel; escape; moveto #currentx, #currenty; exit: setsearch $searchbuffer, #searchoption; enableinvert; enabledraw; endmacro;