1 2 20 package org.netbeans.editor.ext.html; 21 22 import org.netbeans.editor.ext.html.parser.SyntaxParser; 23 import org.netbeans.editor.ext.html.parser.SyntaxElement; 24 import java.util.*; 25 import javax.swing.text.BadLocationException ; 26 import javax.swing.text.JTextComponent ; 27 import org.netbeans.api.html.lexer.HTMLTokenId; 28 import org.netbeans.api.lexer.Token; 29 import org.netbeans.api.lexer.TokenHierarchy; 30 import org.netbeans.api.lexer.TokenSequence; 31 import org.netbeans.editor.BaseDocument; 32 import org.netbeans.editor.ext.ExtSyntaxSupport; 33 import org.netbeans.editor.ext.html.dtd.DTD; 34 import org.netbeans.editor.ext.html.dtd.DTD.Element; 35 import org.netbeans.editor.ext.html.dtd.InvalidateEvent; 36 import org.netbeans.editor.ext.html.dtd.InvalidateListener; 37 38 39 45 public class HTMLSyntaxSupport extends ExtSyntaxSupport implements InvalidateListener { 46 private static final String FALLBACK_DOCTYPE = 47 "-//W3C//DTD HTML 4.01 Transitional//EN"; 49 private DTD dtd; 50 private String docType; 51 private final SyntaxParser parser; 52 53 54 public HTMLSyntaxSupport( BaseDocument doc ) { 55 super(doc); 56 parser = SyntaxParser.get(doc); 57 } 58 59 61 public void dtdInvalidated(InvalidateEvent evt) { 62 if( dtd != null && evt.isInvalidatedIdentifier( docType ) ) { 63 dtd = null; 64 } 65 } 66 67 public DTD getDTD() { 68 String type = getDocType(); 69 if( type == null ) type = FALLBACK_DOCTYPE; 70 71 if( dtd != null && type == docType ) return dtd; 72 73 docType = type; 74 dtd = org.netbeans.editor.ext.html.dtd.Registry.getDTD( docType, null ); 75 return dtd; 76 } 77 78 protected String getDocType() { 79 try { 80 SyntaxElement elem = getElementChain( 0 ); 81 82 if( elem == null ) return null; 84 int type = elem.getType(); 85 86 while( type != SyntaxElement.TYPE_DECLARATION 87 && type != SyntaxElement.TYPE_TAG ) { 88 elem = elem.getNext(); 89 if( elem == null ) break; 90 type = elem.getType(); 91 } 92 93 if( type == SyntaxElement.TYPE_DECLARATION ) 94 return ((SyntaxElement.Declaration)elem).getPublicIdentifier(); 95 96 return null; 97 } catch( BadLocationException e ) { 98 return null; 99 } 100 } 101 102 103 104 113 @Override public int[] findMatchingBlock(int offset, boolean simpleSearch) 114 throws BadLocationException { 115 BaseDocument document = getDocument(); 116 document.readLock(); 117 try { 118 TokenHierarchy hi = TokenHierarchy.get(document); 119 TokenSequence ts = tokenSequence(hi, offset); 120 if(ts == null) { 121 return null; 123 } 124 125 ts.move(offset); 126 if(!ts.moveNext() && !ts.movePrevious()) { 127 return null; } 129 130 Token token = ts.token(); 131 132 if (token.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) { 134 ts.moveIndex(ts.index() - 2); 135 if(ts.moveNext()) { 136 token = ts.token(); 137 } 138 } 139 140 boolean isInside = false; if (isTagButNotSymbol(token)) { 142 isInside = true; } else { 144 if(ts.moveNext()) { 145 token = ts.token(); 146 if(token.id() == HTMLTokenId.TAG_OPEN_SYMBOL) { 147 if((token.id() == HTMLTokenId.TAG_CLOSE) 151 || (token.id() == HTMLTokenId.TAG_OPEN)) { 152 isInside = true; } else { 154 return null; 155 } 156 } else { 157 boolean found = false; 161 while(!isTagButNotSymbol(token) && token.id() != HTMLTokenId.TAG_CLOSE_SYMBOL && ts.movePrevious()) { 162 token = ts.token(); 163 } 164 165 if (ts.index() != -1 && isTagButNotSymbol(token)) { 166 isInside = true; 167 } 168 } 169 } else { 170 return null; } 172 } 173 174 if (ts.index() != -1 && isTagButNotSymbol(token) && isInside){ 175 int start; int end; int poss = -1; 179 String tag = token.text().toString().toLowerCase().trim(); 180 if (token.id() == HTMLTokenId.TAG_CLOSE) { 182 do { 184 token = ts.token(); 185 if (isTagButNotSymbol(token)) { 186 String tagName = token.text().toString().toLowerCase().trim(); 187 if (tagName.equals(tag) 188 && (token.id() == HTMLTokenId.TAG_OPEN) 189 && !isSingletonTag(ts)) { 190 if (poss == 0){ 192 ts.movePrevious(); 194 start = ts.token().offset(hi); 195 ts.moveIndex(ts.index() + 2); 196 ts.moveNext(); 197 Token tok = ts.token(); 198 end = tok.offset(hi)+ (tok.id() == HTMLTokenId.TAG_CLOSE_SYMBOL ? tok.text().length() : 0); 199 return new int[] {start, end}; 200 } else{ 201 poss--; 202 } 203 } else { 204 if ((tagName.indexOf(tag) > -1) 206 && !isSingletonTag(ts)) { 207 poss++; 208 } 209 } 210 } 211 } while(ts.movePrevious()); 212 213 } else{ 214 if (tag.charAt(0) == '>') 216 return null; 217 218 if(isSingletonTag(ts)) return null; 221 222 do { 223 token = ts.token(); 224 if (isTagButNotSymbol(token)) { 225 String tagName = token.text().toString().toLowerCase().trim(); 226 if (tagName.equals(tag) 227 && token.id() == HTMLTokenId.TAG_CLOSE){ 228 if (poss == 0) { 229 end = token.offset(hi) + token.text().length() + 1; 231 ts.movePrevious(); 232 start = ts.token().offset(hi); 233 234 do { 235 token = ts.token(); 236 } while(ts.moveNext() && token.id() != HTMLTokenId.TAG_CLOSE_SYMBOL); 237 238 if (ts.index() != -1) { 239 end = token.offset(hi)+token.text().length(); 240 } 241 return new int[] {start, end}; 242 } else 243 poss--; 244 } else{ 245 if (tagName.equals(tag) 246 && !isSingletonTag(ts)) { 247 poss++; 248 } 249 } 250 } 251 } while (ts.moveNext()); 252 } 253 } 254 255 ts.move(offset); ts.moveNext(); 257 token = ts.token(); 258 259 if(ts.index() != -1 && token.id() == HTMLTokenId.BLOCK_COMMENT) { 261 String tokenImage = token.text().toString(); 262 if(tokenImage.startsWith("<!--") && (offset < (token.offset(hi)) + "<!--".length())) { do { 265 token = ts.token(); 266 tokenImage = token.text().toString(); 267 if((token.id() == HTMLTokenId.BLOCK_COMMENT)) { 268 if(tokenImage.endsWith("-->")) { int end = token.offset(hi) + tokenImage.length(); 271 int start = end - "-->".length(); return new int[] {start, end}; 273 } 274 } else break; 275 } while(ts.moveNext()); 276 } 277 278 if(tokenImage.endsWith("-->") && (offset >= (token.offset(hi)) + tokenImage.length() - "-->".length())) { do { 281 token = ts.token(); 282 if((token.id() == HTMLTokenId.BLOCK_COMMENT)) { 283 if(token.text().toString().startsWith("<!--")) { int start = token.offset(hi); 286 int end = start + "<!--".length(); return new int[] {start, end}; 288 } 289 } else break; 290 291 } while(ts.movePrevious()); 292 } 293 } 295 } finally { 296 document.readUnlock(); 297 } 298 return null; 299 } 300 301 305 public boolean isSingletonTag(TokenSequence ts) { 306 int tsIndex = ts.index(); if(tsIndex != -1) { try { 309 do { 310 Token ti = ts.token(); 311 if(ti.id() == HTMLTokenId.TAG_CLOSE_SYMBOL) { 312 if("/>".equals(ti.text().toString())) { return true; 315 } 316 if(">".equals(ti.text().toString())) { 317 break; } 319 } 320 if((ti.id() == HTMLTokenId.TEXT) 323 || (ti.id() == HTMLTokenId.TAG_OPEN_SYMBOL)) { 324 break; 325 } 326 } while(ts.moveNext()); 327 }finally{ 328 ts.moveIndex(tsIndex); ts.moveNext(); 330 } 331 } else { 332 } 334 return false; 335 } 336 337 343 public SyntaxElement getElementChain( int offset ) throws BadLocationException { 344 return parser.getElementChain(offset); 345 } 346 347 351 SyntaxElement getPreviousElement( int offset ) throws BadLocationException { 352 return offset == 0 ? null : getElementChain( offset - 1 ); 353 } 354 355 public List getPossibleEndTags( int offset, String prefix ) throws BadLocationException { 356 prefix = prefix.toUpperCase(); 357 int prefixLen = prefix.length(); 358 SyntaxElement elem = getElementChain( offset ); 359 Stack stack = new Stack(); 360 List result = new ArrayList(); 361 Set found = new HashSet(); 362 DTD dtd = getDTD(); 363 364 if(elem == null) { 365 if(offset > 0) { 366 elem = getElementChain( offset - 1); 367 if(elem == null) return result; 368 } else return result; 369 } 370 371 int itemsCount = 0; 372 for( ; elem != null; elem = elem.getPrevious() ) { 373 if( elem.getType() == SyntaxElement.TYPE_ENDTAG) { DTD.Element tag = dtd.getElement( ((SyntaxElement.Named)elem).getName().toUpperCase() ); 375 if(tag != null && !tag.isEmpty()) stack.push( ((SyntaxElement.Named)elem).getName().toUpperCase() ); 376 } else if(elem.getType() == SyntaxElement.TYPE_TAG) { DTD.Element tag = dtd.getElement( ((SyntaxElement.Tag)elem).getName().toUpperCase() ); 378 379 if( tag == null ) continue; if( tag.isEmpty() ) continue; 382 String name = tag.getName(); 383 384 if( stack.empty() ) { if( name.startsWith( prefix ) && !found.contains( name ) ) { found.add( name ); 387 result.add( new HTMLCompletionQuery.EndTagItem( name, offset-2-prefixLen, prefixLen+2, name, itemsCount ) ); 388 } 389 if( ! tag.hasOptionalEnd() ) break; } else { if( stack.peek().equals( name ) ) { stack.pop(); 393 } else if( ! tag.hasOptionalEnd() ) break; } 395 } 396 } 397 398 return result; 399 } 400 401 public List getAutocompletedEndTag(int offset) { 402 List l = new ArrayList(); 403 try { 404 SyntaxElement elem = getElementChain( offset - 1); 405 if(elem != null && elem.getType() == SyntaxElement.TYPE_TAG) { 406 String tagName = ((SyntaxElement.Named)elem).getName(); 407 Element dtdElem = getDTD().getElement(tagName.toUpperCase()); 409 if( dtdElem == null || !dtdElem.isEmpty()) { 410 HTMLCompletionQuery.ResultItem eti = new HTMLCompletionQuery.AutocompleteEndTagItem(tagName, offset); 411 l.add(eti); 412 } 413 } 414 }catch(BadLocationException e) { 415 } 417 return l; 418 } 419 420 public int checkCompletion(JTextComponent target, String typedText, boolean visible ) { 421 int retVal = COMPLETION_CANCEL; 422 int dotPos = target.getCaret().getDot(); 423 BaseDocument doc = (BaseDocument)target.getDocument(); 424 switch( typedText.charAt( typedText.length()-1 ) ) { 425 case '/': 426 if (dotPos >= 2) { try { 428 String txtBeforeSpace = doc.getText(dotPos-2, 2); 429 if( txtBeforeSpace.equals("</") ) return COMPLETION_POPUP; 431 } catch (BadLocationException e) {} 432 } 433 break; 434 case ' ': 435 doc.readLock(); 436 try { 437 TokenHierarchy hi = TokenHierarchy.get(doc); 438 TokenSequence ts = tokenSequence(hi, dotPos - 1); 439 if(ts == null) { 440 return COMPLETION_POST_REFRESH; 442 } 443 444 ts.move(dotPos-1); 445 if(ts.moveNext() || ts.movePrevious()) { 446 if(ts.token().id() == HTMLTokenId.WS) { 447 return COMPLETION_POPUP; 448 } 449 } 450 }finally { 451 doc.readUnlock(); 452 } 453 break; 454 case '<': 455 case '&': 456 return COMPLETION_POPUP; 457 case ';': 458 return COMPLETION_HIDE; 459 case '>': 460 HTMLSyntaxSupport sup = (HTMLSyntaxSupport)doc.getSyntaxSupport().get(HTMLSyntaxSupport.class); 461 try { 462 SyntaxElement se = getElementChain(dotPos-1); 464 if(se != null && se.getType() == SyntaxElement.TYPE_TAG) { 465 return COMPLETION_POPUP; 466 } 467 }catch(BadLocationException e) { 468 } 470 return COMPLETION_HIDE; 471 472 } 473 return COMPLETION_POST_REFRESH; 474 475 } 476 477 478 public static boolean isTag(Token t) { 479 return (( t.id() == HTMLTokenId.TAG_OPEN ) || 480 ( t.id() == HTMLTokenId.TAG_CLOSE ) || 481 ( t.id() == HTMLTokenId.TAG_OPEN_SYMBOL) || 482 ( t.id() == HTMLTokenId.TAG_CLOSE_SYMBOL)); 483 } 484 485 public static boolean isTagButNotSymbol(Token t) { 486 return (( t.id() == HTMLTokenId.TAG_OPEN) || 487 ( t.id() == HTMLTokenId.TAG_CLOSE)); 488 } 489 490 private static TokenSequence tokenSequence(TokenHierarchy hi, int offset) { 491 TokenSequence ts = hi.tokenSequence(HTMLTokenId.language()); 492 if(ts == null) { 493 ts = hi.tokenSequence(); 495 int diff = ts.move(offset); 496 if(!ts.moveNext() && !ts.movePrevious()) { 497 return null; } else { 499 ts = ts.embedded(HTMLTokenId.language()); 500 } 501 } 502 return ts; 503 } 504 505 } 506 | Popular Tags |