1 /*******************************************************************************
2  * Copyright (c) 2000, 2008 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  * Port to the D programming language:
11  *     Frank Benoit <benoit@tionex.de>
12  *******************************************************************************/
13 module org.eclipse.swt.custom.DefaultContent;
14 
15 import org.eclipse.swt.SWT;
16 import org.eclipse.swt.SWTException;
17 import org.eclipse.swt.internal.Compatibility;
18 import org.eclipse.swt.widgets.TypedListener;
19 import org.eclipse.swt.custom.StyledTextContent;
20 import org.eclipse.swt.custom.TextChangeListener;
21 import org.eclipse.swt.custom.StyledTextEvent;
22 import org.eclipse.swt.custom.StyledTextListener;
23 import org.eclipse.swt.custom.StyledText;
24 
25 import java.lang.all;
26 import java.nonstandard.UnsafeUtf;
27 
28 version(Tango){
29     static import tango.io.model.IFile;
30 } else { // Phobos
31     static import std..string;
32     static import std.ascii;
33 }
34 
35 
36 class DefaultContent : StyledTextContent {
37     version(Tango){
38         private const static String LineDelimiter = tango.io.model.IFile.FileConst.NewlineString;
39     } else { // Phobos
40         private const static String LineDelimiter = std.ascii.newline;
41     }
42 
43     StyledTextListener[] textListeners; // stores text listeners for event sending
44     char[] textStore; // stores the actual text
45     int gapStart = -1;  // the character position start of the gap
46     int gapEnd = -1;    // the character position after the end of the gap
47     int gapLine = -1;   // the line on which the gap exists, the gap will always be associated with one line
48     int highWatermark = 300;
49     int lowWatermark = 50;
50 
51     int[][] lines; // array of character positions and lengths representing the lines of text
52     int lineCount_ = 0;  // the number of lines of text
53     int expandExp = 1;  // the expansion exponent, used to increase the lines array exponentially
54     int replaceExpandExp = 1;   // the expansion exponent, used to increase the lines array exponentially
55 
56 /**
57  * Creates a new DefaultContent and initializes it.  A <code>StyledTextContent</> will always have
58  * at least one empty line.
59  */
60 this() {
61     lines = new int[][]( 50, 2 );
62     setText("");
63 }
64 /**
65  * Adds a line to the end of the line indexes array.  Increases the size of the array if necessary.
66  * <code>lineCount</code> is updated to reflect the new entry.
67  * <p>
68  *
69  * @param start the start of the line
70  * @param length the length of the line
71  */
72 void addLineIndex(int start, int length) {
73     auto size = lines.length;
74     if (lineCount_ is size) {
75         // expand the lines by powers of 2
76         auto newLines = new int[][]( size+Compatibility.pow2(expandExp), 2 );
77         System.arraycopy(lines, 0, newLines, 0, size);
78         lines = newLines;
79         expandExp++;
80     }
81     auto range = [start, length];
82     lines[lineCount_] = range;
83     lineCount_++;
84 }
85 /**
86  * Adds a line index to the end of <code>linesArray</code>.  Increases the
87  * size of the array if necessary and returns a new array.
88  * <p>
89  *
90  * @param start the start of the line
91  * @param length the length of the line
92  * @param linesArray the array to which to add the line index
93  * @param count the position at which to add the line
94  * @return a new array of line indexes
95  */
96 int[][] addLineIndex(int start, int length, int[][] linesArray, int count) {
97     auto size = linesArray.length;
98     int[][] newLines = linesArray;
99     if (count is size) {
100         newLines = new int[][]( size+Compatibility.pow2(replaceExpandExp), 2 );
101         replaceExpandExp++;
102         System.arraycopy(linesArray, 0, newLines, 0, size);
103     }
104     int[] range = [start, length];
105     newLines[count] = range;
106     return newLines;
107 }
108 /**
109  * Adds a <code>TextChangeListener</code> listening for
110  * <code>TextChangingEvent</code> and <code>TextChangedEvent</code>. A
111  * <code>TextChangingEvent</code> is sent before changes to the text occur.
112  * A <code>TextChangedEvent</code> is sent after changes to the text
113  * occurred.
114  * <p>
115  *
116  * @param listener the listener
117  * @exception IllegalArgumentException <ul>
118  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
119  * </ul>
120  */
121 public void addTextChangeListener(TextChangeListener listener) {
122     if (listener is null) error(SWT.ERROR_NULL_ARGUMENT);
123     StyledTextListener typedListener = new StyledTextListener(listener);
124     textListeners ~= typedListener;
125 }
126 /**
127  * Adjusts the gap to accommodate a text change that is occurring.
128  * <p>
129  *
130  * @param position the position at which a change is occurring
131  * @param sizeHint the size of the change
132  * @param line the line where the gap will go
133  */
134 void adjustGap(int position, int sizeHint, int line) {
135     if (position is gapStart) {
136         // text is being inserted at the gap position
137         int size = (gapEnd - gapStart) - sizeHint;
138         if (lowWatermark <= size && size <= highWatermark)
139             return;
140     } else if ((position + sizeHint is gapStart) && (sizeHint < 0)) {
141         // text is being deleted at the gap position
142         int size = (gapEnd - gapStart) - sizeHint;
143         if (lowWatermark <= size && size <= highWatermark)
144             return;
145     }
146     moveAndResizeGap(position, sizeHint, line);
147 }
148 /**
149  * Calculates the indexes of each line in the text store.  Assumes no gap exists.
150  * Optimized to do less checking.
151  */
152 void indexLines(){
153     int start = 0;
154     lineCount_ = 0;
155     auto textLength = textStore.length;
156     typeof(textLength) i;
157     for (i = start; i < textLength; i++) {
158         char ch = textStore[i];
159         if (ch is SWT.CR) {
160             // see if the next character is a LF
161             if (i + 1 < textLength) {
162                 ch = textStore[i+1];
163                 if (ch is SWT.LF) {
164                     i++;
165                 }
166             }
167             addLineIndex(start, cast(int)/*64bit*/(i - start + 1));
168             start = cast(int)/*64bit*/(i + 1);
169         } else if (ch is SWT.LF) {
170             addLineIndex(start, cast(int)/*64bit*/(i - start + 1));
171             start = cast(int)/*64bit*/(i + 1);
172         }
173     }
174     addLineIndex(start, cast(int)/*64bit*/(i - start));
175 }
176 /**
177  * Returns whether or not the given character is a line delimiter.  Both CR and LF
178  * are valid line delimiters.
179  * <p>
180  *
181  * @param ch the character to test
182  * @return true if ch is a delimiter, false otherwise
183  */
184 bool isDelimiter(char ch) {
185     if (ch is SWT.CR) return true;
186     if (ch is SWT.LF) return true;
187     return false;
188 }
189 /**
190  * Determine whether or not the replace operation is valid.  DefaultContent will not allow
191  * the /r/n line delimiter to be split or partially deleted.
192  * <p>
193  *
194  * @param start start offset of text to replace
195  * @param replaceLength start offset of text to replace
196  * @param newText start offset of text to replace
197  * @return a bool specifying whether or not the replace operation is valid
198  */
199 protected bool isValidReplace(int start, int replaceLength, String newText){
200     if (replaceLength is 0) {
201         // inserting text, see if the \r\n line delimiter is being split
202         if (start is 0) return true;
203         if (start is getCharCount()) return true;
204         char before = getTextRange(start - 1, 1)[0];
205         if (before is '\r') {
206             char after = getTextRange(start, 1)[0];
207             if (after is '\n') return false;
208         }
209     } else {
210         // deleting text, see if part of a \r\n line delimiter is being deleted
211         char startChar = getTextRange(start, 1)[0];
212         if (startChar is '\n') {
213             // see if char before delete position is \r
214             if (start !is 0) {
215                 char before = getTextRange(start - 1, 1)[0];
216                 if (before is '\r') return false;
217             }
218         }
219         char endChar = getTextRange(start + replaceLength - 1, 1)[0];
220         if (endChar is '\r') {
221             // see if char after delete position is \n
222             if (start + replaceLength !is getCharCount()) {
223                 char after = getTextRange(start + replaceLength, 1)[0];
224                 if (after is '\n') return false;
225             }
226         }
227     }
228     return true;
229 }
230 /**
231  * Calculates the indexes of each line of text in the given range.
232  * <p>
233  *
234  * @param offset the logical start offset of the text lineate
235  * @param length the length of the text to lineate, includes gap
236  * @param numLines the number of lines to initially allocate for the line index array,
237  *  passed in for efficiency (the exact number of lines may be known)
238  * @return a line indexes array where each line is identified by a start offset and
239  *  a length
240  */
241 int[][] indexLines(int offset, int length, int numLines){
242     int[][] indexedLines = new int[][]( numLines, 2 );
243     int start = 0;
244     int lineCount_ = 0;
245     int i;
246     replaceExpandExp = 1;
247     for (i = start; i < length; i++) {
248         int location = i + offset;
249         if ((location >= gapStart) && (location < gapEnd)) {
250             // ignore the gap
251         } else {
252             char ch = textStore[location];
253             if (ch is SWT.CR) {
254                 // see if the next character is a LF
255                 if (location+1 < textStore.length) {
256                     ch = textStore[location+1];
257                     if (ch is SWT.LF) {
258                         i++;
259                     }
260                 }
261                 indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount_);
262                 lineCount_++;
263                 start = i + 1;
264             } else if (ch is SWT.LF) {
265                 indexedLines = addLineIndex(start, i - start + 1, indexedLines, lineCount_);
266                 lineCount_++;
267                 start = i + 1;
268             }
269         }
270     }
271     int[][] newLines = new int[][]( lineCount_+1, 2 );
272     System.arraycopy(indexedLines, 0, newLines, 0, lineCount_);
273     int[] range = [start, i - start];
274     newLines[lineCount_] = range;
275     return newLines;
276 }
277 /**
278  * Inserts text.
279  * <p>
280  *
281  * @param position the position at which to insert the text
282  * @param text the text to insert
283  */
284 void insert(int position, String text) {
285     if (text.length is 0) return;
286 
287     int startLine = getLineAtOffset(position);
288     int change = cast(int)/*64bit*/text.length;
289     bool endInsert = position is getCharCount();
290     adjustGap(position, change, startLine);
291 
292     // during an insert the gap will be adjusted to start at
293     // position and it will be associated with startline, the
294     // inserted text will be placed in the gap
295     int startLineOffset = getOffsetAtLine(startLine);
296     // at this point, startLineLength will include the start line
297     // and all of the newly inserted text
298     int startLineLength = cast(int)/*64bit*/getPhysicalLine(startLine).length;
299 
300     if (change > 0) {
301         // shrink gap
302         gapStart += (change);
303         for (int i = 0; i < text.length; i++) {
304             textStore[position + i]= text[i];
305         }
306     }
307 
308     // figure out the number of new lines that have been inserted
309     auto newLines = indexLines(startLineOffset, startLineLength, 10);
310     // only insert an empty line if it is the last line in the text
311     int numNewLines = cast(int)/*64bit*/newLines.length - 1;
312     if (newLines[numNewLines][1] is 0) {
313         // last inserted line is a new line
314         if (endInsert) {
315             // insert happening at end of the text, leave numNewLines as
316             // is since the last new line will not be concatenated with another
317             // line
318             numNewLines += 1;
319         } else {
320             numNewLines -= 1;
321         }
322     }
323 
324     // make room for the new lines
325     expandLinesBy(numNewLines);
326     // shift down the lines after the replace line
327     for (ptrdiff_t i = cast(ptrdiff_t) (lineCount_) - 1; i > startLine; i--) {
328         lines[i + numNewLines]=lines[i];
329     }
330     // insert the new lines
331     for (int i = 0; i < numNewLines; i++) {
332         newLines[i][0] += startLineOffset;
333         lines[startLine + i]=newLines[i];
334     }
335     // update the last inserted line
336     if (numNewLines < newLines.length) {
337         newLines[numNewLines][0] += startLineOffset;
338         lines[startLine + numNewLines] = newLines[numNewLines];
339     }
340 
341     lineCount_ += numNewLines;
342     gapLine = getLineAtPhysicalOffset(gapStart);
343 }
344 /**
345  * Moves the gap and adjusts its size in anticipation of a text change.
346  * The gap is resized to actual size + the specified size and moved to the given
347  * position.
348  * <p>
349  *
350  * @param position the position at which a change is occurring
351  * @param size the size of the change
352  * @param newGapLine the line where the gap should be put
353  */
354 void moveAndResizeGap(int position, int size, int newGapLine) {
355     char[] content = null;
356     int oldSize = gapEnd - gapStart;
357     int newSize;
358     if (size > 0) {
359         newSize = highWatermark + size;
360     } else {
361         newSize = lowWatermark - size;
362     }
363     // remove the old gap from the lines information
364     if (gapExists()) {
365         // adjust the line length
366         lines[gapLine][1] = lines[gapLine][1] - oldSize;
367         // adjust the offsets of the lines after the gapLine
368         for (int i = gapLine + 1; i < lineCount_; i++) {
369             lines[i][0] = lines[i][0] - oldSize;
370         }
371     }
372 
373     if (newSize < 0) {
374         if (oldSize > 0) {
375             // removing the gap
376             content = new char[textStore.length - oldSize];
377             System.arraycopy(textStore, 0, content, 0, gapStart);
378             System.arraycopy(textStore, gapEnd, content, gapStart, content.length - gapStart);
379             textStore = content;
380         }
381         gapStart = gapEnd = position;
382         return;
383     }
384     content = new char[textStore.length + (newSize - oldSize)];
385     int newGapStart = position;
386     int newGapEnd = newGapStart + newSize;
387     if (oldSize is 0) {
388         System.arraycopy(textStore, 0, content, 0, newGapStart);
389         System.arraycopy(textStore, newGapStart, content, newGapEnd, content.length - newGapEnd);
390     } else if (newGapStart < gapStart) {
391         int delta = gapStart - newGapStart;
392         System.arraycopy(textStore, 0, content, 0, newGapStart);
393         System.arraycopy(textStore, newGapStart, content, newGapEnd, delta);
394         System.arraycopy(textStore, gapEnd, content, newGapEnd + delta, textStore.length - gapEnd);
395     } else {
396         int delta = newGapStart - gapStart;
397         System.arraycopy(textStore, 0, content, 0, gapStart);
398         System.arraycopy(textStore, gapEnd, content, gapStart, delta);
399         System.arraycopy(textStore, gapEnd + delta, content, newGapEnd, content.length - newGapEnd);
400     }
401     textStore = content;
402     gapStart = newGapStart;
403     gapEnd = newGapEnd;
404 
405     // add the new gap to the lines information
406     if (gapExists()) {
407         gapLine = newGapLine;
408         // adjust the line length
409         int gapLength = gapEnd - gapStart;
410         lines[gapLine][1] = lines[gapLine][1] + (gapLength);
411         // adjust the offsets of the lines after the gapLine
412         for (int i = gapLine + 1; i < lineCount_; i++) {
413             lines[i][0] = lines[i][0] + gapLength;
414         }
415     }
416 }
417 /**
418  * Returns the number of lines that are in the specified text.
419  * <p>
420  *
421  * @param startOffset the start of the text to lineate
422  * @param length the length of the text to lineate
423  * @return number of lines
424  */
425 int lineCount(int startOffset, int length){
426     if (length is 0) {
427         return 0;
428     }
429     int lineCount_ = 0;
430     int count = 0;
431     int i = startOffset;
432     if (i >= gapStart) {
433         i += gapEnd - gapStart;
434     }
435     while (count < length) {
436         if ((i >= gapStart) && (i < gapEnd)) {
437             // ignore the gap
438         } else {
439             char ch = textStore[i];
440             if (ch is SWT.CR) {
441                 // see if the next character is a LF
442                 if (i + 1 < textStore.length) {
443                     ch = textStore[i+1];
444                     if (ch is SWT.LF) {
445                         i++;
446                         count++;
447                     }
448                 }
449                 lineCount_++;
450             } else if (ch is SWT.LF) {
451                 lineCount_++;
452             }
453             count++;
454         }
455         i++;
456     }
457     return lineCount_;
458 }
459 /**
460  * Returns the number of lines that are in the specified text.
461  * <p>
462  *
463  * @param text the text to lineate
464  * @return number of lines in the text
465  */
466 int lineCount(String text){
467     int lineCount_ = 0;
468     auto length = text.length;
469     for (int i = 0; i < length; i++) {
470         char ch = text[i];
471         if (ch is SWT.CR) {
472             if (i + 1 < length && text[i + 1] is SWT.LF) {
473                 i++;
474             }
475             lineCount_++;
476         } else if (ch is SWT.LF) {
477             lineCount_++;
478         }
479     }
480     return lineCount_;
481 }
482 /**
483  * @return the logical length of the text store
484  */
485 public int getCharCount() {
486     int length = gapEnd - gapStart;
487     return cast(int)/*64bit*/textStore.length - length;
488 }
489 /**
490  * Returns the line at <code>index</code> without delimiters.
491  * <p>
492  *
493  * @param index the index of the line to return
494  * @return the logical line text (i.e., without the gap)
495  * @exception IllegalArgumentException <ul>
496  *   <li>ERROR_INVALID_ARGUMENT when index is out of range</li>
497  * </ul>
498  */
499 public String getLine(int index) {
500     if ((index >= lineCount_) || (index < 0)) error(SWT.ERROR_INVALID_ARGUMENT);
501     auto start = lines[index][0];
502     auto length_ = lines[index][1];
503     auto end = start + length_ - 1;
504     if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
505         // line is before or after the gap
506         while ((length_ - 1 >= 0) && isDelimiter(textStore[start+length_-1])) {
507             length_--;
508         }
509         return textStore[ start .. start + length_]._idup();
510     } else {
511         // gap is in the specified range, strip out the gap
512         StringBuffer buf = new StringBuffer();
513         int gapLength = gapEnd - gapStart;
514         buf.append(textStore[ start .. gapStart ] );
515         buf.append(textStore[ gapEnd .. gapEnd + length_ - gapLength - (gapStart - start) ]);
516         length_ = cast(int)/*64bit*/buf.length;
517         while ((length_ - 1 >=0) && isDelimiter(buf.slice[length_ - 1])) {
518             length_--;
519         }
520         return buf.slice()[ 0 .. length_ ]._idup();
521     }
522 }
523 /**
524  * Returns the line delimiter that should be used by the StyledText
525  * widget when inserting new lines.  This delimiter may be different than the
526  * delimiter that is used by the <code>StyledTextContent</code> interface.
527  * <p>
528  *
529  * @return the platform line delimiter as specified in the line.separator
530  *  system property.
531  */
532 public String getLineDelimiter() {
533     return LineDelimiter;
534 }
535 /**
536  * Returns the line at the given index with delimiters.
537  * <p>
538  * @param index the index of the line to return
539  * @return the logical line text (i.e., without the gap) with delimiters
540  */
541 String getFullLine(int index) {
542     auto start = lines[index][0];
543     auto length_ = lines[index][1];
544     auto end = start + length_ - 1;
545     if (!gapExists() || (end < gapStart) || (start >= gapEnd)) {
546         // line is before or after the gap
547         return textStore[ start .. start + length_ ]._idup();
548     } else {
549         // gap is in the specified range, strip out the gap
550         StringBuffer buffer = new StringBuffer();
551         int gapLength = gapEnd - gapStart;
552         buffer.append(textStore[ start .. gapStart ]);
553         buffer.append(textStore[ gapEnd .. gapEnd + length_ - gapLength - (gapStart - start) ]);
554         return buffer.toString();
555     }
556 }
557 /**
558  * Returns the physical line at the given index (i.e., with delimiters and the gap).
559  * <p>
560  *
561  * @param index the line index
562  * @return the physical line
563  */
564 String getPhysicalLine(int index) {
565     int start = lines[index][0];
566     int length_ = lines[index][1];
567     return getPhysicalText(start, length_);
568 }
569 /**
570  * @return the number of lines in the text store
571  */
572 public int getLineCount(){
573     return lineCount_;
574 }
575 /**
576  * Returns the line at the given offset.
577  * DWT: index can be an invalid UTF-8 index
578  * <p>
579  *
580  * @param charPosition logical character offset (i.e., does not include gap)
581  * @return the line index
582  * @exception IllegalArgumentException <ul>
583  *    <li>ERROR_INVALID_ARGUMENT when charPosition is out of range</li>
584  * </ul>
585  */
586 public int getLineAtOffset(int charPosition){
587     if ((charPosition > getCharCount()) || (charPosition < 0)) error(SWT.ERROR_INVALID_ARGUMENT);
588     int position;
589     if (charPosition < gapStart) {
590         // position is before the gap
591         position = charPosition;
592     } else {
593         // position includes the gap
594         position = charPosition + (gapEnd - gapStart);
595     }
596 
597     // if last line and the line is not empty you can ask for
598     // a position that doesn't exist (the one to the right of the
599     // last character) - for inserting
600     if (lineCount_ > 0) {
601         int lastLine = lineCount_ - 1;
602         if (position is lines[lastLine][0] + lines[lastLine][1])
603             return lastLine;
604     }
605 
606     int high = lineCount_;
607     int low = -1;
608     int index = lineCount_;
609     while (high - low > 1) {
610         index = (high + low) / 2;
611         int lineStart = lines[index][0];
612         int lineEnd = lineStart + lines[index][1] - 1;
613         if (position <= lineStart) {
614             high = index;
615         } else if (position <= lineEnd) {
616             high = index;
617             break;
618         } else {
619             low = index;
620         }
621     }
622     return high;
623 }
624 /**
625  * Returns the line index at the given physical offset.
626  * <p>
627  *
628  * @param position physical character offset (i.e., includes gap)
629  * @return the line index
630  */
631 int getLineAtPhysicalOffset(int position){
632     int high = lineCount_;
633     int low = -1;
634     int index = lineCount_;
635     while (high - low > 1) {
636         index = (high + low) / 2;
637         int lineStart = lines[index][0];
638         int lineEnd = lineStart + lines[index][1] - 1;
639         if (position <= lineStart) {
640             high = index;
641         } else if (position <= lineEnd) {
642             high = index;
643             break;
644         } else {
645             low = index;
646         }
647     }
648     return high;
649 }
650 /**
651  * Returns the logical offset of the given line.
652  * <p>
653  *
654  * @param lineIndex index of line
655  * @return the logical starting offset of the line.  When there are not any lines,
656  *  getOffsetAtLine(0) is a valid call that should answer 0.
657  * @exception IllegalArgumentException <ul>
658  *   <li>ERROR_INVALID_ARGUMENT when lineIndex is out of range</li>
659  * </ul>
660  */
661 public int getOffsetAtLine(int lineIndex) {
662     if (lineIndex is 0) return 0;
663     if ((lineIndex >= lineCount_) || (lineIndex < 0)) error(SWT.ERROR_INVALID_ARGUMENT);
664     int start = lines[lineIndex][0];
665     if (start > gapEnd) {
666         return start - (gapEnd - gapStart);
667     } else {
668         return start;
669     }
670 }
671 /**
672  * Increases the line indexes array to accommodate more lines.
673  * <p>
674  *
675  * @param numLines the number to increase the array by
676  */
677 void expandLinesBy(int numLines) {
678     int size = cast(int)/*64bit*/lines.length;
679     if (size - lineCount_ >= numLines) {
680         return;
681     }
682     int[][] newLines = new int[][]( size+Math.max(10, numLines), 2 );
683     System.arraycopy(lines, 0, newLines, 0, size);
684     lines = newLines;
685 }
686 /**
687  * Reports an SWT error.
688  * <p>
689  *
690  * @param code the error code
691  */
692 void error (int code) {
693     SWT.error(code);
694 }
695 /**
696  * Returns whether or not a gap exists in the text store.
697  * <p>
698  *
699  * @return true if gap exists, false otherwise
700  */
701 bool gapExists() {
702     return gapStart !is gapEnd;
703 }
704 /**
705  * Returns a string representing the continuous content of
706  * the text store.
707  * <p>
708  *
709  * @param start the physical start offset of the text to return
710  * @param length the physical length of the text to return
711  * @return the text
712  */
713 String getPhysicalText(int start, int length_) {
714     return textStore[ start .. start + length_ ]._idup();
715 }
716 /**
717  * Returns a string representing the logical content of
718  * the text store (i.e., gap stripped out).
719  * <p>
720  *
721  * @param start the logical start offset of the text to return
722  * @param length the logical length of the text to return
723  * @return the text
724  */
725 public String getTextRange(int start, int length_) {
726     if (textStore is null)
727         return "";
728     if (length_ is 0)
729         return "";
730     int end= start + length_;
731     if (!gapExists() || (end < gapStart))
732         return textStore[ start .. start + length_]._idup();
733     if (gapStart < start) {
734         int gapLength= gapEnd - gapStart;
735         return textStore[ start + gapLength .. start + gapLength + length_ ]._idup();
736     }
737     StringBuffer buf = new StringBuffer();
738     buf.append(textStore[ start .. start + gapStart - start ] );
739     buf.append(textStore[ gapEnd .. gapEnd + end - gapStart ] );
740     return buf.toString();
741 }
742 /**
743  * Removes the specified <code>TextChangeListener</code>.
744  * <p>
745  *
746  * @param listener the listener which should no longer be notified
747  *
748  * @exception IllegalArgumentException <ul>
749  *    <li>ERROR_NULL_ARGUMENT when listener is null</li>
750  * </ul>
751  */
752 public void removeTextChangeListener(TextChangeListener listener){
753     if (listener is null) error(SWT.ERROR_NULL_ARGUMENT);
754     for (int i = 0; i < textListeners.length; i++) {
755         TypedListener typedListener = cast(TypedListener) textListeners[i];
756         if (typedListener.getEventListener () is listener) {
757             textListeners = textListeners[ 0 .. i ] ~ textListeners[ i+1 .. $ ];
758             break;
759         }
760     }
761 }
762 /**
763  * Replaces the text with <code>newText</code> starting at position <code>start</code>
764  * for a length of <code>replaceLength</code>.  Notifies the appropriate listeners.
765  * <p>
766  *
767  * When sending the TextChangingEvent, <code>newLineCount</code> is the number of
768  * lines that are going to be inserted and <code>replaceLineCount</code> is
769  * the number of lines that are going to be deleted, based on the change
770  * that occurs visually.  For example:
771  * <ul>
772  * <li>(replaceText,newText) is> (replaceLineCount,newLineCount)
773  * <li>("","\n") is> (0,1)
774  * <li>("\n\n","a") is> (2,0)
775  * </ul>
776  * </p>
777  *
778  * @param start start offset of text to replace
779  * @param replaceLength start offset of text to replace
780  * @param newText start offset of text to replace
781  *
782  * @exception SWTException <ul>
783  *   <li>ERROR_INVALID_ARGUMENT when the text change results in a multi byte
784  *      line delimiter being split or partially deleted.  Splitting a line
785  *      delimiter by inserting text between the CR and LF characters of the
786  *      \r\n delimiter or deleting part of this line delimiter is not supported</li>
787  * </ul>
788  */
789 public void replaceTextRange(int start, int replaceLength, String newText){
790     // check for invalid replace operations
791     if (!isValidReplace(start, replaceLength, newText)) SWT.error(SWT.ERROR_INVALID_ARGUMENT);
792 
793     // inform listeners
794     StyledTextEvent event = new StyledTextEvent(this);
795     event.type = StyledText.TextChanging;
796     event.start = start;
797     event.replaceLineCount = lineCount(start, replaceLength);
798     event.text = newText;
799     event.newLineCount = lineCount(newText);
800     event.replaceCharCount = replaceLength;
801     event.newCharCount = cast(int)/*64bit*/newText.length;
802     sendTextEvent(event);
803 
804     // first delete the text to be replaced
805     delete_(start, replaceLength, event.replaceLineCount + 1);
806     // then insert the new text
807     insert(start, newText);
808     // inform listeners
809     event = new StyledTextEvent(this);
810     event.type = StyledText.TextChanged;
811     sendTextEvent(event);
812 }
813 /**
814  * Sends the text listeners the TextChanged event.
815  */
816 void sendTextEvent(StyledTextEvent event) {
817     for (int i = 0; i < textListeners.length; i++) {
818         (cast(StyledTextListener)textListeners[i]).handleEvent(event);
819     }
820 }
821 /**
822  * Sets the content to text and removes the gap since there are no sensible predictions
823  * about where the next change will occur.
824  * <p>
825  *
826  * @param text the text
827  */
828 public void setText (String text){
829     textStore = text.dup;
830     gapStart = -1;
831     gapEnd = -1;
832     expandExp = 1;
833     indexLines();
834     StyledTextEvent event = new StyledTextEvent(this);
835     event.type = StyledText.TextSet;
836     event.text = "";
837     sendTextEvent(event);
838 }
839 /**
840  * Deletes text.
841  * <p>
842  * @param position the position at which the text to delete starts
843  * @param length the length of the text to delete
844  * @param numLines the number of lines that are being deleted
845  */
846 void delete_(int position, int length_, int numLines) {
847     if (length_ is 0) return;
848 
849     int startLine = getLineAtOffset(position);
850     int startLineOffset = getOffsetAtLine(startLine);
851     int endLine = getLineAtOffset(position + length_);
852 
853     String endText = "";
854     bool splittingDelimiter = false;
855     if (position + length_ < getCharCount()) {
856         endText = getTextRange(position + length_ - 1, 2);
857         if ((endText[0] is SWT.CR) && (endText[1] is SWT.LF)) {
858             splittingDelimiter = true;
859         }
860     }
861 
862     adjustGap(position + length_, -length_, startLine);
863     int [][] oldLines = indexLines(position, length_ + (gapEnd - gapStart), numLines);
864 
865     // enlarge the gap - the gap can be enlarged either to the
866     // right or left
867     if (position + length_ is gapStart) {
868         gapStart -= length_;
869     } else {
870         gapEnd += length_;
871     }
872 
873     // figure out the length of the new concatenated line, do so by
874     // finding the first line delimiter after position
875     int j = position;
876     bool eol = false;
877     while (j < textStore.length && !eol) {
878         if (j < gapStart || j >= gapEnd) {
879             char ch = textStore[j];
880             if (isDelimiter(ch)) {
881                 if (j + 1 < textStore.length) {
882                     if (ch is SWT.CR && (textStore[j+1] is SWT.LF)) {
883                         j++;
884                     }
885                 }
886                 eol = true;
887             }
888         }
889         j++;
890     }
891     // update the line where the deletion started
892     lines[startLine][1] = (position - startLineOffset) + (j - position);
893     // figure out the number of lines that have been deleted
894     int numOldLines = cast(int)/*64bit*/oldLines.length - 1;
895     if (splittingDelimiter) numOldLines -= 1;
896     // shift up the lines after the last deleted line, no need to update
897     // the offset or length of the lines
898     for (int i = endLine + 1; i < lineCount_; i++) {
899         lines[i - numOldLines] = lines[i];
900     }
901     lineCount_ -= numOldLines;
902     gapLine = getLineAtPhysicalOffset(gapStart);
903 }
904 
905 
906 }