1 19 20 package org.netbeans.editor; 21 22 import org.netbeans.lib.editor.util.swing.DocumentUtilities; 23 import org.openide.ErrorManager; 24 25 import javax.swing.text.Document ; 26 import javax.swing.text.BadLocationException ; 27 import javax.swing.text.Position ; 28 import javax.swing.event.DocumentEvent ; 29 import java.util.*; 30 import java.util.regex.Pattern ; 31 import java.util.regex.Matcher ; 32 import org.netbeans.api.editor.fold.Fold; 33 import org.netbeans.api.editor.fold.FoldType; 34 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction; 35 import org.netbeans.spi.editor.fold.FoldManager; 36 import org.netbeans.spi.editor.fold.FoldManagerFactory; 37 import org.netbeans.spi.editor.fold.FoldOperation; 38 39 45 46 final class CustomFoldManager implements FoldManager { 47 48 private static final boolean debug = false; 49 50 public static final FoldType CUSTOM_FOLD_TYPE = new FoldType("custom-fold"); 52 private FoldOperation operation; 53 private Document doc; 54 private org.netbeans.editor.GapObjectArray markArray = new org.netbeans.editor.GapObjectArray(); 55 private int minUpdateMarkOffset; 56 private int maxUpdateMarkOffset; 57 private List removedFoldList; 58 private HashMap customFoldId = new HashMap(); 59 60 public void init(FoldOperation operation) { 61 this.operation = operation; 62 } 63 64 private FoldOperation getOperation() { 65 return operation; 66 } 67 68 public void initFolds(FoldHierarchyTransaction transaction) { 69 try { 70 doc = getOperation().getHierarchy().getComponent().getDocument(); 71 updateFolds(SyntaxUpdateTokens.getTokenInfoList(doc), transaction); 72 } catch (BadLocationException e) { 73 ErrorManager.getDefault().notify(e); 74 } 75 } 76 77 public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 78 try { 79 processRemovedFolds(transaction); 80 updateFolds(SyntaxUpdateTokens.getTokenInfoList(evt), transaction); 81 } catch (BadLocationException e) { 82 ErrorManager.getDefault().notify(e); 83 } 84 } 85 86 public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 87 try { 88 processRemovedFolds(transaction); 89 removeAffectedMarks(evt, transaction); 90 updateFolds(SyntaxUpdateTokens.getTokenInfoList(evt), transaction); 91 } catch (BadLocationException e) { 92 ErrorManager.getDefault().notify(e); 93 } 94 } 95 96 public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 97 } 98 99 public void removeEmptyNotify(Fold emptyFold) { 100 removeFoldNotify(emptyFold); 101 } 102 103 public void removeDamagedNotify(Fold damagedFold) { 104 removeFoldNotify(damagedFold); 105 } 106 107 public void expandNotify(Fold expandedFold) { 108 109 } 110 111 public void release() { 112 113 } 114 115 private void removeFoldNotify(Fold removedFold) { 116 if (removedFoldList == null) { 117 removedFoldList = new ArrayList(3); 118 } 119 removedFoldList.add(removedFold); 120 } 121 122 private void removeAffectedMarks(DocumentEvent evt, FoldHierarchyTransaction transaction) { 123 int removeOffset = evt.getOffset(); 124 int markIndex = findMarkIndex(removeOffset); 125 if (markIndex < getMarkCount()) { 126 FoldMarkInfo mark; 127 while (markIndex >= 0 && (mark = getMark(markIndex)).getOffset() == removeOffset) { 128 mark.release(false, transaction); 129 removeMark(markIndex); 130 markIndex--; 131 } 132 } 133 } 134 135 private void processRemovedFolds(FoldHierarchyTransaction transaction) { 136 if (removedFoldList != null) { 137 for (int i = removedFoldList.size() - 1; i >= 0; i--) { 138 Fold removedFold = (Fold)removedFoldList.get(i); 139 FoldMarkInfo startMark = (FoldMarkInfo)getOperation().getExtraInfo(removedFold); 140 if (startMark.getId() != null) 141 customFoldId.put(startMark.getId(), Boolean.valueOf(removedFold.isCollapsed())); FoldMarkInfo endMark = startMark.getPairMark(); if (getOperation().isStartDamaged(removedFold)) { startMark.release(true, transaction); } 146 if (getOperation().isEndDamaged(removedFold)) { 147 endMark.release(true, transaction); 148 } 149 } 150 } 151 removedFoldList = null; 152 } 153 154 private void markUpdate(FoldMarkInfo mark) { 155 markUpdate(mark.getOffset()); 156 } 157 158 private void markUpdate(int offset) { 159 if (offset < minUpdateMarkOffset) { 160 minUpdateMarkOffset = offset; 161 } 162 if (offset > maxUpdateMarkOffset) { 163 maxUpdateMarkOffset = offset; 164 } 165 } 166 167 private FoldMarkInfo getMark(int index) { 168 return (FoldMarkInfo)markArray.getItem(index); 169 } 170 171 private int getMarkCount() { 172 return markArray.getItemCount(); 173 } 174 175 private void removeMark(int index) { 176 if (debug) { 177 System.err.println("Removing mark from ind=" + index + ": " + getMark(index)); } 180 markArray.remove(index, 1); 181 } 182 183 private void insertMark(int index, FoldMarkInfo mark) { 184 markArray.insertItem(index, mark); 185 if (debug) { 186 System.err.println("Inserted mark at ind=" + index + ": " + mark); } 189 } 190 191 private int findMarkIndex(int offset) { 192 int markCount = getMarkCount(); 193 int low = 0; 194 int high = markCount - 1; 195 196 while (low <= high) { 197 int mid = (low + high) / 2; 198 int midMarkOffset = getMark(mid).getOffset(); 199 200 if (midMarkOffset < offset) { 201 low = mid + 1; 202 } else if (midMarkOffset > offset) { 203 high = mid - 1; 204 } else { 205 mid++; 208 while (mid < markCount && getMark(mid).getOffset() == offset) { 209 mid++; 210 } 211 mid--; 212 return mid; 213 } 214 } 215 return low; } 217 218 private List getMarkList(List tokenList) { 219 List markList = null; 220 int tokenListSize = tokenList.size(); 221 if (tokenListSize != 0) { 222 for (int i = 0; i < tokenListSize; i++) { 223 SyntaxUpdateTokens.TokenInfo tokenInfo = (SyntaxUpdateTokens.TokenInfo) tokenList.get(i); 224 FoldMarkInfo info; 225 try { 226 info = scanToken(tokenInfo); 227 } catch (BadLocationException e) { 228 ErrorManager.getDefault().notify(e); 229 info = null; 230 } 231 232 if (info != null) { 233 if (markList == null) { 234 markList = new ArrayList(); 235 } 236 markList.add(info); 237 } 238 } 239 } 240 return markList; 241 } 242 243 private void processTokenList(List tokenList, FoldHierarchyTransaction transaction) { 244 List markList = getMarkList(tokenList); 245 int markListSize; 246 if (markList != null && ((markListSize = markList.size()) > 0)) { 247 int offset = ((FoldMarkInfo)markList.get(0)).getOffset(); 249 int arrayMarkIndex = findMarkIndex(offset); 250 FoldMarkInfo arrayMark; 252 int arrayMarkOffset; 253 if (arrayMarkIndex < getMarkCount()) { 254 arrayMark = getMark(arrayMarkIndex); 255 arrayMarkOffset = arrayMark.getOffset(); 256 } else { arrayMark = null; 258 arrayMarkOffset = Integer.MAX_VALUE; 259 } 260 261 for (int i = 0; i < markListSize; i++) { 262 FoldMarkInfo listMark = (FoldMarkInfo)markList.get(i); 263 int listMarkOffset = listMark.getOffset(); 264 if (i == 0 || i == markListSize - 1) { 265 markUpdate(listMarkOffset); 267 } 268 if (listMarkOffset >= arrayMarkOffset) { 269 if (listMarkOffset == arrayMarkOffset) { 270 listMark.setCollapsed(arrayMark.isCollapsed()); 273 } 274 if (!arrayMark.isReleased()) { arrayMark.release(false, transaction); 276 } 277 removeMark(arrayMarkIndex); 278 if (debug) { 279 System.err.println("Removed dup mark from ind=" 280 + arrayMarkIndex + ": " + arrayMark); } 282 if (arrayMarkIndex < getMarkCount()) { 283 arrayMark = getMark(arrayMarkIndex); 284 arrayMarkOffset = arrayMark.getOffset(); 285 } else { arrayMark = null; 287 arrayMarkOffset = Integer.MAX_VALUE; 288 } 289 } 290 insertMark(arrayMarkIndex, listMark); 292 if (debug) { 293 System.err.println("Inserted mark at ind=" + arrayMarkIndex + ": " + listMark); } 296 arrayMarkIndex++; 297 } 298 } 299 } 300 301 private void updateFolds(List tokenList, FoldHierarchyTransaction transaction) 302 throws BadLocationException { 303 304 if (tokenList.size() > 0) { 305 processTokenList(tokenList, transaction); 306 } 307 308 if (maxUpdateMarkOffset == -1) { return; 310 } 311 312 int index = findMarkIndex(minUpdateMarkOffset); 314 FoldMarkInfo prevMark; 315 FoldMarkInfo parentMark; 316 if (index == 0) { prevMark = null; 318 parentMark = null; 319 } else { 320 prevMark = getMark(index - 1); 321 parentMark = prevMark.getParentMark(); 322 } 323 324 int markCount = getMarkCount(); 326 while (index < markCount) { FoldMarkInfo mark = getMark(index); 328 329 if (mark.isReleased()) { 331 if (debug) { 332 System.err.println("Removing released mark at ind=" + index + ": " + mark); } 335 removeMark(index); 336 markCount--; 337 continue; 338 } 339 340 if (mark.isStartMark()) { if (prevMark == null || prevMark.isStartMark()) { mark.setParentMark(prevMark); parentMark = prevMark; 345 346 } 348 } else { if (prevMark != null) { 350 if (prevMark.isStartMark()) { prevMark.setEndMark(mark, false, transaction); 352 353 } else { if (parentMark != null) { 355 parentMark.setEndMark(mark, false, transaction); 357 parentMark = parentMark.getParentMark(); 358 359 } else { mark.makeSolitaire(false, transaction); 361 } 362 } 363 364 } else { mark.makeSolitaire(false, transaction); 366 } 367 } 368 369 mark.setParentMark(parentMark); 371 372 373 prevMark = mark; 374 index++; 375 } 376 377 minUpdateMarkOffset = Integer.MAX_VALUE; 378 maxUpdateMarkOffset = -1; 379 380 if (debug) { 381 System.err.println("MARKS DUMP:\n" + this); 382 } 383 } 384 385 public String toString() { 386 StringBuffer sb = new StringBuffer (); 387 int markCount = getMarkCount(); 388 int markCountDigitCount = Integer.toString(markCount).length(); 389 for (int i = 0; i < markCount; i++) { 390 sb.append("["); String iStr = Integer.toString(i); 392 appendSpaces(sb, markCountDigitCount - iStr.length()); 393 sb.append(iStr); 394 sb.append("]:"); FoldMarkInfo mark = getMark(i); 396 397 int indent = 0; 399 FoldMarkInfo parentMark = mark.getParentMark(); 400 while (parentMark != null) { 401 indent += 4; 402 parentMark = parentMark.getParentMark(); 403 } 404 appendSpaces(sb, indent); 405 406 sb.append(mark); 407 sb.append('\n'); 408 } 409 return sb.toString(); 410 } 411 412 private static void appendSpaces(StringBuffer sb, int spaces) { 413 while (--spaces >= 0) { 414 sb.append(' '); 415 } 416 } 417 418 private static Pattern pattern = Pattern.compile("(<\\s*editor-fold(?:(?:\\s+id=\"(\\S*)\")?(?:\\s+defaultstate=\"(\\S*)\")?(?:\\s+desc=\"([\\S \\t]*)\")?)(?:(?:\\s+defaultstate=\"(\\S*)\")?(?:\\s+desc=\"([\\S \\t]*)\")?)(?:\\s+defaultstate=\"(\\S*)\")?\\s*>)|(?:</\\s*editor-fold\\s*>)"); 420 private FoldMarkInfo scanToken(SyntaxUpdateTokens.TokenInfo tokenInfo) throws BadLocationException { 421 Matcher matcher = pattern.matcher(DocumentUtilities.getText(doc, tokenInfo.getOffset(), tokenInfo.getLength())); 422 if (matcher.find()) { 423 if (matcher.group(1) != null) { boolean state = "collapsed".equals(matcher.group(3)); if (matcher.group(2) != null) { Boolean collapsed = (Boolean )customFoldId.get(matcher.group(2)); 427 if (collapsed != null) 428 state = collapsed.booleanValue(); else 430 customFoldId.put(matcher.group(2), Boolean.valueOf(state)); 431 } 432 return new FoldMarkInfo(true, tokenInfo.getOffset(), tokenInfo.getLength(), matcher.group(2), state, matcher.group(4)); } else { return new FoldMarkInfo(false, tokenInfo.getOffset(), tokenInfo.getLength(), null, false, null); 435 } 436 } 437 return null; 438 } 439 440 private final class FoldMarkInfo { 441 442 private boolean startMark; 443 private Position pos; 444 private int length; 445 private String id; 446 private boolean collapsed; 447 private String description; 448 449 450 private FoldMarkInfo pairMark; 451 452 453 private FoldMarkInfo parentMark; 454 455 460 private Fold fold; 461 462 private boolean released; 463 464 private FoldMarkInfo(boolean startMark, int offset, 465 int length, String id, boolean collapsed, String description) 466 throws BadLocationException { 467 468 this.startMark = startMark; 469 this.pos = doc.createPosition(offset); 470 this.length = length; 471 this.id = id; 472 this.collapsed = collapsed; 473 this.description = description; 474 } 475 476 public String getId() { 477 return id; 478 } 479 480 public String getDescription() { 481 return description; 482 } 483 484 public boolean isStartMark() { 485 return startMark; 486 } 487 488 public int getLength() { 489 return length; 490 } 491 492 public int getOffset() { 493 return pos.getOffset(); 494 } 495 496 public int getEndOffset() { 497 return getOffset() + getLength(); 498 } 499 500 public boolean isCollapsed() { 501 return (fold != null) ? fold.isCollapsed() : collapsed; 502 } 503 504 public boolean hasFold() { 505 return (fold != null); 506 } 507 508 public void setCollapsed(boolean collapsed) { 509 this.collapsed = collapsed; 510 } 511 512 public boolean isSolitaire() { 513 return (pairMark == null); 514 } 515 516 public void makeSolitaire(boolean forced, FoldHierarchyTransaction transaction) { 517 if (!isSolitaire()) { 518 if (isStartMark()) { 519 setEndMark(null, forced, transaction); 520 } else { getPairMark().setEndMark(null, forced, transaction); 522 } 523 } 524 } 525 526 public boolean isReleased() { 527 return released; 528 } 529 530 533 public void release(boolean forced, FoldHierarchyTransaction transaction) { 534 if (!released) { 535 makeSolitaire(forced, transaction); 536 released = true; 537 markUpdate(this); 538 } 539 } 540 541 public FoldMarkInfo getPairMark() { 542 return pairMark; 543 } 544 545 private void setPairMark(FoldMarkInfo pairMark) { 546 this.pairMark = pairMark; 547 } 548 549 public void setEndMark(FoldMarkInfo endMark, boolean forced, 550 FoldHierarchyTransaction transaction) { 551 if (!isStartMark()) { 552 throw new IllegalStateException ("Not start mark"); } 554 if (pairMark == endMark) { 555 return; 556 } 557 558 if (pairMark != null) { releaseFold(forced, transaction); 560 pairMark.setPairMark(null); 561 } 562 563 pairMark = endMark; 564 if (endMark != null) { 565 if (!endMark.isSolitaire()) { endMark.makeSolitaire(false, transaction); } 568 endMark.setPairMark(this); 569 endMark.setParentMark(this.getParentMark()); 570 ensureFoldExists(transaction); 571 } 572 } 573 574 public FoldMarkInfo getParentMark() { 575 return parentMark; 576 } 577 578 public void setParentMark(FoldMarkInfo parentMark) { 579 this.parentMark = parentMark; 580 } 581 582 private void releaseFold(boolean forced, FoldHierarchyTransaction transaction) { 583 if (isSolitaire() || !isStartMark()) { 584 throw new IllegalStateException (); 585 } 586 587 if (fold != null) { 588 setCollapsed(fold.isCollapsed()); if (!forced) { 590 getOperation().removeFromHierarchy(fold, transaction); 591 } 592 fold = null; 593 } 594 } 595 596 public Fold getFold() { 597 if (isSolitaire()) { 598 return null; 599 } 600 if (!isStartMark()) { 601 return pairMark.getFold(); 602 } 603 return fold; 604 } 605 606 public void ensureFoldExists(FoldHierarchyTransaction transaction) { 607 if (isSolitaire() || !isStartMark()) { 608 throw new IllegalStateException (); 609 } 610 611 if (fold == null) { 612 try { 613 if (!startMark) { 614 throw new IllegalStateException ("Not start mark: " + this); } 616 if (pairMark == null) { 617 throw new IllegalStateException ("No pairMark for mark:" + this); } 619 int startOffset = getOffset(); 620 int startGuardedLength = getLength(); 621 int endGuardedLength = pairMark.getLength(); 622 int endOffset = pairMark.getOffset() + endGuardedLength; 623 fold = getOperation().addToHierarchy( 624 CUSTOM_FOLD_TYPE, getDescription(), collapsed, 625 startOffset, endOffset, 626 startGuardedLength, endGuardedLength, 627 this, 628 transaction 629 ); 630 } catch (BadLocationException e) { 631 ErrorManager.getDefault().notify(e); 632 } 633 } 634 } 635 636 public String toString() { 637 StringBuffer sb = new StringBuffer (); 638 sb.append(isStartMark() ? 'S' : 'E'); 640 if (hasFold() || (!isSolitaire() && getPairMark().hasFold())) { 642 sb.append("F"); 644 if (isStartMark() && (isSolitaire() 646 || getOffset() != fold.getStartOffset() 647 || getPairMark().getEndOffset() != fold.getEndOffset()) 648 ) { 649 sb.append("!!<"); sb.append(fold.getStartOffset()); 651 sb.append(","); sb.append(fold.getEndOffset()); 653 sb.append(">!!"); } 655 } 656 657 sb.append(" ("); sb.append("o="); sb.append(pos.getOffset()); 661 sb.append(", l="); sb.append(length); 663 sb.append(", d='"); sb.append(description); 665 sb.append('\''); 666 if (getPairMark() != null) { 667 sb.append(", <->"); sb.append(getPairMark().getOffset()); 669 } 670 if (getParentMark() != null) { 671 sb.append(", ^"); sb.append(getParentMark().getOffset()); 673 } 674 sb.append(')'); 675 676 return sb.toString(); 677 } 678 679 } 680 681 public static final class Factory implements FoldManagerFactory { 682 683 public FoldManager createFoldManager() { 684 return new CustomFoldManager(); 685 } 686 } 687 } 688 | Popular Tags |