1 module fluid.rope;
2 
3 import std.range;
4 import std.string;
5 import std.algorithm;
6 import std.exception;
7 
8 
9 @safe:
10 
11 
12 /// Rope implementation, providing more efficient modification if there's lots of text.
13 ///
14 /// The `Rope` structure acts as a slice, a view into the rope's contents. If additional text is added to a node stored
15 /// inside, the change will not be reflected by the rope.
16 ///
17 /// `rope.init` is guaranteed to be valid and empty.
18 ///
19 /// See_Also: https://en.wikipedia.org/wiki/Rope_(data_structure)
20 struct Rope {
21 
22     static assert(isInputRange!Rope);
23     static assert(isBidirectionalRange!Rope);
24     static assert(isForwardRange!Rope);
25     static assert(hasSlicing!Rope);
26 
27     /// Content of the rope.
28     const(RopeNode)* node;
29 
30     /// Start and length of the rope, in UTF-8 bytes.
31     size_t start, length;
32 
33     /// Depth of the node.
34     int depth = 1;
35 
36     /// Create a rope holding given text.
37     this(inout const(char)[] text) inout pure nothrow {
38 
39         // No text, stay with Rope.init
40         if (text == "")
41             this(null, 0, 0, 1);
42         else
43             this(new inout RopeNode(text));
44 
45     }
46 
47     /// Create a rope concatenating two other ropes.
48     ///
49     /// Avoids gaps: If either node is empty, copies and becomes the other node.
50     this(inout Rope left, inout Rope right) inout pure nothrow {
51 
52         // No need to create a new node there's empty space
53         if (left.length == 0) {
54 
55             // Both empty, return .init
56             if (right.length == 0)
57                 this(null, 0, 0, 1);
58 
59             // Right node only, clone it
60             else
61                 this(right);
62 
63         }
64 
65         // Only the left side is present
66         else if (right.length == 0)
67             this(left);
68 
69         // Neither is empty, create a new node
70         else 
71             this(new inout RopeNode(left, right));
72 
73     }
74 
75     unittest {
76 
77         import std.stdio;
78 
79         auto bumpy = Rope(
80             new RopeNode(
81                 Rope(""),
82                 Rope("foo"),
83             ),
84         );
85         auto flattened = Rope(
86             Rope(""),
87             Rope("foo"),
88         );
89 
90         assert(bumpy.byNode.equal(["", "foo"]));
91         assert(flattened.byNode.equal(["foo"]));
92 
93         auto c = Rope(
94             Rope("AAA"),
95             Rope(
96                 Rope(""),
97                 Rope("BBB"),
98             ),
99         );
100         assert(c.byNode.equal(["AAA", "BBB"]));
101 
102     }
103 
104     /// Create a rope from given node.
105     this(const inout(RopeNode)* node) inout pure nothrow {
106 
107         // No node given, set all to init
108         if (node is null) return;
109 
110         this.node = node;
111         this.start = 0;
112         this.length = node.length;
113         this.depth = node.isLeaf
114             ? 1
115             : max(node.left.depth, node.right.depth) + 1;
116 
117     }
118 
119     /// Copy a `Rope`.
120     this(inout const Rope rope) inout pure nothrow {
121 
122         this(rope.node, rope.start, rope.length, rope.depth);
123 
124     }
125 
126     private this(inout(RopeNode)* node, size_t start, size_t length, int depth) inout pure nothrow {
127 
128         this.node = node;
129         this.start = start;
130         this.length = length;
131         this.depth = depth;
132 
133     }
134 
135     unittest {
136 
137         auto rope = Rope(Rope.init, Rope("foo"));
138 
139         assert(rope == "foo");
140 
141     }
142 
143     /// Get a hash of the rope.
144     size_t toHash() const nothrow {
145 
146         import std.digest.murmurhash;
147 
148         auto hash = MurmurHash3!32();
149 
150         // Hash each item
151         foreach (item; byNode) {
152 
153             assert(item.isLeaf);
154 
155             hash.put(cast(const ubyte[]) item.value);
156 
157         }
158 
159         return hash.get();
160 
161     }
162 
163     unittest {
164 
165         auto rope = Rope("This is Fluid!");
166         auto rope2 = Rope(
167             Rope("This is "),
168             Rope("Fluid!"),
169         );
170 
171         assert(rope.toHash == rope.toHash);
172         assert(rope.toHash == rope2.toHash);
173         assert(rope2.toHash == rope2.toHash);
174         assert(rope2.left.toHash == rope2.left.toHash);
175         assert(rope2.right.toHash == rope2.right.toHash);
176 
177         assert(rope2.left.toHash != rope2.right.toHash);
178         assert(rope2.left.toHash != rope2.toHash);
179         assert(rope2.left.toHash != rope.toHash);
180         assert(rope2.right.toHash != rope2.toHash);
181         assert(rope2.right.toHash != rope.toHash);
182 
183     }
184 
185     /// Compare the text to a string.
186     bool opEquals(const char[] str) const nothrow {
187 
188         return equal(this[], str[]);
189 
190     }
191 
192     /// Compare two ropes.
193     bool opEquals(const Rope str) const nothrow {
194 
195         return equal(this[], str[]);
196 
197     }
198 
199     /// Assign a new value to the rope.
200     ref opAssign(const char[] str) return nothrow {
201 
202         return opAssign(new RopeNode(str));
203 
204     }
205 
206     /// ditto
207     ref opAssign(RopeNode* node) return nothrow {
208 
209         this.node = node;
210         this.start = 0;
211         this.length = node ? node.length : 0;
212         return this;
213 
214     }
215 
216     /// Concatenate two ropes together.
217     Rope opBinary(string op : "~")(const Rope that) const nothrow {
218 
219         return Rope(this, that).rebalance();
220 
221     }
222 
223     /// Concatenate with a string.
224     Rope opBinary(string op : "~")(const(char)[] text) const nothrow {
225 
226         return Rope(this, Rope(text)).rebalance();
227 
228     }
229 
230     /// ditto
231     Rope opBinaryRight(string op : "~")(const(char)[] text) const nothrow {
232 
233         return Rope(Rope(text), this).rebalance();
234 
235     }
236 
237     /// True if the node is a leaf.
238     bool isLeaf() const nothrow pure {
239 
240         return node is null
241             || node.isLeaf;
242 
243     }
244 
245     Rope save() const nothrow {
246 
247         return this;
248 
249     }
250 
251     /// If true, the rope is empty.
252     bool empty() const nothrow {
253 
254         return node is null
255             || length == 0;
256 
257     }
258 
259     /// Get the first *byte* from the rope.
260     char front() const nothrow {
261 
262         assert(!empty, "Cannot access `.front` in an empty rope");
263 
264         // Load nth character of the leaf
265         if (node.isLeaf)
266             return node.value[start];
267 
268         // Accessing the left node
269         else if (!left.empty)
270             return left.front;
271 
272         // Accessing the right node
273         else
274             return right.front;
275 
276     }
277 
278     /// Remove the first byte from the rope.
279     void popFront() nothrow {
280 
281         assert(!empty, "Cannot `.popFront` in an empty rope");
282 
283         start++;
284         length--;
285 
286     }
287 
288     /// Get the last *byte* from the rope.
289     char back() const nothrow {
290 
291         assert(!empty, "Cannot access `.back` in an empty rope");
292 
293         // Decode the nth character of the leaf
294         if (node.isLeaf)
295             return node.value[start + length - 1];
296 
297         // Accessing the right node
298         else if (!right.empty)
299             return right.back;
300 
301         // Accessing the left node
302         else
303             return left.back;
304 
305     }
306 
307     /// Remove the last byte from the rope.
308     void popBack() nothrow {
309 
310         length--;
311 
312     }
313 
314     /// Return a mutable slice.
315     Rope opIndex() const nothrow {
316 
317         return this;
318 
319     }
320 
321     /// Get character at given index.
322     char opIndex(size_t index) const nothrow {
323 
324         assert(index < length, format!"Given index [%s] exceeds rope length %s"(index, length).assumeWontThrow);
325 
326         // Access the nth byte of the leaf
327         if (node.isLeaf)
328             return node.value[start + index];
329 
330         // Accessing the left node
331         else if (index < left.length)
332             return left[index];
333 
334         // Accessing the right node
335         else
336             return right[index - left.length];
337 
338     }
339 
340     /// Get the rope's length.
341     size_t opDollar() const nothrow {
342 
343         return length;
344 
345     }
346 
347     /// Slice the rope.
348     Rope opIndex(size_t[2] slice) const nothrow {
349 
350         assert(slice[0] <= length,
351             format!"Left boundary of slice [%s .. %s] exceeds rope length %s"(slice[0], slice[1], length)
352                 .assumeWontThrow);
353         assert(slice[1] <= length,
354             format!"Right boundary of slice [%s .. %s] exceeds rope length %s"(slice[0], slice[1], length)
355                 .assumeWontThrow);
356         assert(slice[0] <= slice[1],
357             format!"Right boundary of slice [%s .. %s] is greater than left boundary"(slice[0], slice[1])
358                 .assumeWontThrow);
359 
360         // .init stays the same
361         if (node is null) return Rope.init;
362 
363         slice[] += start;
364 
365         // Flatten if slicing into a specific side of the slice
366         // e.g. Rope(Rope("foo"), Rope("bar"))[0..3] returns Rope("foo")
367         if (!isLeaf) {
368 
369             const divide = node.left.length;
370 
371             // Slicing into the left node
372             if (slice[1] <= divide)
373                 return node.left[slice];
374 
375             // Slicing into the right node
376             else if (slice[0] >= divide)
377                 return node.right[slice[0] - divide .. slice[1] - divide];
378 
379         }
380 
381         // Overlap or a leaf: return both as they are
382         return Rope(node, slice[0], slice[1] - slice[0], depth);
383 
384     }
385 
386     unittest {
387 
388         auto myRope = Rope(
389             Rope("foo"),
390             Rope("bar"),
391         );
392 
393         assert(myRope[0..3].isLeaf);
394         assert(myRope[0..3] == "foo");
395         assert(myRope[3..6].isLeaf);
396         assert(myRope[3..6] == "bar");
397         assert(myRope[1..2].isLeaf);
398         assert(myRope[1..2] == "o");
399         assert(myRope[4..5].isLeaf);
400         assert(myRope[4..5] == "a");
401 
402         auto secondRope = Rope(
403             myRope,
404             myRope,
405         );
406 
407         assert(secondRope[1..$][0..2].isLeaf);
408         assert(secondRope[1..$][0..2] == "oo");
409 
410     }
411 
412     size_t[2] opSlice(size_t dim : 0)(size_t left, size_t right) const nothrow {
413 
414         return [left, right];
415 
416     }
417 
418     /// Returns: 
419     ///     True if the rope is fairly balanced.
420     /// Params:
421     ///     maxDistance = Maximum allowed `depth` difference
422     bool isBalanced(int maxDistance = 3) const nothrow {
423 
424         // Leaves are always balanced
425         if (isLeaf) return true;
426 
427         const depthDifference = node.left.depth - node.right.depth;
428 
429         return depthDifference >= -maxDistance
430             && depthDifference <= +maxDistance;
431 
432     }
433 
434     /// Returns: 
435     ///     If the rope is unbalanced, returns a copy of the rope, optimized to improve reading performance. 
436     ///     If the rope is already balanced, returns the original rope unmodified.
437     /// Params:
438     ///     maxDistance = Maximum allowed `depth` difference before rebalancing happens.
439     Rope rebalance() const nothrow
440     out (r) {
441         assert(r.isBalanced, 
442             format("rebalance(%s) failed. Depth %s (left %s, right %s)", this, depth, left.depth, right.depth)
443                 .assumeWontThrow);
444     }
445     do {
446 
447         import std.array;
448 
449         if (isBalanced) return this;
450 
451         return merge(byNode.array);
452 
453     }
454 
455     /// Returns: A rope created by concatenating an array of leaves together.
456     static Rope merge(Rope[] leaves) nothrow {
457 
458         if (leaves.length == 0)
459             return Rope.init;
460         else if (leaves.length == 1)
461             return leaves[0];
462         else if (leaves.length == 2)
463             return Rope(leaves[0], leaves[1]);
464         else
465             return Rope(merge(leaves[0 .. $/2]), merge(leaves[$/2 .. $]));
466 
467     }
468 
469 
470     unittest {
471 
472         auto a = Rope();
473         assert(a.depth == 1);
474 
475         auto b = Rope("foo");
476         assert(b.depth == 1);
477 
478         auto c = Rope(
479             Rope("foo"),
480             Rope("bar"),
481         );
482         assert(c.depth == 2);
483 
484         auto d = Rope(b, c);
485         assert(d.depth == 3);
486 
487         auto e = Rope(d, d);
488         assert(e.depth == 4);
489 
490     }
491 
492     /// Get a leaf node that is a subrope starting with the given index. The length of the node may vary, and does not
493     /// have to reach the end of the rope.
494     Rope leafFrom(size_t start) const nothrow
495     out (r; r.isLeaf)
496     do {
497 
498         auto slice = this[start..$];
499 
500         // The slice is a leaf node, return it
501         if (slice.isLeaf)
502             return slice;
503 
504         // Not a leaf, get the chunk containing the node
505         if (slice.left.empty)
506             return slice.right.leafFrom(0);
507         else
508             return slice.left.leafFrom(0);
509 
510     }
511 
512     ///
513     unittest {
514 
515         auto myRope = Rope(
516             Rope("Hello, "),
517             Rope(
518                 Rope("Flu"),
519                 Rope("id"),
520             ),
521         );
522 
523         assert(myRope.leafFrom(0) == Rope("Hello, "));
524         assert(myRope.leafFrom(7) == Rope("Flu"));
525         assert(myRope.leafFrom(10) == Rope("id"));
526 
527         assert(myRope.leafFrom(2) == Rope("llo, "));
528         assert(myRope.leafFrom(7) == Rope("Flu"));
529         assert(myRope.leafFrom(8) == Rope("lu"));
530         assert(myRope.leafFrom(9) == Rope("u"));
531 
532         assert(myRope.leafFrom(myRope.length) == Rope.init);
533 
534     }
535 
536     /// Get the left side of the rope
537     Rope left() const nothrow {
538 
539         if (node is null) return Rope.init;
540 
541         const start = min(node.left.length, start);
542         const end = min(node.left.length, start + length);
543 
544         return node.left[start .. end];
545 
546     }
547 
548     unittest {
549 
550         auto a = Rope("ABC");
551         auto b = Rope("DEF");
552         auto ab = Rope(a, b);
553 
554         assert(ab.left.equal("ABC"));
555         assert(ab[1..$].left.equal("BC"));
556         assert(ab[3..$].left.equal(""));
557         assert(ab[4..$].left.equal(""));
558         assert(ab[0..4].left.equal("ABC"));
559         assert(Rope(ab.node, 0, 3, 1).left.equal("ABC"));
560         assert(Rope(ab.node, 0, 2, 1).left.equal("AB"));
561         assert(Rope(ab.node, 0, 1, 1).left.equal("A"));
562         assert(ab[0..0].left.equal(""));
563         assert(ab[1..1].left.equal(""));
564         assert(ab[4..4].left.equal(""));
565 
566         assert(ab[0..3].equal("ABC"));
567         assert(ab[0..2].equal("AB"));
568         assert(ab[0..1].equal("A"));
569 
570     }
571 
572     unittest {
573 
574         auto a = Rope("ABC")[1..$];
575         auto b = Rope("DEF")[1..$];
576         auto ab = Rope(a, b);
577 
578         assert(ab.left.equal("BC"));
579         assert(ab[1..$].left.equal("C"));
580         assert(ab[3..$].left.equal(""));
581         assert(ab[4..$].left.equal(""));
582         assert(ab[0..4].left.equal("BC"));
583         assert(ab[0..3].left.equal("BC"));
584         assert(Rope(ab.node, 0, 2, 1).left.equal("BC"));
585         assert(Rope(ab.node, 0, 1, 1).left.equal("B"));
586         assert(ab[0..0].left.equal(""));
587         assert(ab[1..1].left.equal(""));
588         assert(ab[4..4].left.equal(""));
589 
590         assert(ab[0..2].equal("BC"));
591         assert(ab[0..1].equal("B"));
592 
593     }
594 
595     /// Get the right side of the rope
596     Rope right() const nothrow {
597 
598         if (node is null) return Rope.init;
599 
600         const leftStart = min(node.left.length, start);
601         const leftEnd = min(node.left.length, start + length);
602 
603         const start = min(node.right.length, this.start - leftStart);
604         const end = min(node.right.length, this.start + length - leftEnd);
605 
606         return node.right[start .. end];
607 
608     }
609 
610     unittest {
611 
612         auto a = Rope("ABC");
613         auto b = Rope("DEF");
614         auto ab = Rope(a, b);
615 
616         assert(ab.right.equal("DEF"));
617         assert(ab[1..$].right.equal("DEF"));
618         assert(Rope(ab.node, 3, 3, 1).right.equal("DEF"));
619         assert(Rope(ab.node, 4, 2, 1).right.equal("EF"));
620         assert(Rope(ab.node, 4, 1, 1).right.equal("E"));
621         assert(Rope(ab.node, 3, 2, 1).right.equal("DE"));
622         assert(ab[2..$-1].right.equal("DE"));
623         assert(ab[1..1].right.equal(""));
624         assert(ab[4..4].right.equal(""));
625 
626         assert(ab[3..$].equal("DEF"));
627         assert(ab[4..$].equal("EF"));
628         assert(ab[4..$-1].equal("E"));
629         assert(ab[3..$-1].equal("DE"));
630 
631     }
632 
633     unittest {
634 
635         auto a = Rope("ABC")[1..$];  // BC
636         auto b = Rope("DEF")[1..$];  // EF
637         auto ab = Rope(a, b);
638 
639         assert(ab.right.equal("EF"));
640         assert(ab[1..$].right.equal("EF"));
641         assert(Rope(ab.node, 3, 1, 1).right.equal("F"));
642         assert(Rope(ab.node, 4, 0, 1).right.equal(""));
643         assert(Rope(ab.node, 1, 2, 1).right.equal("E"));
644         assert(Rope(ab.node, 2, 1, 1).right.equal("E"));
645         assert(Rope(ab.node, 3, 0, 1).right.equal(""));
646         assert(ab[1..1].right.equal(""));
647         assert(ab[4..4].right.equal(""));
648 
649         assert(ab[3..$].equal("F"));
650         assert(ab[4..$].equal(""));
651         assert(ab[1..$-1].right.equal("E"));
652         assert(ab[2..$-1].equal("E"));
653         assert(ab[3..$-1].equal(""));
654 
655     }
656 
657     /// Get the value of this rope. Only works for leaf nodes.
658     const(char)[] value() const nothrow {
659 
660         // Null node → null string
661         if (node is null) return null;
662 
663         return node.value[start .. start + length];
664 
665     }
666 
667     /// Split the rope, creating a new root node that connects the left and right side of the split.
668     ///
669     /// This functions never returns leaf nodes, but either side of the node may be empty.
670     Rope split(size_t index) const nothrow
671     out (r; !r.node.isLeaf)
672     do {
673 
674         assert(index <= length, format!"Split index (%s) exceeds rope length %s"(index, length).assumeWontThrow);
675 
676         auto left = this.left;
677         auto right = this.right;
678 
679         const(RopeNode)* result;
680 
681         // Leaf node, split by slicing
682         if (node.isLeaf)
683             result = new RopeNode(this[0..index], this[index..$]);
684 
685         // Already split
686         else if (index == left.length)
687             return this;
688 
689         // Splitting inside left node
690         else if (index < left.length) {
691 
692             auto div = left.split(index);
693 
694             result = new RopeNode(
695                 div.left,
696                 Rope(div.right, right),
697             );
698 
699         }
700 
701         // Splitting inside right node
702         else {
703 
704             auto div = right.split(index - left.length);
705 
706             result = new RopeNode(
707                 Rope(left, div.left),
708                 div.right,
709             );
710 
711         }
712 
713         return Rope(result);
714 
715     }
716 
717     unittest {
718 
719         auto a = Rope("Hello, World!");
720         auto b = a.split(7);
721 
722         assert(b.node.left == "Hello, ");
723         assert(b.node.right == "World!");
724 
725         auto startSplit = a.split(0);
726 
727         assert(startSplit.node.left == "");
728         assert(startSplit.node.right == a);
729 
730         auto endSplit = a.split(a.length);
731 
732         assert(endSplit.node.left == a);
733         assert(endSplit.node.right == "");
734 
735         auto c = a[1..$-4].split(6);
736 
737         assert(c.node.left == "ello, ");
738         assert(c.node.right == "Wo");
739 
740     }
741 
742     unittest {
743 
744         auto myRope = Rope(
745             Rope(
746                 Rope("HËl"),
747                 Rope("lo"),
748             ),
749             Rope(
750                 Rope(", "),
751                 Rope(
752                     Rope("Wor"),
753                     Rope("ld!"),
754                 ),
755             )
756         );
757 
758         assert(myRope.equal("HËllo, World!"));
759         assert(myRope[1..$].equal("Ëllo, World!"));
760 
761         {
762             auto split = myRope[1..$].split(6);
763 
764             assert(split.equal("Ëllo, World!"));
765             assert(split.left.equal("Ëllo,"));
766             assert(split.right.equal(" World!"));
767         }
768         {
769             auto split = myRope[5..$-3].split(4);
770 
771             assert(split.equal("o, Wor"));
772             assert(split.left.equal("o, W"));
773             assert(split.right.equal("or"));
774         }
775         {
776             auto split = myRope.split(6);
777             assert(split == myRope);
778             assert(split.left.equal("HËllo"));
779         }
780         {
781             auto split = myRope[1..$].split(5);
782             assert(split == myRope[1..$]);
783             assert(split.left.equal("Ëllo"));
784         }
785         {
786             auto split = myRope.split(0);
787             assert(split.left.equal(""));
788             assert(split.right.equal(myRope));
789         }
790         {
791             auto split = myRope.split(myRope.length);
792             assert(split.left.equal(myRope));
793             assert(split.right.equal(""));
794         }
795         {
796             auto split = myRope[1..$].split(0);
797 
798             assert(split.left.equal(""));
799             assert(split.right.equal("Ëllo, World!"));
800         }
801         {
802             auto split = myRope[1..$-1].split(myRope.length-2);
803             assert(split.left.equal("Ëllo, World"));
804             assert(split.right.equal(""));
805         }
806 
807     }
808 
809     /// Insert a new node into the rope.
810     Rope insert(size_t index, Rope value) const nothrow {
811 
812         // Perform a split
813         auto split = split(index);
814 
815         // Insert the element
816         return Rope(split.left, Rope(value, split.right)).rebalance();
817 
818     }
819 
820     unittest {
821 
822         auto a = Rope("Hello, !");
823         auto b = a.insert(7, Rope("World"));
824 
825         assert(a.equal("Hello, !"));
826         assert(b.equal("Hello, World!"));
827 
828         assert(Rope("World!")
829             .insert(0, Rope("Hello, "))
830             .equal("Hello, World!"));
831         assert(Rope("Hellø, ")
832             .insert(8, Rope("Fluid!"))
833             .equal("Hellø, Fluid!"));
834 
835     }
836 
837     unittest {
838 
839         auto a = Rope("Hello, !");
840         auto b = a.insert(7, Rope("rl"));
841 
842         assert(b.equal("Hello, rl!"));
843 
844         auto c = b.insert(7, Rope("Wo"));
845 
846         assert(c.equal("Hello, Worl!"));
847 
848         auto d = c.insert(11, Rope("d"));
849 
850         assert(d.equal("Hello, World!"));
851 
852     }
853 
854     /// Replace value between two indexes with a new one.
855     ///
856     /// Params:
857     ///     low   = Low index, inclusive; First index to delete.
858     ///     high  = High index, exclusive; First index after the newly inserted fragment to keep.
859     ///     value = Value to insert.
860     Rope replace(size_t low, size_t high, Rope value) const nothrow {
861 
862         assert(low <= high,
863             format!"Low boundary of replace slice [%s .. %s] is greater than the high boundary"(low, high)
864                 .assumeWontThrow);
865         assert(high <= length,
866             format!"Replace slice [%s .. %s] exceeds Rope length %s"(low, high, length)
867                 .assumeWontThrow);
868 
869         // Return the value as-is if the node is empty
870         if (length == 0)
871             return value;
872 
873         // Split the rope in both points
874         const leftSplit = split(low);
875         const rightSplit = split(high);
876 
877         return Rope(
878             leftSplit.left,
879             Rope(
880                 value,
881                 rightSplit.right,
882             ),
883         ).rebalance();
884 
885     }
886 
887     unittest {
888 
889         auto a = Rope("Hello, World!");
890         auto b = a.replace(7, 12, Rope("Fluid"));
891 
892         assert(b == "Hello, Fluid!");
893 
894         auto c = Rope("Foo Bar Baz Ban");
895         auto d = c.replace(4, 12, Rope.init);
896 
897         assert(d == "Foo Ban");
898 
899     }
900 
901     unittest {
902 
903         auto a = Rope(
904             Rope("Hello"),
905             Rope("Fluid"),
906         );
907         auto b = a.replace(3, 6, Rope("LOf"));
908 
909         assert(b == "HelLOfluid");
910         assert(b.byNode.equal(["Hel", "LOf", "luid"]));
911 
912         auto c = b.replace(3, 6, Rope("~~~"));
913 
914         assert(c.byNode.equal(["Hel", "~~~", "luid"]));
915 
916         auto d = c.replace(0, 3, Rope("***"));
917 
918         assert(d.byNode.equal(["***", "~~~", "luid"]));
919 
920         auto e = d.replace(3, 10, Rope("###"));
921 
922         assert(e.byNode.equal(["***", "###"]));
923 
924     }
925 
926     unittest {
927 
928         auto a = Rope("Hello, World!");
929 
930         // Replacing with an empty node should effectively split
931         a = a.replace(7, 12, Rope(""));
932         assert(a.byNode.equal(["Hello, ", "!"]));
933 
934         // Insert characters into the text by replacing a whole node
935         a = a.replace(7, 7, Rope("a"));
936         assert(a.byNode.equal(["Hello, ", "a", "!"]));
937 
938         a = a.replace(7, 8, Rope("ab"));
939         assert(a.byNode.equal(["Hello, ", "ab", "!"]));
940 
941         a = a.replace(7, 9, Rope("abc"));
942         assert(a.byNode.equal(["Hello, ", "abc", "!"]));
943 
944         // Now, reuse the same node
945         auto node = new RopeNode("abcd");
946 
947         a = a.replace(7, 10, Rope(node));
948         assert(a.byNode.equal(["Hello, ", "abcd", "!"]));
949         assert(a == "Hello, abcd!");
950 
951         // Adding the node should have no effect since slices weren't updated
952         node.value = "abcde";
953         assert(a == "Hello, abcd!");
954 
955         // Update the slices
956         a = a.replace(7, 11, Rope(node));
957         assert(a.byNode.equal(["Hello, ", "abcde", "!"]));
958         assert(a == "Hello, abcde!");
959 
960     }
961 
962     unittest {
963 
964         auto a = Rope("Rope");
965 
966         a = a.replace(0, 2, Rope("Car"));
967 
968         assert(a.byNode.equal(["Car", "pe"]));
969 
970         a = a.replace(5, 5, Rope(" diem"));
971 
972         assert(a.byNode.equal(["Car", "pe", " diem"]));
973 
974     }
975 
976     unittest {
977 
978         auto a = Rope();
979 
980         a = a.replace(0, 0, Rope("foo"));
981 
982         assert(a.byNode.equal(["foo"]));
983         assert(a.isLeaf);
984 
985     }
986 
987     /// Replace given substring with a new value
988     Rope replace(String)(String oldValue, Rope value) const nothrow {
989 
990         const start = this[].indexOf(oldValue);
991         const end = start + oldValue.length;
992 
993         // Substring not found, do nothing
994         if (start == -1) return this;
995 
996         return replace(start, end, value);
997 
998     }
999 
1000     unittest {
1001 
1002         assert(Rope("foo baz baz").replace("baz", Rope("bar")) == "foo bar baz");
1003 
1004     }
1005 
1006     /// Append text to the rope.
1007     ref Rope opOpAssign(string op : "~")(const(char)[] value) return nothrow {
1008 
1009         auto left = this;
1010 
1011         return this = Rope(left, Rope(value)).rebalance;
1012 
1013     }
1014 
1015     /// Append another rope to the rope.
1016     ref Rope opOpAssign(string op : "~")(const Rope value) return nothrow {
1017 
1018         auto left = this;
1019 
1020         return this = Rope(left, value).rebalance();
1021 
1022     }
1023 
1024     /// Iterate the rope by characters.
1025     auto byDchar() const {
1026 
1027         import std.utf : byDchar;
1028 
1029         return byDchar(this[]);
1030 
1031     }
1032 
1033     /// Count characters in the string. Iterates the whole rope.
1034     size_t countCharacters() const {
1035 
1036         return byDchar.walkLength;
1037 
1038     }
1039 
1040     /// Perform deep-first search through leaf nodes of the rope.
1041     auto byNode() inout {
1042 
1043         import std.container.dlist;
1044 
1045         struct ByNode {
1046 
1047             DList!Rope ancestors;
1048             Rope front;
1049             bool empty;
1050 
1051             /// Switch to the next sibling or ascend.
1052             void popFront() @safe nothrow {
1053 
1054                 assert(!empty);
1055 
1056                 // No ancestors remain, stop
1057                 if (ancestors.empty) {
1058                     empty = true;
1059                     return;
1060                 }
1061 
1062                 // Get the last ancestor; remove it so we don't return to it
1063                 auto parent = ancestors.back;
1064                 ancestors.removeBack;
1065 
1066                 // Switch to its right sibling
1067                 descend(parent.right);
1068 
1069             }
1070 
1071             /// Descend into given node.
1072             void descend(Rope node) @safe nothrow {
1073 
1074                 // Leaf node, set as front
1075                 if (node.isLeaf) {
1076                     front = node;
1077                     return;
1078                 }
1079 
1080                 // Descend
1081                 ancestors.insertBack(node);
1082 
1083                 // Start from the left side
1084                 descend(node.left);
1085 
1086             }
1087 
1088         }
1089 
1090         auto result = ByNode();
1091         result.descend(this);
1092         return result;
1093 
1094     }
1095 
1096     unittest {
1097 
1098         auto mr = Rope(
1099             Rope("a"),
1100             Rope(
1101                 Rope(
1102                     Rope(
1103                         Rope("b"),
1104                         Rope(
1105                             Rope("c"),
1106                             Rope("d"),
1107                         ),
1108                     ),
1109                     Rope(
1110                         Rope("e"),
1111                         Rope("f"),
1112                     ),
1113                 ),
1114                 Rope("g")
1115             ),
1116         );
1117 
1118         assert(mr.byNode.equal([
1119             Rope("a"),
1120             Rope("b"),
1121             Rope("c"),
1122             Rope("d"),
1123             Rope("e"),
1124             Rope("f"),
1125             Rope("g"),
1126         ]));
1127 
1128     }
1129 
1130     /// Get line in the rope by a byte index.
1131     /// Returns: A rope slice with the line containing the given index.
1132     Rope lineByIndex(KeepTerminator keepTerminator = No.keepTerminator)(size_t index) const
1133     in (index >= 0 && index <= length, format!"Index %s is out of bounds of Rope of length %s"(index, length))
1134     do {
1135 
1136         import fluid.typeface : Typeface;
1137 
1138         auto back  = Typeface.lineSplitter(this[0..index].retro).front;
1139         auto front = Typeface.lineSplitter!keepTerminator(this[index..$]).front;
1140 
1141         static assert(is(ElementType!(typeof(back)) == char));
1142         static assert(is(ElementType!(typeof(front)) == char));
1143 
1144         const backLength  = back.walkLength;
1145         const frontLength = front.walkLength;
1146 
1147         // Combine everything on the same line, before and after the cursor
1148         return this[index - backLength .. index + frontLength];
1149 
1150     }
1151 
1152     unittest {
1153 
1154         Rope root;
1155         assert(root.lineByIndex(0) == "");
1156 
1157         root = root ~ "aaą\nbbČb\n c \r\n\n **Ą\n";
1158         assert(root.lineByIndex(0) == "aaą");
1159         assert(root.lineByIndex(1) == "aaą");
1160         assert(root.lineByIndex(4) == "aaą");
1161         assert(root.lineByIndex(5) == "bbČb");
1162         assert(root.lineByIndex(7) == "bbČb");
1163         assert(root.lineByIndex(10) == "bbČb");
1164         assert(root.lineByIndex(11) == " c ");
1165         assert(root.lineByIndex!(Yes.keepTerminator)(11) == " c \r\n");
1166         assert(root.lineByIndex(16) == "");
1167         assert(root.lineByIndex!(Yes.keepTerminator)(16) == "\n");
1168         assert(root.lineByIndex(17) == " **Ą");
1169         assert(root.lineByIndex(root.value.length) == "");
1170         assert(root.lineByIndex!(Yes.keepTerminator)(root.value.length) == "");
1171 
1172     }
1173 
1174     /// Get the column the given index is on.
1175     /// Returns:
1176     ///     Return value depends on the type fed into the function. `column!dchar` will use characters and `column!char`
1177     ///     will use bytes. The type does not have effect on the input index.
1178     ptrdiff_t column(Chartype)(size_t index) const {
1179 
1180         import std.utf : byUTF;
1181         import fluid.typeface : Typeface;
1182 
1183         // Get last line
1184         return Typeface.lineSplitter(this[0..index].retro).front
1185 
1186             // Count characters
1187             .byUTF!Chartype.walkLength;
1188 
1189     }
1190 
1191     unittest {
1192 
1193         Rope root;
1194         assert(root.column!dchar(0) == 0);
1195 
1196         root = Rope(" aąąą");
1197         assert(root.column!dchar(8) == 5);
1198         assert(root.column!char(8) == 8);
1199 
1200         root = Rope(" aąąąO\n");
1201         assert(root.column!dchar(10) == 0);
1202         assert(root.column!char(10) == 0);
1203 
1204         root = Rope(" aąąąO\n ");
1205         assert(root.column!dchar(11) == 1);
1206 
1207         root = Rope(" aąąąO\n Ω = 1+2");
1208         assert(root.column!dchar(14) == 3);
1209         assert(root.column!char(14) == 4);
1210 
1211     }
1212 
1213     /// Get the index of the start or end of the line — from index of any character on the same line.
1214     size_t lineStartByIndex(size_t index) {
1215 
1216         return index - column!char(index);
1217 
1218     }
1219 
1220     /// ditto
1221     size_t lineEndByIndex(size_t index) {
1222 
1223         return lineStartByIndex(index) + lineByIndex(index).length;
1224 
1225     }
1226 
1227     ///
1228     unittest {
1229 
1230         auto rope = Rope("Hello, World!\nHello, Fluid!");
1231 
1232         assert(rope.lineStartByIndex(5) == 0);
1233         assert(rope.lineEndByIndex(5) == 13);
1234         assert(rope.lineStartByIndex(18) == 14);
1235         assert(rope.lineEndByIndex(18) == rope.length);
1236 
1237     }
1238 
1239     struct DiffRegion {
1240 
1241         size_t start;
1242         Rope first;
1243         Rope second;
1244 
1245         alias asArray this;
1246 
1247         /// Returns true if the two compared ropes are identical.
1248         bool isSame() const {
1249 
1250             return first.empty && second.empty;
1251 
1252         }
1253 
1254         inout(Rope)[2] asArray() inout {
1255 
1256             return [first, second];
1257 
1258         }
1259 
1260     }
1261 
1262     /// Find the difference between the two ropes, in terms of a single contiguous region.
1263     /// Params:
1264     ///     other = Rope to compare this rope to.
1265     /// Returns:
1266     ///     The region containing the change, in terms of two subropes. The first rope is a subrope exclusive to this
1267     ///     rope, and the second is a subrope exclusive to the other.
1268     ///
1269     ///     The return value includes a `start` field which indicates the exact index the resulting range starts with.
1270     DiffRegion diff(const Rope other) const {
1271 
1272         if (this is other) {
1273             return DiffRegion.init;
1274         }
1275 
1276         if (!isLeaf) {
1277 
1278             // Left side is identical, compare right side only
1279             if (left is other.left) {
1280 
1281                 auto result = right.diff(other.right);
1282 
1283                 return DiffRegion(left.length + result.start, result.first, result.second);
1284 
1285             }
1286 
1287             // Or, right side is identical
1288             else if (right is other.right) {
1289 
1290                 return left.diff(other.left);
1291 
1292             }
1293 
1294         }
1295 
1296         // Perform string comparison
1297         const prefix = commonPrefix(
1298             BasicRopeRange(this[]), 
1299             BasicRopeRange(other[])).length;
1300         const suffix = commonPrefix(this[prefix..$].retro, other[prefix..$].retro).length;
1301 
1302         const start = prefix;
1303         const thisEnd = this.length - suffix;
1304         const otherEnd = other.length - suffix;
1305 
1306         return DiffRegion(start,
1307             thisEnd == 0
1308                 ? Rope()
1309                 : this[start .. thisEnd],
1310             otherEnd == 0
1311                 ? Rope()
1312                 : other[start .. otherEnd]);
1313 
1314     }
1315 
1316     unittest {
1317 
1318         auto rope1 = Rope("Hello, World!");
1319         auto rope2 = Rope("Hello, Fluid!");
1320         auto diff = rope1.diff(rope2);
1321 
1322         assert(diff.start == 7);
1323         assert(diff[].equal(["Worl", "Flui"]));
1324 
1325     }
1326 
1327     unittest {
1328 
1329         auto rope1 = Rope("Hello!");
1330         auto rope2 = Rope("Hello!");
1331 
1332         assert(rope1.diff(rope2)[] == [Rope(), Rope()]);
1333 
1334     }
1335 
1336     unittest {
1337 
1338         auto rope1 = Rope(
1339             Rope("Hello, "),
1340             Rope("World!"));
1341         auto rope2 = Rope("Hello, Fluid!");
1342         auto diff = rope1.diff(rope2);
1343 
1344         assert(diff.start == 7);
1345         assert(diff[].equal([
1346             "Worl",
1347             "Flui",
1348         ]));
1349 
1350     }
1351 
1352     unittest {
1353 
1354         auto rope = Rope(
1355             Rope(
1356                 Rope("auto rope"),
1357                 Rope(
1358                     Rope("1"),
1359                     Rope(" = Rope();"),
1360                 )
1361             ),
1362             Rope(
1363                 Rope(
1364                     Rope("auto rope2 = Rope(\""),
1365                     Rope("Hello, Fluid!")
1366                 ),
1367                 Rope(
1368                     Rope("\");"),
1369                     Rope("auto diff = rope1.diff(rope2);"),
1370                 ),
1371             )
1372         );
1373 
1374         assert(
1375             rope.replace(0, 4, Rope("Rope"))
1376                 .diff(rope)
1377             == DiffRegion(0, Rope("Rope"), Rope("auto"))
1378         );
1379 
1380         auto tmp = rope;
1381 
1382         auto findRope1() {
1383 
1384             return tmp.indexOf("rope1");
1385 
1386         }
1387 
1388         assert(
1389             rope
1390                 .replace("rope1", Rope("rope"))
1391                 .replace("rope1", Rope("rope"))
1392                 .diff(rope)
1393             == DiffRegion(
1394                 9,
1395                 Rope(` = Rope();auto rope2 = Rope("Hello, Fluid!");auto diff = rope`),
1396                 Rope(`1 = Rope();auto rope2 = Rope("Hello, Fluid!");auto diff = rope1`))
1397         );
1398 
1399     }
1400 
1401     unittest {
1402 
1403         auto core = Rope("foobar");
1404         auto rope1 = core[0..5];
1405         auto rope2 = rope1.replace(0, 5, core);
1406 
1407         assert(rope1 == "fooba");
1408         assert(rope2 == "foobar");
1409         assert(rope1.diff(rope2) == DiffRegion(5, Rope(""), Rope("r")));
1410 
1411     }
1412 
1413     unittest {
1414 
1415         auto core = Rope("foobar");
1416         auto rope1 = Rope(core[0..5], Rope(";"));
1417         auto rope2 = rope1.replace(0, 5, core);
1418 
1419         assert(rope1 == "fooba;");
1420         assert(rope2 == "foobar;");
1421         assert(rope1.diff(rope2) == DiffRegion(5, Rope(""), Rope("r")));
1422 
1423     }
1424 
1425     unittest {
1426 
1427         auto core = Rope("foobar");
1428         auto rope1 = Rope(
1429             Rope("return "),
1430             Rope(
1431                 core[0..5],
1432                 Rope(`("Hello, World!");`)
1433             ),
1434         );
1435         auto rope2 = rope1.replace(7, 7+5, core);
1436 
1437         assert(rope1 == `return fooba("Hello, World!");`);
1438         assert(rope2 == `return foobar("Hello, World!");`);
1439         assert(rope1.diff(rope2) == DiffRegion(12, Rope(""), Rope("r")));
1440 
1441     }
1442 
1443     /// Put the rope's contents inside given output range.
1444     void toString(Writer)(ref Writer w) const {
1445 
1446         foreach (node; byNode) {
1447 
1448             put(w, node.value);
1449 
1450         }
1451 
1452     }
1453 
1454     unittest {
1455 
1456         auto rope1 = Rope("bar");
1457         const rope2 = Rope(
1458             Rope("b"),
1459             Rope(
1460                 Rope("a"),
1461                 Rope("r"),
1462             ),
1463         );
1464 
1465         assert(format!"Foo, %s, baz"(rope1) == "Foo, bar, baz");
1466         assert(format!"Foo, %s, baz"(rope2) == "Foo, bar, baz");
1467 
1468     }
1469 
1470     string toString() const @trusted {
1471 
1472         // Allocate space for the string
1473         auto buffer = new char[length];
1474 
1475         // Write all characters
1476         foreach (i, char ch; this[].enumerate) {
1477 
1478             buffer[i] = ch;
1479 
1480         }
1481 
1482         return cast(string) buffer;
1483 
1484     }
1485 
1486     unittest {
1487 
1488         auto rope1 = Rope("bar");
1489         const rope2 = Rope(
1490             Rope("b"),
1491             Rope(
1492                 Rope("a"),
1493                 Rope("r"),
1494             ),
1495         );
1496 
1497         assert(rope1.toString == "bar");
1498         assert(rope2.toString == "bar");
1499 
1500     }
1501 
1502     immutable(char)* toStringz() const @trusted {
1503 
1504         return cast(immutable) toStringzMutable;
1505 
1506     }
1507 
1508     char* toStringzMutable() const {
1509 
1510         // Allocate space for the whole string and a null terminator
1511         auto buffer = new char[length + 1];
1512 
1513         // Write all characters
1514         foreach (i, char ch; this[].enumerate) {
1515 
1516             buffer[i] = ch;
1517 
1518         }
1519 
1520         // Add a terminator null byte
1521         buffer[$-1] = '\0';
1522 
1523         return &buffer[0];
1524 
1525     }
1526 
1527     @system
1528     unittest {
1529 
1530         import core.stdc.string;
1531         
1532         auto input = Rope("Hello, World!");
1533 
1534         assert(strlen(input.toStringzMutable) == input.length);
1535 
1536     }
1537 
1538 
1539 }
1540 
1541 struct RopeNode {
1542 
1543     public {
1544 
1545         /// Left child of this node.
1546         Rope left;
1547 
1548         /// Right child of this node.
1549         Rope right;
1550 
1551         /// Direct content of the node, if a leaf node.
1552         ///
1553         /// This must be a fully valid string. Content may not be split in the middle of a codepoint.
1554         const(char)[] value;
1555 
1556     }
1557 
1558     /// Create a leaf node from a slice
1559     this(inout const(char)[] text) inout pure nothrow {
1560 
1561         this.value = text;
1562 
1563     }
1564 
1565     /// Create a node from two other node; Concatenate the two other nodes. Both must not be null.
1566     this(inout Rope left, inout Rope right) inout pure nothrow {
1567 
1568         this.left = left;
1569         this.right = right;
1570 
1571     }
1572 
1573     /// Get length of this node.
1574     size_t length() const pure nothrow {
1575 
1576         return isLeaf
1577             ? value.length
1578             : left.length + right.length;
1579 
1580     }
1581 
1582     /// True if this is a leaf node and contains text rather than child nodes.
1583     bool isLeaf() const pure nothrow {
1584 
1585         return left.node is null
1586             && right.node is null;
1587 
1588     }
1589 
1590 }
1591 
1592 unittest {
1593 
1594     auto a = Rope("Hello, ");
1595     auto b = Rope("World! ");
1596 
1597     auto combined = Rope(a, b);
1598 
1599     assert(combined.equal("Hello, World! "));
1600     assert(combined[1..$].equal("ello, World! "));
1601     assert(combined[1..5].equal("ello"));
1602 
1603 }
1604 
1605 unittest {
1606 
1607     assert(Rope.init.empty);
1608     assert(Rope("  Hello, World! ").strip == "Hello, World!");
1609     assert(Rope("  Hello, World! ").stripLeft == "Hello, World! ");
1610     assert(Rope("  Hello, World! ").stripRight == "  Hello, World!");
1611 
1612 }
1613 
1614 /// `std.utf.codeLength` implementation for Rope.
1615 alias codeLength(T : Rope) = imported!"std.utf".codeLength!char;
1616 
1617 /// A wrapper over Range which disables slicing. Some algorithms assume slicing is faster than regular range access, 
1618 /// but it's not the case for `Rope`.
1619 struct BasicRopeRange {
1620 
1621     Rope rope;
1622 
1623     size_t length() const {
1624         return rope.length;
1625     }
1626 
1627     bool empty() const {
1628         return rope.empty;
1629     }
1630 
1631     void popFront() {
1632         rope.popFront;
1633     }
1634 
1635     char front() const {
1636         return rope.front;
1637     }
1638 
1639     BasicRopeRange save() {
1640         return this;
1641     }
1642 
1643 }