Antes de leer este tutorial, te advierto que es un tema complicado: no apto para principiantes. Este es el último de los tutoriales sobre el control richedit.
Baja el ejemplo.
El realce de la sintaxis es materia de gran debate entre los escritores de editores de texto. El mejor método(en mi opinión) es escribir el código de un control edit personalizado y esta es la aproximación tomada por gran cantidad de softwares comerciales. Sin embargo, los que no tenemos tiempo para escribir el código para un control de estos lo mejor que podemos hacer es adaptar el control existente a nuestras necesidades.
Echemos una mirada a lo que nos suministra el control RichEdit y que nos puede ayudar a implementar el realce de sintaxis. Debo dejar establecido en este momento que el siguiente método no es el camino "correcto": sólo quiero mostrar el pitfall en el que muchos caen. El control RichEdit suministra mensajes EM_SETCHARFORMAT que puedes usar para cambiar el colordel texto. A primera vista, este mensaje parecería la solución perfecta (lo sé porque fuí una de las víctimas). Sin embargo, un examen más detenido nos mostrará varias cosas que son indeseables:
Con la discusión anterior, puedes ver que usar EM_SETCHARFORMAT es una elección errónea. Te mostraré la opción "relativamente correcta".
El método que generalmente uso es "realce de sintaxis en el momento" (just syntax hilighting just-in-time). Resaltaré sólo una porción visible del texto. Así la velocidad del realce no será relativa a todo el tamaño del archivo. No importa cuan grande sea el archivo, sólo una pequeña parte de él es visible al mismo tiempo.
¿Cómo hacerlo? La respuesta es simple:
Por suspuesto, el camino no es fácil: todavía hay muchos detalles que cuidar pero el método trabaja muy bien. La velocidad de despliegue es satisfactoria.
Ahora concentrémosnos en los detalles. El proceso de subclasificación es simple y no requiere mucha atención. La parte realmente complicada es cuando tenemos que encontrar una forma rápida de buscar las palabras a ser resaltadas. Esto es complicado porque no se necesita resaltar cualquier palabra dentro del bloque comentado.
El método que uso puede no ser el mejor pero trabaja bien. Estoy seguro que puedes encontrar una forma más rápida. De cualquier manera, aquí está:
WORDINFO struct WordLen dd ? ; tamaño de la palabra: usado como una comparación rápida pszWord dd ? ; puntero a la palabra pColor dd ? ; puntero a la dword que contiene el color usado para resaltar la palabra NextLink dd ? ; puntero a la siguiente estructura WORDINFO WORDINFO ends
Como puedes ver, uso el tamaño de las palabras como la segunda comprobación rápida. Si el primer caracter de la palabra coincide, luego comparamos su tamaño con las palabras disponibles. Cada palabra en ASMSyntaxArray contiene un puntero al encabezado del array asociado WORDINFO. Por ejemplo, la dword que representa el caracter "i" contendrá el puntero a la lista enlazada de las palabras que comienzan con "i". El miembro pColor apunta a la dword que contiene el valor del color color usado para realsar la palabra. pszWord apunta a la palabra a ser realsada, en minúscula.
La lista de palabras es almacenada en un archivo llamado "wordfile.txt" y accedo a él con GetPrivateProfileString APIs. Suministro como 10 diferentes sintaxis de coloración, comenzando por C1 hasta C10. El array de color es llamado ASMColorArray. El miembro pColor de cada estructura WORDINFO structure apunta a una de las dwords en ASMColorArray. Así que es fácil cambiar la coloración de la sintaxis en el momento [on the fly]: sólo cambias la dword en ASMColorArray que usen ese color ahora cambiarán de color inmediatamente.
.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 ? ; tamaño de la palabra: usada para una comparación rápida pszWord dd ? ; puntero a la palabra pColor dd ? ; puntero a la dword que contiene el color usado para ; resaltar la palabra NextLink dd ? ; puntero a la siguiente estructura WORDINFO WORDINFO ends .const IDR_MAINMENU equ 101 IDM_OPEN equ 40001 IDM_SAVE equ 40002 IDM_CLOSE equ 40003 IDM_SAVEAS equ 40004 IDM_EXIT equ 40005 IDM_COPY equ 40006 IDM_CUT equ 40007 IDM_PASTE equ 40008 IDM_DELETE equ 40009 IDM_SELECTALL equ 40010 IDM_OPTION equ 40011 IDM_UNDO equ 40012 IDM_REDO equ 40013 IDD_OPTIONDLG equ 101 IDC_BACKCOLORBOX equ 1000 IDC_TEXTCOLORBOX equ 1001 IDR_MAINACCEL equ 105 IDD_FINDDLG equ 102 IDD_GOTODLG equ 103 IDD_REPLACEDLG equ 104 IDC_FINDEDIT equ 1000 IDC_MATCHCASE equ 1001 IDC_REPLACEEDIT equ 1001 IDC_WHOLEWORD equ 1002 IDC_DOWN equ 1003 IDC_UP equ 1004 IDC_LINENO equ 1005 IDM_FIND equ 40014 IDM_FINDNEXT equ 40015 IDM_REPLACE equ 40016 IDM_GOTOLINE equ 40017 IDM_FINDPREV equ 40018 RichEditID equ 300 .data ClassName db "IczEditClass",0 AppName db "IczEdit version 3.0",0 RichEditDLL db "riched20.dll",0 RichEditClass db "RichEdit20A",0 NoRichEdit db "Cannot find riched20.dll",0 ASMFilterString db "ASM Source code (*.asm)",0,"*.asm",0 db "All Files (*.*)",0,"*.*",0,0 OpenFileFail db "Cannot open the file",0 WannaSave db "The data in the control is modified. Want to save it?",0 FileOpened dd FALSE BackgroundColor dd 0FFFFFFh ; blanco por defecto TextColor dd 0 ; negro por defecto 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 al cuadro de diálogo buscar/reemplazar hAccel dd ? hMainHeap dd ? ; handle del montículo [heap] 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
La primera acción antes de llamar a WinMain es llamar a FillHiliteInfo. Esta función lee el contenido de wordfile.txt y analiza el contenido.
FillHiliteInfo proc uses edi LOCAL buffer[1024]:BYTE LOCAL pTemp:DWORD LOCAL BlockSize:DWORD invoke RtlZeroMemory,addr ASMSyntaxArray,sizeof ASMSyntaxArray
Se inicializa ASMSyntaxArray en cero.
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
Construímos todo el nombre del camino hacia wordfile.txt: asumimos que siempre es el mismo
del programa.
invoke GetFileAttributes,addr buffer .if eax!=-1
Uso este método como una manera de chequear rápido si el fichero existe.
mov BlockSize,1024*10 invoke HeapAlloc,hMainHeap,0,BlockSize mov pTemp,eax
SE localiza el bloque de memoria para almacenar las words. Por defecto será 10K. La memoria
es localizada a partir del montículo por defecto.
@@: invoke GetPrivateProfileString,addr ASMSection,addr C1Key,\ addr ZeroString,pTemp,BlockSize,addr buffer .if eax!=0
Uso GetPrivateProfileString para recuperar el contenido
de cada clave en wordfile.txt. La clave va desde C1 hasta 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
Se chequea si el bloque de memoria es lo suficientemente grande. Si no lo es, incrementamos
el tamaño del bloque por 10K hasta que el bloque sea lo suficientemente grande.
mov edx,offset ASMColorArray invoke ParseBuffer,hMainHeap,pTemp,eax,edx,addr ASMSyntaxArray
Se pasan las palabras, el handle del bloque de memoria, el tamaño de los datos leídos de wordfile.txt, la dirección de la dword de color que será usada para resaltar las palabras y la dirección de ASMSyntaxArray.
Ahora vamos a examinar qué hace ParseBuffer. Básicamente,
esta función acepta el buffer que contiene las palabras a ser resaltadas, las pasa a palabras individuales y las almacena en un array de estructuras WORDINFO que puede ser accedida rápidamente desde ASMSyntaxArray.
ParseBuffer proc uses edi esi hHeap:DWORD,pBuffer:DWORD, nSize:DWORD, ArrayOffset:DWORD,pArray:DWORD LOCAL buffer[128]:BYTE LOCAL InProgress:DWORD mov InProgress,FALSE
InProgress es la bandera [flag] que uso para introducir si el proceso de revisión ha comenzado.
Si el valor es FALSE, no hemos encontrado un caracter de espacio en blanco.
lea esi,buffer mov edi,pBuffer invoke CharLower,edi
esi apunta a nuestro buffer local que contendrá la palabra que hemos parsed de la lista de
palabras. edi apunta a la cadena de lista de palabras. Para simplificar la búsqueda posterior, convertimos todos los caracteres en minúsculas.
mov ecx,nSize SearchLoop: or ecx,ecx jz Finished cmp byte ptr [edi]," " je EndOfWord cmp byte ptr [edi],9 ; tab je EndOfWord
Revisamos toda la lista de palabras en el buffer, fijándonos en los espacios en blanco. Si se ha encontrado un espacio en blanco, tenemos que determinar si marca el final del comienzo de una palabra.
mov InProgress,TRUE mov al,byte ptr [edi] mov byte ptr [esi],al inc esi SkipIt: inc edi dec ecx jmp SearchLoop
Si el byte que se está revisando no es un espacio en blanco, lo copiamos en el buffer para construir una palabra y luego seguimos con la revisión .
EndOfWord: cmp InProgress,TRUE je WordFound jmp SkipIt
Si se encuentra un espacio en blanco, chequeamos el valor en InProgress. Si el valor es TRUE, podemos asumir que el espacio en blanco marca el final de una palabra y podemos proceder a poner la palabra actual en el buffer local (apuntado por esi) dentro de una estructura WORDINFO. Si el valor es FALSE, seguimos la revisión hasta que encontramos un espacio en blanco.
WordFound: mov byte ptr [esi],0 push ecx invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,sizeof WORDINFO
Cuando encontramos el final de la palabra, anexamos 0 al buffer para hacer de la palabra una cadena ASCIIZ. Luego localizamos un bloque de memoria a partir del montículo con el tamaño de WORDINFO para esta palabra.
push esi mov esi,eax assume esi:ptr WORDINFO invoke lstrlen,addr buffer mov [esi].WordLen,eax
Obtenemos el tamaño de la palabre en el buffer local y lo almacenamos en el miembro WordLen de la estructura WORDINFO, a ser usada como una comparación rápida.
push ArrayOffset pop [esi].pColor
Almacenamos la dirección de la dword que contiene el color a ser usado para remarcar la palabra en el miembro pColor.
inc eax invoke HeapAlloc,hHeap,HEAP_ZERO_MEMORY,eax mov [esi].pszWord,eax mov edx,eax invoke lstrcpy,edx,addr buffer
Localizamos memoria desde el montículo para almacenar la misma palabra. Bien, ahora la estructura WORDINFO está preparada para ser insertada en la lista enlazada apropiada.
mov eax,pArray movzx edx,byte ptr [buffer] shl edx,2 ; multiply by 4 add eax,edx
pArray contiene la dirección de ASMSyntaxArray. Queremos mover la dword que tiene el mismo índice que el valor del primer caracter de la palabra. Así que ponemos el valor del primer caracter de la palabra en edx y luego multiplicamos edx por 4 (porque cada elemento en ASMSyntaxArray tiene un tamaño de 4 bytes) y luego agregamos el desplazamiento de la dirección de ASMSyntaxArray. Tenemos en eax la dirección de la dword corespondiente.
.if dword ptr [eax]==0 mov dword ptr [eax],esi .else push dword ptr [eax] pop [esi].NextLink mov dword ptr [eax],esi .endif
Chequeamos el valor de la dword. Si es 0, significa que actualmente no hay una palabra que comience con este caracter en la lista. Así que ponemos la dirección de la estructura WORDINFO actual en esa dword.
Si el valor en la dword no es 0, significa que al menos hay una palabra que comienza con este caracter en el array. Así queinsertamos esta estructura WORDINFO en la cabeza de la lista enlazada y actualizamos su miembro NextLink para que apunte a la siguiente estructura WORDINFO.
pop esi pop ecx lea esi,buffer mov InProgress,FALSE jmp SkipIt
Después que la operación se haya completado, comezamos el siguiente ciclo de revisión hasta que alacanzamos el final del buffer.
invoke SendMessage,hwndRichEdit,EM_SETTYPOGRAPHYOPTIONS,TO_SIMPLELINEBREAK,TO_SIMPLELINEBREAK invoke SendMessage,hwndRichEdit,EM_GETTYPOGRAPHYOPTIONS,1,1 .if eax==0 ; significa que este mensaje no procede mov RichEditVersion,2 .else mov RichEditVersion,3 invoke SendMessage,hwndRichEdit,EM_SETEDITSTYLE,SES_EMULATESYSEDIT,SES_EMULATESYSEDIT .endif
Después de que es creado el control richedit necesitamos determinar su versión. Este paso es necesario porque EM_POSFROMCHAR se comporta de modo diferente para la versión 2.0 y la 3.0, y EM_POSFROMCHAR es crucial para nuestra rutina remarcadora de sintaxis. Nunca he visto documentada una manera de chequear la versión del control richedit, así que uso una estratagema [workaround]. En este caso, pongo una opción que es específica a la versión 3.0 e inmediatamente recupero su valor. Si puedo recuperar el valor, asumo que la versión del control es 3.0.
Si usas el control RichEdit versión 3.0, notarás que la actualización del color de la fuente para un archivo más largo, toma un poco más de tiempo. Este problema parece específico a la versión 3.0. Encuentro un estratagema [workaround]: haciendo que el control emule el comportamiento del control edit del sistema enviando un mensaje EM_SETEDITSTYLE.
Después de que podemos obtener información de la versión, procedemos a subclasificar el control richedit. Examinaremos ahora el procedimiento de ventana para el control RichEdit.
NewRichEditProc proc hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD ........ ....... .if uMsg==WM_PAINT push edi push esi invoke HideCaret,hWnd invoke CallWindowProc,OldWndProc,hWnd,uMsg,wParam,lParam push eax
Manejamos el mensaje WM_PAINT. Primero, escondemos la careta para evitar algún horrible efecto después de hacer el remarcado. Después de eso, pasamos elmensaje al procedimiento richedit original para que actualice la ventana. Cuando CallWindowProc regresa, el texto es actualizado con su usual color/fondo. Ahora es la oportunidad de hacer el remarcado de la sintaxis.
mov edi,offset ASMSyntaxArray invoke GetDC,hWnd mov hdc,eax invoke SetBkMode,hdc,TRANSPARENT
Almacenamos la dirección de ASMSyntaxArray en edi. Luego obtenemos el handle al contexto de dispositivo y ponemos el texto en modo de fondo para transparentar el texto que escribiremos
usando el color del fondo por defecto.
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
Queremos obtener el texto visible, así que primero tenemos que obtener el rectángulo formateado enviando el mensaje EM_GETRECT al control richedit. Ahora que tenemos el rectángulo delineado, obtenemos el índice del caracter más cercano a la esquina izquierda superior del rectángulo con EM_CHARFROMPOS. Una vez que tenemos el índice del caracter (el primer caracter visible en el control), podemos comenzar a hacer remarcado de sintaxis comenzando desde esa posición. Pero el efecto no podría ser tan bueno como cuando comenzamos desde el primer caracter de la linea en la que está el caracter. Por esto necesito obtener el número de la línea en donde está ese primer caracter visible enviando el mensaje EM_LINEFROMCHAR. Para obtener el primer caracter de esa línea, envío el mensaje EM_LINEINDEX.
mov txtrange.chrg.cpMin,eax mov FirstChar,eax invoke SendMessage,hWnd,EM_CHARFROMPOS,0,addr rect.right mov txtrange.chrg.cpMax,eax
Una vez que tenemos el primer índice del caracter, lo almacenamos para una referencia futura para la variable FirstChar. Luego obtenemos el índice al último caracter visible enviando EM_CHARFROMPOS, pasando la esquina inferior derecha del rectángulo formateado en lParam.
push rect.left pop RealRect.left push rect.top pop RealRect.top push rect.right pop RealRect.right push rect.bottom pop RealRect.bottom invoke CreateRectRgn,RealRect.left,RealRect.top,RealRect.right,RealRect.bottom mov hRgn,eax invoke SelectObject,hdc,hRgn mov hOldRgn,eax
Mientras hacía el remarcado de sintaxis, noté un efecto antiestético colateral de este método: si el control richedit tiene un margen (puedes especificar un margen enviando el mensaje EM_SETMARGINS al control richedit), DrawText escribe sobre él. Así que necesitamos crear una región de recorte, el tamaño del rectángulo formateado, llamando a CreateRectRgn. La salida de las funciones GDI será recortada y limitada al área que puede ser escrita.
Luego, necesitamos remarcar primero los comentarios y sacarlos de nuestro camino. Mi método es buscar ";" y remarcar el texto con el color de comentario hasta encontrar un retorno de carro. No analizaré aquí la rutina: es demasiado larga y complicada. Es suficiente decir aquí que, cuando todos los comentarios están remarcados, los reemplazamos con 0s en el buffer de manera que las palabras en los comentarios no sean luego procesados/remarcados.
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
Una vez que los comentarios estén fuera de nuestro camino, separamos las palabras en el buffer poniendo en 0s en los separadores de caracteres. Con este método ya no tenemos que ver más con caracteres marcadores mientras procesamos las palabras en el buffer: hay sólo un caracter separador: NULL.
lea esi,buffer mov ecx,BufferSize .while ecx>0 mov al,byte ptr [esi] .if al!=0
Buscamos el primer caracter que no sea nulo, es decir, el primer caracter de una palabra.
push ecx invoke lstrlen,esi push eax mov edx,eax
Obtenemos el tamaño de la palabra y lo ponemos en edx
movzx eax,byte ptr [esi] .if al>="A" && al<="Z" sub al,"A" add al,"a" .endif
Convertimos el caracter en minúscula (si está en mayúscula)
shl eax,2 add eax,edi ; edi contiene un puntero al array de punteros WORDINFO .if dword ptr [eax]!=0
Después de eso, nos saltamos la correspondiente dword en ASMSyntaxArray y chequeamos si el valor en esa dword es 0. Si lo es, podemos saltar a la siguiente palabra.
mov eax,dword ptr [eax] assume eax:ptr WORDINFO .while eax!=0 .if edx==[eax].WordLen
Si el valor en la dword es diferente de cero, apunta a la lista enlazada de estructuras WORDINFO. Procedemos a revisar la lista enlazada, comparando el tamaño de la palabara en nuestro buffer local con el largo de la palabra en la estructura WORDINFO. Esta es una prueba rápida antes de comparar las palabras. Deberáa salvar unos ciclos del CPU.
pushad invoke lstrcmpi,[eax].pszWord,esi .if eax==0
Si el tamaño de ambas palabras es igual, procedemos a compararla con lstrcmpi.
popad mov ecx,esi lea edx,buffer sub ecx,edx add ecx,FirstChar
Construimos el índice de caracteres a partir de la dirección del primer caracter de la palabra que coincide en el buffer. Primero obtenemos su desplazamiento relativo a partir de la dirección inicial del buffer, luego agregamos el índice del caracter del primer caracter visible.
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
Una vez que conocemos el índice de caracter del primer caracter de la palabra a ser resaltada, procedemos a obtener su coordenada enviendo el mensaje EM_POSFROMCHAR. Sin embargo, este mensaje es interpretado de manera muy diferente por richedit 2.0 y 3.0. Para richedit 2.0, wParam contiene el índice del caracter y lParam no es usado. Regresa la coordenada en eax. Para richedit 3.0, wParam es el puntero a una estructura POINT que será llenada con la coordenada y lParam contiene el índice del caracter.
Como puedes ver, pasar los argumentos equivocados a EM_POSFROMCHAR puede hacer estragos [wreak havoc] en tu sistema. Por eso tengo que diferenciar entre las versiones de los controles RichEdit.
mov edx,[eax].pColor invoke SetTextColor,hdc,dword ptr [edx] invoke DrawText,hdc,esi,-1,addr rect,0
Una vez que he obtenido la coordenada para comenzar, ponemos el color del texto con el especificado en la estructura WORDINFO. Y luego procedemos a sobreescribir la palabra con el nuevo color.
En las palabras finales, este método puede ser mejorado de varias maneras. Por ejemplo, obtengo todos los textos que comienzan desde la primera hasta la última línea visible. Si las líneas son muy largas, la ejecución puede perjudicarse por procesar palabras que no son visibles. Puedes optimizar esto obteniendo el texto realmente visible línea por línea. También el algorritmo de búsqueda puede ser mejorado usando un método más eficiente. No creas que me he equivocado: el método de realce usado en este ejemplo es RÁPIDO pero puede ser más RÁPIDO:)
[Iczelion's Win32 Assembly Homepage]
n u M I T_o r's Programming Page
Este tutorial, original de Iczelion, ha sido traducido por: n u M I T_o r