1 19 20 package org.netbeans.modules.editor.lib2.highlighting; 21 22 import java.util.ArrayList ; 23 import java.util.ConcurrentModificationException ; 24 import java.util.HashMap ; 25 import java.util.List ; 26 import java.util.NoSuchElementException ; 27 import java.util.WeakHashMap ; 28 import java.util.logging.Level ; 29 import java.util.logging.Logger ; 30 import javax.swing.text.AttributeSet ; 31 import javax.swing.text.Document ; 32 import javax.swing.text.SimpleAttributeSet ; 33 import org.netbeans.api.editor.mimelookup.MimeLookup; 34 import org.netbeans.api.editor.mimelookup.MimePath; 35 import org.netbeans.api.editor.settings.AttributesUtilities; 36 import org.netbeans.api.editor.settings.FontColorSettings; 37 import org.netbeans.api.lexer.Language; 38 import org.netbeans.api.lexer.LanguagePath; 39 import org.netbeans.api.lexer.TokenHierarchy; 40 import org.netbeans.api.lexer.TokenHierarchyEvent; 41 import org.netbeans.api.lexer.TokenHierarchyListener; 42 import org.netbeans.api.lexer.TokenId; 43 import org.netbeans.api.lexer.TokenSequence; 44 import org.netbeans.spi.editor.highlighting.HighlightsSequence; 45 import org.netbeans.spi.editor.highlighting.support.AbstractHighlightsContainer; 46 import org.openide.util.Lookup; 47 import org.openide.util.WeakListeners; 48 49 54 public final class SyntaxHighlighting extends AbstractHighlightsContainer implements TokenHierarchyListener { 55 56 private static final Logger LOG = Logger.getLogger(SyntaxHighlighting.class.getName()); 57 58 public static final String LAYER_TYPE_ID = "org.netbeans.modules.editor.lib2.highlighting.SyntaxHighlighting"; 60 private final HashMap <String , WeakHashMap <TokenId, AttributeSet >> attribsCache = new HashMap <String , WeakHashMap <TokenId, AttributeSet >>(); 61 private final HashMap <String , FontColorSettings> fcsCache = new HashMap <String , FontColorSettings>(); 62 63 private final Document document; 64 private final String mimeTypeForHack; 65 private TokenHierarchy<? extends Document > hierarchy = null; 66 private long version = 0; 67 68 69 public SyntaxHighlighting(Document document) { 70 this.document = document; 71 72 String mimeType = (String ) document.getProperty("mimeType"); if (mimeType != null && mimeType.startsWith("test")) { this.mimeTypeForHack = mimeType; 75 } else { 76 this.mimeTypeForHack = null; 77 } 78 } 79 80 public HighlightsSequence getHighlights(int startOffset, int endOffset) { 81 synchronized (this) { 82 if (hierarchy == null) { 83 hierarchy = TokenHierarchy.get(document); 84 if (hierarchy != null) { 85 hierarchy.addTokenHierarchyListener(WeakListeners.create(TokenHierarchyListener.class, this, hierarchy)); 86 } 87 } 88 89 if (hierarchy != null) { 90 return new HSImpl(version, hierarchy, startOffset, endOffset); 91 } else { 92 return HighlightsSequence.EMPTY; 93 } 94 } 95 } 96 97 101 public void tokenHierarchyChanged(TokenHierarchyEvent evt) { 102 synchronized (this) { 103 version++; 104 } 105 106 fireHighlightsChange(evt.affectedStartOffset(), evt.affectedEndOffset()); 107 } 108 109 113 private final class HSImpl implements HighlightsSequence { 114 115 private static final int S_NORMAL = 1; 116 private static final int S_EMBEDDED_HEAD = 2; 117 private static final int S_EMBEDDED_TAIL = 3; 118 private static final int S_DONE = 4; 119 120 private long version; 121 private TokenHierarchy<? extends Document > scanner; 122 private List <TokenSequence<? extends TokenId>> sequences; 123 private int startOffset; 124 private int endOffset; 125 private int state; 126 127 public HSImpl(long version, TokenHierarchy<? extends Document > scanner, int startOffset, int endOffset) { 128 this.version = version; 129 this.scanner = scanner; 130 this.startOffset = startOffset; 131 this.endOffset = endOffset; 132 this.sequences = null; 133 } 134 135 public boolean moveNext() { 136 synchronized (SyntaxHighlighting.this) { 137 checkVersion(); 138 139 if (sequences == null) { 140 TokenSequence<? extends TokenId> seq = scanner.tokenSequence(); 142 seq.move(startOffset); 143 sequences = new ArrayList <TokenSequence<? extends TokenId>>(); 144 sequences.add(seq); 145 state = S_NORMAL; 146 } 147 148 switch (state) { 149 case S_NORMAL: 150 state = moveTheSequence(); 152 break; 153 154 case S_EMBEDDED_HEAD: 155 TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1); 157 seq.moveStart(); 158 if (seq.moveNext()) { 159 state = S_NORMAL; 160 } else { 161 throw new IllegalStateException ("Invalid state"); 162 } 163 break; 164 165 case S_EMBEDDED_TAIL: 166 sequences.remove(sequences.size() - 1); 168 state = moveTheSequence(); 169 break; 170 171 case S_DONE: 172 break; 174 175 default: 176 throw new IllegalStateException ("Invalid state: " + state); 177 } 178 179 if (state == S_NORMAL) { 180 TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1); 182 TokenSequence<? extends TokenId> embeddedSeq = seq.embedded(); 183 while (embeddedSeq != null && embeddedSeq.moveNext()) { 184 sequences.add(sequences.size(), embeddedSeq); 185 if (embeddedSeq.offset() > seq.offset()) { 186 state = S_EMBEDDED_HEAD; 187 break; 188 } else { 189 seq = embeddedSeq; 190 embeddedSeq = seq.embedded(); 191 } 192 } 193 } else if (state == S_DONE) { 194 attribsCache.clear(); 195 } 196 197 return state != S_DONE; 198 } 199 } 200 201 public int getStartOffset() { 202 synchronized (SyntaxHighlighting.this) { 203 checkVersion(); 204 205 if (sequences == null) { 206 throw new NoSuchElementException ("Call moveNext() first."); 207 } 208 209 switch (state) { 210 case S_NORMAL: { 211 TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1); 212 return seq.offset(); 213 } 214 case S_EMBEDDED_HEAD: { 215 TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2); 216 return embeddingSeq.offset(); 217 } 218 case S_EMBEDDED_TAIL: { 219 TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1); 220 seq.moveEnd(); 221 if (seq.movePrevious()) { 222 return seq.offset() + seq.token().length(); 223 } else { 224 throw new IllegalStateException ("Invalid state"); 225 } 226 } 227 case S_DONE: 228 throw new NoSuchElementException (); 229 230 default: 231 throw new IllegalStateException ("Invalid state: " + state); 232 } 233 } 234 } 235 236 public int getEndOffset() { 237 synchronized (SyntaxHighlighting.this) { 238 checkVersion(); 239 240 if (sequences == null) { 241 throw new NoSuchElementException ("Call moveNext() first."); 242 } 243 244 switch (state) { 245 case S_NORMAL: { 246 TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1); 247 return seq.offset() + seq.token().length(); 248 } 249 case S_EMBEDDED_HEAD: { 250 TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1); 251 seq.moveStart(); 252 if (seq.moveNext()) { 253 return seq.offset(); 254 } else { 255 TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2); 256 return embeddingSeq.offset() + embeddingSeq.token().length(); 257 } 258 } 259 case S_EMBEDDED_TAIL: 260 TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2); 261 return embeddingSeq.offset() + embeddingSeq.token().length(); 262 263 case S_DONE: 264 throw new NoSuchElementException (); 265 266 default: 267 throw new IllegalStateException ("Invalid state: " + state); 268 } 269 } 270 } 271 272 public AttributeSet getAttributes() { 273 synchronized (SyntaxHighlighting.this) { 274 checkVersion(); 275 276 if (sequences == null) { 277 throw new NoSuchElementException ("Call moveNext() first."); 278 } 279 280 switch (state) { 281 case S_NORMAL: 282 return findAttribs(sequences.size() - 1); 283 284 case S_EMBEDDED_HEAD: 285 case S_EMBEDDED_TAIL: 286 return findAttribs(sequences.size() - 2); 287 288 case S_DONE: 289 throw new NoSuchElementException (); 290 291 default: 292 throw new IllegalStateException ("Invalid state: " + state); 293 } 294 } 295 } 296 297 private AttributeSet findAttribs(int seqIdx) { 298 TokenSequence<? extends TokenId> seq = sequences.get(seqIdx); 299 TokenId tokenId = seq.token().id(); 300 String mimePath; 301 302 if (mimeTypeForHack != null) { 303 mimePath = languagePathToMimePathHack(seq.languagePath()); 304 } else { 305 mimePath = seq.languagePath().mimePath(); 306 } 307 308 WeakHashMap <TokenId, AttributeSet > token2attribs = attribsCache.get(mimePath); 309 if (token2attribs == null) { 310 token2attribs = new WeakHashMap <TokenId, AttributeSet >(); 311 attribsCache.put(mimePath, token2attribs); 312 } 313 314 AttributeSet tokenAttribs = token2attribs.get(tokenId); 315 if (tokenAttribs == null) { 316 tokenAttribs = findTokenAttribs(tokenId, mimePath, seq.languagePath().innerLanguage()); 317 318 if (seqIdx > 0) { 319 AttributeSet embeddingTokenAttribs = findAttribs(seqIdx - 1); 320 tokenAttribs = AttributesUtilities.createComposite( 321 tokenAttribs, embeddingTokenAttribs); 322 } 323 324 token2attribs.put(tokenId, tokenAttribs); 325 } 326 327 return tokenAttribs; 328 } 329 330 private AttributeSet findTokenAttribs(TokenId tokenId, String mimePath, Language<? extends TokenId> innerLanguage) { 331 FontColorSettings fcs = fcsCache.get(mimePath); 332 333 if (fcs == null) { 334 Lookup lookup = MimeLookup.getLookup(MimePath.parse(mimePath)); 335 fcs = lookup.lookup(FontColorSettings.class); 336 fcsCache.put(mimePath, fcs); 337 } 338 339 AttributeSet attribs = findFontAndColors(fcs, tokenId, innerLanguage); 340 341 if (LOG.isLoggable(Level.FINE)) { 342 LOG.fine(mimePath + ":" + tokenId.name() + " -> " + attribs); } 344 345 return attribs != null ? attribs : SimpleAttributeSet.EMPTY; 346 } 347 348 private String languagePathToMimePathHack(LanguagePath languagePath) { 372 if (languagePath.size() == 1) { 373 return mimeTypeForHack; 374 } else if (languagePath.size() > 1) { 375 return mimeTypeForHack + "/" + languagePath.subPath(1).mimePath(); } else { 377 throw new IllegalStateException ("LanguagePath should not be empty."); } 379 } 380 381 private AttributeSet findFontAndColors(FontColorSettings fcs, TokenId tokenId, Language lang) { 382 String name = tokenId.name(); 384 AttributeSet attribs = fcs.getTokenFontColors(name); 385 386 if (attribs == null) { 388 String primary = tokenId.primaryCategory(); 389 if (primary != null) { 390 attribs = fcs.getTokenFontColors(primary); 391 } 392 } 393 394 if (attribs == null) { 396 @SuppressWarnings ("unchecked") 397 List <String > categories = ((Language<TokenId>)lang).nonPrimaryTokenCategories(tokenId); 398 for(String c : categories) { 399 attribs = fcs.getTokenFontColors(c); 400 if (attribs != null) { 401 break; 402 } 403 } 404 } 405 406 return attribs; 407 } 408 409 private int moveTheSequence() { 410 TokenSequence<? extends TokenId> seq = sequences.get(sequences.size() - 1); 411 412 if (seq.moveNext() && seq.offset() < endOffset) { 413 return S_NORMAL; 414 } else { 415 if (sequences.size() > 1) { 416 TokenSequence<? extends TokenId> embeddingSeq = sequences.get(sequences.size() - 2); 417 seq.moveEnd(); 418 if (seq.movePrevious()) { 419 if ((seq.offset() + seq.token().length()) < (embeddingSeq.offset() + embeddingSeq.token().length())) { 420 return S_EMBEDDED_TAIL; 421 } else { 422 sequences.remove(sequences.size() - 1); 423 return moveTheSequence(); 424 } 425 } else { 426 throw new IllegalStateException ("Invalid state"); 427 } 428 } else { 429 sequences.clear(); 430 return S_DONE; 431 } 432 } 433 } 434 435 private void checkVersion() { 436 if (this.version != SyntaxHighlighting.this.version) { 437 throw new ConcurrentModificationException (); 438 } 439 } 440 } } 442 | Popular Tags |