(***********************************************)
(*                                             *)
(* The SEARCH include file                     *)
(* This is a part of the Simple Text Editor    *)
(* Copyright (c) 1999 by TMT Development Corp. *)
(* Author: Vadim Bodrov, TMT Development Corp. *)
(*                                             *)
(***********************************************)

const
  WordDelimiters: set of Char = [#0..#255] - ['a'..'z','A'..'Z','1'..'9','0'];

function SearchBuf(Buf: PChar; BufLen: Longint;
                   SelStart, SelLength: Longint;
                   SearchString: String;
                   FindOptions: TFindOptions): PChar;
var
  SearchCount, i: Longint;
  C: Char;
  Direction: Shortint;
  CharMap: array [Char] of Char;

function FindNextWordStart(var BufPtr: PChar): Boolean;
begin                   
  Result := False;
  while (SearchCount > 0) and
        ((Direction = 1) xor (BufPtr^ in WordDelimiters)) do
  begin
    Inc(BufPtr, Direction);
    Dec(SearchCount);
  end;
  while (SearchCount > 0) and
        ((Direction = -1) xor (BufPtr^ in WordDelimiters)) do
  begin
    Inc(BufPtr, Direction);
    Dec(SearchCount);
  end;
  Result := SearchCount > 0;
  if Direction = -1 then
  begin   { back up one char, to leave ptr on first non delim }
    Dec(BufPtr, Direction);
    Inc(SearchCount);
  end;
end;

begin
  Result := nil;
  if BufLen <= 0 then Exit;
  if FindOptions.frDown then
  begin
    Direction := 1;
    Inc(SelStart, SelLength);  { start search past end of selection }
    SearchCount := BufLen - SelStart - Length(SearchString);
    if SearchCount < 0 then Exit;
    if Longint(SelStart) + SearchCount > BufLen then Exit;
  end
  else
  begin
    Direction := -1;
    Dec(SelStart, Length(SearchString));
    SearchCount := SelStart;
  end;
  if (SelStart < 0) or (SelStart > BufLen) then Exit;
  Result := @Buf[SelStart];

  { Using a Char map array is faster than calling AnsiUpper on every character }
  for C := Low(CharMap) to High(CharMap) do
    CharMap[C] := C;

  if not FindOptions.frMatchCase then
  begin
    AnsiUpperBuff(PChar(@CharMap), sizeof(CharMap));
    AnsiUpperBuff(@SearchString[1], Length(SearchString));
  end;

  while SearchCount > 0 do
  begin
    if FindOptions.frWholeWord then
      if not FindNextWordStart(Result) then Break;
    I := 0;
    while (CharMap[Result[I]] = SearchString[I+1]) do
    begin
      Inc(I);
      if I >= Length(SearchString) then
      begin
        if (not (FindOptions.frWholeWord)) or
           (SearchCount = 0) or
           (Result[I] in WordDelimiters) then
          Exit;
        Break;
      end;
    end;
    Inc(Result, Direction);
    Dec(SearchCount);
  end;
  Result := nil;
end;

procedure FindErrorMsg;
var
  Message: String;
begin
  Message := 'Cannot find "' + SrchString + '"' + #0;
  MessageBoxEx(0, @Message[1], 'Simple Text Editor', MB_ICONINFORMATION or MB_TASKMODAL, LANG_NEUTRAL);
end;

function SearchText(SearchString: String; FindOptions: TFindOptions): Boolean;
var
  Buffer: Pointer;
  P: PChar;
  Size: DWord;
  SelStart, SelEnd: Longint;
begin
  Result := False;
  if (Length(SearchString) = 0) then Exit;
  Size := SendMessage(Editor, WM_GETTEXTLENGTH, 0, 0);
  if (Size = 0) then
  begin
    FindErrorMsg;
    exit;
  end;
  GetMem(Buffer, Size + 1);
  SendMessage(Editor, WM_GETTEXT, Size + 1, DWORD(Buffer));
  SendMessage(Editor, EM_GETSEL, DWORD(@SelStart), DWORD(@SelEnd));
  P := SearchBuf(PChar(Buffer), Size, SelStart, SelEnd - SelStart, SearchString, FindOptions);
  if P <> nil then
  begin
    SelStart := P - PChar(Buffer);
    SelEnd := SelStart + Length(SearchString);
    SendMessage(Editor, EM_SETSEL, SelStart, SelEnd);
    SendMessage(Editor, EM_SCROLLCARET, 0, 0);
    Result := True;
  end else
    FindErrorMsg;
  FreeMem(Buffer, Size + 1);
end;