「対応する括弧に移動」の版間の差分
(「masme版」を追加。目次を表示。) |
細 (更新履歴の書式を変更) |
||
(2人の利用者による、間の18版が非表示) | |||
1行目: | 1行目: | ||
<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 版 | = masme 版 = | ||
* ステータスバーに括弧間の文字数を表示するようにしました。 | |||
* 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; | ||
// | |||
var | 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; | |||
} | |||
( | // 選択範囲がカッコか | ||
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 | |||
var | 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( /\./, ". " ) + " 秒 ]" ; | |||
</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( /\./, ". " ) + " 秒 ]" ;