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_WhileOnce 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. :)