分享

RichEdit 控件:语法高亮显示

 求知881 2015-10-14

在读这篇教程之前先提醒你,这是一个复杂的主题:不适合初学者。这是最后一篇RichEdit 控件教程。


下载 例子程序.


Theory


语法高亮显示对那些编写文本编辑器的人来说是一个热点主题。最好的解决方法(我自己认为的)是编写一个定制的Edit控件,这也是很多商业软件所使用的方法。然而,对于那些没什么时间来编写这么一个控件的人来说,次策就是改写现有的控件使之符合我们的需要。


让我们看一看,到底 RichEdit 控件提供了什么功能来帮助我们实现语法高亮。现在我应该声明,下面的方法不是一个“正确”方法:我只是给你们指出那些缺陷。 RichEdit 控件提供了EM_SETCHARFORMAT 消息,是你可以用来改变正文颜色。乍一看,这个消息好象是一个完美的解决方法(我之所以知道是因为我也是其中的一个受骗者)。然而靠近看看,将会发现有几个不合意的地方:



  • EM_SETCHARFORMAT 仅仅对选定的正文或者控件中所有的正文有影响。如果你想改变正文颜色(高亮显示某一个特定的词),你必须先选定它。 
  • EM_SETCHARFORMAT 执行速度很慢。 
  • RichEdit 控件中的插入点位置处理也有一点问题。

通过上面的讨论,你可以看到使用 EM_SETCHARFORMAT 是一个错误的选择,我会给你演示 "相当正确" 的选择。


我现在使用的方法是“即时语法高亮”,我只高亮显示可见部分的正文。因此高亮显示的速度跟文件的大小根本是无关的。无论是多大的文件,在某一时刻只有一小部分是可见的。


怎么样实现?答案很简单:



  1. 子类化RichEdit控件并在你自己的窗口处理函数中处理 WM_PAINT 消息。 
  2. 当收到 WM_PAINT 消息时,它调用RichEdit控件原来的窗口过程,让它正常地更新屏幕。 
  3. 之后,我们将要高亮显示的词用不同的颜色来覆盖掉。

当然了,路也不是这么容易走的:仍然有两个次要的问题需要矫正,不过上面的方法工作起来很好。显示速度令人很满意。


现在让我们集中在细节上。子类化处理是很简单的,不需要很多注意力。真正复杂的部分是:我们必须找到一个快速的方法来搜索那些需要高亮的词。更复杂的那些在某个注释块里的 不 需要高亮显示的词。


我使用的方法可能不是最好的,但是它工作的很好。我敢肯定你可以找到更快的方法。不管怎么说,先看看我下面的方法:



  • 我创建一个有256元素的双字(DWORD)数组,全部初始化为0。每一个元素对应一个可能的 ASCII 字符,数组名叫 ASMSyntaxArray。例如,第21个元素代表ASCII 20h (空格字符)。我将他们作为快速查询表使用:譬如,假定我有一个词 "include",我从词中分离出第一个字符 (i) ,并以响应索引查找数组。如果该元素为0,我就立刻知道需要高亮的词是没有以 "i" 开头的。如果该元素非0,它就包含一个指针,指向一个 WORDINFO 结构的链表。里面包含了需要高亮词的信息。 
  • 我读取需要高亮显示的词,并为每个词创建一个 WORDINFO 结构。
			WORDINFO struct 
 				WordLen dd ? 		; 词的长度,用来快速比较
 				pszWord dd ? 	    ; 词的指针
 				pColor dd ? 		; 用来高亮显示的颜色所在的DWORD的指针
 				NextLink dd ? 		; 下一个 WORDINFO 结构
 			WORDINFO ends 
 


正如你所看到的,我使用词的长度来作为第二个快速比较方法。如果词中的第一个字符匹配后,我们下一个比较的是词的才长度。ASMSyntaxArray 中的每一个元素包含了一个指针,指向一个相关的WORDINFO 数组.例如,代表字符 "i" 的元素将会包含一个指向以"i"开头的词的链表。 pColor 成员指向一个DWORD,包含用来做高亮显示该词的颜色值。pszWord 指向要高亮显示的词。是小写形式的。



  • 链表的内存是从堆(heap)中分配的,速度快,容易清除,也就是说根本不用清楚。

高亮词列表保存在文件 "wordfile.txt"中,我通过 GetPrivateProfileString API 函数来访问。我提供了多达10种不同的语法颜色,从 C1 到 C10。颜色数组名叫 ASMColorArray。每一个 WORDINFO 结构的 pColor 成员都指向 ASMColorArray 中的某一个元素。因此闲时很容易改变语法颜色:你只需要改变 ASMColorArray 中的元素的值,这样所有使用那种颜色高亮的词就立刻使用新颜色显示。


例子

.386
 .model flat,stdcall
 option casemap:none
 include \masm32\include\windows.inc
 include \masm32\include\user32.inc
 include \masm32\include\comdlg32.inc
 include \masm32\include\gdi32.inc
 include \masm32\include\kernel32.inc
 includelib \masm32\lib\gdi32.lib
 includelib \masm32\lib\comdlg32.lib
 includelib \masm32\lib\user32.lib
 includelib \masm32\lib\kernel32.lib
 
 WinMain proto :DWORD,:DWORD,:DWORD,:DWORD
 
 WORDINFO struct
 	WordLen dd ?		; 词的长度:用来进行快速比较
 	pszWord dd ?		; 词的指针
 	pColor dd ?		; 指向某个DWORD,里面包含用来高亮显示词的颜色值。
 	NextLink dd ?		; 指向下一个 WORDINFO 结构
 WORDINFO ends
 
 .const
 IDR_MAINMENU          equ 101
 IDM_OPEN           equ 40001
 IDM_SAVE            equ 40002
 IDM_CLOSE           equ 40003
 IDM_SAVEAS           equ 40004
 IDM_EXIT            equ 40005
 IDM_COPY           equ 40006
 IDM_CUT            equ 40007
 IDM_PASTE           equ 40008
 IDM_DELETE           equ 40009
 IDM_SELECTALL         equ 40010
 IDM_OPTION 			equ 40011
 IDM_UNDO			equ 40012
 IDM_REDO			equ 40013
 IDD_OPTIONDLG         equ 101
 IDC_BACKCOLORBOX        equ 1000
 IDC_TEXTCOLORBOX        equ 1001
 IDR_MAINACCEL         equ 105
 IDD_FINDDLG          equ 102
 IDD_GOTODLG          equ 103
 IDD_REPLACEDLG         equ 104
 IDC_FINDEDIT         equ 1000
 IDC_MATCHCASE         equ 1001
 IDC_REPLACEEDIT         equ 1001
 IDC_WHOLEWORD         equ 1002
 IDC_DOWN            equ 1003
 IDC_UP            equ  1004
 IDC_LINENO          equ  1005
 IDM_FIND            equ 40014
 IDM_FINDNEXT         equ 40015
 IDM_REPLACE           equ 40016
 IDM_GOTOLINE          equ 40017
 IDM_FINDPREV         equ 40018
 RichEditID 			equ 300
 
 .data
 ClassName db "IczEditClass",0
 AppName db "IczEdit version 3.0",0
 RichEditDLL db "riched20.dll",0
 RichEditClass db "RichEdit20A",0
 NoRichEdit db "Cannot find riched20.dll",0
 ASMFilterString 		db "ASM Source code (*.asm)",0,"*.asm",0
 				db "All Files (*.*)",0,"*.*",0,0
 OpenFileFail db "Cannot open the file",0
 WannaSave db "The data in the control is modified. Want to save it?",0
 FileOpened dd FALSE
 BackgroundColor dd 0FFFFFFh		; 缺省为白色
 TextColor dd 0		; 缺省为黑色
 WordFileName db "\wordfile.txt",0
 ASMSection db "ASSEMBLY",0
 C1Key db "C1",0
 C2Key db "C2",0
 C3Key db "C3",0
 C4Key db "C4",0
 C5Key db "C5",0
 C6Key db "C6",0
 C7Key db "C7",0
 C8Key db "C8",0
 C9Key db "C9",0
 C10Key db "C10",0
 ZeroString db 0
 ASMColorArray dd 0FF0000h,0805F50h,0FFh,666F00h,44F0h,5F8754h,4 dup(0FF0000h)
 CommentColor dd 808000h
 
 .data?
 hInstance dd ?
 hRichEdit dd ?
 hwndRichEdit dd ?
 FileName db 256 dup(?)
 AlternateFileName db 256 dup(?)
 CustomColors dd 16 dup(?)
 FindBuffer db 256 dup(?)
 ReplaceBuffer db 256 dup(?)
 uFlags dd ?
 findtext FINDTEXTEX <>
 ASMSyntaxArray dd 256 dup(?)
 hSearch dd ?		; 搜索/替换对话框的句柄
 hAccel dd ?
 hMainHeap dd ?		; 堆的句柄
 OldWndProc dd ?
 RichEditVersion dd ?
 
 .code
 start:
 	mov byte ptr [FindBuffer],0
 	mov byte ptr [ReplaceBuffer],0
 	invoke GetModuleHandle, NULL
 	mov  hInstance,eax
 	invoke LoadLibrary,addr RichEditDLL
 	.if eax!=0
 		mov hRichEdit,eax
 		invoke GetProcessHeap
 		mov hMainHeap,eax
 		call FillHiliteInfo
 		invoke WinMain, hInstance,0,0, SW_SHOWDEFAULT
 		invoke FreeLibrary,hRichEdit
 	.else
 		invoke MessageBox,0,addr NoRichEdit,addr AppName,MB_OK or MB_ICONERROR
 	.endif
 	invoke ExitProcess,eax
 	
 WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,CmdShow:DWORD
 	LOCAL wc:WNDCLASSEX
 	LOCAL msg:MSG
 	LOCAL hwnd:DWORD
 	mov  wc.cbSize,SIZEOF WNDCLASSEX
 	mov  wc.style, CS_HREDRAW or CS_VREDRAW
 	mov  wc.lpfnWndProc, OFFSET WndProc
 	mov  wc.cbClsExtra,NULL
 	mov  wc.cbWndExtra,NULL
 	push hInst
 	pop  wc.hInstance
 	mov  wc.hbrBackground,COLOR_WINDOW+1
 	mov  wc.lpszMenuName,IDR_MAINMENU
 	mov  wc.lpszClassName,OFFSET ClassName
 	invoke LoadIcon,NULL,IDI_APPLICATION
 	mov  wc.hIcon,eax
 	mov  wc.hIconSm,eax
 	invoke LoadCursor,NULL,IDC_ARROW
 	mov  wc.hCursor,eax
 	invoke RegisterClassEx, addr wc
 	INVOKE CreateWindowEx,NULL,ADDR ClassName,ADDR AppName,       WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,       CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL,       hInst,NULL
 	mov  hwnd,eax
 	invoke ShowWindow, hwnd,SW_SHOWNORMAL
 	invoke UpdateWindow, hwnd
 	invoke LoadAccelerators,hInstance,IDR_MAINACCEL
 	mov hAccel,eax
 	.while TRUE
 		invoke GetMessage, ADDR msg,0,0,0
 		.break .if (!eax)
 		invoke IsDialogMessage,hSearch,addr msg
 		.if eax==FALSE
 			invoke TranslateAccelerator,hwnd,hAccel,addr msg
 			.if eax==0
 				invoke TranslateMessage, ADDR msg
 				invoke DispatchMessage, ADDR msg
 			.endif
 		.endif
 	.endw
 	mov  eax,msg.wParam
 	ret
 WinMain endp
 
 StreamInProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesRead:DWORD
 	invoke ReadFile,hFile,pBuffer,NumBytes,pBytesRead,0
 	xor eax,1
 	ret
 StreamInProc endp
 
 StreamOutProc proc hFile:DWORD,pBuffer:DWORD, NumBytes:DWORD, pBytesWritten:DWORD
 	invoke WriteFile,hFile,pBuffer,NumBytes,pBytesWritten,0
 	xor eax,1
 	ret
 StreamOutProc endp
 
 CheckModifyState proc hWnd:DWORD
 	invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
 	.if eax!=0
 		invoke MessageBox,hWnd,addr WannaSave,addr AppName,MB_YESNOCANCEL
 		.if eax==IDYES
 			invoke SendMessage,hWnd,WM_COMMAND,IDM_SAVE,0
 		.elseif eax==IDCANCEL
 			mov eax,FALSE
 			ret
 		.endif
 	.endif
 	mov eax,TRUE
 	ret
 CheckModifyState endp
 
 SetColor proc
 	LOCAL cfm:CHARFORMAT	
 	invoke SendMessage,hwndRichEdit,EM_SETBKGNDCOLOR,0,BackgroundColor
 	invoke RtlZeroMemory,addr cfm,sizeof cfm
 	mov cfm.cbSize,sizeof cfm
 	mov cfm.dwMask,CFM_COLOR
 	push TextColor
 	pop cfm.crTextColor
 	invoke SendMessage,hwndRichEdit,EM_SETCHARFORMAT,SCF_ALL,addr cfm
 	ret
 SetColor endp
 
 OptionProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
 	LOCAL clr:CHOOSECOLOR
 	.if uMsg==WM_INITDIALOG
 	.elseif uMsg==WM_COMMAND
 		mov eax,wParam
 		shr eax,16
 		.if ax==BN_CLICKED
 			mov eax,wParam
 			.if ax==IDCANCEL
 				invoke SendMessage,hWnd,WM_CLOSE,0,0
 			.elseif ax==IDC_BACKCOLORBOX
 				invoke RtlZeroMemory,addr clr,sizeof clr
 				mov clr.lStructSize,sizeof clr
 				push hWnd
 				pop clr.hwndOwner
 				push hInstance
 				pop clr.hInstance
 				push BackgroundColor
 				pop clr.rgbResult
 				mov clr.lpCustColors,offset CustomColors
 				mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
 				invoke ChooseColor,addr clr
 				.if eax!=0
 					push clr.rgbResult
 					pop BackgroundColor
 					invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
 					invoke InvalidateRect,eax,0,TRUE
 				.endif
 			.elseif ax==IDC_TEXTCOLORBOX
 				invoke RtlZeroMemory,addr clr,sizeof clr
 				mov clr.lStructSize,sizeof clr
 				push hWnd
 				pop clr.hwndOwner
 				push hInstance
 				pop clr.hInstance
 				push TextColor
 				pop clr.rgbResult
 				mov clr.lpCustColors,offset CustomColors
 				mov clr.Flags,CC_ANYCOLOR or CC_RGBINIT
 				invoke ChooseColor,addr clr
 				.if eax!=0
 					push clr.rgbResult
 					pop TextColor
 					invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
 					invoke InvalidateRect,eax,0,TRUE
 				.endif
 			.elseif ax==IDOK
 				invoke SendMessage,hwndRichEdit,EM_GETMODIFY,0,0
 				push eax
 				invoke SetColor
 				pop eax
 				invoke SendMessage,hwndRichEdit,EM_SETMODIFY,eax,0
 				invoke EndDialog,hWnd,0
 			.endif
 		.endif
 	.elseif uMsg==WM_CTLCOLORSTATIC
 		invoke GetDlgItem,hWnd,IDC_BACKCOLORBOX
 		.if eax==lParam
 			invoke CreateSolidBrush,BackgroundColor			
 			ret
 		.else
 			invoke GetDlgItem,hWnd,IDC_TEXTCOLORBOX
 			.if eax==lParam
 				invoke CreateSolidBrush,TextColor
 				ret
 			.endif
 		.endif
 		mov eax,FALSE
 		ret
 	.elseif uMsg==WM_CLOSE
 		invoke EndDialog,hWnd,0
 	.else
 		mov eax,FALSE
 		ret
 	.endif
 	mov eax,TRUE
 	ret
 OptionProc endp
 
 SearchProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
 	.if uMsg==WM_INITDIALOG
 		push hWnd
 		pop hSearch
 		invoke CheckRadioButton,hWnd,IDC_DOWN,IDC_UP,IDC_DOWN
 		invoke SendDlgItemMessage,hWnd,IDC_FINDEDIT,WM_SETTEXT,0,addr FindBuffer
 	.elseif uMsg==WM_COMMAND
 		mov eax,wParam
 		shr eax,16
 		.if ax==BN_CLICKED
 			mov eax,wParam
 			.if ax==IDOK
 				mov uFlags,0
 				invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
 				invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
 				.if eax!=0
 					invoke IsDlgButtonChecked,hWnd,IDC_DOWN
 					.if eax==BST_CHECKED
 						or uFlags,FR_DOWN
 						mov eax,findtext.chrg.cpMin
 						.if eax!=findtext.chrg.cpMax
 							push findtext.chrg.cpMax
 							pop findtext.chrg.cpMin
 						.endif
 						mov findtext.chrg.cpMax,-1
 					.else
 						mov findtext.chrg.cpMax,0
 					.endif
 					invoke IsDlgButtonChecked,hWnd,IDC_MATCHCASE
 					.if eax==BST_CHECKED
 						or uFlags,FR_MATCHCASE
 					.endif
 					invoke IsDlgButtonChecked,hWnd,IDC_WHOLEWORD
 					.if eax==BST_CHECKED
 						or uFlags,FR_WHOLEWORD
 					.endif
 					mov findtext.lpstrText,offset FindBuffer
 					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,uFlags,addr findtext
 					.if eax!=-1
 						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
 					.endif
 				.endif
 			.elseif ax==IDCANCEL
 				invoke SendMessage,hWnd,WM_CLOSE,0,0
 			.else
 				mov eax,FALSE
 				ret
 			.endif
 		.endif
 	.elseif uMsg==WM_CLOSE
 		mov hSearch,0
 		invoke EndDialog,hWnd,0
 	.else
 		mov eax,FALSE
 		ret
 	.endif
 	mov eax,TRUE
 	ret
 SearchProc endp
 
 ReplaceProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
 	LOCAL settext:SETTEXTEX
 	.if uMsg==WM_INITDIALOG
 		push hWnd
 		pop hSearch
 		invoke SetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer
 		invoke SetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer
 	.elseif uMsg==WM_COMMAND
 		mov eax,wParam
 		shr eax,16
 		.if ax==BN_CLICKED
 			mov eax,wParam
 			.if ax==IDCANCEL
 				invoke SendMessage,hWnd,WM_CLOSE,0,0
 			.elseif ax==IDOK
 				invoke GetDlgItemText,hWnd,IDC_FINDEDIT,addr FindBuffer,sizeof FindBuffer
 				invoke GetDlgItemText,hWnd,IDC_REPLACEEDIT,addr ReplaceBuffer,sizeof ReplaceBuffer
 				mov findtext.chrg.cpMin,0
 				mov findtext.chrg.cpMax,-1
 				mov findtext.lpstrText,offset FindBuffer
 				mov settext.flags,ST_SELECTION
 				mov settext.codepage,CP_ACP
 				.while TRUE
 					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
 					.if eax==-1
 						.break
 					.else
 						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
 						invoke SendMessage,hwndRichEdit,EM_SETTEXTEX,addr settext,addr ReplaceBuffer
 					.endif
 				.endw
 			.endif
 		.endif
 	.elseif uMsg==WM_CLOSE
 		mov hSearch,0
 		invoke EndDialog,hWnd,0
 	.else
 		mov eax,FALSE
 		ret
 	.endif
 	mov eax,TRUE
 	ret
 ReplaceProc endp
 
 GoToProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
 	LOCAL LineNo:DWORD
 	LOCAL chrg:CHARRANGE
 	.if uMsg==WM_INITDIALOG
 		push hWnd
 		pop hSearch
 	.elseif uMsg==WM_COMMAND
 		mov eax,wParam
 		shr eax,16
 		.if ax==BN_CLICKED
 			mov eax,wParam
 			.if ax==IDCANCEL
 				invoke SendMessage,hWnd,WM_CLOSE,0,0
 			.elseif ax==IDOK
 				invoke GetDlgItemInt,hWnd,IDC_LINENO,NULL,FALSE
 				mov LineNo,eax
 				invoke SendMessage,hwndRichEdit,EM_GETLINECOUNT,0,0
 				.if eax>LineNo
 					invoke SendMessage,hwndRichEdit,EM_LINEINDEX,LineNo,0
 					invoke SendMessage,hwndRichEdit,EM_SETSEL,eax,eax
 					invoke SetFocus,hwndRichEdit
 				.endif
 			.endif
 		.endif
 	.elseif uMsg==WM_CLOSE
 		mov hSearch,0
 		invoke EndDialog,hWnd,0
 	.else
 		mov eax,FALSE
 		ret
 	.endif
 	mov eax,TRUE
 	ret
 GoToProc endp
 
 PrepareEditMenu proc hSubMenu:DWORD
 	LOCAL chrg:CHARRANGE
 	invoke SendMessage,hwndRichEdit,EM_CANPASTE,CF_TEXT,0
 	.if eax==0		; 剪贴板里没有正文
 		invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_GRAYED
 	.else
 		invoke EnableMenuItem,hSubMenu,IDM_PASTE,MF_ENABLED
 	.endif
 	invoke SendMessage,hwndRichEdit,EM_CANUNDO,0,0
 	.if eax==0
 		invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_GRAYED
 	.else
 		invoke EnableMenuItem,hSubMenu,IDM_UNDO,MF_ENABLED
 	.endif
 	invoke SendMessage,hwndRichEdit,EM_CANREDO,0,0
 	.if eax==0
 		invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_GRAYED
 	.else
 		invoke EnableMenuItem,hSubMenu,IDM_REDO,MF_ENABLED
 	.endif
 	invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr chrg
 	mov eax,chrg.cpMin
 	.if eax==chrg.cpMax		; 当前没有选定正文
 		invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_GRAYED
 		invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_GRAYED
 		invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_GRAYED
 	.else
 		invoke EnableMenuItem,hSubMenu,IDM_COPY,MF_ENABLED
 		invoke EnableMenuItem,hSubMenu,IDM_CUT,MF_ENABLED
 		invoke EnableMenuItem,hSubMenu,IDM_DELETE,MF_ENABLED
 	.endif
 	ret
 PrepareEditMenu endp
 
 ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD
 	LOCAL buffer[128]:BYTE
 	LOCAL InProgress:DWORD
 	mov InProgress,FALSE
 	lea esi,buffer
 	mov edi,pBuffer
 	invoke CharLower,edi
 	mov ecx,nSize
 SearchLoop:
 	or ecx,ecx
 	jz Finished
 	cmp byte ptr [edi]," "
 	je EndOfWord
 	cmp byte ptr [edi],9 	; tab
 	je EndOfWord
 	mov InProgress,TRUE
 	mov al,byte ptr [edi]
 	mov byte ptr [esi],al
 	inc esi
 SkipIt:
 	inc edi
 	dec ecx
 	jmp SearchLoop
 EndOfWord:
 	cmp InProgress,TRUE
 	je WordFound
 	jmp SkipIt
 WordFound:
 	mov byte ptr [esi],0
 	push ecx
 	invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
 	push esi
 	mov esi,eax
 	assume esi:ptr WORDINFO
 	invoke lstrlen,addr buffer
 	mov [esi].WordLen,eax
 	push ArrayOffset
 	pop [esi].pColor
 	inc eax
 	invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
 	mov [esi].pszWord,eax
 	mov edx,eax
 	invoke lstrcpy,edx,addr buffer
 	mov eax,pArray
 	movzx edx,byte ptr [buffer]
 	shl edx,2		; 乘以 4
 	add eax,edx
 	.if dword ptr [eax]==0
 		mov dword ptr [eax],esi
 	.else
 		push dword ptr [eax]
 		pop [esi].NextLink
 		mov dword ptr [eax],esi
 	.endif
 	pop esi
 	pop ecx
 	lea esi,buffer
 	mov InProgress,FALSE
 	jmp SkipIt
 Finished:
 	.if InProgress==TRUE
 		invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
 		push esi
 		mov esi,eax
 		assume esi:ptr WORDINFO
 		invoke lstrlen,addr buffer
 		mov [esi].WordLen,eax
 		push ArrayOffset
 		pop [esi].pColor
 		inc eax
 		invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
 		mov [esi].pszWord,eax
 		mov edx,eax
 		invoke lstrcpy,edx,addr buffer
 		mov eax,pArray
 		movzx edx,byte ptr [buffer]
 		shl edx,2		; 乘以 4
 		add eax,edx
 		.if dword ptr [eax]==0
 			mov dword ptr [eax],esi
 		.else
 			push dword ptr [eax]
 			pop [esi].NextLink
 			mov dword ptr [eax],esi
 		.endif
 		pop esi
 	.endif
 	ret
 ParseBuffer endp
 
 FillHiliteInfo proc uses edi
 	LOCAL buffer[1024]:BYTE
 	LOCAL pTemp:DWORD
 	LOCAL BlockSize:DWORD
 	invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray
 	invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer
 	invoke lstrlen,addr buffer
 	mov ecx,eax
 	dec ecx
 	lea edi,buffer
 	add edi,ecx
 	std
 	mov al,"\"
 	repne scasb
 	cld
 	inc edi
 	mov byte ptr [edi],0
 	invoke lstrcat,addr buffer,addr WordFileName
 	invoke GetFileAttributes,addr buffer
 	.if eax!=-1
 		mov BlockSize,1024*10
 		invoke HeapAlloc,hMainHeap,0,BlockSize
 		mov pTemp,eax
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C2Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,4
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C3Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	;缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,8
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C4Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,12
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C5Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,16
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C6Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,20
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C7Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,24
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C8Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,28
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C9Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,32
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 @@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C10Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0
 			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif
 			mov edx,offset ASMColorArray
 			add edx,36
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
 		.endif
 		invoke HeapFree,hMainHeap,0,pTemp
 	.endif
 	ret
 FillHiliteInfo endp
 
 NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
 	LOCAL hdc:DWORD
 	LOCAL hOldFont:DWORD
 	LOCAL FirstChar:DWORD
 	LOCAL rect:RECT
 	LOCAL txtrange:TEXTRANGE
 	LOCAL buffer[1024*10]:BYTE
 	LOCAL hRgn:DWORD
 	LOCAL hOldRgn:DWORD
 	LOCAL RealRect:RECT
 	LOCAL pString:DWORD
 	LOCAL BufferSize:DWORD
 	LOCAL pt:POINT
 	.if uMsg==WM_PAINT
 		push edi
 		push esi
 		invoke HideCaret,hWnd
 		invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
 		push eax
 		mov edi,offset ASMSyntaxArray
 		invoke GetDC,hWnd
 		mov hdc,eax
 		invoke SetBkMode,hdc,TRANSPARENT
 		invoke SendMessage,hWnd,EM_GETRECT,0,addr rect
 		invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect
 		invoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0
 		invoke SendMessage,hWnd,EM_LINEINDEX,eax,0
 		mov txtrange.chrg.cpMin,eax
 		mov FirstChar,eax
 		invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right
 		mov txtrange.chrg.cpMax,eax
 		push rect.left
 		pop RealRect.left
 		push rect.top
 		pop RealRect.top
 		push rect.right
 		pop RealRect.right
 		push rect.bottom
 		pop RealRect.bottom
 		invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom
 		mov hRgn,eax
 		invoke SelectObject,hdc,hRgn
 		mov hOldRgn,eax
 		invoke SetTextColor,hdc,CommentColor
 		lea eax,buffer
 		mov txtrange.lpstrText,eax
 		invoke SendMessage,hWnd,EM_GETTEXTRANGE,0,addr txtrange
 		.if eax>0	
 			mov esi,eax		; esi == 正文大小		
 			mov BufferSize,eax
 			push edi
 			push ebx
 			lea edi,buffer
 			mov edx,edi		; 用做参照点
 			mov ecx,esi
 			mov al,";"
 ScanMore:
 			repne scasb
 			je NextSkip
 			jmp NoMoreHit
 NextSkip:
 			dec edi
 			inc ecx
 			mov pString,edi
 			mov ebx,edi
 			sub ebx,edx
 			add ebx,FirstChar
 			mov txtrange.chrg.cpMin,ebx
 			push eax
 			mov al,0Dh
 			repne scasb
 			pop eax
 HiliteTheComment:
 			.if ecx>0
 				mov byte ptr [edi-1],0
 			.endif
 			mov ebx,edi
 			sub ebx,edx
 			add ebx,FirstChar
 			mov txtrange.chrg.cpMax,ebx
 			pushad
 			mov edi,pString
 			mov esi,txtrange.chrg.cpMax
 			sub esi,txtrange.chrg.cpMin		; esi 是缓冲区长度
 			mov eax,esi
 			push edi
 			.while eax>0
 				.if byte ptr [edi]==9
 					mov byte ptr [edi],0
 				.endif
 				inc edi
 				dec eax
 			.endw
 			pop edi
 			.while esi>0
 				.if byte ptr [edi]!=0
 					invoke lstrlen,edi
 					push eax
 					mov ecx,edi
 					lea edx,buffer
 					sub ecx,edx
 					add ecx,FirstChar
 					.if RichEditVersion==3
 						invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
 					.else
 						invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
 						mov ecx,eax
 						and ecx,0FFFFh
 						mov rect.left,ecx
 						shr eax,16
 						mov rect.top,eax
 					.endif
 					invoke DrawText,hdc,edi,-1,addr rect,0
 					pop eax
 					add edi,eax
 					sub esi,eax
 				.else
 					inc edi
 					dec esi
 				.endif			
 			.endw
 			mov ecx,txtrange.chrg.cpMax
 			sub ecx,txtrange.chrg.cpMin
 			invoke RtlZeroMemory,pString,ecx
 			popad
 			.if ecx>0
 				jmp ScanMore
 			.endif
 NoMoreHit:
 			pop ebx
 			pop edi
 			mov ecx,BufferSize
 			lea esi,buffer
 			.while ecx>0
 				mov al,byte ptr [esi]
 				.if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9
 					mov byte ptr [esi],0
 				.endif
 				dec ecx
 				inc esi
 			.endw
 			lea esi,buffer
 			mov ecx,BufferSize
 			.while ecx>0
 				mov al,byte ptr [esi]
 				.if al!=0
 					push ecx
 					invoke lstrlen,esi
 					push eax
 					mov edx,eax		; edx 为字符串长度
 					movzx eax,byte ptr [esi]
 					.if al>="A" && al<="Z"
 						sub al,"A"
 						add al,"a"
 					.endif
 					shl eax,2
 					add eax,edi		; edi 为 WORDINFO 指针数组的指针
 					.if dword ptr [eax]!=0
 						mov eax,dword ptr [eax]
 						assume eax:ptr WORDINFO
 						.while eax!=0
 							.if edx==[eax].WordLen
 								pushad
 								invoke lstrcmpi,[eax].pszWord,esi
 								.if eax==0								
 									popad
 									mov ecx,esi
 									lea edx,buffer
 									sub ecx,edx
 									add ecx,FirstChar
 									pushad
 									.if RichEditVersion==3
 										invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
 									.else
 										invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
 										mov ecx,eax
 										and ecx,0FFFFh
 										mov rect.left,ecx
 										shr eax,16
 										mov rect.top,eax
 									.endif
 									popad
 									mov edx,[eax].pColor
 									invoke SetTextColor,hdc,dword ptr [edx]								
 									invoke DrawText,hdc,esi,-1,addr rect,0
 									.break
 								.endif
 								popad
 							.endif
 							push [eax].NextLink
 							pop eax
 						.endw
 					.endif
 					pop eax
 					pop ecx
 					add esi,eax
 					sub ecx,eax
 				.else
 					inc esi
 					dec ecx
 				.endif
 			.endw
 		.endif
 		invoke SelectObject,hdc,hOldRgn
 		invoke DeleteObject,hRgn
 		invoke SelectObject,hdc,hOldFont
 		invoke ReleaseDC,hWnd,hdc
 		invoke ShowCaret,hWnd
 		pop eax
 		pop esi
 		pop edi
 		ret
 	.elseif uMsg==WM_CLOSE
 		invoke SetWindowLong,hWnd,GWL_WNDPROC,OldWndProc		
 	.else
 		invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
 		ret
 	.endif
 NewRichEditProc endp
 
 WndProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
 	LOCAL ofn:OPENFILENAME
 	LOCAL buffer[256]:BYTE
 	LOCAL editstream:EDITSTREAM
 	LOCAL hFile:DWORD
 	LOCAL hPopup:DWORD
 	LOCAL pt:POINT
 	LOCAL chrg:CHARRANGE
 	.if uMsg==WM_CREATE
 		invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr RichEditClass,0,WS_CHILD or WS_VISIBLE or ES_MULTILINE or WS_VSCROLL or WS_HSCROLL or ES_NOHIDESEL, 				CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,hWnd,RichEditID,hInstance,0
 		mov hwndRichEdit,eax
 		invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK
 		invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1
 		.if eax==0		; 说明该消息没被处理
 			mov RichEditVersion,2
 		.else
 			mov RichEditVersion,3
 			invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT
 		.endif
 		invoke SetWindowLong,hwndRichEdit,GWL_WNDPROC, addr NewRichEditProc
 		mov OldWndProc,eax
 		invoke SendMessage,hwndRichEdit,EM_LIMITTEXT,-1,0
 		invoke SetColor
 		invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
 		invoke SendMessage,hwndRichEdit,EM_SETEVENTMASK,0,ENM_MOUSEEVENTS
 		invoke SendMessage,hwndRichEdit,EM_EMPTYUNDOBUFFER,0,0
 	.elseif uMsg==WM_NOTIFY
 		push esi
 		mov esi,lParam
 		assume esi:ptr NMHDR
 		.if [esi].code==EN_MSGFILTER
 			assume esi:ptr MSGFILTER
 			.if [esi].msg==WM_RBUTTONDOWN
 				invoke GetMenu,hWnd
 				invoke GetSubMenu,eax,1
 				mov hPopup,eax
 				invoke PrepareEditMenu,hPopup
 				mov edx,[esi].lParam
 				mov ecx,edx
 				and edx,0FFFFh
 				shr ecx,16
 				mov pt.x,edx
 				mov pt.y,ecx
 				invoke ClientToScreen,hWnd,addr pt
 				invoke TrackPopupMenu,hPopup,TPM_LEFTALIGN or TPM_BOTTOMALIGN,pt.x,pt.y,NULL,hWnd,NULL
 			.endif
 		.endif
 		pop esi
 	.elseif uMsg==WM_INITMENUPOPUP
 		mov eax,lParam
 		.if ax==0		; file 菜单			
 			.if FileOpened==TRUE	; 已经打开了一个文件
 				invoke EnableMenuItem,wParam,IDM_OPEN,MF_GRAYED
 				invoke EnableMenuItem,wParam,IDM_CLOSE,MF_ENABLED
 				invoke EnableMenuItem,wParam,IDM_SAVE,MF_ENABLED
 				invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_ENABLED
 			.else
 				invoke EnableMenuItem,wParam,IDM_OPEN,MF_ENABLED
 				invoke EnableMenuItem,wParam,IDM_CLOSE,MF_GRAYED
 				invoke EnableMenuItem,wParam,IDM_SAVE,MF_GRAYED
 				invoke EnableMenuItem,wParam,IDM_SAVEAS,MF_GRAYED
 			.endif
 		.elseif ax==1	; edit 菜单
 			invoke PrepareEditMenu,wParam
 		.elseif ax==2		; search 菜单条
 			.if FileOpened==TRUE
 				invoke EnableMenuItem,wParam,IDM_FIND,MF_ENABLED
 				invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_ENABLED
 				invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_ENABLED
 				invoke EnableMenuItem,wParam,IDM_REPLACE,MF_ENABLED
 				invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_ENABLED
 			.else
 				invoke EnableMenuItem,wParam,IDM_FIND,MF_GRAYED
 				invoke EnableMenuItem,wParam,IDM_FINDNEXT,MF_GRAYED
 				invoke EnableMenuItem,wParam,IDM_FINDPREV,MF_GRAYED
 				invoke EnableMenuItem,wParam,IDM_REPLACE,MF_GRAYED
 				invoke EnableMenuItem,wParam,IDM_GOTOLINE,MF_GRAYED
 			.endif
 		.endif
 	.elseif uMsg==WM_COMMAND
 		.if lParam==0		; 菜单命令
 			mov eax,wParam
 			.if ax==IDM_OPEN
 				invoke RtlZeroMemory,addr ofn,sizeof ofn
 				mov ofn.lStructSize,sizeof ofn
 				push hWnd
 				pop ofn.hwndOwner
 				push hInstance
 				pop ofn.hInstance
 				mov ofn.lpstrFilter,offset ASMFilterString
 				mov ofn.lpstrFile,offset FileName
 				mov byte ptr [FileName],0
 				mov ofn.nMaxFile,sizeof FileName
 				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
 				invoke GetOpenFileName,addr ofn
 				.if eax!=0
 					invoke CreateFile,addr FileName,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
 					.if eax!=INVALID_HANDLE_VALUE
 						mov hFile,eax
 						mov editstream.dwCookie,eax
 						mov editstream.pfnCallback,offset StreamInProc
 						invoke SendMessage,hwndRichEdit,EM_STREAMIN,SF_TEXT,addr editstream
 						invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
 						invoke CloseHandle,hFile
 						mov FileOpened,TRUE
 					.else
 						invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
 					.endif
 				.endif
 			.elseif ax==IDM_CLOSE
 				invoke CheckModifyState,hWnd
 				.if eax==TRUE
 					invoke SetWindowText,hwndRichEdit,0
 					mov FileOpened,FALSE
 				.endif
 			.elseif ax==IDM_SAVE
 				invoke CreateFile,addr FileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
 				.if eax!=INVALID_HANDLE_VALUE
 @@:				
 					mov hFile,eax
 					mov editstream.dwCookie,eax
 					mov editstream.pfnCallback,offset StreamOutProc
 					invoke SendMessage,hwndRichEdit,EM_STREAMOUT,SF_TEXT,addr editstream
 					invoke SendMessage,hwndRichEdit,EM_SETMODIFY,FALSE,0
 					invoke CloseHandle,hFile
 				.else
 					invoke MessageBox,hWnd,addr OpenFileFail,addr AppName,MB_OK or MB_ICONERROR
 				.endif
 			.elseif ax==IDM_COPY
 				invoke SendMessage,hwndRichEdit,WM_COPY,0,0
 			.elseif ax==IDM_CUT
 				invoke SendMessage,hwndRichEdit,WM_CUT,0,0
 			.elseif ax==IDM_PASTE
 				invoke SendMessage,hwndRichEdit,WM_PASTE,0,0
 			.elseif ax==IDM_DELETE
 				invoke SendMessage,hwndRichEdit,EM_REPLACESEL,TRUE,0
 			.elseif ax==IDM_SELECTALL
 				mov chrg.cpMin,0
 				mov chrg.cpMax,-1
 				invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr chrg
 			.elseif ax==IDM_UNDO
 				invoke SendMessage,hwndRichEdit,EM_UNDO,0,0
 			.elseif ax==IDM_REDO
 				invoke SendMessage,hwndRichEdit,EM_REDO,0,0
 			.elseif ax==IDM_OPTION
 				invoke DialogBoxParam,hInstance,IDD_OPTIONDLG,hWnd,addr OptionProc,0
 			.elseif ax==IDM_SAVEAS
 				invoke RtlZeroMemory,addr ofn,sizeof ofn
 				mov ofn.lStructSize,sizeof ofn
 				push hWnd
 				pop ofn.hwndOwner
 				push hInstance
 				pop ofn.hInstance
 				mov ofn.lpstrFilter,offset ASMFilterString
 				mov ofn.lpstrFile,offset AlternateFileName
 				mov byte ptr [AlternateFileName],0
 				mov ofn.nMaxFile,sizeof AlternateFileName
 				mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
 				invoke GetSaveFileName,addr ofn
 				.if eax!=0
 					invoke CreateFile,addr AlternateFileName,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
 					.if eax!=INVALID_HANDLE_VALUE
 						jmp @B
 					.endif
 				.endif
 			.elseif ax==IDM_FIND
 				.if hSearch==0
 					invoke CreateDialogParam,hInstance,IDD_FINDDLG,hWnd,addr SearchProc,0
 				.endif
 			.elseif ax==IDM_REPLACE
 				.if hSearch==0
 					invoke CreateDialogParam,hInstance,IDD_REPLACEDLG,hWnd,addr ReplaceProc,0
 				.endif
 			.elseif ax==IDM_GOTOLINE
 				.if hSearch==0
 					invoke CreateDialogParam,hInstance,IDD_GOTODLG,hWnd,addr GoToProc,0
 				.endif
 			.elseif ax==IDM_FINDNEXT
 				invoke lstrlen,addr FindBuffer
 				.if eax!=0
 					invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
 					mov eax,findtext.chrg.cpMin
 					.if eax!=findtext.chrg.cpMax
 						push findtext.chrg.cpMax
 						pop findtext.chrg.cpMin
 					.endif
 					mov findtext.chrg.cpMax,-1
 					mov findtext.lpstrText,offset FindBuffer
 					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,FR_DOWN,addr findtext
 					.if eax!=-1
 						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
 					.endif
 				.endif
 			.elseif ax==IDM_FINDPREV
 				invoke lstrlen,addr FindBuffer
 				.if eax!=0
 					invoke SendMessage,hwndRichEdit,EM_EXGETSEL,0,addr findtext.chrg
 					mov findtext.chrg.cpMax,0
 					mov findtext.lpstrText,offset FindBuffer
 					invoke SendMessage,hwndRichEdit,EM_FINDTEXTEX,0,addr findtext
 					.if eax!=-1
 						invoke SendMessage,hwndRichEdit,EM_EXSETSEL,0,addr findtext.chrgText
 					.endif
 				.endif
 			.elseif ax==IDM_EXIT
 				invoke SendMessage,hWnd,WM_CLOSE,0,0
 			.endif
 		.endif
 	.elseif uMsg==WM_CLOSE
 		invoke CheckModifyState,hWnd
 		.if eax==TRUE
 			invoke DestroyWindow,hWnd
 		.endif
 	.elseif uMsg==WM_SIZE
 		mov eax,lParam
 		mov edx,eax
 		and eax,0FFFFh
 		shr edx,16
 		invoke MoveWindow,hwndRichEdit,0,0,eax,edx,TRUE		
 	.elseif uMsg==WM_DESTROY
 		invoke PostQuitMessage,NULL
 	.else
 		invoke DefWindowProc,hWnd,uMsg,wParam,lParam		
 		ret
 	.endif
 	xor eax,eax
 	ret
 WndProc endp
 end start

分析:


第一步,在调用 WinMain 之前先调用 FillHiliteInfo。这个函数读出 wordfile.txt 的内容然后解析该内容。

FillHiliteInfo proc uses edi
 	LOCAL buffer[1024]:BYTE
 	LOCAL pTemp:DWORD
 	LOCAL BlockSize:DWORD
 	invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray

初始化 ASMSyntaxArray 为 0 。

	invoke GetModuleFileName,hInstance,addr buffer,sizeof buffer
 	invoke lstrlen,addr buffer
 	mov ecx,eax
 	dec ecx
 	lea edi,buffer
 	add edi,ecx
 	std
 	mov al,"\"
 	repne scasb
 	cld
 	inc edi
 	mov byte ptr [edi],0
 	invoke lstrcat,addr buffer,addr WordFileName

构建 of wordfile.txt 的完全路径: 我假定该文件总是跟程序文件在同一个文件夹。

	invoke GetFileAttributes,addr buffer
 	.if eax!=-1

我使用这个方法来快速检验一个文件是否存在。

		mov BlockSize,1024*10
 		invoke HeapAlloc,hMainHeap,0,BlockSize
 		mov pTemp,eax

分配内存块来保存高亮词。缺省是10K。是从缺省堆中分配的。

@@:		
 		invoke GetPrivateProfileString,addr ASMSection,addr C1Key,addr ZeroString,pTemp,BlockSize,addr buffer
 		.if eax!=0

我使用 GetPrivateProfileString 来检索 wordfile.txt 中每一个关键字的内容。关键字从 C1 到 C10。

			inc eax
 			.if eax==BlockSize	; 缓冲区太小
 				add BlockSize,1024*10
 				invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize
 				mov pTemp,eax
 				jmp @B
 			.endif

检查内存块是否足够大。如果不够的话,我们就增大10K,直到足够大为止。

			mov edx,offset ASMColorArray
 			invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray

传递需要高亮显示的词的缓冲区, 内存块句柄,从WordFile.txt中读出的数据的大小,用来高亮显示词的颜色数组的地址,和ASMSyntaxArray的地址。


现在让我们来看看 ParseBuffer 做了什么。基本上,这个函数接收包含需要高亮显示词的缓冲区,将它们解析为单个的词并将它们每个都保存在一个 WORDINFO 结构数组中,这样可以从 ASMSyntaxArray 中快速访问它们。 

ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD
 	LOCAL buffer[128]:BYTE
 	LOCAL InProgress:DWORD
 	mov InProgress,FALSE

InProgress 是一个标志,我用来指示扫描处理是否已经开始。如果它的值是FALSE,表示我们还没遇到一个非空格字符。

	lea esi,buffer	
 	mov edi,pBuffer
 	invoke CharLower,edi

ESI 指向一个局部缓冲区,用来保存从词表中分离出来的单个词。EDI 指向词表字符串。为了方便以后的搜索,我们将所有的词都转换为小写形式。

	mov ecx,nSize			
 SearchLoop:
 	or ecx,ecx
 	jz Finished
 	cmp byte ptr [edi]," "
 	je EndOfWord
 	cmp byte ptr [edi],9 	; tab
 	je EndOfWord

扫描缓冲区里的整个词表,搜索空格(White Space)字符。如果找到的话,我们必须判断它表示一个词的结束还是开始。

	mov InProgress,TRUE
 	mov al,byte ptr [edi]
 	mov byte ptr [esi],al
 	inc esi
 SkipIt:
 	inc edi
 	dec ecx
 	jmp SearchLoop

如果该字节经过详细审查证实不是空格,我们就将它复制到一个缓冲区来组成一个词,并继续扫描。

EndOfWord:
 	cmp InProgress,TRUE
 	je WordFound
 	jmp SkipIt

如果找到了一个空格字符,我们就检查 InProgress 的值。如果值为 TRUE ,我们可以假定空格标识一个词的结束,我们可以继续将解析出来的在局部缓冲区(由ESI指向)中的词保存到一个 WORDINFO 结构中。如果值为 FALSE ,我们继续扫描直到发现一个非空白(non-white space)的空格字符。

WordFound:
 	mov byte ptr [esi],0
 	push ecx
 	invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO

到达词的结尾后,我们就在词缓冲区尾加一个0,标识该词是一个 ASCIIZ 字符串。然后我们从堆中为该词分配一块 WORDINFO 内存。

	push esi
 	mov esi,eax
 	assume esi:ptr WORDINFO
 	invoke lstrlen,addr buffer
 	mov [esi].WordLen,eax

我们取得局部变量中该词的长度,保存在 WORDINFO 结构的 WordLen 成员中,以后用来快速比较。

	push ArrayOffset
 	pop [esi].pColor

将包含用来高亮显示该词的颜色的双字的地址保存到 pColor 成员中。

	inc eax
 	invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax
 	mov [esi].pszWord,eax
 	mov edx,eax
 	invoke lstrcpy,edx,addr buffer

从堆中分配一块内存用来保存词本身。现在 WORDINFO 结构已经都填充好了,可以插入到适当的链表中去。

	mov eax,pArray
 	movzx edx,byte ptr [buffer]
 	shl edx,2		; 乘以 4
 	add eax,edx

pArray包含的是 ASMSyntaxArray 的地址。我们要移动到跟词中第一个字符具有相同索引的DWORD上。因此我们将词中的第一个字符放入EDX,然后将EDX乘以4(因为 ASMSyntaxArray 中的每个元素都是 4 字节大小),再将偏移量加到 ASMSyntaxArray 的地址上,我们就在EAX中得到相应DWORD的地址。

	.if dword ptr [eax]==0
 		mov dword ptr [eax],esi
 	.else
 		push dword ptr [eax]
 		pop [esi].NextLink
 		mov dword ptr [eax],esi
 	.endif

检查那个DWORD的值,如果为 0 , 意味着当前链表中没有词是以这个字符开头的。因此我们将当前 WORDINFO 结构的地址保存到那个DWORD中。


如果值为非0, 意味着数组中至少有一个词是以这个字符开头的,因此我们将这个WORDINFO 结构插入到该链表头并更新其 NextLink 成员来指向下一个WORDINFO 结构。

	pop esi
 	pop ecx
 	lea esi,buffer
 	mov InProgress,FALSE
 	jmp SkipIt

完成这个操作后,我们就开始下一轮扫描循环直到到达缓冲区的尾。

		invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK
 		invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1
 		.if eax==0		; 说明该消息没被处理
 			mov RichEditVersion,2
 		.else
 			mov RichEditVersion,3
 			invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT
 		.endif

创建RichEdit控件之后,我们需要确定其版本。这一步很必要,因为EM_POSFROMCHAR 在 RichEdit 2.0 和 3.0 中是不同的,而 EM_POSFROMCHAR 对于我们的语法高亮显示程序是至关重要的。我从来没见到有文档介绍到检查RichEdit控件版本的方法,因此我必须使用一个解决办法(workaround)。在这种情况下,我设置一个选项指定 3.0 版本并立即检索它的值。如果我能检索到该值的话我就假设该控件是3.0版本的。


如果你使用 RichEdit 3.0 ,你会注意到更改一个大文件的正文颜色会花费很长的时间。这个问题好象是3.0版本特有的。我找到一个解决办法:通过发送EM_SETEDITSTYLE 消息,使控件模拟系统Edit控件行为。


取得版本信息后,我们继续下一步,子类化RichEdit控件。现在我们检查RichEdit的新的窗口过程。

NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
 	........
 	.......
 	.if uMsg==WM_PAINT
 		push edi
 		push esi
 		invoke HideCaret,hWnd
 		invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam
 		push eax

我们处理 WM_PAINT 消息。首先,我们隐藏光标以便避免一些高亮后的难看的gfx (ugly gfx)。我们发送WM_PAINT消息给 RichEdit的原来的处理过程,让它更新窗口。CallWindowProc 返回后,正文已经被使用原来的颜色/背景色更新过了。现在是我们做语法高亮显示的机会了!

		mov edi,offset ASMSyntaxArray
 		invoke GetDC,hWnd
 		mov hdc,eax
 		invoke SetBkMode,hdc,TRANSPARENT

保存 ASMSyntaxArray 的地址到 EDI。然后我们取得DC的句柄,设置正文背景模式为 Transparent ,以便我们写入的正文使用缺省的背景色。

		invoke SendMessage,hWnd,EM_GETRECT,0,addr rect
 		invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect
 		invoke SendMessage,hWnd,EM_LINEFROMCHAR,eax,0
 		invoke SendMessage,hWnd,EM_LINEINDEX,eax,0

我们要取得可见的正文,因此我们首先必须发送 EM_GETRECT 消息给RichEdit控件来取得格式化矩形(formatting rectangle)。现在我们有了范围矩形,我们通过EM_CHARFROMPOS消息,取得矩形左上角的最接近的字符索引 。一旦我们有了字符索引(控件中第一个可见字符), 我们就可以从该位置开始做语法高亮显示了。但是效果可能没有从该字符所在行的第一个字符开始高亮显示的效果好。这就是为什么我需要通过 EM_LINEFROMCHAR 消息去取得第一个可见字符所在行的行号的原因。为了取得该行的第一个字符,我需要发送 EM_LINEINDEX 消息。

		mov txtrange.chrg.cpMin,eax
 		mov FirstChar,eax
 		invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right
 		mov txtrange.chrg.cpMax,eax

取得第一个字符的索引后,将之保存到变量 FirstChar 中以便以后引用。跟着我们来取得最后一个可见字符的索引,通过发送 EM_CHARFROMPOS 消息,在 lParam 中传递矩形框的右下角的位置。

		push rect.left
 		pop RealRect.left
 		push rect.top
 		pop RealRect.top
 		push rect.right
 		pop RealRect.right
 		push rect.bottom
 		pop RealRect.bottom
 		invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom
 		mov hRgn,eax
 		invoke SelectObject,hdc,hRgn
 		mov hOldRgn,eax

在进行语法高亮显示时,我注意到这个方法的一个不好看的边缘效果:如果RichEdit控件有页边空白的话(你可以通过发送 EM_SETMARGINS 消息给RichEdit控件来指定页边空白), DrawText 写出了页边空白。 因而我需要创建一个裁剪区域,大小为矩形框的大小,这可以通过调用 CreateRectRgn 来实现。这样 GDI 函数的输出就会被裁剪在“可写”区域内。


跟着,我们需要先高亮显示注释,并将它们排除在目标之外。我使用的方法是搜索 ";" 并用注释颜色来高亮显示那些正文,直到遇到回车符。我在这里不分析这个过程了:它相当的长和复杂。现在可以说,当所有的注释高亮显示后,我们就在缓冲区中用 0 来替换掉它们,以便注释中的词在以后不会再被重复高亮处理。

		mov ecx,BufferSize
 		lea esi,buffer
 		.while ecx>0
 			mov al,byte ptr [esi]
 			.if al==" " || al==0Dh || al=="/" || al=="," || al=="|" || al=="+" || al=="-" || al=="*" || al=="&" || al=="<" || al==">" || al=="=" || al=="(" || al==")" || al=="{" || al=="}" || al=="[" || al=="]" || al=="^" || al==":" || al==9
 				mov byte ptr [esi],0
 			.endif
 			dec ecx
 			inc esi
 		.endw

将注释内容排除掉后,我们通过使用 0 替换掉分隔符,将缓冲区中的词表分开为一个个单个的词。用这个方法,我们在处理缓冲区中的词时就再不需要考虑分隔符了:只有一个分隔符,就是 NULL 。

		lea esi,buffer
 		mov ecx,BufferSize
 		.while ecx>0
 			mov al,byte ptr [esi]
 			.if al!=0

搜索缓冲区找到第一个非NULL字符,也就是某个词的第一个字符。

				push ecx
 				invoke lstrlen,esi
 				push eax
 				mov edx,eax

取得词的长度并保存到 EDX

				movzx eax,byte ptr [esi]
 				.if al>="A" && al<="Z"
 					sub al,"A"
 					add al,"a"
 				.endif

将字符转换为小写的 (如果是大写字符的话)

				shl eax,2
 				add eax,edi		; edi 为 WORDINFO 指针数组的指针
 				.if dword ptr [eax]!=0

之后,我们跳到 ASMSyntaxArray 中的相应的DWORD,并检查该DWORD的值是否为 0 ,是的话我们就跳到下一个词。

					mov eax,dword ptr [eax]
 					assume eax:ptr WORDINFO
 					.while eax!=0
 						.if edx==[eax].WordLen

如果DWORD的值不为 0 ,它就指向一个 WORDINFO 结构的链表。我们跟着遍历该链表,将局部缓冲区中词的长度跟 WORDINFO 结构中词的长度进行比较。这是在我们比较词之前进行的快速测试,可以节约一些时钟周期。

							pushad
 							invoke lstrcmpi,[eax].pszWord,esi
 							.if eax==0

如果两个词的长度相等,我们就继续使用 lstrcmpi 比较两个词。

								popad
 								mov ecx,esi
 								lea edx,buffer
 								sub ecx,edx
 								add ecx,FirstChar

我们用缓冲区中的匹配词的第一个字符的地址来构造字符索引。首先取得它跟缓冲区开始地址的相对偏移量,然后加上第一个可见字符的字符索引。

								pushad
 								.if RichEditVersion==3
 									invoke SendMessage,hWnd,EM_POSFROMCHAR,addr rect,ecx
 								.else
 									invoke SendMessage,hWnd,EM_POSFROMCHAR,ecx,0
 									mov ecx,eax
 									and ecx,0FFFFh
 									mov rect.left,ecx
 									shr eax,16
 									mov rect.top,eax
 								.endif
 								popad

知道需要高亮显示的词的第一个字符的字符索引后,我们跟着通过发送 EM_POSFROMCHAR 消息取得它的位置。然而这个消息在RichEdit 2.0 和 3.0 中的解释是不同的。对RichEdit 2.0 来说,wParam 包含字符索引,lParam 没有使用。它在 EAX 返回 坐标。对 RichEdit 3.0 来说,wParam 是一个指向 POINT 结构的指针,该调用将会将结果坐标填入这里,而 lParam 则包含字符索引。


正如你看到的,如果传递错误的参数给 EM_POSFROMCHAR ,它可以对你的系统造成重大破坏。这个就是为什么我必须区分 RichEdit 控件的版本的缘故。

								mov edx,[eax].pColor
 								invoke SetTextColor,hdc,dword ptr [edx]								
 								invoke DrawText,hdc,esi,-1,addr rect,0

一旦我们取得了开始坐标,我们就使用 WORDINFO 结构中指定的颜色来设置正文颜色。跟着使用新颜色值重写该词。


As the final words, 这个方法可以从几个方面提高。例如,我取得从第一个可见行到最后一个可见行的所有正文。如果那些行很长的话,处理那些不可见的词会降低性能。你可以通过一行一行地取得真正的可见行来进行优化。同样,搜索算法也可以使用更多的不同方法来提高速度。不要受我的错误影响(Don't take me wrong):这个例子用来进行语法高亮显示的方法是快,但是它可以更快!:)

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多