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