{ *************************************************************************** }
{                                                                             }
{ EControl Syntax Editor SDK                                                  }
{                                                                             }
{ Copyright (c) 2004 - 2008 EControl Ltd., Zaharov Michael                    }
{     www.econtrol.ru                                                         }
{     support@econtrol.ru                                                     }
{                                                                             }
{ *************************************************************************** }
{$I ecSyntEdit.INC}

unit ecSyntDlg;

interface

uses Classes, Controls, {.$IFNDEF EC_VCL6_UP}Forms,{.$ENDIF} StdCtrls, ecFTextDlg,
     ecZRegExpr, ecSyntMemo, ecSyntAnal, ecStrUtils, ComCtrls;

type
  // Find text flags
  TSearchOption = (ftCaseSensitive,
                   ftWholeWordOnly,
                   ftBackward,       // else Forward
                   ftSelectedText,   // else Glodal
                   ftEntireScope,    // else From cursor
                   ftRegularExpr,    // use regular expressions
                   ftPromtOnReplace,
                   ftReplaceTemplate);
  TSearchOptions = set of TSearchOption;

  TOnFindEvent = procedure(Sender: TObject; StartPos, EndPos: integer; Accept: Boolean) of object;
  TOnShowFindDialog = procedure(Sender: TObject; var ModalResult: TModalResult) of object;

  TTextFinder = class(TComponent)
  private
    FFlags: TSearchOptions;
    FFindText: ecString;
    FRegExpr: TecRegExpr;
    FLastSearchPos: integer;
  protected
    function MatchAtPos(const Text: ecString; StrtPos: integer; var mLen: integer): Boolean;
  public
    destructor Destroy; override;
    function Find(const Text: ecString; var StrtPos, EndPos: integer; ToBack: Boolean): Boolean;
  published
    property FindText: ecString read FFindText write FFindText;
    property Flags: TSearchOptions read FFlags write FFlags;
  end;

  TBaseFindDialog = class(TTextFinder)
  private
    FTarget: TControl;
    FOnFind: TOnFindEvent;
    FNoSearchMsg: string;
    FOnNotFound: TNotifyEvent;
    FHistory: TStrings;
    FOnShowDialog: TOnShowFindDialog;
    procedure SetTarget(const Value: TControl);
    procedure SetHistory(const Value: TStrings);
  protected
    procedure SaveHistory(Text: string; List: TStrings);
    procedure Notification(AComponent: TComponent; Operation: TOperation); override;

    function DoSearch(ToAll, ToBack, FromCur: Boolean): Boolean; virtual;
    procedure SetDlgControls(Dlg: TCustomForm); virtual;
    procedure GetDlgControls(Dlg: TCustomForm); virtual;
    function CreateDlg: TCustomForm; virtual;

    function CanAccept(StrtPos, EndPos: integer): Boolean; dynamic;
    procedure TextNotFound; dynamic;

    property Target: TControl read FTarget write SetTarget;
  public
    constructor Create(Aowner: TComponent); override;
    destructor Destroy; override;

    function ShowDialog: TModalResult; virtual;
    function SearchAgain: Boolean;
    function FindNext: Boolean;
    function FindPrev: Boolean;
    function FindAll: Boolean;
    function FindFirst: Boolean;
    function FindLast: Boolean;
  published
    property NoSearchMsg: string read FNoSearchMsg write FNoSearchMsg;
    property History: TStrings read FHistory write SetHistory;
    property OnFind: TOnFindEvent read FOnFind write FOnFind;
    property OnNotFound: TNotifyEvent read FOnNotFound write FOnNotFound;
    property OnShowDialog: TOnShowFindDialog read FOnShowDialog write FOnShowDialog;
  end;

  TSyntFindDialog = class(TBaseFindDialog)
  private
    FAutoFill: Boolean;
    FSelectResult: Boolean;
    FSearchRanges: TList;
    FKeepSelection: Boolean;
    function GetControl: TCustomSyntaxMemo;
    procedure SetControl(const Value: TCustomSyntaxMemo);
  protected
    procedure GetSearchBound;
    procedure ShowSearchRes(Pos, Count: integer; ToBack: Boolean);
    procedure SetDlgControls(Dlg: TCustomForm); override;
    function Search(StrtPos, EndPos: integer; ToBack: Boolean; var ToAll, DoPrompt: Boolean): Boolean; virtual;
    function DoSearch(ToAll, ToBack, FromCur: Boolean): Boolean; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    function Execute: Boolean; virtual;
  published
    property AutoFill: Boolean read FAutoFill write FAutoFill default True;
    property SelectResult: Boolean read FSelectResult write FSelectResult default False;
    property KeepSelection: Boolean read FKeepSelection write FKeepSelection default False;
    property Control: TCustomSyntaxMemo read GetControl write SetControl;
  end;

  TSyntReplaceDialog = class(TSyntFindDialog)
  private
    FReplHistory: TStrings;
    FReplaceText: ecString;
    FReplacePrompt: string;
    FAutoPosPrompt: Boolean;
    procedure SetReplHistory(const Value: TStrings);
  protected
    procedure SetDlgControls(Dlg: TCustomForm); override;
    procedure GetDlgControls(Dlg: TCustomForm); override;
    function CreateDlg: TCustomForm; override;

    function Search(StrtPos, EndPos: integer; ToBack: Boolean; var ToAll, DoPrompt: Boolean): Boolean; override;
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    property ReplaceText: ecString read FReplaceText write FReplaceText;
    property ReplaceHistory: TStrings read FReplHistory write SetReplHistory;
    property ReplacePrompt: string read FReplacePrompt write FReplacePrompt;
    property AutoPosPrompt: Boolean read FAutoPosPrompt write FAutoPosPrompt default True;
  end;

  TecFindInTreeDialog = class(TBaseFindDialog)
  private
    FPrevFound: TTreeNode;
    function GetControl: TCustomTreeView;
    procedure SetControl(const Value: TCustomTreeView);
  protected
    function DoSearch(ToAll: Boolean; ToBack: Boolean; FromCur: Boolean): Boolean; override;
    function MatchNode(Node: TTreeNode): Boolean; virtual;
    function CreateDlg: TCustomForm; override;
  public
    function Execute: Boolean;
  published
    property Control: TCustomTreeView read GetControl write SetControl;
  end;

implementation

uses ecFReplDlg, Dialogs, SysUtils, Windows, Contnrs{$IFNDEF EC_VCL5}, Types{$ENDIF},
  ecFInTreeDlg;

type
  TSearchRange = class
    RangeStart: integer;
    RangeEnd: integer;
  end;

{ TTextFinder }

function TTextFinder.MatchAtPos(const Text: ecString; StrtPos: integer; var mLen: integer): Boolean;
var cmp_fl, L2: integer;
begin
  L2 := Length(FFindText);
  if ftRegularExpr in Flags then
   begin
    mLen := StrtPos;
    Result := FRegExpr.Match(Text, mLen);
    if Result then
      mLen := mLen - StrtPos;
   end
  else
   begin
    if ftCaseSensitive in Flags then cmp_fl := 0
                                else cmp_fl := NORM_IGNORECASE;
    if StrtPos > Length(Text) then
     begin
      Result := False;
      Exit;
     end;

    {$IFNDEF EC_DOTNET}
    {$IFDEF EC_UNICODE}
    Result := CompareStringW(LOCALE_SYSTEM_DEFAULT, cmp_fl,
              @Text[StrtPos], L2, PWideChar(FFindText), L2) = 2;
    {$ELSE}
    Result := CompareStringA(LOCALE_SYSTEM_DEFAULT, cmp_fl,
              @Text[StrtPos], L2, PChar(FFindText), L2) = 2;
    {$ENDIF}
    {$ELSE}
    Result := CompareString(LOCALE_SYSTEM_DEFAULT, cmp_fl,
              Text.Substring(StrtPos), L2, FFindText, L2) = 2;
    {$ENDIF}
    if Result then mLen := L2;
   end;

  if Result and (ftWholeWordOnly in Flags) then
    Result := ((StrtPos <= 1) or not IsWordChar(Text[StrtPos - 1]) or not IsWordChar(Text[StrtPos])) and
              ((StrtPos + L2 >= Length(Text)) or not IsWordChar(Text[StrtPos + L2]) or not IsWordChar(Text[StrtPos + L2-1]));
//    Result := ((StrtPos <= 1) or not IsWordChar(Text[StrtPos - 1])) and
//              ((StrtPos + L2 >= Length(Text)) or not IsWordChar(Text[StrtPos + L2]));
end;

// Search routine
function TTextFinder.Find(const Text: ecString; var StrtPos, EndPos: integer; ToBack: Boolean): Boolean;
var i, L: integer;
begin
  Result := False;
  L := Length(Text);
  if L = 0 then
   begin
    StrtPos := 1;
    EndPos := 1;
   end else
   begin
    if (EndPos < 1) or (EndPos > L) then EndPos := L;
    if StrtPos < 1 then StrtPos := 1 else
     if StrtPos > L then StrtPos := L;
   end;

  if ftRegularExpr in Flags then
   begin
     if not Assigned(FRegExpr) then
       FRegExpr := TecRegExpr.Create;
     FRegExpr.Expression := FFindText;
     FRegExpr.ModifierI := not (ftCaseSensitive in Flags);
   end else
   begin
     L := Length(FFindText);
     if EndPos - StrtPos + 1 < L then Exit;
     if L > 1 then
       dec(EndPos, L - 1);
   end;

  if ToBack then
   begin
     for i := EndPos downto StrtPos do
      if MatchAtPos(Text, i, L) and
         ((L > 0) or (i < EndPos) or (FLastSearchPos <> EndPos)) then // <== fix
       begin
         Result := true;
         StrtPos := i;
         EndPos := i + L;
         FLastSearchPos := EndPos;
         Exit;
       end;
   end else
     for i := StrtPos to EndPos do
      if MatchAtPos(Text, i, L) and
         ((L > 0) or (i > StrtPos) or (StrtPos <> FLastSearchPos)) then // <== fix
       begin
         Result := true;
         StrtPos := i;
         EndPos := i + L;
         FLastSearchPos := StrtPos;
         Exit;
       end;
end;

destructor TTextFinder.Destroy;
begin
  FreeAndNil(FRegExpr);
  inherited;
end;

{ TBaseFindDialog }

function TBaseFindDialog.SearchAgain: Boolean;
begin
  Result := DoSearch(False, ftBackward in Flags, True);
end;

function TBaseFindDialog.FindNext: Boolean;
begin
  Result := DoSearch(False, False, True);
end;

function TBaseFindDialog.FindPrev: Boolean;
begin
  Result := DoSearch(False, True, True);
end;

function TBaseFindDialog.FindAll: Boolean;
begin
  Result := DoSearch(True, False, False);
end;

function TBaseFindDialog.FindFirst: Boolean;
begin
  Result := DoSearch(False, False, True);
end;

function TBaseFindDialog.FindLast: Boolean;
begin
  Result := DoSearch(False, True, True);
end;

function TBaseFindDialog.DoSearch(ToAll, ToBack,
  FromCur: Boolean): Boolean;
begin
  Result := False;
end;

procedure TBaseFindDialog.SetTarget(const Value: TControl);
begin
  if FTarget <> Value then
    begin
      FTarget := Value;
      if Assigned(FTarget) then
        FTarget.FreeNotification(Self);
    end;
end;

procedure TBaseFindDialog.Notification(AComponent: TComponent;
  Operation: TOperation);
begin
  inherited;
  if (AComponent = FTarget) and (Operation = opRemove) then
    FTarget := nil;
end;

function TBaseFindDialog.CanAccept(StrtPos, EndPos: integer): Boolean;
begin
  Result := True;
  if Assigned(FOnFind) then
    FOnFind(Self, StrtPos, EndPos, Result);
end;

constructor TBaseFindDialog.Create(Aowner: TComponent);
begin
  inherited;
  FHistory := TStringList.Create;
  if not Assigned(AOwner) or not (csLoading in AOwner.ComponentState) then
    FNoSearchMsg := 'Search string ''%s'' not found.';
end;

destructor TBaseFindDialog.Destroy;
begin
  FreeAndNil(FHistory);
  inherited;
end;

procedure TBaseFindDialog.TextNotFound;
begin
  if Assigned(FOnNotFound) then  FOnNotFound(Self) else
   if FNoSearchMsg <> '' then
    begin
      Target.Invalidate;
      MessageDlg(Format(FNoSearchMsg, [FFindText]), mtInformation, [mbOk], 0);
      if (Target <> nil) and (Target is TWinControl) and not TWinControl(Target).Focused then
        TWinControl(Target).SetFocus;
    end;
end;

procedure TBaseFindDialog.SetHistory(const Value: TStrings);
begin
  FHistory.Assign(Value);
end;

procedure TBaseFindDialog.SaveHistory(Text: string; List: TStrings);
var idx: integer;
begin
  idx := List.IndexOf(Text);
  if idx <> -1 then List.Delete(idx);
  List.Insert(0, Text);
end;

function TBaseFindDialog.CreateDlg: TCustomForm;
begin
  Result := TFindTextDlg.Create(nil);
end;

procedure TBaseFindDialog.GetDlgControls(Dlg: TCustomForm);
begin
  with Dlg as TFindTextDlg do
   begin
     FFindText := ComboBox1.Text;
     SaveHistory(FFindText, FHistory);
     FFlags := [];
     if CheckBox1.Checked then Include(FFlags, ftCaseSensitive);
     if CheckBox2.Checked then Include(FFlags, ftWholeWordOnly);
     if CheckBox3.Checked then Include(FFlags, ftRegularExpr);
     if RadioGroup1.ItemIndex > 0 then Include(FFlags, ftBackward);
     if RadioGroup2.ItemIndex > 0 then Include(FFlags, ftSelectedText);
     if RadioGroup3.ItemIndex > 0 then Include(FFlags, ftEntireScope);
   end;
end;

procedure TBaseFindDialog.SetDlgControls(Dlg: TCustomForm);
begin
  with Dlg as TFindTextDlg do
   begin
     ComboBox1.Items := FHistory;
     ComboBox1.Text := FFindText;
     if ftEntireScope in FFlags then RadioGroup3.ItemIndex := 1
                                else RadioGroup3.ItemIndex := 0;
     if ftSelectedText in FFlags then RadioGroup2.ItemIndex := 1
                                 else RadioGroup2.ItemIndex := 0;
     CheckBox1.Checked := ftCaseSensitive in FFlags;
     CheckBox2.Checked := ftWholeWordOnly in FFlags;
     CheckBox3.Checked := ftRegularExpr   in FFlags;
     if ftBackward in FFlags then RadioGroup1.ItemIndex := 1
                             else RadioGroup1.ItemIndex := 0;
   end;
end;

function TBaseFindDialog.ShowDialog: TModalResult;
var dlg: TCustomForm;
begin
  Result := mrCancel;
  if Assigned(FOnShowDialog) then
    FOnShowDialog(Self, Result)
  else
    begin
      dlg := CreateDlg;
      if dlg <> nil then
        try
          SetDlgControls(dlg);
          Result := dlg.ShowModal;
          if Result <> mrCancel then
            GetDlgControls(dlg);
        finally
          dlg.Free;
        end;
    end;
end;

{ TSyntFindDialog }

constructor TSyntFindDialog.Create(AOwner: TComponent);
begin
  inherited;
  FAutoFill := True;
end;

destructor TSyntFindDialog.Destroy;
begin
  inherited;
end;

function TSyntFindDialog.Execute: Boolean;
var i: integer;
    FPromtRes: TModalResult;
    ToAll, DoPrompt: Boolean;
begin
  Result := False;
  if Control = nil then Exit;

  FPromtRes := ShowDialog;
  if FPromtRes <> mrCancel then
    try
      GetSearchBound;
      ToAll := FPromtRes = mrAll;
      DoPrompt := ftPromtOnReplace in FFlags;
      Control.Lines.BeginUndoGroup;
      try
        if Assigned(FSearchRanges) then
          if ftBackward in Flags then
            begin
              for i := FSearchRanges.Count - 1 downto 0 do
                with TSearchRange(FSearchRanges[i]) do
                  Result := Search(RangeStart, RangeEnd, True, ToAll, DoPrompt) or Result;
            end else
            begin
              for i := 0 to FSearchRanges.Count - 1 do
                with TSearchRange(FSearchRanges[i]) do
                  Result := Search(RangeStart, RangeEnd, False, ToAll, DoPrompt) or Result;
            end;
      finally
        Control.Lines.EndUndoGroup;
      end;
      if not Result then
        TextNotFound;
    finally
      FreeAndNil(FSearchRanges);
    end;
  if Control.CanFocus then
    Control.SetFocus;
end;

// Determine bounds of search
//   used only in Excute
procedure TSyntFindDialog.GetSearchBound;
var curPos, StrtPos, EndPos, i: integer;
    sLeft, sRight: integer;
    R: TRect;

  procedure AddRange(sPos, ePos: integer);
  var Sr: TSearchRange;
  begin
    if not Assigned(FSearchRanges) then
      FSearchRanges := TObjectList.Create;
    Sr := TSearchRange.Create;
    Sr.RangeStart := sPos;
    Sr.RangeEnd := ePos;
    FSearchRanges.Add(Sr);
  end;

begin
  if not Assigned(Control) then Exit;

  if ftSelectedText in Flags then
   begin
     with Control do
     if SelectMode = msColumn then
       begin
         R := SelRect;
         for i := R.Top to R.Bottom do
           begin
             sLeft := CaretPosToStrPos(LogToLinesPos(Point(R.Left, i)));
             sRight := CaretPosToStrPos(LogToLinesPos(Point(R.Right, i)));
             if sLeft < sRight then
               AddRange(sLeft, sRight);
           end;
         Exit;
       end else
       begin
         StrtPos := Control.SelStart + 1;
         EndPos := Control.SelStart + Control.SelLength + 1;
       end;
   end
  else
   begin
     StrtPos := 1;
     EndPos := Control.Lines.TextLength;
   end;
  if not (ftEntireScope in Flags) then
   begin
    curPos := Control.CaretStrPos;
    if ftBackward in Flags then
     begin
       if curPos < EndPos then EndPos := curPos + 1
     end else
      if curPos > StrtPos then StrtPos := curPos + 1;
   end;

   AddRange(StrtPos, EndPos);
end;

function TSyntFindDialog.Search(StrtPos, EndPos: integer; ToBack: Boolean; var ToAll, DoPrompt: Boolean): Boolean;
var st, en: integer;
    FndCount: integer;
begin
  Result := (FFindText <> '') and (Control <> nil);
  if Result then
   begin
     st := StrtPos;
     en := EndPos;
     FndCount := 0;
     repeat
       if not Find(Control.Lines.FText, st, en, ToBack) then Break else
        begin
         if CanAccept(st, en) then
           begin
             if FndCount = 0 then ShowSearchRes(st, en - st, ToBack) // fix 0000040
              else Control.SetSearchMark(st - 1, en - st, True);
             Inc(FndCount);
             if not ToAll and (FndCount >= 1) then Break;
           end;
        end;
       if ToBack{ftBackward in Flags} then // fix 0000040
        begin
          if EndPos > st then EndPos := st
           else Dec(EndPos);
        end else
        begin
          StrtPos := en;
          if st = en then Inc(StrtPos);
        end;
       st := StrtPos;
       en := EndPos;
     until False;

     Result := FndCount > 0;
     if FndCount > 1 then Control.Invalidate;
   end;
end;

procedure TSyntFindDialog.ShowSearchRes(Pos, Count: integer; ToBack: Boolean);
var cng_PS: Boolean;
//    p: TPoint;
    ps: integer;
begin
  with Control do
   begin
    cng_PS := FKeepSelection and not (soPersistentBlocks in Options);
    if cng_PS then
      Options := Options + [soPersistentBlocks];
    try
      ps := Pos + Count - 1;
      if ToBack
        then
          begin
            CaretPos := StrPosToCaretPos(ps);
            CaretPos := StrPosToCaretPos(Pos - 1);
//            p := StrPosToCaretPos(Pos - 1)
          end
        else
          begin
            CaretPos := StrPosToCaretPos(Pos - 1);
            CaretPos := StrPosToCaretPos(ps);
//            p := StrPosToCaretPos(Pos + Count - 1);
          end;
//      ShowLine(p.Y);
//      CaretPos := p;
      if FSelectResult
        then SetSelection(Pos - 1, Count)
        else SetSearchMark(Pos - 1, Count);
      if not Focused then SetFocus;
    finally
      if cng_PS then
        Options := Options - [soPersistentBlocks];
    end;
   end;
end;

function TSyntFindDialog.DoSearch(ToAll, ToBack, FromCur: Boolean): Boolean;
var StrtPos, EndPos: integer;
    DoPrompt: Boolean;
begin
  if Control = nil then
    begin
      Result := False;
      Exit;
    end;
  StrtPos := 1;
  EndPos := Control.Lines.TextLength;
  DoPrompt := ftPromtOnReplace in FFlags;
  if FromCur then
   if ToBack then EndPos := Control.CaretStrPos + 1
    else StrtPos := Control.CaretStrPos + 1; //v2.02
  Result := Search(StrtPos, EndPos, ToBack, ToAll, DoPrompt);
  if not Result then
    TextNotFound;
end;

function TSyntFindDialog.GetControl: TCustomSyntaxMemo;
begin
  Result := TCustomSyntaxMemo(Target);
end;

procedure TSyntFindDialog.SetControl(const Value: TCustomSyntaxMemo);
begin
  if Target <> Value then
    begin
      if Target <> nil then
        TCustomSyntaxMemo(Target).RemoveClient(Self);
      Target := Value;
      if Value <> nil then
        Value.AddClient(Self);
    end;
end;

procedure TSyntFindDialog.SetDlgControls(Dlg: TCustomForm);
begin
  inherited;
  if FAutoFill then
   if (Dlg <> nil) and (Dlg is TFindTextDlg) then
    with TFindTextDlg(Dlg) do
     if Control.SelLength > 0 then
       begin
         ComboBox1.Text := Control.SelText;
         RadioGroup2.ItemIndex := 1;
         RadioGroup3.ItemIndex := 1;
       end else
       begin
         ComboBox1.Text := Control.WordAtPos(Control.CaretPos);
         RadioGroup2.ItemIndex := 0;
         RadioGroup3.ItemIndex := 0;
       end;
end;

{ TSyntReplaceDialog }

constructor TSyntReplaceDialog.Create(AOwner: TComponent);
begin
  inherited;
  FReplHistory := TStringList.Create;
  FAutoPosPrompt := True;
  if not Assigned(AOwner) or not (csLoading in AOwner.ComponentState) then
    FReplacePrompt := 'Replace this occurence of ''%s''?';
end;

destructor TSyntReplaceDialog.Destroy;
begin
  FreeAndNil(FReplHistory);
  inherited;
end;

function TSyntReplaceDialog.CreateDlg: TCustomForm;
begin
  Result := TReplaceTextDlg.Create(nil);
end;

procedure TSyntReplaceDialog.SetDlgControls(Dlg: TCustomForm);
begin
  inherited;
  if (Dlg <> nil) and (Dlg is TReplaceTextDlg) then
  with Dlg as TReplaceTextDlg do
   begin
     ComboBox2.Items := FReplHistory;
     ComboBox2.Text := FReplaceText;
     CheckBox4.Checked := ftPromtOnReplace in FFlags;
     CheckBox5.Checked := ftReplaceTemplate in FFlags;
     CheckBox5.Enabled := CheckBox3.Checked;
   end;
end;

procedure TSyntReplaceDialog.GetDlgControls(Dlg: TCustomForm);
begin
  inherited;
  if (Dlg <> nil) and (Dlg is TReplaceTextDlg) then
  with Dlg as TReplaceTextDlg do
   begin
     FReplaceText := ComboBox2.Text;
     SaveHistory(FReplaceText, FReplHistory);
     if CheckBox4.Checked then Include(FFlags, ftPromtOnReplace);
     if CheckBox5.Checked then Include(FFlags, ftReplaceTemplate);
   end;
end;

function TSyntReplaceDialog.Search(StrtPos, EndPos: integer; ToBack: Boolean; var ToAll, DoPrompt: Boolean): Boolean;
var N, st, en, Cnt, RepLen: integer;
    FBgUpd, cng_PS: Boolean;
    S: ecString;
    mr: TModalResult;
    P: TPoint;
begin
  Result := (FFindText <> '') and (Control <> nil);
  if not Result then Exit;

  FBgUpd := not DoPrompt;
  if FBgUpd then
    Control.BeginUpdate;
//  FControl.BeginUndoGroup;
  Result := False;
  Cnt := 0;
  repeat
    en := EndPos;
    st := StrtPos;
    if (st > en) or not Find(Control.Lines.FText, st, en, ToBack) then Break;

    N := en - st;
    RepLen := N;
    if CanAccept(st, en) then
      begin
        Inc(Cnt);
        if DoPrompt then
         begin
           ShowSearchRes(st, N, ToBack); // fix 0000040
           if Control.SyntObj <> nil then
             Control.SyntObj.DisableIdleAppend := True;
           try
             with CreateMessageDialog(Format(FReplacePrompt, [FindText]),
                                      mtConfirmation,
                                      [mbYes, mbNo, mbCancel, mbAll]) do
               try
                 if FAutoPosPrompt then
                   begin
                     with Control, StrPosToCaretPos(st - 1) do
                       P := ClientToScreen(CaretToMouse(X, Y));

                     if P.Y - Height > 0 then Top := P.Y - Height
                       else Top := P.Y + Control.LineHeight(Control.CaretPos);
                     if Top < 0 then Top := 0 else
                       if Top + Width > Screen.Width then
                         Top := Screen.Width - Width;

                     if Screen.Width > P.X + Width then
                       begin
                         Left := P.X;
                         if Left < 0 then Left := 0;
                       end else Left := Screen.Width - Width;
                   end else
                     Position := poScreenCenter;
                 mr := ShowModal;
               finally
                 Free;
               end;
           finally
             if Control.SyntObj <> nil then
               Control.SyntObj.DisableIdleAppend := False;
           end;
           case mr of
              mrCancel: Break;
              mrAll: begin
                      DoPrompt := False;
                      ToAll := True;
                     end;
              mrNo:  begin
                       if ToBack then EndPos := st else StrtPos := st+1;
                       Continue;
                     end;
           end;
         end else
        if not FBgUpd then
         begin
          FBgUpd := True;
          Control.BeginUpdate;
         end;

        if (ftRegularExpr in FFlags) and (ftReplaceTemplate in FFlags) then
             S := FRegExpr.Substitute(Control.Lines.FText, FReplaceText)
         else
             S := FReplaceText;

        with Control do
          begin
            cng_PS := FKeepSelection and not (soPersistentBlocks in Options);
            if cng_PS then
              Options := Options + [soPersistentBlocks];
            ReplaceText(st - 1, N, S);
            if cng_PS then
              Options := Options - [soPersistentBlocks];
          end;

        RepLen := Length(S);
        Result := True;

        if (Cnt >= 1) and not ToAll then Break;
      end;

    if not ToBack then
     begin
       if RepLen = 0 then StrtPos := st + 1
        else
         begin
          StrtPos := st + RepLen;
          if N = 0 then Inc(StrtPos)
         end;
       EndPos := EndPos - N + RepLen;
     end
    else
     begin
      if N = 0 then Dec(st);
      EndPos := st;
     end;

   until False;

   if FBgUpd then
     begin
       Control.EndUpdate;
       if Assigned(Control.SyntObj) then
         Control.SyntObj.Analyze(False);
     end;

   if Result then
    with Control do
     begin
       cng_PS := FKeepSelection and not (soPersistentBlocks in Options);
       if cng_PS then
         Options := Options + [soPersistentBlocks];
       CaretStrPos := StrtPos - 1;
       if cng_PS then
         Options := Options - [soPersistentBlocks];
     end;

   // Text not found
   Result := Cnt > 0;
end;

procedure TSyntReplaceDialog.SetReplHistory(const Value: TStrings);
begin
  FReplHistory.Assign( Value );
end;

{ TecFindInTreeDialog }

type
  TTreeViewAccess = class(TTreeView);

function TecFindInTreeDialog.DoSearch(ToAll, ToBack, FromCur: Boolean): Boolean;
var Node: TTreeNode;

  procedure TakeNext;
  begin
    if Node <> nil then
      if ToBack then
        Node := Node.GetPrev
      else
        Node := Node.GetNext;
  end;
begin
  if FromCur then
    Node := Control.Selected
  else
    with TTreeViewAccess(Control) do
      if Items.Count > 0 then
        Node := Items[0]
      else
        Node := nil;
  if Node = FPrevFound then
    TakeNext;

  while Node <> nil do
    if MatchNode(Node) then
      begin
        FPrevFound := Node;
        Node.Selected := True;
        Result := True;
        Exit;
      end else
        TakeNext;

  Result := False;
  TextNotFound;
end;

function TecFindInTreeDialog.MatchNode(Node: TTreeNode): Boolean;
var sp, ep: integer;
begin
  sp := 0;
  ep := 0;
  Result := Find(Node.Text, sp, ep, False) and
            CanAccept(Node.AbsoluteIndex, sp);
end;

function TecFindInTreeDialog.Execute: Boolean;
begin
  Result := (Control <> nil) and
            (ShowDialog <> mrCancel);
  if Result then
    Result := DoSearch(False, ftBackward in Flags, not (ftEntireScope in Flags));
end;

function TecFindInTreeDialog.GetControl: TCustomTreeView;
begin
  Result := TCustomTreeView(Target);
end;

procedure TecFindInTreeDialog.SetControl(const Value: TCustomTreeView);
begin
  Target := Value;
end;

function TecFindInTreeDialog.CreateDlg: TCustomForm;
begin
  Result := TFindTextInTreeDlg.Create(Application);
end;

end.

