1 /**
2 Copyright: Copyright (c) 2014-2018 Andrey Penechko.
3 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4 Authors: Andrey Penechko.
5 */
6 
7 module voxelman.gui.textedit.textbuffer;
8 
9 import std.algorithm : equal, min;
10 import std.array : Appender, appender, empty, back, front, popFront;
11 import std.format : formattedWrite;
12 import std.exception : assumeUnique, assertThrown;
13 import std..string : format;
14 import std.range : isForwardRange, isBidirectionalRange, hasSlicing, dropExactly;
15 import std.typecons : Tuple, Yes;
16 import std.traits : isSomeString;
17 import std.uni : byGrapheme;
18 import std.utf : byDchar, count, stride, strideBack, decode, decodeFront;
19 
20 import core.exception : AssertError;
21 
22 bool equalDchars(S1, S2)(S1 str1, S2 str2)
23 {
24 	return equal(str1.byDchar, str2.byDchar);
25 }
26 
27 void* shortPtr(void* ptr)
28 {
29 	return cast(void*)(cast(uint)ptr >> 4 & 0xFF);
30 	//return ptr;
31 }
32 
33 // Refers to a text slice in a buffer.
34 private struct Piece
35 {
36 	// Offset in code points (bytes for utf-8) from the begining of the buffer.
37 	size_t bufferOffset;
38 
39 	// length in bytes
40 	size_t length;
41 
42 	PieceRef prev;
43 	PieceRef next;
44 
45 	void toString(scope void delegate(const(char)[]) sink)
46 	{
47 		//sink.formattedWrite("P(O %s L %s P %s N %s)",
48 		//	bufferOffset, length, shortPtr(prev), shortPtr(next));
49 		sink.formattedWrite("P %s N %s", shortPtr(prev), shortPtr(next));
50 	}
51 }
52 alias PieceRef = Piece*;
53 
54 struct PieceRestoreRange
55 {
56 	PieceRef first;
57 	PieceRef last;
58 
59 	// length of the whole sequence in bytes
60 	size_t sequenceLength;
61 
62 	// true if need to restore link between first and last
63 	// otherwise need to restore link first.prev.next -> first
64 	// and last.next.prev -> last
65 	bool boundary;
66 
67 	PieceRestoreRange apply(ref size_t currentSequenceLength)
68 	{
69 		if (boundary)
70 		{
71 			auto undoItem = PieceRestoreRange(first.next, last.prev, currentSequenceLength, false);
72 			first.next = last;
73 			last.prev = first;
74 			currentSequenceLength = sequenceLength;
75 			return undoItem;
76 		}
77 		else
78 		{
79 			PieceRestoreRange undoItem;
80 			PieceRef left = first.prev;
81 			PieceRef right = last.next;
82 
83 			if (left.next == right)
84 			{
85 				// we are inserting range first..last between
86 				// left and right, so new sequence is
87 				// left - first..last - right
88 				// generate Op to connect left and right
89 				undoItem = PieceRestoreRange(left, right, currentSequenceLength, true);
90 			}
91 			else
92 			{
93 				// we are replacing some sequence with first..last sequence
94 				// left - curFirst..curLast - right becomes
95 				// left - first..last - right
96 				// generate op to insert curFirst..curLast between left and right
97 				undoItem = PieceRestoreRange(left.next, right.prev, currentSequenceLength, false);
98 			}
99 			first.prev.next = first;
100 			last.next.prev = last;
101 			currentSequenceLength = sequenceLength;
102 			return undoItem;
103 		}
104 	}
105 
106 	void toString()(scope void delegate(const(char)[]) sink)
107 	{
108 		sink.formattedWrite("F[%s]%s L[%s]%s B%s G%s",
109 			shortPtr(first), *first, shortPtr(last), *last, cast(int)boundary, cast(int)group);
110 	}
111 }
112 
113 private struct PieceWithPos
114 {
115 	PieceRef piece;
116 	/// Offset into buffer in bytes
117 	size_t pos;
118 
119 	// Can be used to continue searching for next position.
120 	PieceWithPos pieceAt(size_t index)
121 	{
122 		PieceRef piece = piece;
123 		size_t textPosition = pos;
124 
125 		while (index >= textPosition + piece.length)
126 		{
127 			textPosition += piece.length;
128 			piece = piece.next;
129 		}
130 
131 		return PieceWithPos(piece, textPosition);
132 	}
133 }
134 
135 private enum Previous {no, yes};
136 
137 private PieceStorage pieceStorage()
138 {
139 	PieceStorage storage;
140 	storage.sentinel = new Piece; // bug 17740
141 	storage.sentinel.next = storage.sentinel;
142 	storage.sentinel.prev = storage.sentinel;
143 	return storage;
144 }
145 
146 private struct PieceStorage
147 {
148 	//PieceRef sentinel = new Piece; // bug 17740
149 	PieceRef sentinel;
150 
151 	// length in bytes
152 	size_t length;
153 
154 	void toString(scope void delegate(const(char)[]) sink)
155 	{
156 		auto piece = sentinel.next;
157 		sink.formattedWrite("PieceStorage(S[%s]%s <-> ", shortPtr(sentinel), *sentinel);
158 		while(piece != sentinel)
159 		{
160 			sink.formattedWrite("P[%s]%s <-> ", shortPtr(piece), *piece);
161 			piece = piece.next;
162 		}
163 		sink.formattedWrite("S[%s]%s)", shortPtr(sentinel), *sentinel);
164 	}
165 
166 	// test fresh storage.
167 	unittest
168 	{
169 		PieceStorage storage = pieceStorage();
170 
171 		assert((*storage.sentinel).length == 0);
172 		assert(storage.length == 0);
173 	}
174 
175 	// Find piece at position index.
176 	PieceWithPos pieceAt(size_t index)
177 	{
178 		assert(index < length);
179 		return PieceWithPos(sentinel.next, 0).pieceAt(index);
180 	}
181 
182 	unittest
183 	{
184 		PieceStorage storage = pieceStorage();
185 		//storage.writeln;
186 
187 		auto piece1 = new Piece(10, 2);
188 		storage.insertBack(piece1);
189 		//writefln("piece1 %s", shortPtr(piece1));
190 		//storage.writeln;
191 
192 		auto piece2 = new Piece(5, 2);
193 		storage.insertBack(piece2);
194 		//writefln("piece1 %s", shortPtr(piece2));
195 		//storage.writeln;
196 
197 		auto piece3 = new Piece(1, 2);
198 		storage.insertBack(piece3);
199 		//writefln("piece1 %s", shortPtr(piece3));
200 		//storage.writeln;
201 
202 		assert(storage.pieceAt(0) == PieceWithPos(piece1, 0));
203 		assert(storage.pieceAt(1) == PieceWithPos(piece1, 0));
204 		assert(storage.pieceAt(2) == PieceWithPos(piece2, 2));
205 		assert(storage.pieceAt(3) == PieceWithPos(piece2, 2));
206 		assert(storage.pieceAt(4) == PieceWithPos(piece3, 4));
207 		assert(storage.pieceAt(5) == PieceWithPos(piece3, 4));
208 		assert(storage.pieceAt(2).pieceAt(4) == PieceWithPos(piece3, 4));
209 		assertThrown!AssertError(storage.pieceAt(6));
210 	}
211 
212 	PieceRestoreRange insertFront(PieceRef piece)
213 	{
214 		assert(piece);
215 		return insertAt(piece, 0);
216 	}
217 
218 	unittest
219 	{
220 		PieceStorage storage = pieceStorage();
221 
222 		auto piece1 = new Piece(0, 2);
223 		storage.insertFront(piece1);
224 
225 		assert(storage.sentinel.next == piece1);
226 		assert(storage.sentinel.prev == piece1);
227 		assert(storage.sentinel == storage.sentinel.next.next);
228 		assert(storage.sentinel == storage.sentinel.prev.prev);
229 		assert(storage.length == 2);
230 
231 		auto piece2 = new Piece(4, 2);
232 		storage.insertFront(piece2);
233 		// sentinel <-> piece2 <-> piece1 <-> sentinel
234 
235 		assert(storage.sentinel.next == piece2);
236 		assert(storage.sentinel.next.next == piece1);
237 		assert(storage.sentinel.next.next.next == storage.sentinel);
238 		assert(storage.sentinel.prev == piece1);
239 		assert(storage.sentinel.prev.prev == piece2);
240 		assert(storage.sentinel.prev.prev.prev == storage.sentinel);
241 		assert(storage.length == 4);
242 
243 		assert(piece1.next == storage.sentinel);
244 		assert(piece1.prev == piece2);
245 		assert(piece2.next == piece1);
246 		assert(piece2.prev == storage.sentinel);
247 	}
248 
249 	PieceRestoreRange insertBack(PieceRef piece)
250 	{
251 		assert(piece);
252 		return insertAt(piece, length);
253 	}
254 
255 	unittest
256 	{
257 		PieceStorage storage = pieceStorage();
258 
259 		auto piece1 = new Piece(0, 2);
260 		storage.insertBack(piece1);
261 
262 		assert(storage.sentinel.next == piece1);
263 		assert(storage.sentinel.next.next == storage.sentinel);
264 		assert(storage.length == 2);
265 		// sentinel <-> piece1 <-> sentinel
266 
267 		auto piece2 = new Piece(4, 2);
268 		storage.insertBack(piece2);
269 		// sentinel <-> piece1 <-> piece2 <-> sentinel
270 
271 		assert(storage.sentinel.next == piece1);
272 		assert(storage.sentinel.next.next == piece2);
273 		assert(storage.length == 4);
274 
275 		assert(piece1.next == piece2);
276 		assert(piece2.prev == piece1);
277 		assert(piece2.next == storage.sentinel);
278 		assert(piece1.prev == storage.sentinel);
279 	}
280 
281 	PieceRestoreRange insertAt(PieceRef newPiece, size_t insertPos)
282 	{
283 		assert(newPiece);
284 		assert(newPiece.length > 0);
285 
286 		if (insertPos >= length) // At the end of text
287 		{
288 			auto last = sentinel.prev;
289 
290 			PieceRestoreRange restoreRange = PieceRestoreRange(last, sentinel, length, true);
291 
292 			length += newPiece.length;
293 
294 			last.next = newPiece;
295 			newPiece.prev = last;
296 
297 			newPiece.next = sentinel;
298 			sentinel.prev = newPiece;
299 
300 			return restoreRange;
301 		}
302 
303 		auto pair = pieceAt(insertPos);
304 
305 		if (insertPos == pair.pos) // At the begining of piece
306 		{
307 			PieceRef before = pair.piece.prev;
308 			PieceRef after = pair.piece;
309 
310 			PieceRestoreRange restoreRange = PieceRestoreRange(pair.piece.prev, pair.piece, length, true);
311 
312 			before.next = newPiece;
313 			newPiece.prev = before;
314 
315 			newPiece.next = after;
316 			after.prev = newPiece;
317 
318 			length += newPiece.length;
319 
320 			return restoreRange;
321 		}
322 		else // In the middle of piece
323 		{
324 			auto restoreRange = PieceRestoreRange(pair.piece, pair.piece, length);
325 
326 			length += newPiece.length;
327 
328 			PieceRef before = pair.piece.prev;
329 			PieceRef after = pair.piece.next;
330 
331 			auto leftPieceLength = insertPos - pair.pos;
332 			auto rightPiecePos = pair.piece.bufferOffset + leftPieceLength;
333 
334 			PieceRef leftPiece = createPiece(pair.piece.bufferOffset, leftPieceLength, before, newPiece);
335 			PieceRef rightPiece = createPiece(rightPiecePos, pair.piece.length - leftPieceLength, newPiece, after);
336 
337 			before.next = leftPiece;
338 
339 			newPiece.prev = leftPiece;
340 			newPiece.next = rightPiece;
341 
342 			after.prev = rightPiece;
343 
344 			return restoreRange;
345 		}
346 	}
347 }
348 
349 private PieceRef createPiece(size_t position = 0, size_t length = 0, PieceRef prev = null, PieceRef next = null)
350 {
351 	return new Piece(position, length, prev, next);
352 }
353 
354 import std.stdio;
355 struct PieceTable
356 {
357 	PieceStorage pieces;
358 	// Stores original text followed by inserted text.
359 	Appender!(char[]) buffer;
360 
361 	/// Returns appended text length in bytes
362 	size_t appendBuffer(S)(S text)
363 	{
364 		auto initialLength = buffer.data.length;
365 		buffer ~= text;
366 		size_t textLength = buffer.data.length - initialLength;
367 		return textLength;
368 	}
369 
370 	this(S)(S initialText)
371 	{
372 		pieces = pieceStorage();
373 		if (initialText.empty) return; // sequence cannot contain empty pieces
374 		size_t textLength = appendBuffer(initialText);
375 		pieces.insertBack(createPiece(0, textLength));
376 	}
377 
378 	unittest
379 	{
380 		PieceTable table = PieceTable("test");
381 		assert(table.length == 4);
382 		table = PieceTable("абвгде");
383 		assert(table.length == 12);
384 	}
385 
386 	Range!char opSlice()
387 	{
388 		if (!pieces.length)
389 			return Range!char();
390 
391 		PieceRef first = pieces.sentinel.next;
392 		PieceRef last = pieces.sentinel.prev;
393 		size_t lastOffset = last.length;
394 		return Range!char(first, 0, last, lastOffset,
395 			pieces.length, cast(string)buffer.data);
396 	}
397 
398 	Range!char opSlice(size_t x, size_t y)
399 	{
400 		assert(x <= y, format("%s <= %s", x, y));
401 		assert(y <= length, format("%s <= %s", y, length));
402 		if (x == y) return Range!char();
403 
404 		PieceWithPos first = pieces.pieceAt(x);
405 		PieceWithPos last;
406 
407 		if (y == length)
408 			last = PieceWithPos(pieces.sentinel.prev, pieces.sentinel.prev.length);
409 		else
410 			last = first.pieceAt(y);
411 
412 		return Range!char(
413 			first.piece, x-first.pos,
414 			last.piece, y-last.pos,
415 			y - x,
416 			cast(string)buffer.data);
417 	}
418 
419 	size_t length() @property { return pieces.length; }
420 	alias opDollar = length;
421 
422 	unittest
423 	{
424 		PieceTable table = PieceTable("абвгде");
425 
426 		assert("абвгде".length == 12);
427 		//writefln("%s", "абвгде".length);
428 		assert(table.length == 12);
429 		//writefln("%s", table.length);
430 		assert(table[0..2].length == 2);
431 
432 		//writefln("%s", table[0..$]);
433 		//writefln("%s", table[]);
434 		assert(table[0..$].equalDchars(table[]));
435 		//writefln("1: %s где", table[6..$]);
436 		assert(table[6..$].equalDchars("где"));
437 		assert(table[0..6].equalDchars("абв"));
438 		//assertThrown!AssertError(table[6..2]);
439 		assert(table[0..12].equalDchars(table[]));
440 
441 		auto range = table[0..$];
442 		assert(range.equalDchars(range.save[0..$]));
443 		assert((table[4..8])[0..2].length == 2);
444 		assert((table[4..8])[0..2].equalDchars("в"));
445 	}
446 
447 	/// Remove sequence of text starting at index of length length
448 	/*
449 	 *    |---------| - Piece
450 	 *
451 	 * 1. |XXXXX----| - Remove from begining to the middle
452 	 *
453 	 * 2. |XXXXXXXXX| - Remove whole piece
454 	 *
455 	 * 3. |XXXXXXXXX|XXX... - Remove whole piece and past piece
456 	 *
457 	 * 4. |--XXXXX--| - Remove in the middle of piece
458 	 *
459 	 * 5. |--XXXXXXX| - Remove from middle to the end of piece
460 	 *
461 	 * 6. |--XXXXXXX|XXX... - Remove from middle and past piece
462 	 */
463 	PieceRestoreRange remove(size_t removePos, size_t removeLength)
464 	{
465 		assert(removePos < pieces.length && removeLength > 0);
466 
467 		if (removePos + removeLength > length)
468 		{
469 			removeLength = length - removePos;
470 		}
471 
472 		size_t removeEnd = removePos + removeLength - 1;
473 
474 		// First piece in the sequence.
475 		PieceWithPos first = pieces.pieceAt(removePos);
476 		PieceWithPos last = first.pieceAt(removeEnd);
477 		size_t lastEnd = last.pos + last.piece.length - 1;
478 
479 		PieceRef newPieces = first.piece.prev;
480 
481 		// handle cases 4, 5 and 6.
482 		if (removePos > first.pos)
483 		{
484 			PieceRef subPiece = createPiece(first.piece.bufferOffset, removePos - first.pos, newPieces);
485 			newPieces.next = subPiece;
486 			newPieces = subPiece;
487 		}
488 
489 		// Handle cases 1 and 4
490 		if (removeEnd < lastEnd)
491 		{
492 			auto offset = removeEnd - last.pos + 1;
493 			PieceRef subPiece = createPiece(last.piece.bufferOffset + offset, lastEnd - removeEnd, newPieces);
494 			newPieces.next = subPiece;
495 			newPieces = subPiece;
496 		}
497 
498 		PieceRef after = last.piece.next;
499 		newPieces.next = after;
500 		after.prev = newPieces;
501 
502 		auto undoItem = PieceRestoreRange(first.piece, last.piece, pieces.length);
503 
504 		pieces.length -= removeLength;
505 
506 		return undoItem;
507 	}
508 
509 	unittest
510 	{
511 		PieceTable table = PieceTable("абвгде");
512 
513 		assert(table.length == 12);
514 		auto piece1 = table.pieces.sentinel.next;
515 
516 		table.remove(0, 2); // case 1
517 		assert(table.length == 10);
518 		assert(equalDchars(table[], "бвгде"));
519 
520 		table.remove(0, 12); // case 2
521 		assert(table.length == 0);
522 		assert(equalDchars(table[], ""));
523 
524 		table = PieceTable("абвгде");
525 
526 		table.remove(4, 2); // case 4
527 		assert(table.pieces.sentinel.next.length == 4);
528 		assert(table.pieces.sentinel.next.next.length == 6);
529 		assert(table.pieces.sentinel.prev.length == 6);
530 		assert(table.pieces.sentinel.prev.prev.length == 4);
531 		assert(table.length == 10);
532 		assert(equalDchars(table[], "абгде"));
533 
534 		table.remove(0, 10); // case 3 + case 2
535 		assert(table.length == 0);
536 		assert(equalDchars(table[], ""));
537 
538 		table = PieceTable("абвгде");
539 
540 		table.remove(4, 8); // case 5
541 		assert(table.length == 4);
542 		assert(equalDchars(table[], "аб"));
543 
544 		table = PieceTable("абвгде");
545 
546 		table.remove(4, 2);
547 		table.remove(2, 10); // case 6 + case 2
548 		assert(table.length == 2);
549 		assert(equalDchars(table[], "а"));
550 
551 		table = PieceTable("аб");
552 		table.insert("вг");
553 		table.insert("де");
554 		table.remove(4, 4);
555 		assert(table[].equalDchars("абде"));
556 	}
557 
558 	PieceRestoreRange insert(S)(S text)
559 		if (isSomeString!S || (isInputRange!S && isSomeChar!(ElementType!S)))
560 	{
561 		return insert(length, text);
562 	}
563 
564 	PieceRestoreRange insert(S)(size_t insertPos, S text)
565 		if (isSomeString!S || (isInputRange!S && isSomeChar!(ElementType!S)))
566 	{
567 		size_t bufferPos = buffer.data.length;
568 		size_t textLength = appendBuffer(text);
569 
570 		PieceRef middlePiece = createPiece(bufferPos, textLength);
571 
572 		return pieces.insertAt(middlePiece, insertPos);
573 	}
574 
575 	unittest
576 	{
577 		PieceTable table = PieceTable("абвгде");
578 
579 		table.insert(0, "абв");
580 		assert(table.length == 18);
581 		assert(equalDchars(table[], "абвабвгде"));
582 
583 		table.insert(18, "абв");
584 		assert(table.length == 24);
585 		assert(equalDchars(table[], "абвабвгдеабв"));
586 
587 		table = PieceTable("абвгде");
588 		table.insert(6, "ggg");
589 		assert(table.length == 15);
590 		assert(equalDchars(table[], "абвgggгде"));
591 	}
592 
593 	unittest
594 	{
595 		PieceTable table = PieceTable("\ntest");
596 		table.insert(0, "d");
597 		assert(equalDchars(table[], "d\ntest"));
598 	}
599 
600 	unittest
601 	{
602 		PieceTable table = PieceTable("");
603 
604 		table.insert(4, "абв");
605 		table.insert(4, "абв");
606 		assert(table.length == 12);
607 		assert(equalDchars(table[], "абабвв"));
608 	}
609 }
610 
611 private struct Range(T)
612 {
613 	private PieceRef first;
614 	private size_t firstOffset;
615 	private PieceRef last;
616 	private size_t lastOffset;
617 	private size_t _length;
618 	private string buffer;
619 	size_t length() @property { return _length; }
620 	alias opDollar = length;
621 	bool empty() @property { return _length == 0; }
622 	T front() @property {
623 		static if (is(T == char))
624 			return buffer[first.bufferOffset + firstOffset];
625 		else // dchar
626 		{
627 			auto offset = first.bufferOffset + firstOffset;
628 			auto str = buffer[offset..$];
629 			if (str.length == 0)
630 			{
631 				writefln("str [%s..%s]", offset, buffer.length);
632 			}
633 			return decodeFront!(Yes.useReplacementDchar)(str);
634 		}
635 	}
636 	T back() @property {
637 		static if (is(T == char))
638 			return buffer[first.bufferOffset + firstOffset];
639 		else // dchar
640 		{
641 			uint backLength = strideBack(buffer, last.bufferOffset + lastOffset);
642 			auto pos = last.bufferOffset + lastOffset - backLength;
643 			return decode!(Yes.useReplacementDchar)(buffer, pos);
644 		}
645 	}
646 
647 	void popFront() {
648 		static if (is(T == char))
649 			uint frontLength = 1;
650 		else // dchar
651 			uint frontLength = stride(buffer, first.bufferOffset + firstOffset);
652 
653 		firstOffset += frontLength;
654 		_length -= frontLength;
655 
656 		if (firstOffset == first.length)
657 		{
658 			first = first.next;
659 			firstOffset = 0;
660 		}
661 	}
662 
663 	void popBack()
664 	{
665 		static if (is(T == char))
666 			uint backLength = 1;
667 		else // dchar
668 			uint backLength = strideBack(buffer, last.bufferOffset + lastOffset);
669 
670 		lastOffset -= backLength;
671 		_length -= backLength;
672 
673 		if (lastOffset == 0)
674 		{
675 			last = last.prev;
676 			lastOffset = last.length;
677 		}
678 	}
679 
680 	Range opSlice(size_t x, size_t y)
681 	{
682 		assert(x <= y);
683 		assert(y <= _length);
684 		if (x == y) return Range();
685 
686 		PieceWithPos first = PieceWithPos(this.first, 0).pieceAt(firstOffset + x);
687 		PieceWithPos last = first.pieceAt(y);
688 
689 		return Range(
690 			first.piece, firstOffset + x - first.pos,
691 			last.piece, y-last.pos,
692 			y - x,
693 			buffer);
694 	}
695 
696 	Range save() { return this; }
697 
698 	static if (is(T == dchar))
699 	Range!char byChar() {
700 		return cast(Range!char)this;
701 	}
702 
703 	import voxelman.container.chunkedrange;
704 	ChunkedRange!char toChunkedRange()
705 	{
706 		if (_length == 0) return ChunkedRange!char(null, 0, null, null, &ChunkedRange_popFront);
707 		size_t itemsLeft = _length;
708 		size_t chunkLength = min(itemsLeft, first.length-firstOffset);
709 		auto from = first.bufferOffset + firstOffset;
710 		auto to = from + chunkLength;
711 		itemsLeft -= chunkLength;
712 		auto front = buffer[from..to];
713 		return ChunkedRange!char(
714 			cast(char[])front, itemsLeft, cast(void*)first.next, cast(void*)buffer.ptr, &ChunkedRange_popFront);
715 	}
716 
717 	private static void ChunkedRange_popFront(
718 		ref char[] front,
719 		ref size_t itemsLeft,
720 		ref void* nextPieceData,
721 		ref void* bufferData)
722 	{
723 		PieceRef nextPiece = cast(PieceRef)nextPieceData;
724 		char* buffer = cast(char*)bufferData;
725 
726 		if (itemsLeft == 0)
727 		{
728 			front = null;
729 			return;
730 		}
731 
732 		size_t chunkLength = min(itemsLeft, nextPiece.length);
733 		front = buffer[nextPiece.bufferOffset..nextPiece.bufferOffset+chunkLength];
734 		nextPiece = nextPiece.next;
735 
736 		itemsLeft -= chunkLength;
737 
738 		nextPieceData = cast(void*)nextPiece;
739 	}
740 
741 	void copyInto(char[] sink)
742 	{
743 		assert(sink.length == _length);
744 
745 		auto bytesLeft = _length;
746 
747 		void copyChunk(size_t from, size_t to)
748 		{
749 			auto chunkLen = to - from;
750 			sink[0..chunkLen] = buffer[from..to];
751 			sink = sink[chunkLen..$];
752 			bytesLeft -= chunkLen;
753 		}
754 
755 		// copy first
756 		auto firstFrom = first.bufferOffset + firstOffset;
757 		auto firstLen = min(first.length - firstOffset, bytesLeft);
758 		auto firstTo = firstFrom + firstLen;
759 		copyChunk(firstFrom, firstTo);
760 
761 		// copy other pieces
762 		auto piece = first.next;
763 		while(piece != last.next)
764 		{
765 			auto from = piece.bufferOffset;
766 			auto to = from + min(piece.length, bytesLeft);
767 			copyChunk(from, to);
768 			piece = piece.next;
769 		}
770 
771 		assert(sink.length == 0); // filled sink fully
772 	}
773 }
774 
775 static assert(isForwardRange!(Range!dchar));
776 static assert(hasSlicing!(Range!dchar));
777 static assert(isBidirectionalRange!(Range!dchar));
778 static assert(isForwardRange!(Range!char));
779 static assert(hasSlicing!(Range!char));
780 static assert(isBidirectionalRange!(Range!char));
781 
782 version(unittest) string callCopy(PieceTable table, size_t from, size_t to)
783 {
784 	char[] buf = new char[to-from];
785 	table[from..to].copyInto(buf);
786 	return cast(string)buf;
787 }
788 
789 unittest
790 {
791 	PieceTable table = PieceTable("abcd");
792 	assert(table.callCopy(0, 4).equalDchars("abcd"));
793 	assert(table.callCopy(1, 4).equalDchars("bcd"));
794 	assert(table.callCopy(1, 3).equalDchars("bc"));
795 }