Let us take a look at what RichEdit control provides to help us in implementing syntax hilighting. I should state at this moment that the following method is not the "correct" path: I just want to show you the pitfall that many fall for. RichEdit control provides EM_SETCHARFORMAT message that you can use to change the color of the text. At first glance, this message seems to be the perfect solution (I know because I was one of the victim). However, a closer examination will show you several things that are undesirable:
The method I currently use is "syntax hilighting just-in-time". I'll hilight only the visible portion of text. Thus the speed of the hilighting will not be related to the size of the file at all. No matter how large the file, only a small portion of it is visible at one time.
How to do that? The answer is simple:
Now let's concentrate on the detail. The subclassing process is simple and doesn't require much attention. The really complicated part is when we have to find a fast way of searching for the words to be hilighted. This is further complicated by the need not to hilight any word within a comment block.
The method I use may not be the best but it works ok. I'm sure you can find a faster way. Anyway, here it is:
[WORDINFO: WordLen: D§ ? ; the length of the word: used as a quick comparison pszWord: D§ ? ; pointer to the word pColor: D§ ? ; point to the dword that contains the color used to hilite the word NextLink: D§ ?] ; point to the next WORDINFO structure
As you can see, I use the length of the word as the second quick comparison. If the first character of the word matches, we next compare its length to the available words. Each dword in ASMSyntaxArray contains a pointer to the head of the associated WORDINFO array. For example, the dword that represents the character "i" will contain the pointer to the linked list of the words that begin with "i". pColor member points to the dword that contains the color value used to hilight the word. pszWord points to the word to be hilighted, in lowercase.
____________________________________________________________________________________________
[WORDINFO.WordLen 0 ; the length of the word: used as a quick comparison
WORDINFO.pszWord 4 ; pointer to the word
WORDINFO.pColor 8 ; point to the dword that contains the color used to hilite the word
WORDINFO.NextLink 12 ; point to the next WORDINFO structure
WordInfoLen 16]
[IDD_OPTIONDLG 1000
IDC_BACKCOLORBOX 1001
IDC_TEXTCOLORBOX 1002
IDD_FINDDLG 2000
IDD_GOTODLG 3000
IDD_REPLACEDLG 4000
IDC_FINDEDIT 1000
IDC_MATCHCASE 1001
IDC_REPLACEEDIT 1001
IDC_WHOLEWORD 1002
IDC_DOWN 1003
IDC_UP 1004
IDC_LINENO 1005]
[RichEditID 300]
[ClassName: B§ 'IczEditClass',0
AppName : B§ 'IczEdit version 1.0',0
RichEditDLL: B§ 'riched20.dll',0
RichEditClass: B§ 'RichEdit20A',0
NoRichEdit: B§ 'Cannot find riched20.dll',0
ASMFilterString: B§ 'ASM Source code (*.asm)',0,'*.asm',0
B§ 'All Files (*.*)',0,'*.*',0,0
OpenFileFail: B§ 'Cannot open the file',0
WannaSave: B§ 'The data in the control is modified. Want to save it?',0]
[FileOpened: D§ &FALSE
BackgroundColor: D§ 0FFFFFF ; default to white
TextColor: D§ 0] ; default to black
[WordFileName: B§ '\wordfile.txt',0
ASMSection: 'ASSEMBLY',0
C1Key: 'C1',0
C2Key: 'C2',0
C3Key: 'C3',0
C4Key: 'C4',0
C5Key: 'C5',0
C6Key: 'C6',0
C7Key: 'C7',0
C8Key: 'C8',0
C9Key: 'C9',0
C10Key: 'C10',0
ZeroString: 0]
[ASMColorArray: D§ 0FF0000,0805F50,0FF,0666F00,044F0,05F8754,0FF0000,0FF0000,0FF0000,0FF0000
CommentColor: 0808000]
[hInstance: D§ ?
hRichEdit: D§ ?
hwndRichEdit: D§ ?]
[FileName: B§ ? #260]
[AlternateFileName: B§ ? #260]
[CustomColors: D§ ? #16]
[FindBuffer: B§ ? #260]
[ReplaceBuffer: B§ ? #260]
[uFlags: ?]
[FINDTEXT:
@chrg: @chrg@cpMin: D§ 0
@chrg@cpMax: D§ 0
@lpstrText: D§ 0
@chrgText: @chrgText@cpMin: D§ 0
@chrgText@cpMax: D§ 0]
[ASMSyntaxArray: D§ 0 #256 ASMSyntaxArrayLen: len]
[hMainHeap: ? ; heap handle
OldWndProc: ?
RichEditVersion: ?]
____________________________________________________________________________________________
____________________________________________________________________________________________
[WNDCLASSEX:
@cbSize: D§ len
@style: D§ &CS_HREDRAW__&CS_VREDRAW
@lpfnWndProc: D§ MainWindowProc
@cbClsExtra: D§ 0
@cbWndExtra: D§ 0
@hInstance: D§ 0
@hIcon: D§ 0
@hCursor: D§ 0
@hbrBackground: D§ &COLOR_WINDOW+1
@lpszMenuName: D§ M00_Menu
@lpszClassName: D§ ClassName
@hIconSm: D§ 0]
[MSG:
@hwnd: D§ 0
@message: D§ 0
@wParam: D§ 0
@lParam: D§ 0
@time: D§ 0
@MSG@pt: @MSG@pt@x: D§ 0
@MSG@pt@y: D§ 0]
[hwnd: D§ 0]
[M00_Menu 1000 M00_Open 1001 M00_Close 1002
M00_Save 1003 M00_save_As 1004 M00_Exit 1005
M00_Undo 1006 M00_Redo 1007 M00_Copy 1008
M00_Cut 1009 M00_Paste 1010 M00_Delete 1011
M00_select_All 1012 M00_Find 1013 M00_Find_Next 1014
M00_Find_Prev 1015 M00_Replace 1016 M00_Go_To_Line 1017
M00_Options 1018]
____________________________________________________________________________________________
____________________________________________________________________________________________
[&SCF_ALL 4]
[ACCELERATORS:
U§ &FVIRTKEY__&FNOINVERT &VK_F3 M00_Find_Next
&FVIRTKEY__&FCONTROL__&FNOINVERT &VK_F3 M00_Find_Prev
&FVIRTKEY__&FCONTROL__&FNOINVERT 'F' M00_Find
&FVIRTKEY__&FCONTROL__&FNOINVERT 'G' M00_Go_To_Line
&FVIRTKEY__&FCONTROL__&FNOINVERT+FLAGLAST 'R' M00_Replace]
[ACCELNUMBER 5 FLAGLAST 080]
[AccelHandle: 0 hSearch: 0]
____________________________________________________________________________________________
____________________________________________________________________________________________
Main:
call 'KERNEL32.GetModuleHandleA' &NULL
mov D§hInstance eax, D§WNDCLASSEX@hInstance eax
call 'KERNEL32.LoadLibraryA' RichEditDLL
.If eax <> 0
mov D§hRichEdit eax
call 'KERNEL32.GetProcessHeap' | mov D§hMainHeap eax
;===========================================================
; Load the words to be hilighted
;===========================================================
call FillHiliteInfo
.Else
call 'USER32.MessageBoxA' 0 NoRichEdit AppName &MB_OK__&MB_ICONERROR | jmp L9>>
.End_If
call 'USER32.LoadIconA' &NULL &IDI_APPLICATION
mov D§WNDCLASSEX@hIcon eax, D§WNDCLASSEX@hIconSm eax
call 'USER32.LoadCursorA' &NULL &IDC_ARROW
mov D§WNDCLASSEX@hCursor eax
call 'USER32.RegisterClassExA' WNDCLASSEX
call 'USER32.CreateWindowExA' &NULL ClassName AppName,
&WS_OVERLAPPEDWINDOW &CW_USEDEFAULT,
&CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &NULL &NULL,
D§hInstance &NULL
mov D§hwnd eax
call 'USER32.ShowWindow' D§hwnd &SW_SHOWNORMAL
call 'USER32.UpdateWindow' D§hwnd
call 'USER32.CreateAcceleratorTableA' ACCELERATORS ACCELNUMBER
mov D§AccelHandle eax
L1: call 'USER32.GetMessageA' MSG 0 0 0 | On eax = 0, jmp L8>
call 'USER32.IsDialogMessage' D§hSearch MSG
.If eax = 0
call 'USER32.TranslateAccelerator' D§hwnd D§AccelHandle MSG
If eax = 0
call 'USER32.TranslateMessage' MSG
call 'USER32.DispatchMessageA' MSG
End_If
.End_If
jmp L1<
L8: call 'KERNEL32.FreeLibrary' D§hRichEdit
call 'USER32.DestroyAcceleratorTable' D§AccelHandle
L9: call 'KERNEL32.ExitProcess' 0
____________________________________________________________________________________________
____________________________________________________________________________________________
Proc StreamInProc:
arguments @hFile @pBuffer @NumBytes @pBytesRead
call 'KERNEL32.ReadFile' D@hFile D@pBuffer D@NumBytes D@pBytesRead 0
xor eax 1
EndP
Proc StreamOutProc:
Arguments @hFile @pBuffer @NumBytes @pBytesWritten
call 'KERNEL32.WriteFile' D@hFile D@pBuffer D@NumBytes D@pBytesWritten 0
xor eax 1
EndP
Proc CheckModifyState:
Argument @hWnd
call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETMODIFY 0 0
.If eax <> 0
call 'USER32.MessageBoxA' D§hWnd WannaSave AppName &MB_YESNOCANCEL
If eax = &IDYES
call 'USER32.SendMessageA' D@hWnd &WM_COMMAND M00_SAVE 0
Else _If eax = &IDCANCEL
mov eax &FALSE | Exit
End_If
.Endif
mov eax &TRUE
EndP
[CHARFORMAT:
@cbSize: D§ 60
@dwMask: D§ &CFM_COLOR
@dwEffects: D§ 0 ; &CFE_AUTOCOLOR ; ?
@yHeight: D§ 0
@yOffset: D§ 0
@crTextColor: D§ 0
@bCharSet: B§ 0
@bPitchAndFamily: B§ 0]
[@szFaceName: B§ 0 #32] ; &LF_FACESIZE
[wPad2: W§ 0]
SetColor:
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETBKGNDCOLOR 0 D§BackgroundColor
move D§CHARFORMAT@crTextColor D§TextColor
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETCHARFORMAT &SCF_ALL CHARFORMAT
ret
[CHOOSECOLOR:
@lStructSize: D§ len
@hwndOwner: D§ 0
@hInstance: D§ 0
@rgbResult: D§ 0
@lpCustColors: D§ CustomColors
@Flags: D§ &CC_RGBINIT
@lCustData: D§ 0
@lpfnHook: D§ 0
@lpTemplateName: D§ 0]
Proc OptionProc:
Arguments @hWnd @uMsg @wParam @lParam
pushad
...If D@uMsg = &WM_INITDIALOG
; returns &TRUE
...Else_If D@uMsg = &WM_COMMAND
mov eax D@wParam | shr eax 16
..If ax = &BN_CLICKED
mov eax D@wParam
.If ax = &IDCANCEL
call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
.Else_If ax = IDC_BACKCOLORBOX
move D§CHOOSECOLOR@hwndOwner D@hwnd
move D§ CHOOSECOLOR@hInstance D§hInstance
Move D§CHOOSECOLOR@rgbResult D§BackgroundColor
call 'COMDLG32.ChooseColorA' CHOOSECOLOR
If eax <> 0
move D§BackgroundColor D§CHOOSECOLOR@rgbResult
call 'USER32.GetDlgItem' D@hWnd IDC_BACKCOLORBOX
call 'USER32.InvalidateRect' eax 0 &TRUE
End_If
.Else_If ax = IDC_TEXTCOLORBOX
move D§CHOOSECOLOR@hwndOwner D@hwnd
move D§ CHOOSECOLOR@hInstance D§hInstance
move D§CHOOSECOLOR@rgbResult D§TextColor
call 'COMDLG32.ChooseColorA' CHOOSECOLOR
If eax <> 0
move D§TextColor D§CHOOSECOLOR@rgbResult
call 'USER32.GetDlgItem' D@hWnd IDC_TEXTCOLORBOX
call 'USER32.InvalidateRect' eax 0 &TRUE
End_If
.Else_If ax = &IDOK
;================================================================================
; Save the modify state of the richedit control because changing the text color changes the
; modify state of the richedit control.
;==================================================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETMODIFY 0 0
push eax
call SetColor
pop eax
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY eax 0
call 'USER32.EndDialog' D@hWnd 0
.End_If
..End_If
...Else_If D@uMsg = &WM_CTLCOLORSTATIC
call 'USER32.GetDlgItem' D@hWnd IDC_BACKCOLORBOX
.If eax = D@lParam
popad
call 'GDI32.CreateSolidBrush' D§BackgroundColor
Exit
.Else
call 'USER32.GetDlgItem' D@hWnd IDC_TEXTCOLORBOX
If eax = D@lParam
popad
call 'GDI32.CreateSolidBrush' D§TextColor
Exit
End_If
.End_If
popad | mov eax &FALSE | Exit
...Else_If D@uMsg = &WM_CLOSE
call 'USER32.EndDialog' D@hWnd 0
...Else
popad | mov eax &FALSE | Exit
...End_If
popad | mov eax &TRUE
EndP
____________________________________________________________________________________________
____________________________________________________________________________________________
Proc SearchProc:
Arguments @hWnd, @uMsg, @wParam, @lParam
pushad
...If D@uMsg = &WM_INITDIALOG
move D§hSearch D@hWnd
;===================================================
; Select the default search down option
;===================================================
call 'USER32.CheckRadioButton' D@hWnd IDC_DOWN IDC_UP IDC_DOWN
call 'USER32.SendDlgItemMessageA' D@hWnd IDC_FINDEDIT &WM_SETTEXT 0 FindBuffer
...Else_If D@uMsg = &WM_COMMAND
mov eax D@wParam | shr eax 16
..If ax = &BN_CLICKED
mov eax D@wParam
On ax = &IDCANCEL, call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
If ax <> &IDOK
mov eax &FALSE | Exit
End_If
mov D§uFlags 0
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 FINDTEXT@chrg
call 'USER32.GetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer 260
If eax = 0
mov eax &TRUE | Exit
End_If
call 'USER32.IsDlgButtonChecked' D@hWnd IDC_DOWN
If eax = &BST_CHECKED
or D§uFlags &FR_DOWN
mov eax D§FINDTEXT@chrg@cpMin
On eax <> D§FINDTEXT@chrg@cpMax,
move D§FINDTEXT@chrg@cpMin D§FINDTEXT@chrg@cpMax
mov D§FINDTEXT@chrg@cpMax 0-1
Else
mov D§FINDTEXT@chrg@cpMax 0
End_If
call 'USER32.IsDlgButtonChecked' D@hWnd IDC_MATCHCASE
On eax = &BST_CHECKED, or D§uFlags &FR_MATCHCASE
call 'USER32.IsDlgButtonChecked' D@hWnd IDC_WHOLEWORD
On eax = &BST_CHECKED, or D§uFlags &FR_WHOLEWORD
mov D§FINDTEXT@lpstrText FindBuffer
call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX D§uFlags FINDTEXT
On eax <> 0-1,
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0,
FINDTEXT@chrgText
..Else
popad | mov eax &FALSE | Exit
..End_If
...Else_If D@uMsg = &WM_CLOSE
mov D§hSearch 0
call 'USER32.EndDialog' D@hWnd 0
...Else
popad | mov eax &FALSE | Exit
...End_If
popad | mov eax &TRUE
EndP
[&ST_SELECTION 2 &CP_ACP 0 &EM_SETTEXTEX 0461]
Proc ReplaceProc:
Arguments @hWnd, @uMsg, @wParam, @lParam
Structure @SETTEXT 8, @SETTEXT.flags 0, @SETTEXT.codepage 4
...If D@uMsg = &WM_INITDIALOG
move D§hSearch D@hwnd
call 'USER32.SetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer
call 'USER32.SetDlgItemTextA' D@hWnd IDC_REPLACEEDIT ReplaceBuffer
call 'USER32.GetDlgItem' D@hWnd IDC_REPLACEEDIT
call 'USER32.SetFocus' eax
...Else_If D@uMsg = &WM_COMMAND
mov eax D@wParam | shr eax 16
..If ax = &BN_CLICKED
mov eax D@wParam
.If ax = &IDCANCEL
call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
.Else_If ax = &IDOK
call 'USER32.GetDlgItemTextA' D@hWnd IDC_FINDEDIT FindBuffer 260
call 'USER32.GetDlgItemTextA' D@hWnd IDC_REPLACEEDIT ReplaceBuffer 260
mov D§FINDTEXT@chrg@cpMin 0
mov D§FINDTEXT@chrg@cpMax 0-1
mov D§FINDTEXT@lpstrText FindBuffer
mov D@SETTEXT.flags &ST_SELECTION
mov D@SETTEXT.codepage &CP_ACP
L1: call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX &FR_DOWN FINDTEXT
On eax = 0-1, jp L2>
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0 FINDTEXT@chrgText
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETTEXTEX D@SETTEXT ReplaceBuffer
jmp L1<
L2:
.End_If
..End_If
...Else_If D@uMsg = &WM_CLOSE
mov D§hSearch 0 | call 'USER32.EndDialog' D@hWnd 0
...Else
mov eax &FALSE | Exit
...End_If
mov eax &TRUE
EndP
Proc GoToProc:
Arguments @hWnd, @uMsg, @wParam, @lParam
LOCAL @LineNo
...If D@uMsg = &WM_INITDIALOG
move D§hSearch D@hwnd
...Else_If D@uMsg = &WM_COMMAND
mov eax D@wParam | shr eax 16
..If ax = &BN_CLICKED
mov eax D@wParam
.If ax = &IDCANCEL
call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
.Else_If ax = &IDOK
call 'USER32.GetDlgItemInt' D@hWnd IDC_LINENO &NULL &FALSE
mov D@LineNo eax
call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETLINECOUNT 0 0
If eax > D@LineNo
call 'USER32.SendMessageA' D§hwndRichEdit &EM_LINEINDEX D@LineNo 0
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETSEL eax eax
call 'USER32.SetFocus' D§hwndRichEdit
End_If
.End_If
..End_If
...Else_If D@uMsg = &WM_CLOSE
mov D§hSearch 0 | call 'USER32.EndDialog' D@hWnd 0
...Else
mov eax &FALSE | Exit
...End_If
mov eax &TRUE
EndP
Proc PrepareEditMenu:
Argument @hSubMenu
Structure @CHARRANGE 8, @CHARRANGE.cpMin 0, @CHARRANGE.cpMax 4
;=============================================================================
; Check whether there is some text in the clipboard. If so, we enable the paste menuitem
;=============================================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANPASTE &CF_TEXT 0
If eax = 0 ; no text in the clipboard
call 'USER32.EnableMenuItem' D@hSubMenu M00_PASTE &MF_GRAYED
Else
call 'USER32.EnableMenuItem' D@hSubMenu M00_PASTE &MF_ENABLED
End_If
;==========================================================
; check whether the undo queue is empty
;==========================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANUNDO 0 0
If eax = 0
call 'USER32.EnableMenuItem' D@hSubMenu M00_UNDO &MF_GRAYED
Else
call 'USER32.EnableMenuItem' D@hSubMenu M00_UNDO &MF_ENABLED
End_If
;=========================================================
; check whether the redo queue is empty
;=========================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_CANREDO 0 0
If eax = 0
call 'USER32.EnableMenuItem' D@hSubMenu M00_REDO &MF_GRAYED
Else
call 'USER32.EnableMenuItem' D@hSubMenu M00_REDO &MF_ENABLED
End_If
;=========================================================
; check whether there is a current selection in the richedit control.
; If there is, we enable the cut/copy/delete menuitem
;=========================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 D@CHARRANGE
mov eax D@CHARRANGE.cpMin
If eax = D@CHARRANGE.cpMax ; no current selection
call 'USER32.EnableMenuItem' D@hSubMenu M00_COPY &MF_GRAYED
call 'USER32.EnableMenuItem' D@hSubMenu M00_CUT &MF_GRAYED
call 'USER32.EnableMenuItem' D@hSubMenu M00_DELETE &MF_GRAYED
Else
call 'USER32.EnableMenuItem' D@hSubMenu M00_COPY &MF_ENABLED
call 'USER32.EnableMenuItem' D@hSubMenu M00_CUT &MF_ENABLED
call 'USER32.EnableMenuItem' D@hSubMenu M00_DELETE &MF_ENABLED
End_If
EndP
____________________________________________________________________________________________
____________________________________________________________________________________________
[ParsBuffer: B§ ? #128]
Proc ParseBuffer:
Arguments @hHeap, @pBuffer, @nSize, @ArrayOffset, @pArray
Local @InProgress
Uses edi esi
mov D@InProgress &FALSE
mov esi ParsBuffer, edi D@pBuffer
call 'USER32.CharLowerA' edi
mov ecx D@nSize | or ecx ecx | jz L9>>
L0: cmp B§edi ' ' | je L2>
cmp B§edi 9 | je L2> ; tab
mov D@InProgress &TRUE
mov al B§edi, B§esi al | inc esi
L1: inc edi | dec ecx | jz L9>>
jmp L0<
L2: cmp D@InProgress &TRUE | jne L1<
L9: mov B§esi 0 ; Found
push ecx
;========================================================
; store the word in a WORDINFO structure
;========================================================
call 'KERNEL32.HeapAlloc' D@hHeap &HEAP_ZERO_MEMORY WordInfoLen
push esi
mov esi eax
call 'KERNEL32.lstrlen' ParsBuffer
mov D§esi+WORDINFO.WordLen eax
move D§esi+WORDINFO.pColor D@ArrayOffset
inc eax
call 'KERNEL32.HeapAlloc' D@hHeap &HEAP_ZERO_MEMORY eax
mov D§esi+WORDINFO.pszWord eax, edx eax
call 'KERNEL32.lstrcpy' edx ParsBuffer
mov eax D@pArray
movzx edx B§ParsBuffer | shl edx 2 ; multiply by 4
add eax edx
If D§eax = 0
mov D§eax esi
Else
move D§esi+WORDINFO.NextLink D§eax
mov D§eax esi
End_If
pop esi
pop ecx
mov esi ParsBuffer, D@InProgress &FALSE
cmp ecx 0 | ja L1<<
EndP
____________________________________________________________________________________________
[HiliteBuffer: B§ ? #1024]
Proc FillHiliteInfo:
LOCAL @pTemp, @BlockSize
pushad
;===================================================================
; Zero out the array
;===================================================================
call 'KERNEL32.RtlZeroMemory' ASMSyntaxArray D§ASMSyntaxArrayLen
;===================================================================
; obtaining the path of this program instance
;===================================================================
call 'KERNEL32.GetModuleFileNameA' D§hInstance HiliteBuffer 1024
call 'KERNEL32.lstrlen' HiliteBuffer
mov ecx eax | dec ecx
mov edi HiliteBuffer | add edi ecx
std
mov al '\'
repne scasb
cld
inc edi | mov B§edi 0
call 'KERNEL32.lstrcat' HiliteBuffer WordFileName
;==================================================================
; Check whether the file exists
;==================================================================
call 'KERNEL32.GetFileAttributesA' HiliteBuffer
...If eax <> 0-1
;===================================================================
; allocate a block of memory from the heap for the strings
;===================================================================
mov D@BlockSize 10240
call 'KERNEL32.HeapAlloc' D§hMainHeap 0 D@BlockSize
mov D@pTemp,eax
FillHiliteFromProfile C1Key 0, C2Key 4, C3Key 8, C4Key 12, C5Key 16,
C6Key 20, C7Key 24, C8Key 28, C9Key 32, C10Key 36
call 'KERNEL32.HeapFree' D§hMainHeap 0 D@pTemp
...End_If
popad
EndP
[FillHiliteFromProfile | L1:
call 'KERNEL32.GetPrivateProfileStringA' ASMSection #1 ZeroString,
D@pTemp D@BlockSize HiliteBuffer
.If eax <> 0
inc eax
If eax = D§FillHiliteInfo@BlockSize ; the buffer is too small
add D§FillHiliteInfo@BlockSize 10240
call 'KERNEL32.HeapReAlloc' D§hMainHeap 0 D§FillHiliteInfo@pTemp D§FillHiliteInfo@BlockSize
mov D§FillHiliteInfo@pTemp eax
jmp L1<
End_If
mov edx ASMColorArray | add edx #2
call ParseBuffer D§hMainHeap D§FillHiliteInfo@pTemp eax edx ASMSyntaxArray
.endif
#+2]
____________________________________________________________________________________________
[NewBuffer: B§ ? #10240]
[txtrange:
@chrg: @cpMin: D§ 0
@cpMax: D§ 0
@lpstrText: D§ 0]
[VirtRECT: @left: 0 @top: 0 @right: 0 @bottom: 0]
[RealRECT: @left: 0 @top: 0 @right: 0 @bottom: 0]
[POINT: @x: 0 @y: 0]
Proc NewRichEditProc:
Arguments @hWnd, @uMsg, @wParam, @lParam
Local @hdc, @hOldFont, @FirstChar, @hRgn, @hOldRgn, @pString, @BufferSize
...If D@uMsg = &WM_PAINT
push edi
push esi
call 'USER32.HideCaret' D@hWnd
call 'USER32.CallWindowProcA' D§OldWndProc D@hWnd D@uMsg D@wParam D@lParam
push eax
mov edi ASMSyntaxArray
call 'USER32.GetDC' D@hWnd | mov D@hdc eax
call 'GDI32.SetBkMode' D@hdc &TRANSPARENT
;===================================================================
; Do syntax hiliting here!
;===================================================================
call 'USER32.SendMessageA' D@hWnd &EM_GETRECT 0 VirtRECT
call 'USER32.SendMessageA' D@hWnd &EM_CHARFROMPOS 0 VirtRECT
;========================================================
; obtain the line number
;========================================================
call 'USER32.SendMessageA' D@hWnd &EM_LINEFROMCHAR eax 0
call 'USER32.SendMessageA' D@hWnd &EM_LINEINDEX eax 0
mov D§txtrange@cpMin eax
mov D@FirstChar eax
call 'USER32.SendMessageA' D@hWnd &EM_CHARFROMPOS 0 VirtRECT@right
mov D§txtrange@cpMax eax
move D§RealRect@left D§VirtRECT@left
move D§RealRect@top D§VirtRECT@top
move D§RealRect@right D§VirtRECT@right
move D§RealRect@bottom D§VirtRECT@bottom
call 'GDI32.CreateRectRgn' D§RealRect@left D§RealRect@top D§RealRect@right,
D§RealRect@bottom
mov D@hRgn eax
call 'GDI32.SelectObject' D@hdc D@hRgn
mov D@hOldRgn eax
call 'GDI32.SetTextColor' D@hdc D§CommentColor
;===================================================================
; Get the visible text into buffer
;===================================================================
mov D§txtrange@lpstrText NewBuffer
call 'USER32.SendMessageA' D@hWnd &EM_GETTEXTRANGE 0 txtrange
mov esi eax ; esi == size of the text
..If esi > 0
mov D@BufferSize eax
;=========================================================
; Search for comments first
;=========================================================
push edi
push ebx
mov edi NewBuffer
mov edx edi ; used as the reference point
mov ecx esi
mov al ';'
L0: repne scasb | jne L2>>
dec edi | inc ecx
mov D@pString edi
mov ebx edi | sub ebx edx
add ebx D@FirstChar
mov D§txtrange@cpMin ebx
;===================================================
; search the end of line or the end of buffer
;===================================================
push eax
mov al 0D
repne scasb
pop eax
; HiliteTheComment:
On ecx > 0, mov B§edi-1 0
mov ebx edi
sub ebx edx
add ebx D@FirstChar
mov D§txtrange@cpMax ebx
pushad
;====================================================================
; Now we must search the range for the tabs
;====================================================================
mov edi D@pString
mov esi D§txtrange@cpMax
sub esi D§txtrange@cpMin ; esi contains the length of the buffer
mov eax esi
push edi
While eax > 0
On B§edi = 9, mov B§edi 0
inc edi | dec eax
End_While
pop edi
.while esi > 0
.If B§edi <> 0
call 'KERNEL32.lstrlen' edi
push eax
mov ecx edi
mov edx NewBuffer
sub ecx edx
add ecx D@FirstChar
If D§RichEditVersion = 3
call 'USER32.SendMessageA' D@hWnd &EM_POSFROMCHAR VirtRECT ecx
Else
call 'USER32.SendMessageA' D@hWnd &EM_POSFROMCHAR ecx 0
mov ecx eax
and ecx 0FFFF
mov D§VirtRECT@left ecx
shr eax 16
mov D§VirtRECT@top eax
End_If
call 'USER32.DrawTextA' D@hdc edi 0-1 VirtRECT 0
pop eax
add edi eax | sub esi eax
.Else
inc edi | dec esi
.End_If
.End_While
mov ecx D§txtrange@cpMax
sub ecx D§txtrange@cpMin
call 'KERNEL32.RtlZeroMemory' D@pString ecx
popad
On ecx > 0, jmp L0<<
L2:
pop ebx
pop edi
;==============================================================================
; Now that the comments are out of our way, Get rid of the separators
;==============================================================================
mov ecx D@BufferSize
mov esi NewBuffer
.While ecx > 0
mov al B§esi
On al = ' ', jmp L2>
On al = 0D, jmp L2>
On al = '/', jmp L2>
On al = ',', jmp L2>
On al = '|', jmp L2>
On al = '+', jmp L2>
On al = '-', jmp L2>
On al = '*', jmp L2>
On al = '&', jmp L2>
On al = '<', jmp L2>
On al = '>', jmp L2>
On al = '=', jmp L2>
On al = '(', jmp L2>
On al = ')', jmp L2>
On al = '{', jmp L2>
On al = '}', jmp L2>
On al = '[', jmp L2>
On al = ']', jmp L2>
On al = '^', jmp L2>
On al = ':', jmp L2>
On al = 9, jmp L2>
jmp L3>
L2: mov B§esi 0
L3: dec ecx
inc esi
.End_While
;============================================================================
; Begin the search
;============================================================================
mov esi NewBuffer
mov ecx D@BufferSize
..While ecx > 0
mov al B§esi
.If al <> 0
push ecx
call 'KERNEL32.lstrlen' esi
push eax
mov edx eax ; edx contains the length of the string
movzx eax B§esi
On al < 'A', jmp L2>
On al > 'Z', jmp L2>
add al 020
L2: shl eax 2
add eax edi ; edi contains the pointer to the WORDINFO pointer array
On D§eax = 0, jmp L7>>
mov eax D§eax
; assume eax:ptr WORDINFO
.While eax <> 0
On edx <> D§eax+WORDINFO.WordLen, jmp L6>>
pushad
call 'KERNEL32.lstrcmpi' D§eax+WORDINFO.pszWord esi
On eax <> 0, jmp L5>>
popad
;=================================================
; hilite the word
;=================================================
mov ecx esi
mov edx NewBuffer
sub ecx edx
add ecx D@FirstChar
pushad
If D§RichEditVersion = 3
call 'USER32.SendMessageA' D@hWnd &EM_POSFROMCHAR,
VirtRECT ecx
Else
call 'USER32.SendMessageA' D@hWnd &EM_POSFROMCHAR,
ecx,0
mov ecx eax | and ecx 0FFFF | mov D§VirtRECT@left ecx
shr eax 16 | mov D§VirtRECT@top eax
End_If
popad
mov edx D§eax+WORDINFO.pColor
call 'GDI32.SetTextColor' D@hdc D§edx
call 'USER32.DrawTextA' D@hdc esi 0-1 VirtRECT 0
jmp L7>> ; !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! .break
L5: ; .End_If
popad
L6: ; .End_If
mov eax D§eax+WORDINFO.NextLink
.End_While
; .End_If
L7: pop eax
pop ecx
add esi eax | sub ecx eax
.Else
inc esi | dec ecx
.End_If
..End_While
..End_If
call 'GDI32.SelectObject' D@hdc D@hOldRgn
call 'GDI32.DeleteObject' D@hRgn
call 'GDI32.SelectObject' D@hdc D@hOldFont
call 'USER32.ReleaseDC' D@hWnd D@hdc
call 'USER32.ShowCaret' D@hWnd
pop eax
pop esi
pop edi
...Else_If D@uMsg = &WM_CLOSE
call 'USER32.SetWindowLongA' D@hWnd &GWL_WNDPROC D§OldWndProc
...Else
call 'USER32.CallWindowProcA' D§OldWndProc D@hWnd D@uMsg D@wParam D@lParam
...End_If
EndP
____________________________________________________________________________________________
____________________________________________________________________________________________
[CHARRANGE:
@cpMin: D§ 0
@cpMax: D§ 0]
[OPENFILENAME:
@lStructSize: D§ Len
@hWndOwner: D§ 0
@hInstance: D§ 0
@lpstrFilter: D§ ASMFilterString
@lpstrCustomFilter: D§ 0
@nMaxCustFilter: D§ 0
@nFilterIndex: D§ 0
@lpstrFile: D§ FileName
@nMaxFile: D§ 260
@lpstrFileTitle: D§ 0
@nMaxFileTitle: D§ 260
@lpstrInitialDir: D§ 0
@lpstrTitle: D§ 0
@Flags: D§ 0
@nFileOffset: W§ 0
@nFileExtension: W§ 0
@lpstrDefExt: D§ 0
@lCustData: D§ 0
@lpfnHook: D§ 0
@lpTemplateName: D§ 0]
[Buffer: B§ ? #256]
[EDITSTREAM:
@dwCookie: D§ 0
@dwError: D§ 0
@pfnCallback: D§ 0]
[hFile: 0 hPopup: 0]
[Pt: Pt.X: 0 Pt.Y: 0]
[&TO_SIMPLELINEBREAK 2]
Proc MainWindowProc:
Arguments @hWnd @uMsg @wParam @lParam
pushad
...If D@uMsg = &WM_CREATE
call 'USER32.CreateWindowExA' &WS_EX_CLIENTEDGE RichEditClass 0,
&WS_CHILD__&WS_VISIBLE__&ES_MULTILINE__&WS_VSCROLL__&WS_HSCROLL__&ES_NOHIDESEL,
&CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT &CW_USEDEFAULT D@hWnd,
RichEditID D§hInstance 0
mov D§hwndRichEdit eax
;=======================================================
; Check the richedit version
;=======================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETTYPOGRAPHYOPTIONS,
&TO_SIMPLELINEBREAK &TO_SIMPLELINEBREAK
call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETTYPOGRAPHYOPTIONS 1 1
If eax = 0 ; means this message is not processed
mov D§RichEditVersion 2
Else
mov D§RichEditVersion 3
;=============================================================================
; Make it emulate system edit control so the text color update doesn't take very long
;=============================================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETEDITSTYLE,
&SES_EMULATESYSEDIT &SES_EMULATESYSEDIT
End_If
;=======================================================
; Subclass the richedit control
;=======================================================
call 'USER32.SetWindowLongA' D§hwndRichEdit &GWL_WNDPROC NewRichEditProc
mov D§OldWndProc eax
;=============================================================
; Set the text limit. The default is 64K
;=============================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_LIMITTEXT 0-1 0
;=============================================================
; Set the default text/background color
;=============================================================
call SetColor
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0
;============================================================
; set event mask
;============================================================
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETEVENTMASK 0 &ENM_MOUSEEVENTS
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EMPTYUNDOBUFFER 0 0
...Else_If D@uMsg = &WM_NOTIFY
;[MSGFILTER:
; @nmhdr: @NMHDR@hwndFrom: D§ 0 ; +0
; @NMHDR@idfrom: D§ 0 ; +4
; @NMHDR@code: D§ 0 ; +8
; @msg: D§ 0 ; +12
; @wParam: D§ 0 ; +16
; @lParam: D§ 0] ; +20
push esi
mov esi D@lParam
.If D§esi+8 = &EN_MSGFILTER
.If D§esi+12 = &WM_RBUTTONDOWN
call 'USER32.GetMenu' D@hWnd
call 'USER32.GetSubMenu' eax 1 | mov D§hPopup eax
call PrepareEditMenu D§hPopup
mov edx D§esi+20, ecx edx
and edx 0FFFF | shr ecx 16
mov D§Pt.X edx, D§Pt.Y ecx
call 'USER32.ClientToScreen' D@hWnd Pt
call 'USER32.TrackPopupMenu' D§hPopup &TPM_LEFTALIGN__&TPM_BOTTOMALIGN,
D§Pt.X D§Pt.Y &NULL D@hWnd &NULL
.End_If
.End_If
pop esi
...Else_If D@uMsg = &WM_INITMENUPOPUP
mov eax D@lParam
..If ax = 0 ; file menu
.If D§FileOpened = &TRUE ; a file is already opened
call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_GRAYED
call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_ENABLED
call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_ENABLED
call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_ENABLED
.Else
call 'USER32.EnableMenuItem' D@wParam M00_OPEN &MF_ENABLED
call 'USER32.EnableMenuItem' D@wParam M00_CLOSE &MF_GRAYED
call 'USER32.EnableMenuItem' D@wParam M00_SAVE &MF_GRAYED
call 'USER32.EnableMenuItem' D@wParam M00_SAVEAS &MF_GRAYED
.End_If
..Else_If ax = 1 ; edit menu
call PrepareEditMenu D@wParam
..Else_If ax = 2 ; search menu bar
.If B§FileOpened = &TRUE
call 'USER32.EnableMenuItem' D@wParam M00_FIND &MF_ENABLED
call 'USER32.EnableMenuItem' D@wParam M00_FINDNEXT &MF_ENABLED
call 'USER32.EnableMenuItem' D@wParam M00_FINDPREV &MF_ENABLED
call 'USER32.EnableMenuItem' D@wParam M00_REPLACE &MF_ENABLED
call 'USER32.EnableMenuItem' D@wParam M00_GOTOLINE &MF_ENABLED
.Else
call 'USER32.EnableMenuItem' D@wParam M00_FIND &MF_GRAYED
call 'USER32.EnableMenuItem' D@wParam M00_FINDNEXT &MF_GRAYED
call 'USER32.EnableMenuItem' D@wParam M00_FINDPREV &MF_GRAYED
call 'USER32.EnableMenuItem' D@wParam M00_REPLACE &MF_GRAYED
call 'USER32.EnableMenuItem' D@wParam M00_GOTOLINE &MF_GRAYED
.End_If
..End_If
...Else_If D@uMsg = &WM_COMMAND
..If D@lParam = 0 ; menu commands
mov eax D@wParam
.If ax = M00_OPEN
call OpenCommand D@hWnd
.Else_If ax = M00_CLOSE
call CheckModifyState D@hWnd
If eax = &TRUE
call 'USER32.SetWindowTextA' D§hwndRichEdit 0
mov D§FileOpened &FALSE
End_If
.Else_If ax = M00_SAVE
call 'KERNEL32.CreateFileA' FileName &GENERIC_WRITE &FILE_SHARE_READ,
&NULL &CREATE_ALWAYS &FILE_ATTRIBUTE_NORMAL 0
If eax <> &INVALID_HANDLE_VALUE
call InvalidFile
Else
call 'USER32.MessageBoxA' D@hWnd OpenFileFail AppName,
&MB_OK__&MB_ICONERROR
End_If
.Else_If ax = M00_COPY
call 'USER32.SendMessageA' D§hwndRichEdit &WM_COPY 0 0
.Else_If ax = M00_CUT
call 'USER32.SendMessageA' D§hwndRichEdit &WM_CUT 0 0
.Else_If ax = M00_PASTE
call 'USER32.SendMessageA' D§hwndRichEdit &WM_PASTE 0 0
.Else_If ax = M00_DELETE
call 'USER32.SendMessageA' D§hwndRichEdit &EM_REPLACESEL &TRUE 0
.Else_If ax = M00_SELECTALL
mov D§CHARRANGE@cpMin 0
mov D§CHARRANGE@cpMax 0-1
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0 CHARRANGE
.Else_If ax = M00_UNDO
call 'USER32.SendMessageA' D§hwndRichEdit &EM_UNDO 0 0
.Else_If ax = M00_REDO
call 'USER32.SendMessageA' D§hwndRichEdit &EM_REDO 0 0
.Else_If ax = M00_OPTIONS
call 'USER32.DialogBoxParamA' D§hInstance IDD_OPTIONDLG D@hWnd OptionProc 0
.Else_If ax = M00_SAVEAS
move D§OPENFILENAME@hwndOwner D@hwnd
move D§OPENFILENAME@hInstance D§hInstance
mov D§OPENFILENAME@lpstrFilter ASMFilterString
mov D§OPENFILENAME@lpstrFile AlternateFileName
mov D§AlternateFileName 0
mov D§OPENFILENAME@Flags &OFN_FILEMUSTEXIST__&OFN_HIDEREADONLY__&OFN_PATHMUSTEXIST
call 'COMDLG32.GetSaveFileNameA' OPENFILENAME
If eax <> 0
call 'KERNEL32.CreateFileA' AlternateFileName &GENERIC_WRITE,
&FILE_SHARE_READ &NULL &CREATE_ALWAYS,
&FILE_ATTRIBUTE_NORMAL 0
On eax <> &INVALID_HANDLE_VALUE, call InvalidFile
End_If
.Else_If ax = M00_FIND
On D§hSearch = 0,
call 'USER32.CreateDialogParamA' D§hInstance IDD_FINDDLG D@hWnd SearchProc 0
call 'USER32.GetDlgItem' eax IDC_FINDEDIT
call 'USER32.SetFocus' eax
.Else_If ax = M00_REPLACE
On D§hSearch = 0,
call 'USER32.CreateDialogParamA' D§hInstance IDD_REPLACEDLG D@hWnd,
ReplaceProc 0
call 'USER32.GetDlgItem' eax IDC_FINDEDIT
call 'USER32.SetFocus' eax
.Else_If ax = M00_GO_TO_LINE
On D§hSearch = 0
call 'USER32.CreateDialogParamA' D§hInstance IDD_GOTODLG D@hWnd GoToProc 0
call 'USER32.GetDlgItem' eax IDC_LINENO
call 'USER32.SetFocus' eax
.Else_If ax = M00_FIND_NEXT
call 'KERNEL32.lstrlen' FindBuffer
If eax = 0
popad | mov eax &FALSE | Exit
End_If
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 FINDTEXT@chrg
mov eax D§FINDTEXT@chrg@cpMin
On eax <> D§FINDTEXT@chrg@cpMax,
move D§FINDTEXT@chrg@cpMin, D§FINDTEXT@chrg@cpMax
mov D§FINDTEXT@chrg@cpMax 0-1
mov D§FINDTEXT@lpstrText FindBuffer
call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX &FR_DOWN,
findtext
On eax <> 0-1,
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0,
FINDTEXT@chrgText
.Else_If ax = M00_FIND_PREV
call 'KERNEL32.lstrlen' FindBuffer
If eax <> 0
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXGETSEL 0 FINDTEXT@chrg
mov D§FINDTEXT@chrg@cpMax 0
mov D§FINDTEXT@lpstrText FindBuffer
call 'USER32.SendMessageA' D§hwndRichEdit &EM_FINDTEXTEX 0 findtext
On eax <> 0-1,
call 'USER32.SendMessageA' D§hwndRichEdit &EM_EXSETSEL 0,
FINDTEXT@chrgText
End_If
.Else_If ax = M00_EXIT
call 'USER32.SendMessageA' D@hWnd &WM_CLOSE 0 0
.End_If
..End_If
...Else_If D@uMsg = &WM_CLOSE
call CheckModifyState D@hWnd
On eax = &TRUE, call 'USER32.DestroyWindow' D@hWnd
...Else_If D@uMsg = &WM_SIZE
mov eax D@lParam, edx eax
and eax 0FFFF | shr edx 16
call 'USER32.MoveWindow' D§hwndRichEdit 0 0 eax edx &TRUE
...Else_If D@uMsg = &WM_DESTROY
call 'USER32.PostQuitMessage' &NULL
...Else
popad
call 'USER32.DefWindowProcA' D@hWnd D@uMsg D@wParam D@lParam
Exit
...End_If
popad | mov eax &FALSE
EndP
InvalidFile:
mov D§hFile eax
_____________________________
; stream the text to the file
_____________________________
mov D§EDITSTREAM@dwCookie eax
mov D§EDITSTREAM@pfnCallback StreamOutProc
call 'USER32.SendMessageA' D§hwndRichEdit &EM_STREAMOUT &SF_TEXT EDITSTREAM
______________________________________
; Initialize the modify state to false
______________________________________
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0
call 'KERNEL32.CloseHandle' D§hFile
ret
Proc OpenCommand:
Argument @hWnd
move D§OPENFILENAME@hwndOwner D@hWnd
move D§OPENFILENAME@hInstance D§hInstance
mov D§FileName 0
mov D§OPENFILENAME@Flags &OFN_FILEMUSTEXIST__&OFN_HIDEREADONLY__&OFN_PATHMUSTEXIST
call 'COMDLG32.GetOpenFileNameA' OPENFILENAME
...If eax <> 0
call 'KERNEL32.CreateFileA' FileName &GENERIC_READ,
&FILE_SHARE_READ &NULL,
&OPEN_EXISTING &FILE_ATTRIBUTE_NORMAL 0
.If eax <> &INVALID_HANDLE_VALUE
mov D§hFile eax
___________________________________________
; stream the text into the richedit control
___________________________________________
mov D§EDITSTREAM@dwCookie eax
mov D§EDITSTREAM@pfnCallback StreamInProc
call 'USER32.SendMessageA' D§hwndRichEdit &EM_STREAMIN,
&SF_TEXT EDITSTREAM
______________________________________
; Initialize the modify state to false
______________________________________
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETMODIFY &FALSE 0
call 'KERNEL32.CloseHandle' D§hFile
mov B§FileOpened &TRUE
.Else
call 'USER32.MessageBoxA' D@hWnd OpenFileFail AppName,
&MB_OK__&MB_ICONERROR
.End_If
...End_If
EndP
Proc FillHiliteInfo: LOCAL @pTemp, @BlockSize pushad call 'KERNEL32.RtlZeroMemory' ASMSyntaxArray D§ASMSyntaxArrayLen
Initialize ASMSyntaxArray to zero.
Construct the full path name of wordfile.txt: I assume that it's always in the same folder as the program.
call 'KERNEL32.GetFileAttributesA' HiliteBuffer ...If eax <> 0-1I use this method as a quick way of checking whether a file exists.
mov D@BlockSize 10240 call 'KERNEL32.HeapAlloc' D§hMainHeap 0 D@BlockSize mov D@pTemp,eaxAllocate the memory block to store the words. Default to 10K. The memory is allocated from the default heap.
call 'KERNEL32.GetPrivateProfileStringA' ASMSection #1 ZeroString, D@pTemp D@BlockSize HiliteBuffer .If eax <> 0I use GetPrivateProfileString to retrieve the content of each key in wordfile.txt. The key starts from C1 to C10.
inc eax If eax = D§FillHiliteInfo@BlockSize ; the buffer is too small add D§FillHiliteInfo@BlockSize 10240 call 'KERNEL32.HeapReAlloc' D§hMainHeap 0 D§FillHiliteInfo@pTemp D§FillHiliteInfo@BlockSize mov D§FillHiliteInfo@pTemp eax jmp L1< End_If mov edx ASMColorArray | add edx #2 call ParseBuffer D§hMainHeap D§FillHiliteInfo@pTemp eax edx ASMSyntaxArray .endifChecking whether the memory block is large enough. If it is not, we increment the size by 10K until the block is large enough.
mov edx ASMColorArray | add edx #2 call ParseBuffer D§hMainHeap D§FillHiliteInfo@pTemp eax edx ASMSyntaxArrayPass the words, the memory block handle, the size of the data read from wordfile.txt, the address of the color dword that will be used to hilight the words and the address of ASMSyntaxArray.
Now, let's examine what ParseBuffer does. In essence, this function accepts the buffer containing the words to be hilighted ,parses them to individual words and stores each of them in a WORDINFO structure array that can be accessed quickly from ASMSyntaxArray.
Proc ParseBuffer: Arguments @hHeap, @pBuffer, @nSize, @ArrayOffset, @pArray Local @InProgress Uses edi esi mov D@InProgress &FALSEInProgress is the flag I use to indicate whether the scanning process has begun. If the value is FALSE, we haven't encountered a non-white space character yet.
mov ecx D@nSize | or ecx ecx | jz L9>> L0: cmp B§edi ' ' | je L2> cmp B§edi 9 | je L2> ; tabScan the whole word list in the buffer, looking for the white spaces. If a white space is found, we have to determine whether it marks the end or the beginning of a word.
mov D@InProgress &TRUE mov al B§edi, B§esi al | inc esi L1: inc edi | dec ecx | jz L9>> jmp L0<If the byte under scrutiny is not a white space, we copy it to the buffer to construct a word and then continue the scan.
L2: cmp D@InProgress &TRUE | jne L1<If a white space is found, we check the value in InProgress. If the value is TRUE, we can assume that the white space marks the end of a word and we may proceed to put the word currently in the local buffer (pointed to by esi) into a WORDINFO structure. If the value is FALSE, we continue the scan until a non-white space character is found.
L9: mov B§esi 0 ; Found push ecx call 'KERNEL32.HeapAlloc' D@hHeap &HEAP_ZERO_MEMORY WordInfoLenWhen the end of a word is found, we append 0 to the buffer to make the word an ASCIIZ string. We then allocate a block of memory from the heap the size of WORDINFO for this word.
push esi mov esi eax call 'KERNEL32.lstrlen' ParsBuffer mov D§esi+WORDINFO.WordLen eaxWe obtain the length of the word in the local buffer and store it in the WordLen member of the WORDINFO structure, to be used as a quick comparison.
move D§esi+WORDINFO.pColor D@ArrayOffsetStore the address of the dword that contains the color to be used to hilight the word in pColor member.
inc eax call 'KERNEL32.HeapAlloc' D@hHeap &HEAP_ZERO_MEMORY eax mov D§esi+WORDINFO.pszWord eax, edx eax call 'KERNEL32.lstrcpy' edx ParsBufferAllocate memory from the heap to store the word itself. Right now, the WORDINFO structure is ready to be inserted into the appropriate linked list.
mov eax D@pArray movzx edx B§ParsBuffer | shl edx 2 ; multiply by 4 add eax edxpArray contains the address of ASMSyntaxArray. We want to move to the dword that has the same index as the value of the first character of the word. So we put the first character of the word in edx then multiply edx by 4 (because each element in ASMSyntaxArray is 4 bytes in size) and then add the offset to the address of ASMSyntaxArray. We have the address of the corresponding dword in eax.
If D§eax = 0 mov D§eax esi Else move D§esi+WORDINFO.NextLink D§eax mov D§eax esi End_IfCheck the value of the dword. If it's 0, it means there is currently no word that begins with this character in the list. We thus put the address of the current WORDINFO structure in that dword.
If the value in the dword is not 0, it means there is at least one word that begins with this character in the array. We thus insert this WORDINFO structure to the head of the linked list and update its NextLink member to point to the next WORDINFO structure.
pop esi pop ecx mov esi ParsBuffer, D@InProgress &FALSE cmp ecx 0 | ja L1<<After the operation is complete, we begin the next scan cycle until the end of buffer is reached.
call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETTYPOGRAPHYOPTIONS, &TO_SIMPLELINEBREAK &TO_SIMPLELINEBREAK call 'USER32.SendMessageA' D§hwndRichEdit &EM_GETTYPOGRAPHYOPTIONS 1 1 If eax = 0 ; means this message is not processed mov D§RichEditVersion 2 Else mov D§RichEditVersion 3 ;============================================================================= ; Make it emulate system edit control so the text color update doesn't take very long ;============================================================================= call 'USER32.SendMessageA' D§hwndRichEdit &EM_SETEDITSTYLE, &SES_EMULATESYSEDIT &SES_EMULATESYSEDIT End_IfAfter the richedit control is created, we need to determine the its version. This step is necessary since EM_POSFROMCHAR behaves differently for RichEdit 2.0 and 3.0 and EM_POSFROMCHAR is crucial to our syntax hilighting routine. I have never seen a documented way of checking the version of richedit control thus I have to use a workaround. In this case, I set an option that is specific to version 3.0 and immediately retrieve its value. If I can retrieve the value, I assume that the control version is 3.0.
If you use RichEdit control version 3.0, you will notice that updating the font color for a large file takes quite a long time. This problem seems to be specific to version 3.0. I found a workaround: making the control emulate the behavior of the system edit control by sending EM_SETEDITSTYLE message.
After we can obtain the version information, we proceed to subclass the richedit control. We will now examine the new window procedure for the richedit control.
Proc NewRichEditProc: Arguments @hWnd, @uMsg, @wParam, @lParam Local @hdc, @hOldFont, @FirstChar, @hRgn, @hOldRgn, @pString, @BufferSize ...If D@uMsg = &WM_PAINT push edi push esi call 'USER32.HideCaret' D@hWnd call 'USER32.CallWindowProcA' D§OldWndProc D@hWnd D@uMsg D@wParam D@lParam push eaxWe handle WM_PAINT message. First, we hide the caret so as to avoid some ugly gfx after the hilighting. After that we pass the message to the original richedit procedure to let it update the window. When CallWindowProc returns, the text is updated with its usual color/background. Now is our opportunity to do syntax hilighting.
mov edi ASMSyntaxArray call 'USER32.GetDC' D@hWnd | mov D@hdc eax call 'GDI32.SetBkMode' D@hdc &TRANSPARENTStore the address of ASMSyntaxArray in edi. Then we obtain the handle to the device context and set the text background mode to transparent so the text that we will write will use the default background color.
call 'USER32.SendMessageA' D@hWnd &EM_GETRECT 0 VirtRECT call 'USER32.SendMessageA' D@hWnd &EM_CHARFROMPOS 0 VirtRECT call 'USER32.SendMessageA' D@hWnd &EM_LINEFROMCHAR eax 0 call 'USER32.SendMessageA' D@hWnd &EM_LINEINDEX eax 0We want to obtain the visible text so we first have to obtain the formatting rectangle by sending EM_GETRECT message to the richedit control. Now that we have the bounding rectangle, we obtain the nearest character index to the upper left corner of the rectangle with EM_CHARFROMPOS. Once we have the character index (the first visible character in the control), we can start to do syntax hilighting starting from that position. But the effect might not be as good as when we start from the first character of the line that the character is in. That's why I need to obtain the line number of that the first visible character is in by sending EM_LINEFROMCHAR message. To obtain the first character of that line, I send EM_LINEINDEX message.
mov D§txtrange@cpMin eax mov D@FirstChar eax call 'USER32.SendMessageA' D@hWnd &EM_CHARFROMPOS 0 VirtRECT@right mov D§txtrange@cpMax eaxOnce we have the first character index, store it for future reference in FirstChar variable. Next we obtain the last visible character index by sending EM_CHARFROMPOS, passing the lower-right corner of the formatting rectangle in lParam.
move D§RealRect@left D§VirtRECT@left move D§RealRect@top D§VirtRECT@top move D§RealRect@right D§VirtRECT@right move D§RealRect@bottom D§VirtRECT@bottom call 'GDI32.CreateRectRgn' D§RealRect@left D§RealRect@top D§RealRect@right, D§RealRect@bottom mov D@hRgn eax call 'GDI32.SelectObject' D@hdc D@hRgn mov D@hOldRgn eaxWhile doing syntax hilighting, I noticed an unsightly side-effect of this method: if the richedit control has a margin (you can specify margin by sending EM_SETMARGINS message to the richedit control), DrawText writes over the margin. Thus I need to create a clipping region, the size of the formatting rectangle, by calling CreateRectRgn. The output of GDI functions will be clipped to the "writable" area.
Next, we need to hilight the comments first and get them out of our way. My method is to search for ";" and hilight the text with the comment color until the carriage return is found. I will not analyze the routine here: it's fairly long and complicated. Suffice here to say that, when all the comments are hilighted, we replace them with 0s in the buffer so that the words in the comments will not be processed/hilighted later.
mov ecx D@BufferSize
mov esi NewBuffer
.While ecx > 0
mov al B§esi
On al = ' ', jmp L2>
On al = 0D, jmp L2>
On al = '/', jmp L2>
On al = ',', jmp L2>
On al = '|', jmp L2>
On al = '+', jmp L2>
On al = '-', jmp L2>
On al = '*', jmp L2>
On al = '&', jmp L2>
On al = '<', jmp L2>
On al = '>', jmp L2>
On al = '=', jmp L2>
On al = '(', jmp L2>
On al = ')', jmp L2>
On al = '{', jmp L2>
On al = '}', jmp L2>
On al = '[', jmp L2>
On al = ']', jmp L2>
On al = '^', jmp L2>
On al = ':', jmp L2>
On al = 9, jmp L2>
jmp L3>
L2: mov B§esi 0
L3: dec ecx
inc esi
.End_While
Once the comments are out of our way,
we separate the words in the buffer by replacing the "separator" characters
with 0s. With this method, we need not concern about the separator characters
while processing the words in the buffer anymore: there is only one separator
character, NULL.
mov esi NewBuffer mov ecx D@BufferSize ..While ecx > 0 mov al B§esi .If al <> 0Search the buffer for the first character that is not null,ie, the first character of a word.
push ecx call 'KERNEL32.lstrlen' esi push eax mov edx eax ; edx contains the length of the stringObtain the length of the word and put it in edx
movzx eax B§esi On al < 'A', jmp L2> On al > 'Z', jmp L2> add al 020 L2:Convert the character to lowercase (if it's an uppercase character)
shl eax 2 add eax edi ; edi contains the pointer to the WORDINFO pointer array On D§eax = 0, jmp L7>>After that, we skip to the corresponding dword in ASMSyntaxArray and check whether the value in that dword is 0. If it is, we can skip to the next word.
mov eax D§eax ; assume eax:ptr WORDINFO .While eax <> 0 On edx <> D§eax+WORDINFO.WordLen, jmp L6>>If the value in the dword is non-zero, it points to the linked list of WORDINFO structures. We process to walk the linked list, comparing the length of the word in our local buffer with the length of the word in the WORDINFO structure. This is a quick test before we compare the words. Should save some clock cycles.
pushad call 'KERNEL32.lstrcmpi' D§eax+WORDINFO.pszWord esi On eax <> 0, jmp L5>>If the lengths of both words are equal, we proceed to compare them with lstrcmpi.
popad mov ecx esi mov edx NewBuffer sub ecx edx add ecx D@FirstCharWe construct the character index from the address of the first character of the matching word in the buffer. We first obtain its relative offset from the starting address of the buffer then add the character index of the first visible character to it.
pushad If D§RichEditVersion = 3 call 'USER32.SendMessageA' D@hWnd &EM_POSFROMCHAR, VirtRECT ecx Else call 'USER32.SendMessageA' D@hWnd &EM_POSFROMCHAR, ecx,0 mov ecx eax | and ecx 0FFFF | mov D§VirtRECT@left ecx shr eax 16 | mov D§VirtRECT@top eax End_If popadOnce we know the character index of the first character of the word to be hilighted, we proceed to obtain the coordinate of it by sending EM_POSFROMCHAR message. However, this message is interpreted differently by richedit 2.0 and 3.0. For richedit 2.0, wParam contains the character index and lParam is not used. It returns the coordinate in eax. For richedit 3.0, wParam is the pointer to a POINT structure that will be filled with the coordinate and lParam contains the character index.
As you can see, passing the wrong arguments to EM_POSFROMCHAR can wreak havoc to your system. That's why I have to differentiate between RichEdit control versions.
mov edx D§eax+WORDINFO.pColor call 'GDI32.SetTextColor' D@hdc D§edx call 'USER32.DrawTextA' D@hdc esi 0-1 VirtRECT 0Once we got the coordinate to start, we set the text color with the one specified in the WORDINFO structure. And then proceed to overwrite the word with the new color.
As the final words, this method can
be improved in several ways. For example, I obtain all the text starting
from the first to the last visible line. If the lines are very long, the
performance may hurt by processing the words that are not visible. You
can optimize this by obtaining the really visible text line by line. Also
the searching algorithm can be improved by using a more efficient method.
Don't take me wrong: the syntax hilighting method used in this example
is FAST but it can be FASTER. :)