//////////////////////////////////////////////////////////////////////////////// // tex 入力支援 // \begin{...}の直後に\end{...}を自動で補完します。 // // インストール方法 // キー SHIFT + ] にこのマクロを割り当ててください。 Main(); function Main() { // SHIFT + ] で呼び出されるので、とりあえず } を出力する Editor.Char(0x7d); // 0x7d = '}' if(Editor.IsCurTypeExt("tex")){ CompleteTagTex(false); } } // タグ補間 // bForce == true のとき // カーソル位置に入るべき終了タグを探して挿入し // カーソル位置をその直後に移す // bForce == false のとき // カーソル位置の直前に開始タグがある場合のみ // それに対応する終了タグを挿入し // カーソル位置を開始・終了タグの間に置く // いずれの場合も // 終了タグを挿入しなくてもいい場合は挿入しない // 間違った終了タグが存在する場合は、 // まずそれを置き換えることを考えてみる function CompleteTagTex(bForce) { // 現在位置と全文 var xCur = Number(Editor.ExpandParameter("$x")) - 1; var yCur = Number(Editor.ExpandParameter("$y")) - 1; var asText = []; var height = Editor.GetLineCount(0); for(var y = 1; y <= height; ++y){ asText.push(Editor.GetLineStr(y)); } // コメント(など)邪魔なのを黒塗りする for(var y = 0; y < height; ++y){ asText[y] = asText[y].replace(/\\\\|\\%|%.*?$/mg, function($0){ return $0.replace(/./g, "*"); } ) } // カーソルより左の全テキスト var as = asText.slice(0, yCur+1); as[yCur] = as[yCur].substring(0, xCur); var sBack = as.join(''); // カーソルより左にある部分から、タグを抜き出す var atTagBack = ExtractTags(sBack); if(atTagBack.length <= 0) return; // 強制補完を指定されなかったときは、なにもしないで終わる条件がいくつかある if(! bForce){ var tTagLast = atTagBack[atTagBack.length - 1]; // 今しがた入力された '>' が タグの一部でなければなにもしない if(tTagLast.posEnd != sBack.length) return; // 今入力されたタグが終了タグなら、なにもしない if(tTagLast.name.substring(0,3) == "end") return; } // カーソルより右の全テキスト as = asText.slice(yCur); as[0] = as[0].substring(xCur); var sFore = as.join(''); // カーソルより右にある部分から、タグを抜き出す var atTagFore = ExtractTags(sFore); // もし何も補完しなくてもよいならば、なにもしない var err = {}; if(IsConsistent(atTagBack.concat(atTagFore), err)){ return; } var ixErr = err["ixTagEnd"]; // 補完すべきタグ var sEndTag; if(bForce){ // 最内でまだ開いたままのタグを選択 IsConsistent(atTagBack, err); ix = err["ixTagBeg"]; if(ix != null){ sEndTag = "end" + atTagBack[ix].name.substring(5); } else{ return; } } else{ // 最後の一個 sEndTag = "end" + atTagBack[atTagBack.length - 1].name.substring(5); } // 不整合な"\end{...}"がある場合、それを消して代わりに終了タグをいれてみる if( ixErr != null && ixErr >= atTagBack.length ){ var ixErrFore = ixErr - atTagBack.length; if( atTagFore[ixErrFore].name != sEndTag ){ var t = atTagFore[ixErrFore]; atTagFore[ixErrFore] = { "name": sEndTag }; // 整合性がとれるなら if(IsConsistent(atTagBack.concat(atTagFore), {})){ // 置き換え xyTag = PositionToXY(sBack.length + t.posBeg, asText); lenTag = t.posEnd - t.posBeg; GotoXY(xyTag.x, xyTag.y); Editor.BeginSelect(); for(var i = 0; i < lenTag; ++i) Editor.Right(); Editor.InsText(sEndTag); if(! bForce){ // カーソルを挿入前のところに戻す GotoXY(xCur, yCur); } return; } atTagFore[ixErrFore] = t; // 元に戻す }} // 上でうまくいかなかったときは、そのまま挿入 Editor.InsText("\\" + sEndTag); if(! bForce){ // カーソルを挿入前のところに戻す GotoXY(xCur, yCur); } } // タグを抜き出す -> atTags = array of { name, beg, end } // name は begin{tag} もしくは end{tag} の形式 function ExtractTags(s) { var atTags = []; var re = /\\((?:begin|end)\{.+?\})/g; RegExp.lastIndex = -1; var match; while(match = re.exec(s), match != null){ atTags.push({"name": RegExp.$1, "posBeg": match.index + 1, "posEnd": match.lastIndex}); } return atTags; } // タグの並びは正常か function IsConsistent(atTags, err) { var stack = []; for(var ix = 0; ix < atTags.length; ++ix){ var tTag = atTags[ix]; var sTag = tTag.name; if(sTag.substring(0,3) == 'end'){ sTag = sTag.slice(4,-1); while(true){ if(stack.length == 0){ err["msg"] = "対応する開始タグがない"; err["ixTagBeg"] = null; err["ixTagEnd"] = ix; return false; } stackTop = stack[stack.length - 1].name; if(stackTop == sTag){ stack.pop(); break; } else{ err["msg"] = "開始タグと終了タグが一致しない"; err["ixTagBeg"] = stack[stack.length - 1].index; err["ixTagEnd"] = ix; return false; } } } else{ stack.push({ "name": sTag.slice(6,-1), "index": ix }); } } if(stack.length <= 0){ err["msg"] = null; err["ixTagBeg"] = null; err["ixTagEnd"] = null; return true; } else{ err["msg"] = "対応する終了タグがない"; err["ixTagBeg"] = stack[stack.length - 1].index; err["ixTagEnd"] = null; return false; } } // 指定した座標にカーソルを移動 function GotoXY(x, y) { var yCur = Number(Editor.ExpandParameter("$y")) - 1; if(yCur < y){ for(var i = yCur; i < y; ++i) Editor.Down(); } else if(yCur > y){ for(var i = yCur; i > y; --i) Editor.Up(); } var xCur = Number(Editor.ExpandParameter("$x")) - 1; if(xCur < x){ for(var i = xCur; i < x; ++i) Editor.Right(); } else if(xCur > x){ for(var i = xCur; i > x; --i) Editor.Left(); } } // 先頭からの文字数を、座標に変換 function PositionToXY(pos, asText) { for(var y = 0; y < asText.length; ++y){ var len = asText[y].length; if(pos < len) return {"x": pos, "y": y}; pos -= len; } var y = asText.length - 1; return {"x": pos + asText[y].length, "y": y}; }