1 /*******************************************************************************
2 
3         copyright:      Copyright (c) 2005 Kris Bell. All rights reserved
4 
5         license:        BSD style: $(LICENSE)
6 
7         version:        Initial release: December 2005
8 
9         author:         Kris
10 
11 
12         Text is a class for managing and manipulating Unicode character
13         arrays.
14 
15         Text maintains a current "selection", controlled via the select()
16         and search() methods. Each of append(), prepend(), replace() and
17         remove() operate with respect to the selection.
18 
19         The search() methods also operate with respect to the current
20         selection, providing a means of iterating across matched patterns.
21         To set a selection across the entire content, use the select()
22         method with no arguments.
23 
24         Indexes and lengths of content always count code units, not code
25         points. This is similar to traditional ascii string handling, yet
26         indexing is rarely used in practice due to the selection idiom:
27         substring indexing is generally implied as opposed to manipulated
28         directly. This allows for a more streamlined model with regard to
29         utf-surrogates.
30 
31         Strings support a range of functionality, from insert and removal
32         to utf encoding and decoding. There is also an immutable subset
33         called TextView, intended to simplify life in a multi-threaded
34         environment. However, TextView must expose the raw content as
35         needed and thus immutability depends to an extent upon so-called
36         "honour" of a callee. D does not enable immutability enforcement
37         at this time, but this class will be modified to support such a
38         feature when it arrives - via the slice() method.
39 
40         The class is templated for use with char[], wchar[], and dchar[],
41         and should migrate across encodings seamlessly. In particular, all
42         functions in tango.text.Util are compatible with Text content in
43         any of the supported encodings. In future, this class will become
44         a principal gateway to the extensive ICU unicode library.
45 
46         Note that several common text operations can be constructed through
47         combining tango.text.Text with tango.text.Util e.g. lines of text
48         can be processed thusly:
49         ---
50         auto source = new Text!(char)("one\ntwo\nthree");
51 
52         foreach (line; Util.lines(source.slice()))
53                  // do something with line
54         ---
55 
56         Speaking a bit like Yoda might be accomplished as follows:
57         ---
58         auto dst = new Text!(char);
59 
60         foreach (element; Util.delims ("all cows eat grass", " "))
61                  dst.prepend (element);
62         ---
63 
64         Below is an overview of the API and class hierarchy:
65         ---
66         class Text(T) : TextView!(T)
67         {
68                 // set or reset the content
69                 Text set (T[] chars, bool mutable=true);
70                 Text set (const(TextView) other, bool mutable=true);
71 
72                 // retrieve currently selected text
73                 T[] selection ();
74 
75                 // set and retrieve current selection point
76                 Text point (size_t index);
77                 size_t point ();
78 
79                 // mark a selection
80                 Text select (int start=0, int length=int.max);
81 
82                 // return an iterator to move the selection around.
83                 // Also exposes "replace all" functionality
84                 Search search (T chr);
85                 Search search (T[] pattern);
86 
87                 // format arguments behind current selection
88                 Text format (T[] format, ...);
89 
90                 // append behind current selection
91                 Text append (T[] text);
92                 Text append (const(TextView) other);
93                 Text append (T chr, int count=1);
94                 Text append (InputStream source);
95 
96                 // transcode behind current selection
97                 Text encode (char[]);
98                 Text encode (wchar[]);
99                 Text encode (dchar[]);
100 
101                 // insert before current selection
102                 Text prepend (T[] text);
103                 Text prepend (const(TextView) other);
104                 Text prepend (T chr, int count=1);
105 
106                 // replace current selection
107                 Text replace (T chr);
108                 Text replace (T[] text);
109                 Text replace (const(TextView) other);
110 
111                 // remove current selection
112                 Text remove ();
113 
114                 // clear content
115                 Text clear ();
116 
117                 // trim leading and trailing whitespace
118                 Text trim ();
119 
120                 // trim leading and trailing chr instances
121                 Text strip (T chr);
122 
123                 // truncate at point, or current selection
124                 Text truncate (int point = int.max);
125 
126                 // reserve some space for inserts/additions
127                 Text reserve (int extra);
128 
129                 // write content to stream
130                 Text write (OutputStream sink);
131         }
132 
133         class TextView(T) : UniText
134         {
135                 // hash content
136                 hash_t toHash ();
137 
138                 // return length of content
139                 size_t length ();
140 
141                 // compare content
142                 bool equals  (T[] text);
143                 bool equals  (const(TextView) other);
144                 bool ends    (T[] text);
145                 bool ends    (const(TextView) other);
146                 bool starts  (T[] text);
147                 bool starts  (const(TextView) other);
148                 int compare  (T[] text);
149                 int compare  (const(TextView) other);
150                 int opEquals (Object other);
151                 int opCmp    (Object other);
152 
153                 // copy content
154                 T[] copy (T[] dst);
155 
156                 // return content
157                 T[] slice ();
158 
159                 // return data type
160                 typeinfo encoding ();
161 
162                 // replace the comparison algorithm
163                 Comparator comparator (Comparator other);
164         }
165 
166         class UniText
167         {
168                 // convert content
169                 abstract char[]  toString   (char[]  dst = null);
170                 abstract wchar[] toString16 (wchar[] dst = null);
171                 abstract dchar[] toString32 (dchar[] dst = null);
172         }
173 
174         struct Search
175         {
176                 // select prior instance
177                 bool prev();
178 
179                 // select next instance
180                 bool next();
181 
182                 // return instance count
183                 size_t count();
184 
185                 // contains instance?
186                 bool within();
187 
188                 // replace all with char
189                 void replace(T);
190 
191                 // replace all with text (null == remove all)
192                 void replace(T[]);
193         }
194         ---
195 
196 *******************************************************************************/
197 
198 module tango.text.Text;
199 
200 private import  tango.text.Search;
201 
202 private import  tango.io.model.IConduit;
203 
204 private import  tango.text.convert.Layout;
205 
206 private import  Util = tango.text.Util;
207 
208 private import  Utf = tango.text.convert.Utf;
209 
210 private import  Float = tango.text.convert.Float;
211 
212 private import  Integer = tango.text.convert.Integer;
213 
214 private import tango.stdc.string : memmove;
215 
216 private import tango.core.Compiler;
217 
218 static if(DMDFE_Version == 2062)
219 {
220     pragma(msg, "Warning: This module is broken with this DMDFE version");
221 }
222 
223 version(GNU)
224 {
225     private import tango.core.Vararg;
226 }
227 else version(DigitalMars)
228 {
229     private import tango.core.Vararg;
230     version(X86_64) version=DigitalMarsX64;
231 }
232 
233 /*******************************************************************************
234 
235         The mutable Text class actually implements the full API, whereas
236         the superclasses are purely abstract (could be interfaces instead).
237 
238 *******************************************************************************/
239 
240 class Text(T) : TextView!(T)
241 {
242         public  alias set               opAssign;
243         public  alias append            opCatAssign;
244         private alias TextView!(T)      TextViewT;
245         private alias Layout!(T)        LayoutT;
246 
247         private T[]                     content;
248         private bool                    mutable;
249         private Comparator              comparator_;
250         private size_t                  selectPoint,
251                                         selectLength,
252                                         contentLength;
253 
254         /***********************************************************************
255 
256                 Search Iterator
257 
258         ***********************************************************************/
259 
260         private struct Search(T)
261         {
262                 private alias SearchFruct!(T) Engine;
263                 private alias size_t delegate(const(T)[], size_t) Call;
264 
265                 private Text    text;
266                 private Engine  engine;
267 
268                 /***************************************************************
269 
270                         Construct a Search instance
271 
272                 ***************************************************************/
273 
274                 static Search opCall (Text text, const(T)[] match)
275                 {
276                         Search s = void;
277                         s.engine.match = match;
278                         text.selectLength = 0;
279                         s.text = text;
280                         return s;
281                 }
282 
283                 /***************************************************************
284 
285                         Search backward, starting at the character prior to
286                         the selection point
287 
288                 ***************************************************************/
289 
290                 @property bool prev ()
291                 {
292                         return locate (&engine.reverse, text.slice(), text.point - 1);
293                 }
294 
295                 /***************************************************************
296 
297                         Search forward, starting just after the currently
298                         selected text
299 
300                 ***************************************************************/
301 
302                 @property bool next ()
303                 {
304                         return locate (&engine.forward, text.slice(),
305                                         text.selectPoint + text.selectLength);
306                 }
307 
308                 /***************************************************************
309 
310                         Returns true if there is a match within the
311                         associated text
312 
313                 ***************************************************************/
314 
315                 @property bool within ()
316                 {
317                         return engine.within (text.slice());
318                 }
319 
320                 /***************************************************************
321 
322                         Returns number of matches within the associated
323                         text
324 
325                 ***************************************************************/
326 
327                 @property size_t count ()
328                 {
329                         return engine.count (text.slice());
330                 }
331 
332                 /***************************************************************
333 
334                         Replace all matches with the given character
335 
336                 ***************************************************************/
337 
338                 void replace (T chr)
339                 {
340                         replace ((&chr)[0..1]);
341                 }
342 
343                 /***************************************************************
344 
345                         Replace all matches with the given substitution
346 
347                 ***************************************************************/
348 
349                 void replace (const(T)[] sub = null)
350                 {
351                         auto dst = new T[text.length];
352                         dst.length = 0;
353 
354                         foreach (token; engine.tokens (text.slice(), sub))
355                                  dst ~= token;
356                         text.set (dst, false);
357                 }
358 
359                 /***************************************************************
360 
361                         locate pattern index and select as appropriate
362 
363                 ***************************************************************/
364 
365                 private bool locate (Call call, const(T)[] content, size_t from)
366                 {
367                         auto index = call (content, from);
368                         if (index < content.length)
369                            {
370                            text.select (index, engine.match().length);
371                            return true;
372                            }
373                         return false;
374                 }
375         }
376 
377         /***********************************************************************
378 
379                 Selection span
380 
381                 deprecated: use point() instead
382 
383         ***********************************************************************/
384 
385         deprecated public struct Span
386         {
387                 size_t  begin,                  /// index of selection point
388                         length;                 /// length of selection
389         }
390 
391         /***********************************************************************
392 
393                 Create an empty Text with the specified available
394                 space
395 
396                 Note: A character like 'a' will be implicitly converted to
397                 uint and thus will be accepted for this constructor, making
398                 it appear like you can initialize a Text instance with a
399                 single character, something which is not supported.
400 
401         ***********************************************************************/
402 
403         this (size_t space = 0)
404         {
405                 content.length = space;
406                 this.comparator_ = &simpleComparator;
407         }
408 
409         /***********************************************************************
410 
411                 Create a Text upon the provided content. If said
412                 content is immutable (read-only) then you might consider
413                 setting the 'copy' parameter to false. Doing so will
414                 avoid allocating heap-space for the content until it is
415                 modified via Text methods. This can be useful when
416                 wrapping an array "temporarily" with a stack-based Text
417 
418         ***********************************************************************/
419 
420         this (T[] content, bool copy)
421         {
422                 set (content, copy);
423                 this.comparator_ = &simpleComparator;
424         }
425 
426         this (const(T)[] content)
427         {
428                 set (content);
429                 this.comparator_ = &simpleComparator;
430         }
431 
432         /***********************************************************************
433 
434                 Create a Text via the content of another. If said
435                 content is immutable (read-only) then you might consider
436                 setting the 'copy' parameter to false. Doing so will avoid
437                 allocating heap-space for the content until it is modified
438                 via Text methods. This can be useful when wrapping an array
439                 temporarily with a stack-based Text
440 
441         ***********************************************************************/
442 
443         this (TextViewT other, bool copy = true)
444         {
445                 this (other.mslice(), copy);
446         }
447 
448         this (const(TextViewT) other, bool copy = true)
449         {
450                 this (other.slice());
451         }
452 
453         /***********************************************************************
454 
455                 Set the content to the provided array. Parameter 'copy'
456                 specifies whether the given array is likely to change. If
457                 not, the array is aliased until such time it is altered via
458                 this class. This can be useful when wrapping an array
459                 "temporarily" with a stack-based Text.
460 
461                 Also resets the curent selection to null
462 
463         ***********************************************************************/
464 
465         final Text set (T[] chars, bool copy)
466         {
467                 contentLength = chars.length;
468                 if ((this.mutable = copy) is true)
469                      content = chars.dup;
470                 else
471                    content = chars;
472 
473                 // no selection
474                 return select (0, 0);
475         }
476 
477         final Text set (const(T)[] chars)
478         {
479                 contentLength = chars.length;
480                 content = chars.dup;
481 
482                 // no selection
483                 return select (0, 0);
484         }
485 
486         /***********************************************************************
487 
488                 Replace the content of this Text. If the new content
489                 is immutable (read-only) then you might consider setting the
490                 'copy' parameter to false. Doing so will avoid allocating
491                 heap-space for the content until it is modified via one of
492                 these methods. This can be useful when wrapping an array
493                 "temporarily" with a stack-based Text.
494 
495                 Also resets the curent selection to null
496 
497         ***********************************************************************/
498 static if(DMDFE_Version != 2061)
499 {
500         final Text set (TextViewT other, bool copy = true)
501         {
502                 return set (other.mslice(), copy);
503         }
504 }
505 
506         /***********************************************************************
507 
508                 Explicitly set the current selection to the given start and
509                 length. values are pinned to the content extents
510 
511         ***********************************************************************/
512 
513         final Text select (size_t start=0, size_t length=int.max)
514         {
515                 pinIndices (start, length);
516                 selectPoint = start;
517                 selectLength = length;
518                 return this;
519         }
520 
521         /***********************************************************************
522 
523                 Return the currently selected content
524 
525         ***********************************************************************/
526 
527         final const const(T)[] selection ()
528         {
529                 return slice() [selectPoint .. selectPoint+selectLength];
530         }
531 
532         /***********************************************************************
533 
534                 Return the index and length of the current selection
535 
536                 deprecated: use point() instead
537 
538         ***********************************************************************/
539 
540         deprecated final Span span ()
541         {
542                 Span s;
543                 s.begin = selectPoint;
544                 s.length = selectLength;
545                 return s;
546         }
547 
548         /***********************************************************************
549 
550                 Return the current selection point
551 
552         ***********************************************************************/
553 
554         @property final size_t point ()
555         {
556                 return selectPoint;
557         }
558 
559         /***********************************************************************
560 
561                 Set the current selection point, and resets selection length
562 
563         ***********************************************************************/
564 
565         @property final Text point (size_t index)
566         {
567                 return select (index, 0);
568         }
569 
570         /***********************************************************************
571 
572                 Return a search iterator for a given pattern. The iterator
573                 sets the current text selection as appropriate. For example:
574                 ---
575                 auto t = new Text ("hello world");
576                 auto s = t.search ("world");
577 
578                 assert (s.next);
579                 assert (t.selection() == "world");
580                 ---
581 
582                 Replacing patterns operates in a similar fashion:
583                 ---
584                 auto t = new Text ("hello world");
585                 auto s = t.search ("world");
586 
587                 // replace all instances of "world" with "everyone"
588                 assert (s.replace ("everyone"));
589                 assert (s.count is 0);
590                 ---
591 
592         ***********************************************************************/
593 
594         Search!(T) search (const(T)[] match)
595         {
596                 return Search!(T) (this, match);
597         }
598 
599         Search!(T) search (ref T match)
600         {
601                 return search ((&match)[0..1]);
602         }
603 
604         /***********************************************************************
605 
606                 Find and select the next occurrence of a BMP code point
607                 in a string. Returns true if found, false otherwise
608 
609                 deprecated: use search() instead
610 
611         ***********************************************************************/
612 
613         deprecated final bool select (T c)
614         {
615                 auto s = slice();
616                 auto x = Util.locate (s, c, selectPoint);
617                 if (x < s.length)
618                    {
619                    select (x, 1);
620                    return true;
621                    }
622                 return false;
623         }
624 
625         /***********************************************************************
626 
627                 Find and select the next substring occurrence.  Returns
628                 true if found, false otherwise
629 
630                 deprecated: use search() instead
631 
632         ***********************************************************************/
633 
634         deprecated final bool select (const(TextViewT) other)
635         {
636                 return select (other.slice());
637         }
638 
639         /***********************************************************************
640 
641                 Find and select the next substring occurrence. Returns
642                 true if found, false otherwise
643 
644                 deprecated: use search() instead
645 
646         ***********************************************************************/
647 
648         deprecated final bool select (const(T)[] chars)
649         {
650                 auto s = slice();
651                 auto x = Util.locatePattern (s, chars, selectPoint);
652                 if (x < s.length)
653                    {
654                    select (x, chars.length);
655                    return true;
656                    }
657                 return false;
658         }
659 
660         /***********************************************************************
661 
662                 Find and select a prior occurrence of a BMP code point
663                 in a string. Returns true if found, false otherwise
664 
665                 deprecated: use search() instead
666 
667         ***********************************************************************/
668 
669         deprecated final bool selectPrior (T c)
670         {
671                 auto s = slice();
672                 auto x = Util.locatePrior (s, c, selectPoint);
673                 if (x < s.length)
674                    {
675                    select (x, 1);
676                    return true;
677                    }
678                 return false;
679         }
680 
681         /***********************************************************************
682 
683                 Find and select a prior substring occurrence. Returns
684                 true if found, false otherwise
685 
686                 deprecated: use search() instead
687 
688         ***********************************************************************/
689 
690         deprecated final bool selectPrior (const(TextViewT) other)
691         {
692                 return selectPrior (other.slice());
693         }
694 
695         /***********************************************************************
696 
697                 Find and select a prior substring occurrence. Returns
698                 true if found, false otherwise
699 
700                 deprecated: use search() instead
701 
702         ***********************************************************************/
703 
704         deprecated final bool selectPrior (const(T)[] chars)
705         {
706                 auto s = slice();
707                 auto x = Util.locatePatternPrior (s, chars, selectPoint);
708                 if (x < s.length)
709                    {
710                    select (x, chars.length);
711                    return true;
712                    }
713                 return false;
714         }
715 
716         /***********************************************************************
717 
718                 Append formatted content to this Text
719 
720         ***********************************************************************/
721 
722         final Text format (const(T)[] format, ...)
723         {
724                 size_t emit (const(T)[] s)
725                 {
726                     append (s);
727                     return s.length;
728                 }
729 
730                 version (DigitalMarsX64)
731                 {
732                     va_list ap;
733 
734                     va_start(ap, format);
735 
736                     scope(exit) va_end(ap);
737 
738                     LayoutT.instance.convert (&emit, _arguments, ap, format);
739                 }
740                 else
741                     LayoutT.instance.convert (&emit, _arguments, _argptr, format);
742 
743                 return this;
744         }
745 
746         /***********************************************************************
747 
748           Append text to this Text
749 
750         ***********************************************************************/
751 
752         final Text append (const(TextViewT) other)
753         {
754                 return append (other.slice());
755         }
756 
757         /***********************************************************************
758 
759                 Append text to this Text
760 
761         ***********************************************************************/
762 
763         final Text append (const(T)[] chars)
764         {
765                 return append (chars.ptr, chars.length);
766         }
767 
768         /***********************************************************************
769 
770                 Append a count of characters to this Text
771 
772         ***********************************************************************/
773 
774         final Text append (T chr, size_t count=1)
775         {
776                 size_t point = selectPoint + selectLength;
777                 expand (point, count);
778                 return set (chr, point, count);
779         }
780 
781         /***********************************************************************
782 
783                 Append an integer to this Text
784 
785                 deprecated: use format() instead
786 
787         ***********************************************************************/
788 
789         deprecated final Text append (int v, const(T)[] fmt = null)
790         {
791                 return append (cast(long) v, fmt);
792         }
793 
794         /***********************************************************************
795 
796                 Append a long to this Text
797 
798                 deprecated: use format() instead
799 
800         ***********************************************************************/
801 
802         deprecated final Text append (long v, const(T)[] fmt = null)
803         {
804                 T[64] tmp = void;
805                 return append (Integer.format(tmp, v, fmt));
806         }
807 
808         /***********************************************************************
809 
810                 Append a double to this Text
811 
812                 deprecated: use format() instead
813 
814         ***********************************************************************/
815 
816         deprecated final Text append (double v, int decimals=2, int e=10)
817         {
818                 T[64] tmp = void;
819                 return append (Float.format(tmp, v, decimals, e));
820         }
821 
822         /***********************************************************************
823 
824                 Append content from input stream at insertion point. Use
825                 tango.io.stream.Utf as a wrapper to perform conversion as
826                 necessary
827 
828         ***********************************************************************/
829 
830         final Text append (InputStream source)
831         {
832                 T[8192/T.sizeof] tmp = void;
833                 while (true)
834                       {
835                       auto len = source.read (tmp);
836                       if (len is source.Eof)
837                           break;
838 
839                       // check to ensure UTF conversion is ok
840                       assert ((len & (T.sizeof-1)) is 0);
841                       append (tmp [0 .. len/T.sizeof]);
842                       }
843                 return this;
844         }
845 
846         /***********************************************************************
847 
848                 Insert characters into this Text
849 
850         ***********************************************************************/
851 
852         final Text prepend (T chr, int count=1)
853         {
854                 expand (selectPoint, count);
855                 return set (chr, selectPoint, count);
856         }
857 
858         /***********************************************************************
859 
860                 Insert text into this Text
861 
862         ***********************************************************************/
863 
864         final Text prepend (const(T)[] other)
865         {
866                 expand (selectPoint, other.length);
867                 content[selectPoint..selectPoint+other.length] = other[];
868                 return this;
869         }
870 
871         /***********************************************************************
872 
873                 Insert another Text into this Text
874 
875         ***********************************************************************/
876 
877         final Text prepend (const(TextViewT) other)
878         {
879                 return prepend (other.slice());
880         }
881 
882         /***********************************************************************
883 
884                 Append encoded text at the current selection point. The text
885                 is converted as necessary to the appropritate utf encoding.
886 
887         ***********************************************************************/
888 
889         final Text encode (const(char)[] s)
890         {
891                 T[1024] tmp = void;
892 
893                 static if (is (T == char))
894                            return append(s);
895 
896                 static if (is (T == wchar))
897                            return append (Utf.toString16(s, tmp));
898 
899                 static if (is (T == dchar))
900                            return append (Utf.toString32(s, tmp));
901         }
902 
903         /// ditto
904         final Text encode (const(wchar)[] s)
905         {
906                 T[1024] tmp = void;
907 
908                 static if (is (T == char))
909                            return append (Utf.toString(s, tmp));
910 
911                 static if (is (T == wchar))
912                            return append (s);
913 
914                 static if (is (T == dchar))
915                            return append (Utf.toString32(s, tmp));
916         }
917 
918         /// ditto
919         final Text encode (const(dchar)[] s)
920         {
921                 T[1024] tmp = void;
922 
923                 static if (is (T == char))
924                            return append (Utf.toString(s, tmp));
925 
926                 static if (is (T == wchar))
927                            return append (Utf.toString16(s, tmp));
928 
929                 static if (is (T == dchar))
930                            return append (s);
931         }
932 
933         /// ditto
934         final Text encode (Object o)
935         {
936                 return encode (o.toString());
937         }
938 
939         /***********************************************************************
940 
941                 Replace a section of this Text with the specified
942                 character
943 
944         ***********************************************************************/
945 
946         final Text replace (T chr)
947         {
948                 return set (chr, selectPoint, selectLength);
949         }
950 
951         /***********************************************************************
952 
953                 Replace a section of this Text with the specified
954                 array
955 
956         ***********************************************************************/
957 
958         final Text replace (const(T)[] chars)
959         {
960                 int chunk = cast(int)chars.length - cast(int)selectLength;
961                 if (chunk >= 0)
962                     expand (selectPoint, chunk);
963                 else
964                    remove (selectPoint, -chunk);
965 
966                 content [selectPoint .. selectPoint+chars.length] = chars[];
967                 return select (selectPoint, chars.length);
968         }
969 
970         /***********************************************************************
971 
972                 Replace a section of this Text with another
973 
974         ***********************************************************************/
975 
976         final Text replace (const(TextViewT) other)
977         {
978                 return replace (other.slice());
979         }
980 
981         /***********************************************************************
982 
983                 Remove the selection from this Text and reset the
984                 selection to zero length (at the current position)
985 
986         ***********************************************************************/
987 
988         final Text remove ()
989         {
990                 remove (selectPoint, selectLength);
991                 return select (selectPoint, 0);
992         }
993 
994         /***********************************************************************
995 
996                 Remove the selection from this Text
997 
998         ***********************************************************************/
999 
1000         private Text remove (size_t start, size_t count)
1001         {
1002                 pinIndices (start, count);
1003                 if (count > 0)
1004                    {
1005                    if (! mutable)
1006                          realloc ();
1007 
1008                    size_t i = start + count;
1009                    memmove (content.ptr+start, content.ptr+i, (contentLength-i) * T.sizeof);
1010                    contentLength -= count;
1011                    }
1012                 return this;
1013         }
1014 
1015         /***********************************************************************
1016 
1017                 Truncate this string at an optional index. Default behaviour
1018                 is to truncate at the current append point. Current selection
1019                 is moved to the truncation point, with length 0
1020 
1021         ***********************************************************************/
1022 
1023         final Text truncate (size_t index = size_t.max)
1024         {
1025                 if (index is int.max)
1026                     index = selectPoint + selectLength;
1027 
1028                 pinIndex (index);
1029                 return select (contentLength = index, 0);
1030         }
1031 
1032         /***********************************************************************
1033 
1034                 Clear the string content
1035 
1036         ***********************************************************************/
1037 
1038         final Text clear ()
1039         {
1040                 return select (contentLength = 0, 0);
1041         }
1042 
1043         /***********************************************************************
1044 
1045                 Remove leading and trailing whitespace from this Text,
1046                 and reset the selection to the trimmed content
1047 
1048         ***********************************************************************/
1049 
1050         final Text trim ()
1051         {
1052                 content = Util.trim (mslice());
1053                 select (0, contentLength = content.length);
1054                 return this;
1055         }
1056 
1057         /***********************************************************************
1058 
1059                 Remove leading and trailing matches from this Text,
1060                 and reset the selection to the stripped content
1061 
1062         ***********************************************************************/
1063 
1064         final Text strip (T matches)
1065         {
1066                 content = Util.strip (mslice(), matches);
1067                 select (0, contentLength = content.length);
1068                 return this;
1069         }
1070 
1071         /***********************************************************************
1072 
1073                 Reserve some extra room
1074 
1075         ***********************************************************************/
1076 
1077         final Text reserve (size_t extra)
1078         {
1079                 realloc (extra);
1080                 return this;
1081         }
1082 
1083         /***********************************************************************
1084 
1085                 Write content to output stream
1086 
1087         ***********************************************************************/
1088 
1089         Text write (OutputStream sink)
1090         {
1091                 sink.write (slice());
1092                 return this;
1093         }
1094 
1095         /* ======================== TextView methods ======================== */
1096 
1097 
1098 
1099         /***********************************************************************
1100 
1101                 Get the encoding type
1102 
1103         ***********************************************************************/
1104 
1105         final override const TypeInfo encoding()
1106         {
1107                 return typeid(T);
1108         }
1109 
1110         /***********************************************************************
1111 
1112                 Set the comparator delegate. Where other is null, we behave
1113                 as a getter only
1114 
1115         ***********************************************************************/
1116 
1117         final override Comparator comparator (Comparator other)
1118         {
1119                 auto tmp = comparator_;
1120                 if (other)
1121                     comparator_ = other;
1122                 return tmp;
1123         }
1124 
1125         /***********************************************************************
1126 
1127                 Hash this Text
1128 
1129         ***********************************************************************/
1130 
1131         override @trusted nothrow
1132         hash_t toHash ()
1133         {
1134                 return Util.jhash (cast(ubyte*) content.ptr, contentLength * T.sizeof);
1135         }
1136 
1137         /***********************************************************************
1138 
1139                 Return the length of the valid content
1140 
1141         ***********************************************************************/
1142 
1143         @property override final const size_t length ()
1144         {
1145                 return contentLength;
1146         }
1147 
1148         /***********************************************************************
1149 
1150                 Is this Text equal to another?
1151 
1152         ***********************************************************************/
1153 
1154         final override const bool equals (const(TextViewT) other)
1155         {
1156                 if (other is this)
1157                     return true;
1158                 return equals (other.slice());
1159         }
1160 
1161         /***********************************************************************
1162 
1163                 Is this Text equal to the provided text?
1164 
1165         ***********************************************************************/
1166 
1167         final override const bool equals (const(T)[] other)
1168         {
1169                 if (other.length == contentLength)
1170                     return Util.matching (other.ptr, content.ptr, contentLength);
1171                 return false;
1172         }
1173 
1174         /***********************************************************************
1175 
1176                 Does this Text end with another?
1177 
1178         ***********************************************************************/
1179 
1180         final override const bool ends (const(TextViewT) other)
1181         {
1182                 return ends (other.slice());
1183         }
1184 
1185         /***********************************************************************
1186 
1187                 Does this Text end with the specified string?
1188 
1189         ***********************************************************************/
1190 
1191         final override const bool ends (const(T)[] chars)
1192         {
1193                 if (chars.length <= contentLength)
1194                     return Util.matching (content.ptr+(contentLength-chars.length), chars.ptr, chars.length);
1195                 return false;
1196         }
1197 
1198         /***********************************************************************
1199 
1200                 Does this Text start with another?
1201 
1202         ***********************************************************************/
1203 
1204         final override const bool starts (const(TextViewT) other)
1205         {
1206                 return starts (other.slice());
1207         }
1208 
1209         /***********************************************************************
1210 
1211                 Does this Text start with the specified string?
1212 
1213         ***********************************************************************/
1214 
1215         final override const bool starts (const(T)[] chars)
1216         {
1217                 if (chars.length <= contentLength)
1218                     return Util.matching (content.ptr, chars.ptr, chars.length);
1219                 return false;
1220         }
1221 
1222         /***********************************************************************
1223 
1224                 Compare this Text start with another. Returns 0 if the
1225                 content matches, less than zero if this Text is "less"
1226                 than the other, or greater than zero where this Text
1227                 is "bigger".
1228 
1229         ***********************************************************************/
1230 
1231         final override const int compare (const(TextViewT) other)
1232         {
1233                 if (other is this)
1234                     return 0;
1235 
1236                 return compare (other.slice());
1237         }
1238 
1239         /***********************************************************************
1240 
1241                 Compare this Text start with an array. Returns 0 if the
1242                 content matches, less than zero if this Text is "less"
1243                 than the other, or greater than zero where this Text
1244                 is "bigger".
1245 
1246         ***********************************************************************/
1247 
1248         final override const int compare (const(T)[] chars)
1249         {
1250                 return comparator_ (slice(), chars);
1251         }
1252 
1253         /***********************************************************************
1254 
1255                 Return content from this Text
1256 
1257                 A slice of dst is returned, representing a copy of the
1258                 content. The slice is clipped to the minimum of either
1259                 the length of the provided array, or the length of the
1260                 content minus the stipulated start point
1261 
1262         ***********************************************************************/
1263 
1264         final override const T[] copy (T[] dst)
1265         {
1266                 size_t i = contentLength;
1267                 if (i > dst.length)
1268                     i = dst.length;
1269 
1270                 return dst [0 .. i] = content [0 .. i];
1271         }
1272 
1273         /***********************************************************************
1274 
1275                 Return an alias to the content of this TextView. Note
1276                 that you are bound by honour to leave this content wholly
1277                 unmolested. D surely needs some way to enforce immutability
1278                 upon array references
1279 
1280         ***********************************************************************/
1281 
1282         final override const const(T)[] slice ()
1283         {
1284                 return content [0 .. contentLength];
1285         }
1286 
1287         override T[] mslice ()
1288         {
1289                 return content [0 .. contentLength];
1290         }
1291 
1292         /***********************************************************************
1293 
1294                 Convert to the UniText types. The optional argument
1295                 dst will be resized as required to house the conversion.
1296                 To minimize heap allocation during subsequent conversions,
1297                 apply the following pattern:
1298                 ---
1299                 Text  string;
1300 
1301                 wchar[] buffer;
1302                 wchar[] result = string.utf16 (buffer);
1303 
1304                 if (result.length > buffer.length)
1305                     buffer = result;
1306                 ---
1307                 You can also provide a buffer from the stack, but the output
1308                 will be moved to the heap if said buffer is not large enough
1309 
1310         ***********************************************************************/
1311 
1312         override const string toString()
1313         {
1314                 return toString(null).idup;
1315         }
1316 
1317         final override const char[] toString (char[] dst)
1318         {
1319                 static if (is (T == char)) {
1320                            if(dst.length < length)
1321                                 dst.length = length;
1322                            dst[] = slice()[];
1323                            return dst[0..this.length];
1324                 }
1325 
1326                 static if (is (T == wchar))
1327                            return Utf.toString (slice(), dst);
1328 
1329                 static if (is (T == dchar))
1330                            return Utf.toString (slice(), dst);
1331         }
1332 
1333         /// ditto
1334         final override const wchar[] toString16 (wchar[] dst = null)
1335         {
1336                 static if (is (T == char))
1337                            return Utf.toString16 (slice(), dst);
1338 
1339                 static if (is (T == wchar)) {
1340                            if(dst.length < length)
1341                                 dst.length = length;
1342                            dst[] = slice()[];
1343                            return dst[0..this.length];
1344                 }
1345 
1346                 static if (is (T == dchar))
1347                            return Utf.toString16 (slice(), dst);
1348         }
1349 
1350         /// ditto
1351         final override const dchar[] toString32 (dchar[] dst = null)
1352         {
1353                 static if (is (T == char))
1354                            return Utf.toString32 (slice(), dst);
1355 
1356                 static if (is (T == wchar))
1357                            return Utf.toString32 (slice(), dst);
1358 
1359                 static if (is (T == dchar)) {
1360                            if(dst.length < length)
1361                                 dst.length = length;
1362                            dst[] = slice()[];
1363                            return dst[0..length];
1364                 }
1365         }
1366 
1367         /***********************************************************************
1368 
1369                 Compare this Text to another. We compare against other
1370                 Strings only. Literals and other objects are not supported
1371 
1372         ***********************************************************************/
1373 
1374         override const int opCmp (Object o)
1375         {
1376                 auto other = cast (TextViewT) o;
1377 
1378                 if (other is null)
1379                     return -1;
1380 
1381                 return compare (other);
1382         }
1383 
1384         /***********************************************************************
1385 
1386                 Is this Text equal to the text of something else?
1387 
1388         ***********************************************************************/
1389 
1390         override bool opEquals (Object o)
1391         {
1392                 auto other = cast (TextViewT) o;
1393 
1394                 if (other)
1395                     return equals (other);
1396 
1397                 // this can become expensive ...
1398                 char[1024] tmp = void;
1399                 return this.toString(tmp) == o.toString();
1400         }
1401 
1402         /// ditto
1403         final override bool opEquals (const(T)[] s)
1404         {
1405                 return slice() == s;
1406         }
1407 
1408         /***********************************************************************
1409 
1410                 Pin the given index to a valid position.
1411 
1412         ***********************************************************************/
1413 
1414         private const void pinIndex (ref size_t x)
1415         {
1416                 if (x > contentLength)
1417                     x = contentLength;
1418         }
1419 
1420         /***********************************************************************
1421 
1422                 Pin the given index and length to a valid position.
1423 
1424         ***********************************************************************/
1425 
1426         private const void pinIndices (ref size_t start, ref size_t length)
1427         {
1428                 if (start > contentLength)
1429                     start = contentLength;
1430 
1431                 if (length > (contentLength - start))
1432                     length = contentLength - start;
1433         }
1434 
1435         /***********************************************************************
1436 
1437                 Compare two arrays. Returns 0 if the content matches, less
1438                 than zero if A is "less" than B, or greater than zero where
1439                 A is "bigger". Where the substrings match, the shorter is
1440                 considered "less".
1441 
1442         ***********************************************************************/
1443 
1444         private int simpleComparator (const(T)[] a, const(T)[] b)
1445         {
1446                 size_t i = a.length;
1447                 if (b.length < i)
1448                     i = b.length;
1449 
1450                 for (int j, k; j < i; ++j)
1451                      if ((k = a[j] - b[j]) != 0)
1452                           return k;
1453 
1454                 return cast(int)a.length - cast(int)b.length;
1455         }
1456 
1457         /***********************************************************************
1458 
1459                 Make room available to insert or append something
1460 
1461         ***********************************************************************/
1462 
1463         private void expand (size_t index, size_t count)
1464         {
1465                 if (!mutable || (contentLength + count) > content.length)
1466                      realloc (count);
1467 
1468                 memmove (content.ptr+index+count, content.ptr+index, (contentLength - index) * T.sizeof);
1469                 selectLength += count;
1470                 contentLength += count;
1471         }
1472 
1473         /***********************************************************************
1474 
1475                 Replace a section of this Text with the specified
1476                 character
1477 
1478         ***********************************************************************/
1479 
1480         private Text set (T chr, size_t start, size_t count)
1481         {
1482                 content [start..start+count] = chr;
1483                 return this;
1484         }
1485 
1486         /***********************************************************************
1487 
1488                 Allocate memory due to a change in the content. We handle
1489                 the distinction between mutable and immutable here.
1490 
1491         ***********************************************************************/
1492 
1493         private void realloc (size_t count = 0)
1494         {
1495                 size_t size = (content.length + count + 127) & ~127;
1496 
1497                 if (mutable)
1498                     content.length = size;
1499                 else
1500                    {
1501                    mutable = true;
1502                    T[] x = content;
1503                    content = new T[size];
1504                    if (contentLength)
1505                        content[0..contentLength] = x[];
1506                    }
1507         }
1508 
1509         /***********************************************************************
1510 
1511                 Internal method to support Text appending
1512 
1513         ***********************************************************************/
1514 
1515         private Text append (const(T)* chars, size_t count)
1516         {
1517                 size_t point = selectPoint + selectLength;
1518                 expand (point, count);
1519                 content[point .. point+count] = chars[0 .. count];
1520                 return this;
1521         }
1522 }
1523 
1524 
1525 
1526 /*******************************************************************************
1527 
1528         Immutable string
1529 
1530 *******************************************************************************/
1531 
1532 class TextView(T) : UniText
1533 {
1534         alias int delegate (const(T)[] a, const(T)[] b) Comparator;
1535 
1536         /***********************************************************************
1537 
1538                 Return the length of the valid content
1539 
1540         ***********************************************************************/
1541 
1542         @property abstract const size_t length ();
1543 
1544         /***********************************************************************
1545 
1546                 Is this Text equal to another?
1547 
1548         ***********************************************************************/
1549 
1550         abstract const bool equals (const(TextView) other);
1551 
1552         /***********************************************************************
1553 
1554                 Is this Text equal to the the provided text?
1555 
1556         ***********************************************************************/
1557 
1558         abstract const bool equals (const(T)[] other);
1559 
1560         /***********************************************************************
1561 
1562                 Does this Text end with another?
1563 
1564         ***********************************************************************/
1565 
1566         abstract const bool ends (const(TextView) other);
1567 
1568         /***********************************************************************
1569 
1570                 Does this Text end with the specified string?
1571 
1572         ***********************************************************************/
1573 
1574         abstract const bool ends (const(T)[] chars);
1575 
1576         /***********************************************************************
1577 
1578                 Does this Text start with another?
1579 
1580         ***********************************************************************/
1581 
1582         abstract const bool starts (const(TextView) other);
1583 
1584         /***********************************************************************
1585 
1586                 Does this Text start with the specified string?
1587 
1588         ***********************************************************************/
1589 
1590         abstract const bool starts (const(T)[] chars);
1591 
1592         /***********************************************************************
1593 
1594                 Compare this Text start with another. Returns 0 if the
1595                 content matches, less than zero if this Text is "less"
1596                 than the other, or greater than zero where this Text
1597                 is "bigger".
1598 
1599         ***********************************************************************/
1600 
1601         abstract const int compare (const(TextView) other);
1602 
1603         /***********************************************************************
1604 
1605                 Compare this Text start with an array. Returns 0 if the
1606                 content matches, less than zero if this Text is "less"
1607                 than the other, or greater than zero where this Text
1608                 is "bigger".
1609 
1610         ***********************************************************************/
1611 
1612         abstract const int compare (const(T)[] chars);
1613 
1614         /***********************************************************************
1615 
1616                 Return content from this Text. A slice of dst is
1617                 returned, representing a copy of the content. The
1618                 slice is clipped to the minimum of either the length
1619                 of the provided array, or the length of the content
1620                 minus the stipulated start point
1621 
1622         ***********************************************************************/
1623 
1624         abstract const T[] copy (T[] dst);
1625 
1626         /***********************************************************************
1627 
1628                 Compare this Text to another
1629 
1630         ***********************************************************************/
1631 
1632         abstract override const int opCmp (Object o);
1633 
1634         /***********************************************************************
1635 
1636                 Is this Text equal to another?
1637 
1638         ***********************************************************************/
1639 
1640         abstract override bool opEquals (Object other);
1641 
1642         /***********************************************************************
1643 
1644                 Is this Text equal to another?
1645 
1646         ***********************************************************************/
1647 
1648         abstract bool opEquals (const(T)[] other);
1649 
1650         /***********************************************************************
1651 
1652                 Get the encoding type
1653 
1654         ***********************************************************************/
1655 
1656         abstract override const TypeInfo encoding();
1657 
1658         /***********************************************************************
1659 
1660                 Set the comparator delegate
1661 
1662         ***********************************************************************/
1663 
1664         abstract Comparator comparator (Comparator other);
1665 
1666         /***********************************************************************
1667 
1668                 Hash this Text
1669 
1670         ***********************************************************************/
1671 
1672         abstract override @trusted nothrow hash_t toHash ();
1673 
1674         /***********************************************************************
1675 
1676                 Return an alias to the content of this TextView. Note
1677                 that you are bound by honour to leave this content wholly
1678                 unmolested. D surely needs some way to enforce immutability
1679                 upon array references
1680 
1681         ***********************************************************************/
1682 
1683         abstract const const(T)[] slice ();
1684         abstract T[] mslice ();
1685 }
1686 
1687 
1688 /*******************************************************************************
1689 
1690         A string abstraction that converts to anything
1691 
1692 *******************************************************************************/
1693 
1694 class UniText
1695 {
1696         override string toString() { return toString(null).idup; }
1697 
1698         abstract const char[]  toString  (char[]  dst = null);
1699 
1700         abstract const wchar[] toString16 (wchar[] dst = null);
1701 
1702         abstract const dchar[] toString32 (dchar[] dst = null);
1703 
1704         abstract const TypeInfo encoding();
1705 }
1706 
1707 
1708 
1709 /*******************************************************************************
1710 
1711 *******************************************************************************/
1712 
1713 debug (UnitTest)
1714 {
1715         import tango.io.device.Array;
1716 
1717         //void main() {}
1718         unittest
1719         {
1720         auto s = new Text!(char);
1721         s = "hello";
1722 
1723         auto array = new Array(1024);
1724         s.write (array);
1725         assert (array.slice() == "hello");
1726         s.select (1, 0);
1727         assert (s.append(array) == "hhelloello");
1728 
1729         s = "hello";
1730         s.search("hello").next;
1731         assert (s.selection() == "hello");
1732         s.replace ("1");
1733         assert (s.selection() == "1");
1734         assert (s == "1");
1735 
1736         assert (s.clear() == "");
1737 
1738         assert (s.format("{}", 12345) == "12345");
1739         assert (s.selection() == "12345");
1740 
1741         s ~= "fubar";
1742         assert (s.selection() == "12345fubar");
1743         assert (s.search("5").next);
1744         assert (s.selection() == "5");
1745         assert (s.remove() == "1234fubar");
1746         assert (s.search("fubar").next);
1747         assert (s.selection() == "fubar");
1748         assert (s.search("wumpus").next is false);
1749         assert (s.selection() == "");
1750 
1751         assert (s.clear().format("{:f4}", 1.2345) == "1.2345");
1752 
1753         assert (s.clear().format("{:b}", 0xf0) == "11110000");
1754 
1755         assert (s.clear().encode("one"d).toString() == "one");
1756 
1757         assert (Util.splitLines(s.clear().append("a\nb").slice()).length is 2);
1758 
1759         assert (s.select().replace("almost ") == "almost ");
1760         foreach (element; Util.patterns ("all cows eat grass", "eat", "chew"))
1761                  s.append (element);
1762         assert (s.selection() == "almost all cows chew grass");
1763         assert (s.clear().format("{}:{}", 1, 2) == "1:2");
1764         }
1765 }
1766 
1767 
1768 debug (Text)
1769 {
1770         void main()
1771         {
1772                 auto t = new Text!(char);
1773                 t = "hello world";
1774                 auto s = t.search ("o");
1775                 assert (s.next);
1776                 assert (t.selection() == "o");
1777                 assert (t.point is 4);
1778                 assert (s.next);
1779                 assert (t.selection() == "o");
1780                 assert (t.point is 7);
1781                 assert (!s.next);
1782 
1783                 t.point = 9;
1784                 assert (s.prev);
1785                 assert (t.selection() == "o");
1786                 assert (t.point is 7);
1787                 assert (s.prev);
1788                 assert (t.selection() == "o");
1789                 assert (t.point is 4);
1790                 assert (s.next);
1791                 assert (t.point is 7);
1792                 assert (s.prev);
1793                 assert (t.selection() == "o");
1794                 assert (t.point is 4);
1795                 assert (!s.prev);
1796                 assert (s.count is 2);
1797                 s.replace ('O');
1798                 assert (t.slice() == "hellO wOrld");
1799                 assert (s.count is 0);
1800 
1801                 t.point = 0;
1802                 assert (t.search("hellO").next);
1803                 assert (t.selection() == "hellO");
1804                 assert (t.search("hellO").next);
1805                 assert (t.selection() == "hellO");
1806         }
1807 }