サクラエディタに、対応するHTML閉じタグへジャンプする機能を追加するパッチ
サクラエディタには、 Ctrl + ] とかで、 対応する括弧に飛ぶ機能があります。
これを拡張して、 HTMLタグだったら、その閉じタグ(または開始タグ)に飛ぶ機能を作りました。
追記:当初は、Ctrl + ] の拡張でしたが、いろいろと話し合った結果、 Ctrl + Shift + ] に割り当てることにしました。
世の中、divとかspanが複雑にネストしたHTMLがあるので、そういうコードを解読するときに便利だと思います。
HTMLはいろいろと面倒なので、ソースコードがあれになってしまったが、とりあえずちゃんと動くと思います。
<div> ↑中ほどで Ctrl + Shift + ] を押してください。 閉じタグへ飛びます。 既存の動作を優先しているので端っこで押すと、 < > の対応ジャンプを優先します。 </div> ↑閉じタグでも同様に、中ほど、 Ctrl + Shift + ] を押すと、開始タグに飛びます
こんな 感じの複雑な HTMLでも大丈夫です。
<div> <!-- <div > --> <div class="d"> <div /> </div> </div>
ただ、一部、ダメな例もあります。
以下のようなコメントがネストするみたいな壊れたHTMLでは無理です。
もちろん、クラッシュはしませんが、狙った動作はしません。
<div> <!-- <!-- <div> --> <div > --> <div class="d"> <div /> </div> </div>
また、br img などの閉じタグがないタグでは、タグジャンプしません。
それと、現状、 < >の括弧ジャンプを優先しているので、3文字以上のタグではないと、ジャンプできません。
span や div は OK ですが、 pタグはダメです。
これは既存の対応括弧に飛ぶという処理を優先しているためです。
どうするかは課題です。
Ctrl + Shift + ] にしたので問題なくなりました。
以下パッチ。
SVNの最新版に対するパッチです。
Index: sakura_core/Funccode_define.h =================================================================== --- sakura_core/Funccode_define.h (revision 4158) +++ sakura_core/Funccode_define.h (working copy) @@ -240,6 +240,7 @@ #define F_TAGJUMP_KEYWORD 30946 #define F_COMPARE 30950 #define F_BRACKETPAIR 30960 +#define F_HTMLTAGPAIR 30961 #define F_BOOKMARK_SET 30970 #define F_BOOKMARK_NEXT 30971 #define F_BOOKMARK_PREV 30972 Index: sakura_core/Funccode_enum.h =================================================================== --- sakura_core/Funccode_enum.h (revision 4158) +++ sakura_core/Funccode_enum.h (working copy) @@ -241,6 +241,7 @@ F_TAGJUMP_KEYWORD = 30946, F_COMPARE = 30950, F_BRACKETPAIR = 30960, + F_HTMLTAGPAIR = 30961, F_BOOKMARK_SET = 30970, F_BOOKMARK_NEXT = 30971, F_BOOKMARK_PREV = 30972, Index: sakura_core/Funccode_x.hsrc =================================================================== --- sakura_core/Funccode_x.hsrc (revision 4158) +++ sakura_core/Funccode_x.hsrc (working copy) @@ -317,6 +317,7 @@ F_TAGJUMP_KEYWORD = 30946, //キーワードを指定してダイレクトタグジャンプ const WCHAR* keyword F_COMPARE = 30950, //ファイル内容比較 なし F_BRACKETPAIR = 30960, //対括弧の検索 なし +F_HTMLTAGPAIR = 30961, //対HTMLタグの検索 なし F_BOOKMARK_SET = 30970, //ブックマーク設定・解除 なし F_BOOKMARK_NEXT = 30971, //次のブックマークへ なし F_BOOKMARK_PREV = 30972, //前のブックマークへ なし Index: sakura_core/cmd/CViewCommander.cpp =================================================================== --- sakura_core/cmd/CViewCommander.cpp (revision 4158) +++ sakura_core/cmd/CViewCommander.cpp (working copy) @@ -455,6 +455,7 @@ case F_DIFF_PREV: Command_Diff_Prev();break; /* DIFF差分表示(前へ) */ //@@@ 2002.05.25 MIK case F_DIFF_RESET: Command_Diff_Reset();break; /* DIFF差分表示(全解除) */ //@@@ 2002.05.25 MIK case F_BRACKETPAIR: Command_BRACKETPAIR(); break; //対括弧の検索 + case F_HTMLTAGPAIR: Command_HTMLTAGPAIR(); break; //対HTMLタグの検索 // From Here 2001.12.03 hor case F_BOOKMARK_SET: Command_BOOKMARK_SET();break; /* ブックマーク設定・解除 */ case F_BOOKMARK_NEXT: Command_BOOKMARK_NEXT();break; /* 次のブックマークへ */ Index: sakura_core/cmd/CViewCommander.h =================================================================== --- sakura_core/cmd/CViewCommander.h (revision 4158) +++ sakura_core/cmd/CViewCommander.h (working copy) @@ -299,6 +299,7 @@ void Command_Diff_Prev( void ); /* 前の差分へ */ //@@@ 2002.05.25 MIK void Command_Diff_Reset( void ); /* 差分の全解除 */ //@@@ 2002.05.25 MIK void Command_BRACKETPAIR( void ); /* 対括弧の検索 */ + void Command_HTMLTAGPAIR( void ); /* 対HTMLタグの検索 */ // From Here 2001.12.03 hor void Command_BOOKMARK_SET( void ); /* ブックマーク設定・解除 */ void Command_BOOKMARK_NEXT( void ); /* 次のブックマークへ */ Index: sakura_core/cmd/CViewCommander_Search.cpp =================================================================== --- sakura_core/cmd/CViewCommander_Search.cpp (revision 4158) +++ sakura_core/cmd/CViewCommander_Search.cpp (working copy) @@ -1547,3 +1547,27 @@ // 何もしない } } + +// Dec. 25 rti7743 +// 対HTMLタグの検索 +void CViewCommander::Command_HTMLTAGPAIR( void ) +{ + CLayoutPoint ptColLine; + //int nLine, nCol; + + int mode = 3; + /* + bit0(in) : 表示領域外を調べるか? 0:調べない 1:調べる + bit1(in) : 前方文字を調べるか? 0:調べない 1:調べる + bit2(out) : 見つかった位置 0:後ろ 1:前 + */ + if( m_pCommanderView->SearchHTMLTag( GetCaret().GetCaretLayoutPos(), &ptColLine, &mode ) ){ // 02/09/18 ai + // 2005.06.24 Moca + // 2006.07.09 genta 表示更新漏れ:新規関数にて対応 + m_pCommanderView->MoveCursorSelecting( ptColLine, m_pCommanderView->GetSelectionInfo().m_bSelectingLock ); + } + else{ + // 失敗した場合は nCol/nLineには有効な値が入っていない. + // 何もしない + } +} Index: sakura_core/func/CKeyBind.cpp =================================================================== --- sakura_core/func/CKeyBind.cpp (revision 4158) +++ sakura_core/func/CKeyBind.cpp (working copy) @@ -796,10 +796,10 @@ { 0x00de, (LPCTSTR)STR_KEY_BIND_HAT_ENG_QT, { F_0, F_0, F_COPYTAG, F_0, F_0, F_0, F_0, F_0 }, }, { 0x00dc, _T("\\"), { F_0, F_0, F_COPYPATH, F_SPLIT_H, F_0, F_0, F_0, F_0 }, }, { 0x00c0, (LPCTSTR)STR_KEY_BIND_AT_ENG_BQ, { F_0, F_0, F_COPYLINES, F_0, F_0, F_0, F_0, F_0 }, }, - { 0x00db, _T("["), { F_0, F_0, F_BRACKETPAIR, F_0, F_0, F_0, F_0, F_0 }, }, + { 0x00db, _T("["), { F_0, F_0, F_BRACKETPAIR, F_HTMLTAGPAIR, F_0, F_0, F_0, F_0 }, }, { 0x00bb, _T(";"), { F_0, F_0, F_0, F_SPLIT_VH, F_INS_DATE, F_0, F_0, F_0 }, }, { 0x00ba, _T(":"), { F_0, F_0, _COPYWITHLINENUM, F_0, F_INS_TIME, F_0, F_0, F_0 }, }, - { 0x00dd, _T("]"), { F_0, F_0, F_BRACKETPAIR, F_0, F_0, F_0, F_0, F_0 }, }, + { 0x00dd, _T("]"), { F_0, F_0, F_BRACKETPAIR, F_HTMLTAGPAIR, F_0, F_0, F_0, F_0 }, }, { 0x00bc, _T(","), { F_0, F_0, F_0, F_0, F_0, F_0, F_0, F_0 }, }, { 0x00be, _T("."), { F_0, F_0, F_COPYLINESASPASSAGE, F_0, F_0, F_0, F_0, F_0 }, }, { 0x00bf, _T("/"), { F_0, F_0, F_HOKAN, F_0, F_0, F_0, F_0, F_0 }, }, Index: sakura_core/func/Funccode.cpp =================================================================== --- sakura_core/func/Funccode.cpp (revision 4158) +++ sakura_core/func/Funccode.cpp (working copy) @@ -353,6 +353,7 @@ F_DIFF_PREV , //前の差分へ F_DIFF_RESET , //差分の全解除 F_BRACKETPAIR , //対括弧の検索 + F_HTMLTAGPAIR , //対HTMLタグの検索 F_BOOKMARK_SET , //ブックマーク設定・解除 F_BOOKMARK_NEXT , //次のブックマークへ F_BOOKMARK_PREV , //前のブックマークへ Index: sakura_core/macro/CSMacroMgr.cpp =================================================================== --- sakura_core/macro/CSMacroMgr.cpp (revision 4158) +++ sakura_core/macro/CSMacroMgr.cpp (working copy) @@ -280,6 +280,7 @@ {F_DIFF_PREV, LTEXT("DiffPrev"), {VT_EMPTY, VT_EMPTY, VT_EMPTY, VT_EMPTY}, VT_EMPTY, NULL}, //DIFF差分表示(前へ) //@@@ 2002.05.25 MIK {F_DIFF_RESET, LTEXT("DiffReset"), {VT_EMPTY, VT_EMPTY, VT_EMPTY, VT_EMPTY}, VT_EMPTY, NULL}, //DIFF差分表示(全解除) //@@@ 2002.05.25 MIK {F_BRACKETPAIR, LTEXT("BracketPair"), {VT_EMPTY, VT_EMPTY, VT_EMPTY, VT_EMPTY}, VT_EMPTY, NULL}, //対括弧の検索 + {F_HTMLTAGPAIR, LTEXT("HtmlTagPair"), {VT_EMPTY, VT_EMPTY, VT_EMPTY, VT_EMPTY}, VT_EMPTY, NULL}, //対HTMLタグの検索 // From Here 2001.12.03 hor {F_BOOKMARK_SET, LTEXT("BookmarkSet"), {VT_EMPTY, VT_EMPTY, VT_EMPTY, VT_EMPTY}, VT_EMPTY, NULL}, //ブックマーク設定・解除 {F_BOOKMARK_NEXT, LTEXT("BookmarkNext"), {VT_EMPTY, VT_EMPTY, VT_EMPTY, VT_EMPTY}, VT_EMPTY, NULL}, //次のブックマークへ @@ -1103,6 +1104,7 @@ // case F_DIFF_PREV ://DIFF差分表示(前へ) //@@@ 2002.05.25 MIK // case F_DIFF_RESET ://DIFF差分表示(全解除) //@@@ 2002.05.25 MIK case F_BRACKETPAIR ://対括弧の検索 + case F_HTMLTAGPAIR ://対HTMLタグの検索 // From Here 2001.12.03 hor case F_BOOKMARK_SET ://ブックマーク設定・解除 case F_BOOKMARK_NEXT ://次のブックマークへ Index: sakura_core/sakura_rc.rc =================================================================== --- sakura_core/sakura_rc.rc (revision 4158) +++ sakura_core/sakura_rc.rc (working copy) @@ -3239,6 +3239,7 @@ STRINGTABLE DISCARDABLE BEGIN F_BRACKETPAIR "対括弧の検索" + F_HTMLTAGPAIR "対HTMLタグの検索" F_BOOKMARK_SET "ブックマーク設定・解除" F_BOOKMARK_NEXT "次のブックマークへ" F_BOOKMARK_PATTERN "該当行マーク" Index: sakura_core/view/CEditView.h =================================================================== --- sakura_core/view/CEditView.h (revision 4158) +++ sakura_core/view/CEditView.h (working copy) @@ -499,6 +499,11 @@ void DrawBracketPair( bool ); /* 対括弧の強調表示 02/09/18 ai */ bool IsBracket( const wchar_t*, CLogicInt, CLogicInt ); /* 括弧判定 03/01/09 ai */ + bool SearchHTMLTag( const CLayoutPoint& ptPos, CLayoutPoint* pptLayoutNew, int* mode ); // 対HTMLタグの検索 + int getHTMLTagName( const wchar_t *cline ,const CLogicPoint& ptPos , wchar_t *out_html_tag, int max_html_tag_size); + bool SearchHTMLTagForward(CLogicPoint ptPos,CLayoutPoint* pptLayoutNew,const wchar_t* tagname,int* mode); + bool SearchHTMLTagBackward(CLogicPoint ptPos,CLayoutPoint* pptLayoutNew,const wchar_t* tagname,int* mode); + // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- // // 補完 // // -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- // Index: sakura_core/view/CEditView_Paint_Bracket.cpp =================================================================== --- sakura_core/view/CEditView_Paint_Bracket.cpp (revision 4158) +++ sakura_core/view/CEditView_Paint_Bracket.cpp (working copy) @@ -599,3 +599,688 @@ } //@@@ 2003.01.09 End + +/*! + @brief 対HTMLタグの検索 + + カーソル位置のHTMLタグに対応するHTMLタグを探す。カーソル位置がHTMLタグでない場合は + カーソルの後ろの文字がHTMLタグかどうかを調べる。 + + カーソルの前後いずれもがHTMLタグでない場合は何もしない。 + + @param ptLayout [in] 検索開始点の物理座標 + @param pptLayoutNew [out] 移動先のレイアウト座標 + @param mode [in,out] bit0(in) : 表示領域外を調べるか? 0:調べない 1:調べる + bit1(in) : 前方文字を調べるか? 0:調べない 1:調べる (このbitを参照) + bit2(out) : 見つかった位置 0:後ろ 1:前 (このbitを更新) + + @retval true 成功 + @retval false 失敗 + + @author rti7743 + @date Dec. 25, 2016 rti7743 +*/ +bool CEditView::SearchHTMLTag( + const CLayoutPoint& ptLayout, + CLayoutPoint* pptLayoutNew, + int* mode +) +{ + CLogicInt len; // 行の長さ + + CLogicPoint ptPos; + + m_pcEditDoc->m_cLayoutMgr.LayoutToLogic( ptLayout, &ptPos ); + const wchar_t *cline = m_pcEditDoc->m_cDocLineMgr.GetLine(ptPos.GetY2())->GetDocLineStrWithEOL(&len); + + // Jun. 19, 2000 genta + if( cline == NULL ) // 最後の行に本文がない場合 + return false; + + //HTMLタグ検索 + //2016/12/24 rti7743 + wchar_t html_tag[64]; + int found_html_tag = getHTMLTagName( cline , ptPos , html_tag , 64); + if(found_html_tag == 0) + { + return false; + } + + //閉じタグいらないタグだったら無視. + static const wchar_t* aSkipTags[] = { + L"BR" + ,L"IMG" + ,L"HR" + ,L"META" + ,L"INPUT" + ,L"EMBED" + ,L"AREA" + ,L"BASE" + ,L"COL" + ,L"KEYGEN" +// ,L"LINK" //RSSなどのxmlで使われることがあるので外すか + ,L"PARAM" + ,L"SOURCE" + //終端 + ,NULL + }; + for( int i = 0; aSkipTags[i] ; i++ ) + { + if( wcscmp(html_tag,aSkipTags[i]) == 0 ) + {//閉じタグ不要の検索禁止タグ + return false; + } + } + + if( found_html_tag == 1 ) + { + return SearchHTMLTagForward( ptPos, pptLayoutNew, html_tag, mode ); + } + else if( found_html_tag == 2 ) + { + return SearchHTMLTagBackward( ptPos, pptLayoutNew, html_tag, mode ); + } + + return false; +} + +/*! + @brief 現在位置にあるHTMLタグを取得 + + @author rti7743 + + @param cline [in] 現在の行データ + @param ptPos [in] 移動の座標 + @param out_html_tag[out] タグ名 DIV等が入る. 必ず大文字で格納 + @param max_html_tag_size [in] out_html_tagバッファのサイズ + + @retval 0 タグはありません + @retval 1 開始タグ + @retval 2 閉じタグ +*/ +int CEditView::getHTMLTagName( const wchar_t *cline ,const CLogicPoint& ptPos , wchar_t *out_html_tag, int max_html_tag_size) +{ + int x = ptPos.x; + for( ; x >= 0 ; x--) + { + if( cline[x] == L'<' ) + { + break; + } + } + if(x < 0) + {//タグではない + return 0; + } + x++;//skip < tag + + //タグ名の取得 + bool isCloseTag = false; + if( cline[x] == L'/' ) + {//閉じタグ + isCloseTag = true; + x++; // skip / + } + + int x2 = x; + for( ; cline[x2] ; x2++ ) + { + if(cline[x2] == L' ' + || cline[x2] == L'\t' + || cline[x2] == L'/' + || cline[x2] == L'>' + || cline[x2] == L'\n' + || cline[x2] == L'\r' + ) + { + break; + } + } + + const int size = x2-x; + if(size+1 >= max_html_tag_size) + {//タグの長さが長すぎる + return 0; + } + //タグ名を大文字に変換して格納. + for(int i = 0 ; i < size ; i++) + { + wchar_t c = cline[x+i]; + if(c >= L'a' && c <= L'z') + { + c = c - L'a' + L'A'; //強制大文字 + } + out_html_tag[i] = c; + } + out_html_tag[size] = 0; //null終端 + + if(isCloseTag) + { + return 2; //閉じタグ + } + return 1; //開始タグ +} + + + +/*! + @brief HTMLタグの検索:順方向 + + @author rti7743 + + @param ptPos [in] 検索開始点の物理座標 + @param pptLayoutNew [out] 移動先のレイアウト座標 + @param tagname[in] 括弧の始まりの文字 + @param mode [in] bit0(in) : 表示領域外を調べるか? 0:調べない 1:調べる (このbitを参照) + bit1(in) : 前方文字を調べるか? 0:調べない 1:調べる + bit2(out) : 見つかった位置 0:後ろ 1:前 + + @retval true 成功 + @retval false 失敗 +*/ +bool CEditView::SearchHTMLTagForward( + CLogicPoint ptPos, + CLayoutPoint* pptLayoutNew, + const wchar_t* tagname, + int* mode +) +{ + const CDocLine* ci; + + int len; + const wchar_t* cPos; + const wchar_t* nPos; + const wchar_t* cline; + const wchar_t* lineend; + int level = 1; + + CLayoutPoint ptColLine; + CLayoutInt nSearchNum; + + // 初期位置の設定 + m_pcEditDoc->m_cLayoutMgr.LogicToLayout( ptPos, &ptColLine ); + nSearchNum = ( GetTextArea().GetBottomLine() ) - ptColLine.y; + ci = m_pcEditDoc->m_cDocLineMgr.GetLine( ptPos.GetY2() ); + cline = ci->GetDocLineStrWithEOL( &len ); + lineend = cline + len; + cPos = cline + ptPos.x + 1; + + enum state_need + { + state_need_start_braket // < + ,state_need_if_slash // / + ,state_need_tagname // div + ,state_need_end_braket // > + }; + state_need state = state_need_start_braket; + + enum quote_skip{ + quote_skip_none + ,quote_skip_dquote //" + ,quote_skip_escape_dquote //\" + ,quote_skip_htmlcomment_s1 //<! + ,quote_skip_htmlcomment_s2 //<!- + ,quote_skip_htmlcomment_s3 //<!-- + ,quote_skip_htmlcomment_e1 // + ,quote_skip_htmlcomment_e2 // + ,quote_skip_htmlcomment_e3 //--> + }; + quote_skip qskip = quote_skip_none; + + bool isCLoseTag = false; + int tag_match_pos = 0; + wchar_t lastPos = 0; + + do { + for( ; cPos < lineend ; cPos = nPos){ + nPos = CNativeW::GetCharNext( cline, len, cPos ); + if( nPos - cPos > 1 ){ + continue; // skip + } + + //クウォートの読み飛ばし. + + if(qskip == quote_skip_none){ + if(*cPos == L'"'){ + qskip = quote_skip_dquote; + continue; + } + } + else if(qskip == quote_skip_dquote){ //" " を読み飛ばす + if(*cPos == L'\\'){ + qskip = quote_skip_escape_dquote; + } + else if(*cPos == L'"'){ + qskip = quote_skip_none; + } + continue; + } + else if(qskip == quote_skip_escape_dquote){ // \" を読み飛ばす + qskip = quote_skip_dquote; + continue; + } + else if(qskip == quote_skip_htmlcomment_s1){// <!-- --> + if(*cPos == L'-'){ + qskip = quote_skip_htmlcomment_s2; + continue; + } + qskip = quote_skip_none; + } + else if(qskip == quote_skip_htmlcomment_s2){// <!-- --> + if(*cPos == L'-'){ + qskip = quote_skip_htmlcomment_e1; + continue; + } + qskip = quote_skip_none; + } + else if(qskip == quote_skip_htmlcomment_e1){// <!-- --> + if(*cPos == L'-'){ + qskip = quote_skip_htmlcomment_e2; + } + continue; + } + else if(qskip == quote_skip_htmlcomment_e2){// <!-- --> + if(*cPos == L'-'){ + qskip = quote_skip_htmlcomment_e3; + } + else{ + qskip = quote_skip_htmlcomment_e1; + } + continue; + } + else if(qskip == quote_skip_htmlcomment_e3) {// <!-- --> + if(*cPos == L'>'){ + qskip = quote_skip_none; + } + else{ + qskip = quote_skip_htmlcomment_e1; + } + continue; + } + + if(state == state_need_start_braket){ + if(*cPos == L'<'){ + state = state_need_if_slash; + } + + continue; + } + + if(state == state_need_if_slash){ + isCLoseTag = (*cPos == L'/'); + state = state_need_tagname; + tag_match_pos = 0; + if(isCLoseTag){ + continue; //skip / + } + } + + if(state == state_need_tagname){ + if(tagname[tag_match_pos]){ + wchar_t c = *cPos; + if(c >= L'a' && c <= L'z'){ + c = c - L'a' + L'A'; //強制大文字 + } + if( c == tagname[tag_match_pos]){ + tag_match_pos++; + continue; + } + + state = state_need_start_braket; + if( !isCLoseTag && c == L'!'){ //<!-- のコメントかも知れない + qskip = quote_skip_htmlcomment_s1; + continue; + } + continue; + } + + //タグ名の終端までマッチ + if(*cPos == L'>'){ + //<div> 名前後即閉じ + state = state_need_end_braket; + lastPos = *cPos; + } + else if(*cPos == L' ' || *cPos == L'\t' || *cPos == L'\r' || *cPos == L'\n'){ + //<div > + state = state_need_end_braket; + lastPos = *cPos; + } + else{ + //<div/> or <divaa> 自己完結しているか、名前違いなので読み飛ばす. + state = state_need_start_braket; + continue; + } + } + + if(state == state_need_end_braket){ + if(*cPos != L'>'){ + lastPos = *cPos; + continue; + } + + //タグの終わり + if(isCLoseTag){ + --level; + if( level == 0 ){ // 見つかった! + ptPos.x = cPos - cline; + m_pcEditDoc->m_cLayoutMgr.LogicToLayout( ptPos, pptLayoutNew ); + return true; + // Happy Ending + } + } + else{ + if(lastPos == L'/'){ + //<div /> 自己完結しているので無視 + } + else{//<div> + ++level; + } + } + + //タグ検索の最初に戻る. + state = state_need_start_braket; + continue; + } + } + + nSearchNum--; + if( ( 0 > nSearchNum ) && ( 0 == (*mode & 1 ) ) ){ + // 表示領域外を調べないモードで表示領域の終端の場合 + break; + } + + // 次の行へ + ptPos.y++; + ci = ci->GetNextLine(); // 次のアイテム + if( ci == NULL ){ + break; // 終わりに達した + } + + cline = ci->GetDocLineStrWithEOL( &len ); + cPos = cline; + lineend = cline + len; + }while( cline != NULL ); + + return false; +} + +/*! + @brief HTMLタグの検索:逆方向 + + @author rti7743 + + @param ptPos [in] 検索開始点の物理座標 + @param pptLayoutNew [out] 移動先のレイアウト座標 + @param tagname [in] 括弧の始まりの文字 + @param mode [in] bit0(in) : 表示領域外を調べるか? 0:調べない 1:調べる (このbitを参照) + bit1(in) : 前方文字を調べるか? 0:調べない 1:調べる + bit2(out) : 見つかった位置 0:後ろ 1:前 + + @retval true 成功 + @retval false 失敗 +*/ +bool CEditView::SearchHTMLTagBackward( + CLogicPoint ptPos, + CLayoutPoint* pptLayoutNew, + const wchar_t* tagname, + int* mode +) +{ + const CDocLine* ci; + + int len; + const wchar_t* cPos; + const wchar_t* pPos; + const wchar_t* cline; + int level = 1; + + CLayoutPoint ptColLine; + CLayoutInt nSearchNum; + + // 初期位置の設定 + m_pcEditDoc->m_cLayoutMgr.LogicToLayout( ptPos, &ptColLine ); + nSearchNum = ptColLine.y - GetTextArea().GetViewTopLine(); + ci = m_pcEditDoc->m_cDocLineMgr.GetLine( ptPos.GetY2() ); + cline = ci->GetDocLineStrWithEOL( &len ); + cPos = cline + ptPos.x; + + enum state_need + { + state_need_end_braket // > + ,state_need_if_slash // / + ,state_need_space // space + ,state_need_tagname // div + ,state_need_if_fist_slash // / + ,state_need_start_braket // < + }; + state_need state = state_need_end_braket; + + enum quote_skip{ + quote_skip_none + ,quote_skip_dquote //" + ,quote_skip_if_term_dquote //" + + ,quote_skip_htmlcomment_e1 //--> + ,quote_skip_htmlcomment_s1 //- + ,quote_skip_htmlcomment_s2 //-- + ,quote_skip_htmlcomment_s3 //!-- + ,quote_skip_htmlcomment_s4 //<!-- + }; + quote_skip qskip = quote_skip_none; + + bool isCLoseTag = false; + int tag_match_pos = 0; + const int tagname_size = wcslen(tagname) - 1; + if(tagname_size < 0) + { + return false; + } + + do { + for( ; cPos > cline ; cPos = pPos){ + pPos = CNativeW::GetCharPrev( cline, len, cPos ); + if( cPos - pPos > 1 ){ + continue; + } + + //クウォートの読み飛ばし. + if(qskip == quote_skip_none){ + if(*pPos == L'"'){ + qskip = quote_skip_dquote; + continue; + } + } + else if(qskip == quote_skip_dquote){//" " を読み飛ばす + if(*pPos == L'"') + { + qskip = quote_skip_if_term_dquote; + } + continue; + } + else if(qskip == quote_skip_if_term_dquote){ + if(*pPos == L'\\') + {// \" なのでまだ継続中 + qskip = quote_skip_dquote; + continue; + } + qskip = quote_skip_none; + } + else if(qskip == quote_skip_htmlcomment_e1){// <!-- --> + if(*pPos == L'-'){ + qskip = quote_skip_htmlcomment_s1; + continue; + } + qskip = quote_skip_none; + } + else if(qskip == quote_skip_htmlcomment_s1){// <!-- --> + if(*pPos == L'-'){ + qskip = quote_skip_htmlcomment_s2; + } + continue; + } + else if(qskip == quote_skip_htmlcomment_s2){// <!-- --> + if(*pPos == L'-'){ + qskip = quote_skip_htmlcomment_s3; + } + else{ + qskip = quote_skip_htmlcomment_s1; + } + continue; + } + else if(qskip == quote_skip_htmlcomment_s3){// <!-- --> + if(*pPos == L'!'){ + qskip = quote_skip_htmlcomment_s4; + } + else{ + qskip = quote_skip_htmlcomment_s1; + } + continue; + } + else if(qskip == quote_skip_htmlcomment_s4){// <!-- --> + if(*pPos == L'<'){ + qskip = quote_skip_none; + } + else{ + qskip = quote_skip_htmlcomment_s1; + } + continue; + } + + + if(state == state_need_end_braket) { + if(*pPos == L'>'){ + state = state_need_if_slash; + } + continue; + } + + //後ろから読んでいるので <div/> みたいに 即閉じの可能性検証 + if(state == state_need_if_slash) { + if(*pPos == L'/'){ //即閉じタグ <div/> なので読み飛ばす + state = state_need_end_braket; + continue; + } + if(*pPos == L'-'){ // --> コメントの可能性 + state = state_need_end_braket; + qskip = quote_skip_htmlcomment_e1; + continue; + } + state = state_need_tagname; + tag_match_pos = 0; + } + + //タグのアトリビュートがあった後で、スペースがあってタグの名前があるので、 + //そのスペースを探す<div a=fの スペース + if(state == state_need_space){ + if(*pPos == L' ' + || *pPos == L'\t' + || *pPos == L'\r' + || *pPos == L'\n'){ + state = state_need_tagname; + tag_match_pos = 0; + continue; + } + if(*pPos == L'<'){ + state = state_need_end_braket; + continue; + } + + if(*pPos == L'(' + || *pPos == L')' + || *pPos == L'{' + || *pPos == L'}' + || *pPos == L';'){//javascript / style内誤爆 + state = state_need_end_braket; + continue; + } + } + + //タグの名前 + if(state == state_need_tagname){ + if(*pPos == L'>'){ + state = state_need_if_slash; + continue; + } + if(tag_match_pos == 0){ + if(*pPos == L' ' + || *pPos == L'\t' + || *pPos == L'\r' + || *pPos == L'\n'){ + continue; + } + } + if(tag_match_pos <= tagname_size){ + if(*pPos == L'<'){ + state = state_need_end_braket; + continue; + } + + wchar_t c = *pPos; + if(c >= L'a' && c <= L'z'){ + c = c - L'a' + L'A'; //強制大文字 + } + + if(c != tagname[tagname_size - tag_match_pos]){ //名前が違う + state = state_need_space; // div で <divaaa> を無視するために必要. + tag_match_pos=0; + } + else{ + tag_match_pos++; + } + continue; + } + state = state_need_if_fist_slash; + } + + //名前の後に / があれば</div>みたいな閉じタグ + if(state == state_need_if_fist_slash) { + isCLoseTag = (*pPos == L'/'); + state = state_need_start_braket; + if(isCLoseTag) { + continue; + } + } + + //タグ開始の<検索 + if(state == state_need_start_braket) { + if(*pPos != L'<') { + state = state_need_tagname; + tag_match_pos = 0; + continue; + } + + if(isCLoseTag) { //閉じタグ + ++level; + } + else{ //開始タグ + --level; + if( level == 0 ){ // 見つかった! + ptPos.x = pPos - cline; + m_pcEditDoc->m_cLayoutMgr.LogicToLayout( ptPos, pptLayoutNew ); + return true; + // Happy Ending + } + } + state = state_need_end_braket; + } + } + + nSearchNum--; + if( ( 0 > nSearchNum ) && ( 0 == (*mode & 1 ) ) ){ + // 表示領域外を調べないモードで表示領域の先頭の場合 + break; + } + + // 次の行へ + ptPos.y--; + ci = ci->GetPrevLine(); // 次のアイテム + if( ci == NULL ){ + break; // 終わりに達した + } + + cline = ci->GetDocLineStrWithEOL( &len ); + cPos = cline + len; + }while( cline != NULL ); + + return false; +}
patch -p0 -E < saruka_htmltag_jump.diff みたいな感じでバッチを当ててください。
(多少変えているので、github参照してください。)
githubに、sakura editorのパッチ適応版を作っていた人がいたので、それを利用してフルビルド用のルーチンも作ってみました。
https://github.com/rti7743/sakura