「対応する括弧に移動」の版間の差分

提供:MeryWiki
ナビゲーションに移動 検索に移動
(「masme版」を追加。目次を表示。)
(更新履歴の書式を変更)
(2人の利用者による、間の18版が非表示)
1行目: 1行目:
対応する括弧に移動します。「shift」変数を「true」に設定した場合は選択しながら移動します。
<div>__TOC__</div>
<div>__TOC__</div>


= Kuro, kurama 版 (2009/08) =
= Kuro, kurama 版 (2009/08) =
* 「shift」変数を「true」に書き換えると、選択しながら移動します。


<source lang="javascript">
<source lang="javascript">
124行目: 123行目:
Redraw = true;
Redraw = true;
</source>
</source>
<br>


= masme 版 (2014/02) =
= masme 版 =
Get/SetActivePos メソッドと正規表現検索を活用したバージョンです。
* ステータスバーに括弧間の文字数を表示するようにしました。
* 正規表現検索を利用することで、より高速に動作するようになりました。
* Kuro, kurama 版の不安定な挙動を調整しました。
* ステータスバーに括弧間の距離(文字数)を表示するようにしました。
** カーソル右側が改行か[EOF]の場合、左側の括弧に反応するのを修正。例外なく右側のみ反応するようにした。
* Kuro, kurama 版に存在するバグっぽい挙動を修正しました。
** 開き括弧の直前ではなく、直後に移動する場合<sup>※</sup>があるのを修正。直前に移動するよう統一した。<br>※カーソル右側が改行か[EOF]の場合。改行をまたぐ移動になる場合。対応する括弧が行頭にある場合。
** カーソル右側に改行がある場合に限り、左側の括弧に反応するのを修正。例外なく、右側の括弧にのみ反応するようにした。
** 開き括弧の「直前」ではなく「直後」に移動する場合があるのを修正。括弧の「直前」に移動するよう統一した。


== 更新履歴 ==
; 2019/04/12
* Quit() → break ラベル文に変更。
; 2017/05/27
* 処理方法を正規表現検索 → 文字列検索に変更し、高速化。
* 対応する括弧が見つからなかった場合、選択解除する → しない仕様に変更。
; 2014/02/05
* 初版公開。
== ソースコード ==
<source lang="javascript">
<source lang="javascript">
//■対応する括弧に移動
//■対応する括弧に移動
// 2014/02/05
// 2014/02/05-2019/04/12


//■括弧の定義(0+2n:開き/1+2n:閉じ)
var BRACKET = "()<>[]{}「」『』【】()";
//■範囲選択(true:する/false:しない)
//■範囲選択(true:する/false:しない)
var shift = false;
var SHIFT = false;
//■括弧の定義(0+2n:開き/1+2n:閉じ)
 
var br = "()<>[]{}「」『』【】()";
quit: {
var Sel = Document.Selection;
var txt = Document.Text;
var sPos = Sel.GetActivePos(), ePos = sPos; //カーソル始点, 探索位置&終点
var sBrc = txt.charAt(sPos); //カーソル右側の文字を取得
var iBrc = BRACKET.indexOf(sBrc);
if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; }
var nest = 1, s, e;
if (iBrc % 2) { //◆閉じ括弧の場合、先頭方向へ探す
  var eBrc = BRACKET.charAt(iBrc-1); //対応する開き括弧を取得
  while (nest) {
    s = txt.lastIndexOf(sBrc,ePos-1);
    e = txt.lastIndexOf(eBrc,ePos-1);
    if (e===-1 || ePos<=0) break;
    if (e<s) {nest++; ePos=s;} else {nest--; ePos=e;}
  }
} else { //◆開き括弧の場合、末尾方向へ探す
  var eBrc = BRACKET.charAt(iBrc+1); //対応する閉じ括弧を取得
  while (nest) {
    s = txt.indexOf(sBrc,ePos+1);
    e = txt.indexOf(eBrc,ePos+1);
    if (e===-1) break;
    if (s<e && s!==-1) {nest++; ePos=s;} else {nest--; ePos=e;}
  }
}
if (nest!==0) { Status = "対応する括弧が見つかりませんでした"; break quit; }
Sel.SetActivePos(sPos);
Sel.SetActivePos(ePos, SHIFT);
Status = "対応する括弧の距離: "+(Math.abs(ePos - sPos)-1)+"文字";
}
</source>
<br>
 
= masme 版用 機能拡張コード =
== 範囲選択の拡張 ==
(sukemaru 2019/04/24)<br>
[[GetKeyState.exe(キー状態取得実行ファイル)]] を利用して、'''カッコを含めた全体を範囲選択'''、または '''カッコの内側部分だけを範囲選択''' するための追加コードです。<br>
[[#masme 版|masme 版]] (2019/04/12) のソースコードに追加コードを挿入してください('''2ヵ所''')。<br><br>
<span style="color:#c00;">ツールバーアイコンやメニュー(マクロメニューや右クリックメニュー)からマクロを実行するさいに Ctrl または Shift キーを押しながら実行すると、拡張コードが適用されます。 </span><br>
 
* '''Ctrl''' キーを押しながらマクロを実行した場合には、カッコを含めた全体を範囲選択します。
::<span style="background:#bfdfff;">【 Ctrl ありでの選択範囲 】</span> → ※カーソルは 開きカッコの左 (選択範囲の先頭)
* '''Shift''' キーを押しながらマクロを実行した場合には、カッコの内側部分だけを範囲選択します。
:: 【<span style="background:#bfdfff;"> Shift ありでの選択範囲 </span>】 → ※カーソルは 閉じカッコの左 (選択範囲の末尾)
* '''Ctrl'''+'''Shift''' キー両方が押されている(または両方とも押されていない)場合には、masme 版の設定変数「SHIFT」の true / false で指定された動作をします。
::<span style="background:#bfdfff;">【 SHIFT=true のばあい </span>】
※「対応する括弧に移動」を連続で実行(トグル移動)できるよう、拡張機能で範囲選択したあともカーソルは開きカッコか閉じカッコいずれかの「カッコの左」にセットされます。
<br><br>
※ ショートカットキーでマクロを実行する場合、Ctrl キーを含むパターン、Shift キーを含むパターン、Ctrl と Shift 両方を含む(または両方を含まない)パターンのみっつのショートカットキーを登録する必要があります。 すでに masme 版「対応する括弧に移動」マクロにショートカットキーを割り当てて使用している場合、この追加コードを使用するにあたり、ショートカットの再割り当てをしてください。<br>
e.g. Ctrl+F9 / Shift+F9 / Ctrl+Shift+F9(または Alt+F9 など)
<span style="color:#c00;">※ 外部実行ファイル GetKeyState.exe を2回呼び出すため、拡張コードの処理にはタイムラグが生じます。選択範囲が確定するまで Ctrl または Shift キーを押しっぱなしにしてください。</span><br><br>
※ GetKeyState.exe のダウンロードや導入方法については、マクロライブラリ内の「[[GetKeyState.exe(キー状態取得実行ファイル)]] 」のページへ
<br><br>
 
;追加コード➀<br>
15行目<br>
<syntaxhighlight lang="javascript" inline>if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; }</syntaxhighlight><br>
の下に追加する
<source lang="javascript">
// ▼ GetKeyState を利用した機能拡張 ▼
  // Ctrl / Shift キーの状態を取得
  var $ctrl = 0, $shift = 0;
  var getKeyState = editor.FullName.replace(/mery\.exe$/i,"") + "Macros\\GetKeyState.exe";
  var WshShell = new ActiveXObject("WScript.Shell");
  $ctrl = WshShell.Run('"' + getKeyState + '" ctrl',0,true);
  $shift = WshShell.Run('"' + getKeyState + '" shift',0,true);
// ▲ GetKeyState を利用した機能拡張 ▲
</source>
※「[[#カーソルの右側にカッコがないなら左側のカッコで|カーソルの右側にカッコがないなら左側のカッコで]]」の改造コードと併用する場合は、改造コードの下に挿入してください。
<br><br>
;追加コード➁<br>
37行目<br>
<syntaxhighlight lang="javascript" inline>Status = "対応する括弧の距離: "+(Math.abs(ePos - sPos)-1)+"文字";</syntaxhighlight><br>
の下に追加する
<source lang="javascript">
// ▼ GetKeyState を利用した機能拡張 ▼
  // ※SHIFT 変数による選択範囲を上書きする(SHIFT = false; でも拡張コードでの範囲選択は有効)
  if ($ctrl || $shift) {
    var tPos = Math.min(sPos,ePos);
    var bPos = Math.max(sPos,ePos);
    // Ctrl キーだけ押し下げ時にはカッコを含めた全体を範囲選択
    if ($ctrl>0 && $shift==0) {
      Sel.SetActivePos(tPos);
      Sel.SetAnchorPos(bPos+1);
    }
    // Shift キーだけ押し下げ時にはカッコの内側だけを範囲選択
    else if ($ctrl==0 && $shift>0) {
      Sel.SetActivePos(bPos);
      Sel.SetAnchorPos(tPos+1);
    }
  }
// ▲ GetKeyState を利用した機能拡張 ▲
</source>
* 2019/05/12 変数名間違いを修正 sel → Sel (sukemaru)
<br><br>
 
== カーソルの右側にカッコがないなら左側のカッコで ==
(sukemaru 2019/12/01)<br>
<span style="color:#c00;">カーソルの右側にカッコがないとき</span> で、左側にカッコがあるなら、そのカッコに対応するカッコに移動します。<br>
* <b style="color:#c00;">右側のカッコ優先</b> です。<br>
: 移動後は対応する '''カッコの左側''' にカーソルがセットされます(連続で実行しても元のカッコの左側に)。
<br>
[[#masme 版|masme 版]] (2019/04/12) のソースコードを以下のとおり書き換えてください。
 
'''変更箇所''': 15 行目<br>
<syntaxhighlight lang="javascript" inline>if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; }</syntaxhighlight><br>
をコメントアウトして
<source lang="javascript">
// if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; } // ※ 1行 コメントアウト
 
// ▼ カーソルの左側にカッコがある場合に対応 ▼
  if (iBrc>-1 && sBrc) { /* empty */ ; }
  else if (BRACKET.indexOf(txt.charAt(sPos-1))>-1) {
    sPos = ePos = sPos-1;
    sBrc = txt.charAt(sPos);
    iBrc = BRACKET.indexOf(txt.charAt(sPos));
  }
  else { Status = "カーソルの側に括弧がありません"; break quit; }
// ▲ カーソルの左側にカッコがある場合に対応 ▲
</source>
 
※「[[#範囲選択の拡張|範囲選択の拡張]]」コードを併用する場合は、この変更箇所の下に「追加コード①」を挿入してください。
<br><br>
 
== カッコの種類を追加 ==
(sukemaru, 2019/12/01)<br>
[[#masme 版|masme 版]] (2019/04/12) のソースコードにカッコの種類を追加します。
:※ Mery 本体のオプション機能の「対応する括弧を強調する」には非対応です。
<br>
4 行目<br>
<syntaxhighlight lang="javascript" inline>var BRACKET = "()<>[]{}「」『』【】()";</syntaxhighlight><br>
の下に追加する
<source lang="javascript">
BRACKET += "<>[]{}「」〖〗⦅⦆〈〉《》〚〛〔〕〘〙‹›«»≪≫〝〟‘’“”︵︶︿﹀︽︾︷︸﹁﹂﹃﹄︻︼︹︺";
</source>
 
※ 左右のカッコのかたち(文字)が異なるペアしか登録できないので、半角二重引用符 <syntaxhighlight lang="javascript" inline> "" </syntaxhighlight> や 半角一重引用符 <syntaxhighlight lang="javascript" inline> '' </syntaxhighlight> などは不可。
<br><br><br>
 
= sukemaru 版 =
【自家用】 '''対応するカッコを選択''' (sukemaru, 2019/12/02)
<br><br>
[[#masme 版|masme 版]] (2019/04/12) をベースに、<q>ジャンプ後の範囲選択の拡張</q>、 <q>キャレットの左側にあるカッコ</q> に対応、 <q>カッコの種類を追加</q>、 <q>タグなどの複数文字のペアに対応</q> などの改造をした【自家用版】も貼っておきます(おもっていたほど便利ではないアルファバージョンですが)。
<div class="warningbox">
要:[[GetKeyState.exe(キー状態取得実行ファイル)]]
</div>
<source lang="javascript" style="height:60em; overflow:auto;">
#title  = "対応するカッコを選択"
#tooltip = "対応するカッコに移動・選択"
// #icon    = "Mery用 マテリアルデザインっぽいアイコン.icl" ,127
 
/**
* ---------------------------------------------------------
* 「対応するカッコに移動」
* Original Coded by: Kuro, kurama,      ( - 2009/08)
*                  ; masme,(2014/02/05 - 2019/04/12)
* 「対応するカッコを選択」
* Modified by: sukemaru,  (2019/04/24 - 2019/12/05)
* ---------------------------------------------------------
* ・masme 版「対応するカッコに移動」マクロ (2019/04/12) をベースに、
*  キャレットの左側にあるカッコの取得や、
*  タグなどの複数文字同士のペアに対応できるようにしました。
*
* ・以下の優先順位でカッコを検索します。
*  ➀ カッコを範囲選択しているとき、対応するカッコを検索
*  ➁ 選択範囲が HTML タグ形式 <hoge> のときは </hoge> を、
*      終了タグ </fuga> の ときは <fuga> を検索(オプション設定)
*  ➂ キャレットの右側にカッコがあるとき、対応するカッコを検索
*  ➃ キャレットの左側にカッコがあるとき、対応するカッコを検索(オプション設定)
*
* ・pizz 氏作成の "GetKeyState.exe(キー状態取得実行ファイル)" を利用して
*  対応するカッコに移動後の範囲選択状態を選択できます。
*  GetKeyState.exe を Mery\Macros フォルダに用意してください。
* ▼ Ctrl, Shift キーを押しながらマクロを実行したとき ▼
*
* ・Ctrl + Shift キー両方を押しているとき
*    → 対応するカッコの右側にキャレットを移動します。
* ・Ctrl キーのみ押しているとき
*    → 対応するカッコをふくむ全体(外側)を範囲選択します。
* ・Shift キーのみ押しているとき
*    → 対応するカッコの中身(内側)を範囲選択します。
*
* ・Ctrl / Shift キーを押していないときは
*    → 対応するカッコだけを範囲選択します。
*
* 【仕様上の制限】
* ・カッコの定義は、通例として前後関係が定まっているもののみとしてください。
*  同一の記号を使う '一重引用符' や "二重引用符"、―ダーシ― などでは、
*  開き ← 閉じ の方向に戻れないので使用できません。
* ・\ などでエスケープされたものを考慮せずにジャンプします。
* ・HTML/XML タグなどのように < > をカッコとして使用しているものと
*  比較・計算で使用している半角不等号 < > とを区別しません。
* ・<div id="hoge"> や <div style="fuga"> のような属性つきのタグを
*  個別に登録することはできません。
*  (終了側のタグが同一 </div> なので、</div> から前方に検索したときに
*  <div> や <div class="piyo"> などと区別をつけられない)
*
*  (2019/12/05)
* ・設定項目 tagEnable と altTagInvert を追加。
*  <hoge> </hoge> 形式のタグ全般に対応 (配列 brackets2 への登録不要)。
*
* ※ タグを範囲選択しているときのみ有効。
*    範囲選択なしの場合、キャレットの左または右にあるタグを自動取得しません
*  (配列 brackets2 に登録されたペアであれば、自動取得しますが…)。
* ※ 開始タグ側から実行する場合、キャレットがタグの先頭位置にあるとき、
*    または選択範囲に属性部分を含めていないときのみ有効。
* ※ 基本的に、開始タグは "<" の直後が空白でないこと(正規表現 ^<\w+ )、
*    終了タグ内には空白文字を含めないこと(正規表現 \/\w+>$ )を条件とします。
*    e.g. < hoge > </hoge > などは NG.
*    <fuga/> <!PIYO> を範囲選択している場合も通常のカッコ囲いとして扱います。
* ※ <hoge> の hoge 部分が有効な HTML タグであるかをチェックしないので、
*    <ABC> 形式のカッコにたいしては、通常の「対応するカッコに移動・選択」
*    の動作になりません(設定項目 tagEnable = false で無効化できます)。
*  「対応するカッコがない」 となって両端の "<" と ">" での移動ができなくなります。
* ※ tagEnable = true の場合、配列 brackets2 にタグが登録されていると
*    うまく動作しないことがあります。
* ※ tagEnable = false の場合でも、配列 brackets2 に登録されたペアであれば
*    通常のカッコと同様に扱います。
* ※ 同種のタグがネストすることはないはずですが、
*    便宜上、通常のカッコと同様にネストのチェックをします。
*/
 
 
// ---------- ▼ 設定項目 ▼ ---------- //
 
// ■ <hoge> </hoge> 形式のタグを検索対象にする(優先オプション)
var tagEnable = true;
 
// ■ Alt キー押し下げで tabEnable の true / false を一時的に反転させる
var altTagInvert = true;
 
// ■ キャレットの左側のカッコも検索対象にする(true でも右側が優先)
var leftsideEnable = true;
 
// ■ Shift キーのみ押し下げのとき、実行後の選択範囲が複数行になるなら
//    カッコの内側の行だけを範囲選択する
var insideLines = false;
 
// ■ カッコの定義1 (開きカッコと閉じカッコをペアにして列挙する)
var brackets1 = "()<>[]{}「」『』【】()" // Mery で強調表示可能なカッコ
brackets1    += "<>[]{}「」〖〗⦅⦆〈〉《》〚〛〔〕〘〙"
              + "︵︶︿﹀︽︾︷︸﹁﹂﹃﹄︻︼︹︺"
              + "‹›«»≪≫〝〟‘’“”" ; // ―——
 
// ■ カッコの定義2 (タグなどの複数文字のペアを配列に列挙する)
// ※ おなじ開きカッコを持つペアや、おなじ閉じカッコを持つペア、
//    braclets1 に含まれているカッコ1文字だけの要素は不可。
var brackets2 = [];
brackets2 = [
  "\\(" , "\\)" , "\\[" , "\\]" , "\\{" , "\\}" ,"[[" , "]]" ,
  /* HTML/XML 形式のタグは登録不要 (2019/12/05) */
  // "<b>" , "</b>" , "<a>" , "</a>" , "<pre>" , "</pre>" ,
  // "<b" , "/b>" , "<a" , "/a>" , // <abc> や <blah> に反応しなくなるかも?
  // "<div>" , "</div>" , "<div" , "/div>" ,
  "/**" , " */" , "/*" , "*/" , "<!--" , "-->"
];
 
// ---------- ▲ 設定項目 ▲ ---------- //
 
 
// Ctrl キーと Shift キーの状態を取得
var $ctrl = 0,  $shift = 0,  $alt = 0;
var gks = editor.FullName.replace( /[^\\]+$/, "" )
        + "Macros\\GetKeyState.exe";
if ( new ActiveXObject( "Scripting.FileSystemObject" ).FileExists( gks ) ) {
  var WshShell = new ActiveXObject( "WScript.Shell" );
  $ctrl  = WshShell.Run( '"' + gks + '" control', 0, true );
  $shift = WshShell.Run( '"' + gks + '" shift', 0, true );
  if ( altTagInvert ) {
    $alt = WshShell.Run( '"' + gks + '" alt', 0, true );
    if ( $alt === 1 ) { tagEnable = ! tagEnable; }
  }
}
 
var start = new Date(); // ここでタイマースタート
 
// カッコの定義をひとつの配列に統合する (brackets2 が前、brackets1 が後ろ)
var brackets = brackets2.concat( brackets1.split( "" ) );
var len = brackets.length;
 
var d = editor.ActiveDocument,  sel = d.selection;
var dt = d.Text,  st = sel.Text;
var act = sel.GetActivePos(),  anc = sel.GetAnchorPos();
var tPos = Math.min( act, anc ),  bPos = Math.max( act, anc );
var txt, brc, bLen, sPos, ePos, sBrc, iBrc;
if ( tagEnable ) {
  var isTag = false,  isEndTag = false;
  var pos, gt;
}


(function(){
// 選択範囲がカッコか
  var sel = Document.Selection;
check: {
  var pos = sel.GetActivePos(); //カーソル位置を記憶
  if ( st ) {
  var $br1 = Document.Text.charAt(pos) || -1; //右側の文字を取得/[EOF]の場合、-1 に
    for ( var i = 0; i < len; i ++ ) {
  var $bri = br.indexOf($br1);
      brc = brackets[i];
  switch (true) { //括弧の判定、探索準備
      if ( st === brc ) {
  case ($bri == -1): //◆括弧でない場合
        sPos = ePos = tPos;  sBrc = brc;  iBrc = i;
    Status = "カーソル右側に括弧がありません";
        break check;
     Quit();
      }
  case ($bri % 2 == 0): //◆開き括弧の場合
    }
    var $br2 = br.charAt($bri+1); //対応する閉じ括弧を取得
    // 選択範囲が <HTML> タグ(開始タグ)か
    var $text = Document.Text.slice(pos+1);
    // ※ 開始タグの閉じカッコを含めずに "開きカッコ" とする
    //※探索範囲は括弧の直後から文末まで
    if ( ! sBrc && tagEnable && ! /(?:\/\s*>|\W>)$/.test( st ) &&
    break;
        ( /^<\w+[\t >]?$/.test( st ) ||
   case ($bri % 2 == 1): //◆閉じ括弧の場合
          ( act < anc && /^<\w+[\t >](?!\s*\/)/.test( st ) ) ) ) {
     var $br2 = br.charAt($bri-1); //対応する開き括弧を取得
      brc = /^(<\w+)[ >]?/.exec( st )[1]; // <hoge
     var $text = Document.Text.slice(0,pos).split("").reverse().join("");
      bLen = brc.length;  isTag = true;
    //※探索範囲は括弧の直前から文頭まで
      eBrc = "</" + brc.slice( 1 ) + ">"; // </hoge>
     break;
      sPos = ePos = tPos;  sBrc = brc;  iBrc = 0;
      break check;
     }
    // 選択範囲が </HTML> タグ(終了タグ)か // </hoge>
    else if ( ! sBrc && tagEnable &&
              ( /^<?\/\w+>$/.test( st ) ||
                ( anc < act && /<\/\w+>$/.test( st ) ) ) ) {
      brc = "<" + /<?(\/\w+>)$/.exec( st )[1];
      bLen = brc.length;  isEndTag = true;
      eBrc = "<" + brc.slice( 2, -1 ); // <hoge
      sPos = ePos = bPos - bLen;  sBrc = brc;  iBrc = 1;
      break check;
    }
  }
  // 右側にカッコがあるか
   if ( ! sBrc && act < dt.length ) {
     txt = dt.slice( act );
     for ( var i = 0; i < len; i ++ ) {
      brc = brackets[i];  bLen = brc.length;
      if ( txt.slice( 0, bLen ) === brc ) {
        sPos = ePos = act;  sBrc = brc;  iBrc = i;
        break check;
      }
     }
   }
   }
   //対応する括弧を探索し、距離を測定
   // 左側にカッコがあるか
   var nest= 1, dist= i= 0, re= new RegExp("["+"\\"+$br1+"\\"+$br2+"]");
   if ( ! sBrc && act > 0 && leftsideEnable ) {
   while (nest) { //ネストが無くなるまで探す
    txt = dt.slice( 0, act );
     i = $text.search(re);
    for ( var i = 0; i < len; i ++ ) {
    if (i == -1) break; //括弧が見当たらない場合、探索終了
      brc = brackets[i];  bLen = brc.length;
    if ($text.charAt(i) == $br1) nest++; else nest--;
      if ( txt.slice( - bLen ) === brc ) {
    $text = $text.slice(i+1); //探索した範囲を除去
        sPos = ePos = act - bLen;  sBrc = brc;  iBrc = i;
     dist += i+1; //距離を加算
        break check;
      }
    }
   }
}
 
if ( sBrc ) {
  // カッコありならネスト(入れ子)をチェックする
  var nest = 1;
  var s, e, eBrc, eLen;
  var sLen = sBrc.length; // 検索元のカッコの文字数
 
  // 閉じカッコの場合、先頭方向へ探す ※剰余あり = 奇数
  if ( iBrc % 2 ) {
    eBrc = eBrc ? eBrc : brackets[ iBrc - 1 ]; // 対応する開きカッコ
    eLen = eBrc.length; // 開きカッコの文字数
    // ネストが無くなるまで探す
     while ( nest > 0 ) {
      s = dt.lastIndexOf( sBrc, ePos - sLen ); // 開きカッコが見つかった位置
      e = dt.lastIndexOf( eBrc, ePos - eLen ); // 閉じカッコが見つかった位置
      if ( e === -1 || ePos <= 0 ) { break; } // 閉じカッコがなければ終了
      if ( e < s ) { nest ++; ePos = s; } // ( ) セットで見つかれば
      else { nest --; ePos = e; } // 開きカッコだけが見つかれば
     }
   }
   }
   switch (true) { //結果の判定、移動処理
   // 開きカッコの場合、末尾方向へ探す ※剰余なし = 偶数
   case (nest != 0): //◆ネストが残る場合
   else {
     Status = "対応する括弧が見つかりませんでした";
    eBrc = eBrc ? eBrc : brackets[ iBrc + 1 ]; // 対応する閉じカッコ
     sel.SetActivePos(pos); //カーソル位置を復元、選択解除
     eLen = eBrc.length; // 閉じカッコの文字数
     Quit();
     //ネストが無くなるまで探す
  case ($bri % 2 == 0): //◆開き括弧の場合
     while ( nest > 0 ) {
    sel.SetAnchorPos(pos);
      s = dt.indexOf( sBrc, ePos + sLen ); // 開きカッコが見つかった位置
    sel.SetActivePos(pos + dist, shift); //閉じ括弧の直前に移動
      e = dt.indexOf( eBrc, ePos + eLen ); // 閉じカッコが見つかった位置
    break;
      if ( e === -1 ) { break }; // 閉じカッコがなければ終了
  case ($bri % 2 == 1): //◆閉じ括弧の場合
      if ( s > -1 && s < e ) { nest ++; ePos = s; } // ( ) セットで見つかれば
    sel.SetAnchorPos(pos);
      else { nest --; ePos = e; } // 閉じカッコだけが見つかれば
    sel.SetActivePos(pos - dist, shift); //開き括弧の直前に移動
     }
     break;
   }
   }
   Status = "対応する括弧間の距離: "+(dist - 1)+"文字";
 
})();
   // 対応するカッコにジャンプ
  if ( nest === 0 ) {
    // 修飾キーなし  →  対応するカッコを範囲選択
    if ( $ctrl !== 1 && $shift !== 1 ) {
      pos = ePos + eLen;
      if ( isEndTag ) { // 開始タグの閉じカッコを探す
        gt = dt.slice( pos, sPos ).indexOf( ">" ) + 1;
        sel.SetActivePos( pos + gt );
      } else {
        sel.SetActivePos( pos );
      }
      sel.SetActivePos( ePos, true );
    }
    // Ctrl + Shift キー両方  →  対応するカッコの左側にジャンプ
    else if ( $ctrl === 1 && $shift === 1 ) {
      sel.SetActivePos( ePos );
    }
    // Ctrl キーのみ  →  対応するカッコをふくめて範囲選択(外側)
    else if ( $ctrl === 1 && $shift !== 1 ) {
      if ( ePos < sPos ) {
        sel.SetActivePos( sPos + sLen );
        sel.SetActivePos( ePos, true );
      } else { // ( sPos < ePos )
        sel.SetActivePos( sPos );
        sel.SetActivePos( ePos + eLen, true );
      }
    }
    // Shift キーのみ  →  対応するカッコとの中身を範囲選択(内側)
    else if ( $shift === 1 && $ctrl !== 1 ) {
      if ( ePos < sPos ) {
        pos = ePos + eLen;
        sel.SetActivePos( sPos );
        if ( isEndTag ) {
          gt = dt.slice( pos, sPos ).indexOf( ">" ) + 1;
          sel.SetActivePos( pos + gt, true );
        } else {
          sel.SetActivePos( pos, true );
        }
      } else { // ( sPos < ePos )
        pos = sPos + sLen;
        if ( isTag ) {
          gt = dt.slice( pos, ePos ).indexOf( ">" ) + 1;
          sel.SetActivePos( pos + gt );
        } else {
          sel.SetActivePos( pos );
        }
        sel.SetActivePos( ePos, true );
      }
      // カッコの内側の行を範囲選択
      if ( insideLines ) {
        var ty = sel.GetTopPointY( mePosLogical );
        var by = sel.GetBottomPointY( mePosLogical );
        var bx = sel.GetBottomPointX( mePosLogical );
        var strTest = /[^\t ]/.test( d.GetLine( by, 0 ).slice( 0, bx -1 ) );
        if ( ( by - ty ) > 1 && ! strTest ) {
          sel.SetActivePoint( mePosLogical, 1, by - 1 );
          sel.EndOfLine( false, mePosLogical );
          sel.SetAnchorPoint( mePosLogical, 1, ty + 1 );
        }
      }
    }
    Status = " 対応するカッコに移動" ;
  } else { Status = " 対応するカッコが見つかりませんでした"; }
} else { Status = " カーソルの側にカッコがありません"; }
 
// var elapsedSec = ( ( new Date() - start ) / 1000 ).toFixed( 3 );
// Status += " [ " + elapsedSec.replace( /\./, ". " ) + " 秒 ]" ;
</source>
</source>

2019年12月21日 (土) 22:04時点における版

Kuro, kurama 版 (2009/08)

  • 「shift」変数を「true」に書き換えると、選択しながら移動します。
// -----------------------------------------------------------------------------
// 対応する括弧に移動
//
// Copyright (c) Kuro. All Rights Reserved.
// www:    http://www.haijin-boys.com/
// Special Thanks for Kurama さん, Take さん
// -----------------------------------------------------------------------------

// シフトの状態(オンの場合は選択、オフの場合は移動)
var shift = false;
// 括弧として認識する文字
var lp = "(<[{「『【(";
var rp = ")>]}」』】)";
// 描画停止
Redraw = false;
// ステータスバーを消去
Status = "";
with (document.selection) {
  // カーソル位置を保存
  var ax = GetActivePointX(mePosLogical);
  var ay = GetActivePointY(mePosLogical);
  // スクロール位置を保存
  var sx = ScrollX;
  var sy = ScrollY;
  // カーソル位置を復元
  SetActivePoint(mePosLogical, ax, ay, false);
  // 単語を選択
  SelectWord();
  // 現在位置の括弧を取得
  var c1 = Text;
  // 選択範囲を解除
  Collapse();
  var l = lp.indexOf(c1);
  var r = rp.indexOf(c1);
  var st = 0;
  if (l > -1) {
    // 対応する括弧の種類を取得
    var c2 = rp.charAt(l);
    EndOfDocument(true);
    var s = Text;
    // カーソル位置を復元
    SetActivePoint(mePosLogical, ax, ay, false);
    // スクロール位置を復元
    ScrollX = sx;
    ScrollY = sy;
    var x = ax;
    var y = ay;
    for (var i = 0; i < s.length; i++) {
      x++;
      if (s.charAt(i) == c1)
        st++;
      if (s.charAt(i) == c2)
        st--;
      if (st == 0) {
        y = GetActivePointY(mePosLogical);
        // カーソル位置を復元
        SetActivePoint(mePosLogical, ax, ay, false);
        SetActivePoint(mePosLogical, x, y, shift);
        // 左に戻る
        CharLeft(shift, 1);
        break;
      }
      if (s.charAt(i) == "\n") {
        x = 1;
        y = GetActivePointY(mePosLogical) + 1;
        SetActivePoint(mePosLogical, x, y, false);
        StartOfLine(false, mePosLogical);
        x = GetActivePointX(mePosLogical);
      }
    }
  } else if (r > -1) {
    // 対応する括弧の種類を取得
    var c2 = lp.charAt(r);
    CharRight(false, 1);
    StartOfDocument(true);
    var s = Text;
    // カーソル位置を復元
    SetActivePoint(mePosLogical, ax, ay, false);
    // スクロール位置を復元
    ScrollX = sx;
    ScrollY = sy;
    var x = ax;
    for (var i = s.length - 1; i >= 0; i--) {
      x--;
      if (s.charAt(i) == c1)
        st++;
      if (s.charAt(i) == c2)
        st--;
      if (st == 0) {
        y = GetActivePointY(mePosLogical);
        // カーソル位置を復元
        SetActivePoint(mePosLogical, ax, ay, false);
        SetActivePoint(mePosLogical, x, y, shift);
        // 右に進む
        CharRight(shift, 1);
        break;
      }
      if (s.charAt(i) == "\n") {
        x = 1;
        y = GetActivePointY(mePosLogical) - 1;
        SetActivePoint(mePosLogical, x, y, false);
        EndOfLine(false, mePosLogical);
        x = GetActivePointX(mePosLogical);
      }
    }
  } else {
    // 括弧が無い場合は元の位置に戻す
    SetActivePoint(mePosLogical, ax, ay, false);
    Status = "カーソル位置に括弧が見つかりませんでした";
  }
  if (st != 0) {
    SetActivePoint(mePosLogical, ax, ay, false);
    Status = "対応する括弧が見つかりませんでした";
  }
}
// 描画開始
Redraw = true;


masme 版

  • ステータスバーに括弧間の文字数を表示するようにしました。
  • Kuro, kurama 版の不安定な挙動を調整しました。
    • カーソル右側が改行か[EOF]の場合、左側の括弧に反応するのを修正。例外なく右側のみ反応するようにした。
    • 開き括弧の直前ではなく、直後に移動する場合があるのを修正。直前に移動するよう統一した。
      ※カーソル右側が改行か[EOF]の場合。改行をまたぐ移動になる場合。対応する括弧が行頭にある場合。

更新履歴

2019/04/12
  • Quit() → break ラベル文に変更。
2017/05/27
  • 処理方法を正規表現検索 → 文字列検索に変更し、高速化。
  • 対応する括弧が見つからなかった場合、選択解除する → しない仕様に変更。
2014/02/05
  • 初版公開。

ソースコード

//■対応する括弧に移動
// 2014/02/05-2019/04/12

//■括弧の定義(0+2n:開き/1+2n:閉じ)
var BRACKET = "()<>[]{}「」『』【】()";
//■範囲選択(true:する/false:しない)
var SHIFT = false;

quit: {
var Sel = Document.Selection;
var txt = Document.Text;
var sPos = Sel.GetActivePos(), ePos = sPos; //カーソル始点, 探索位置&終点
var sBrc = txt.charAt(sPos); //カーソル右側の文字を取得
var iBrc = BRACKET.indexOf(sBrc);
if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; }
var nest = 1, s, e;
if (iBrc % 2) { //◆閉じ括弧の場合、先頭方向へ探す
  var eBrc = BRACKET.charAt(iBrc-1); //対応する開き括弧を取得
  while (nest) {
    s = txt.lastIndexOf(sBrc,ePos-1);
    e = txt.lastIndexOf(eBrc,ePos-1);
    if (e===-1 || ePos<=0) break;
    if (e<s) {nest++; ePos=s;} else {nest--; ePos=e;}
  }
} else { //◆開き括弧の場合、末尾方向へ探す
  var eBrc = BRACKET.charAt(iBrc+1); //対応する閉じ括弧を取得
  while (nest) {
    s = txt.indexOf(sBrc,ePos+1);
    e = txt.indexOf(eBrc,ePos+1);
    if (e===-1) break;
    if (s<e && s!==-1) {nest++; ePos=s;} else {nest--; ePos=e;}
  }
}
if (nest!==0) { Status = "対応する括弧が見つかりませんでした"; break quit; }
Sel.SetActivePos(sPos);
Sel.SetActivePos(ePos, SHIFT);
Status = "対応する括弧の距離: "+(Math.abs(ePos - sPos)-1)+"文字";
}


masme 版用 機能拡張コード

範囲選択の拡張

(sukemaru 2019/04/24)
GetKeyState.exe(キー状態取得実行ファイル) を利用して、カッコを含めた全体を範囲選択、または カッコの内側部分だけを範囲選択 するための追加コードです。
masme 版 (2019/04/12) のソースコードに追加コードを挿入してください(2ヵ所)。

ツールバーアイコンやメニュー(マクロメニューや右クリックメニュー)からマクロを実行するさいに Ctrl または Shift キーを押しながら実行すると、拡張コードが適用されます。

  • Ctrl キーを押しながらマクロを実行した場合には、カッコを含めた全体を範囲選択します。
【 Ctrl ありでの選択範囲 】 → ※カーソルは 開きカッコの左 (選択範囲の先頭)
  • Shift キーを押しながらマクロを実行した場合には、カッコの内側部分だけを範囲選択します。
 Shift ありでの選択範囲 】 → ※カーソルは 閉じカッコの左 (選択範囲の末尾)
  • Ctrl+Shift キー両方が押されている(または両方とも押されていない)場合には、masme 版の設定変数「SHIFT」の true / false で指定された動作をします。
【 SHIFT=true のばあい 

※「対応する括弧に移動」を連続で実行(トグル移動)できるよう、拡張機能で範囲選択したあともカーソルは開きカッコか閉じカッコいずれかの「カッコの左」にセットされます。

※ ショートカットキーでマクロを実行する場合、Ctrl キーを含むパターン、Shift キーを含むパターン、Ctrl と Shift 両方を含む(または両方を含まない)パターンのみっつのショートカットキーを登録する必要があります。 すでに masme 版「対応する括弧に移動」マクロにショートカットキーを割り当てて使用している場合、この追加コードを使用するにあたり、ショートカットの再割り当てをしてください。

e.g. Ctrl+F9 / Shift+F9 / Ctrl+Shift+F9(または Alt+F9 など)

※ 外部実行ファイル GetKeyState.exe を2回呼び出すため、拡張コードの処理にはタイムラグが生じます。選択範囲が確定するまで Ctrl または Shift キーを押しっぱなしにしてください。

※ GetKeyState.exe のダウンロードや導入方法については、マクロライブラリ内の「GetKeyState.exe(キー状態取得実行ファイル) 」のページへ

追加コード➀

15行目
if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; }
の下に追加する

// ▼ GetKeyState を利用した機能拡張 ▼
  // Ctrl / Shift キーの状態を取得
  var $ctrl = 0, $shift = 0;
  var getKeyState = editor.FullName.replace(/mery\.exe$/i,"") + "Macros\\GetKeyState.exe";
  var WshShell = new ActiveXObject("WScript.Shell");
  $ctrl = WshShell.Run('"' + getKeyState + '" ctrl',0,true);
  $shift = WshShell.Run('"' + getKeyState + '" shift',0,true);
// ▲ GetKeyState を利用した機能拡張 ▲

※「カーソルの右側にカッコがないなら左側のカッコで」の改造コードと併用する場合は、改造コードの下に挿入してください。

追加コード➁

37行目
Status = "対応する括弧の距離: "+(Math.abs(ePos - sPos)-1)+"文字";
の下に追加する

// ▼ GetKeyState を利用した機能拡張 ▼
  // ※SHIFT 変数による選択範囲を上書きする(SHIFT = false; でも拡張コードでの範囲選択は有効)
  if ($ctrl || $shift) {
    var tPos = Math.min(sPos,ePos);
    var bPos = Math.max(sPos,ePos);
    // Ctrl キーだけ押し下げ時にはカッコを含めた全体を範囲選択
    if ($ctrl>0 && $shift==0) {
      Sel.SetActivePos(tPos);
      Sel.SetAnchorPos(bPos+1);
    }
    // Shift キーだけ押し下げ時にはカッコの内側だけを範囲選択
    else if ($ctrl==0 && $shift>0) {
      Sel.SetActivePos(bPos);
      Sel.SetAnchorPos(tPos+1);
    }
  }
// ▲ GetKeyState を利用した機能拡張 ▲
  • 2019/05/12 変数名間違いを修正 sel → Sel (sukemaru)



カーソルの右側にカッコがないなら左側のカッコで

(sukemaru 2019/12/01)
カーソルの右側にカッコがないとき で、左側にカッコがあるなら、そのカッコに対応するカッコに移動します。

  • 右側のカッコ優先 です。
移動後は対応する カッコの左側 にカーソルがセットされます(連続で実行しても元のカッコの左側に)。


masme 版 (2019/04/12) のソースコードを以下のとおり書き換えてください。

変更箇所: 15 行目
if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; }
をコメントアウトして

// if (iBrc===-1 || sBrc==="") { Status = "カーソル右側に括弧がありません"; break quit; }	// ※ 1行 コメントアウト

// ▼ カーソルの左側にカッコがある場合に対応 ▼
  if (iBrc>-1 && sBrc) { /* empty */ ; }
  else if (BRACKET.indexOf(txt.charAt(sPos-1))>-1) {
    sPos = ePos = sPos-1;
    sBrc = txt.charAt(sPos);
    iBrc = BRACKET.indexOf(txt.charAt(sPos));
  }
  else { Status = "カーソルの側に括弧がありません"; break quit; }
// ▲ カーソルの左側にカッコがある場合に対応 ▲

※「範囲選択の拡張」コードを併用する場合は、この変更箇所の下に「追加コード①」を挿入してください。

カッコの種類を追加

(sukemaru, 2019/12/01)
masme 版 (2019/04/12) のソースコードにカッコの種類を追加します。

※ Mery 本体のオプション機能の「対応する括弧を強調する」には非対応です。


4 行目
var BRACKET = "()<>[]{}「」『』【】()";
の下に追加する

BRACKET += "<>[]{}「」〖〗⦅⦆〈〉《》〚〛〔〕〘〙‹›«»≪≫〝〟‘’“”︵︶︿﹀︽︾︷︸﹁﹂﹃﹄︻︼︹︺";

※ 左右のカッコのかたち(文字)が異なるペアしか登録できないので、半角二重引用符 "" や 半角一重引用符 '' などは不可。


sukemaru 版

【自家用】 対応するカッコを選択 (sukemaru, 2019/12/02)

masme 版 (2019/04/12) をベースに、ジャンプ後の範囲選択の拡張キャレットの左側にあるカッコ に対応、 カッコの種類を追加タグなどの複数文字のペアに対応 などの改造をした【自家用版】も貼っておきます(おもっていたほど便利ではないアルファバージョンですが)。

#title   = "対応するカッコを選択" 
#tooltip = "対応するカッコに移動・選択" 
// #icon    = "Mery用 マテリアルデザインっぽいアイコン.icl" ,127

/**
 * ---------------------------------------------------------
 * 「対応するカッコに移動」
 * Original Coded by: Kuro, kurama,      ( - 2009/08)
 *                  ; masme,(2014/02/05 - 2019/04/12)
 * 「対応するカッコを選択」
 * Modified by: sukemaru,   (2019/04/24 - 2019/12/05)
 * ---------------------------------------------------------
 * ・masme 版「対応するカッコに移動」マクロ (2019/04/12) をベースに、
 *   キャレットの左側にあるカッコの取得や、
 *   タグなどの複数文字同士のペアに対応できるようにしました。
 * 
 * ・以下の優先順位でカッコを検索します。
 *   ➀ カッコを範囲選択しているとき、対応するカッコを検索
 *   ➁ 選択範囲が HTML タグ形式 <hoge> のときは </hoge> を、
 *      終了タグ </fuga> の ときは <fuga> を検索(オプション設定)
 *   ➂ キャレットの右側にカッコがあるとき、対応するカッコを検索
 *   ➃ キャレットの左側にカッコがあるとき、対応するカッコを検索(オプション設定)
 * 
 * ・pizz 氏作成の "GetKeyState.exe(キー状態取得実行ファイル)" を利用して
 *   対応するカッコに移動後の範囲選択状態を選択できます。
 *   GetKeyState.exe を Mery\Macros フォルダに用意してください。
 *   
 * ▼ Ctrl, Shift キーを押しながらマクロを実行したとき ▼
 * 
 * ・Ctrl + Shift キー両方を押しているとき
 *     → 対応するカッコの右側にキャレットを移動します。
 * ・Ctrl キーのみ押しているとき
 *     → 対応するカッコをふくむ全体(外側)を範囲選択します。
 * ・Shift キーのみ押しているとき
 *     → 対応するカッコの中身(内側)を範囲選択します。
 * 
 * ・Ctrl / Shift キーを押していないときは
 *     → 対応するカッコだけを範囲選択します。
 * 
 * 【仕様上の制限】
 * ・カッコの定義は、通例として前後関係が定まっているもののみとしてください。
 *   同一の記号を使う '一重引用符' や "二重引用符"、―ダーシ― などでは、
 *   開き ← 閉じ の方向に戻れないので使用できません。
 * ・\ などでエスケープされたものを考慮せずにジャンプします。
 * ・HTML/XML タグなどのように < > をカッコとして使用しているものと
 *   比較・計算で使用している半角不等号 < > とを区別しません。
 * ・<div id="hoge"> や <div style="fuga"> のような属性つきのタグを
 *   個別に登録することはできません。
 *  (終了側のタグが同一 </div> なので、</div> から前方に検索したときに
 *   <div> や <div class="piyo"> などと区別をつけられない)
 * 
 *  (2019/12/05)
 * ・設定項目 tagEnable と altTagInvert を追加。
 *   <hoge> </hoge> 形式のタグ全般に対応 (配列 brackets2 への登録不要)。
 * 
 * ※ タグを範囲選択しているときのみ有効。
 *    範囲選択なしの場合、キャレットの左または右にあるタグを自動取得しません
 *   (配列 brackets2 に登録されたペアであれば、自動取得しますが…)。
 * ※ 開始タグ側から実行する場合、キャレットがタグの先頭位置にあるとき、
 *    または選択範囲に属性部分を含めていないときのみ有効。
 * ※ 基本的に、開始タグは "<" の直後が空白でないこと(正規表現 ^<\w+ )、
 *    終了タグ内には空白文字を含めないこと(正規表現 \/\w+>$ )を条件とします。
 *    e.g. < hoge > </hoge > などは NG.
 *    <fuga/> <!PIYO> を範囲選択している場合も通常のカッコ囲いとして扱います。
 * ※ <hoge> の hoge 部分が有効な HTML タグであるかをチェックしないので、
 *    <ABC> 形式のカッコにたいしては、通常の「対応するカッコに移動・選択」
 *    の動作になりません(設定項目 tagEnable = false で無効化できます)。
 *   「対応するカッコがない」 となって両端の "<" と ">" での移動ができなくなります。
 * ※ tagEnable = true の場合、配列 brackets2 にタグが登録されていると
 *    うまく動作しないことがあります。
 * ※ tagEnable = false の場合でも、配列 brackets2 に登録されたペアであれば
 *    通常のカッコと同様に扱います。
 * ※ 同種のタグがネストすることはないはずですが、
 *    便宜上、通常のカッコと同様にネストのチェックをします。
 */


// ---------- ▼ 設定項目 ▼ ---------- //

// ■ <hoge> </hoge> 形式のタグを検索対象にする(優先オプション)
var tagEnable = true;

// ■ Alt キー押し下げで tabEnable の true / false を一時的に反転させる
var altTagInvert = true;

// ■ キャレットの左側のカッコも検索対象にする(true でも右側が優先)
var leftsideEnable = true;

// ■ Shift キーのみ押し下げのとき、実行後の選択範囲が複数行になるなら
//    カッコの内側の行だけを範囲選択する
var insideLines = false;

// ■ カッコの定義1 (開きカッコと閉じカッコをペアにして列挙する)
var brackets1 = "()<>[]{}「」『』【】()" 	// Mery で強調表示可能なカッコ
brackets1    += "<>[]{}「」〖〗⦅⦆〈〉《》〚〛〔〕〘〙" 
              + "︵︶︿﹀︽︾︷︸﹁﹂﹃﹄︻︼︹︺" 
              + "‹›«»≪≫〝〟‘’“”" ;		// ―——

// ■ カッコの定義2 (タグなどの複数文字のペアを配列に列挙する)
// ※ おなじ開きカッコを持つペアや、おなじ閉じカッコを持つペア、
//    braclets1 に含まれているカッコ1文字だけの要素は不可。
var brackets2 = [];
brackets2 = [
  "\\(" , "\\)" , "\\[" , "\\]" , "\\{" , "\\}" ,"[[" , "]]" , 
  /* HTML/XML 形式のタグは登録不要 (2019/12/05) */ 
  // "<b>" , "</b>" , "<a>" , "</a>" , "<pre>" , "</pre>" , 
  // "<b" , "/b>" , "<a" , "/a>" , 	// <abc> や <blah> に反応しなくなるかも?
  // "<div>" , "</div>" , "<div" , "/div>" , 
  "/**" , " */" , "/*" , "*/" , "<!--" , "-->"
];

// ---------- ▲ 設定項目 ▲ ---------- //


// Ctrl キーと Shift キーの状態を取得
var $ctrl = 0,  $shift = 0,  $alt = 0;
var gks = editor.FullName.replace( /[^\\]+$/, "" )
        + "Macros\\GetKeyState.exe";
if ( new ActiveXObject( "Scripting.FileSystemObject" ).FileExists( gks ) ) {
  var WshShell = new ActiveXObject( "WScript.Shell" );
  $ctrl  = WshShell.Run( '"' + gks + '" control', 0, true );
  $shift = WshShell.Run( '"' + gks + '" shift', 0, true );
  if ( altTagInvert ) {
    $alt = WshShell.Run( '"' + gks + '" alt', 0, true );
    if ( $alt === 1 ) { tagEnable = ! tagEnable; }
  }
}

var start = new Date();	// ここでタイマースタート

// カッコの定義をひとつの配列に統合する (brackets2 が前、brackets1 が後ろ)
var brackets = brackets2.concat( brackets1.split( "" ) );
var len = brackets.length;

var d = editor.ActiveDocument,  sel = d.selection;
var dt = d.Text,  st = sel.Text;
var act = sel.GetActivePos(),  anc = sel.GetAnchorPos();
var tPos = Math.min( act, anc ),  bPos = Math.max( act, anc );
var txt, brc, bLen, sPos, ePos, sBrc, iBrc;
if ( tagEnable ) {
  var isTag = false,  isEndTag = false;
  var pos, gt;
}

// 選択範囲がカッコか
check: {
  if ( st ) {
    for ( var i = 0; i < len; i ++ ) {
      brc = brackets[i];
      if ( st === brc ) {
        sPos = ePos = tPos;  sBrc = brc;  iBrc = i;
        break check;
      }
    }
    // 選択範囲が <HTML> タグ(開始タグ)か
    // ※ 開始タグの閉じカッコを含めずに "開きカッコ" とする
    if ( ! sBrc && tagEnable && ! /(?:\/\s*>|\W>)$/.test( st ) &&
         ( /^<\w+[\t >]?$/.test( st ) ||
           ( act < anc && /^<\w+[\t >](?!\s*\/)/.test( st ) ) ) ) {
      brc = /^(<\w+)[ >]?/.exec( st )[1];		// <hoge
      bLen = brc.length;  isTag = true;
      eBrc = "</" + brc.slice( 1 ) + ">";		// </hoge>
      sPos = ePos = tPos;  sBrc = brc;  iBrc = 0;
      break check;
    }
    // 選択範囲が </HTML> タグ(終了タグ)か	// </hoge>
    else if ( ! sBrc && tagEnable &&
              ( /^<?\/\w+>$/.test( st ) ||
                ( anc < act && /<\/\w+>$/.test( st ) ) ) ) {
      brc = "<" + /<?(\/\w+>)$/.exec( st )[1];
      bLen = brc.length;  isEndTag = true;
      eBrc = "<" + brc.slice( 2, -1 );		// <hoge
      sPos = ePos = bPos - bLen;  sBrc = brc;  iBrc = 1;
      break check;
    }
  }
  // 右側にカッコがあるか
  if ( ! sBrc && act < dt.length ) {
    txt = dt.slice( act );
    for ( var i = 0; i < len; i ++ ) {
      brc = brackets[i];  bLen = brc.length;
      if ( txt.slice( 0, bLen ) === brc ) {
        sPos = ePos = act;  sBrc = brc;  iBrc = i;
        break check;
      }
    }
  }
  // 左側にカッコがあるか
  if ( ! sBrc && act > 0 && leftsideEnable ) {
    txt = dt.slice( 0, act );
    for ( var i = 0; i < len; i ++ ) {
      brc = brackets[i];  bLen = brc.length;
      if ( txt.slice( - bLen ) === brc ) {
        sPos = ePos = act - bLen;  sBrc = brc;  iBrc = i;
        break check;
      }
    }
  }
}

if ( sBrc ) {
  // カッコありならネスト(入れ子)をチェックする
  var nest = 1;
  var s, e, eBrc, eLen;
  var sLen = sBrc.length;	// 検索元のカッコの文字数

  // 閉じカッコの場合、先頭方向へ探す	※剰余あり = 奇数
  if ( iBrc % 2 ) {
    eBrc = eBrc ? eBrc : brackets[ iBrc - 1 ];	// 対応する開きカッコ
    eLen = eBrc.length;				// 開きカッコの文字数
    // ネストが無くなるまで探す
    while ( nest > 0 ) {
      s = dt.lastIndexOf( sBrc, ePos - sLen );	// 開きカッコが見つかった位置
      e = dt.lastIndexOf( eBrc, ePos - eLen );	// 閉じカッコが見つかった位置
      if ( e === -1 || ePos <= 0 ) { break; }	// 閉じカッコがなければ終了
      if ( e < s ) { nest ++;  ePos = s; }	// ( ) セットで見つかれば
      else { nest --;  ePos = e; }			// 開きカッコだけが見つかれば
    }
  }
  // 開きカッコの場合、末尾方向へ探す	※剰余なし = 偶数
  else {
    eBrc = eBrc ? eBrc : brackets[ iBrc + 1 ];	// 対応する閉じカッコ
    eLen = eBrc.length;				// 閉じカッコの文字数
    //ネストが無くなるまで探す
    while ( nest > 0 ) {
      s = dt.indexOf( sBrc, ePos + sLen );	// 開きカッコが見つかった位置
      e = dt.indexOf( eBrc, ePos + eLen );	// 閉じカッコが見つかった位置
      if ( e === -1 ) { break };			// 閉じカッコがなければ終了
      if ( s > -1 && s < e ) { nest ++;  ePos = s; }	// ( ) セットで見つかれば
      else { nest --;  ePos = e; }					// 閉じカッコだけが見つかれば
    }
  }

  // 対応するカッコにジャンプ
  if ( nest === 0 ) {
    // 修飾キーなし  →  対応するカッコを範囲選択
    if ( $ctrl !== 1 && $shift !== 1 ) {
      pos = ePos + eLen;
      if ( isEndTag ) {	// 開始タグの閉じカッコを探す
        gt = dt.slice( pos, sPos ).indexOf( ">" ) + 1;
        sel.SetActivePos( pos + gt );
      } else {
        sel.SetActivePos( pos );
      }
      sel.SetActivePos( ePos, true );
    }
    // Ctrl + Shift キー両方  →  対応するカッコの左側にジャンプ
    else if ( $ctrl === 1 && $shift === 1 ) {
      sel.SetActivePos( ePos );
    }
    // Ctrl キーのみ  →  対応するカッコをふくめて範囲選択(外側)
    else if ( $ctrl === 1 && $shift !== 1 ) {
      if ( ePos < sPos ) {
        sel.SetActivePos( sPos + sLen );
        sel.SetActivePos( ePos, true );
      } else {	// ( sPos < ePos )
        sel.SetActivePos( sPos );
        sel.SetActivePos( ePos + eLen, true );
      }
    }
    // Shift キーのみ  →  対応するカッコとの中身を範囲選択(内側)
    else if ( $shift === 1 && $ctrl !== 1 ) {
      if ( ePos < sPos ) {
        pos = ePos + eLen;
        sel.SetActivePos( sPos );
        if ( isEndTag ) {
          gt = dt.slice( pos, sPos ).indexOf( ">" ) + 1;
          sel.SetActivePos( pos + gt, true );
        } else {
          sel.SetActivePos( pos, true );
        }
      } else {	// ( sPos < ePos )
        pos = sPos + sLen;
        if ( isTag ) {
          gt = dt.slice( pos, ePos ).indexOf( ">" ) + 1;
          sel.SetActivePos( pos + gt );
        } else {
          sel.SetActivePos( pos );
        }
        sel.SetActivePos( ePos, true );
      }
      // カッコの内側の行を範囲選択
      if ( insideLines ) {
        var ty = sel.GetTopPointY( mePosLogical );
        var by = sel.GetBottomPointY( mePosLogical );
        var bx = sel.GetBottomPointX( mePosLogical );
        var strTest = /[^\t ]/.test( d.GetLine( by, 0 ).slice( 0, bx -1 ) );
        if ( ( by - ty ) > 1 && ! strTest ) {
          sel.SetActivePoint( mePosLogical, 1, by - 1 );
          sel.EndOfLine( false, mePosLogical );
          sel.SetAnchorPoint( mePosLogical, 1, ty + 1 );
        }
      }
    }
    Status = " 対応するカッコに移動" ; 
  } else { Status = " 対応するカッコが見つかりませんでした"; }
} else { Status = " カーソルの側にカッコがありません"; }

// var elapsedSec = ( ( new Date() - start ) / 1000 ).toFixed( 3 );
// Status += " [ " + elapsedSec.replace( /\./, ". " ) + " 秒 ]" ;
スポンサーリンク