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