comparison dwtx/jface/text/TreeLineTracker.d @ 140:26688fec6d23

Following dsss compile errors
author Frank Benoit <benoit@tionex.de>
date Sun, 24 Aug 2008 03:23:46 +0200
parents 6dcb0baaa031
children 000f9136b8f7
comparison
equal deleted inserted replaced
139:93a6ec48fd28 140:26688fec6d23
183 * <strong>Performance:</strong> The query operations perform in <i>O(log n)</i> where <var>n</var> 183 * <strong>Performance:</strong> The query operations perform in <i>O(log n)</i> where <var>n</var>
184 * is the number of lines in the document. The modification operations roughly perform in <i>O(l * 184 * is the number of lines in the document. The modification operations roughly perform in <i>O(l *
185 * log n)</i> where <var>n</var> is the number of lines in the document and <var>l</var> is the 185 * log n)</i> where <var>n</var> is the number of lines in the document and <var>l</var> is the
186 * sum of the number of removed, added or modified lines. 186 * sum of the number of removed, added or modified lines.
187 * </p> 187 * </p>
188 * 188 *
189 * @since 3.2 189 * @since 3.2
190 */ 190 */
191 abstract class TreeLineTracker : ILineTracker { 191 abstract class TreeLineTracker : ILineTracker {
192 /* 192 /*
193 * Differential Balanced Binary Tree 193 * Differential Balanced Binary Tree
194 * 194 *
195 * Assumption: lines cannot overlap => there exists a total ordering of the lines by their offset, 195 * Assumption: lines cannot overlap => there exists a total ordering of the lines by their offset,
196 * which is the same as the ordering by line number 196 * which is the same as the ordering by line number
197 * 197 *
198 * Base idea: store lines in a binary search tree 198 * Base idea: store lines in a binary search tree
199 * - the key is the line number / line offset 199 * - the key is the line number / line offset
200 * -> lookup_line is O(log n) 200 * -> lookup_line is O(log n)
201 * -> lookup_offset is O(log n) 201 * -> lookup_offset is O(log n)
202 * - a change in a line somewhere will change any succeeding line numbers / line offsets 202 * - a change in a line somewhere will change any succeeding line numbers / line offsets
203 * -> replace is O(n) 203 * -> replace is O(n)
204 * 204 *
205 * Differential tree: instead of storing the key (line number, line offset) directly, every node 205 * Differential tree: instead of storing the key (line number, line offset) directly, every node
206 * stores the difference between its key and its parent's key 206 * stores the difference between its key and its parent's key
207 * - the sort key is still the line number / line offset, but it remains "virtual" 207 * - the sort key is still the line number / line offset, but it remains "virtual"
208 * - inserting a node (a line) really increases the virtual key of all succeeding nodes (lines), but this 208 * - inserting a node (a line) really increases the virtual key of all succeeding nodes (lines), but this
209 * fact will not be realized in the key information encoded in the nodes. 209 * fact will not be realized in the key information encoded in the nodes.
210 * -> any change only affects the nodes in the node's parent chain, although more bookkeeping 210 * -> any change only affects the nodes in the node's parent chain, although more bookkeeping
211 * has to be done when changing a node or balancing the tree 211 * has to be done when changing a node or balancing the tree
212 * -> replace is O(log n) 212 * -> replace is O(log n)
213 * -> line offsets and line numbers have to be computed when walking the tree from the root / 213 * -> line offsets and line numbers have to be computed when walking the tree from the root /
214 * from a node 214 * from a node
215 * -> still O(log n) 215 * -> still O(log n)
216 * 216 *
217 * The balancing algorithm chosen does not depend on the differential tree property. An AVL tree 217 * The balancing algorithm chosen does not depend on the differential tree property. An AVL tree
218 * implementation has been chosen for simplicity. 218 * implementation has been chosen for simplicity.
219 */ 219 */
220 220
221 /* 221 /*
222 * Turns assertions on/off. Don't make this a a debug option for performance reasons - this way 222 * Turns assertions on/off. Don't make this a a debug option for performance reasons - this way
223 * the compiler can optimize the asserts away. 223 * the compiler can optimize the asserts away.
224 */ 224 */
225 private static final bool ASSERT= false; 225 private static final bool ASSERT= false;
226 226
227 /** 227 /**
228 * The empty delimiter of the last line. The last line and only the last line must have this 228 * The empty delimiter of the last line. The last line and only the last line must have this
229 * zero-length delimiter. 229 * zero-length delimiter.
230 */ 230 */
231 private static final String NO_DELIM= ""; //$NON-NLS-1$ 231 private static final String NO_DELIM= ""; //$NON-NLS-1$
232 232
233 /** 233 /**
234 * A node represents one line. Its character and line offsets are 0-based and relative to the 234 * A node represents one line. Its character and line offsets are 0-based and relative to the
235 * subtree covered by the node. All nodes under the left subtree represent lines before, all 235 * subtree covered by the node. All nodes under the left subtree represent lines before, all
236 * nodes under the right subtree lines after the current node. 236 * nodes under the right subtree lines after the current node.
237 */ 237 */
260 Node left; 260 Node left;
261 /** The right subtree, possibly <code>null</code>. */ 261 /** The right subtree, possibly <code>null</code>. */
262 Node right; 262 Node right;
263 /** The balance factor. */ 263 /** The balance factor. */
264 byte balance; 264 byte balance;
265 265
266 /* 266 /*
267 * @see java.lang.Object#toString() 267 * @see java.lang.Object#toString()
268 */ 268 */
269 public final String toString() { 269 public final String toString() {
270 String bal; 270 String bal;
290 return "[" + offset + "+" + pureLength() + "+" + delimiter.length() + "|" + line + "|" + bal + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ 290 return "[" + offset + "+" + pureLength() + "+" + delimiter.length() + "|" + line + "|" + bal + "]"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
291 } 291 }
292 292
293 /** 293 /**
294 * Returns the pure (without the line delimiter) length of this line. 294 * Returns the pure (without the line delimiter) length of this line.
295 * 295 *
296 * @return the pure line length 296 * @return the pure line length
297 */ 297 */
298 int pureLength() { 298 int pureLength() {
299 return length - delimiter.length(); 299 return length - delimiter.length();
300 } 300 }
308 /** 308 /**
309 * Creates a new line tracker. 309 * Creates a new line tracker.
310 */ 310 */
311 protected this() { 311 protected this() {
312 } 312 }
313 313
314 /** 314 /**
315 * Package visible constructor for creating a tree tracker from a list tracker. 315 * Package visible constructor for creating a tree tracker from a list tracker.
316 * 316 *
317 * @param tracker 317 * @param tracker
318 */ 318 */
319 this(ListLineTracker tracker) { 319 this(ListLineTracker tracker) {
320 final List lines= tracker.getLines(); 320 final List lines= tracker.getLines();
321 final int n= lines.size(); 321 final int n= lines.size();
322 if (n is 0) 322 if (n is 0)
323 return; 323 return;
324 324
325 Line line= cast(Line) lines.get(0); 325 Line line= cast(Line) lines.get(0);
326 String delim= line.delimiter; 326 String delim= line.delimiter;
327 if (delim is null) 327 if (delim is null)
328 delim= NO_DELIM; 328 delim= NO_DELIM;
329 int length= line.length; 329 int length= line.length;
330 fRoot= new Node(length, delim); 330 fRoot= new Node(length, delim);
331 Node node= fRoot; 331 Node node= fRoot;
332 332
333 for (int i= 1; i < n; i++) { 333 for (int i= 1; i < n; i++) {
334 line= cast(Line) lines.get(i); 334 line= cast(Line) lines.get(i);
335 delim= line.delimiter; 335 delim= line.delimiter;
336 if (delim is null) 336 if (delim is null)
337 delim= NO_DELIM; 337 delim= NO_DELIM;
338 length= line.length; 338 length= line.length;
339 node= insertAfter(node, length, delim); 339 node= insertAfter(node, length, delim);
340 } 340 }
341 341
342 if (node.delimiter !is NO_DELIM) 342 if (node.delimiter !is NO_DELIM)
343 insertAfter(node, 0, NO_DELIM); 343 insertAfter(node, 0, NO_DELIM);
344 344
345 if cast(ASSERT) checkTree(); 345 if (ASSERT) checkTree();
346 } 346 }
347 347
348 /** 348 /**
349 * Returns the node (line) including a certain offset. If the offset is between two 349 * Returns the node (line) including a certain offset. If the offset is between two
350 * lines, the line starting at <code>offset</code> is returned. 350 * lines, the line starting at <code>offset</code> is returned.
358 * If <code>offset</code> is the document length, then this is true: 358 * If <code>offset</code> is the document length, then this is true:
359 * </p> 359 * </p>
360 * <p> 360 * <p>
361 * <code>offset= line.offset + line.length</code>. 361 * <code>offset= line.offset + line.length</code>.
362 * </p> 362 * </p>
363 * 363 *
364 * @param offset a document offset 364 * @param offset a document offset
365 * @return the line starting at or containing <code>offset</code> 365 * @return the line starting at or containing <code>offset</code>
366 * @throws BadLocationException if the offset is invalid 366 * @throws BadLocationException if the offset is invalid
367 */ 367 */
368 private Node nodeByOffset(final int offset) { 368 private Node nodeByOffset(final int offset) {
370 * Works for any binary search tree. 370 * Works for any binary search tree.
371 */ 371 */
372 int remaining= offset; 372 int remaining= offset;
373 Node node= fRoot; 373 Node node= fRoot;
374 int line= 0; 374 int line= 0;
375 375
376 while (true) { 376 while (true) {
377 if (node is null) 377 if (node is null)
378 fail(offset); 378 fail(offset);
379 379
380 if (remaining < node.offset) { 380 if (remaining < node.offset) {
381 node= node.left; 381 node= node.left;
382 } else { 382 } else {
383 remaining -= node.offset; 383 remaining -= node.offset;
384 line+= node.line; 384 line+= node.line;
389 remaining -= node.length; 389 remaining -= node.length;
390 line ++; 390 line ++;
391 node= node.right; 391 node= node.right;
392 } 392 }
393 } 393 }
394 394
395 return node; 395 return node;
396 } 396 }
397 /** 397 /**
398 * Returns the line number for the given offset. If the offset is between two lines, the line 398 * Returns the line number for the given offset. If the offset is between two lines, the line
399 * starting at <code>offset</code> is returned. The last line is returned if 399 * starting at <code>offset</code> is returned. The last line is returned if
400 * <code>offset</code> is equal to the document length. 400 * <code>offset</code> is equal to the document length.
401 * 401 *
402 * @param offset a document offset 402 * @param offset a document offset
403 * @return the line number starting at or containing <code>offset</code> 403 * @return the line number starting at or containing <code>offset</code>
404 * @throws BadLocationException if the offset is invalid 404 * @throws BadLocationException if the offset is invalid
405 */ 405 */
406 private int lineByOffset(final int offset) { 406 private int lineByOffset(final int offset) {
408 * Works for any binary search tree. 408 * Works for any binary search tree.
409 */ 409 */
410 int remaining= offset; 410 int remaining= offset;
411 Node node= fRoot; 411 Node node= fRoot;
412 int line= 0; 412 int line= 0;
413 413
414 while (true) { 414 while (true) {
415 if (node is null) 415 if (node is null)
416 fail(offset); 416 fail(offset);
417 417
418 if (remaining < node.offset) { 418 if (remaining < node.offset) {
419 node= node.left; 419 node= node.left;
420 } else { 420 } else {
421 remaining -= node.offset; 421 remaining -= node.offset;
422 line+= node.line; 422 line+= node.line;
427 line ++; 427 line ++;
428 node= node.right; 428 node= node.right;
429 } 429 }
430 } 430 }
431 } 431 }
432 432
433 /** 433 /**
434 * Returns the node (line) with the given line number. Note that the last line is always 434 * Returns the node (line) with the given line number. Note that the last line is always
435 * incomplete, i.e. has the {@link #NO_DELIM} delimiter. 435 * incomplete, i.e. has the {@link #NO_DELIM} delimiter.
436 * 436 *
437 * @param line a line number 437 * @param line a line number
438 * @return the line with the given line number 438 * @return the line with the given line number
439 * @throws BadLocationException if the line is invalid 439 * @throws BadLocationException if the line is invalid
440 */ 440 */
441 private Node nodeByLine(final int line) { 441 private Node nodeByLine(final int line) {
443 * Works for any binary search tree. 443 * Works for any binary search tree.
444 */ 444 */
445 int remaining= line; 445 int remaining= line;
446 int offset= 0; 446 int offset= 0;
447 Node node= fRoot; 447 Node node= fRoot;
448 448
449 while (true) { 449 while (true) {
450 if (node is null) 450 if (node is null)
451 fail(line); 451 fail(line);
452 452
453 if (remaining is node.line) 453 if (remaining is node.line)
454 break; 454 break;
455 if (remaining < node.line) { 455 if (remaining < node.line) {
456 node= node.left; 456 node= node.left;
457 } else { 457 } else {
458 remaining -= node.line + 1; 458 remaining -= node.line + 1;
459 offset += node.offset + node.length; 459 offset += node.offset + node.length;
460 node= node.right; 460 node= node.right;
461 } 461 }
462 } 462 }
463 463
464 return node; 464 return node;
465 } 465 }
466 466
467 /** 467 /**
468 * Returns the offset for the given line number. Note that the 468 * Returns the offset for the given line number. Note that the
469 * last line is always incomplete, i.e. has the {@link #NO_DELIM} delimiter. 469 * last line is always incomplete, i.e. has the {@link #NO_DELIM} delimiter.
470 * 470 *
471 * @param line a line number 471 * @param line a line number
472 * @return the line offset with the given line number 472 * @return the line offset with the given line number
473 * @throws BadLocationException if the line is invalid 473 * @throws BadLocationException if the line is invalid
474 */ 474 */
475 private int offsetByLine(final int line) { 475 private int offsetByLine(final int line) {
477 * Works for any binary search tree. 477 * Works for any binary search tree.
478 */ 478 */
479 int remaining= line; 479 int remaining= line;
480 int offset= 0; 480 int offset= 0;
481 Node node= fRoot; 481 Node node= fRoot;
482 482
483 while (true) { 483 while (true) {
484 if (node is null) 484 if (node is null)
485 fail(line); 485 fail(line);
486 486
487 if (remaining is node.line) 487 if (remaining is node.line)
488 return offset + node.offset; 488 return offset + node.offset;
489 489
490 if (remaining < node.line) { 490 if (remaining < node.line) {
491 node= node.left; 491 node= node.left;
494 offset += node.offset + node.length; 494 offset += node.offset + node.length;
495 node= node.right; 495 node= node.right;
496 } 496 }
497 } 497 }
498 } 498 }
499 499
500 /** 500 /**
501 * Left rotation - the given node is rotated down, its right child is rotated up, taking the 501 * Left rotation - the given node is rotated down, its right child is rotated up, taking the
502 * previous structural position of <code>node</code>. 502 * previous structural position of <code>node</code>.
503 * 503 *
504 * @param node the node to rotate around 504 * @param node the node to rotate around
505 */ 505 */
506 private void rotateLeft(Node node) { 506 private void rotateLeft(Node node) {
507 if cast(ASSERT) Assert.isNotNull(node); 507 if (ASSERT) Assert.isNotNull(node);
508 Node child= node.right; 508 Node child= node.right;
509 if cast(ASSERT) Assert.isNotNull(child); 509 if (ASSERT) Assert.isNotNull(child);
510 bool leftChild= node.parent is null || node is node.parent.left; 510 bool leftChild= node.parent is null || node is node.parent.left;
511 511
512 // restructure 512 // restructure
513 setChild(node.parent, child, leftChild); 513 setChild(node.parent, child, leftChild);
514 514
515 setChild(node, child.left, false); 515 setChild(node, child.left, false);
516 setChild(child, node, true); 516 setChild(child, node, true);
517 517
518 // update relative info 518 // update relative info
519 // child becomes the new parent, its line and offset counts increase as the former parent 519 // child becomes the new parent, its line and offset counts increase as the former parent
520 // moves under child's left subtree 520 // moves under child's left subtree
521 child.line += node.line + 1; 521 child.line += node.line + 1;
522 child.offset += node.offset + node.length; 522 child.offset += node.offset + node.length;
523 } 523 }
524 524
525 /** 525 /**
526 * Right rotation - the given node is rotated down, its left child is rotated up, taking the 526 * Right rotation - the given node is rotated down, its left child is rotated up, taking the
527 * previous structural position of <code>node</code>. 527 * previous structural position of <code>node</code>.
528 * 528 *
529 * @param node the node to rotate around 529 * @param node the node to rotate around
530 */ 530 */
531 private void rotateRight(Node node) { 531 private void rotateRight(Node node) {
532 if cast(ASSERT) Assert.isNotNull(node); 532 if (ASSERT) Assert.isNotNull(node);
533 Node child= node.left; 533 Node child= node.left;
534 if cast(ASSERT) Assert.isNotNull(child); 534 if (ASSERT) Assert.isNotNull(child);
535 bool leftChild= node.parent is null || node is node.parent.left; 535 bool leftChild= node.parent is null || node is node.parent.left;
536 536
537 setChild(node.parent, child, leftChild); 537 setChild(node.parent, child, leftChild);
538 538
539 setChild(node, child.right, true); 539 setChild(node, child.right, true);
540 setChild(child, node, false); 540 setChild(child, node, false);
541 541
542 // update relative info 542 // update relative info
543 // node loses its left subtree, except for what it keeps in its new subtree 543 // node loses its left subtree, except for what it keeps in its new subtree
544 // this is exactly the amount in child 544 // this is exactly the amount in child
545 node.line -= child.line + 1; 545 node.line -= child.line + 1;
546 node.offset -= child.offset + child.length; 546 node.offset -= child.offset + child.length;
547 } 547 }
548 548
549 /** 549 /**
550 * Helper method for moving a child, ensuring that parent pointers are set correctly. 550 * Helper method for moving a child, ensuring that parent pointers are set correctly.
551 * 551 *
552 * @param parent the new parent of <code>child</code>, <code>null</code> to replace the 552 * @param parent the new parent of <code>child</code>, <code>null</code> to replace the
553 * root node 553 * root node
554 * @param child the new child of <code>parent</code>, may be <code>null</code> 554 * @param child the new child of <code>parent</code>, may be <code>null</code>
555 * @param isLeftChild <code>true</code> if <code>child</code> shall become 555 * @param isLeftChild <code>true</code> if <code>child</code> shall become
556 * <code>parent</code>'s left child, <code>false</code> if it shall become 556 * <code>parent</code>'s left child, <code>false</code> if it shall become
569 parent.right= child; 569 parent.right= child;
570 } 570 }
571 if (child !is null) 571 if (child !is null)
572 child.parent= parent; 572 child.parent= parent;
573 } 573 }
574 574
575 /** 575 /**
576 * A left rotation around <code>parent</code>, whose structural position is replaced by 576 * A left rotation around <code>parent</code>, whose structural position is replaced by
577 * <code>node</code>. 577 * <code>node</code>.
578 * 578 *
579 * @param node the node moving up and left 579 * @param node the node moving up and left
580 * @param parent the node moving left and down 580 * @param parent the node moving left and down
581 */ 581 */
582 private void singleLeftRotation(Node node, Node parent) { 582 private void singleLeftRotation(Node node, Node parent) {
583 rotateLeft(parent); 583 rotateLeft(parent);
586 } 586 }
587 587
588 /** 588 /**
589 * A right rotation around <code>parent</code>, whose structural position is replaced by 589 * A right rotation around <code>parent</code>, whose structural position is replaced by
590 * <code>node</code>. 590 * <code>node</code>.
591 * 591 *
592 * @param node the node moving up and right 592 * @param node the node moving up and right
593 * @param parent the node moving right and down 593 * @param parent the node moving right and down
594 */ 594 */
595 private void singleRightRotation(Node node, Node parent) { 595 private void singleRightRotation(Node node, Node parent) {
596 rotateRight(parent); 596 rotateRight(parent);
599 } 599 }
600 600
601 /** 601 /**
602 * A double left rotation, first rotating right around <code>node</code>, then left around 602 * A double left rotation, first rotating right around <code>node</code>, then left around
603 * <code>parent</code>. 603 * <code>parent</code>.
604 * 604 *
605 * @param node the node that will be rotated right 605 * @param node the node that will be rotated right
606 * @param parent the node moving left and down 606 * @param parent the node moving left and down
607 */ 607 */
608 private void rightLeftRotation(Node node, Node parent) { 608 private void rightLeftRotation(Node node, Node parent) {
609 Node child= node.left; 609 Node child= node.left;
624 } 624 }
625 625
626 /** 626 /**
627 * A double right rotation, first rotating left around <code>node</code>, then right around 627 * A double right rotation, first rotating left around <code>node</code>, then right around
628 * <code>parent</code>. 628 * <code>parent</code>.
629 * 629 *
630 * @param node the node that will be rotated left 630 * @param node the node that will be rotated left
631 * @param parent the node moving right and down 631 * @param parent the node moving right and down
632 */ 632 */
633 private void leftRightRotation(Node node, Node parent) { 633 private void leftRightRotation(Node node, Node parent) {
634 Node child= node.right; 634 Node child= node.right;
648 } 648 }
649 } 649 }
650 650
651 /** 651 /**
652 * Inserts a line with the given length and delimiter after <code>node</code>. 652 * Inserts a line with the given length and delimiter after <code>node</code>.
653 * 653 *
654 * @param node the predecessor of the inserted node 654 * @param node the predecessor of the inserted node
655 * @param length the line length of the inserted node 655 * @param length the line length of the inserted node
656 * @param delimiter the delimiter of the inserted node 656 * @param delimiter the delimiter of the inserted node
657 * @return the inserted node 657 * @return the inserted node
658 */ 658 */
659 private Node insertAfter(Node node, int length, String delimiter) { 659 private Node insertAfter(Node node, int length, String delimiter) {
660 /* 660 /*
661 * An insertion really shifts the key of all succeeding nodes. Hence we insert the added node 661 * An insertion really shifts the key of all succeeding nodes. Hence we insert the added node
662 * between node and the successor of node. The added node becomes either the right child 662 * between node and the successor of node. The added node becomes either the right child
663 * of the predecessor node, or the left child of the successor node. 663 * of the predecessor node, or the left child of the successor node.
664 */ 664 */
665 Node added= new Node(length, delimiter); 665 Node added= new Node(length, delimiter);
666 666
667 if (node.right is null) 667 if (node.right is null)
668 setChild(node, added, false); 668 setChild(node, added, false);
669 else 669 else
670 setChild(successorDown(node.right), added, true); 670 setChild(successorDown(node.right), added, true);
671 671
672 // parent chain update 672 // parent chain update
673 updateParentChain(added, length, 1); 673 updateParentChain(added, length, 1);
674 updateParentBalanceAfterInsertion(added); 674 updateParentBalanceAfterInsertion(added);
675 675
676 return added; 676 return added;
677 } 677 }
678 678
679 /** 679 /**
680 * Updates the balance information in the parent chain of node until it reaches the root or 680 * Updates the balance information in the parent chain of node until it reaches the root or
681 * finds a node whose balance violates the AVL constraint, which is the re-balanced. 681 * finds a node whose balance violates the AVL constraint, which is the re-balanced.
682 * 682 *
683 * @param node the child of the first node that needs balance updating 683 * @param node the child of the first node that needs balance updating
684 */ 684 */
685 private void updateParentBalanceAfterInsertion(Node node) { 685 private void updateParentBalanceAfterInsertion(Node node) {
686 Node parent= node.parent; 686 Node parent= node.parent;
687 while (parent !is null) { 687 while (parent !is null) {
703 rebalanceAfterInsertionRight(node); 703 rebalanceAfterInsertionRight(node);
704 break; 704 break;
705 case 0: 705 case 0:
706 break; 706 break;
707 default: 707 default:
708 if cast(ASSERT) 708 if (ASSERT)
709 Assert.isTrue(false); 709 Assert.isTrue(false);
710 } 710 }
711 return; 711 return;
712 } 712 }
713 } 713 }
714 714
715 /** 715 /**
716 * Re-balances a node whose parent has a double positive balance. 716 * Re-balances a node whose parent has a double positive balance.
717 * 717 *
718 * @param node the node to re-balance 718 * @param node the node to re-balance
719 */ 719 */
720 private void rebalanceAfterInsertionRight(Node node) { 720 private void rebalanceAfterInsertionRight(Node node) {
721 Node parent= node.parent; 721 Node parent= node.parent;
722 if (node.balance is 1) { 722 if (node.balance is 1) {
723 singleLeftRotation(node, parent); 723 singleLeftRotation(node, parent);
724 } else if (node.balance is -1) { 724 } else if (node.balance is -1) {
725 rightLeftRotation(node, parent); 725 rightLeftRotation(node, parent);
726 } else if cast(ASSERT) { 726 } else if (ASSERT) {
727 Assert.isTrue(false); 727 Assert.isTrue(false);
728 } 728 }
729 } 729 }
730 730
731 /** 731 /**
732 * Re-balances a node whose parent has a double negative balance. 732 * Re-balances a node whose parent has a double negative balance.
733 * 733 *
734 * @param node the node to re-balance 734 * @param node the node to re-balance
735 */ 735 */
736 private void rebalanceAfterInsertionLeft(Node node) { 736 private void rebalanceAfterInsertionLeft(Node node) {
737 Node parent= node.parent; 737 Node parent= node.parent;
738 if (node.balance is -1) { 738 if (node.balance is -1) {
739 singleRightRotation(node, parent); 739 singleRightRotation(node, parent);
740 } else if (node.balance is 1) { 740 } else if (node.balance is 1) {
741 leftRightRotation(node, parent); 741 leftRightRotation(node, parent);
742 } else if cast(ASSERT) { 742 } else if (ASSERT) {
743 Assert.isTrue(false); 743 Assert.isTrue(false);
744 } 744 }
745 } 745 }
746 746
747 /* 747 /*
748 * @see dwtx.jface.text.ILineTracker#replace(int, int, java.lang.String) 748 * @see dwtx.jface.text.ILineTracker#replace(int, int, java.lang.String)
749 */ 749 */
750 public final void replace(int offset, int length, String text) { 750 public final void replace(int offset, int length, String text) {
751 if cast(ASSERT) checkTree(); 751 if (ASSERT) checkTree();
752 752
753 // Inlined nodeByOffset as we need both node and offset 753 // Inlined nodeByOffset as we need both node and offset
754 int remaining= offset; 754 int remaining= offset;
755 Node first= fRoot; 755 Node first= fRoot;
756 final int firstNodeOffset; 756 final int firstNodeOffset;
757 757
758 while (true) { 758 while (true) {
759 if (first is null) 759 if (first is null)
760 fail(offset); 760 fail(offset);
761 761
762 if (remaining < first.offset) { 762 if (remaining < first.offset) {
763 first= first.left; 763 first= first.left;
764 } else { 764 } else {
765 remaining -= first.offset; 765 remaining -= first.offset;
766 if (remaining < first.length 766 if (remaining < first.length
771 remaining -= first.length; 771 remaining -= first.length;
772 first= first.right; 772 first= first.right;
773 } 773 }
774 } 774 }
775 // Inline nodeByOffset end 775 // Inline nodeByOffset end
776 if cast(ASSERT) Assert.isTrue(first !is null); 776 if (ASSERT) Assert.isTrue(first !is null);
777 777
778 Node last; 778 Node last;
779 if (offset + length < firstNodeOffset + first.length) 779 if (offset + length < firstNodeOffset + first.length)
780 last= first; 780 last= first;
781 else 781 else
782 last= nodeByOffset(offset + length); 782 last= nodeByOffset(offset + length);
783 if cast(ASSERT) Assert.isTrue(last !is null); 783 if (ASSERT) Assert.isTrue(last !is null);
784 784
785 int firstLineDelta= firstNodeOffset + first.length - offset; 785 int firstLineDelta= firstNodeOffset + first.length - offset;
786 if (first is last) 786 if (first is last)
787 replaceInternal(first, text, length, firstLineDelta); 787 replaceInternal(first, text, length, firstLineDelta);
788 else 788 else
789 replaceFromTo(first, last, text, length, firstLineDelta); 789 replaceFromTo(first, last, text, length, firstLineDelta);
790 790
791 if cast(ASSERT) checkTree(); 791 if (ASSERT) checkTree();
792 } 792 }
793 793
794 /** 794 /**
795 * Replace happening inside a single line. 795 * Replace happening inside a single line.
796 * 796 *
797 * @param node the affected node 797 * @param node the affected node
798 * @param text the added text 798 * @param text the added text
799 * @param length the replace length, &lt; <code>firstLineDelta</code> 799 * @param length the replace length, &lt; <code>firstLineDelta</code>
800 * @param firstLineDelta the number of characters from the replacement offset to the end of 800 * @param firstLineDelta the number of characters from the replacement offset to the end of
801 * <code>node</code> &gt; <code>length</code> 801 * <code>node</code> &gt; <code>length</code>
802 */ 802 */
803 private void replaceInternal(Node node, String text, int length, int firstLineDelta) { 803 private void replaceInternal(Node node, String text, int length, int firstLineDelta) {
804 // 1) modification on a single line 804 // 1) modification on a single line
805 805
806 DelimiterInfo info= text is null ? null : nextDelimiterInfo(text, 0); 806 DelimiterInfo info= text is null ? null : nextDelimiterInfo(text, 0);
807 807
808 if (info is null || info.delimiter is null) { 808 if (info is null || info.delimiter is null) {
809 // a) trivial case: insert into a single node, no line mangling 809 // a) trivial case: insert into a single node, no line mangling
810 int added= text is null ? 0 : text.length(); 810 int added= text is null ? 0 : text.length();
812 } else { 812 } else {
813 // b) more lines to add between two chunks of the first node 813 // b) more lines to add between two chunks of the first node
814 // remember what we split off the first line 814 // remember what we split off the first line
815 int remainder= firstLineDelta - length; 815 int remainder= firstLineDelta - length;
816 String remDelim= node.delimiter; 816 String remDelim= node.delimiter;
817 817
818 // join the first line with the first added 818 // join the first line with the first added
819 int consumed= info.delimiterIndex + info.delimiterLength; 819 int consumed= info.delimiterIndex + info.delimiterLength;
820 int delta= consumed - firstLineDelta; 820 int delta= consumed - firstLineDelta;
821 updateLength(node, delta); 821 updateLength(node, delta);
822 node.delimiter= info.delimiter; 822 node.delimiter= info.delimiter;
823 823
824 // Inline addlines start 824 // Inline addlines start
825 info= nextDelimiterInfo(text, consumed); 825 info= nextDelimiterInfo(text, consumed);
826 while (info !is null) { 826 while (info !is null) {
827 int lineLen= info.delimiterIndex - consumed + info.delimiterLength; 827 int lineLen= info.delimiterIndex - consumed + info.delimiterLength;
828 node= insertAfter(node, lineLen, info.delimiter); 828 node= insertAfter(node, lineLen, info.delimiter);
829 consumed += lineLen; 829 consumed += lineLen;
830 info= nextDelimiterInfo(text, consumed); 830 info= nextDelimiterInfo(text, consumed);
831 } 831 }
832 // Inline addlines end 832 // Inline addlines end
833 833
834 // add remaining chunk merged with last (incomplete) additional line 834 // add remaining chunk merged with last (incomplete) additional line
835 insertAfter(node, remainder + text.length() - consumed, remDelim); 835 insertAfter(node, remainder + text.length() - consumed, remDelim);
836 } 836 }
837 } 837 }
838 838
839 /** 839 /**
840 * Replace spanning from one node to another. 840 * Replace spanning from one node to another.
841 * 841 *
842 * @param node the first affected node 842 * @param node the first affected node
843 * @param last the last affected node 843 * @param last the last affected node
844 * @param text the added text 844 * @param text the added text
845 * @param length the replace length, &gt;= <code>firstLineDelta</code> 845 * @param length the replace length, &gt;= <code>firstLineDelta</code>
846 * @param firstLineDelta the number of characters removed from the replacement offset to the end 846 * @param firstLineDelta the number of characters removed from the replacement offset to the end
847 * of <code>node</code>, &lt;= <code>length</code> 847 * of <code>node</code>, &lt;= <code>length</code>
848 */ 848 */
849 private void replaceFromTo(Node node, Node last, String text, int length, int firstLineDelta) { 849 private void replaceFromTo(Node node, Node last, String text, int length, int firstLineDelta) {
850 // 2) modification covers several lines 850 // 2) modification covers several lines
851 851
852 // delete intermediate nodes 852 // delete intermediate nodes
853 // TODO could be further optimized: replace intermediate lines with intermediate added lines 853 // TODO could be further optimized: replace intermediate lines with intermediate added lines
854 // to reduce re-balancing 854 // to reduce re-balancing
855 Node successor= successor(node); 855 Node successor= successor(node);
856 while (successor !is last) { 856 while (successor !is last) {
865 if (info is null || info.delimiter is null) { 865 if (info is null || info.delimiter is null) {
866 int added= text is null ? 0 : text.length(); 866 int added= text is null ? 0 : text.length();
867 867
868 // join the two lines if there are no lines added 868 // join the two lines if there are no lines added
869 join(node, last, added - length); 869 join(node, last, added - length);
870 870
871 } else { 871 } else {
872 872
873 // join the first line with the first added 873 // join the first line with the first added
874 int consumed= info.delimiterIndex + info.delimiterLength; 874 int consumed= info.delimiterIndex + info.delimiterLength;
875 updateLength(node, consumed - firstLineDelta); 875 updateLength(node, consumed - firstLineDelta);
876 node.delimiter= info.delimiter; 876 node.delimiter= info.delimiter;
877 length -= firstLineDelta; 877 length -= firstLineDelta;
883 node= insertAfter(node, lineLen, info.delimiter); 883 node= insertAfter(node, lineLen, info.delimiter);
884 consumed += lineLen; 884 consumed += lineLen;
885 info= nextDelimiterInfo(text, consumed); 885 info= nextDelimiterInfo(text, consumed);
886 } 886 }
887 // Inline addLines end 887 // Inline addLines end
888 888
889 updateLength(last, text.length() - consumed - length); 889 updateLength(last, text.length() - consumed - length);
890 } 890 }
891 } 891 }
892 892
893 /** 893 /**
894 * Joins two consecutive node lines, additionally adjusting the resulting length of the combined 894 * Joins two consecutive node lines, additionally adjusting the resulting length of the combined
895 * line by <code>delta</code>. The first node gets deleted. 895 * line by <code>delta</code>. The first node gets deleted.
896 * 896 *
897 * @param one the first node to join 897 * @param one the first node to join
898 * @param two the second node to join 898 * @param two the second node to join
899 * @param delta the delta to apply to the remaining single node 899 * @param delta the delta to apply to the remaining single node
900 */ 900 */
901 private void join(Node one, Node two, int delta) { 901 private void join(Node one, Node two, int delta) {
902 int oneLength= one.length; 902 int oneLength= one.length;
903 updateLength(one, -oneLength); 903 updateLength(one, -oneLength);
904 updateLength(two, oneLength + delta); 904 updateLength(two, oneLength + delta);
905 } 905 }
906 906
907 /** 907 /**
908 * Adjusts the length of a node by <code>delta</code>, also adjusting the parent chain of 908 * Adjusts the length of a node by <code>delta</code>, also adjusting the parent chain of
909 * <code>node</code>. If the node's length becomes zero and is not the last (incomplete) 909 * <code>node</code>. If the node's length becomes zero and is not the last (incomplete)
910 * node, it is deleted after the update. 910 * node, it is deleted after the update.
911 * 911 *
912 * @param node the node to adjust 912 * @param node the node to adjust
913 * @param delta the character delta to add to the node's length 913 * @param delta the character delta to add to the node's length
914 */ 914 */
915 private void updateLength(Node node, int delta) { 915 private void updateLength(Node node, int delta) {
916 if cast(ASSERT) Assert.isTrue(node.length + delta >= 0); 916 if (ASSERT) Assert.isTrue(node.length + delta >= 0);
917 917
918 // update the node itself 918 // update the node itself
919 node.length += delta; 919 node.length += delta;
920 920
921 // check deletion 921 // check deletion
922 final int lineDelta; 922 final int lineDelta;
923 bool delete= node.length is 0 && node.delimiter !is NO_DELIM; 923 bool delete__= node.length is 0 && node.delimiter !is NO_DELIM;
924 if (delete) 924 if (delete__)
925 lineDelta= -1; 925 lineDelta= -1;
926 else 926 else
927 lineDelta= 0; 927 lineDelta= 0;
928 928
929 // update parent chain 929 // update parent chain
930 if (delta !is 0 || lineDelta !is 0) 930 if (delta !is 0 || lineDelta !is 0)
931 updateParentChain(node, delta, lineDelta); 931 updateParentChain(node, delta, lineDelta);
932 932
933 if (delete) 933 if (delete__)
934 delete(node); 934 delete_(node);
935 } 935 }
936 936
937 /** 937 /**
938 * Updates the differential indices following the parent chain. All nodes from 938 * Updates the differential indices following the parent chain. All nodes from
939 * <code>from.parent</code> to the root are updated. 939 * <code>from.parent</code> to the root are updated.
940 * 940 *
941 * @param node the child of the first node to update 941 * @param node the child of the first node to update
942 * @param deltaLength the character delta 942 * @param deltaLength the character delta
943 * @param deltaLines the line delta 943 * @param deltaLines the line delta
944 */ 944 */
945 private void updateParentChain(Node node, int deltaLength, int deltaLines) { 945 private void updateParentChain(Node node, int deltaLength, int deltaLines) {
946 updateParentChain(node, null, deltaLength, deltaLines); 946 updateParentChain(node, null, deltaLength, deltaLines);
947 } 947 }
948 948
949 /** 949 /**
950 * Updates the differential indices following the parent chain. All nodes from 950 * Updates the differential indices following the parent chain. All nodes from
951 * <code>from.parent</code> to <code>to</code> (exclusive) are updated. 951 * <code>from.parent</code> to <code>to</code> (exclusive) are updated.
952 * 952 *
953 * @param from the child of the first node to update 953 * @param from the child of the first node to update
954 * @param to the first node not to update 954 * @param to the first node not to update
955 * @param deltaLength the character delta 955 * @param deltaLength the character delta
956 * @param deltaLines the line delta 956 * @param deltaLines the line delta
957 */ 957 */
958 private void updateParentChain(Node from, Node to, int deltaLength, int deltaLines) { 958 private void updateParentChain(Node from, Node to, int deltaLength, int deltaLines) {
959 Node parent= from.parent; 959 Node parent= from.parent;
960 while (parent !is to) { 960 while (parent !is to) {
965 } 965 }
966 from= parent; 966 from= parent;
967 parent= from.parent; 967 parent= from.parent;
968 } 968 }
969 } 969 }
970 970
971 /** 971 /**
972 * Deletes a node from the tree, re-balancing it if necessary. The differential indices in the 972 * Deletes a node from the tree, re-balancing it if necessary. The differential indices in the
973 * node's parent chain have to be updated in advance to calling this method. Generally, don't 973 * node's parent chain have to be updated in advance to calling this method. Generally, don't
974 * call <code>delete</code> directly, but call 974 * call <code>delete</code> directly, but call
975 * {@link #updateLength(Node, int) update_length(node, -node.length)} to properly remove a 975 * {@link #updateLength(Node, int) update_length(node, -node.length)} to properly remove a
976 * node. 976 * node.
977 * 977 *
978 * @param node the node to delete. 978 * @param node the node to delete.
979 */ 979 */
980 private void delete(Node node) { 980 private void delete_(Node node) {
981 if cast(ASSERT) Assert.isTrue(node !is null); 981 if (ASSERT) Assert.isTrue(node !is null);
982 if cast(ASSERT) Assert.isTrue(node.length is 0); 982 if (ASSERT) Assert.isTrue(node.length is 0);
983 983
984 Node parent= node.parent; 984 Node parent= node.parent;
985 Node toUpdate; // the parent of the node that lost a child 985 Node toUpdate; // the parent of the node that lost a child
986 bool lostLeftChild; 986 bool lostLeftChild;
987 bool isLeftChild= parent is null || node is parent.left; 987 bool isLeftChild= parent is null || node is parent.left;
988 988
989 if (node.left is null || node.right is null) { 989 if (node.left is null || node.right is null) {
990 // 1) node has one child at max - replace parent's pointer with the only child 990 // 1) node has one child at max - replace parent's pointer with the only child
991 // also handles the trivial case of no children 991 // also handles the trivial case of no children
992 Node replacement= node.left is null ? node.right : node.left; 992 Node replacement= node.left is null ? node.right : node.left;
993 setChild(parent, replacement, isLeftChild); 993 setChild(parent, replacement, isLeftChild);
1014 // toUpdate= replacement; 1014 // toUpdate= replacement;
1015 // lostLeftChild= true; 1015 // lostLeftChild= true;
1016 } else { 1016 } else {
1017 // 3) hard case - replace node with its successor 1017 // 3) hard case - replace node with its successor
1018 Node successor= successor(node); 1018 Node successor= successor(node);
1019 1019
1020 // successor exists (otherwise node would not have right child, case 1) 1020 // successor exists (otherwise node would not have right child, case 1)
1021 if cast(ASSERT) Assert.isNotNull(successor); 1021 if (ASSERT) Assert.isNotNull(successor);
1022 // successor has no left child (a left child would be the real successor of node) 1022 // successor has no left child (a left child would be the real successor of node)
1023 if cast(ASSERT) Assert.isTrue(successor.left is null); 1023 if (ASSERT) Assert.isTrue(successor.left is null);
1024 if cast(ASSERT) Assert.isTrue(successor.line is 0); 1024 if (ASSERT) Assert.isTrue(successor.line is 0);
1025 // successor is the left child of its parent (otherwise parent would be smaller and 1025 // successor is the left child of its parent (otherwise parent would be smaller and
1026 // hence the real successor) 1026 // hence the real successor)
1027 if cast(ASSERT) Assert.isTrue(successor is successor.parent.left); 1027 if (ASSERT) Assert.isTrue(successor is successor.parent.left);
1028 // successor is not a child of node (would have been covered by 2a) 1028 // successor is not a child of node (would have been covered by 2a)
1029 if cast(ASSERT) Assert.isTrue(successor.parent !is node); 1029 if (ASSERT) Assert.isTrue(successor.parent !is node);
1030 1030
1031 toUpdate= successor.parent; 1031 toUpdate= successor.parent;
1032 lostLeftChild= true; 1032 lostLeftChild= true;
1033 1033
1034 // update relative indices 1034 // update relative indices
1035 updateParentChain(successor, node, -successor.length, -1); 1035 updateParentChain(successor, node, -successor.length, -1);
1036 1036
1037 // delete successor from its current place - like 1) 1037 // delete successor from its current place - like 1)
1038 setChild(toUpdate, successor.right, true); 1038 setChild(toUpdate, successor.right, true);
1039 1039
1040 // move node's subtrees to its successor 1040 // move node's subtrees to its successor
1041 setChild(successor, node.right, false); 1041 setChild(successor, node.right, false);
1042 setChild(successor, node.left, true); 1042 setChild(successor, node.left, true);
1043 1043
1044 // replace node by successor in its parent 1044 // replace node by successor in its parent
1045 setChild(parent, successor, isLeftChild); 1045 setChild(parent, successor, isLeftChild);
1046 1046
1047 // update the successor 1047 // update the successor
1048 successor.line= node.line; 1048 successor.line= node.line;
1049 successor.offset= node.offset; 1049 successor.offset= node.offset;
1050 successor.balance= node.balance; 1050 successor.balance= node.balance;
1051 } 1051 }
1052 1052
1053 updateParentBalanceAfterDeletion(toUpdate, lostLeftChild); 1053 updateParentBalanceAfterDeletion(toUpdate, lostLeftChild);
1054 } 1054 }
1055 1055
1056 /** 1056 /**
1057 * Updates the balance information in the parent chain of node. 1057 * Updates the balance information in the parent chain of node.
1058 * 1058 *
1059 * @param node the first node that needs balance updating 1059 * @param node the first node that needs balance updating
1060 * @param wasLeftChild <code>true</code> if the deletion happened on <code>node</code>'s 1060 * @param wasLeftChild <code>true</code> if the deletion happened on <code>node</code>'s
1061 * left subtree, <code>false</code> if it occurred on <code>node</code>'s right 1061 * left subtree, <code>false</code> if it occurred on <code>node</code>'s right
1062 * subtree 1062 * subtree
1063 */ 1063 */
1065 while (node !is null) { 1065 while (node !is null) {
1066 if (wasLeftChild) 1066 if (wasLeftChild)
1067 node.balance++; 1067 node.balance++;
1068 else 1068 else
1069 node.balance--; 1069 node.balance--;
1070 1070
1071 Node parent= node.parent; 1071 Node parent= node.parent;
1072 if (parent !is null) 1072 if (parent !is null)
1073 wasLeftChild= node is parent.left; 1073 wasLeftChild= node is parent.left;
1074 1074
1075 switch (node.balance) { 1075 switch (node.balance) {
1085 return; 1085 return;
1086 break; // propagate up 1086 break; // propagate up
1087 case 0: 1087 case 0:
1088 break; // propagate up 1088 break; // propagate up
1089 default: 1089 default:
1090 if cast(ASSERT) 1090 if (ASSERT)
1091 Assert.isTrue(false); 1091 Assert.isTrue(false);
1092 } 1092 }
1093 1093
1094 node= parent; 1094 node= parent;
1095 } 1095 }
1096 } 1096 }
1097 1097
1098 /** 1098 /**
1099 * Re-balances a node whose parent has a double positive balance. 1099 * Re-balances a node whose parent has a double positive balance.
1100 * 1100 *
1101 * @param node the node to re-balance 1101 * @param node the node to re-balance
1102 * @return <code>true</code> if the re-balancement leaves the height at 1102 * @return <code>true</code> if the re-balancement leaves the height at
1103 * <code>node.parent</code> constant, <code>false</code> if the height changed 1103 * <code>node.parent</code> constant, <code>false</code> if the height changed
1104 */ 1104 */
1105 private bool rebalanceAfterDeletionLeft(Node node) { 1105 private bool rebalanceAfterDeletionLeft(Node node) {
1114 rotateLeft(parent); 1114 rotateLeft(parent);
1115 node.balance= -1; 1115 node.balance= -1;
1116 parent.balance= 1; 1116 parent.balance= 1;
1117 return true; 1117 return true;
1118 } else { 1118 } else {
1119 if cast(ASSERT) Assert.isTrue(false); 1119 if (ASSERT) Assert.isTrue(false);
1120 return true; 1120 return true;
1121 } 1121 }
1122 } 1122 }
1123 1123
1124 /** 1124 /**
1125 * Re-balances a node whose parent has a double negative balance. 1125 * Re-balances a node whose parent has a double negative balance.
1126 * 1126 *
1127 * @param node the node to re-balance 1127 * @param node the node to re-balance
1128 * @return <code>true</code> if the re-balancement leaves the height at 1128 * @return <code>true</code> if the re-balancement leaves the height at
1129 * <code>node.parent</code> constant, <code>false</code> if the height changed 1129 * <code>node.parent</code> constant, <code>false</code> if the height changed
1130 */ 1130 */
1131 private bool rebalanceAfterDeletionRight(Node node) { 1131 private bool rebalanceAfterDeletionRight(Node node) {
1140 rotateRight(parent); 1140 rotateRight(parent);
1141 node.balance= 1; 1141 node.balance= 1;
1142 parent.balance= -1; 1142 parent.balance= -1;
1143 return true; 1143 return true;
1144 } else { 1144 } else {
1145 if cast(ASSERT) Assert.isTrue(false); 1145 if (ASSERT) Assert.isTrue(false);
1146 return true; 1146 return true;
1147 } 1147 }
1148 } 1148 }
1149 1149
1150 /** 1150 /**
1151 * Returns the successor of a node, <code>null</code> if node is the last node. 1151 * Returns the successor of a node, <code>null</code> if node is the last node.
1152 * 1152 *
1153 * @param node a node 1153 * @param node a node
1154 * @return the successor of <code>node</code>, <code>null</code> if there is none 1154 * @return the successor of <code>node</code>, <code>null</code> if there is none
1155 */ 1155 */
1156 private Node successor(Node node) { 1156 private Node successor(Node node) {
1157 if (node.right !is null) 1157 if (node.right !is null)
1158 return successorDown(node.right); 1158 return successorDown(node.right);
1159 1159
1160 return successorUp(node); 1160 return successorUp(node);
1161 } 1161 }
1162 1162
1163 /** 1163 /**
1164 * Searches the successor of <code>node</code> in its parent chain. 1164 * Searches the successor of <code>node</code> in its parent chain.
1165 * 1165 *
1166 * @param node a node 1166 * @param node a node
1167 * @return the first node in <code>node</code>'s parent chain that is reached from its left 1167 * @return the first node in <code>node</code>'s parent chain that is reached from its left
1168 * subtree, <code>null</code> if there is none 1168 * subtree, <code>null</code> if there is none
1169 */ 1169 */
1170 private Node successorUp(final Node node) { 1170 private Node successorUp(final Node node) {
1174 if (child is parent.left) 1174 if (child is parent.left)
1175 return parent; 1175 return parent;
1176 child= parent; 1176 child= parent;
1177 parent= child.parent; 1177 parent= child.parent;
1178 } 1178 }
1179 if cast(ASSERT) Assert.isTrue(node.delimiter is NO_DELIM); 1179 if (ASSERT) Assert.isTrue(node.delimiter is NO_DELIM);
1180 return null; 1180 return null;
1181 } 1181 }
1182 1182
1183 /** 1183 /**
1184 * Searches the left-most node in a given subtree. 1184 * Searches the left-most node in a given subtree.
1185 * 1185 *
1186 * @param node a node 1186 * @param node a node
1187 * @return the left-most node in the given subtree 1187 * @return the left-most node in the given subtree
1188 */ 1188 */
1189 private Node successorDown(Node node) { 1189 private Node successorDown(Node node) {
1190 Node child= node.left; 1190 Node child= node.left;
1197 1197
1198 /* miscellaneous */ 1198 /* miscellaneous */
1199 1199
1200 /** 1200 /**
1201 * Throws an exception. 1201 * Throws an exception.
1202 * 1202 *
1203 * @param offset the illegal character or line offset that caused the exception 1203 * @param offset the illegal character or line offset that caused the exception
1204 * @throws BadLocationException always 1204 * @throws BadLocationException always
1205 */ 1205 */
1206 private void fail(int offset) { 1206 private void fail(int offset) {
1207 throw new BadLocationException(); 1207 throw new BadLocationException();
1208 } 1208 }
1209 1209
1210 /** 1210 /**
1211 * Returns the information about the first delimiter found in the given 1211 * Returns the information about the first delimiter found in the given
1212 * text starting at the given offset. 1212 * text starting at the given offset.
1213 * 1213 *
1214 * @param text the text to be searched 1214 * @param text the text to be searched
1215 * @param offset the offset in the given text 1215 * @param offset the offset in the given text
1216 * @return the information of the first found delimiter or <code>null</code> 1216 * @return the information of the first found delimiter or <code>null</code>
1217 */ 1217 */
1218 protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset); 1218 protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset);
1219 1219
1220 /* 1220 /*
1221 * @see dwtx.jface.text.ILineTracker#getLineDelimiter(int) 1221 * @see dwtx.jface.text.ILineTracker#getLineDelimiter(int)
1222 */ 1222 */
1223 public final String getLineDelimiter(int line) { 1223 public final String getLineDelimiter(int line) {
1224 Node node= nodeByLine(line); 1224 Node node= nodeByLine(line);
1261 if (length is 0) 1261 if (length is 0)
1262 return 1; 1262 return 1;
1263 1263
1264 int startLine= lineByOffset(offset); 1264 int startLine= lineByOffset(offset);
1265 int endLine= lineByOffset(offset + length); 1265 int endLine= lineByOffset(offset + length);
1266 1266
1267 return endLine - startLine + 1; 1267 return endLine - startLine + 1;
1268 } 1268 }
1269 1269
1270 /* 1270 /*
1271 * @see dwtx.jface.text.ILineTracker#getLineOffset(int) 1271 * @see dwtx.jface.text.ILineTracker#getLineOffset(int)
1295 public final IRegion getLineInformationOfOffset(final int offset) { 1295 public final IRegion getLineInformationOfOffset(final int offset) {
1296 // Inline nodeByOffset start as we need both node and offset 1296 // Inline nodeByOffset start as we need both node and offset
1297 int remaining= offset; 1297 int remaining= offset;
1298 Node node= fRoot; 1298 Node node= fRoot;
1299 final int lineOffset; 1299 final int lineOffset;
1300 1300
1301 while (true) { 1301 while (true) {
1302 if (node is null) 1302 if (node is null)
1303 fail(offset); 1303 fail(offset);
1304 1304
1305 if (remaining < node.offset) { 1305 if (remaining < node.offset) {
1306 node= node.left; 1306 node= node.left;
1307 } else { 1307 } else {
1308 remaining -= node.offset; 1308 remaining -= node.offset;
1309 if (remaining < node.length 1309 if (remaining < node.length
1326 try { 1326 try {
1327 // Inline nodeByLine start 1327 // Inline nodeByLine start
1328 int remaining= line; 1328 int remaining= line;
1329 int offset= 0; 1329 int offset= 0;
1330 Node node= fRoot; 1330 Node node= fRoot;
1331 1331
1332 while (true) { 1332 while (true) {
1333 if (node is null) 1333 if (node is null)
1334 fail(line); 1334 fail(line);
1335 1335
1336 if (remaining is node.line) { 1336 if (remaining is node.line) {
1337 offset += node.offset; 1337 offset += node.offset;
1338 break; 1338 break;
1339 } 1339 }
1340 if (remaining < node.line) { 1340 if (remaining < node.line) {
1357 line= line - 1; 1357 line= line - 1;
1358 // Inline nodeByLine start 1358 // Inline nodeByLine start
1359 int remaining= line; 1359 int remaining= line;
1360 int offset= 0; 1360 int offset= 0;
1361 Node node= fRoot; 1361 Node node= fRoot;
1362 1362
1363 while (true) { 1363 while (true) {
1364 if (node is null) 1364 if (node is null)
1365 fail(line); 1365 fail(line);
1366 1366
1367 if (remaining is node.line) { 1367 if (remaining is node.line) {
1368 offset+= node.offset; 1368 offset+= node.offset;
1369 break; 1369 break;
1370 } 1370 }
1371 if (remaining < node.line) { 1371 if (remaining < node.line) {
1394 replace(0, 0, text); 1394 replace(0, 0, text);
1395 } catch (BadLocationException x) { 1395 } catch (BadLocationException x) {
1396 throw new InternalError(); 1396 throw new InternalError();
1397 } 1397 }
1398 } 1398 }
1399 1399
1400 /* 1400 /*
1401 * @see java.lang.Object#toString() 1401 * @see java.lang.Object#toString()
1402 */ 1402 */
1403 public String toString() { 1403 public String toString() {
1404 int depth= computeDepth(fRoot); 1404 int depth= computeDepth(fRoot);
1405 int WIDTH= 30; 1405 int WIDTH= 30;
1406 int leaves= cast(int) Math.pow(2, depth - 1); 1406 int leaves= cast(int) Math.pow(2, depth - 1);
1407 int width= WIDTH * leaves; 1407 int width= WIDTH * leaves;
1408 String empty= "."; //$NON-NLS-1$ 1408 String empty= "."; //$NON-NLS-1$
1409 1409
1410 List roots= new LinkedList(); 1410 List roots= new LinkedList();
1411 roots.add(fRoot); 1411 roots.add(fRoot);
1412 StringBuffer buf= new StringBuffer((width + 1) * depth); 1412 StringBuffer buf= new StringBuffer((width + 1) * depth);
1413 int nodes= 1; 1413 int nodes= 1;
1414 int indents= leaves; 1414 int indents= leaves;
1420 int spaces= Math.max(0, indents * WIDTH - WIDTH / 2); 1420 int spaces= Math.max(0, indents * WIDTH - WIDTH / 2);
1421 // print nodes 1421 // print nodes
1422 for (ListIterator it= roots.listIterator(); it.hasNext();) { 1422 for (ListIterator it= roots.listIterator(); it.hasNext();) {
1423 // pad before 1423 // pad before
1424 buf.append(space, 0, spaces); 1424 buf.append(space, 0, spaces);
1425 1425
1426 Node node= cast(Node) it.next(); 1426 Node node= cast(Node) it.next();
1427 String box; 1427 String box;
1428 // replace the node with its children 1428 // replace the node with its children
1429 if (node is null) { 1429 if (node is null) {
1430 it.add(null); 1430 it.add(null);
1432 } else { 1432 } else {
1433 it.set(node.left); 1433 it.set(node.left);
1434 it.add(node.right); 1434 it.add(node.right);
1435 box= node.toString(); 1435 box= node.toString();
1436 } 1436 }
1437 1437
1438 // draw the node, pad to WIDTH 1438 // draw the node, pad to WIDTH
1439 int pad_left= (WIDTH - box.length() + 1) / 2; 1439 int pad_left= (WIDTH - box.length() + 1) / 2;
1440 int pad_right= WIDTH - box.length() - pad_left; 1440 int pad_right= WIDTH - box.length() - pad_left;
1441 buf.append(space, 0, pad_left); 1441 buf.append(space, 0, pad_left);
1442 buf.append(box); 1442 buf.append(box);
1443 buf.append(space, 0, pad_right); 1443 buf.append(space, 0, pad_right);
1444 1444
1445 // pad after 1445 // pad after
1446 buf.append(space, 0, spaces); 1446 buf.append(space, 0, spaces);
1447 } 1447 }
1448 1448
1449 buf.append('\n'); 1449 buf.append('\n');
1450 nodes *= 2; 1450 nodes *= 2;
1451 } 1451 }
1452 1452
1453 return buf.toString(); 1453 return buf.toString();
1454 } 1454 }
1455 1455
1456 /** 1456 /**
1457 * Recursively computes the depth of the tree. Only used by {@link #toString()}. 1457 * Recursively computes the depth of the tree. Only used by {@link #toString()}.
1458 * 1458 *
1459 * @param root the subtree to compute the depth of, may be <code>null</code> 1459 * @param root the subtree to compute the depth of, may be <code>null</code>
1460 * @return the depth of the given tree, 0 if it is <code>null</code> 1460 * @return the depth of the given tree, 0 if it is <code>null</code>
1461 */ 1461 */
1462 private byte computeDepth(Node root) { 1462 private byte computeDepth(Node root) {
1463 if (root is null) 1463 if (root is null)
1464 return 0; 1464 return 0;
1465 1465
1466 return cast(byte) (Math.max(computeDepth(root.left), computeDepth(root.right)) + 1); 1466 return cast(byte) (Math.max(computeDepth(root.left), computeDepth(root.right)) + 1);
1467 } 1467 }
1468 1468
1469 /** 1469 /**
1470 * Debug-only method that checks the tree structure and the differential offsets. 1470 * Debug-only method that checks the tree structure and the differential offsets.
1471 */ 1471 */
1472 private void checkTree() { 1472 private void checkTree() {
1473 checkTreeStructure(fRoot); 1473 checkTreeStructure(fRoot);
1474 1474
1475 try { 1475 try {
1476 checkTreeOffsets(nodeByOffset(0), new int[] {0, 0}, null); 1476 checkTreeOffsets(nodeByOffset(0), [0, 0], null);
1477 } catch (BadLocationException x) { 1477 } catch (BadLocationException x) {
1478 throw new AssertionError(); 1478 throw new AssertionError();
1479 } 1479 }
1480 } 1480 }
1481 1481
1482 /** 1482 /**
1483 * Debug-only method that validates the tree structure below <code>node</code>. I.e. it 1483 * Debug-only method that validates the tree structure below <code>node</code>. I.e. it
1484 * checks whether all parent/child pointers are consistent and whether the AVL balance 1484 * checks whether all parent/child pointers are consistent and whether the AVL balance
1485 * information is correct. 1485 * information is correct.
1486 * 1486 *
1487 * @param node the node to validate 1487 * @param node the node to validate
1488 * @return the depth of the tree under <code>node</code> 1488 * @return the depth of the tree under <code>node</code>
1489 */ 1489 */
1490 private byte checkTreeStructure(Node node) { 1490 private byte checkTreeStructure(Node node) {
1491 if (node is null) 1491 if (node is null)
1492 return 0; 1492 return 0;
1493 1493
1494 byte leftDepth= checkTreeStructure(node.left); 1494 byte leftDepth= checkTreeStructure(node.left);
1495 byte rightDepth= checkTreeStructure(node.right); 1495 byte rightDepth= checkTreeStructure(node.right);
1496 Assert.isTrue(node.balance is rightDepth - leftDepth); 1496 Assert.isTrue(node.balance is rightDepth - leftDepth);
1497 Assert.isTrue(node.left is null || node.left.parent is node); 1497 Assert.isTrue(node.left is null || node.left.parent is node);
1498 Assert.isTrue(node.right is null || node.right.parent is node); 1498 Assert.isTrue(node.right is null || node.right.parent is node);
1499 1499
1500 return cast(byte) (Math.max(rightDepth, leftDepth) + 1); 1500 return cast(byte) (Math.max(rightDepth, leftDepth) + 1);
1501 } 1501 }
1502 1502
1503 /** 1503 /**
1504 * Debug-only method that checks the differential offsets of the tree, starting at 1504 * Debug-only method that checks the differential offsets of the tree, starting at
1505 * <code>node</code> and continuing until <code>last</code>. 1505 * <code>node</code> and continuing until <code>last</code>.
1506 * 1506 *
1507 * @param node the first <code>Node</code> to check, may be <code>null</code> 1507 * @param node the first <code>Node</code> to check, may be <code>null</code>
1508 * @param offLen an array of length 2, with <code>offLen[0]</code> the expected offset of 1508 * @param offLen an array of length 2, with <code>offLen[0]</code> the expected offset of
1509 * <code>node</code> and <code>offLen[1]</code> the expected line of 1509 * <code>node</code> and <code>offLen[1]</code> the expected line of
1510 * <code>node</code> 1510 * <code>node</code>
1511 * @param last the last <code>Node</code> to check, may be <code>null</code> 1511 * @param last the last <code>Node</code> to check, may be <code>null</code>
1514 * in <code>node</code>'s subtree 1514 * in <code>node</code>'s subtree
1515 */ 1515 */
1516 private int[] checkTreeOffsets(Node node, int[] offLen, Node last) { 1516 private int[] checkTreeOffsets(Node node, int[] offLen, Node last) {
1517 if (node is last) 1517 if (node is last)
1518 return offLen; 1518 return offLen;
1519 1519
1520 Assert.isTrue(node.offset is offLen[0]); 1520 Assert.isTrue(node.offset is offLen[0]);
1521 Assert.isTrue(node.line is offLen[1]); 1521 Assert.isTrue(node.line is offLen[1]);
1522 1522
1523 if (node.right !is null) { 1523 if (node.right !is null) {
1524 int[] result= checkTreeOffsets(successorDown(node.right), new int[2], node); 1524 int[] result= checkTreeOffsets(successorDown(node.right), new int[2], node);
1525 offLen[0] += result[0]; 1525 offLen[0] += result[0];
1526 offLen[1] += result[1]; 1526 offLen[1] += result[1];
1527 } 1527 }
1528 1528
1529 offLen[0] += node.length; 1529 offLen[0] += node.length;
1530 offLen[1]++; 1530 offLen[1]++;
1531 return checkTreeOffsets(node.parent, offLen, last); 1531 return checkTreeOffsets(node.parent, offLen, last);
1532 } 1532 }
1533 } 1533 }