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 }