Site hosted by Angelfire.com: Build your free website today!

Tutorial 35: Control: RichEdit: Realce de Sintaxis

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.

Teoría

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:

  1. subclasificar el control richedit y manejar el WM_PAINT dentro de tu propio procedimiento de ventana
  2. Cuando se reciba el mensaje WM_PAINT, llama al procedimiento de ventana original del control richedit para permitirle actualizar la pantalla como es lo usual.
  3. Después de eso, sobreescribimos las palabras a ser resaltadas con diferente color

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.

Ejemplo

.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

Análisis:

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


Indice

Siguiente

[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