サクラエディタに、対応する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