{$ifdef nnn}begin end;{$endif}

function TATSynEdit.DoCommand_TextTabulation: TATCommandResults;
var
  N1, N2: integer;
begin
  //multiline selection?
  //instead of tabulation, do indent
  if FOptKeyTabIndents then
    if Carets.Count=1 then
    begin
      Carets[0].GetSelLines(N1, N2);
      if (N1>=0) and (N2>N1) then
      begin
        Result:= DoCommand_TextIndentUnindent(true);
        Exit
      end;
    end;

  if FOptTabSpaces then
    Result:= DoCommand_TextInsertTabSpacesAtCarets(FOverwrite)
  else
    Result:= DoCommand_TextInsertAtCarets(#9, false, FOverwrite, false);
end;


function TATSynEdit.DoCommand_TextInsertAtCarets(const AText: atString;
  AKeepCaret, AOvrMode, ASelectThen: boolean): TATCommandResults;
var
  List: TStringList;
  //
  function TextItem(i: integer): atString;
  begin
    if Assigned(List) and (Carets.Count=List.Count) and (i>=0) and (i<List.Count) then
      Result:= UTF8Decode(List[i])
    else
      Result:= AText;
  end;
  //
var
  Caret: TATCaretItem;
  Shift, PosAfter: TPoint;
  bNeedGroup: boolean;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;
  List:= nil;

  bNeedGroup:= (Carets.Count>1) or (Carets.IsSelection);
  if bNeedGroup then Strings.BeginUndoGroup;

  DoSelectionDeleteOrReset;

  //list allows to insert each clip-line into one caret
  if (AText<>sLineBreak) and (Length(AText)>1) and (Carets.Count>1) then
  begin
    List:= TStringList.Create;
    List.Text:= UTF8Encode(AText);
  end;

  try
    for i:= Carets.Count-1 downto 0 do
    begin
      Caret:= Carets[i];
      Strings.TextInsert(Caret.PosX, Caret.PosY, TextItem(i), AOvrMode, Shift, PosAfter);
      DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter);

      if not AKeepCaret then
      begin
        Caret.EndX:= IfThen(ASelectThen, Caret.PosX, -1);
        Caret.EndY:= IfThen(ASelectThen, Caret.PosY, -1);
        Caret.PosX:= PosAfter.X;
        Caret.PosY:= PosAfter.Y;
      end;
    end;
  finally
    if Assigned(List) then
      FreeAndNil(List);
  end;

  if bNeedGroup then Strings.EndUndoGroup;

  Result:= [cResultText, cResultCaretBottom];
end;

function TATSynEdit.DoCommand_TextInsertTabSpacesAtCarets(AOvrMode: boolean): TATCommandResults;
var
  Caret: TATCaretItem;
  Shift, PosAfter: TPoint;
  StrSpaces: atString;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  Strings.BeginUndoGroup;
  try
    DoSelectionDeleteOrReset;

    for i:= Carets.Count-1 downto 0 do
    begin
      Caret:= Carets[i];
      StrSpaces:= StringOfChar(' ', FTabSize - Caret.PosX mod FTabSize);
      Strings.TextInsert(Caret.PosX, Caret.PosY, StrSpaces, AOvrMode, Shift, PosAfter);
      DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter);

      Caret.PosX:= PosAfter.X;
      Caret.PosY:= PosAfter.Y;
      Caret.EndX:= -1;
      Caret.EndY:= -1;
    end;
  finally
    Strings.EndUndoGroup;
  end;

  Result:= [cResultText, cResultCaretBottom];
end;

function TATSynEdit.DoCalcIndentCharsFromPrevLines(AX, AY: integer): integer;
var
  Str: atString;
  NIndent, i: integer;
begin
  Result:= -1;
  if not Strings.IsIndexValid(AY) then Exit;

  //allow smart unindent only if caret on 1st nonspace char
  //(else Bksp must delete 1 char)
  Str:= Strings.Lines[AY];
  NIndent:= SGetIndentChars(Str);
  if not ((AX=NIndent) and (NIndent>0)) then Exit;

  //calc indent of N prev lines.
  //if indent<AX then ok
  for i:= 1 to FOptMaxLinesToCountUnindent do
  begin
    Dec(AY);
    if not Strings.IsIndexValid(AY) then Exit;

    Str:= Strings.Lines[AY];
    NIndent:= SGetIndentChars(Str);
    if NIndent<AX then
      Exit(NIndent);
  end;
end;

function TATSynEdit.DoCommand_TextDeleteLeft(ALen: integer; AAllowUnindent: boolean): TATCommandResults;
var
  Caret: TATCaretItem;
  Shift, PosAfter: TPoint;
  NIndent, NDeleteLen: integer;
  bNeedGroup: boolean;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  //selection? delete it, exit.
  if Carets.IsSelection then
  begin
    Result:= DoCommand_TextDeleteSelection;
    Exit
  end;

  bNeedGroup:= Carets.Count>1;
  if bNeedGroup then Strings.BeginUndoGroup;

  for i:= 0 to Carets.Count-1 do
  begin
    Caret:= Carets[i];
    NDeleteLen:= ALen;

    if AAllowUnindent then
    begin
      NIndent:= DoCalcIndentCharsFromPrevLines(Caret.PosX, Caret.PosY);
      if NIndent>=0 then
        if Caret.PosX>NIndent then
          NDeleteLen:= Caret.PosX-NIndent
        else
          NDeleteLen:= Caret.PosX;
    end;

    Strings.TextDeleteLeft(Caret.PosX, Caret.PosY, NDeleteLen, Shift, PosAfter);
    DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter);
    Caret.PosX:= PosAfter.X;
    Caret.PosY:= PosAfter.Y;
  end;

  if bNeedGroup then Strings.EndUndoGroup;

  Result:= [cResultText, cResultCaretAny];
end;

function TATSynEdit.DoCommand_TextDelete: TATCommandResults;
var
  bColBlock: boolean;
begin
  bColBlock:= not IsSelRectEmpty;
  if bColBlock then
    if FSelRect.Left=FSelRect.Right then
    begin
      DoSelect_None;
      bColBlock:= false;
    end;

  if bColBlock or Carets.IsSelection then
    Result:= DoCommand_TextDeleteSelection
  else
    Result:= DoCommand_TextDeleteRight(1);
end;

function TATSynEdit.DoCommand_TextDeleteSelection: TATCommandResults;
var
  Caret: TATCaretItem;
  Shift, PosAfter: TPoint;
  i: integer;
  AX1, AY1, AX2, AY2: integer;
  bSel: boolean;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  if not IsSelRectEmpty and not OptCaretManyAllowed then
  begin
    DoSelectionDeleteColumnBlock;
    Result:= [cResultText, cResultCaretTop];
    Exit
  end;

  if not Carets.IsSelection then Exit;

  Strings.BeginUndoGroup;
  try
    for i:= FCarets.Count-1 downto 0 do
    begin
      Caret:= Carets[i];
      Caret.GetRange(AX1, AY1, AX2, AY2, bSel);
      if not bSel then Continue;

      Strings.TextDeleteRange(AX1, AY1, AX2, AY2, Shift, PosAfter);
      DoCaretsShift(AX1, AY1, Shift.X, Shift.Y, PosAfter);
      Caret.PosX:= PosAfter.X;
      Caret.PosY:= PosAfter.Y;
      Caret.EndX:= -1;
      Caret.EndY:= -1;
    end;
  finally
    Strings.EndUndoGroup;
  end;

  Result:= [cResultText, cResultCaretTop];
end;


function TATSynEdit.DoCommand_TextDeleteRight(ALen: integer): TATCommandResults;
var
  Caret: TATCaretItem;
  i, Len, ShiftBelowX: integer;
  Shift, PosAfter: TPoint;
  bNeedGroup: boolean;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  //selection? delete it, exit.
  if Carets.IsSelection then
  begin
    Result:= DoCommand_TextDeleteSelection;
    exit
  end;

  bNeedGroup:= Carets.Count>1;
  if bNeedGroup then Strings.BeginUndoGroup;

  for i:= 0 to FCarets.Count-1 do
  begin
    Caret:= FCarets[i];

    //offsetX for carets in line[PosY+1]
    ShiftBelowX:= 0;
    Len:= Length(Strings.Lines[Caret.PosY]);
    if Caret.PosX=Len then
      ShiftBelowX:= Len;

    Strings.TextDeleteRight(Caret.PosX, Caret.PosY, ALen, Shift, PosAfter);
    DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter, ShiftBelowX);
    Caret.PosX:= PosAfter.X;
    Caret.PosY:= PosAfter.Y;
  end;

  if bNeedGroup then Strings.EndUndoGroup;

  Result:= [cResultText, cResultCaretAny];
end;

function TATSynEdit.DoCommand_TextInsertEol(AKeepCaret: boolean): TATCommandResults;
var
  Caret: TATCaretItem;
  Shift, PosAfter: TPoint;
  Str: atString;
  bNeedGroup: boolean;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;
  if ModeOneLine then Exit;

  bNeedGroup:= Carets.Count>1;
  if bNeedGroup then Strings.BeginUndoGroup;

  DoSelectionDeleteOrReset;

  for i:= FCarets.Count-1 downto 0 do
  begin
    Caret:= FCarets[i];
    Str:= GetAutoIndentString(Caret.PosX, Caret.PosY);
    Strings.TextInsertEol(Caret.PosX, Caret.PosY, AKeepCaret, Str, Shift, PosAfter);
    DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter);
    Caret.PosX:= PosAfter.X;
    Caret.PosY:= PosAfter.Y;
  end;

  if bNeedGroup then Strings.EndUndoGroup;

  Result:= [cResultText, cResultCaretBottom];
end;

function TATSynEdit.DoCommand_TextDeleteLines: TATCommandResults;
var
  Caret: TATCaretItem;
  Shift, PosAfter: TPoint;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  Strings.BeginUndoGroup;
  try
    DoCaretsDeleteOnSameLines;

    for i:= FCarets.Count-1 downto 0 do
    begin
      Caret:= FCarets[i];
      Strings.TextDeleteLine(Caret.PosX, Caret.PosY, Shift, PosAfter);
      DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter);
      Caret.PosX:= PosAfter.X;
      Caret.PosY:= PosAfter.Y;
    end;
  finally
    Strings.EndUndoGroup;
  end;

  Result:= [cResultText, cResultCaretTop];
end;

function TATSynEdit.DoCommand_TextDuplicateLine: TATCommandResults;
var
  Caret: TATCaretItem;
  Shift, PosAfter: TPoint;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  Strings.BeginUndoGroup;
  try
    DoCaretsDeleteOnSameLines;

    for i:= FCarets.Count-1 downto 0 do
    begin
      Caret:= FCarets[i];
      Strings.TextDuplicateLine(Caret.PosX, Caret.PosY, Shift, PosAfter);
      DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter);
      Caret.PosX:= PosAfter.X;
      Caret.PosY:= PosAfter.Y;
    end;
  finally
    Strings.EndUndoGroup;
  end;

  Result:= [cResultText, cResultCaretAny];
end;

function TATSynEdit.DoCommand_TextDeleteToLineBegin: TATCommandResults;
var
  Caret: TATCaretItem;
  Str: atString;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;
  DoCaretsDeleteOnSameLines;

  for i:= FCarets.Count-1 downto 0 do
  begin
    Caret:= FCarets[i];
    Str:= Strings.Lines[Caret.PosY];
    Delete(Str, 1, Caret.PosX);
    Strings.Lines[Caret.PosY]:= Str;
    Caret.PosX:= 0;
  end;

  Result:= [cResultText, cResultCaretLeft];
end;

function TATSynEdit.DoCommand_TextDeleteToLineEnd: TATCommandResults;
var
  Caret: TATCaretItem;
  Str: atString;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;
  DoCaretsDeleteOnSameLines;

  for i:= FCarets.Count-1 downto 0 do
  begin
    Caret:= FCarets[i];
    Str:= Strings.Lines[Caret.PosY];
    if Caret.PosX<Length(Str) then
    begin
      Delete(Str, Caret.PosX+1, MaxInt);
      Strings.Lines[Caret.PosY]:= Str;
    end;
  end;

  Result:= [cResultText, cResultCaretAny];
end;

function TATSynEdit.DoCommand_TextDeleteWord(ANext: boolean): TATCommandResults;
var
  Caret: TATCaretItem;
  Str: atString;
  Shift, PosAfter: TPoint;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  for i:= FCarets.Count-1 downto 0 do
  begin
    Caret:= FCarets[i];
    if not Strings.IsIndexValid(Caret.PosY) then Continue;
    Str:= Strings.Lines[Caret.PosY];
    Shift.X:= 0;
    Shift.Y:= 0;
    PosAfter.X:= Caret.PosX;
    PosAfter.Y:= Caret.PosY;

    //delete to prev line?
    if (Caret.PosX=0) and (not ANext) then
    begin
      Strings.TextDeleteLeft(Caret.PosX, Caret.PosY, 1, Shift, PosAfter);
    end
    else
    //delete to next line?
    if (Caret.PosX>=Length(Str)) and ANext then
    begin
      Strings.TextDeleteRight(Caret.PosX, Caret.PosY, 1, Shift, PosAfter);
    end
    else
    //jump from beyond eol to eol?
    if (Caret.PosX>Length(Str)) and (not ANext) then
    begin
      Caret.PosX:= Length(Str);
    end
    else
    //delete inside line?
    if (Caret.PosX<=Length(Str)) then
    begin
      PosAfter.X:= SFindWordOffset(Str, Caret.PosX, ANext, false, FOptWordChars);
      if PosAfter.X<>Caret.PosX then
      begin
        System.Delete(Str, Min(Caret.PosX, PosAfter.X)+1, Abs(Caret.PosX-PosAfter.X));
        Strings.Lines[Caret.PosY]:= Str;
        Shift.X:= -Abs(Caret.PosX-PosAfter.X);
        PosAfter.X:= Min(Caret.PosX, PosAfter.X);
      end;
    end;

    DoCaretsShift(Caret.PosX, Caret.PosY, Shift.X, Shift.Y, PosAfter);

    if ((Caret.PosX<>PosAfter.X) or (Caret.PosY<>PosAfter.Y)) and
      (FCarets.IndexOfPosXY(PosAfter.X, PosAfter.Y)>=0) then
    begin
      if FCarets.Count>1 then
        FCarets.Delete(i);
    end
    else
    begin
      Caret.PosX:= PosAfter.X;
      Caret.PosY:= PosAfter.Y;
    end;
  end;

  if ANext then
    Result:= [cResultText, cResultCaretBottom]
  else
    Result:= [cResultText, cResultCaretTop];
end;

function TATSynEdit.DoCommand_TextIndentUnindent(ARight: boolean): TATCommandResults;
var
  Y1, Y2: integer;
  NDecSpaces, NMinSpaces, i: integer;
  Str: atString;
  Caret: TATCaretItem;
  NShiftInit, NShift1, NShift2, NIndent1, NIndent2: integer;
begin
  Result:= [];
  DoCaretSingleAsIs;

  Caret:= Carets[0];
  Caret.GetSelLines(Y1, Y2, true{Allow no sel});
  if Y1<0 then Exit;

  if Caret.EndY<0 then
  begin
    Caret.EndX:= Caret.PosX;
    Caret.EndY:= Caret.PosY;
  end;

  if FOptIndentSize>=0 then
    NDecSpaces:= FOptIndentSize
  else
    NDecSpaces:= Abs(FOptIndentSize)*FTabSize;

  //calc minimal indent of all
  NMinSpaces:= MaxInt;
  for i:= Y1 to Y2 do
  begin
    Str:= Strings.Lines[i];
    if Trim(Str)='' then Continue;
    NMinSpaces:= Min(NMinSpaces, SGetIndentExpanded(Str, FTabSize));
  end;
  if NMinSpaces=MaxInt then Exit;

  //consider "Unindent keeps align"
  if FOptIndentKeepsAlign then
    if (not ARight) and (NMinSpaces<NDecSpaces) then Exit;

  //calc shifts (emulate Laz ide indent)
  NIndent1:= SGetIndentChars(Strings.Lines[Caret.PosY]);
  NIndent2:= SGetIndentChars(Strings.Lines[Caret.EndY]);
  NShiftInit:= Abs(FOptIndentSize) * IfThen(ARight, 1, -1);
  NShift1:= IfThen((Caret.PosX>=NIndent1) and (Caret.PosX>0), NShiftInit, 0);
  NShift2:= IfThen((Caret.EndX>=NIndent2), NShiftInit, 0);

  //do indent
  Strings.BeginUndoGroup;
  try
    for i:= Y1 to Y2 do
    begin
      Str:= Strings.Lines[i];
      if Trim(Str)='' then Continue;
      Str:= SIndentUnindent(Str, ARight, FOptIndentSize, FTabSize);
      if Strings.Lines[i]<>Str then
        Strings.Lines[i]:= Str;
    end;
  finally
    Strings.EndUndoGroup;
  end;

  //correct selection
  Caret.PosX:= Max(0, Caret.PosX+NShift1);
  Caret.EndX:= Max(0, Caret.EndX+NShift2);

  Result:= [cResultText, cResultCaretAny];
end;

function TATSynEdit.DoCommand_Undo: TATCommandResults;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  Strings.Undo(FOptUndoGrouped);
  Result:= [cResultText, cResultCaretBottom];
end;

function TATSynEdit.DoCommand_Redo: TATCommandResults;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  Strings.SetGroupMark;
  Strings.Redo(FOptUndoGrouped);
  Result:= [cResultText, cResultCaretBottom];
end;


function TATSynEdit.DoCommand_TextInsertColumnBlockOnce(const AText: atString;
  AKeepCaret: boolean): TATCommandResults;
var
  Caret: TATCaretItem;
  Block: TATStrings;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  //cannot handle carets/selections for colblock
  DoCaretSingleAsIs;
  DoSelect_None;
  Caret:= FCarets[0];

  Block:= TATStrings.Create;
  try
    Block.LoadFromString(AText);
    Block.ActionDeleteFakeLine;
    if Block.Count=0 then Exit;

    Strings.TextInsertColumnBlock(Caret.PosX, Caret.PosY, Block, FOverwrite);
    if not AKeepCaret then
      Inc(Caret.PosY, Block.Count-1);
  finally
    FreeAndNil(Block);
  end;

  Result:= [cResultText, cResultCaretBottom];
end;


function TATSynEdit.DoCommand_TextDeleteToFileEnd: TATCommandResults;
var
  Caret: TATCaretItem;
  Str: atString;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  DoCaretSingleAsIs;

  Caret:= FCarets[0];
  Str:= Strings.Lines[Caret.PosY];
  if Caret.PosX<Length(Str) then
  begin
    Delete(Str, Caret.PosX+1, MaxInt);
    Strings.Lines[Caret.PosY]:= Str;
  end;

  for i:= Strings.Count-1 downto Caret.PosY+1 do
    Strings.LineDelete(i);

  if Caret.PosY>=Strings.Count-1 then
    Strings.LinesEnds[Caret.PosY]:= cEndNone;

  Result:= [cResultText, cResultCaretBottom];
end;


function TATSynEdit.DoCommand_TextInsertEmptyAboveBelow(ADown: boolean): TATCommandResults;
var
  Caret: TATCaretItem;
  PosAfter: TPoint;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  DoCaretsDeleteOnSameLines;

  for i:= Carets.Count-1 downto 0 do
  begin
    Caret:= Carets[i];
    Strings.LineInsert(Caret.PosY + IfThen(ADown, 1), '');

    PosAfter.X:= 0;
    PosAfter.Y:= Caret.PosY+IfThen(ADown, 1);

    DoCaretsShift(0, Caret.PosY, 0, 1, PosAfter);

    Caret.PosX:= PosAfter.X;
    Caret.PosY:= PosAfter.Y;
  end;

  Result:= [cResultText, cResultCaretAny];
end;

function TATSynEdit.DoCommand_TextChangeCase(AMode: TATCaseConvert): TATCommandResults;
var
  Caret: TATCaretItem;
  Str1, Str2: atString;
  X1, Y1, X2, Y2: integer;
  Shift, PosAfter: TPoint;
  bSel: boolean;
  i: integer;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  Strings.BeginUndoGroup;
  for i:= Carets.Count-1 downto 0 do
  begin
    Caret:= Carets[i];
    Caret.GetRange(X1, Y1, X2, Y2, bSel);
    if not bSel then
    begin
      SFindWordBounds(Strings.Lines[Caret.PosY], Caret.PosX, X1, X2, FOptWordChars);
      if X1<0 then Continue;
      Y1:= Caret.PosY;
      Y2:= Caret.PosY;
    end;

    Str1:= Strings.TextSubstring(X1, Y1, X2, Y2);
    case AMode of
      cCaseLower: Str2:= UnicodeLowerCase(Str1);
      cCaseUpper: Str2:= UnicodeUpperCase(Str1);
      cCaseTitle: Str2:= SCaseTitle(Str1, FOptWordChars);
      cCaseInvert: Str2:= SCaseInvert(Str1);
      cCaseSentence: Str2:= SCaseSentence(Str1, FOptWordChars);
    end;
    if Str1=Str2 then Continue;

    Strings.TextDeleteRange(X1, Y1, X2, Y2, Shift, PosAfter);
    Strings.TextInsert(X1, Y1, Str2, false, Shift, PosAfter);
  end;
  Strings.EndUndoGroup;

  Result:= [cResultText, cResultCaretAny];
end;


procedure TATSynEdit.DoCommentSelectionLines(Act: TATCommentAction; const AComment: atString);
var
  Caret: TATCaretItem;
  L: TStringList;
  NFrom, NTo, i: integer;
  Shift, PosAfter: TPoint;
  bChange: boolean;
begin
  if Carets.Count=0 then exit;
  Caret:= Carets[0];
  Caret.GetSelLines(NFrom, NTo, true);
  if NFrom<0 then exit;
  if NTo<0 then exit;

  L:= TStringList.Create;
  try
    for i:= NFrom to NTo do
      L.Add(Utf8Encode(Strings.Lines[i]));

    bChange:= SCommentLineAction(L, AComment, Act);
    if not bChange then exit;
    Assert(L.Count=(NTo-NFrom+1), 'DoCommentSel changed line count');

    Strings.BeginUndoGroup;
    try
      for i:= NFrom to NTo do
        Strings.Lines[i]:= Utf8Decode(L[i-NFrom]);
    finally
      Strings.EndUndoGroup;
    end;
  finally
    L.Free;
  end;

  DoEventChange;
  Update(true);
end;

function TATSynEdit.DoCommand_TextTrimSpaces(AMode: TATTrimSpaces
  ): TATCommandResults;
begin
  Result:= [];
  if ModeReadOnly then Exit;

  if Strings.ActionTrimSpaces(AMode) then
    Result:= [cResultCaretAny, cResultText];
end;

