1 unit TestMarkupHighAll;
2 
3 {$mode objfpc}{$H+}
4 
5 interface
6 
7 uses
8   Classes, SysUtils, testregistry, TestBase, LCLProc, Controls,
9   Graphics, SynEdit, SynEditMarkupHighAll;
10 
11 type
12 
13   { TTestMarkupHighAll }
14 
15   TTestMarkupHighAll = class(TTestBase)
16   private
17     FMatchList: Array of record
18         p: PChar;
19         l: Integer;
20       end;
21     FStopAtMatch, FNoMatchCount: Integer;
22     procedure DoDictMatch(Match: PChar; MatchIdx: Integer; var IsMatch: Boolean;
23       var StopSeach: Boolean);
24   protected
25     //procedure SetUp; override;
26     //procedure TearDown; override;
27     //procedure ReCreateEdit; reintroduce;
TestText1null28     //function TestText1: TStringArray;
29   published
30     procedure TestDictionary;
31     procedure TestValidateMatches;
32   end;
33 
34 implementation
35 
36   type
37 
38     { TTestSynEditMarkupHighlightAllMulti }
39 
40     TTestSynEditMarkupHighlightAllMulti = class(TSynEditMarkupHighlightAllMulti)
41     private
42       FScannedLineCount: Integer;
43     protected
FindMatchesnull44       function FindMatches(AStartPoint, AEndPoint: TPoint; var AIndex: Integer;
45         AStopAfterLine: Integer = - 1; ABackward: Boolean = False): TPoint; override;
46     public
47       procedure ResetScannedCount;
48       property Matches;
49       property ScannedLineCount: Integer read FScannedLineCount;
50     end;
51 
52 { TTestSynEditMarkupHighlightAllMulti }
53 
TTestSynEditMarkupHighlightAllMulti.FindMatchesnull54 function TTestSynEditMarkupHighlightAllMulti.FindMatches(AStartPoint, AEndPoint: TPoint;
55   var AIndex: Integer; AStopAfterLine: Integer; ABackward: Boolean): TPoint;
56 begin
57   FScannedLineCount := FScannedLineCount + AEndPoint.y - AStartPoint.y + 1;
58   Result := inherited FindMatches(AStartPoint, AEndPoint, AIndex, AStopAfterLine, ABackward);
59 end;
60 
61 procedure TTestSynEditMarkupHighlightAllMulti.ResetScannedCount;
62 begin
63   FScannedLineCount := 0;
64 end;
65 
66 
67 { TTestMarkupHighAll }
68 
69 procedure TTestMarkupHighAll.DoDictMatch(Match: PChar; MatchIdx: Integer;
70   var IsMatch: Boolean; var StopSeach: Boolean);
71 var
72   i: Integer;
73 begin
74   i := length(FMatchList);
75   SetLength(FMatchList, i+1);
76 DebugLn([copy(Match, 1, MatchIdx)]);
77   FMatchList[i].p := Match;
78   FMatchList[i].l := MatchIdx;
79   StopSeach := FStopAtMatch <> 0;
80   IsMatch   := FNoMatchCount <= 0;
81   dec(FStopAtMatch);
82   dec(FNoMatchCount);
83 end;
84 
85 procedure TTestMarkupHighAll.TestDictionary;
86 var
87   Dict: TSynSearchDictionary;
88   Name, LineText: String;
89   Res1, Res2: PChar;
90 
91   procedure InitTest(AName, ALineText: String; AStopAtMatch: Integer = -1; ANoMatchCount: Integer = 0);
92   begin
93     Name := AName + '[in: "'+ALineText+'", StopAt='+IntToStr(AStopAtMatch)+', NoMatchCnt='+IntToStr(ANoMatchCount)+']';
94     LineText := ALineText;
95     SetLength(FMatchList, 0);
96     FStopAtMatch := AStopAtMatch;
97     FNoMatchCount := ANoMatchCount;;
98     if LineText = '' then begin
99       Res1 := Dict.Search(nil, Length(LineText), nil);
100       Res2 := Dict.Search(nil, Length(LineText), @DoDictMatch);
101     end
102     else begin
103       Res1 := Dict.Search(@LineText[1], Length(LineText), nil);
104       Res2 := Dict.Search(@LineText[1], Length(LineText), @DoDictMatch);
105       //dict.GetMatchAtChar();
106     end;
107   end;
108 
109   procedure CheckExp(ExpRes1, ExpRes2: Integer);
110   begin
111     if ExpRes1 = 0
112     then AssertTrue(Name+' Result (no event)', nil = Res1)
113     else if ExpRes1 > 0
114     then AssertEquals(Name+' Result (no event)', ExpRes1, Res1 - @LineText[1]);
115     if ExpRes2 = 0
116     then AssertTrue(Name+' Result (event)', nil = Res2)
117     else if ExpRes2 > 0
118     then AssertEquals(Name+' Result (event)', ExpRes2, Res2 - @LineText[1]);
119   end;
120 
121   procedure CheckExp(AExpCount: Integer; AExpList: array of Integer);
122   var
123     i: Integer;
124   begin
125     AssertEquals(Name+' (len list)', AExpCount, Length(FMatchList));
126     for i := 0 to Length(AExpList) div 2 -1 do begin
127       AssertEquals(Name+' (start '+IntToStr(i)+')', AExpList[i*2], FMatchList[i].p - @LineText[1]);
128       AssertEquals(Name+' (len '+IntToStr(i)+')', AExpList[i*2+1], FMatchList[i].l);
129     end;
130   end;
131 
132   procedure CheckExp(ExpRes1, ExpRes2, AExpCount: Integer; AExpList: array of Integer);
133   begin
134     CheckExp(ExpRes1, ExpRes2);
135     CheckExp(AExpCount, AExpList);
136   end;
137 
138 
139 var
140   i: Integer;
141 begin
142   Dict := TSynSearchDictionary.Create;
143   Dict.Add('debugln',1);
144   Dict.Add('debuglnenter',2);
145   Dict.Add('debuglnexit',3);
146   Dict.Add('dbgout',4);
147   //Dict.DebugPrint();
148   Dict.Free;
149 
150 
151   Dict := TSynSearchDictionary.Create;
152   Dict.Add('foo'       , 0);
153   Dict.Add('Hello'     , 1);
154   Dict.Add('yello12345', 2);
155   Dict.Add(   'lo123'  , 3);
156   Dict.Add(   'lo789'  , 4);
157   Dict.Add('hell'      , 5);
158   Dict.Add('hatter'    , 6);
159   Dict.Add('log'       , 7);
160   Dict.Add('lantern'   , 8);
161   Dict.Add('terminal'  , 9);
162   Dict.Add('all'       ,10);
163   Dict.Add('alt'       ,11);
164   Dict.Add('YESTERDAY' ,12);
165   Dict.Add(  'STER'    ,13);
166   Dict.Add(   'TE'     ,14);
167   Dict.Add(    'ER'    ,15);
168   Dict.Add(      'DAY' ,16);
169 
170 (*
171 Dict.Add('Algoritmus', 0);
172 Dict.Add('Aho', 0);
173 Dict.Add('Corasick', 0);
174 Dict.Add('je', 0);
175 Dict.Add('vyhledávací', 0);
176 Dict.Add('algoritmus', 0);
177 Dict.Add('vynalezený', 0);
178 Dict.Add('Alfredem', 0);
179 Dict.Add('Ahem', 0);
180 Dict.Add('a', 0);
181 Dict.Add('Margaret', 0);
182 Dict.Add('J', 0);
183 Dict.Add('Corasickovou', 0);
184 Dict.Add('Je', 0);
185 Dict.Add('to', 0);
186 Dict.Add('druh', 0);
187 Dict.Add('slovníkového', 0);
188 Dict.Add('vyhledávacího', 0);
189 Dict.Add('algoritmu', 0);
190 Dict.Add('který', 0);
191 Dict.Add('ve', 0);
192 Dict.Add('vstupním', 0);
193 Dict.Add('textu', 0);
194 Dict.Add('hledá', 0);
195 Dict.Add('prvky', 0);
196 Dict.Add('konečné', 0);
197 Dict.Add('množiny', 0);
198 Dict.Add('řetězců', 0);
199 Dict.Add('Vyhledává', 0);
200 Dict.Add('všechny', 0);
201 Dict.Add('prvky', 0);
202 Dict.Add('množiny', 0);
203 Dict.Add('najednou', 0);2 pi *
204 Dict.Add('jeho', 0);
205 Dict.Add('asymptotická', 0);
206 Dict.Add('složitost', 0);
207 Dict.Add('je', 0);
208 Dict.Add('proto', 0);
209 Dict.Add('lineární', 0);
210 Dict.Add('k', 0);
211 Dict.Add('délce', 0);
212 Dict.Add('všech', 0);
213 Dict.Add('vyhledávaných', 0);
214 Dict.Add('prvků', 0);
215 Dict.Add('plus', 0);
216 Dict.Add('délce', 0);
217 Dict.Add('vstupního', 0);
218 Dict.Add('textu', 0);
219 Dict.Add('plus', 0);
220 Dict.Add('délce', 0);
221 Dict.Add('výstupu', 0);
222 Dict.Add('Jelikož', 0);
223 Dict.Add('algoritmus', 0);
224 Dict.Add('najde', 0);
225 Dict.Add('všechny', 0);
226 Dict.Add('výskyty', 0);
227 Dict.Add('celkový', 0);
228 Dict.Add('počet', 0);
229 Dict.Add('výskytů', 0);
230 Dict.Add('pro', 0);
231 Dict.Add('celou', 0);
232 Dict.Add('množinu', 0);
233 Dict.Add('může', 0);
234 Dict.Add('být', 0);
235 Dict.Add('až', 0);
236 Dict.Add('kvadratický', 0);
237 Dict.Add('(například', 0);
238 Dict.Add('v', 0);
239 Dict.Add('případě', 0);
240 Dict.Add('kdy', 0);
241 Dict.Add('vyhledávané', 0);
242 Dict.Add('řetězce', 0);
243 Dict.Add('jsou', 0);
244 Dict.Add('a', 0);
245 Dict.Add('aa', 0);
246 Dict.Add('aaa', 0);
247 Dict.Add('aaaa', 0);
248 Dict.Add('a', 0);
249 Dict.Add('vstupní', 0);
250 Dict.Add('text', 0);
251 Dict.Add('je', 0);
252 Dict.Add('aaaa)', 0);
253 Dict.Add('Neformálně', 0);
254 Dict.Add('řečeno', 0);
255 Dict.Add('algoritmus', 0);
256 Dict.Add('konstruuje', 0);
257 Dict.Add('trie', 0);
258 Dict.Add('se', 0);
259 Dict.Add('zpětnými', 0);
260 Dict.Add('odkazy', 0);
261 Dict.Add('pro', 0);
262 Dict.Add('každý', 0);
263 Dict.Add('vrchol', 0);
264 Dict.Add('(například', 0);
265 Dict.Add('abc)', 0);
266 Dict.Add('na', 0);
267 Dict.Add('nejdelší', 0);
268 Dict.Add('vlastní', 0);
269 Dict.Add('sufix', 0);
270 Dict.Add('(pokud', 0);
271 Dict.Add('existuje', 0);
272 Dict.Add('tak', 0);
273 Dict.Add('bc', 0);
274 Dict.Add('jinak', 0);
275 Dict.Add('pokud', 0);
276 Dict.Add('existuje', 0);
277 Dict.Add('c', 0);
278 Dict.Add('jinak', 0);
279 Dict.Add('do', 0);
280 Dict.Add('kořene)', 0);
281 Dict.Add('Obsahuje', 0);
282 Dict.Add('také', 0);
283 Dict.Add('odkazy', 0);
284 Dict.Add('z', 0);
285 Dict.Add('každého', 0);
286 Dict.Add('vrcholu', 0);
287 Dict.Add('na', 0);
288 Dict.Add('prvek', 0);
289 Dict.Add('slovníku', 0);
290 Dict.Add('obsahující', 0);
291 Dict.Add('odpovídající', 0);
292 Dict.Add('nejdelší', 0);
293 Dict.Add('sufix', 0);
294 Dict.Add('Tudíž', 0);
295 Dict.Add('všechny', 0);
296 Dict.Add('výsledky', 0);
297 Dict.Add('mohou', 0);
298 Dict.Add('být', 0);
299 Dict.Add('vypsány', 0);
300 Dict.Add('procházením', 0);
301 Dict.Add('výsledného', 0);
302 Dict.Add('spojového', 0);
303 Dict.Add('seznamu', 0);
304 Dict.Add('Algoritmus', 0);
305 Dict.Add('pak', 0);
306 Dict.Add('pracuje', 0);
307 Dict.Add('tak', 0);
308 Dict.Add('že', 0);
309 Dict.Add('postupně', 0);
310 Dict.Add('zpracovává', 0);
311 Dict.Add('vstupní', 0);
312 Dict.Add('řetězec', 0);
313 Dict.Add('a', 0);
314 Dict.Add('pohybuje', 0);
315 Dict.Add('se', 0);
316 Dict.Add('po', 0);
317 Dict.Add('nejdelší', 0);
318 Dict.Add('odpovídající', 0);
319 Dict.Add('cestě', 0);
320 Dict.Add('stromu', 0);
321 Dict.Add('Pokud', 0);
322 Dict.Add('algoritmus', 0);
323 Dict.Add('načte', 0);
324 Dict.Add('znak', 0);
325 Dict.Add('který', 0);
326 Dict.Add('neodpovídá', 0);
327 Dict.Add('žádné', 0);
328 Dict.Add('další', 0);
329 Dict.Add('možné', 0);
330 Dict.Add('cestě', 0);
331 Dict.Add('přejde', 0);
332 Dict.Add('po', 0);
333 Dict.Add('zpětném', 0);
334 Dict.Add('odkazu', 0);
335 Dict.Add('na', 0);
336 Dict.Add('nejdelší', 0);
337 Dict.Add('odpovídající', 0);
338 Dict.Add('sufix', 0);
339 Dict.Add('a', 0);
340 Dict.Add('pokračuje', 0);
341 Dict.Add('tam', 0);
342 Dict.Add('(případně', 0);
343 Dict.Add('opět', 0);
344 Dict.Add('přejde', 0);
345 Dict.Add('zpět)', 0);
346 Dict.Add('Pokud', 0);
347 Dict.Add('je', 0);
348 Dict.Add('množina', 0);
349 Dict.Add('vyhledávaných', 0);
350 Dict.Add('řetězců', 0);
351 Dict.Add('známa', 0);
352 Dict.Add('předem', 0);
353 Dict.Add('(např', 0);
354 Dict.Add('databáze', 0);
355 Dict.Add('počítačových', 0);
356 Dict.Add('virů)', 0);
357 Dict.Add('je', 0);
358 Dict.Add('možné', 0);
359 Dict.Add('zkonstruovat', 0);
360 Dict.Add('automat', 0);
361 Dict.Add('předem', 0);
362 Dict.Add('a', 0);
363 Dict.Add('ten', 0);
364 Dict.Add('pak', 0);
365 Dict.Add('uložit', 0);
366 //*)
367 
368   //Dict.Search('aallhellxlog', 12, nil);
369   //Dict.DebugPrint();
370 
371   InitTest('Nothing to find: empty input', '', -1);
372   CheckExp(0, 0,  0, []);
373   InitTest('Nothing to find: short input', '@', -1);
374   CheckExp(0, 0,  0, []);
375   InitTest('Nothing to find: long  input', StringOfChar('#',100), -1);
376   CheckExp(0, 0,  0, []);
377 
378   // result points to end of word (0 based)
379   // end, end, count, idx(from add)
380   InitTest('find hell', 'hell', 0);
381   CheckExp(4, 4,  1, [4, 5]);
382   InitTest('find hell', 'hell', -1);
383   CheckExp(4, 4,  1, [4, 5]);
384 
385   InitTest('find hell', 'hell1');
386   CheckExp(4, 4,  1, [4, 5]);
387 
388   InitTest('find hell', '2hell');
389   CheckExp(5, 5,  1, [5, 5]);
390 
391   InitTest('find hell', '2hell1');
392   CheckExp(5, 5,  1, [5, 5]);
393 
394   InitTest('find hell', 'hell hell'); // no event stops after 1st
395   CheckExp(4, 9,  2, [4, 5,   9, 5]);
396 
397   InitTest('find hell', 'hellhell'); // no event stops after 1st
398   CheckExp(4, 8,  2, [4, 5,   8, 5]);
399 
400   InitTest('find hell', 'hell hell', 0);
401   CheckExp(4, 4,  1, [4, 5]);
402 
403   InitTest('find hell', 'hellog', -1, 0); // hell is match, log can not be found
404   CheckExp(4, 4,  1, [4, 5]);
405 
406   InitTest('find log', 'hellog', -1, 1); // skip hell (still in list), find log
407   CheckExp(-1, 6,  2, [4, 5,  6, 7]);
408 
409   InitTest('find hell', 'hehell', 0);
410   CheckExp(6, 6,  1, [6, 5]);
411 
412   InitTest('find hell', 'thehell', 0);
413   CheckExp(7, 7,  1, [7, 5]);
414 
415   InitTest('lantern', 'lantern');
416   CheckExp(7, 7,  1, [7, 8]);
417 
418   InitTest('find terminal', 'lanterminal');
419   CheckExp(11, 11,  1, [11, 9]);
420 
421   InitTest('find lo123', 'yello123AA');
422   CheckExp(8, 8,  1, [8, 3]);
423 
424   InitTest('find lo123', 'yello1234AA');
425   CheckExp(8, 8,  1, [8, 3]);
426 
427   InitTest('find yello12345 and lo123', 'yello12345AA', -1, 99);
428   CheckExp(-1, 10,  2, [8, 3,  10, 2]);
429 
430   InitTest('find many', 'YESTERDAY', -1, 99);
431   CheckExp(-1, 9,  5, [{TE} 5, 14,  {STER} 6, 13,  {ER} 6, 15,  {YESTERDAY} 9, 12,  {DAY} 9, 16 ]);
432 
433   InitTest('find many', 'YESTERDAY'); // restart after each match
434   CheckExp(-1, 9,  2, [{TE} 5, 14,  {DAY} 9, 16 ]);
435 
436 
437 
438   Dict.Search('aallhellxlog', 12, @DoDictMatch);
439   //Dict.BuildDictionary;
440   //Dict.DebugPrint();
441 
442   //Randomize;
443   //Dict.Clear;
444   //for i := 0 to 5000 do begin
445   //  s := '';
446   //  for j := 10 to 11+Random(20) do s := s + chr(Random(127));
447   //  Dict.Add(s);
448   //end;
449   //Dict.BuildDictionary;
450   //Dict.DebugPrint(true);
451 
452 
453   Dict.Free;
454 end;
455 
456 procedure TTestMarkupHighAll.TestValidateMatches;
457 type
458   TMatchLoc = record
459     y1, y2, x1, x2: Integer;
460   end;
461 var
462   M: TTestSynEditMarkupHighlightAllMulti;
463 
lnull464   function l(y, x1, x2: Integer) : TMatchLoc;
465   begin
466     Result.y1 := y;
467     Result.x1 := x1;
468     Result.y2 := y;
469     Result.x2 := x2;
470   end;
471 
472   procedure StartMatch(Words: Array of string);
473   var
474     i: Integer;
475   begin
476     SynEdit.BeginUpdate;
477     M.Clear;
478     for i := 0 to high(Words) do
479       M.AddSearchTerm(Words[i]);
480     SynEdit.EndUpdate;
481     m.MarkupInfo.Foreground := clRed;
482   end;
483 
484   Procedure TestHasMCount(AName: String; AExpMin: Integer; AExpMax: Integer = -1);
485   begin
486     AName := AName + '(CNT)';
487     if AExpMax < 0 then begin
488       AssertEquals(BaseTestName+' '+AName, AExpMin, M.Matches.Count);
489     end
490     else begin
491       AssertTrue(BaseTestName+' '+AName+ '(Min)', AExpMin <= M.Matches.Count);
492       AssertTrue(BaseTestName+' '+AName+ '(Max)', AExpMax >= M.Matches.Count);
493     end;
494   end;
495 
496   Procedure TestHasMatches(AName: String; AExp: Array of TMAtchLoc; ExpMusNotExist: Boolean = False);
497   var
498     i, j: Integer;
499   begin
500     for i := 0 to High(AExp) do begin
501       j := M.Matches.Count - 1;
502       while (j >= 0) and
503         ( (M.Matches.StartPoint[j].y <> AExp[i].y1) or (M.Matches.StartPoint[j].x <> AExp[i].x1) or
504           (M.Matches.EndPoint[j].y <> AExp[i].y2) or (M.Matches.EndPoint[j].x <> AExp[i].x2) )
505       do
506         dec(j);
507       AssertEquals(BaseTestName+' '+AName+'('+IntToStr(i)+')', not ExpMusNotExist, j >= 0);
508     end
509   end;
510 
511   Procedure TestHasMatches(AName: String; AExpCount: Integer; AExp: Array of TMAtchLoc; ExpMusNotExist: Boolean = False);
512   begin
513     TestHasMatches(AName, AExp, ExpMusNotExist);
514     TestHasMCount(AName, AExpCount);
515   end;
516 
517   Procedure TestHasMatches(AName: String; AExpCountMin, AExpCountMax: Integer; AExp: Array of TMAtchLoc; ExpMusNotExist: Boolean = False);
518   begin
519     TestHasMatches(AName, AExp, ExpMusNotExist);
520     TestHasMCount(AName, AExpCountMin, AExpCountMax);
521   end;
522 
523   Procedure TestHasScanCnt(AName: String; AExpMin: Integer; AExpMax: Integer = -1);
524   begin
525     AName := AName + '(SCANNED)';
526     if AExpMax < 0 then begin
527       AssertEquals(BaseTestName+' '+AName, AExpMin, M.ScannedLineCount);
528     end
529     else begin
530       AssertTrue(BaseTestName+' '+AName+ '(Min)', AExpMin <= M.ScannedLineCount);
531       AssertTrue(BaseTestName+' '+AName+ '(Max)', AExpMax >= M.ScannedLineCount);
532     end;
533   end;
534 
535   procedure SetText(ATopLine: Integer = 1; HideSingle: Boolean = False);
536   var
537     i: Integer;
538   begin
539     ReCreateEdit;
540     SynEdit.BeginUpdate;
541     for i := 1 to 700 do
542       SynEdit.Lines.Add('  a'+IntToStr(i)+'a  b  c'+IntToStr(i)+'d');
543     SynEdit.Align := alTop;
544     SynEdit.Height := SynEdit.LineHeight * 40 + SynEdit.LineHeight div 2;
545     M := TTestSynEditMarkupHighlightAllMulti.Create(SynEdit);
546     M.HideSingleMatch := HideSingle;
547     SynEdit.MarkupMgr.AddMarkUp(M);
548     SynEdit.TopLine := ATopLine;
549     SynEdit.EndUpdate;
550   end;
551 
552   procedure SetTextAndMatch(ATopLine: Integer; HideSingle: Boolean;
553     Words: Array of string;
554     AName: String= ''; AExpMin: Integer = -1; AExpMax: Integer = -1);
555   begin
556     SetText(ATopLine, HideSingle);
557     StartMatch(Words);
558     if AExpMin >= 0 then
559       TestHasMCount(AName + ' init', AExpMin, AExpMax);
560   end;
561 
Mtxtnull562   function Mtxt(i: Integer): String;
563   begin
564     Result := 'a'+IntToStr(i)+'a';
565   end;
566 
567 var
568   N: string;
569   i, j: integer;
570   a, b: integer;
571 begin
572 
573   {%region Searchrange}
574     PushBaseName('Searchrange');
575     PushBaseName('HideSingleMatch=False');
576 
577     N := 'Find match on first line';
578     SetText(250);
579     M.HideSingleMatch := False;
580     StartMatch(['a250a']);
581     TestHasMCount (N, 1);
582     TestHasMatches(N, [l(250, 3, 8)]);
583 
584     N := 'Find match on last line';
585     SetText(250);
586     M.HideSingleMatch := False;
587     StartMatch(['a289a']);
588     TestHasMCount (N, 1);
589     TestHasMatches(N, [l(289, 3, 8)]);
590 
591     N := 'Find match on last part visible) line';
592     SetText(250);
593     M.HideSingleMatch := False;
594     StartMatch(['a290a']);
595     TestHasMCount (N, 1);
596     TestHasMatches(N, [l(290, 3, 8)]);
597 
598     // Before topline
599     SetText(250);
600     M.HideSingleMatch := False;
601     StartMatch(['a249a']);
602     TestHasMCount ('NOT Found before topline', 0);
603 
604     // after lastline
605     SetText(250);
606     M.HideSingleMatch := False;
607     StartMatch(['a291a']);
608     TestHasMCount ('NOT Found after lastline', 0);
609 
610     // first and last
611     SetText(250);
612     M.HideSingleMatch := False;
613     StartMatch(['a250a', 'a290a']);
614     TestHasMCount ('Found on first and last line', 2);
615     TestHasMatches('Found on first and last line', [l(250, 3, 8), l(290, 3, 8)]);
616 
617     // first and last + before
618     SetText(250);
619     M.HideSingleMatch := False;
620     StartMatch(['a250a', 'a290a', 'a249a']);
621     TestHasMCount ('Found on first/last (but not before) line', 2);
622     TestHasMatches('Found on first/last (but not before) line', [l(250, 3, 8), l(290, 3, 8)]);
623 
624     // first and last + after
625     SetText(250);
626     M.HideSingleMatch := False;
627     StartMatch(['a250a', 'a290a', 'a291a']);
628     TestHasMCount ('Found on first/last (but not after) line', 2);
629     TestHasMatches('Found on first/last (but not after) line', [l(250, 3, 8), l(290, 3, 8)]);
630 
631     PopPushBaseName('HideSingleMatch=True');
632 
633     SetText(250);
634     M.HideSingleMatch := True;
635     StartMatch(['a250a']);
636     TestHasMCount ('Found on first line', 1);
637     TestHasMatches('Found on first line', [l(250, 3, 8)]);
638 
639     SetText(250);
640     M.HideSingleMatch := True;
641     StartMatch(['a289a']);
642     TestHasMCount ('Found on last line', 1);
643     TestHasMatches('Found on last line', [l(289, 3, 8)]);
644 
645     SetText(250);
646     M.HideSingleMatch := True;
647     StartMatch(['a290a']);
648     TestHasMCount ('Found on last (partly) line', 1);
649     TestHasMatches('Found on last (partly) line', [l(290, 3, 8)]);
650 
651     // Before topline
652     SetText(250);
653     M.HideSingleMatch := True;
654     StartMatch(['a249a']);
655     TestHasMCount ('NOT Found before topline', 0);
656 
657     // after lastline
658     SetText(250);
659     M.HideSingleMatch := True;
660     StartMatch(['a291a']);
661     TestHasMCount ('NOT Found after lastline', 0);
662 
663     // first and last
664     SetText(250);
665     M.HideSingleMatch := True;
666     StartMatch(['a250a', 'a290a']);
667     TestHasMCount ('Found on first and last line', 2);
668     TestHasMatches('Found on first and last line', [l(250, 3, 8), l(290, 3, 8)]);
669 
670     // first and last + before
671     SetText(250);
672     M.HideSingleMatch := True;
673     StartMatch(['a250a', 'a290a', 'a249a']);
674     TestHasMCount ('Found on first/last (but not before) line', 2);
675     TestHasMatches('Found on first/last (but not before) line', [l(250, 3, 8), l(290, 3, 8)]);
676 
677     // first and last + after
678     SetText(250);
679     M.HideSingleMatch := True;
680     StartMatch(['a250a', 'a290a', 'a291a']);
681     TestHasMCount ('Found on first/last (but not after) line', 2);
682     TestHasMatches('Found on first/last (but not after) line', [l(250, 3, 8), l(290, 3, 8)]);
683 
684     // extend for HideSingle, before
685     N := 'Look for 2nd match before startpoint (first match at topline)';
686     SetText(250);
687     M.HideSingleMatch := True;
688     StartMatch(['a250a', 'a249a']);
689     TestHasMCount (N, 2);
690     TestHasMatches(N, [l(250, 3, 8), l(249, 3, 8)]);
691 
692     N := 'Look for 2nd match before startpoint (first match at lastline)';
693     SetText(250);
694     M.HideSingleMatch := True;
695     StartMatch(['a290a', 'a249a']);
696     TestHasMCount (N, 2);
697     TestHasMatches(N, [l(290, 3, 8), l(249, 3, 8)]);
698 
699     N := 'Look for 2nd match FAR (99l) before startpoint (first match at topline)';
700     SetText(250);
701     M.HideSingleMatch := True;
702     StartMatch(['a250a', 'a151a']);
703     TestHasMCount (N, 2);
704     TestHasMatches(N, [l(250, 3, 8), l(151, 3, 8)]);
705 
706     N := 'Look for 2nd match FAR (99l) before startpoint (first match at lastline)';
707     SetText(250);
708     M.HideSingleMatch := True;
709     StartMatch(['a290a', 'a151a']);
710     TestHasMCount (N, 2);
711     TestHasMatches(N, [l(290, 3, 8), l(151, 3, 8)]);
712 
713     N := 'Look for 2nd match before startpoint, find ONE of TWO';
714     SetText(250);
715     M.HideSingleMatch := True;
716     StartMatch(['a250a', 'a200a', 'a210a']);
717     TestHasMCount (N, 2);
718     TestHasMatches(N, [l(250, 3, 8), l(210, 3, 8)]);
719 
720     // TODO: Not extend too far...
721 
722     // extend for HideSingle, after
723     SetText(250);
724     M.HideSingleMatch := True;
725     StartMatch(['a250a', 'a291a']);
726     TestHasMCount ('Found on first/ext-after line', 2);
727     TestHasMatches('Found on first/ext-after line', [l(250, 3, 8), l(291, 3, 8)]);
728 
729     SetText(250);
730     M.HideSingleMatch := True;
731     StartMatch(['a290a', 'a291a']);
732     TestHasMCount ('Found on last/ext-after line', 2);
733     TestHasMatches('Found on last/ext-after line', [l(290, 3, 8), l(291, 3, 8)]);
734 
735     SetText(250);
736     M.HideSingleMatch := True;
737     StartMatch(['a250a', 'a389a']);
738     TestHasMCount ('Found on first/ext-after-99 line', 2);
739     TestHasMatches('Found on first/ext-after-99 line', [l(250, 3, 8), l(389, 3, 8)]);
740 
741     SetText(250);
742     M.HideSingleMatch := True;
743     StartMatch(['a290a', 'a389a']);
744     TestHasMCount ('Found on last/ext-after-99 line', 2);
745     TestHasMatches('Found on last/ext-after-99 line', [l(290, 3, 8), l(389, 3, 8)]);
746 
747 
748     PopBaseName;
749     PopBaseName;
750   {%endregion}
751 
752   {%region Scroll / LinesInWindow}
753     PushBaseName('Scroll/LinesInWindow');
754     PushBaseName('HideSingleMatch=False');
755 
756 
757     SetText(250);
758     M.HideSingleMatch := False;
759     StartMatch(['a249a']);
760     TestHasMCount ('Not Found before first line', 0);
761 
762     M.ResetScannedCount;
763     SynEdit.TopLine := 251;
764     TestHasMCount ('Not Found before first line (250=>251)', 0);
765     TestHasScanCnt('Not Found before first line (250=>251)', 1, 2); // Allow some range
766 
767     M.ResetScannedCount;
768     SynEdit.TopLine := 249;
769     TestHasMCount ('Found on first line (251=<249', 1);
770     TestHasMatches('Found on first line (251=>249)', [l(249, 3, 8)]);
771     TestHasScanCnt('Found on first line (251=>249)', 1, 2); // Allow some range
772 
773 
774     SetText(250);
775     M.HideSingleMatch := False;
776     StartMatch(['a291a']);
777     TestHasMCount ('Not Found after last line', 0);
778 
779     M.ResetScannedCount;
780     SynEdit.TopLine := 249;
781     TestHasMCount ('Not Found after last line (250=>249)', 0);
782     TestHasScanCnt('Not Found after last line (250=>249)', 1, 2); // Allow some range
783 
784     M.ResetScannedCount;
785     SynEdit.TopLine := 251;
786     TestHasMCount ('Found on last line (249=<251', 1);
787     TestHasMatches('Found on last line (249=>251)', [l(291, 3, 8)]);
788     TestHasScanCnt('Found on last line (249=>251)', 1, 2); // Allow some range
789 
790 
791     SetText(250);
792     M.HideSingleMatch := False;
793     StartMatch(['a291a']);
794     TestHasMCount ('Not Found after last line', 0);
795 
796     M.ResetScannedCount;
797     SynEdit.Height := SynEdit.LineHeight * 41 + SynEdit.LineHeight div 2;
798     TestHasMCount ('Found on last line (40=>41', 1);
799     TestHasMatches('Found on last line (40=>41)', [l(291, 3, 8)]);
800     TestHasScanCnt('Found on last line (40=>41)', 1, 2); // Allow some range
801 
802 
803     PopPushBaseName('HideSingleMatch=True');
804 
805     SetText(250);
806     M.HideSingleMatch := True;
807     StartMatch(['a249a', 'a248a']);
808     TestHasMCount ('Not Found before first line', 0);
809 
810     M.ResetScannedCount;
811     SynEdit.TopLine := 251;
812     TestHasMCount ('Not Found before first line (250=>251)', 0);
813     TestHasScanCnt('Not Found before first line (250=>251)', 1, 2); // Allow some range
814 
815     M.ResetScannedCount;
816     SynEdit.TopLine := 249;
817     TestHasMCount ('Found on first line+ext (251=<249', 2);
818     TestHasMatches('Found on first line+ext (251=>249)', [l(249, 3, 8), l(248, 3, 8)]);
819 
820 
821     SetText(250);
822     M.HideSingleMatch := True;
823     StartMatch(['a291a', 'a292a']);
824     TestHasMCount ('Not Found after last line', 0);
825 
826     M.ResetScannedCount;
827     SynEdit.TopLine := 249;
828     TestHasMCount ('Not Found after last line (250=>249)', 0);
829     TestHasScanCnt('Not Found after last line (250=>249)', 1, 2); // Allow some range
830 
831     M.ResetScannedCount;
832     SynEdit.TopLine := 251;
833     TestHasMCount ('Found on last line+ext (249=<251', 2);
834     TestHasMatches('Found on last line+ext (249=>251)', [l(291, 3, 8), l(292, 3, 8)]);
835 
836 
837     for i := -205 to 205 do begin
838       if abs(i) in [0..2, 5..15, 25..35, 50..95, 105..195] then continue;
839 
840       N := 'Far Scroll '+IntToStr(i)+' to %d matches top/last > last ';
841       SetTextAndMatch(250, False, [Mtxt(250), Mtxt(290), Mtxt(290+i)]);
842       SynEdit.TopLine := 250 + i;
843       TestHasMatches(N + 'Found ', [l(290+i, 3, 3+length(Mtxt(290+i)))]);
844 
845 
846       N := 'Far Scroll '+IntToStr(i)+' to %d matches top/last > top ';
847       SetTextAndMatch(250, False, [Mtxt(250), Mtxt(290), Mtxt(250+i)]);
848       SynEdit.TopLine := 250 + i;
849       TestHasMatches(N + 'Found ', [l(250+i, 3, 3+length(Mtxt(250+i)))]);
850 
851 
852       N := 'Far Scroll '+IntToStr(i)+' to %d matches top > last ';
853       SetTextAndMatch(250, False, [Mtxt(250), Mtxt(290+i)]);
854       SynEdit.TopLine := 250 + i;
855       TestHasMatches(N + 'Found ', [l(290+i, 3, 3+length(Mtxt(290+i)))]);
856 
857 
858       N := 'Far Scroll '+IntToStr(i)+' to %d matches top > top ';
859       SetTextAndMatch(250, False, [Mtxt(250), Mtxt(250+i)]);
860       SynEdit.TopLine := 250 + i;
861       TestHasMatches(N + 'Found ', [l(250+i, 3, 3+length(Mtxt(250+i)))]);
862 
863 
864       N := 'Far Scroll '+IntToStr(i)+' to %d matches last > last ';
865       SetTextAndMatch(250, False, [Mtxt(290), Mtxt(290+i)]);
866       SynEdit.TopLine := 250 + i;
867       TestHasMatches(N + 'Found ', [l(290+i, 3, 3+length(Mtxt(290+i)))]);
868 
869 
870       N := 'Far Scroll '+IntToStr(i)+' to %d matches last > top ';
871       SetTextAndMatch(250, False, [Mtxt(290), Mtxt(250+i)]);
872       SynEdit.TopLine := 250 + i;
873       TestHasMatches(N + 'Found ', [l(250+i, 3, 3+length(Mtxt(250+i)))]);
874 
875 
876     end;
877 
878 
879     PopBaseName;
880     PopBaseName;
881   {%endregion}
882 
883   {%region edit}
884     PushBaseName('Searchrange');
885     //PushBaseName('HideSingleMatch=False');
886 
887     for i := 245 to 295 do begin
888       if ((i > 259) and (i < 280)) then continue;
889 
890       N := 'Edit at '+IntToStr(i)+' / NO match';
891       SetTextAndMatch(250, False,   ['DontMatchMe'],   N+' init/found', 0);
892       M.ResetScannedCount;
893       SynEdit.TextBetweenPoints[point(1, i), point(1, i)] := 'X';
894       SynEdit.SimulatePaintText;
895       TestHasMCount (N+' Found after edit', 0);
896       if (i >= 250) and (i <= 290)
897       then TestHasScanCnt(N+' Found after edit', 1, 3)
898       else
899       if (i < 247) or (i > 293)
900       then TestHasScanCnt(N+' Found after edit', 0)
901       else TestHasScanCnt(N+' Found after edit', 0, 3);
902 
903 
904       N := 'Edit (new line) at '+IntToStr(i)+' / NO match';
905       SetTextAndMatch(250, False,   ['DontMatchMe'],   N+' init/found', 0);
906       M.ResetScannedCount;
907       SynEdit.TextBetweenPoints[point(1, i), point(1, i)] := LineEnding;
908       SynEdit.SimulatePaintText;
909       TestHasMCount (N+' Found after edit', 0);
910       //if (i >= 250) and (i <= 290)
911       //then TestHasScanCnt(N+' Found after edit', 1, 3)
912       //else
913       //if (i < 247) or (i > 293)
914       //then TestHasScanCnt(N+' Found after edit', 0)
915       //else TestHasScanCnt(N+' Found after edit', 0, 3);
916 
917 
918       N := 'Edit (join line) at '+IntToStr(i)+' / NO match';
919       SetTextAndMatch(250, False,   ['DontMatchMe'],   N+' init/found', 0);
920       M.ResetScannedCount;
921       SynEdit.TextBetweenPoints[point(10, i), point(1, i+1)] := '';
922       SynEdit.SimulatePaintText;
923       TestHasMCount (N+' Found after edit', 0);
924       //if (i >= 250) and (i <= 290)
925       //then TestHasScanCnt(N+' Found after edit', 1, 3)
926       //else
927       //if (i < 247) or (i > 293)
928       //then TestHasScanCnt(N+' Found after edit', 0)
929       //else TestHasScanCnt(N+' Found after edit', 0, 3);
930 
931     end;
932 
933 
934 
935     for j := 245 to 295 do begin
936       if ((j > 255) and (j < 270)) or ((j > 270) and (j < 285)) then
937         continue;
938 
939       for i := 245 to 295 do begin
940         N := 'Edit at '+IntToStr(i)+' / single match at '+IntToStr(j);
941         SetTextAndMatch(250, False,   ['a'+IntToStr(j)+'a']);
942         if (j >= 250) and (j <= 290)
943         then TestHasMatches(N+' init/found',   1,   [l(j, 3, 8)])
944         else TestHasMCount (N+' init/not found', 0);
945 
946         M.ResetScannedCount;
947         SynEdit.TextBetweenPoints[point(1, i), point(1, i)] := 'X';
948         SynEdit.SimulatePaintText;
949         if (j >= 250) and (j <= 290) then begin
950           if i = j
951           then TestHasMatches(N+' Found after edit',   1,  [l(j, 4, 9)])
952           else TestHasMatches(N+' Found after edit',   1,  [l(j, 3, 8)]);
953         end
954         else
955           TestHasMCount (N+' still not Found after edit', 0);
956 
957         if (i >= 250) and (i <= 290)
958         then TestHasScanCnt(N+' Found after edit', 1, 3)
959         else
960         if (i < 247) or (i > 293)
961         then TestHasScanCnt(N+' Found after edit', 0)
962         else TestHasScanCnt(N+' Found after edit', 0, 3);
963       end;
964 
965 
966       for i := 245 to 295 do begin
967         N := 'Edit (new line) at '+IntToStr(i)+' / single match at '+IntToStr(j);
968         SetTextAndMatch(250, False,   ['a'+IntToStr(j)+'a']);
969         if (j >= 250) and (j <= 290)
970         then TestHasMatches(N+' init/found',   1,   [l(j, 3, 8)])
971         else TestHasMCount (N+' init/not found', 0);
972 
973         M.ResetScannedCount;
974         SynEdit.BeginUpdate;
975         SynEdit.TextBetweenPoints[point(1, i), point(1, i)] := LineEnding;
976         SynEdit.TopLine := 250;
977         SynEdit.EndUpdate;
978         SynEdit.SimulatePaintText;
979         a := j;
980         if i <= j then inc(a);
981         if (a >= 250) and (a <= 290) then begin
982           if i = a
983           then TestHasMatches(N+' Found after edit',   1,  [l(a, 4, 9)])
984           else TestHasMatches(N+' Found after edit',   1,  [l(a, 3, 8)]);
985         end
986         else
987           TestHasMCount (N+' still not Found after edit', 0);
988 
989         //if (i >= 250) and (i <= 290)
990         //then TestHasScanCnt(N+' Found after edit', 1, 3)
991         //else
992         //if (i < 247) or (i > 293)
993         //then TestHasScanCnt(N+' Found after edit', 0)
994         //else TestHasScanCnt(N+' Found after edit', 0, 3);
995       end;
996 
997     end;
998 
999 
1000 
1001     for j := 0 to 6 do begin
1002       case j of
1003         0: begin a := 260; b := 270 end;
1004         1: begin a := 250; b := 270 end;
1005         2: begin a := 251; b := 270 end;
1006         3: begin a := 270; b := 288 end;
1007         4: begin a := 270; b := 289 end;
1008         5: begin a := 270; b := 290 end;
1009         6: begin a := 250; b := 290 end;
1010       end;
1011 
1012       for i := 245 to 295 do begin
1013         N := 'Edit at '+IntToStr(i)+' / TWO match at '+IntToStr(a)+', '+IntToStr(b);
1014         SetTextAndMatch(250, False,   ['a'+IntToStr(a)+'a', 'a'+IntToStr(b)+'a']);
1015         TestHasMatches(N+' init/found',   2,  [l(a, 3, 8), l(b, 3,8)]);
1016 
1017         M.ResetScannedCount;
1018         SynEdit.TextBetweenPoints[point(10, i), point(10, i)] := 'X';
1019         SynEdit.SimulatePaintText;
1020         TestHasMCount (N+' Found after edit', 2);
1021         TestHasMatches(N+' init/found', [l(a, 3, 8), l(b, 3,8)]);
1022 
1023         if (i >= 250) and (i <= 290)
1024         then TestHasScanCnt(N+' Found after edit', 1, 3)
1025         else
1026         if (i < 247) or (i > 293)
1027         then TestHasScanCnt(N+' Found after edit', 0)
1028         else TestHasScanCnt(N+' Found after edit', 0, 3);
1029       end;
1030     end;
1031 
1032 
1033     N := 'Edit/Topline/LastLine ';
1034     SetTextAndMatch(250, False,   ['a265a', 'a275a']);
1035     TestHasMatches(N+' init/found',   2,  [l(265, 3, 8), l(275, 3,8)]);
1036     M.ResetScannedCount;
1037     SynEdit.BeginUpdate;
1038     SynEdit.TextBetweenPoints[point(10, i), point(10, i)] := 'X';
1039     SynEdit.TopLine := 248; // 2 new lines
1040     SynEdit.Height := SynEdit.LineHeight * 44 + SynEdit.LineHeight div 2; // another 2 lines
1041     SynEdit.EndUpdate;
1042     SynEdit.SimulatePaintText;
1043     TestHasMatches(N+' Found after edit',   2,  [l(265, 3, 8), l(275, 3,8)]);
1044     TestHasScanCnt(N+' Found after edit', 1, 12);
1045 
1046 
1047     N := 'Edit/Topline/LastLine find new points';
1048     SetTextAndMatch(250, False,   ['a265a', 'a275a', 'a248a', 'a292a']);
1049     TestHasMatches(N+' init/found',   2,  [l(265, 3, 8), l(275, 3,8)]);
1050     M.ResetScannedCount;
1051     SynEdit.BeginUpdate;
1052     SynEdit.TextBetweenPoints[point(10, i), point(10, i)] := 'X';
1053     SynEdit.TopLine := 248; // 2 new lines
1054     SynEdit.Height := SynEdit.LineHeight * 44 + SynEdit.LineHeight div 2; // another 2 lines
1055     SynEdit.EndUpdate;
1056     SynEdit.SimulatePaintText;
1057     TestHasMatches(N+' Found after edit',   4,  [l(265, 3, 8), l(275, 3,8), l(248, 3,8), l(292, 3,8)]);
1058     TestHasScanCnt(N+' Found after edit', 1, 12);
1059 
1060 
1061 
1062 
1063 
1064     PopBaseName;
1065   {%endregion}
1066 
1067 
1068 
1069 end;
1070 
1071 initialization
1072 
1073   RegisterTest(TTestMarkupHighAll);
1074 end.
1075 
1076