Tutorial 35: RichEdit Control: Syntax Hilighting

このチュートリアルを読む前に、1つだけ忠告しておきますが、この章は初心者にとっては非常に難しいです。このチュートリアルはリッチエディットコントロールの最後の章です。
 メインソース   ヘッダファイル   リソース   設定ファイル   実行結果 

Theory:

テキストエディタで、指定した文字をハイライトさせるのは非常に重要な機能である。 一番良い方法(私(訳者ではなくIczelion氏)の意見だが)は、専用のエディットコントロールを実装することだ。ほとんどの市販ソフトもこの方法を用いている。 しかしながら、そのようなことをしている時間が無いわれわれにとっての次善策は、既にあるコントロールに必要な機能を追加して、使いまわす事だ。

リッチエディットコントロールが構文ハイライトを実装する我々を助けてくれるということをお見せしよう。 しかし、次に記述する内容が「正しい」方法ではないということをまず先に言っておかなければならない。 ただ、この方法は誰もが陥る罠なのであることを示したいのである。
リッチエディットコントロールにはテキストの文字を変更するEM_SETCHARFORMATメッセージが備わっているが、一見したところこれで実装できると思えてしまう(私はその犠牲者の1人だが・・)。 しかしながら、あって欲しくない現象がいくつか起こる。

上で説明したように、EM_SETCHARFORMATを使用することは間違っているので、「比較的正しい」方法をお教えしよう。

私が現在使用している方法を、「ジャストインタイムハイライト」と呼んでいる。 これはテキストの見えているところだけをハイライトする方法で、ハイライトに要する時間はファイルサイズに依らない。 非常に大きなファイルであっても、見える箇所はほんの少ししかないのである。

ではどうやってやるか。以下がその方法だ。

  1. リッチエディットコントロールをサブクラス化し、WM_PAINTメッセージをハンドリングするようにする。
  2. WM_PAINTメッセージを受け取ったら、オリジナルのウィンドウプロシージャをCALLし、いつもと同じように画面を更新する
  3. その後、指定した単語をハイライトする

もちろんこれは簡単ではない。それにまだ2つの問題が残っているが、この方法は非常にうまく動作する。表示速度も満足いくものとなる。

では詳細に踏み入っていこう。サブクラス化は簡単だが、ハイライトする文字列を高速に見つけ出すのが難しい。 しかもコメント中の単語はハイライトしないというのがさらにややこしい。

私の使用している方法は、ベストではないかもしれないが、きちんと動作する。きっとあなたならもっと速い方法を見つけられるはずだ。まーとにかく、以下の方法である。

単語リストは"wordfile.txt"というファイルに格納しておき、API関数GetPrivateProfileStringでアクセスする。 私は、C1 から C10 まで、10個もの色を設定している。色配列はASMColorArray変数に格納し、WORDINFO構造体のメンバであるpColorはASMColorArrayのどれかを指すことになっている。 なので、起動中に色を変更することも簡単で、ASMColorArrayを変更するだけで実際に表示される色が変更されるようになる。

Example:

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

Analysis:

最初に行われるのは、WinMainの呼び出しではなく、FillHiliteInfo関数をCALLすることだ。この関数はwordfile.txtを読み込み、ファイル内容を解析する。

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

ASMSyntaxArrayを0に初期化する

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

wordfile.txtをフルパスにしなければならない。ただ、ここでは常に同一のフォルダにあるものとする。

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

ファイルの存在をチェックする最速の方法を使用している。

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

メモリブロックを確保する。デフォルトは10Kで、ヒープから割り当てる。

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

GetPrivateProfileString関数をCALLして、wordfile.txtにあるキーに対する値を取得する。キーはC1からC10までとなっている。

inc eax .if eax==BlockSize ; the buffer is too small add BlockSize,1024*10 invoke HeapReAlloc,hMainHeap,0,pTemp,BlockSize mov pTemp,eax jmp @B .endif

メモリブロックが十分かどうかチェックし、不足していたら、十分になるまで10Kずつ増やしていく。

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

その後、単語の配列、メモリブロックのハンドル、wordfile.txtから読み込んだデータサイズ、単語をハイライトする時に使用する色、そして、ASMSyntaxArrayを引数にして、ParseBuffer関数をCALLする。

では、ParseBuffer関数が何をするかを説明しよう。 簡単に言うと、受け取った単語の配列を解析し、 ASMSyntaxArrayから直接アクセスできるようWORDINFO構造体の配列に解析した結果を格納する。

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

InProgressはスキャンが開始されたかどうかを判別するフラグとして使用している。 FALSEなら、まだ非スペース文字が見つかっていないということだ。

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

esiはリスト中の既に検索した単語を格納するバッファをさしており、ediはは単語リストをさしている。 後で簡単に検索するため、全て小文字に変換する。

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

バッファ中の全単語をスキャンし、ホワイトスペースを検索する。見つかれば単語の最初か最後かを判断しなければならない。

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

ホワイトスペースでなければ、その単語をバッファにコピーして、また次の単語を探す。

EndOfWord: cmp InProgress,TRUE je WordFound jmp SkipIt

ホワイトスペースが見つかれば、InProgressをチェックする。TRUEなら、このホワイトスペースが単語の終端を意味しているとして、(esiがさしている)ローカルバッファであるWORDINFO構造体にセットする。 FALSEなら非ホワイトスペースが見つかるまでスキャンを続ける。

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

単語の終端が見つかれば、0を付け加えて文字列とし、この単語のためのWORDINFO構造体のメモリを取得する。

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

高速な検索を実現するためローカルバッファで取得した文字列長を、WORDINFO構造体のWordLenメンバにセットする。

push ArrayOffset pop [esi].pColor

pColorメンバには、ハイライトする色情報へのアドレスをセットする。

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

単語そのものを格納するメモリを取得し、コピーする。そうすればWORDINFO構造体の初期化は完了しリンクリストへセットできる。

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

pArrayはASMSyntaxArrayへのアドレスとなっており、単語の最初の文字の値を配列番号としている。 なので、edxでさされる単語の最初の文字を取り出し4をかける(ASMSyntaxArrayは4バイト要素の配列だからだ)。 そしてASMSyntaxArrayの先頭アドレスからオフセットさせ、eaxレジスタにその値をセットする。

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

その値が0なら、まだその文字から始まる単語が出てきていないということである。 なので、その単語をもとにWORDINFO構造体を作成する。

その値が0でなければ、少なくともその文字から始まる単語が1つ以上登録されている。 なので、WORDINFO構造体を作成し、以前のWORDINFO構造体のメンバNextLinkに今作ったものをセットする。

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

これらの処理が完了すれば、バッファの最後までこれを繰り返す。

invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1 .if eax==0 ; means this message is not processed mov RichEditVersion,2 .else mov RichEditVersion,3 invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT .endif

リッチエディットコントロールを作成後、そのバージョンを決めなければならない。 これは、EM_POSFROMCHARメッセージがバージョン2.0と3.0とで動作が異なるからで、しかもこのEM_POSFROMCHARメッセージはこのプログラムで非常で重要だからだ。

リッチエディットコントロールのバージョンをチェックする方法はどのドキュメントにも載っていないので、少し工夫しなければならない。 この場合、バージョン3.0を表すオプションをセットし、すぐにその値を取得する。 その値が取得できればそのコントロールが3.0であると仮定できる。

リッチエディットコントロールバージョン3.0を使用すると、大きなファイルに対してハイライト処理を行おうとすると非常に時間がかかることがわかるだろう。 この問題はバージョン3.0固有のもので、回避方法を見つけた。 EM_SETEDITSTYLEメッセージを送信することによりシステムエディットコントロールをエミュレートすることだ。

バージョン情報を取得後、リッチエディットコントロールをサブクラス化する。 リッチエディットコントロール用の新しいウィンドウプロシージャを作成する。

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

WM_PAINTメッセージの処理は、まず、ハイライト処理による表示上の問題を無くすためにキャレットを隠す。 その後、ウィンドウを更新するためリッチエディットコントロールにメッセージを送信する。 CallWindowProc関数から戻ると、文字列は通常の色と背景色で更新されている。ここでハイライト処理を行うことにする。

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

ediレジスタにASMSyntaxArrayのアドレスをセットする。 デバイスコンテキストのハンドルを取得し、文字列の背景モードをセットする。そして記述する文字列はデフォルトの背景色を使用する。

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

ここでは見える部分の文字列を取得したい。なのでまず、リッチエディットコントロールにEM_GETRECTメッセージを送信してformatting rectangleを取得しなければならない。 Bounding Rectangleはわかっているので、EM_CHARFROMPOSメッセージで矩形の左上の座標値から一番近い文字を取得する。 (コントロール内で最初に見える単語の)文字番号からハイライト処理を始めるのだが、その文字が属する行の一番最初の単語から始めるやり方よりよくないかもしれない。
なぜなら、EM_LINEFROMCHARメッセージを送信して最初に見える文字が何行目になっているかを取得する必要があるからだ。 その行の最初の文字を取得するためには、EM_LINEINDEXメッセージを使用する。

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

最初の文字番号はわかっているので、将来使うためにFirstChar変数に格納しておく。 次に、lParamにformatting rectableの左上の座標値をセットしてEM_CHARFROMPOSメッセージを送信し、最後に見える文字番号を取得する。

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

ハイライト処理中、この方法の思わぬ副作用に気付いてしまった。 リッチエディットコントロールにマージンがあると(EM_SETMARGINSメッセージでマージンをセットできる)、DrawText関数はそのマージンを上書きしてしまう。 なので、CreateRectRgn関数をCALLしてformatting rectangleのサイズ分、クリッピングしないといけない。 関数の戻り値はクリッピングされた「記述可能」な領域のハンドルとなっている。

次に、コメントをハイライトする。このやり方では、";"を検索し、見つかった場所からその行の終端までをハイライトしている。 非常に長く複雑なので、解説は行わない。 ここで言えることは、コメントが見つかればそれらを全て 0 に置き換え、その結果、コメントに関する処理は、以降、一切忘れていいことになっている。

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

コメントはもう出てこないので、区切り文字毎にバッファにある単語を区切っていく。 この処理により、後に続く処理では単語を区切ることを考えなくてよくなり、NULL文字にだけ気をつければよい。

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

先頭がNULLでない文字、つまり単語の先頭を探す。

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

単語の長さを取得し、edxレジスタにセットする。

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

小文字に変換する(大文字だったら)。

shl eax,2 add eax,edi ; edi contains the pointer to the WORDINFO pointer array .if dword ptr [eax]!=0

その後、ASMSyntaxArrayの要素サイズ分オフセットして、その値が0かどうかチェックする。0なら、次の単語へスキップする。

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

0でなければ、WORDINFO構造体へのポインタになっているので、リンクリストをたどり、現在取得している文字列の長さを比較する。 これにより数クロック分高速化できる。

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

長さが一致すれば、lstrcmpiで文字列が一致しているかどうか確かめる。

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

マッチしていれば、先頭文字

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

ハイライトする単語の先頭文字はわかっているので、EM_POSFROMCHARメッセージを送信して座標値を取得する。 しかしながら、このメッセージはリッチエディットコントロールのバージョン2.0と3.0で動作が異なる。
2.0では、wParamが文字コードでlParamは使用せず、戻り値が座標値となる。 3.0では、wParamPOINT構造体へのポインタでlParamが文字コードである。

見ての通り、EM_POSFROMCHARの引数は違っておりそのまま使用するとひどいことになる。 そのため、バージョンの違いを考慮してコーディングしなければならない。

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

ハイライト処理を開始する座標値は取得しているので、WORDINFO構造体で指定されている色で、新たに文字を書き直す。

最後になるが、この方法はまだ改善の余地が残されている。 例えば、このプログラムでは可視状態の文字列を取得する際、見えている行の全ての文字列を取得しているが、非常に長い行の場合、はみでて見えなくなる文字列があるのでパフォーマンスが悪化するだろう。 この問題は、はみ出る境目を取得することにより回避できる。
文字列検索も、より効果的な方法を用いれば改善できる。当たり前だが・・。
ハイライト処理は、このプログラムの方法で十分速いのだが、より速くできる。


[戻る]