132
|
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 storing and manipulating Unicode characters.
|
|
13
|
|
14 _Text maintains a current "selection", controlled via the mark(),
|
|
15 select() and selectPrior() methods. Each of append(), prepend(),
|
|
16 replace() and remove() operate with respect to the selection. The
|
|
17 select() methods operate with respect to the current selection
|
|
18 also, providing a means of iterating across matched patterns. To
|
|
19 set a selection across the entire content, use the mark() method
|
|
20 with no arguments.
|
|
21
|
|
22 Indexes and lengths of content always count code units, not code
|
|
23 points. This is similar to traditional ascii string handling, yet
|
|
24 indexing is rarely used in practice due to the selection idiom:
|
|
25 substring indexing is generally implied as opposed to manipulated
|
|
26 directly. This allows for a more streamlined model with regard to
|
|
27 surrogates.
|
|
28
|
|
29 Strings support a range of functionality, from insert and removal
|
|
30 to utf encoding and decoding. There is also an immutable subset
|
|
31 called TextView, intended to simplify life in a multi-threaded
|
|
32 environment. However, TextView must expose the raw content as
|
|
33 needed and thus immutability depends to an extent upon so-called
|
|
34 "honour" of a callee. D does not enable immutability enforcement
|
|
35 at this time, but this class will be modified to support such a
|
|
36 feature when it arrives - via the slice() method.
|
|
37
|
|
38 The class is templated for use with char[], wchar[], and dchar[],
|
|
39 and should migrate across encodings seamlessly. In particular, all
|
|
40 functions in tango.text.Util are compatible with _Text content in
|
|
41 any of the supported encodings. In future, this class will become
|
|
42 the principal gateway to the extensive ICU unicode library.
|
|
43
|
|
44 Note that several common text operations can be constructed through
|
|
45 combining tango.text._Text with tango.text.Util e.g. lines of text
|
|
46 can be processed thusly:
|
|
47 ---
|
|
48 auto source = new Text!(char)("one\ntwo\nthree");
|
|
49
|
|
50 foreach (line; Util.lines(source.slice))
|
|
51 // do something with line
|
|
52 ---
|
|
53
|
|
54 Substituting patterns within text can be implemented simply and
|
|
55 rather efficiently:
|
|
56 ---
|
|
57 auto dst = new Text!(char);
|
|
58
|
|
59 foreach (element; Util.patterns ("all cows eat grass", "eat", "chew"))
|
|
60 dst.append (element);
|
|
61 ---
|
|
62
|
|
63 Speaking a bit like Yoda might be accomplished as follows:
|
|
64 ---
|
|
65 auto dst = new Text!(char);
|
|
66
|
|
67 foreach (element; Util.delims ("all cows eat grass", " "))
|
|
68 dst.prepend (element);
|
|
69 ---
|
|
70
|
|
71 Below is an overview of the API and class hierarchy:
|
|
72 ---
|
|
73 class Text(T) : TextView!(T)
|
|
74 {
|
|
75 // set or reset the content
|
|
76 Text set (T[] chars, bool mutable=true);
|
|
77 Text set (TextView other, bool mutable=true);
|
|
78
|
|
79 // retreive currently selected text
|
|
80 T[] selection ();
|
|
81
|
|
82 // get the index and length of the current selection
|
|
83 Span selectionSpan ();
|
|
84
|
|
85 // mark a selection
|
|
86 Text select (int start=0, int length=int.max);
|
|
87
|
|
88 // move the selection around
|
|
89 bool select (T c);
|
|
90 bool select (T[] pattern);
|
|
91 bool select (TextView other);
|
|
92 bool selectPrior (T c);
|
|
93 bool selectPrior (T[] pattern);
|
|
94 bool selectPrior (TextView other);
|
|
95
|
|
96 // append behind current selection
|
|
97 Text append (TextView other);
|
|
98 Text append (T[] text);
|
|
99 Text append (T chr, int count=1);
|
|
100 Text append (int value, options);
|
|
101 Text append (long value, options);
|
|
102 Text append (double value, options);
|
|
103
|
|
104 // transcode behind current selection
|
|
105 Text encode (char[]);
|
|
106 Text encode (wchar[]);
|
|
107 Text encode (dchar[]);
|
|
108
|
|
109 // insert before current selection
|
|
110 Text prepend (T[] text);
|
|
111 Text prepend (TextView other);
|
|
112 Text prepend (T chr, int count=1);
|
|
113
|
|
114 // replace current selection
|
|
115 Text replace (T chr);
|
|
116 Text replace (T[] text);
|
|
117 Text replace (TextView other);
|
|
118
|
|
119 // remove current selection
|
|
120 Text remove ();
|
|
121
|
|
122 // clear content
|
|
123 Text clear ();
|
|
124
|
|
125 // trim leading and trailing whitespace
|
|
126 Text trim ();
|
|
127
|
|
128 // trim leading and trailing chr instances
|
|
129 Text strip (T chr);
|
|
130
|
|
131 // truncate at point, or current selection
|
|
132 Text truncate (int point = int.max);
|
|
133
|
|
134 // reserve some space for inserts/additions
|
|
135 Text reserve (int extra);
|
|
136 }
|
|
137
|
|
138 class TextView(T) : UniText
|
|
139 {
|
|
140 // hash content
|
|
141 hash_t toHash ();
|
|
142
|
|
143 // return length of content
|
|
144 uint length ();
|
|
145
|
|
146 // compare content
|
|
147 bool equals (T[] text);
|
|
148 bool equals (TextView other);
|
|
149 bool ends (T[] text);
|
|
150 bool ends (TextView other);
|
|
151 bool starts (T[] text);
|
|
152 bool starts (TextView other);
|
|
153 int compare (T[] text);
|
|
154 int compare (TextView other);
|
|
155 int opEquals (Object other);
|
|
156 int opCmp (Object other);
|
|
157
|
|
158 // copy content
|
|
159 T[] copy (T[] dst);
|
|
160
|
|
161 // return content
|
|
162 T[] slice ();
|
|
163
|
|
164 // return data type
|
|
165 typeinfo encoding ();
|
|
166
|
|
167 // replace the comparison algorithm
|
|
168 Comparator comparator (Comparator other);
|
|
169 }
|
|
170
|
|
171 class UniText
|
|
172 {
|
|
173 // convert content
|
|
174 abstract char[] toString (char[] dst = null);
|
|
175 abstract wchar[] toString16 (wchar[] dst = null);
|
|
176 abstract dchar[] toString32 (dchar[] dst = null);
|
|
177 }
|
|
178 ---
|
|
179
|
|
180 *******************************************************************************/
|
|
181
|
|
182 module tango.text.Text;
|
|
183
|
|
184 private import Util = tango.text.Util;
|
|
185
|
|
186 private import Utf = tango.text.convert.Utf;
|
|
187
|
|
188 private import Float = tango.text.convert.Float;
|
|
189
|
|
190 private import Integer = tango.text.convert.Integer;
|
|
191
|
|
192 /*******************************************************************************
|
|
193
|
|
194 *******************************************************************************/
|
|
195
|
|
196 private extern (C) void memmove (void* dst, void* src, uint bytes);
|
|
197
|
|
198
|
|
199 /*******************************************************************************
|
|
200
|
|
201 The mutable Text class actually implements the full API, whereas
|
|
202 the superclasses are purely abstract (could be interfaces instead).
|
|
203
|
|
204 *******************************************************************************/
|
|
205
|
|
206 class Text(T) : TextView!(T)
|
|
207 {
|
|
208 public alias set opAssign;
|
|
209 public alias append opCatAssign;
|
|
210 private alias TextView!(T) TextViewT;
|
|
211
|
|
212 private T[] content;
|
|
213 private bool mutable;
|
|
214 private Comparator comparator_;
|
|
215 private uint selectPoint,
|
|
216 selectLength,
|
|
217 contentLength;
|
|
218
|
|
219 /***********************************************************************
|
|
220
|
|
221 Selection span
|
|
222
|
|
223 ***********************************************************************/
|
|
224
|
|
225 public struct Span
|
|
226 {
|
|
227 uint begin, /// index of selection point
|
|
228 length; /// length of selection
|
|
229 }
|
|
230
|
|
231 /***********************************************************************
|
|
232
|
|
233 Create an empty Text with the specified available
|
|
234 space
|
|
235
|
|
236 ***********************************************************************/
|
|
237
|
|
238 this (uint space = 0)
|
|
239 {
|
|
240 content.length = space;
|
|
241 this.comparator_ = &simpleComparator;
|
|
242 }
|
|
243
|
|
244 /***********************************************************************
|
|
245
|
|
246 Create a Text upon the provided content. If said
|
|
247 content is immutable (read-only) then you might consider
|
|
248 setting the 'copy' parameter to false. Doing so will
|
|
249 avoid allocating heap-space for the content until it is
|
|
250 modified via Text methods. This can be useful when
|
|
251 wrapping an array "temporarily" with a stack-based Text
|
|
252
|
|
253 ***********************************************************************/
|
|
254
|
|
255 this (T[] content, bool copy = true)
|
|
256 {
|
|
257 set (content, copy);
|
|
258 this.comparator_ = &simpleComparator;
|
|
259 }
|
|
260
|
|
261 /***********************************************************************
|
|
262
|
|
263 Create a Text via the content of another. If said
|
|
264 content is immutable (read-only) then you might consider
|
|
265 setting the 'copy' parameter to false. Doing so will avoid
|
|
266 allocating heap-space for the content until it is modified
|
|
267 via Text methods. This can be useful when wrapping an array
|
|
268 temporarily with a stack-based Text
|
|
269
|
|
270 ***********************************************************************/
|
|
271
|
|
272 this (TextViewT other, bool copy = true)
|
|
273 {
|
|
274 this (other.slice, copy);
|
|
275 }
|
|
276
|
|
277 /***********************************************************************
|
|
278
|
|
279 Set the content to the provided array. Parameter 'copy'
|
|
280 specifies whether the given array is likely to change. If
|
|
281 not, the array is aliased until such time it is altered via
|
|
282 this class. This can be useful when wrapping an array
|
|
283 "temporarily" with a stack-based Text
|
|
284
|
|
285 ***********************************************************************/
|
|
286
|
|
287 final Text set (T[] chars, bool copy = true)
|
|
288 {
|
|
289 if ((this.mutable = copy) is true)
|
|
290 content = chars.dup;
|
|
291 else
|
|
292 content = chars;
|
|
293
|
|
294 return select (0, contentLength = chars.length);
|
|
295 }
|
|
296
|
|
297 /***********************************************************************
|
|
298
|
|
299 Replace the content of this Text. If the new content
|
|
300 is immutable (read-only) then you might consider setting the
|
|
301 'copy' parameter to false. Doing so will avoid allocating
|
|
302 heap-space for the content until it is modified via one of
|
|
303 these methods. This can be useful when wrapping an array
|
|
304 "temporarily" with a stack-based Text
|
|
305
|
|
306 ***********************************************************************/
|
|
307
|
|
308 final Text set (TextViewT other, bool copy = true)
|
|
309 {
|
|
310 return set (other.slice, copy);
|
|
311 }
|
|
312
|
|
313 /***********************************************************************
|
|
314
|
|
315 Explicitly set the current selection
|
|
316
|
|
317 ***********************************************************************/
|
|
318
|
|
319 final Text select (int start=0, int length=int.max)
|
|
320 {
|
|
321 pinIndices (start, length);
|
|
322 selectPoint = start;
|
|
323 selectLength = length;
|
|
324 return this;
|
|
325 }
|
|
326
|
|
327 /***********************************************************************
|
|
328
|
|
329 Return the currently selected content
|
|
330
|
|
331 ***********************************************************************/
|
|
332
|
|
333 final T[] selection ()
|
|
334 {
|
|
335 return slice [selectPoint .. selectPoint+selectLength];
|
|
336 }
|
|
337
|
|
338 /***********************************************************************
|
|
339
|
|
340 Return the index and length of the current selection
|
|
341
|
|
342 ***********************************************************************/
|
|
343
|
|
344 final Span selectionSpan ()
|
|
345 {
|
|
346 Span span;
|
|
347 span.begin = selectPoint;
|
|
348 span.length = selectLength;
|
|
349 return span;
|
|
350 }
|
|
351
|
|
352 /***********************************************************************
|
|
353
|
|
354 Find and select the next occurrence of a BMP code point
|
|
355 in a string. Returns true if found, false otherwise
|
|
356
|
|
357 ***********************************************************************/
|
|
358
|
|
359 final bool select (T c)
|
|
360 {
|
|
361 auto s = slice();
|
|
362 auto x = Util.locate (s, c, selectPoint);
|
|
363 if (x < s.length)
|
|
364 {
|
|
365 select (x, 1);
|
|
366 return true;
|
|
367 }
|
|
368 return false;
|
|
369 }
|
|
370
|
|
371 /***********************************************************************
|
|
372
|
|
373 Find and select the next substring occurrence. Returns
|
|
374 true if found, false otherwise
|
|
375
|
|
376 ***********************************************************************/
|
|
377
|
|
378 final bool select (TextViewT other)
|
|
379 {
|
|
380 return select (other.slice);
|
|
381 }
|
|
382
|
|
383 /***********************************************************************
|
|
384
|
|
385 Find and select the next substring occurrence. Returns
|
|
386 true if found, false otherwise
|
|
387
|
|
388 ***********************************************************************/
|
|
389
|
|
390 final bool select (T[] chars)
|
|
391 {
|
|
392 auto s = slice();
|
|
393 auto x = Util.locatePattern (s, chars, selectPoint);
|
|
394 if (x < s.length)
|
|
395 {
|
|
396 select (x, chars.length);
|
|
397 return true;
|
|
398 }
|
|
399 return false;
|
|
400 }
|
|
401
|
|
402 /***********************************************************************
|
|
403
|
|
404 Find and select a prior occurrence of a BMP code point
|
|
405 in a string. Returns true if found, false otherwise
|
|
406
|
|
407 ***********************************************************************/
|
|
408
|
|
409 final bool selectPrior (T c)
|
|
410 {
|
|
411 auto s = slice();
|
|
412 auto x = Util.locatePrior (s, c, selectPoint);
|
|
413 if (x < s.length)
|
|
414 {
|
|
415 select (x, 1);
|
|
416 return true;
|
|
417 }
|
|
418 return false;
|
|
419 }
|
|
420
|
|
421 /***********************************************************************
|
|
422
|
|
423 Find and select a prior substring occurrence. Returns
|
|
424 true if found, false otherwise
|
|
425
|
|
426 ***********************************************************************/
|
|
427
|
|
428 final bool selectPrior (TextViewT other)
|
|
429 {
|
|
430 return selectPrior (other.slice);
|
|
431 }
|
|
432
|
|
433 /***********************************************************************
|
|
434
|
|
435 Find and select a prior substring occurrence. Returns
|
|
436 true if found, false otherwise
|
|
437
|
|
438 ***********************************************************************/
|
|
439
|
|
440 final bool selectPrior (T[] chars)
|
|
441 {
|
|
442 auto s = slice();
|
|
443 auto x = Util.locatePatternPrior (s, chars, selectPoint);
|
|
444 if (x < s.length)
|
|
445 {
|
|
446 select (x, chars.length);
|
|
447 return true;
|
|
448 }
|
|
449 return false;
|
|
450 }
|
|
451
|
|
452 /***********************************************************************
|
|
453
|
|
454 Append text to this Text
|
|
455
|
|
456 ***********************************************************************/
|
|
457
|
|
458 final Text append (TextViewT other)
|
|
459 {
|
|
460 return append (other.slice);
|
|
461 }
|
|
462
|
|
463 /***********************************************************************
|
|
464
|
|
465 Append text to this Text
|
|
466
|
|
467 ***********************************************************************/
|
|
468
|
|
469 final Text append (T[] chars)
|
|
470 {
|
|
471 return append (chars.ptr, chars.length);
|
|
472 }
|
|
473
|
|
474 /***********************************************************************
|
|
475
|
|
476 Append a count of characters to this Text
|
|
477
|
|
478 ***********************************************************************/
|
|
479
|
|
480 final Text append (T chr, int count=1)
|
|
481 {
|
|
482 uint point = selectPoint + selectLength;
|
|
483 expand (point, count);
|
|
484 return set (chr, point, count);
|
|
485 }
|
|
486
|
|
487 /***********************************************************************
|
|
488
|
|
489 Append an integer to this Text
|
|
490
|
|
491 ***********************************************************************/
|
|
492
|
|
493 final Text append (int v, Integer.Style fmt=Integer.Style.Signed)
|
|
494 {
|
|
495 return append (cast(long) v, fmt);
|
|
496 }
|
|
497
|
|
498 /***********************************************************************
|
|
499
|
|
500 Append a long to this Text
|
|
501
|
|
502 ***********************************************************************/
|
|
503
|
|
504 final Text append (long v, Integer.Style fmt=Integer.Style.Signed)
|
|
505 {
|
|
506 T[64] tmp = void;
|
|
507 return append (Integer.format(tmp, v, fmt));
|
|
508 }
|
|
509
|
|
510 /***********************************************************************
|
|
511
|
|
512 Append a double to this Text
|
|
513
|
|
514 ***********************************************************************/
|
|
515
|
|
516 final Text append (double v, uint decimals=2, int e=10)
|
|
517 {
|
|
518 T[64] tmp = void;
|
|
519 return append (Float.format(tmp, v, decimals, e));
|
|
520 }
|
|
521
|
|
522 /***********************************************************************
|
|
523
|
|
524 Insert characters into this Text
|
|
525
|
|
526 ***********************************************************************/
|
|
527
|
|
528 final Text prepend (T chr, int count=1)
|
|
529 {
|
|
530 expand (selectPoint, count);
|
|
531 return set (chr, selectPoint, count);
|
|
532 }
|
|
533
|
|
534 /***********************************************************************
|
|
535
|
|
536 Insert text into this Text
|
|
537
|
|
538 ***********************************************************************/
|
|
539
|
|
540 final Text prepend (T[] other)
|
|
541 {
|
|
542 expand (selectPoint, other.length);
|
|
543 content[selectPoint..selectPoint+other.length] = other;
|
|
544 return this;
|
|
545 }
|
|
546
|
|
547 /***********************************************************************
|
|
548
|
|
549 Insert another Text into this Text
|
|
550
|
|
551 ***********************************************************************/
|
|
552
|
|
553 final Text prepend (TextViewT other)
|
|
554 {
|
|
555 return prepend (other.slice);
|
|
556 }
|
|
557
|
|
558 /***********************************************************************
|
|
559
|
|
560 Append encoded text at the current selection point. The text
|
|
561 is converted as necessary to the appropritate utf encoding.
|
|
562
|
|
563 ***********************************************************************/
|
|
564
|
|
565 final Text encode (char[] s)
|
|
566 {
|
|
567 T[1024] tmp = void;
|
|
568
|
|
569 static if (is (T == char))
|
|
570 return append(s);
|
|
571
|
|
572 static if (is (T == wchar))
|
|
573 return append (Utf.toString16(s, tmp));
|
|
574
|
|
575 static if (is (T == dchar))
|
|
576 return append (Utf.toString32(s, tmp));
|
|
577 }
|
|
578
|
|
579 /// ditto
|
|
580 final Text encode (wchar[] s)
|
|
581 {
|
|
582 T[1024] tmp = void;
|
|
583
|
|
584 static if (is (T == char))
|
|
585 return append (Utf.toString(s, tmp));
|
|
586
|
|
587 static if (is (T == wchar))
|
|
588 return append (s);
|
|
589
|
|
590 static if (is (T == dchar))
|
|
591 return append (Utf.toString32(s, tmp));
|
|
592 }
|
|
593
|
|
594 /// ditto
|
|
595 final Text encode (dchar[] s)
|
|
596 {
|
|
597 T[1024] tmp = void;
|
|
598
|
|
599 static if (is (T == char))
|
|
600 return append (Utf.toString(s, tmp));
|
|
601
|
|
602 static if (is (T == wchar))
|
|
603 return append (Utf.toString16(s, tmp));
|
|
604
|
|
605 static if (is (T == dchar))
|
|
606 return append (s);
|
|
607 }
|
|
608
|
|
609 /// ditto
|
|
610 final Text encode (Object o)
|
|
611 {
|
|
612 return encode (o.toString);
|
|
613 }
|
|
614
|
|
615 /***********************************************************************
|
|
616
|
|
617 Replace a section of this Text with the specified
|
|
618 character
|
|
619
|
|
620 ***********************************************************************/
|
|
621
|
|
622 final Text replace (T chr)
|
|
623 {
|
|
624 return set (chr, selectPoint, selectLength);
|
|
625 }
|
|
626
|
|
627 /***********************************************************************
|
|
628
|
|
629 Replace a section of this Text with the specified
|
|
630 array
|
|
631
|
|
632 ***********************************************************************/
|
|
633
|
|
634 final Text replace (T[] chars)
|
|
635 {
|
|
636 int chunk = chars.length - selectLength;
|
|
637 if (chunk >= 0)
|
|
638 expand (selectPoint, chunk);
|
|
639 else
|
|
640 remove (selectPoint, -chunk);
|
|
641
|
|
642 content [selectPoint .. selectPoint+chars.length] = chars;
|
|
643 return select (selectPoint, chars.length);
|
|
644 }
|
|
645
|
|
646 /***********************************************************************
|
|
647
|
|
648 Replace a section of this Text with another
|
|
649
|
|
650 ***********************************************************************/
|
|
651
|
|
652 final Text replace (TextViewT other)
|
|
653 {
|
|
654 return replace (other.slice);
|
|
655 }
|
|
656
|
|
657 /***********************************************************************
|
|
658
|
|
659 Remove the selection from this Text and reset the
|
|
660 selection to zero length
|
|
661
|
|
662 ***********************************************************************/
|
|
663
|
|
664 final Text remove ()
|
|
665 {
|
|
666 remove (selectPoint, selectLength);
|
|
667 return select (selectPoint, 0);
|
|
668 }
|
|
669
|
|
670 /***********************************************************************
|
|
671
|
|
672 Remove the selection from this Text
|
|
673
|
|
674 ***********************************************************************/
|
|
675
|
|
676 private Text remove (int start, int count)
|
|
677 {
|
|
678 pinIndices (start, count);
|
|
679 if (count > 0)
|
|
680 {
|
|
681 if (! mutable)
|
|
682 realloc ();
|
|
683
|
|
684 uint i = start + count;
|
|
685 memmove (content.ptr+start, content.ptr+i, (contentLength-i) * T.sizeof);
|
|
686 contentLength -= count;
|
|
687 }
|
|
688 return this;
|
|
689 }
|
|
690
|
|
691 /***********************************************************************
|
|
692
|
|
693 Truncate this string at an optional index. Default behaviour
|
|
694 is to truncate at the current append point. Current selection
|
|
695 is moved to the truncation point, with length 0
|
|
696
|
|
697 ***********************************************************************/
|
|
698
|
|
699 final Text truncate (int index = int.max)
|
|
700 {
|
|
701 if (index is int.max)
|
|
702 index = selectPoint + selectLength;
|
|
703
|
|
704 pinIndex (index);
|
|
705 return select (contentLength = index, 0);
|
|
706 }
|
|
707
|
|
708 /***********************************************************************
|
|
709
|
|
710 Clear the string content
|
|
711
|
|
712 ***********************************************************************/
|
|
713
|
|
714 final Text clear ()
|
|
715 {
|
|
716 return select (contentLength = 0, 0);
|
|
717 }
|
|
718
|
|
719 /***********************************************************************
|
|
720
|
|
721 Remove leading and trailing whitespace from this Text,
|
|
722 and reset the selection to the trimmed content
|
|
723
|
|
724 ***********************************************************************/
|
|
725
|
|
726 final Text trim ()
|
|
727 {
|
|
728 content = Util.trim (slice);
|
|
729 select (0, contentLength = content.length);
|
|
730 return this;
|
|
731 }
|
|
732
|
|
733 /***********************************************************************
|
|
734
|
|
735 Remove leading and trailing matches from this Text,
|
|
736 and reset the selection to the stripped content
|
|
737
|
|
738 ***********************************************************************/
|
|
739
|
|
740 final Text strip (T matches)
|
|
741 {
|
|
742 content = Util.strip (slice, matches);
|
|
743 select (0, contentLength = content.length);
|
|
744 return this;
|
|
745 }
|
|
746
|
|
747 /***********************************************************************
|
|
748
|
|
749 Reserve some extra room
|
|
750
|
|
751 ***********************************************************************/
|
|
752
|
|
753 final Text reserve (uint extra)
|
|
754 {
|
|
755 realloc (extra);
|
|
756 return this;
|
|
757 }
|
|
758
|
|
759
|
|
760
|
|
761 /* ======================== TextView methods ======================== */
|
|
762
|
|
763
|
|
764
|
|
765 /***********************************************************************
|
|
766
|
|
767 Get the encoding type
|
|
768
|
|
769 ***********************************************************************/
|
|
770
|
|
771 final TypeInfo encoding()
|
|
772 {
|
|
773 return typeid(T);
|
|
774 }
|
|
775
|
|
776 /***********************************************************************
|
|
777
|
|
778 Set the comparator delegate. Where other is null, we behave
|
|
779 as a getter only
|
|
780
|
|
781 ***********************************************************************/
|
|
782
|
|
783 final Comparator comparator (Comparator other)
|
|
784 {
|
|
785 auto tmp = comparator_;
|
|
786 if (other)
|
|
787 comparator_ = other;
|
|
788 return tmp;
|
|
789 }
|
|
790
|
|
791 /***********************************************************************
|
|
792
|
|
793 Hash this Text
|
|
794
|
|
795 ***********************************************************************/
|
|
796
|
|
797 override hash_t toHash ()
|
|
798 {
|
|
799 return Util.jhash (cast(ubyte*) content.ptr, contentLength * T.sizeof);
|
|
800 }
|
|
801
|
|
802 /***********************************************************************
|
|
803
|
|
804 Return the length of the valid content
|
|
805
|
|
806 ***********************************************************************/
|
|
807
|
|
808 final uint length ()
|
|
809 {
|
|
810 return contentLength;
|
|
811 }
|
|
812
|
|
813 /***********************************************************************
|
|
814
|
|
815 Is this Text equal to another?
|
|
816
|
|
817 ***********************************************************************/
|
|
818
|
|
819 final bool equals (TextViewT other)
|
|
820 {
|
|
821 if (other is this)
|
|
822 return true;
|
|
823 return equals (other.slice);
|
|
824 }
|
|
825
|
|
826 /***********************************************************************
|
|
827
|
|
828 Is this Text equal to the provided text?
|
|
829
|
|
830 ***********************************************************************/
|
|
831
|
|
832 final bool equals (T[] other)
|
|
833 {
|
|
834 if (other.length == contentLength)
|
|
835 return Util.matching (other.ptr, content.ptr, contentLength);
|
|
836 return false;
|
|
837 }
|
|
838
|
|
839 /***********************************************************************
|
|
840
|
|
841 Does this Text end with another?
|
|
842
|
|
843 ***********************************************************************/
|
|
844
|
|
845 final bool ends (TextViewT other)
|
|
846 {
|
|
847 return ends (other.slice);
|
|
848 }
|
|
849
|
|
850 /***********************************************************************
|
|
851
|
|
852 Does this Text end with the specified string?
|
|
853
|
|
854 ***********************************************************************/
|
|
855
|
|
856 final bool ends (T[] chars)
|
|
857 {
|
|
858 if (chars.length <= contentLength)
|
|
859 return Util.matching (content.ptr+(contentLength-chars.length), chars.ptr, chars.length);
|
|
860 return false;
|
|
861 }
|
|
862
|
|
863 /***********************************************************************
|
|
864
|
|
865 Does this Text start with another?
|
|
866
|
|
867 ***********************************************************************/
|
|
868
|
|
869 final bool starts (TextViewT other)
|
|
870 {
|
|
871 return starts (other.slice);
|
|
872 }
|
|
873
|
|
874 /***********************************************************************
|
|
875
|
|
876 Does this Text start with the specified string?
|
|
877
|
|
878 ***********************************************************************/
|
|
879
|
|
880 final bool starts (T[] chars)
|
|
881 {
|
|
882 if (chars.length <= contentLength)
|
|
883 return Util.matching (content.ptr, chars.ptr, chars.length);
|
|
884 return false;
|
|
885 }
|
|
886
|
|
887 /***********************************************************************
|
|
888
|
|
889 Compare this Text start with another. Returns 0 if the
|
|
890 content matches, less than zero if this Text is "less"
|
|
891 than the other, or greater than zero where this Text
|
|
892 is "bigger".
|
|
893
|
|
894 ***********************************************************************/
|
|
895
|
|
896 final int compare (TextViewT other)
|
|
897 {
|
|
898 if (other is this)
|
|
899 return 0;
|
|
900
|
|
901 return compare (other.slice);
|
|
902 }
|
|
903
|
|
904 /***********************************************************************
|
|
905
|
|
906 Compare this Text start with an array. Returns 0 if the
|
|
907 content matches, less than zero if this Text is "less"
|
|
908 than the other, or greater than zero where this Text
|
|
909 is "bigger".
|
|
910
|
|
911 ***********************************************************************/
|
|
912
|
|
913 final int compare (T[] chars)
|
|
914 {
|
|
915 return comparator_ (slice, chars);
|
|
916 }
|
|
917
|
|
918 /***********************************************************************
|
|
919
|
|
920 Return content from this Text
|
|
921
|
|
922 A slice of dst is returned, representing a copy of the
|
|
923 content. The slice is clipped to the minimum of either
|
|
924 the length of the provided array, or the length of the
|
|
925 content minus the stipulated start point
|
|
926
|
|
927 ***********************************************************************/
|
|
928
|
|
929 final T[] copy (T[] dst)
|
|
930 {
|
|
931 uint i = contentLength;
|
|
932 if (i > dst.length)
|
|
933 i = dst.length;
|
|
934
|
|
935 return dst [0 .. i] = content [0 .. i];
|
|
936 }
|
|
937
|
|
938 /***********************************************************************
|
|
939
|
|
940 Return an alias to the content of this TextView. Note
|
|
941 that you are bound by honour to leave this content wholly
|
|
942 unmolested. D surely needs some way to enforce immutability
|
|
943 upon array references
|
|
944
|
|
945 ***********************************************************************/
|
|
946
|
|
947 final T[] slice ()
|
|
948 {
|
|
949 return content [0 .. contentLength];
|
|
950 }
|
|
951
|
|
952 /***********************************************************************
|
|
953
|
|
954 Convert to the UniText types. The optional argument
|
|
955 dst will be resized as required to house the conversion.
|
|
956 To minimize heap allocation during subsequent conversions,
|
|
957 apply the following pattern:
|
|
958 ---
|
|
959 _Text string;
|
|
960
|
|
961 wchar[] buffer;
|
|
962 wchar[] result = string.utf16 (buffer);
|
|
963
|
|
964 if (result.length > buffer.length)
|
|
965 buffer = result;
|
|
966 ---
|
|
967 You can also provide a buffer from the stack, but the output
|
|
968 will be moved to the heap if said buffer is not large enough
|
|
969
|
|
970 ***********************************************************************/
|
|
971
|
|
972 final char[] toString (char[] dst = null)
|
|
973 {
|
|
974 static if (is (T == char))
|
|
975 return slice();
|
|
976
|
|
977 static if (is (T == wchar))
|
|
978 return Utf.toString (slice, dst);
|
|
979
|
|
980 static if (is (T == dchar))
|
|
981 return Utf.toString (slice, dst);
|
|
982 }
|
|
983
|
|
984 /// ditto
|
|
985 final wchar[] toString16 (wchar[] dst = null)
|
|
986 {
|
|
987 static if (is (T == char))
|
|
988 return Utf.toString16 (slice, dst);
|
|
989
|
|
990 static if (is (T == wchar))
|
|
991 return slice;
|
|
992
|
|
993 static if (is (T == dchar))
|
|
994 return Utf.toString16 (slice, dst);
|
|
995 }
|
|
996
|
|
997 /// ditto
|
|
998 final dchar[] toString32 (dchar[] dst = null)
|
|
999 {
|
|
1000 static if (is (T == char))
|
|
1001 return Utf.toString32 (slice, dst);
|
|
1002
|
|
1003 static if (is (T == wchar))
|
|
1004 return Utf.toString32 (slice, dst);
|
|
1005
|
|
1006 static if (is (T == dchar))
|
|
1007 return slice;
|
|
1008 }
|
|
1009
|
|
1010 /***********************************************************************
|
|
1011
|
|
1012 Compare this Text to another. We compare against other
|
|
1013 Strings only. Literals and other objects are not supported
|
|
1014
|
|
1015 ***********************************************************************/
|
|
1016
|
|
1017 override int opCmp (Object o)
|
|
1018 {
|
|
1019 auto other = cast (TextViewT) o;
|
|
1020
|
|
1021 if (other is null)
|
|
1022 return -1;
|
|
1023
|
|
1024 return compare (other);
|
|
1025 }
|
|
1026
|
|
1027 /***********************************************************************
|
|
1028
|
|
1029 Is this Text equal to the text of something else?
|
|
1030
|
|
1031 ***********************************************************************/
|
|
1032
|
|
1033 override int opEquals (Object o)
|
|
1034 {
|
|
1035 auto other = cast (TextViewT) o;
|
|
1036
|
|
1037 if (other)
|
|
1038 return equals (other);
|
|
1039
|
|
1040 // this can become expensive ...
|
|
1041 char[1024] tmp = void;
|
|
1042 return this.toString(tmp) == o.toString;
|
|
1043 }
|
|
1044
|
|
1045 /// ditto
|
|
1046 final int opEquals (T[] s)
|
|
1047 {
|
|
1048 return slice == s;
|
|
1049 }
|
|
1050
|
|
1051 /***********************************************************************
|
|
1052
|
|
1053 Pin the given index to a valid position.
|
|
1054
|
|
1055 ***********************************************************************/
|
|
1056
|
|
1057 private void pinIndex (inout int x)
|
|
1058 {
|
|
1059 if (x > contentLength)
|
|
1060 x = contentLength;
|
|
1061 }
|
|
1062
|
|
1063 /***********************************************************************
|
|
1064
|
|
1065 Pin the given index and length to a valid position.
|
|
1066
|
|
1067 ***********************************************************************/
|
|
1068
|
|
1069 private void pinIndices (inout int start, inout int length)
|
|
1070 {
|
|
1071 if (start > contentLength)
|
|
1072 start = contentLength;
|
|
1073
|
|
1074 if (length > (contentLength - start))
|
|
1075 length = contentLength - start;
|
|
1076 }
|
|
1077
|
|
1078 /***********************************************************************
|
|
1079
|
|
1080 Compare two arrays. Returns 0 if the content matches, less
|
|
1081 than zero if A is "less" than B, or greater than zero where
|
|
1082 A is "bigger". Where the substrings match, the shorter is
|
|
1083 considered "less".
|
|
1084
|
|
1085 ***********************************************************************/
|
|
1086
|
|
1087 private int simpleComparator (T[] a, T[] b)
|
|
1088 {
|
|
1089 uint i = a.length;
|
|
1090 if (b.length < i)
|
|
1091 i = b.length;
|
|
1092
|
|
1093 for (int j, k; j < i; ++j)
|
|
1094 if ((k = a[j] - b[j]) != 0)
|
|
1095 return k;
|
|
1096
|
|
1097 return a.length - b.length;
|
|
1098 }
|
|
1099
|
|
1100 /***********************************************************************
|
|
1101
|
|
1102 Make room available to insert or append something
|
|
1103
|
|
1104 ***********************************************************************/
|
|
1105
|
|
1106 private void expand (uint index, uint count)
|
|
1107 {
|
|
1108 if (!mutable || (contentLength + count) > content.length)
|
|
1109 realloc (count);
|
|
1110
|
|
1111 memmove (content.ptr+index+count, content.ptr+index, (contentLength - index) * T.sizeof);
|
|
1112 selectLength += count;
|
|
1113 contentLength += count;
|
|
1114 }
|
|
1115
|
|
1116 /***********************************************************************
|
|
1117
|
|
1118 Replace a section of this Text with the specified
|
|
1119 character
|
|
1120
|
|
1121 ***********************************************************************/
|
|
1122
|
|
1123 private Text set (T chr, uint start, uint count)
|
|
1124 {
|
|
1125 content [start..start+count] = chr;
|
|
1126 return this;
|
|
1127 }
|
|
1128
|
|
1129 /***********************************************************************
|
|
1130
|
|
1131 Allocate memory due to a change in the content. We handle
|
|
1132 the distinction between mutable and immutable here.
|
|
1133
|
|
1134 ***********************************************************************/
|
|
1135
|
|
1136 private void realloc (uint count = 0)
|
|
1137 {
|
|
1138 uint size = (content.length + count + 127) & ~127;
|
|
1139
|
|
1140 if (mutable)
|
|
1141 content.length = size;
|
|
1142 else
|
|
1143 {
|
|
1144 mutable = true;
|
|
1145 T[] x = content;
|
|
1146 content = new T[size];
|
|
1147 if (contentLength)
|
|
1148 content[0..contentLength] = x;
|
|
1149 }
|
|
1150 }
|
|
1151
|
|
1152 /***********************************************************************
|
|
1153
|
|
1154 Internal method to support Text appending
|
|
1155
|
|
1156 ***********************************************************************/
|
|
1157
|
|
1158 private Text append (T* chars, uint count)
|
|
1159 {
|
|
1160 uint point = selectPoint + selectLength;
|
|
1161 expand (point, count);
|
|
1162 content[point .. point+count] = chars[0 .. count];
|
|
1163 return this;
|
|
1164 }
|
|
1165 }
|
|
1166
|
|
1167
|
|
1168
|
|
1169 /*******************************************************************************
|
|
1170
|
|
1171 Immutable string
|
|
1172
|
|
1173 *******************************************************************************/
|
|
1174
|
|
1175 class TextView(T) : UniText
|
|
1176 {
|
|
1177 public typedef int delegate (T[] a, T[] b) Comparator;
|
|
1178
|
|
1179 /***********************************************************************
|
|
1180
|
|
1181 Return the length of the valid content
|
|
1182
|
|
1183 ***********************************************************************/
|
|
1184
|
|
1185 abstract uint length ();
|
|
1186
|
|
1187 /***********************************************************************
|
|
1188
|
|
1189 Is this Text equal to another?
|
|
1190
|
|
1191 ***********************************************************************/
|
|
1192
|
|
1193 abstract bool equals (TextView other);
|
|
1194
|
|
1195 /***********************************************************************
|
|
1196
|
|
1197 Is this Text equal to the the provided text?
|
|
1198
|
|
1199 ***********************************************************************/
|
|
1200
|
|
1201 abstract bool equals (T[] other);
|
|
1202
|
|
1203 /***********************************************************************
|
|
1204
|
|
1205 Does this Text end with another?
|
|
1206
|
|
1207 ***********************************************************************/
|
|
1208
|
|
1209 abstract bool ends (TextView other);
|
|
1210
|
|
1211 /***********************************************************************
|
|
1212
|
|
1213 Does this Text end with the specified string?
|
|
1214
|
|
1215 ***********************************************************************/
|
|
1216
|
|
1217 abstract bool ends (T[] chars);
|
|
1218
|
|
1219 /***********************************************************************
|
|
1220
|
|
1221 Does this Text start with another?
|
|
1222
|
|
1223 ***********************************************************************/
|
|
1224
|
|
1225 abstract bool starts (TextView other);
|
|
1226
|
|
1227 /***********************************************************************
|
|
1228
|
|
1229 Does this Text start with the specified string?
|
|
1230
|
|
1231 ***********************************************************************/
|
|
1232
|
|
1233 abstract bool starts (T[] chars);
|
|
1234
|
|
1235 /***********************************************************************
|
|
1236
|
|
1237 Compare this Text start with another. Returns 0 if the
|
|
1238 content matches, less than zero if this Text is "less"
|
|
1239 than the other, or greater than zero where this Text
|
|
1240 is "bigger".
|
|
1241
|
|
1242 ***********************************************************************/
|
|
1243
|
|
1244 abstract int compare (TextView other);
|
|
1245
|
|
1246 /***********************************************************************
|
|
1247
|
|
1248 Compare this Text start with an array. Returns 0 if the
|
|
1249 content matches, less than zero if this Text is "less"
|
|
1250 than the other, or greater than zero where this Text
|
|
1251 is "bigger".
|
|
1252
|
|
1253 ***********************************************************************/
|
|
1254
|
|
1255 abstract int compare (T[] chars);
|
|
1256
|
|
1257 /***********************************************************************
|
|
1258
|
|
1259 Return content from this Text. A slice of dst is
|
|
1260 returned, representing a copy of the content. The
|
|
1261 slice is clipped to the minimum of either the length
|
|
1262 of the provided array, or the length of the content
|
|
1263 minus the stipulated start point
|
|
1264
|
|
1265 ***********************************************************************/
|
|
1266
|
|
1267 abstract T[] copy (T[] dst);
|
|
1268
|
|
1269 /***********************************************************************
|
|
1270
|
|
1271 Compare this Text to another
|
|
1272
|
|
1273 ***********************************************************************/
|
|
1274
|
|
1275 abstract int opCmp (Object o);
|
|
1276
|
|
1277 /***********************************************************************
|
|
1278
|
|
1279 Is this Text equal to another?
|
|
1280
|
|
1281 ***********************************************************************/
|
|
1282
|
|
1283 abstract int opEquals (Object other);
|
|
1284
|
|
1285 /***********************************************************************
|
|
1286
|
|
1287 Is this Text equal to another?
|
|
1288
|
|
1289 ***********************************************************************/
|
|
1290
|
|
1291 abstract int opEquals (T[] other);
|
|
1292
|
|
1293 /***********************************************************************
|
|
1294
|
|
1295 Get the encoding type
|
|
1296
|
|
1297 ***********************************************************************/
|
|
1298
|
|
1299 abstract TypeInfo encoding();
|
|
1300
|
|
1301 /***********************************************************************
|
|
1302
|
|
1303 Set the comparator delegate
|
|
1304
|
|
1305 ***********************************************************************/
|
|
1306
|
|
1307 abstract Comparator comparator (Comparator other);
|
|
1308
|
|
1309 /***********************************************************************
|
|
1310
|
|
1311 Hash this Text
|
|
1312
|
|
1313 ***********************************************************************/
|
|
1314
|
|
1315 abstract hash_t toHash ();
|
|
1316
|
|
1317 /***********************************************************************
|
|
1318
|
|
1319 Return an alias to the content of this TextView. Note
|
|
1320 that you are bound by honour to leave this content wholly
|
|
1321 unmolested. D surely needs some way to enforce immutability
|
|
1322 upon array references
|
|
1323
|
|
1324 ***********************************************************************/
|
|
1325
|
|
1326 abstract T[] slice ();
|
|
1327 }
|
|
1328
|
|
1329
|
|
1330 /*******************************************************************************
|
|
1331
|
|
1332 A string abstraction that converts to anything
|
|
1333
|
|
1334 *******************************************************************************/
|
|
1335
|
|
1336 class UniText
|
|
1337 {
|
|
1338 abstract char[] toString (char[] dst = null);
|
|
1339
|
|
1340 abstract wchar[] toString16 (wchar[] dst = null);
|
|
1341
|
|
1342 abstract dchar[] toString32 (dchar[] dst = null);
|
|
1343
|
|
1344 abstract TypeInfo encoding();
|
|
1345 }
|
|
1346
|
|
1347
|
|
1348
|
|
1349 /*******************************************************************************
|
|
1350
|
|
1351 *******************************************************************************/
|
|
1352
|
|
1353 debug (UnitTest)
|
|
1354 {
|
|
1355 //void main() {}
|
|
1356 unittest
|
|
1357 {
|
|
1358 auto s = new Text!(char);
|
|
1359 s = "hello";
|
|
1360
|
|
1361 s.select ("hello");
|
|
1362 assert (s.selection == "hello");
|
|
1363 s.replace ("1");
|
|
1364 assert (s.selection == "1");
|
|
1365 assert (s == "1");
|
|
1366
|
|
1367 assert (s.clear == "");
|
|
1368
|
|
1369 assert (s.append(12345) == "12345");
|
|
1370 assert (s.selection == "12345");
|
|
1371
|
|
1372 //s.append ("fubar");
|
|
1373 s ~= "fubar";
|
|
1374 assert (s.selection == "12345fubar");
|
|
1375 assert (s.select('5'));
|
|
1376 assert (s.selection == "5");
|
|
1377 assert (s.remove == "1234fubar");
|
|
1378 assert (s.select("fubar"));
|
|
1379 assert (s.selection == "fubar");
|
|
1380 assert (s.select("wumpus") is false);
|
|
1381 assert (s.selection == "fubar");
|
|
1382
|
|
1383 assert (s.clear.append(1.2345, 4) == "1.2345");
|
|
1384
|
|
1385 assert (s.clear.append(0xf0, Integer.Style.Binary) == "11110000");
|
|
1386
|
|
1387 assert (s.clear.encode("one"d).toString == "one");
|
|
1388
|
|
1389 assert (Util.splitLines(s.clear.append("a\nb").slice).length is 2);
|
|
1390
|
|
1391 assert (s.select.replace("almost ") == "almost ");
|
|
1392 foreach (element; Util.patterns ("all cows eat grass", "eat", "chew"))
|
|
1393 s.append (element);
|
|
1394 assert (s.selection == "almost all cows chew grass");
|
|
1395 }
|
|
1396 }
|