; find/replace routines (Mateusz Tymek)

include "findreplace.inc"

; actFindFirst action handler
proc OnFindFirst, .wparam, .lparam
begin
        push    ebx
        stdcall CreateForm, FindDialog, [hEditorsHost]
        or      ebx,ebx
        jz      .finish

        stdcall SetFindOptions, ebx
        invoke  SendMessageW, ebx, WM_INITDIALOG, 0, 0

        stdcall ShowModal, ebx, MSF_CENTER
        cmp     eax, MR_OK
        jne     .finish

        stdcall GetFindOptions, ebx
        invoke  DestroyWindow, ebx
        mov     [FindOptions.search_type], 1

        stdcall AddStringToHistory, FindOptions.search_strw
        call    FindReplace

  .finish:
        pop     ebx
        return
endp

; actReplace action handler
proc OnReplace, .wparam, .lparam
        begin
        push    ebx
        stdcall CreateForm, ReplaceDialog, [hEditorsHost]
        or      ebx,ebx
        jz      .finish
        stdcall SetFindOptions, ebx
        invoke  SendMessageW, ebx, WM_INITDIALOG, 0, 0
        stdcall ShowModal, ebx,MSF_CENTER
        cmp     eax,MR_OK
        jne     .finish
        stdcall GetFindOptions, ebx
        invoke  DestroyWindow, ebx
        mov     [FindOptions.search_type],2
        stdcall AddStringToHistory, FindOptions.search_strw
        call    FindReplace
  .finish:
        pop     ebx
        return
endp

; actFindNext action handler
proc OnFindNext, .wparam, .lparam
begin
        push    eax
        cmp     [FindOptions.search_type],0
        je      .finish

        stdcall GetCurrentAsmEdit
        jc      .clear_search

        stdcall FindNext, eax
        or      eax,eax
        jz      .not_found

  .finish:
        pop     eax
        return

  .not_found:
        stdcall ErrorMessage, errTextNotFound

  .clear_search:
        mov     [FindOptions.search_type],0
        pop     eax
        return
endp

;--------------------------------------------------------------------------------------------------

; Window proc for find and replace dialogs
proc FindDialogProc, .hwnd, .wmsg, .wparam, .lparam
begin
        cmp     [.wmsg], FM_ONIDLE
        je      .onidle

        stc
        return

.onidle:
        invoke  SendDlgItemMessageW, [.hwnd], fdlgTextEditID, WM_GETTEXTLENGTH, 0, 0
        test    eax, eax
        setnz   al
        movzx   eax, al
        push    eax

        invoke  GetDlgItem, [.hwnd],fdlgFindButtonID
        invoke  EnableWindow, eax ; flag from the stack
        clc
        return
endp

;--------------------------------------------------------------------------------------------------

; loads strings from string history to specified combobox
; modified version from FASMW by Tomasz Grysztar
proc GetStringsFromHistory, .hwnd_combobox
begin
        push    ebx esi
        invoke  SendMessageW, [hFindHistory], LB_GETCOUNT, 0, 0
        mov     esi,eax
        xor     ebx,ebx

  .get_string:
        cmp     ebx,esi
        je      .finish
        invoke  SendMessageW, [hFindHistory], LB_GETTEXT, ebx, string_buffer
        invoke  SendMessageW, [.hwnd_combobox], CB_ADDSTRING, 0, string_buffer
        inc     ebx
        jmp     .get_string

  .finish:
        pop     esi ebx
        return
endp

; add string to find history.
; modified version from FASMW by Tomasz Grysztar
proc AddStringToHistory, .lpstr
begin
        push    esi

        mov     esi, [.lpstr]
        cmp     word [esi], 0
        je      .finish

        invoke  SendMessageW, [hFindHistory], LB_FINDSTRINGEXACT, -1, esi
        cmp     eax,LB_ERR
        je      .insert

        invoke  SendMessageW, [hFindHistory], LB_DELETESTRING, eax, 0

.insert:
        invoke  SendMessageW, [hFindHistory], LB_INSERTSTRING, 0, esi
        cmp     eax,LB_ERRSPACE
        jne     .finish

        invoke  SendMessageW, [hFindHistory], LB_GETCOUNT, 0, 0
        sub     eax,1
        jc      .finish

        invoke  SendMessageW, [hFindHistory], LB_DELETESTRING, eax, 0
        jmp     .insert

.finish:
        pop     esi
        return
endp

;--------------------------------------------------------------------------------------------------

; initializes find/replace routines
proc InitFindOptions, .hParent
begin
        xor     eax,eax
        mov     [FindOptions.case_sensitive],eax
        mov     [FindOptions.whole_words],eax
        mov     [FindOptions.prompt_on_replace],eax
        mov     [FindOptions.direction],eax
        mov     [FindOptions.range],eax
        mov     [FindOptions.search_type],eax
        invoke  CreateWindowExW, 0,cListboxClassName,NULL, WS_CHILD or LBS_HASSTRINGS,0,0,0,0,[.hParent],NULL,[hInstance],NULL
        mov     [hFindHistory],eax
        return
endp

iglobal
  hFindInstance dd ?
endg

proc FindReplace
begin

        mov     eax,[FindOptions.range]
        cmp     eax,2
        jb      .simple_search
        cmp     eax,2
        je      .search_all_open

        stdcall GetProjectFilesCount
        jmp     .number_ok

  .search_all_open:
        stdcall GetEditFilesCount

  .number_ok:
        stdcall ProgressWndCreate, eax, 1, cPerformingSearch, 0

        stdcall ThreadCreate, FindReplaceThread, 0
        mov     [hFindInstance],eax

        mov     [find_cancel_flag], 0
        stdcall ShowModal, [hProgressWnd], MSF_CENTER
        cmp     eax, MR_OK
        je      .search_done            ; if the search was cancelled, we set
                                        ; [find_cancel_flag] to 1, and then we wait
                                        ; for the search routine to cleanup.

        mov     [find_cancel_flag], 1

.search_done:
        invoke  RedrawWindow, [hEditorsHost], NULL, NULL, RDW_INVALIDATE or RDW_ERASE or RDW_ALLCHILDREN or RDW_ERASENOW or RDW_UPDATENOW
        return

  .simple_search:
        stdcall FindReplaceThread, 0
        return
endp



; peforms find or replace operation
proc FindReplaceThread, .dummy
.msg       TFreshMsg
.tvi       TVITEM
.count     dd ?
.parentmsg dd ?
.closeflag dd ?
.rect      RECT
begin
        push    ebx ecx esi edi
        mov     eax,[FindOptions.range]
        or      eax,eax
        jz      .find_first_only
        dec     eax
        jz      .find_whole_current
        dec     eax
        jz      .find_all_open_files
        dec     eax
        jz      .find_whole_project
        jmp     .ret

  .find_first_only:
        stdcall GetFileAsmEdit, [ptrCurrentFile]
        or      eax,eax
        jz      .finish
        stdcall FindFirst,eax
        or      eax,eax
        jz      .not_found
        jmp     .ret

;----------------------------------------------------------------------

  .find_whole_current:
        stdcall GetFileAsmEdit, [ptrCurrentFile]        ; johnfound
        or      eax,eax
        jz      .finish
        mov     ebx,eax
        invoke  SendMessageW, [frmMsg],MWM_CLEARMESSAGES,0,0

        mov     [blockAutoHide], 1

        mov     eax, [ptrCurrentFile]
        mov     eax, [eax+TOpenFile.hFileName]
        stdcall FindAll, ebx, eax, TVI_ROOT
        or      eax,eax
        jz      .not_found
        jmp     .finish


;-----------------------------------------------------------------------

  .find_all_open_files:
        invoke  SendMessageW, [frmMsg],MWM_CLEARMESSAGES,0,0

        mov     [blockAutoHide], 1

        ; prepare message in msg window
        stdcall StrDup, cSearchOneFile1
        mov     edi,eax
        mov     [.msg.hMessage],eax

        stdcall WideCharToUtf8, FindOptions.search_strw
        stdcall StrCat, edi, eax
        stdcall StrDel, eax

        stdcall StrCharCat, edi,'"'
        mov     [.msg.type], mtFind
        mov     [.msg.link.hFileName],0
        lea     eax,[.msg]
        invoke  SendMessageW, [frmMsg],MWM_ADDMESSAGE,eax,TVI_ROOT
        mov     [.parentmsg],eax

        xor     ebx, ebx
        stdcall EnumOpenFiles, _dosearchonefile, [.parentmsg]
        test    ebx, ebx
        jz      .not_found

        ; add number of results to message window
        stdcall StrDup, cSearchResults1
        mov     edx, eax
        stdcall NumToStr, ebx, ntsDec or ntsUnsigned
        stdcall StrCat, edx, eax
        stdcall StrDel, eax
        stdcall StrCat, edi, edx
        stdcall StrDel, edx
        stdcall StrCat, edi, cSearchResults2
        mov     [.tvi.mask], TVIF_TEXT+TVIF_HANDLE
        mov     eax,[.parentmsg]
        mov     [.tvi.hItem],eax

        stdcall utf8ToWideChar, edi
        mov     [.tvi.pszText],eax

        lea     eax,[.tvi]
        invoke  SendMessageW, [tvMessagesWin], TVM_SETITEMW, 0, eax

        stdcall FreeMem, [.tvi.pszText]

        stdcall StrDel, edi

        invoke  SendMessageW, [hProgressWnd], WM_COMMAND, MR_OK, 0
        jmp     .finish

;---------------------------------------------------------------------------

  .find_whole_project:
        invoke  SendMessageW, [frmMsg], MWM_CLEARMESSAGES, 0, 0

        mov     [blockAutoHide], 1

        ; prepare message in msg window
        stdcall StrDup, cSearchOneFile1
        mov     edi,eax
        mov     [.msg.hMessage],eax

        stdcall WideCharToUtf8, FindOptions.search_strw

        stdcall StrCat, edi, eax
        stdcall StrDel, eax

        stdcall StrCharCat, edi,'"'
        mov     [.msg.type], mtFind
        mov     [.msg.link.hFileName],0
        lea     eax,[.msg]
        invoke  SendMessageW, [frmMsg], MWM_ADDMESSAGE, eax, TVI_ROOT
        mov     [.parentmsg],eax

     ; search the project
        xor     ebx, ebx
        stdcall EnumOpenFiles, _dosearchoneprojfile, [.parentmsg]
        test    ebx, ebx
        jz      .not_found

        ; add number of results to message window
        stdcall StrDup, cSearchResults1
        mov     edx, eax
        stdcall NumToStr, ebx, ntsDec or ntsUnsigned
        stdcall StrCat, edx, eax
        stdcall StrDel, eax
        stdcall StrCat, edi, edx
        stdcall StrDel, edx
        stdcall StrCat, edi, cSearchResults2
        mov     [.tvi.mask], TVIF_TEXT+TVIF_HANDLE
        mov     eax,[.parentmsg]
        mov     [.tvi.hItem],eax

        stdcall utf8ToWideChar, edi
        mov     [.tvi.pszText],eax

        lea     eax,[.tvi]
        invoke  SendMessageW, [tvMessagesWin], TVM_SETITEMW, 0, eax

        stdcall FreeMem, [.tvi.pszText]

        stdcall StrDel, edi

        invoke  SendMessageW, [hProgressWnd], WM_COMMAND, MR_OK, 0
        jmp     .finish

;---------------------------------------------------------------------------------

.not_found:
        mov     [FindOptions.search_type],0
        stdcall ErrorMessage, errTextNotFound
        invoke  SendMessageW, [hProgressWnd], WM_COMMAND, MR_OK, 0

.finish:
        xor     eax,eax
        mov     [FindOptions.search_type],eax

.ret:
        pop     edi esi ecx ebx
        return
endp



;----------------------------------------------------------------------
; callback functions for EnumOpenFiles used in search thread function
;----------------------------------------------------------------------

proc _dosearchonefile, .ptrFile, .lParam
begin
        mov     esi, [.ptrFile]
        cmp     [esi+TOpenFile.hEditor], NULL
        je      .finish

        stdcall GetAsmEdit, [esi+TOpenFile.hEditor]
        test    eax, eax
        jz      .step

        stdcall FindAll, eax, [esi+TOpenFile.hFileName], [.lParam]
        add     ebx, eax

.step:
        stdcall ProgressWndStepIt
.finish:
        xor     eax, eax
        cmp     [find_cancel_flag], 0
        jne     .cancel

        clc
        return

.cancel:
        inc     eax
        stc
        return
endp


proc _dosearchoneprojfile, .ptrFile, .parentMsg
.flag dd ?
begin
        push    edi
        mov     esi, [.ptrFile]
        cmp     [esi+TOpenFile.projectID], NULL
        je      .finish                    ; not in the project.

        mov     [.flag], 0
        cmp     [esi+TOpenFile.hEditor], NULL
        jne     .editorok

        stdcall LoadFile, esi, NULL
        cmp     [esi+TOpenFile.hEditor], NULL
        je      .step

        inc     [.flag]

.editorok:
        stdcall GetAsmEdit, [esi+TOpenFile.hEditor]
        test    eax, eax
        jz      .searchok

        stdcall FindAll, eax, [esi+TOpenFile.hFileName], [.parentMsg]
        add     ebx, eax

.searchok:
        cmp     [.flag], 0
        je      .step

        stdcall GetModified, esi
        test    eax, eax
        jz      .saveok

        stdcall SaveFile, esi

.saveok:
        stdcall CloseFile, esi

.step:
        stdcall ProgressWndStepIt

.finish:
        xor     eax, eax
        cmp     [find_cancel_flag], 0
        jne     .cancel

        pop     edi
        clc
        return

.cancel:
        inc     eax
        pop     edi
        stc
        return
endp


; sets members of FindOptions structure corresponding to given find (replace) dialog
proc GetFindOptions, .hwnd
        begin
        push    eax ebx
        invoke  IsDlgButtonChecked, [.hwnd],fdlgCaseSensitiveCheckID
        mov     [FindOptions.case_sensitive],eax
        invoke  IsDlgButtonChecked, [.hwnd],fdlgWholeWordsCheckID
        mov     [FindOptions.whole_words],eax
        invoke  GetDlgItem, [.hwnd],fdlgPromptOnReplaceCheckID
        or      eax,eax
        jz      @f
        invoke  IsDlgButtonChecked, [.hwnd],fdlgPromptOnReplaceCheckID
        mov     [FindOptions.prompt_on_replace],eax
    @@: invoke  IsDlgButtonChecked, [.hwnd],fdlgDirectionRadioID   ; forward direction ?
        or      eax,eax
        jz      @f                                                ; no, jump
        xor     eax,eax                                           ; yes, set proper FindOption
        jmp     .direction_ok
    @@: inc     eax
    .direction_ok:
        mov     [FindOptions.direction],eax
        xor     ebx,ebx
        invoke  IsDlgButtonChecked, [.hwnd],fdlgSearchInRadioID    ; search in current source file?
        or      eax,eax
        jz      @f
        jmp     .range_ok
    @@: inc     ebx
        invoke  IsDlgButtonChecked, [.hwnd],fdlgSearchInRadioID+1  ; search in whole current file?
        or      eax,eax
        jz      @f
        jmp     .range_ok
    @@: inc     ebx
        invoke  IsDlgButtonChecked, [.hwnd],fdlgSearchInRadioID+2  ; search in all opened files?
        or      eax,eax
        jz      @f
        jmp     .range_ok
    @@: inc     ebx
    .range_ok:
        mov     [FindOptions.range],ebx
        invoke  GetDlgItemTextW, [.hwnd], fdlgTextEditID, FindOptions.search_strw, fdlgMaxFindStrLen
        invoke  GetDlgItemTextW, [.hwnd], fdlgReplaceTextEditID, FindOptions.replace_strw, fdlgMaxFindStrLen
        pop     edx eax
        return
endp

; setup given find (replace) dialog corresponding to FindOptions structure
proc SetFindOptions, .hwnd
.str rw 256
begin
        push    eax esi
        invoke  CheckDlgButton, [.hwnd],fdlgCaseSensitiveCheckID,[FindOptions.case_sensitive]
        invoke  CheckDlgButton, [.hwnd],fdlgWholeWordsCheckID,[FindOptions.whole_words]
        invoke  CheckDlgButton, [.hwnd],fdlgPromptOnReplaceCheckID,[FindOptions.prompt_on_replace]
        mov     eax,fdlgDirectionRadioID
        add     eax,[FindOptions.direction]
        invoke  CheckDlgButton, [.hwnd],eax,BST_CHECKED
        mov     eax,fdlgSearchInRadioID
        add     eax,[FindOptions.range]
        invoke  CheckDlgButton, [.hwnd],eax,BST_CHECKED
        invoke  GetDlgItem, [.hwnd],fdlgTextEditID
        push    eax

        stdcall GetStringsFromHistory, eax

        stdcall GetFileAsmEdit, [ptrCurrentFile]
        or      eax,eax
        jz      .finish

        lea     esi, [.str]
        invoke  SendMessageW, eax, AEM_GETWORDATCARET, 256, esi

        pop     eax
        invoke  SendMessageW, eax, WM_SETTEXT, 0, esi

  .finish:
        pop     esi eax
        return
endp


; searches for the first occurrence of specified string in specified AsmEdit control
; (and replaces it if necessary. Returns 1 on success and 0 on fail
proc FindFirst, .hAsmEdit
begin
        push    edx
        xor     edx,edx
        xor     eax,eax
        cmp     [FindOptions.whole_words],eax
        jz      @f
        or      edx,AEFIND_WHOLEWORDS
    @@: cmp     [FindOptions.case_sensitive],eax
        jz      @f
        or      edx,AEFIND_CASESENSITIVE
    @@: cmp     [FindOptions.direction],eax
        jz      @f
        or      edx,AEFIND_BACKWARD
    @@: lea     eax,[FindOptions.search_strw]
        ; find

        invoke  SendMessageW, [.hAsmEdit], AEM_FINDFIRST, edx, eax
        push    eax
        cmp     [FindOptions.search_type],2   ; replace?
        jne     .finish
        or      eax,eax
        jz      .finish
        cmp     [FindOptions.prompt_on_replace],0   ; prompt on replace?
        je      .dont_prompt

        stdcall AskQuestion, cReplacePrompt, [hMainForm], answerYesNo
        cmp     eax,IDNO
        je      .finish

  .dont_prompt:
        ; replace
        invoke  SendMessageW, [.hAsmEdit], EM_REPLACESEL, TRUE, FindOptions.replace_strw

  .finish:
        pop     eax
        pop     edx
        return
endp

; finds (replaces) next occurrence of a string in specified AsmEdit control
proc FindNext, .hAsmEdit
begin
        ; find
        invoke  SendMessageW, [.hAsmEdit], AEM_FINDNEXT, 0, 0
        push    eax
        cmp     [FindOptions.search_type],2   ; replace?
        jne     .finish
        or      eax,eax
        jz      .finish
        cmp     [FindOptions.prompt_on_replace],0   ; prompt on replace?
        je      .dont_prompt

        stdcall AskQuestion, cReplacePrompt, [hMainForm], answerYesNo
        cmp     eax,IDNO
        je      .finish

.dont_prompt:
        ; replace
        invoke  SendMessageW, [.hAsmEdit], EM_REPLACESEL, TRUE, FindOptions.replace_strw

.finish:
        pop     eax
        return
endp

; searches specified AsmEdit for all occurrences and returns their number

proc FindAll, .hAsmEdit, .hFileName, .hParentMsg
.old_pos AEPOS
.msg     TFreshMsg
.line    rw     aeLineCharLen
.tvi     TVITEM
.count   dd ?

begin
        push    ebx esi edi

        mov     [.count],0
        mov     ebx,[.hAsmEdit]

        mov     [.msg.type], mtFind
        mov     eax,[.hFileName]
        mov     [.msg.link.hFileName], eax

        lea     eax, [.old_pos]
        invoke  SendMessageW, ebx, AEM_GETPOS, eax, 0

        ; prepare search message
        stdcall StrDup, cSearchOneFile1
        mov     esi,eax

        stdcall WideCharToUtf8, FindOptions.search_strw

        stdcall StrCat, esi, eax
        stdcall StrDel, eax
        stdcall StrCat, esi,cSearchOneFile2
        mov     [.msg.type], mtFind
        mov     [.msg.hMessage], esi
        mov     eax,[.hFileName]
        mov     [.msg.link.hFileName],eax
        stdcall StrCat, esi,eax
        ; store cursor old position and set it to (1,1)
        mov     [.msg.link.pos.caretLine], 1
        mov     [.msg.link.pos.caretPosition], 1
        mov     [.msg.link.pos.selectionLine], 1
        mov     [.msg.link.pos.selectionPosition], 1
        lea     eax,[.msg.link.pos]
        invoke  SendMessageW, ebx, AEM_SETPOS, eax, 0

        mov     [FindOptions.direction], 0       ; force forward direction
        stdcall FindFirst, [.hAsmEdit]           ; search for the first occurrence
        or      eax,eax
        jz      .finish
        lea     eax, [.msg]
        invoke  SendMessageW, [frmMsg], MWM_ADDMESSAGE, eax, [.hParentMsg]
        mov     [.hParentMsg],eax

  .find_next:
        inc     [.count]
        lea     eax,[.msg.link.pos]
        invoke  SendMessageW, ebx,AEM_GETPOS,eax,0
        stdcall StrDup, cSearchLine1
        mov     edi,eax
        stdcall NumToStr, [.msg.link.pos.caretLine], ntsUnsigned or ntsDec
        stdcall StrCat, edi, eax
        stdcall StrDel, eax
        stdcall StrCat, edi, cSearchLine2
        lea     eax,[.line]
        invoke  SendMessageW, ebx, AEM_GETLINEDATA, [.msg.link.pos.caretLine], eax

        mov     eax, aeLineCharLen

    .findeol:
        dec     eax
        js      .stop
        cmp     word [.line + aeCharSize * eax], 0x20
        je      .findeol

    .stop:
        mov     word [.line + aeCharSize * eax + aeCharSize], 0
        lea     eax, [.line]
        stdcall WideCharToUtf8, eax
        stdcall StrCat, edi, eax
        stdcall StrDel, eax

        stdcall StrCat, edi, cSearchLine3
        mov     [.msg.hMessage], edi
        lea     eax, [.msg]
        invoke  SendMessageW, [frmMsg],MWM_ADDMESSAGE,eax,[.hParentMsg]
        stdcall StrDel, edi
        stdcall FindNext, [.hAsmEdit]
        or      eax,eax
        jnz     .find_next
        ; display number of results
        stdcall StrDup, cSearchResults1
        mov     edx, eax
        stdcall NumToStr, [.count], ntsDec or ntsUnsigned
        stdcall StrCat, edx, eax
        stdcall StrDel, eax
        stdcall StrCat, esi, edx
        stdcall StrDel, edx
        stdcall StrCat, esi,cSearchResults2
        mov     [.tvi.mask],TVIF_TEXT+TVIF_HANDLE
        mov     eax,[.hParentMsg]
        mov     [.tvi.hItem],eax

        stdcall utf8ToWideChar, esi
        stdcall StrDel, esi
        mov     esi, eax

        mov     [.tvi.pszText],esi
        lea     eax,[.tvi]
        invoke  SendMessageW, [tvMessagesWin], TVM_SETITEMW, 0, eax
        stdcall FreeMem, esi

  .finish:
        ; restore old cursor position
        lea     eax,[.old_pos]
        invoke  SendMessageW, ebx, AEM_SETPOS, eax, 0
        mov     eax,[.count]
        pop     edi esi ebx
        return
endp