/* [概要] 1つ上のネストレベルの括弧にカーソルを移動します。 括弧は "{}" (波括弧)が対象です。(→ bCOUNT_xxxxx で変更可。) 移動方向: デフォルトのカーソルの移動方向(検索方向)は上方向ですが、 本マクロのファイル名の拡張子(.js)が大文字(.JS)の場合、下方向になります。 マクロ登録の File 名の設定により、方向が指定可能です。 例:[共通設定] - [マクロ] タブ 番号 マクロ名 ファイル名 1 上方向:1つ上の括弧に移動 ExitNestedBracket.js 2 下方向:1つ上の括弧に移動 ExitNestedBracket.JS (←大文字の.JS) プラグイン[Command]の場合も同様です。ハンドラ(マクロのパス)の設定で、 拡張子を大文字(.JS)にすると下方向になります。 例: plugin.def: [Command] セクション - "C[nnn]" キー [Command] ; 上方向 C[1]=..\..\macro\ExitNestedBracket.js C[1].Label=↑1つ上の括弧に移動 ; 下方向 C[2]=..\..\macro\ExitNestedBracket.JS (←大文字の.JS) C[2].Label=↓1つ上の括弧に移動 (サクラエディタの仕様変更によりこの方法が使えなくなる可能性もあります。) [制限] ・括弧の対応が取れていない場合(コメントや文字列などで)、うまく動作しません。 [変更履歴] 2014/05/30 sakura:2.1.0.0:SetDrawSwitch()でちらつき抑止・速度Up、 StatusMsg()で「見つかりませんでした」表示。 sakura:2.0.6.0:検索キーを履歴に登録しない。(しかし検索ボックスには残る。) プラグイン[Command]からの呼び出しに対応。 2012/07/28 Unicode版対応 2009/04/22 新規 */ //2012/07/28 Unicode版対応 // ANSI版/Unicode版の区別 var bUNICODE_VER = (Editor.ExpandParameter("$V").split(".")[0] == "1") ? false : true; // false=ANSI版 / true=Unicode版 // "$V":サクラエディタのバージョン (例:ANSI版="1.6.6.0" / Unicode版="2.0.5.0") // とりあえず、先頭が "1" ならANSI版とする。 // 「検索ダイアログを自動的に閉じる」チェックをONにするか。 // (チェックOFFで使用されている方はfalseにして下さい。) var bSEARCHDIALOG_AUTOCLOSE = true; // 括弧の検索回数の上限。 // (とりあえずの値。あまり大きくしたくはないが、長いプログラムだと足りなくなる。) var nBRACKET_SEARCHCOUNT_MAX = 2000; // 行頭(桁位置=1)にある括弧を最上位のネストレベル(上のネストレベル無し)とみなすか。 //var bSTOP_SEARCH_WHEN_BRACKET_FOUND_AT_TOPOFLINE = false; var bSTOP_SEARCH_WHEN_BRACKET_FOUND_AT_TOPOFLINE = true; // "{}" (波括弧)を対象にするか。 var bCOUNT_BRACES = true; // "()" (丸括弧)を対象にするか。 var bCOUNT_PARENTHESES = false; // "[]" (角括弧)を対象にするか。 var bCOUNT_SQUAREBRACKETS = false; //-------------- // メイン Main(); function Main() { var ext; // マクロのファイル名の拡張子。 var bUpward; // true=上方向 / false=下方向 に移動。 var nNestLevel; // ネストレベル。(配列) var nLevelIncDec; // ネストレベルの増減。 var sSearch; // 検索文字列。 var nSearchOption; // 検索オプション。(検索ダイアログの状態) var bFound; // 1つ上のネストレベルの括弧が見つかったか。 var y0; var nLoopCount; var s; var x; //2014/05/30 sakura:2.1.0.0、プラグイン対応。 var sMacroPath; // 本マクロのパス var x0; var sSakuraVer; // 各4桁 (例:sakura:1.6.6.0 → "0001.0006.0006.0000") var bSakuraVer2_1; // true=sakura:2.1.0.0以降 / false=2.1.0.0より前のVer //2014/05/30 sakura:2.1.0.0。 // とりあえず4桁にして比較 (例:"1.23.456.7890" → "0001.0023.0456.7890") sSakuraVer = Editor.ExpandParameter("$V").replace( /\d+/g, function($0){return ("000"+$0).slice(-4);} ); bSakuraVer2_1 = (sSakuraVer >= "0002.0001.0000.0000"); //2014/05/30 プラグイン[Command]からの呼び出しに対応。 // ext = Editor.ExpandParameter("$M"); // ext = ext.substring( ext.lastIndexOf(".") + 1 ); sMacroPath = Editor.ExpandParameter("$M"); if( (sMacroPath == "") && (typeof Plugin != "undefined") ){ // プラグインではExpandParameter("$M")=""(空文字列)が返る。 sMacroPath = Plugin.GetDef( "Command", "C[" + Plugin.GetCommandNo() + "]" ); } ext = sMacroPath.replace( /.*\./, "" ); // マクロの拡張子が大文字(="JS")なら下方向。 bUpward = ((ext != "") && (ext == ext.toUpperCase())) ? false : true; nNestLevel = new Array(); nNestLevel["{}"] = 0; nNestLevel["()"] = 0; nNestLevel["[]"] = 0; nLevelIncDec = (bUpward) ? +1 : -1; sSearch = ""; if( bCOUNT_BRACES ) sSearch += "|\\{|\\}"; if( bCOUNT_PARENTHESES ) sSearch += "|\\(|\\)"; if( bCOUNT_SQUAREBRACKETS ) sSearch += "|\\[|\\]"; if( sSearch == "" ) return; // bCOUNT_BRACES等がすべてfalseだった。 sSearch = sSearch.substring( 1 ); // 先頭の "|" を消す。 nSearchOption = 1<<2; //2014/05/30 sakura:2.0.6.0:検索キーを履歴に登録しない。 nSearchOption |= 0x800; // bit11:検索キーを履歴に登録しない(2.0.6.0 以降)。 if( bSEARCHDIALOG_AUTOCLOSE ){ nSearchOption |= 1<<4; // bit4:検索ダイアログを自動的に閉じる。 } bFound = false; y0 = Editor.ExpandParameter("$y") -0; // (-0:文字列→数値) if( bSakuraVer2_1 ){ x0 = Editor.ExpandParameter("$x") -0; // (-0:文字列→数値) Editor.SetDrawSwitch( 0 ); // 画面描画を非表示に。 } Editor.CancelMode(); // 範囲選択中なら取り消し。 for(nLoopCount=0; nLoopCount < nBRACKET_SEARCHCOUNT_MAX; nLoopCount++) { // 括弧を検索。 if( bUpward ){ Editor.SearchPrev( sSearch, nSearchOption ); }else{ Editor.SearchNext( sSearch, nSearchOption ); } if( Editor.IsTextSelected() == 0 ) break; // 見つからない。 // コメント内やクォート内の括弧は無視。(簡易。1行内のみ対応。) s = Editor.GetLineStr( 0 ); x = getCursorColumn(); // 括弧の文字位置(0開始)。 // 1行内で "//" を探す。(ダブルクォート文字列の外にある "//") if( s.search( /^([^"]*"[^"]*")*?[^"]*?\/\// ) != -1 ){ if( x >= RegExp.lastIndex ){ continue; // 括弧は "//" の後ろにある。 } } // 1行内で "/*"〜"*/"、他を探す。 if( isInsideOf( s, x, "/*", "*/" ) ) continue; // 括弧は "/*"〜"*/" の間にある。 if( isInsideOf( s, x, '"', '"' ) ) continue; // 括弧は '"'〜'"' の間にある。 if( isInsideOf( s, x, "'", "'" ) ) continue; // 括弧は "'"〜"'" の間にある。 // ネストレベルの増減。 switch( Editor.GetSelectedString(0) ){ // 検索された文字列。 case "{": nNestLevel["{}"] -= nLevelIncDec; break; case "}": nNestLevel["{}"] += nLevelIncDec; break; case "(": nNestLevel["()"] -= nLevelIncDec; break; case ")": nNestLevel["()"] += nLevelIncDec; break; case "[": nNestLevel["[]"] -= nLevelIncDec; break; case "]": nNestLevel["[]"] += nLevelIncDec; break; default: break; } if( (nNestLevel["{}"] == -1) || (nNestLevel["()"] == -1) || (nNestLevel["[]"] == -1) ){ bFound = true; // 1つ上のネストレベルの括弧が見つかった。 break; } if( bSTOP_SEARCH_WHEN_BRACKET_FOUND_AT_TOPOFLINE ){ // 行頭に括弧があった場合、上のネストレベルは見つからないとみなす。 if( Editor.ExpandParameter("$x") == 1 ){ break; } } } //for if( bFound ){ // カーソルが画面外にあれば画面内にスクロールさせる役目もあり。 if( bUpward ){ Editor.Left(); // 開き括弧("{")の左に移動。 }else{ Editor.Right(); // 閉じ括弧("}")の右に移動。 } Editor.SearchClearMark(); if( bSakuraVer2_1 ){ Editor.SetDrawSwitch( 1 ); // 画面描画を(非表示から)非表示に。 Editor.ReDraw(); // 画面描画を非表示にすると括弧が強調表示されなかったので移動履歴で対処。 Editor.MoveHistSet(); Editor.MoveHistPrev(); } }else{ Editor.CancelMode(); Editor.SearchClearMark(); if( bSakuraVer2_1 ){ Editor.MoveCursor( y0, x0, 0 ); // 開始前の位置に戻る。 Editor.SetDrawSwitch( 1 ); // 画面描画を(非表示から)非表示に。 Editor.ReDraw(); Editor.StatusMsg( ((bUpward) ? "△" : "▽") + "見つかりませんでした" ); Editor.MsgBeep(); }else{ sSearch = "\x03NotFound\x02"; // 検索に失敗させる。(失敗するはず…) nSearchOption &= ~(1<<2); // マクロ実行時の行に戻る。(桁位置も戻したいがちょっと苦しい。) if( bUpward ){ // とりあえず行頭へ。 Editor.Jump( y0, 1 ); // 「△見つかりませんでした」をステータスバーに表示。 Editor.SearchPrev( sSearch, nSearchOption ); }else{ // とりあえず行末へ。 Editor.Jump( y0 +1, 1 ); if( Editor.ExpandParameter("$y") == y0 +1 ) Editor.Left(); // 「▽見つかりませんでした」をステータスバーに表示。 Editor.SearchNext( sSearch, nSearchOption ); } } } //Editor.TraceOut("nLoopCount=" + (" "+nLoopCount).slice(-4) ); //デバッグ用 } // 文字列 sSrc 中の位置 nPos (0開始)が、文字列 sFrom と sTo の間にあるかを調べる。 // 戻り値: // true = nPosはsFrom〜sToの間にある / false = ない // 例: sSrc="012/*5*/89/*2*/56/*9" のとき: // isInsideOf(sSrc, 12, "/*", "*/") → true // isInsideOf(sSrc, 19, "/*", "*/") → false // sSrc="012'4'6789" のとき: // isInsideOf(sSrc, 4, "'", "'") → true function isInsideOf( sSrc, nPos, sFrom, sTo ) { var sta, end; end = 0; while( (sta = sSrc.indexOf( sFrom, end )) != -1 ){ sta += sFrom.length; if( nPos < sta ) break; if( (end = sSrc.indexOf( sTo, sta )) == -1 ) break; if( nPos < end ) return true; // sFrom〜sTo の間にある end += sTo.length; } return false; } // カーソル行の、カーソルのある文字の位置(先頭からの文字数(桁位置)、0開始)を返す。 // 日本語のみ対応。 // 戻り値: // 文字位置(0開始)。 // 例: カーソル行の文字列が「abcあいう」で、カーソルが「い」の位置 → 4 を返す。 function getCursorColumn() { var str; // カーソル行の文字列。 var x; // カーソルの桁位置(先頭からのバイト数)。 var len, i, c; //2012/07/28 Unicode版対応 if( bUNICODE_VER ){ // Unicode版では "$x" は(行頭からの)バイト数ではなく文字数が返る。 return Editor.ExpandParameter("$x") -1; // 文字列→数値、0開始にする。 } str = Editor.GetLineStr( 0 ); x = Editor.ExpandParameter("$x") - 1; // 文字列→数値、0開始にする。 len = 0; for(i=0; i < str.length; i++){ if( len >= x ) break; // Shift_JIS: 0x0 〜 0x80, 0xa0, 0xa1 〜 0xdf, 0xfd 〜 0xff // Unicode : 0x0 〜 0x80, 0xf8f0, 0xff61 〜 0xff9f, 0xf8f1 〜 0xf8f3 c = str.charCodeAt( i ); if( (c>=0x0 && c<=0x80) || (c>=0xf8f0 && c<=0xf8f3) || (c>=0xff61 && c<=0xff9f) ){ len++; }else{ len += 2; } } return i; }