Mercurial > projects > dwt-addons
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, < <code>firstLineDelta</code> | 799 * @param length the replace length, < <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> > <code>length</code> | 801 * <code>node</code> > <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, >= <code>firstLineDelta</code> | 845 * @param length the replace length, >= <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>, <= <code>length</code> | 847 * of <code>node</code>, <= <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 } |