lazarus/components/synedit/test/testsynbeautifier.pas
martin ed4cca60d2 SynEdit: more tests for Beautifier
git-svn-id: trunk@22417 -
2009-11-03 23:33:45 +00:00

846 lines
40 KiB
ObjectPascal

unit TestSynBeautifier;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, fpcunit, testutils, testregistry, TestBase,
SynEdit, SynEditTypes, SynEditTextTrimmer, SynEditKeyCmds, SynBeautifier,
LCLType, LCLProc;
type
TCallBackData = record
LinePos: Integer;
Indent: Integer; // Indent in spaces (Logical = Physical)
RelativeToLinePos: Integer; // Intend specifies +/- offset from intend on RTLine
// 0: for absolute indent
IndentChars: String ; // use the following string to indent; maybe empty, single char, or string
IndentCharsFromLinePos: Integer; // Line for tab/space mix; set to -1 if unknown
end;
{ TTestSynBeautifier }
TTestSynBeautifier = class(TTestBase)
protected
procedure TestRedoUndo(Name: String; Text: Array of String; X, Y: Integer;
Data: Array of const;
SelX: Integer = -1; SelY: Integer = -1);
protected
FGetIndentCallBackResult: Boolean;
FCallBackData: Array of TCallBackData;
FGotLogCaret, FGotOldLogCaret: TPoint;
FGotFirstLinePos, FGotLastLinePos: Integer;
FGotReason: TSynEditorCommand;
function GetIndentCallBack( Sender: TObject; // the beautifier
Editor: TObject; // the synedit
LogCaret, OldLogCaret: TPoint;
FirstLinePos, LastLinePos: Integer;
Reason: TSynEditorCommand; // what caused the evnt
SetIndentProc: TSynBeautifierSetIndentProc
): boolean;
published
(* Test includes:
- no indent if switched off
- indent after VK_RETURN
- indent after CTRL-VK_N (TODO)
* handling split indent (return in the middle of the leading space; next line is already part indented)
* VK_Return on pos=1, keeps indent. line without indent will *not* be indented
* with/without trim spaces
* undo/redo/group-undo
* correct indent from tabs (include split indent)
* correct indent from several lines above (skip empty lines to find indent) VK_RETURN only
* indent modes: sbitSpace, sbitCopySpaceTab, sbitPositionCaret
*)
procedure DefaultIndent;
(*
- no un-indent if switched off
- un-indent after VK_BACK (if at end of leading whitespace)
- no un-indent after VK_BACK (if not at end of leading whitespace)
- act on empty line (only leading whitespace; no chars follow) (with/without trim space)
- unindent after empty line (find indent from lines above)
- handle tabs, in leading whitespace (TODO)
- no unindent, if selection (none persistent)
Todo: decide on none-overwritable block
- TODO: fix group undo
*)
procedure DefaultUnIndent;
procedure IndentCallBack;
end;
implementation
{ TTestSynBeautifier }
var SkipGroupUndo: Boolean;
procedure TTestSynBeautifier.TestRedoUndo(Name: String; Text: Array of String;
X, Y: Integer; Data: array of const; SelX: Integer = -1; SelY: Integer = -1);
function data2txt(n: Integer): String;
begin
if n < 0 then exit(LinesToText(Text));
Result := '';
case data[n].VType of
vtString: Result := data[n].VString^;
vtAnsiString: Result := AnsiString(data[n].VAnsiString);
vtChar: Result := data[n].VChar;
end;
end;
var
i: integer;
st: TShiftState;
begin
PushBaseName(Name + '(no group-undo)');
SetLines(Text);
if (SelX >= 0) then
SetCaretAndSel(SelX, SelY, X, Y)
else
SetCaret(X, Y);
SynEdit.Options := SynEdit.Options - [eoGroupUndo];
for i := 0 to length(data) div 4 - 1 do begin
st := [];
if data[i*4+0].vinteger in [VK_A..VK_Z] then st := [ssCtrl];
DoKeyPress(data[i*4+0].vinteger, st);
TestIsCaret('Key #'+IntToStr(i), data[i*4+1].vinteger, data[i*4+2].vinteger);
TestIsFullText('Key #'+IntToStr(i), data2txt(i*4+3));
end;
for i := length(data) div 4 - 2 downto -1 do begin
SynEdit.Undo;
if i >= 0
then TestIsCaret('Undo #'+IntToStr(i+1), data[i*4+1].vinteger, data[i*4+2].vinteger)
else TestIsCaret('Undo #'+IntToStr(i+1), x, y);
TestIsFullText('Undo #'+IntToStr(i+1), data2txt(i*4+3));
end;
for i := 0 to length(data) div 4 - 1 do begin
SynEdit.Redo;
TestIsCaret('Redo #'+IntToStr(i), data[i*4+1].vinteger, data[i*4+2].vinteger);
TestIsFullText('Redo #'+IntToStr(i), data2txt(i*4+3));
end;
for i := length(data) div 4 - 2 downto -1 do begin
SynEdit.Undo;
if i >= 0
then TestIsCaret('2nd Undo Key #'+IntToStr(i+1), data[i*4+1].vinteger, data[i*4+2].vinteger)
else TestIsCaret('2nd Undo Key #'+IntToStr(i+1), x, y);
TestIsFullText('2nd Undo #'+IntToStr(i+1), data2txt(i*4+3));
end;
if SkipGroupUndo then begin
PopBaseName;
exit;
end;
PopPushBaseName(Name + '(group-undo)');
SetLines(Text);
SetCaret(X, Y);
SynEdit.Options := SynEdit.Options + [eoGroupUndo];
for i := 0 to length(data) div 4 - 1 do begin
st := [];
if data[i*4+0].vinteger in [VK_A..VK_Z] then st := [ssCtrl];
DoKeyPress(data[i*4+0].vinteger, st);
TestIsCaret('Key #'+IntToStr(i), data[i*4+1].vinteger, data[i*4+2].vinteger);
TestIsFullText('Key #'+IntToStr(i), data2txt(i*4+3));
end;
SynEdit.Undo;
TestIsCaret('Undo', x, y);
TestIsFullText('Undo', Text);
SynEdit.Redo;
i := length(data) div 4 - 1;
TestIsCaret('Redo', data[i*4+1].vinteger, data[i*4+2].vinteger);
TestIsFullText('Redo', data2txt(i*4+3));
SynEdit.Undo;
TestIsCaret('2nd Undo', x, y);
TestIsFullText('2nd Undo', Text);
PopBaseName;
end;
procedure TTestSynBeautifier.DefaultIndent;
function TestText: TStringArray;
begin
SetLength(Result, 11);
Result[0] := ' a;';
Result[1] := ' b';
Result[2] := '';
Result[3] := ' c';
Result[5] := '';
Result[6] := '';
Result[7] := #9+'x';
Result[8] := #32#9#32#32#9+'x'; // 8 indent ( 5 logical)
Result[9] := ' c';
Result[10] := '';
end;
function ExpText(rpl: array of const): String;
begin
Result := LinesToText(LinesReplace(TestText, rpl))
end;
function TestText2: TStringArray;
begin
SetLength(Result, 6);
Result[0] := ' a;';
Result[1] := 'b'; // not indent
Result[2] := ' a;';
Result[3] := '';
Result[4] := 'b'; // not indent, after empty line
Result[5] := '';
end;
function ExpText2(rpl: array of const): String;
begin
Result := LinesToText(LinesReplace(TestText2, rpl))
end;
begin
SkipGroupUndo := False;
ReCreateEdit;
SynEdit.TabWidth := 4;
SynEdit.TrimSpaceType := settMoveCaret;
{%region ***** Test WITHOUT indent (no trim) *****}
BaseTestName := 'DefaultIndent (no indent, no trim)';
SynEdit.Options := SynEdit.Options - [eoTrimTrailingSpaces, eoScrollPastEol, eoAutoIndent];
TestRedoUndo('Return EOL', TestText, 5,1, [ VK_RETURN, 1, 2, ExpText([ -2, '']) ] );
TestRedoUndo('Return empty line', TestText, 1,3, [ VK_RETURN, 1, 4, ExpText([ -4, '']) ] );
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 1, 3, ExpText([ 2, ' ', 'b']) ] ); // NOT trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 1, 3, ExpText([ 2, ' ', ' b']) ] ); // NOT trimmed
// VK_N will be ctrl-n (special detection in TestRedoUndo
TestRedoUndo('CTRL-N EOL', TestText, 5,1, [ VK_N, 5, 1, ExpText([ -2, '']) ] );
{%endregion}
{%region ***** Test WITHOUT indent (trim) *****}
BaseTestName := 'DefaultIndent (trim, no indent)';
SynEdit.Options := SynEdit.Options - [eoScrollPastEol, eoAutoIndent] + [eoTrimTrailingSpaces];
TestRedoUndo('Return EOL', TestText, 5,1, [ VK_RETURN, 1, 2, ExpText([ -2, '']) ] );
TestRedoUndo('Return empty line', TestText, 1,3, [ VK_RETURN, 1, 4, ExpText([ -4, '']) ] );
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 1, 3, ExpText([ 2, '', 'b']) ] ); // trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 1, 3, ExpText([ 2, '', ' b']) ] ); // trimmed
{%endregion}
{%region ***** Test WITHOUT trim *****}
{%region sbitSpaces}
BaseTestName := 'DefaultIndent (no trim)';
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitSpace;
SynEdit.Options := SynEdit.Options - [eoTrimTrailingSpaces, eoScrollPastEol, eoTabsToSpaces] + [eoAutoIndent];
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitSpace;
TestRedoUndo('Return EOL', TestText, 5,1, [ VK_RETURN, 3, 2, ExpText([ -2, ' ']) ] );
TestRedoUndo('Return EOL+EOT', TestText, 5,10,[ VK_RETURN, 4,11, ExpText([ -11,' ']) ] );
TestRedoUndo('Return empty line', TestText, 1,3, [ VK_RETURN, 5, 4, ExpText([ -4, ' ']) ] );
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 5, 3, ExpText([ -2, ' ']) ] ); // NOT trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 4, 3, ExpText([ -2, ' ']) ] ); // NOT trimmed
TestRedoUndo('Many Return EOL', TestText, 5,1, [
VK_RETURN, 3, 2, ExpText([ -2, ' ']),
VK_RETURN, 3, 3, ExpText([ -2, ' ', ' ']),
VK_RETURN, 3, 4, ExpText([ -2, ' ', ' ', ' '])
]);
TestRedoUndo('Many Return EOL+EOT', TestText, 5,10, [
VK_RETURN, 4,11, ExpText([ -11, ' ']),
VK_RETURN, 4,12, ExpText([ -11, ' ', ' ']),
VK_RETURN, 4,13, ExpText([ -11, ' ', ' ', ' '])
]);
TestRedoUndo('Many Return empty line', TestText, 1,3, [
VK_RETURN, 5, 4, ExpText([ -4, ' ']),
VK_RETURN, 5, 5, ExpText([ -4, ' ', ' ']),
VK_RETURN, 5, 6, ExpText([ -4, ' ', ' ', ' '])
]);
TestRedoUndo('Many Return after space', TestText, 5,2, [
VK_RETURN, 5, 3, ExpText([ -2, ' ']), // NOT trimmed
VK_RETURN, 5, 4, ExpText([ -2, ' ', ' ']), // NOT trimmed
VK_RETURN, 5, 5, ExpText([ -2, ' ', ' ', ' ']) // NOT trimmed
]);
TestRedoUndo('Many Return before space', TestText, 1,2, [
VK_RETURN, 1, 3, ExpText([ -2, '']),
VK_RETURN, 1, 4, ExpText([ -2, '', '']),
VK_RETURN, 1, 5, ExpText([ -2, '', '', ''])
]);
TestRedoUndo('Many Return mid space', TestText, 4,2, [
VK_RETURN, 4, 3, ExpText([ -2, ' ']), // NOT trimmed
VK_RETURN, 4, 4, ExpText([ -2, ' ', ' ']), // NOT trimmed
VK_RETURN, 4, 5, ExpText([ -2, ' ', ' ', ' ']) // NOT trimmed
]);
TestRedoUndo('Return multi empty', TestText, 1,7, [ VK_RETURN, 4, 8, ExpText([ -8, ' ']) ] );
TestRedoUndo('Return at pos1 of unindeted', TestText2, 1,2, [ VK_RETURN, 1, 3, ExpText2([ -2, '']) ] );
TestRedoUndo('Return at pos1 of unindeted 2', TestText2, 1,5, [ VK_RETURN, 1, 6, ExpText2([ -5, '']) ] );
// caret pos are logical
TestRedoUndo('Return after tab', TestText, 6,9, [ VK_RETURN, 9,10, ExpText([ 9, #32#9#32#32#9, ' x']) ] );
TestRedoUndo('Return before tab', TestText, 1,9, [ VK_RETURN, 1,10, ExpText([-9, '']) ] );
TestRedoUndo('Return middle tab 1', TestText, 3,9, [ VK_RETURN, 5,10, ExpText([ 9, #32#9, ' '+#32#32#9+'x']) ] );
TestRedoUndo('Return middle tab 2', TestText, 4,9, [ VK_RETURN, 6,10, ExpText([ 9, #32#9#32, ' '+#32#9+'x']) ] );
TestRedoUndo('Return tab EOL', TestText, 7,9, [ VK_RETURN, 9,10, ExpText([-10, ' ']) ] );
{%endregion}
(* *** *)
{%region sbitCopySpaceTab}
PushBaseName('CopySpaceTab');
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitCopySpaceTab;
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 5, 3, ExpText([ -2, ' ']) ] ); // NOT trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 4, 3, ExpText([ -2, ' ']) ] ); // NOT trimmed
TestRedoUndo('Return after tab', TestText, 6,9, [ VK_RETURN, 6,10, ExpText([ 9, #32#9#32#32#9, #32#9#32#32#9+'x']) ] );
TestRedoUndo('Return before tab', TestText, 1,9, [ VK_RETURN, 1,10, ExpText([-9, '']) ] );
TestRedoUndo('Return middle tab 1', TestText, 3,9, [ VK_RETURN, 3,10, ExpText([ 9, #32#9, #32#9#32#32#9+'x']) ] );
TestRedoUndo('Return middle tab 2', TestText, 4,9, [ VK_RETURN, 4,10, ExpText([ 9, #32#9#32, #32#9#32#32#9+'x']) ] );
TestRedoUndo('Return tab EOL', TestText, 7,9, [ VK_RETURN, 6,10, ExpText([-10, #32#9#32#32#9]) ] );
TestRedoUndo('Many Return tab EOL', TestText, 7,9, [
VK_RETURN, 6,10, ExpText([-10, #32#9#32#32#9]),
VK_RETURN, 6,11, ExpText([-10, #32#9#32#32#9, #32#9#32#32#9]),
VK_RETURN, 6,12, ExpText([-10, #32#9#32#32#9, #32#9#32#32#9, #32#9#32#32#9])
]);
{%endregion}
(* *** *)
{%region sbitPosCaret}
PopPushBaseName('PosCaret');
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitPositionCaret;
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 5, 3, ExpText([ -2, ' ']) ] ); // NOT trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 4, 3, ExpText([ -2, ' ']) ] ); // NOT trimmed
TestRedoUndo('Return eol', TestText, 6,2, [ VK_RETURN, 5, 3, ExpText([ -3, '']) ] ); // position only
TestRedoUndo('Many Return eol', TestText, 6,2, [ // position only
VK_RETURN, 5, 3, ExpText([ -3, '']),
VK_RETURN, 5, 4, ExpText([ -3, '', '']),
VK_RETURN, 5, 5, ExpText([ -3, '', '', ''])
]);
TestRedoUndo('Return after tab', TestText, 6,9, [ VK_RETURN, 9,10, ExpText([ 9, #32#9#32#32#9, ' '+'x']) ] );
TestRedoUndo('Return before tab', TestText, 1,9, [ VK_RETURN, 1,10, ExpText([-9, '']) ] );
TestRedoUndo('Return middle tab 1', TestText, 3,9, [ VK_RETURN, 5,10, ExpText([ 9, #32#9, ' '+#32#32#9+'x']) ] );
TestRedoUndo('Return middle tab 2', TestText, 4,9, [ VK_RETURN, 6,10, ExpText([ 9, #32#9#32, ' '+#32#9+'x']) ] );
TestRedoUndo('Return tab EOL', TestText, 7,9, [ VK_RETURN, 9,10, ExpText([-10, '']) ] ); // pos only
TestRedoUndo('Many Return tab EOL', TestText, 7,9, [
VK_RETURN, 9,10, ExpText([-10, '']), // pos only
VK_RETURN, 9,11, ExpText([-10, '', '']),
VK_RETURN, 9,12, ExpText([-10, '', '', ''])
]);
{%endregion}
{%region CTRL-N}
PopPushBaseName('SpaceOnly');
// VK_N will be ctrl-n (special detection in TestRedoUndo
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitSpace;
TestRedoUndo('CTRL-N EOL', TestText, 5,1, [ VK_N, 5, 1, ExpText([ -2, ' ']) ] );
TestRedoUndo('Many CTRL-N EOL', TestText, 5,1, [
VK_N, 5, 1, ExpText([ -2, ' ']),
VK_N, 5, 1, ExpText([ -2, ' ', ' ']),
VK_N, 5, 1, ExpText([ -2, ' ', ' ', ' '])
]);
TestRedoUndo('CTRL-N empty line', TestText, 1,3, [ VK_N, 1, 3, ExpText([ -4, ' ']) ] ); // TODO: should it?
TestRedoUndo('CTRL-N pos1 unindeted',TestText2, 1,2, [ VK_N, 1, 2, ExpText2([ -2, '']) ] ); // TODO: must not
TestRedoUndo('CTRL-N after space', TestText, 5,2, [ VK_N, 5, 2, ExpText([ -2, ' ']) ] ); // NOT trimmed
TestRedoUndo('CTRL-N before space', TestText, 1,2, [ VK_N, 1, 2, ExpText([ -2, '']) ] ); // TODO: indents too much
TestRedoUndo('CTRL-N mid space', TestText, 4,2, [ VK_N, 4, 2, ExpText([ -2, ' ']) ] ); // NOT trimmed
PopPushBaseName('CopySpacetab');
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitCopySpaceTab;
TestRedoUndo('CTRL-N EOL', TestText, 5,1, [ VK_N, 5, 1, ExpText([ -2, ' ']) ] );
PopPushBaseName('PosOnly');
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitPositionCaret;
TestRedoUndo('CTRL-N EOL', TestText, 5,1, [ VK_N, 5, 1, ExpText([ -2, '']) ] );
{%endregion}
{%endregion}
{%region ***** Test WITH trim *****}
{%region sbitSpaces}
BaseTestName := 'DefaultIndent (trim)';
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitSpace;
SynEdit.Options := SynEdit.Options - [eoScrollPastEol] + [eoTrimTrailingSpaces, eoAutoIndent];
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitSpace; //, sbitCopySpaceTab, sbitPositionCaret
TestRedoUndo('Return EOL', TestText, 5,1, [ VK_RETURN, 3, 2, ExpText([ -2, ' ']) ] );
TestRedoUndo('Return EOL+EOT', TestText, 5,10,[ VK_RETURN, 4,11, ExpText([ -11,' ']) ] );
TestRedoUndo('Return empty line', TestText, 1,3, [ VK_RETURN, 5, 4, ExpText([ -4, ' ']) ] );
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 5, 3, ExpText([ -2, '']) ] ); // trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 4, 3, ExpText([ -2, '']) ] ); // trimmed
TestRedoUndo('Many Return EOL', TestText, 5,1, [
VK_RETURN, 3, 2, ExpText([ -2, ' ']),
VK_RETURN, 3, 3, ExpText([ -2, '', ' ']), // trimmed
VK_RETURN, 3, 4, ExpText([ -2, '', '', ' '])
]);
TestRedoUndo('Many Return EOL+EOT', TestText, 5,10, [
VK_RETURN, 4,11, ExpText([ -11, ' ']),
VK_RETURN, 4,12, ExpText([ -11, '', ' ']), // trimmed
VK_RETURN, 4,13, ExpText([ -11, '', '', ' '])
]);
TestRedoUndo('Many Return empty line', TestText, 1,3, [
VK_RETURN, 5, 4, ExpText([ -4, ' ']),
VK_RETURN, 5, 5, ExpText([ -4, '', ' ']), // trimmed
VK_RETURN, 5, 6, ExpText([ -4, '', '', ' '])
]);
TestRedoUndo('Many Return after space', TestText, 5,2, [
VK_RETURN, 5, 3, ExpText([ -2, '']), // trimmed
VK_RETURN, 5, 4, ExpText([ -2, '', '']), // trimmed
VK_RETURN, 5, 5, ExpText([ -2, '', '', '']) // trimmed
]);
TestRedoUndo('Many Return before space', TestText, 1,2, [
VK_RETURN, 1, 3, ExpText([ -2, '']),
VK_RETURN, 1, 4, ExpText([ -2, '', '']),
VK_RETURN, 1, 5, ExpText([ -2, '', '', ''])
]);
TestRedoUndo('Many Return mid space', TestText, 4,2, [
VK_RETURN, 4, 3, ExpText([ -2, '']), // trimmed
VK_RETURN, 4, 4, ExpText([ -2, '', '']), // trimmed
VK_RETURN, 4, 5, ExpText([ -2, '', '', '']) // trimmed
]);
TestRedoUndo('Return multi empty', TestText, 1,7, [ VK_RETURN, 4, 8, ExpText([ -8, ' ']) ] );
TestRedoUndo('Return at pos1 of unindeted', TestText2, 1,2, [ VK_RETURN, 1, 3, ExpText2([ -2, '']) ] );
TestRedoUndo('Return at pos1 of unindeted 2', TestText2, 1,5, [ VK_RETURN, 1, 6, ExpText2([ -5, '']) ] );
// caret pos are logical
TestRedoUndo('Return after tab', TestText, 6,9, [ VK_RETURN, 9,10, ExpText([ 9, '', ' x']) ] );
TestRedoUndo('Return before tab', TestText, 1,9, [ VK_RETURN, 1,10, ExpText([-9, '']) ] );
TestRedoUndo('Return middle tab 1', TestText, 3,9, [ VK_RETURN, 5,10, ExpText([ 9, '', ' '+#32#32#9+'x']) ] );
TestRedoUndo('Return middle tab 2', TestText, 4,9, [ VK_RETURN, 6,10, ExpText([ 9, '', ' '+#32#9+'x']) ] );
TestRedoUndo('Return tab EOL', TestText, 7,9, [ VK_RETURN, 9,10, ExpText([-10, ' ']) ] );
{%endregion}
(* *** *)
{%region sbitCopySpaceTab}
PushBaseName('CopySpaceTab');
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitCopySpaceTab;
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 5, 3, ExpText([ -2, '']) ] ); // trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 4, 3, ExpText([ -2, '']) ] ); // trimmed
TestRedoUndo('Return after tab', TestText, 6,9, [ VK_RETURN, 6,10, ExpText([ 9, '', #32#9#32#32#9+'x']) ] );
TestRedoUndo('Return before tab', TestText, 1,9, [ VK_RETURN, 1,10, ExpText([-9, '']) ] );
TestRedoUndo('Return middle tab 1', TestText, 3,9, [ VK_RETURN, 3,10, ExpText([ 9, '', #32#9#32#32#9+'x']) ] );
TestRedoUndo('Return middle tab 2', TestText, 4,9, [ VK_RETURN, 4,10, ExpText([ 9, '', #32#9#32#32#9+'x']) ] );
TestRedoUndo('Return tab EOL', TestText, 7,9, [ VK_RETURN, 6,10, ExpText([-10, #32#9#32#32#9]) ] );
TestRedoUndo('Many Return tab EOL', TestText, 7,9, [
VK_RETURN, 6,10, ExpText([-10, #32#9#32#32#9]),
VK_RETURN, 6,11, ExpText([-10, '', #32#9#32#32#9]),
VK_RETURN, 6,12, ExpText([-10, '', '', #32#9#32#32#9])
]);
{%endregion}
(* *** *)
{%region sbitPosCaret}
PopPushBaseName('PosCaret');
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitPositionCaret;
TestRedoUndo('Return after space', TestText, 5,2, [ VK_RETURN, 5, 3, ExpText([ -2, '']) ] ); // trimmed
TestRedoUndo('Return before space', TestText, 1,2, [ VK_RETURN, 1, 3, ExpText([ -2, '']) ] );
TestRedoUndo('Return mid space', TestText, 4,2, [ VK_RETURN, 4, 3, ExpText([ -2, '']) ] ); //trimmed
TestRedoUndo('Return eol', TestText, 6,2, [ VK_RETURN, 5, 3, ExpText([ -3, '']) ] ); // position only
TestRedoUndo('Many Return eol', TestText, 6,2, [ // position only
VK_RETURN, 5, 3, ExpText([ -3, '']),
VK_RETURN, 5, 4, ExpText([ -3, '', '']),
VK_RETURN, 5, 5, ExpText([ -3, '', '', ''])
]);
TestRedoUndo('Return after tab', TestText, 6,9, [ VK_RETURN, 9,10, ExpText([ 9, '', ' '+'x']) ] );
TestRedoUndo('Return before tab', TestText, 1,9, [ VK_RETURN, 1,10, ExpText([-9, '']) ] );
TestRedoUndo('Return middle tab 1', TestText, 3,9, [ VK_RETURN, 5,10, ExpText([ 9, '', ' '+#32#32#9+'x']) ] );
TestRedoUndo('Return middle tab 2', TestText, 4,9, [ VK_RETURN, 6,10, ExpText([ 9, '', ' '+#32#9+'x']) ] );
TestRedoUndo('Return tab EOL', TestText, 7,9, [ VK_RETURN, 9,10, ExpText([-10, '']) ] ); // pos only
TestRedoUndo('Many Return tab EOL', TestText, 7,9, [
VK_RETURN, 9,10, ExpText([-10, '']), // pos only
VK_RETURN, 9,11, ExpText([-10, '', '']),
VK_RETURN, 9,12, ExpText([-10, '', '', ''])
]);
{%endregion}
{%region CTRL-N}
PopPushBaseName('SpaceOnly');
// VK_N will be ctrl-n (special detection in TestRedoUndo
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitSpace;
TestRedoUndo('CTRL-N EOL', TestText, 5,1, [ VK_N, 5, 1, ExpText([ -2, '']) ] ); // trimmed
TestRedoUndo('CTRL-N pos1 unindeted',TestText2, 1,2, [ VK_N, 1, 2, ExpText2([ -2, '']) ] ); // TODO: must not
TestRedoUndo('CTRL-N after space', TestText, 5,2, [ VK_N, 5, 2, ExpText([ -2, ' ']) ] );
TestRedoUndo('CTRL-N before space', TestText, 1,2, [ VK_N, 1, 2, ExpText([ -2, '']) ] ); // TODO: indents too much
TestRedoUndo('CTRL-N mid space', TestText, 4,2, [ VK_N, 4, 2, ExpText([ -2, ' ']) ] );
{%endregion}
{%endregion}
end;
procedure TTestSynBeautifier.DefaultUnIndent;
function TestText: TStringArray;
begin
SetLength(Result, 11);
Result[0] := ' a;';
Result[1] := ' b';
Result[2] := ' c';
Result[3] := '';
Result[4] := ' d';
Result[5] := '';
Result[6] := ' x';
Result[7] := '';
Result[8] := ' y';
Result[9] := ' z';
Result[10]:= '';
end;
function ExpText(rpl: array of const): String;
begin
Result := LinesToText(LinesReplace(TestText, rpl))
end;
begin
SkipGroupUndo := True; // Todo
ReCreateEdit;
SynEdit.TabWidth := 4;
SynEdit.TrimSpaceType := settMoveCaret;
BaseTestName := 'UnIndent pff';
SynEdit.Options := SynEdit.Options - [eoTrimTrailingSpaces, eoScrollPastEol, eoAutoIndent];
SynEdit.Options2 := SynEdit.Options2 - [eoPersistentBlock];
TestRedoUndo('simple', TestText, 5,2, [ VK_BACK, 4,2, ExpText([ 2, ' b']) ] );
TestRedoUndo('not at indent-end', TestText, 4,2, [ VK_BACK, 3,2, ExpText([ 2, ' b']) ] );
BaseTestName := 'UnIndent';
SynEdit.Options := SynEdit.Options + [eoAutoIndent] - [eoTrimTrailingSpaces, eoScrollPastEol];
TestRedoUndo('simple', TestText, 5,2, [ VK_BACK, 3,2, ExpText([ 2, ' b']) ] );
TestRedoUndo('simple twice', TestText, 5,2, [
VK_BACK, 3,2, ExpText([ 2, ' b']),
VK_BACK, 1,2, ExpText([ 2, 'b'])
]);
TestRedoUndo('not at indent-end', TestText, 4,2, [ VK_BACK, 3,2, ExpText([ 2, ' b']) ] );
TestRedoUndo('2 level', TestText, 7,3, [
VK_BACK, 5,3, ExpText([ 3, ' c']),
VK_BACK, 3,3, ExpText([ 3, ' c']),
VK_BACK, 1,3, ExpText([ 3, 'c'])
]);
TestRedoUndo('2 lvl, not indent-end', TestText, 6,3, [ VK_BACK, 5,3, ExpText([ 3, ' c']) ] );
TestRedoUndo('2 lvl, not indent-end 2', TestText, 5,3, [ VK_BACK, 4,3, ExpText([ 3, ' c']) ] );
TestRedoUndo('below empty line', TestText, 7,5, [ VK_BACK, 5,5, ExpText([ 5, ' d']) ] );
TestRedoUndo('below empty, not I-End', TestText, 6,5, [ VK_BACK, 5,5, ExpText([ 5, ' d']) ] );
TestRedoUndo('below empty, not I-End 2',TestText, 5,5, [ VK_BACK, 4,5, ExpText([ 5, ' d']) ] );
TestRedoUndo('below empty line, many', TestText, 7,5, [
VK_BACK, 5,5, ExpText([ 5, ' d']),
VK_BACK, 3,5, ExpText([ 5, ' d']),
VK_BACK, 1,5, ExpText([ 5, 'd'])
]);
TestRedoUndo('unindent single space', TestText, 6,10, [ VK_BACK, 5,10, ExpText([ 10, ' z']) ] );
TestRedoUndo('empty line in many', TestText, 6,10, [
VK_BACK, 5,10, ExpText([ 10, ' z']),
VK_BACK, 3,10, ExpText([ 10, ' z']),
VK_BACK, 1,10, ExpText([ 10, 'z'])
]);
SynEdit.Options := SynEdit.Options - [eoTrimTrailingSpaces];
TestRedoUndo('only indent, no text', TestText, 8,3, [
VK_BACK, 7,3, ExpText([ 3, ' ']),
VK_BACK, 5,3, ExpText([ 3, ' ']),
VK_BACK, 3,3, ExpText([ 3, ' ']),
VK_BACK, 1,3, ExpText([ 3, ''])
]);
SynEdit.Options := SynEdit.Options + [eoTrimTrailingSpaces];
TestRedoUndo('only indent, no text (trim)', TestText, 8,3, [
VK_BACK, 7,3, ExpText([ 3, ' ']),
VK_BACK, 5,3, ExpText([ 3, ' ']),
VK_BACK, 3,3, ExpText([ 3, ' ']),
VK_BACK, 1,3, ExpText([ 3, ''])
]);
SynEdit.Options := SynEdit.Options - [eoTrimTrailingSpaces];
TestRedoUndo('no unindent (selection)', TestText, 5,2, [ VK_BACK, 4,2, ExpText([ 2, ' b']) ], 4,2);
TestRedoUndo('no unindent (selection)', TestText, 5,2, [ VK_BACK, 1,2, ExpText([ 2, 'b']) ], 1,2);
SynEdit.Options2 := SynEdit.Options2 + [eoPersistentBlock];
TestRedoUndo('unindent (persist selection)', TestText, 5,2, [ VK_BACK, 3,2, ExpText([ 2, ' b']) ], 4,2);
TestRedoUndo('unindent (persist selection)', TestText, 5,2, [ VK_BACK, 3,2, ExpText([ 2, ' b']) ], 1,2);
SynEdit.Options2 := SynEdit.Options2 - [eoPersistentBlock];
end;
function TTestSynBeautifier.GetIndentCallBack(Sender: TObject; Editor: TObject;
LogCaret, OldLogCaret: TPoint; FirstLinePos, LastLinePos: Integer; Reason: TSynEditorCommand;
SetIndentProc: TSynBeautifierSetIndentProc): boolean;
var
i: integer;
begin
FGotLogCaret := LogCaret;
FGotOldLogCaret := OldLogCaret;
FGotFirstLinePos := FirstLinePos;
FGotLastLinePos := LastLinePos;
FGotReason := Reason;
for i := 0 to high(FCallBackData) do
SetIndentProc( FCallBackData[i].LinePos,
FCallBackData[i].Indent,
FCallBackData[i].RelativeToLinePos,
FCallBackData[i].IndentChars,
FCallBackData[i].IndentCharsFromLinePos
);
Result := FGetIndentCallBackResult;
end;
procedure TTestSynBeautifier.IndentCallBack;
function TestText: TStringArray;
begin
SetLength(Result, 13);
Result[0] := ' a;';
Result[1] := #9+'b';
Result[2] := ' c';
Result[3] := '';
Result[4] := #32#9+' d';
Result[5] := '';
Result[6] := ' x';
Result[7] := '';
Result[8] := ' y';
Result[9] := ' z';
Result[10]:= ' mn';
Result[11]:= ' op';
Result[12]:= '';
end;
function ExpText(rpl: array of const): String;
begin
Result := LinesToText(LinesReplace(TestText, rpl))
end;
procedure SetCB(Res: Boolean; Action: array of const);
var i: integer;
begin
FGetIndentCallBackResult := Res;
FGotLogCaret := Point(-3, -33);
FGotOldLogCaret := Point(-3, -33);
FGotFirstLinePos := -99;
FGotLastLinePos := -99;
FGotReason := 27997;
SetLength(FCallBackData, length(Action) div 5);
for i := 0 to length(Action) div 5 - 1 do begin
FCallBackData[i].LinePos := Action[i*5 + 0].VInteger;
FCallBackData[i].Indent := Action[i*5 + 1].VInteger;
FCallBackData[i].RelativeToLinePos := Action[i*5 + 2].VInteger;
case Action[i*5 + 3].VType of
vtString: FCallBackData[i].IndentChars := AnsiString(Action[i*5 + 3].VString);
vtAnsiString: FCallBackData[i].IndentChars := AnsiString(Action[i*5 + 3].VAnsiString);
vtChar: FCallBackData[i].IndentChars := Action[i*5 + 3].VChar;
end;
FCallBackData[i].IndentCharsFromLinePos := Action[i*5 + 4].VInteger;
end;
end;
procedure TestCbArgs(Name: String; OldX, OldY, NewX, NewY, FirstLine, LastLine, Reason: integer);
begin
AssertEquals('CB-Args: Got Caret.x Before', OldX, FGotOldLogCaret.x);
AssertEquals('CB-Args: Got Caret.y Before', OldY, FGotOldLogCaret.y);
AssertEquals('CB-Args: Got Caret.x After', NewX, FGotLogCaret.x); // x=1 => before auto indent
AssertEquals('CB-Args: Got Caret.y After', NewY, FGotLogCaret.y);
AssertEquals('CB-Args: FirstLine', FirstLine, FGotFirstLinePos);
AssertEquals('CB-Args: LastLine', LastLine, FGotLastLinePos);
AssertEquals('CB-Args: Reason', Reason, FGotReason);
end;
begin
ReCreateEdit;
try
SynEdit.Beautifier.OnGetDesiredIndent := @GetIndentCallBack;
SynEdit.TabWidth := 4;
SynEdit.TrimSpaceType := settMoveCaret;
BaseTestName := 'Callback (no trim)';
SynEdit.Options := SynEdit.Options + [eoAutoIndent] - [eoTrimTrailingSpaces, eoScrollPastEol];
TSynBeautifier(SynEdit.Beautifier).IndentType := sbitSpace; // sbitCopySpaceTab;
SetCB(False, []);
TestRedoUndo('cb result = false', TestText, 3,2, [ VK_RETURN, 5,3, ExpText([ -3, ' ']) ] );
TestCbArgs('cb result = false', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, []);
TestRedoUndo('cb result = true ', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ -3, '']) ] );
TestCbArgs('cb result = true', 3,2, 1,3, 3,3, ecLineBreak);
// LinPos, Indend, RelativeLinePos=0, Char(s), indentFromLine=-1,
SetCB(True, [2, 2, 0, '', -1,
3, 3, 0, '', -1
]);
TestRedoUndo('cb absolute', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ 2, ' b', ' ']) ] ); // caret is NOT adjusted
TestCbArgs('cb absolute', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, [2, 3, 0, '', -1,
3, 3, 0, '', 2,
4, 2, 0, '', -1
]);
TestRedoUndo('cb absolute 2', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ 2, 2, ' b', ' ', ' c']) ] );
TestCbArgs('cb absolute 2', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, [2, 7, 0, '', 2,
3, 6, 0, '', 2
]);
TestRedoUndo('cb absolute, keep-cur', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ 2, #9+' b', #9+' ']) ] );
TestCbArgs('cb absolute, keep-cur', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, [2, -2, 2, '', -1,
3, 3, 2, '', -1
]);
TestRedoUndo('cb relative ', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ 2, ' b', ' ']) ] );
TestCbArgs('cb relative ', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, [2, 3, 2, '', -1,
3, -1, 2, '', -1
]);
TestRedoUndo('cb relative 2', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ 2, ' b', ' ']) ] );
TestCbArgs('cb relative 2', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, [2, 3, 2, '', -1,
3, 1, 3, '', -1
]);
TestRedoUndo('cb relative 3', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ 2, ' b', ' ']) ] );
TestCbArgs('cb relative 3', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, [2, 3, 2, '', 2,
3, 0, 2, '', 2
]);
TestRedoUndo('cb relative, keep', TestText, 3,2, [ VK_RETURN, 1,3, ExpText([ 2, #9+' b', #9+' ']) ] );
TestCbArgs('cb relative, keep', 3,2, 1,3, 3,3, ecLineBreak);
SetCB(True, [1, 9, 0, #9, -1]);
TestRedoUndo('cb abs; char=tab', TestText, 5,1, [ VK_RETURN, 1,2, ExpText([ 1, #9#9+' a;', '']) ] );
TestCbArgs('cb abs; char=tab', 5,1, 1,2, 2,2, ecLineBreak);
(* Paste *)
ClipBoardText := '1';
SetCB(True, []);
TestRedoUndo('paste 1 lines, noaction', TestText, 5,11, [ VK_V, 6,11, ExpText([ 11, ' mn1']) ] );
TestCbArgs('paste 1 lines, noaction', 5,11, 6,11, 11,11, ecPaste);
ClipBoardText := '1'+LineEnding+'2';
SetCB(True, []);
TestRedoUndo('paste 2 lines, noaction', TestText, 5,11, [ VK_V, 2,12, ExpText([ 11, ' mn1', '2']) ] );
TestCbArgs('paste 2 lines, noaction', 5,11, 2,12, 11,12, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3';
SetCB(True, []);
TestRedoUndo('paste 3 lines, noaction', TestText, 5,11, [ VK_V, 2,13, ExpText([ 11, ' mn1', '2', '3']) ] );
TestCbArgs('paste 3 lines, noaction', 5,11, 2,13, 11,13, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4';
SetCB(True, []);
TestRedoUndo('paste 4 lines, noaction', TestText, 5,11, [ VK_V, 2,14, ExpText([ 11, ' mn1', '2', '3', '4']) ] );
TestCbArgs('paste 4 lines, noaction', 5,11, 2,14, 11,14, ecPaste);
//ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4'+LineEnding;
//SetCB(True, []);
//TestRedoUndo('paste 4+ lines, noaction', TestText, 5,11, [ VK_V, 1,15, ExpText([ 11, ' mn1', '2', '3', '4', '']) ] );
//TestCbArgs('paste 4+ lines, noaction', 5,11, 1,15, 11,15, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4';
SetCB(True, []);
TestRedoUndo('paste 4 lines on empty, noaction', TestText, 1,8, [ VK_V, 2,11, ExpText([ 8, '1', '2', '3', '4']) ] );
TestCbArgs('paste 4 lines on empty, noaction', 1,8, 2,11, 8,11, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4'+LineEnding +'5';
SetCB(True, []);
TestRedoUndo('paste 5 lines, noaction', TestText, 5,11, [ VK_V, 2,15, ExpText([ 11, ' mn1', '2', '3', '4', '5']) ] );
TestCbArgs('paste 5 lines, noaction', 5,11, 2,15, 11,15, ecPaste);
ClipBoardText := '1'+LineEnding+'2';
SetCB(True, []);
TestRedoUndo('paste 2 lines middle, noaction', TestText, 4,11, [ VK_V, 2,12, ExpText([ 11, ' m1', '2n']) ] );
TestCbArgs('paste 2 lines middle, noaction', 4,11, 2,12, 11,12, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3';
SetCB(True, []);
TestRedoUndo('paste 3 lines middle, noaction', TestText, 4,11, [ VK_V, 2,13, ExpText([ 11, ' m1', '2', '3n']) ] );
TestCbArgs('paste 3 lines middle, noaction', 4,11, 2,13, 11,13, ecPaste);
BaseTestName := 'Callback (trim)';
SynEdit.Options := SynEdit.Options + [eoTrimTrailingSpaces, eoAutoIndent] - [eoScrollPastEol];
(* Paste *)
ClipBoardText := '1';
SetCB(True, []);
TestRedoUndo('paste 1 lines, noaction', TestText, 5,11, [ VK_V, 6,11, ExpText([ 11, ' mn1']) ] );
TestCbArgs('paste 1 lines, noaction', 5,11, 6,11, 11,11, ecPaste);
ClipBoardText := '1'+LineEnding+'2';
SetCB(True, []);
TestRedoUndo('paste 2 lines, noaction', TestText, 5,11, [ VK_V, 2,12, ExpText([ 11, ' mn1', '2']) ] );
TestCbArgs('paste 2 lines, noaction', 5,11, 2,12, 11,12, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3';
SetCB(True, []);
TestRedoUndo('paste 3 lines, noaction', TestText, 5,11, [ VK_V, 2,13, ExpText([ 11, ' mn1', '2', '3']) ] );
TestCbArgs('paste 3 lines, noaction', 5,11, 2,13, 11,13, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4';
SetCB(True, []);
TestRedoUndo('paste 4 lines, noaction', TestText, 5,11, [ VK_V, 2,14, ExpText([ 11, ' mn1', '2', '3', '4']) ] );
TestCbArgs('paste 4 lines, noaction', 5,11, 2,14, 11,14, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4'+LineEnding;
SetCB(True, []);
TestRedoUndo('paste 4+ lines, noaction', TestText, 5,11, [ VK_V, 1,15, ExpText([ 11, ' mn1', '2', '3', '4', '']) ] );
TestCbArgs('paste 4+ lines, noaction', 5,11, 1,15, 11,15, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4';
SetCB(True, []);
TestRedoUndo('paste 4 lines on empty, noaction', TestText, 1,8, [ VK_V, 2,11, ExpText([ 8, '1', '2', '3', '4']) ] );
TestCbArgs('paste 4 lines on empty, noaction', 1,8, 2,11, 8,11, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3'+LineEnding+'4'+LineEnding +'5';
SetCB(True, []);
TestRedoUndo('paste 5 lines, noaction', TestText, 5,11, [ VK_V, 2,15, ExpText([ 11, ' mn1', '2', '3', '4', '5']) ] );
TestCbArgs('paste 5 lines, noaction', 5,11, 2,15, 11,15, ecPaste);
ClipBoardText := '1'+LineEnding+'2';
SetCB(True, []);
TestRedoUndo('paste 2 lines middle, noaction', TestText, 4,11, [ VK_V, 2,12, ExpText([ 11, ' m1', '2n']) ] );
TestCbArgs('paste 2 lines middle, noaction', 4,11, 2,12, 11,12, ecPaste);
ClipBoardText := '1'+LineEnding+'2'+LineEnding+'3';
SetCB(True, []);
TestRedoUndo('paste 3 lines middle, noaction', TestText, 4,11, [ VK_V, 2,13, ExpText([ 11, ' m1', '2', '3n']) ] );
TestCbArgs('paste 3 lines middle, noaction', 4,11, 2,13, 11,13, ecPaste);
finally
SynEdit.Beautifier.OnGetDesiredIndent := nil;
end;
end;
initialization
RegisterTest(TTestSynBeautifier);
end.