1 // Unit Tests for Scintilla internal data structures
2 
3 #include <cstddef>
4 #include <cstring>
5 #include <stdexcept>
6 #include <string_view>
7 #include <vector>
8 #include <algorithm>
9 #include <memory>
10 
11 #include "Platform.h"
12 
13 #include "Scintilla.h"
14 #include "Position.h"
15 #include "SplitVector.h"
16 #include "Partitioning.h"
17 #include "RunStyles.h"
18 #include "CellBuffer.h"
19 
20 #include "catch.hpp"
21 
22 using namespace Scintilla;
23 
24 // Test CellBuffer.
25 
26 TEST_CASE("CellBuffer") {
27 
28 	const char sText[] = "Scintilla";
29 	const Sci::Position sLength = static_cast<Sci::Position>(strlen(sText));
30 
31 	CellBuffer cb(true, false);
32 
33 	SECTION("InsertOneLine") {
34 		bool startSequence = false;
35 		const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
36 		REQUIRE(startSequence);
37 		REQUIRE(sLength == cb.Length());
38 		REQUIRE(memcmp(cpChange, sText, sLength) == 0);
39 		REQUIRE(1 == cb.Lines());
40 		REQUIRE(0 == cb.LineStart(0));
41 		REQUIRE(0 == cb.LineFromPosition(0));
42 		REQUIRE(sLength == cb.LineStart(1));
43 		REQUIRE(0 == cb.LineFromPosition(static_cast<int>(sLength)));
44 		REQUIRE(cb.CanUndo());
45 		REQUIRE(!cb.CanRedo());
46 	}
47 
48 	SECTION("InsertTwoLines") {
49 		const char sText2[] = "Two\nLines";
50 		const Sci::Position sLength2 = static_cast<Sci::Position>(strlen(sText2));
51 		bool startSequence = false;
52 		const char *cpChange = cb.InsertString(0, sText2, static_cast<int>(sLength), startSequence);
53 		REQUIRE(startSequence);
54 		REQUIRE(sLength2 == cb.Length());
55 		REQUIRE(memcmp(cpChange, sText2, sLength2) == 0);
56 		REQUIRE(2 == cb.Lines());
57 		REQUIRE(0 == cb.LineStart(0));
58 		REQUIRE(0 == cb.LineFromPosition(0));
59 		REQUIRE(4 == cb.LineStart(1));
60 		REQUIRE(1 == cb.LineFromPosition(5));
61 		REQUIRE(sLength2 == cb.LineStart(2));
62 		REQUIRE(1 == cb.LineFromPosition(static_cast<int>(sLength)));
63 		REQUIRE(cb.CanUndo());
64 		REQUIRE(!cb.CanRedo());
65 	}
66 
67 	SECTION("UndoOff") {
68 		REQUIRE(cb.IsCollectingUndo());
69 		cb.SetUndoCollection(false);
70 		REQUIRE(!cb.IsCollectingUndo());
71 		bool startSequence = false;
72 		const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
73 		REQUIRE(!startSequence);
74 		REQUIRE(sLength == cb.Length());
75 		REQUIRE(memcmp(cpChange, sText, sLength) == 0);
76 		REQUIRE(!cb.CanUndo());
77 		REQUIRE(!cb.CanRedo());
78 	}
79 
80 	SECTION("UndoRedo") {
81 		const char sTextDeleted[] = "ci";
82 		const char sTextAfterDeletion[] = "Sntilla";
83 		bool startSequence = false;
84 		const char *cpChange = cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
85 		REQUIRE(startSequence);
86 		REQUIRE(sLength == cb.Length());
87 		REQUIRE(memcmp(cpChange, sText, sLength) == 0);
88 		REQUIRE(memcmp(cb.BufferPointer(), sText, sLength) == 0);
89 		REQUIRE(cb.CanUndo());
90 		REQUIRE(!cb.CanRedo());
91 		const char *cpDeletion = cb.DeleteChars(1, 2, startSequence);
92 		REQUIRE(startSequence);
93 		REQUIRE(memcmp(cpDeletion, sTextDeleted, strlen(sTextDeleted)) == 0);
94 		REQUIRE(memcmp(cb.BufferPointer(), sTextAfterDeletion, strlen(sTextAfterDeletion)) == 0);
95 		REQUIRE(cb.CanUndo());
96 		REQUIRE(!cb.CanRedo());
97 
98 		int steps = cb.StartUndo();
99 		REQUIRE(steps == 1);
100 		cb.PerformUndoStep();
101 		REQUIRE(memcmp(cb.BufferPointer(), sText, sLength) == 0);
102 		REQUIRE(cb.CanUndo());
103 		REQUIRE(cb.CanRedo());
104 
105 		steps = cb.StartUndo();
106 		REQUIRE(steps == 1);
107 		cb.PerformUndoStep();
108 		REQUIRE(cb.Length() == 0);
109 		REQUIRE(!cb.CanUndo());
110 		REQUIRE(cb.CanRedo());
111 
112 		steps = cb.StartRedo();
113 		REQUIRE(steps == 1);
114 		cb.PerformRedoStep();
115 		REQUIRE(memcmp(cb.BufferPointer(), sText, sLength) == 0);
116 		REQUIRE(cb.CanUndo());
117 		REQUIRE(cb.CanRedo());
118 
119 		steps = cb.StartRedo();
120 		REQUIRE(steps == 1);
121 		cb.PerformRedoStep();
122 		REQUIRE(memcmp(cb.BufferPointer(), sTextAfterDeletion, strlen(sTextAfterDeletion)) == 0);
123 		REQUIRE(cb.CanUndo());
124 		REQUIRE(!cb.CanRedo());
125 
126 		cb.DeleteUndoHistory();
127 		REQUIRE(!cb.CanUndo());
128 		REQUIRE(!cb.CanRedo());
129 	}
130 
131 	SECTION("LineEndTypes") {
132 		REQUIRE(cb.GetLineEndTypes() == 0);
133 		cb.SetLineEndTypes(1);
134 		REQUIRE(cb.GetLineEndTypes() == 1);
135 		cb.SetLineEndTypes(0);
136 		REQUIRE(cb.GetLineEndTypes() == 0);
137 	}
138 
139 	SECTION("ReadOnly") {
140 		REQUIRE(!cb.IsReadOnly());
141 		cb.SetReadOnly(true);
142 		REQUIRE(cb.IsReadOnly());
143 		bool startSequence = false;
144 		cb.InsertString(0, sText, static_cast<int>(sLength), startSequence);
145 		REQUIRE(cb.Length() == 0);
146 	}
147 
148 }
149 
150 TEST_CASE("CharacterIndex") {
151 
152 	CellBuffer cb(true, false);
153 
154 	SECTION("Setup") {
155 		REQUIRE(cb.LineCharacterIndex() == SC_LINECHARACTERINDEX_NONE);
156 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
157 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 0);
158 		cb.SetUTF8Substance(true);
159 
160 		cb.AllocateLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16);
161 		REQUIRE(cb.LineCharacterIndex() == SC_LINECHARACTERINDEX_UTF16);
162 
163 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
164 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 0);
165 
166 		cb.ReleaseLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16);
167 		REQUIRE(cb.LineCharacterIndex() == SC_LINECHARACTERINDEX_NONE);
168 	}
169 
170 	SECTION("Insertion") {
171 		cb.SetUTF8Substance(true);
172 
173 		cb.AllocateLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16 | SC_LINECHARACTERINDEX_UTF32);
174 
175 		bool startSequence = false;
176 		cb.InsertString(0, "a", 1, startSequence);
177 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
178 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 1);
179 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
180 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 1);
181 
182 		const char *hwair = "\xF0\x90\x8D\x88";
183 		cb.InsertString(0, hwair, strlen(hwair), startSequence);
184 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
185 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 3);
186 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
187 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 2);
188 	}
189 
190 	SECTION("Deletion") {
191 		cb.SetUTF8Substance(true);
192 
193 		cb.AllocateLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16 | SC_LINECHARACTERINDEX_UTF32);
194 
195 		bool startSequence = false;
196 		const char *hwair = "a\xF0\x90\x8D\x88z";
197 		cb.InsertString(0, hwair, strlen(hwair), startSequence);
198 
199 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
200 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 4);
201 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
202 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 3);
203 
204 		cb.DeleteChars(5, 1, startSequence);
205 
206 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
207 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 3);
208 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
209 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 2);
210 
211 		cb.DeleteChars(1, 4, startSequence);
212 
213 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
214 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 1);
215 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
216 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 1);
217 	}
218 
219 	SECTION("Insert Complex") {
220 		cb.SetUTF8Substance(true);
221 		cb.SetLineEndTypes(1);
222 		cb.AllocateLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16 | SC_LINECHARACTERINDEX_UTF32);
223 
224 		bool startSequence = false;
225 		// 3 lines of text containing 8 bytes
226 		const char *data = "a\n\xF0\x90\x8D\x88\nz";
227 		cb.InsertString(0, data, strlen(data), startSequence);
228 
229 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
230 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 2);
231 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 5);
232 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 6);
233 
234 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
235 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 2);
236 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 4);
237 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF32) == 5);
238 
239 		// Insert a new line at end -> "a\n\xF0\x90\x8D\x88\nz\n" 4 lines
240 		// Last line empty
241 		cb.InsertString(strlen(data), "\n", 1, startSequence);
242 
243 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
244 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 2);
245 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 5);
246 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 7);
247 		REQUIRE(cb.IndexLineStart(4, SC_LINECHARACTERINDEX_UTF16) == 7);
248 
249 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
250 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 2);
251 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 4);
252 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF32) == 6);
253 		REQUIRE(cb.IndexLineStart(4, SC_LINECHARACTERINDEX_UTF32) == 6);
254 
255 		// Insert a new line before end -> "a\n\xF0\x90\x8D\x88\nz\n\n" 5 lines
256 		cb.InsertString(strlen(data), "\n", 1, startSequence);
257 
258 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
259 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 2);
260 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 5);
261 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 7);
262 		REQUIRE(cb.IndexLineStart(4, SC_LINECHARACTERINDEX_UTF16) == 8);
263 		REQUIRE(cb.IndexLineStart(5, SC_LINECHARACTERINDEX_UTF16) == 8);
264 
265 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
266 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 2);
267 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 4);
268 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF32) == 6);
269 		REQUIRE(cb.IndexLineStart(4, SC_LINECHARACTERINDEX_UTF32) == 7);
270 		REQUIRE(cb.IndexLineStart(5, SC_LINECHARACTERINDEX_UTF32) == 7);
271 
272 		// Insert a valid 3-byte UTF-8 character at start ->
273 		// "\xE2\x82\xACa\n\xF0\x90\x8D\x88\nz\n\n" 5 lines
274 
275 		const char *euro = "\xE2\x82\xAC";
276 		cb.InsertString(0, euro, strlen(euro), startSequence);
277 
278 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
279 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 3);
280 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 6);
281 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 8);
282 		REQUIRE(cb.IndexLineStart(4, SC_LINECHARACTERINDEX_UTF16) == 9);
283 		REQUIRE(cb.IndexLineStart(5, SC_LINECHARACTERINDEX_UTF16) == 9);
284 
285 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
286 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 3);
287 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 5);
288 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF32) == 7);
289 		REQUIRE(cb.IndexLineStart(4, SC_LINECHARACTERINDEX_UTF32) == 8);
290 		REQUIRE(cb.IndexLineStart(5, SC_LINECHARACTERINDEX_UTF32) == 8);
291 
292 		// Insert a lone lead byte implying a 3 byte character at start of line 2 ->
293 		// "\xE2\x82\xACa\n\EF\xF0\x90\x8D\x88\nz\n\n" 5 lines
294 		// Should be treated as a single byte character
295 
296 		const char *lead = "\xEF";
297 		cb.InsertString(5, lead, strlen(lead), startSequence);
298 
299 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
300 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 3);
301 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 7);
302 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 9);
303 
304 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
305 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 3);
306 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 6);
307 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF32) == 8);
308 
309 		// Insert an ASCII lead byte inside the 3-byte initial character ->
310 		// "\xE2!\x82\xACa\n\EF\xF0\x90\x8D\x88\nz\n\n" 5 lines
311 		// It should b treated as a single character and should cause the
312 		// byte before and the 2 bytes after also be each treated as singles
313 		// so 3 more characters on line 0.
314 
315 		const char *ascii = "!";
316 		cb.InsertString(1, ascii, strlen(ascii), startSequence);
317 
318 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
319 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 6);
320 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 10);
321 
322 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
323 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 6);
324 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 9);
325 
326 		// Insert a NEL after the '!' to trigger the utf8 line end case ->
327 		// "\xE2!\xC2\x85 \x82\xACa\n \EF\xF0\x90\x8D\x88\n z\n\n" 5 lines
328 
329 		const char *nel = "\xC2\x85";
330 		cb.InsertString(2, nel, strlen(nel), startSequence);
331 
332 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
333 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 3);
334 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 7);
335 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 11);
336 
337 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
338 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 3);
339 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 7);
340 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF32) == 10);
341 	}
342 
343 	SECTION("Delete Multiple lines") {
344 		cb.SetUTF8Substance(true);
345 		cb.AllocateLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16 | SC_LINECHARACTERINDEX_UTF32);
346 
347 		bool startSequence = false;
348 		// 3 lines of text containing 8 bytes
349 		const char *data = "a\n\xF0\x90\x8D\x88\nz\nc";
350 		cb.InsertString(0, data, strlen(data), startSequence);
351 
352 		// Delete first 2 new lines -> "az\nc"
353 		cb.DeleteChars(1, strlen(data) - 4, startSequence);
354 
355 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
356 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 3);
357 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 4);
358 
359 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
360 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 3);
361 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 4);
362 	}
363 
364 	SECTION("Delete Complex") {
365 		cb.SetUTF8Substance(true);
366 		cb.AllocateLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16 | SC_LINECHARACTERINDEX_UTF32);
367 
368 		bool startSequence = false;
369 		// 3 lines of text containing 8 bytes
370 		const char *data = "a\n\xF0\x90\x8D\x88\nz";
371 		cb.InsertString(0, data, strlen(data), startSequence);
372 
373 		// Delete lead byte from character on line 1 ->
374 		// "a\n\x90\x8D\x88\nz"
375 		// line 1 becomes 4 single byte characters
376 		cb.DeleteChars(2, 1, startSequence);
377 
378 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
379 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 2);
380 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 6);
381 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 7);
382 
383 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
384 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 2);
385 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 6);
386 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF32) == 7);
387 
388 		// Delete first new line ->
389 		// "a\x90\x8D\x88\nz"
390 		// Only 2 lines with line 0 containing 5 single byte characters
391 		cb.DeleteChars(1, 1, startSequence);
392 
393 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
394 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 5);
395 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 6);
396 
397 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
398 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 5);
399 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 6);
400 
401 		// Restore lead byte from character on line 0 making a 4-byte character ->
402 		// "a\xF0\x90\x8D\x88\nz"
403 
404 		const char *lead4 = "\xF0";
405 		cb.InsertString(1, lead4, strlen(lead4), startSequence);
406 
407 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
408 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 4);
409 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 5);
410 
411 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF32) == 0);
412 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF32) == 3);
413 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF32) == 4);
414 	}
415 
416 	SECTION("Insert separates new line bytes") {
417 		cb.SetUTF8Substance(true);
418 		cb.AllocateLineCharacterIndex(SC_LINECHARACTERINDEX_UTF16 | SC_LINECHARACTERINDEX_UTF32);
419 
420 		bool startSequence = false;
421 		// 2 lines of text containing 4 bytes
422 		const char *data = "a\r\nb";
423 		cb.InsertString(0, data, strlen(data), startSequence);
424 
425 		// 3 lines of text containing 5 bytes ->
426 		// "a\r!\nb"
427 		const char *ascii = "!";
428 		cb.InsertString(2, ascii, strlen(ascii), startSequence);
429 
430 		REQUIRE(cb.IndexLineStart(0, SC_LINECHARACTERINDEX_UTF16) == 0);
431 		REQUIRE(cb.IndexLineStart(1, SC_LINECHARACTERINDEX_UTF16) == 2);
432 		REQUIRE(cb.IndexLineStart(2, SC_LINECHARACTERINDEX_UTF16) == 4);
433 		REQUIRE(cb.IndexLineStart(3, SC_LINECHARACTERINDEX_UTF16) == 5);
434 	}
435 }
436