1 19 20 package org.netbeans.modules.html.editor.folding; 21 22 import java.lang.reflect.InvocationTargetException ; 23 import java.util.ArrayList ; 24 import java.util.HashSet ; 25 import java.util.Hashtable ; 26 import java.util.Iterator ; 27 import java.util.List ; 28 import java.util.Stack ; 29 import java.util.Timer ; 30 import java.util.TimerTask ; 31 import javax.swing.SwingUtilities ; 32 import javax.swing.event.DocumentEvent ; 33 import javax.swing.text.BadLocationException ; 34 import javax.swing.text.Document ; 35 import javax.swing.text.JTextComponent ; 36 37 import org.netbeans.editor.BaseDocument; 38 import org.netbeans.editor.Settings; 39 import org.netbeans.editor.SettingsChangeEvent; 40 import org.netbeans.editor.SettingsChangeListener; 41 import org.netbeans.editor.SettingsUtil; 42 import org.netbeans.editor.Utilities; 43 import org.netbeans.editor.ext.html.HTMLSettingsDefaults; 44 import org.netbeans.editor.ext.html.HTMLSettingsNames; 45 import org.netbeans.editor.ext.html.HTMLSyntaxSupport; 46 import org.netbeans.editor.ext.html.parser.SyntaxElement; 47 48 import org.netbeans.spi.editor.fold.FoldHierarchyTransaction; 49 import org.netbeans.spi.editor.fold.FoldManager; 50 import org.netbeans.spi.editor.fold.FoldOperation; 51 import org.netbeans.api.editor.fold.Fold; 52 import org.netbeans.api.editor.fold.FoldHierarchy; 53 import org.netbeans.api.editor.fold.FoldType; 54 import org.netbeans.api.editor.fold.FoldUtilities; 55 import org.openide.ErrorManager; 56 57 63 64 public class HTMLFoldManager implements FoldManager, SettingsChangeListener { 65 66 private static final boolean SHOW_TIMES = Boolean.getBoolean("org.netbeans.modules.html.editor.folding.measure"); 67 68 private FoldOperation operation; 69 private HTMLSyntaxSupport sup; 70 71 private Timer timer; 73 private TimerTask timerTask; 74 private int foldsUpdateInterval = -1; 75 76 boolean documentDirty = true; 77 private static final String FOLD_MANAGER_CREATED = "FOLD_MANAGER_CREATED"; 79 private BaseDocument doc = null; 80 81 protected FoldOperation getOperation() { 82 return operation; 83 } 84 85 public void init(FoldOperation operation) { 86 this.operation = operation; 87 Settings.addSettingsChangeListener(this); 88 foldsUpdateInterval = getSetting(HTMLSettingsNames.CODE_FOLDING_UPDATE_TIMEOUT); 89 } 90 91 public void initFolds(FoldHierarchyTransaction transaction) { 92 Document doc = getOperation().getHierarchy().getComponent().getDocument(); 94 95 if(doc instanceof BaseDocument 96 && doc.getLength() > 0 && doc.getProperty(FOLD_MANAGER_CREATED) == null) { 98 99 this.doc = (BaseDocument)doc; 100 doc.putProperty(FOLD_MANAGER_CREATED, new Object ()); 101 102 sup = new HTMLSyntaxSupport(getDocument()); 103 104 timer = new Timer (); 107 restartTimer(); 108 } 109 } 110 111 private BaseDocument getDocument() { 112 return this.doc; 113 } 114 115 private void restartTimer() { 116 documentDirty = true; 117 if(timer == null) return ; 120 121 if(timerTask != null) timerTask.cancel(); 122 timerTask = createTimerTask(); 123 timer.schedule(timerTask, foldsUpdateInterval); 124 } 125 126 private TimerTask createTimerTask() { 127 return new TimerTask () { 128 public void run() { 129 Thread thr = new Thread (new Runnable () { 131 public void run() { 132 try { 133 documentDirty = false; 134 updateFolds(); 135 }catch(ParsingCancelledException pce) { 136 if(debug) System.out.println("parsing cancelled"); 137 } 138 } 139 }); 140 thr.setPriority(Thread.MIN_PRIORITY + 1); 141 thr.start(); 142 try { 144 thr.join(); 145 }catch(InterruptedException e) { 146 } 148 } 149 }; 150 } 151 152 public void release() { 153 Settings.removeSettingsChangeListener(this); 154 if (timer != null) { 155 timer.cancel(); 156 timer = null; 157 } 158 } 159 160 public void insertUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 161 restartTimer(); 162 } 163 164 public void removeUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 165 restartTimer(); 166 } 167 168 public void changedUpdate(DocumentEvent evt, FoldHierarchyTransaction transaction) { 169 } 171 172 public void removeEmptyNotify(Fold epmtyFold) { 173 } 174 175 public void removeDamagedNotify(Fold damagedFold) { 176 } 177 178 public void expandNotify(Fold expandedFold) { 179 } 180 181 public void settingsChange(SettingsChangeEvent evt) { 182 if(evt.getSettingName() == HTMLSettingsNames.CODE_FOLDING_UPDATE_TIMEOUT) { 184 foldsUpdateInterval = getSetting(HTMLSettingsNames.CODE_FOLDING_UPDATE_TIMEOUT); 185 restartTimer(); 186 } 187 } 188 189 private List generateFolds() throws BadLocationException , ParsingCancelledException { 190 return generateFolds((HTMLSyntaxSupport)getDocument().getSyntaxSupport()); 191 } 192 193 List generateFolds(HTMLSyntaxSupport sup) throws BadLocationException , ParsingCancelledException { 194 Stack stack = new Stack (); ArrayList generated = new ArrayList (100); 196 197 SyntaxElement sel = sup.getElementChain(0); 198 while(sel != null) { 199 if(documentDirty) throw new ParsingCancelledException(); 201 202 204 if(sel.getType() == SyntaxElement.TYPE_TAG) { 205 SyntaxElement.Tag tag = (SyntaxElement.Tag)sel; 207 if(tag.isEmpty()) { 208 generated.add(new FoldInfo(sel.getElementOffset(), sel.getElementOffset() + sel.getElementLength(), HTMLFoldTypes.TAG, getSingletonTagFoldName(tag.getName()))); 210 } else { 211 stack.push(sel); 212 } 213 } else if(sel.getType() == SyntaxElement.TYPE_ENDTAG) { 214 SyntaxElement.Named endtag = (SyntaxElement.Named)sel; 216 if(!stack.isEmpty()) { 217 SyntaxElement top = (SyntaxElement)stack.peek(); 218 if(top.getType() == SyntaxElement.TYPE_TAG 219 && ((SyntaxElement.Tag)top).getName().equalsIgnoreCase(endtag.getName())) { 220 generated.add(new FoldInfo(top.getElementOffset(), endtag.getElementOffset() + endtag.getElementLength(), HTMLFoldTypes.TAG, getTagFoldName(((SyntaxElement.Tag)top).getName()))); 222 stack.pop(); 223 } else { 224 ArrayList savedElements = new ArrayList (); 227 boolean foundStartTag = false; 230 231 while(!stack.isEmpty()) { 232 SyntaxElement.Tag start = (SyntaxElement.Tag)stack.pop(); 233 savedElements.add(start); 234 235 if(start.getName().equalsIgnoreCase(endtag.getName())) { 236 generated.add(new FoldInfo(start.getElementOffset(), 238 endtag.getElementOffset() + endtag.getElementLength(), 239 HTMLFoldTypes.TAG, getTagFoldName(start.getName()))); 240 241 foundStartTag = true; 242 break; } 244 } 245 if(!foundStartTag) { 246 for(int i = savedElements.size() - 1; i >= 0; i--) { 249 stack.push(savedElements.get(i)); 250 } 251 } 252 } 253 } 254 } else if(sel.getType() == SyntaxElement.TYPE_COMMENT) { 255 generated.add(new FoldInfo(sel.getElementOffset(), sel.getElementOffset() + sel.getElementLength() - 1, HTMLFoldTypes.COMMENT, HTMLFoldTypes.COMMENT_DESCRIPTION)); 257 } 258 260 sel = getNextSyntaxElement(sel); 262 } 263 return generated; 264 } 265 266 private String getSingletonTagFoldName(String tagName) { 267 StringBuffer sb = new StringBuffer (); 268 sb.append('<'); 269 sb.append(tagName); 270 sb.append('/'); 271 sb.append('>'); 272 return sb.toString(); 273 } 274 275 private String getTagFoldName(String tagName) { 276 StringBuffer sb = new StringBuffer (); 277 sb.append('<'); 278 sb.append(tagName); 279 sb.append(">...</"); 280 sb.append(tagName); 281 sb.append('>'); 282 return sb.toString(); 283 } 284 285 288 private synchronized void updateFolds() throws ParsingCancelledException { 289 FoldHierarchy fh = getOperation().getHierarchy(); 290 291 long startTime = System.currentTimeMillis(); 293 294 try { 295 List generated = generateFolds(); 297 298 if(SHOW_TIMES) System.out.println("[html folding] parsing of text of " + getDocument().getProperty(Document.TitleProperty) + " done in " + (System.currentTimeMillis() - startTime) + " millis."); 299 300 Iterator itr = generated.iterator(); 307 HashSet olfs = new HashSet (); 308 while (itr.hasNext()) { 309 FoldInfo elem = (FoldInfo) itr.next(); 310 if(isOneLineElement(elem)) olfs.add(elem); 311 } 312 generated.removeAll(olfs); 313 314 List existingFolds = FoldUtilities.findRecursive(fh.getRootFold()); 316 assert existingFolds != null : "Existing folds is null!"; 318 final ArrayList newborns = new ArrayList (generated.size() / 2); 320 final ArrayList zombies = new ArrayList (generated.size() / 2); 321 322 Iterator genItr = generated.iterator(); 324 Hashtable newbornsLinesCache = new Hashtable (); 325 ArrayList duplicateNewborns = new ArrayList (); 326 while(genItr.hasNext()) { 327 FoldInfo fi = (FoldInfo)genItr.next(); 328 if(debug) System.out.println("NEWBORN " + fi); 329 int fiLineOffset = Utilities.getLineOffset(getDocument(), fi.startOffset); 331 FoldInfo found = (FoldInfo)newbornsLinesCache.get(Integer.valueOf(fiLineOffset)); 332 if(found != null) { 333 if(found.endOffset < fi.endOffset) { 335 duplicateNewborns.add(found); 337 } 338 } 339 newbornsLinesCache.put(Integer.valueOf(fiLineOffset), fi); 341 Fold fs = FoldUtilities.findNearestFold(fh, fi.startOffset); 343 if(fs != null 344 && fs.getStartOffset() == fi.startOffset 345 && fs.getEndOffset() == fi.endOffset) { 346 if(fi.foldType != fs.getType() || !(fi.description.equals(fs.getDescription()))) { 348 zombies.add(fs); 350 newborns.add(fi); 351 } 352 } else { 353 newborns.add(fi); 355 } 356 } 357 newborns.removeAll(duplicateNewborns); 358 existingFolds.removeAll(zombies); 359 360 Hashtable linesToFoldsCache = new Hashtable (); 362 Iterator extItr = existingFolds.iterator(); 364 while(extItr.hasNext()) { 365 Fold f = (Fold)extItr.next(); 366 Iterator genItr2 = generated.iterator(); 368 boolean found = false; 369 while(genItr2.hasNext()) { 370 FoldInfo fi = (FoldInfo)genItr2.next(); 371 if(f.getStartOffset() == fi.startOffset 372 && f.getEndOffset() == fi.endOffset) { 373 found = true; 374 break; 375 } 376 } 377 if(!found) { 378 zombies.add(f); 379 } else { 380 int lineoffset = Utilities.getLineOffset(getDocument(), f.getStartOffset()); 382 linesToFoldsCache.put(Integer.valueOf(lineoffset), f); 383 } 384 } 386 387 Iterator newbornsItr = newborns.iterator(); 391 ArrayList newbornsToRemove = new ArrayList (); 392 while(newbornsItr.hasNext()) { 393 FoldInfo fi = (FoldInfo)newbornsItr.next(); 394 Fold existing = (Fold)linesToFoldsCache.get(Integer.valueOf(Utilities.getLineOffset(getDocument(), fi.startOffset))); 395 if(existing != null) { 396 if(existing.getEndOffset() < fi.endOffset) { 398 zombies.add(existing); 400 } else { 401 newbornsToRemove.add(fi); 403 } 404 } 405 } 406 newborns.removeAll(newbornsToRemove); 407 408 if(SHOW_TIMES) System.out.println("[html folding] parsing and mangles with elements for " + getDocument().getProperty(Document.TitleProperty) + " done in " + (System.currentTimeMillis() - startTime) + " millis."); 409 410 SwingUtilities.invokeAndWait(new Runnable () { 412 public void run() { 413 if(debug) System.out.println("updating folds --> locking document!"); getDocument().readLock(); 416 try { 417 FoldHierarchy fh = getOperation().getHierarchy(); 419 fh.lock(); 420 try { 421 FoldHierarchyTransaction fhTran = getOperation().openTransaction(); 423 try { 424 Iterator i = zombies.iterator(); 426 while(i.hasNext()) { 427 Fold f = (Fold)i.next(); 428 if(getDocument().getLength() == 0) break; 431 432 if(debug) System.out.println("- removing fold " + f); 433 getOperation().removeFromHierarchy(f, fhTran); 434 } 435 436 Iterator newFolds = newborns.iterator(); 438 while(newFolds.hasNext()) { 439 FoldInfo f = (FoldInfo)newFolds.next(); 440 if(getDocument().getLength() == 0) break; 443 444 if(f.startOffset >= 0 445 && f.endOffset >= 0 446 && f.startOffset < f.endOffset 447 && f.endOffset <= getDocument().getLength()) { 448 getOperation().addToHierarchy(f.foldType, f.description, false, f.startOffset , f.endOffset , 0, 0, null, fhTran); 449 } 450 } 451 }catch(BadLocationException ble) { 452 Document fhDoc = getOperation().getHierarchy().getComponent().getDocument(); 454 if(fhDoc.getLength() > 0) ErrorManager.getDefault().notify(ble); 455 }finally { 456 fhTran.commit(); 457 } 458 } finally { 459 fh.unlock(); 460 } 461 } finally { 462 getDocument().readUnlock(); 463 } 464 if(debug) System.out.println("document unlocked!"); } 466 }); 467 468 469 }catch(BadLocationException e) { 470 474 Document fhDoc = getOperation().getHierarchy().getComponent().getDocument(); 476 if(fhDoc.getLength() > 0) ErrorManager.getDefault().notify(e); 477 }catch(InterruptedException ie) { 478 }catch(InvocationTargetException ite) { 480 ErrorManager.getDefault().notify(ite); 481 }catch(ParsingCancelledException pce) { 482 throw new ParsingCancelledException(); 483 }catch(Exception e) { 484 ErrorManager.getDefault().notify(e); 487 } finally { 488 if(debug) HTMLFoldUtils.printFolds(getOperation()); } 490 491 long foldsGenerationTime = System.currentTimeMillis() - startTime; 493 if(SHOW_TIMES) System.out.println("[html folding] folds for " + getDocument().getProperty(Document.TitleProperty) + " generated in " + foldsGenerationTime + " millis."); 494 495 } 496 497 private boolean isOneLineElement(FoldInfo fi) throws BadLocationException { 498 return Utilities.getLineOffset(getDocument(), fi.startOffset) == Utilities.getLineOffset(getDocument(), fi.endOffset); 499 } 500 501 private boolean foldsBoundariesEquals(Fold f1, Fold f2) { 502 return (f1.getStartOffset() == f2.getStartOffset() 503 && f1.getEndOffset() == f2.getEndOffset()); 504 } 505 506 private SyntaxElement getNextSyntaxElement(SyntaxElement se) throws BadLocationException { 508 return se.getNext(); 509 } 516 517 private int getSetting(String settingName){ 518 JTextComponent tc = getOperation().getHierarchy().getComponent(); 519 return SettingsUtil.getInteger(org.netbeans.editor.Utilities.getKitClass(tc), settingName, HTMLSettingsDefaults.defaultCodeFoldingUpdateInterval); 520 } 521 522 private static final boolean debug = false; 524 static class FoldInfo { 525 public int startOffset, endOffset; 526 public FoldType foldType = null; 527 public String description = null; 528 529 public FoldInfo(int startOffset, int endOffset, FoldType foldType, String description) { 530 this.startOffset = startOffset; 531 this.endOffset = endOffset; 532 this.foldType = foldType; 533 this.description = description; 534 } 535 536 public String toString() { 537 return "FI(" + description + ", " + foldType + ", <" + startOffset + "-" + endOffset + ">)"; 538 } 539 } 540 541 static class ParsingCancelledException extends Exception { 542 public ParsingCancelledException() { 543 super(); 544 } 545 } 546 } 547 | Popular Tags |