1 19 20 package org.netbeans.modules.editor.structure.formatting; 21 22 import java.io.IOException ; 23 import java.io.Writer ; 24 import java.util.ArrayList ; 25 import java.util.Arrays ; 26 import java.util.LinkedList ; 27 import java.util.List ; 28 import java.util.logging.Level ; 29 import java.util.logging.Logger ; 30 import javax.swing.text.BadLocationException ; 31 import javax.swing.text.JTextComponent ; 32 import javax.swing.text.Position ; 33 import org.netbeans.api.lexer.Token; 34 import org.netbeans.api.lexer.TokenHierarchy; 35 import org.netbeans.api.lexer.TokenId; 36 import org.netbeans.api.lexer.TokenSequence; 37 import org.netbeans.editor.BaseDocument; 38 import org.netbeans.editor.Utilities; 39 import org.netbeans.editor.ext.ExtFormatter; 40 41 45 public abstract class TagBasedLexerFormatter extends ExtFormatter { 46 private static final Logger logger = Logger.getLogger(TagBasedLexerFormatter.class.getName()); 47 48 49 public TagBasedLexerFormatter(Class kitClass) { 50 super(kitClass); 51 } 52 53 protected abstract boolean isClosingTag(TokenHierarchy tokenHierarchy, int tagTokenOffset); 54 protected abstract boolean isUnformattableToken(TokenHierarchy tokenHierarchy, int tagTokenOffset); 55 protected abstract boolean isUnformattableTag(String tag); 56 protected abstract boolean isOpeningTag(TokenHierarchy tokenHierarchy, int tagTokenOffset); 57 protected abstract String extractTagName(TokenHierarchy tokenHierarchy, int tokenOffset); 58 protected abstract boolean areTagNamesEqual(String tagName1, String tagName2); 59 protected abstract boolean isClosingTagRequired(BaseDocument doc, String tagName); 60 protected abstract int getOpeningSymbolOffset(TokenHierarchy tokenHierarchy, int tagTokenOffset); 61 protected abstract int getTagEndingAtPosition(TokenHierarchy tokenHierarchy, int position) throws BadLocationException ; 62 protected abstract int getTagEndOffset(TokenHierarchy tokenHierarchy, int tagStartOffset); 63 64 protected boolean isWSTag(Token tag){ 65 char chars[] = tag.text().toString().toCharArray(); 66 67 for (char c : chars){ 68 if (!Character.isWhitespace(c)){ 69 return false; 70 } 71 } 72 73 return true; 74 } 75 76 protected int getIndentForTagParameter(TokenHierarchy<?> tokenHierarchy, int tagOffset) throws BadLocationException { 77 BaseDocument doc = (BaseDocument) tokenHierarchy.mutableInputSource(); 78 int tagStartLine = Utilities.getLineOffset(doc, tagOffset); 79 TokenSequence<? extends TokenId> tokenSequence = tokenHierarchy.tokenSequence(); 80 tokenSequence.move(tagOffset); 81 Token<? extends TokenId> token; 82 int tokenOffset; 83 84 89 while (tokenSequence.moveNext()) { 90 token = tokenSequence.token(); 91 tokenOffset = tokenSequence.offset(); 92 if (!isWSTag(token) || tagStartLine != Utilities.getLineOffset(doc, tokenOffset)) { 93 if (!isWSTag(token) && tagStartLine == Utilities.getLineOffset(doc, tokenOffset)){ 94 return tokenOffset - Utilities.getRowIndent(doc, tokenOffset) - Utilities.getRowStart(doc, tokenOffset); 95 } 96 break; 97 } 98 } 99 100 101 return getShiftWidth(); } 103 104 @Override public Writer reformat(BaseDocument doc, int startOffset, int endOffset, 105 boolean indentOnly) throws BadLocationException , IOException { 106 LinkedList <TagIndentationData>unprocessedOpeningTags = new LinkedList <TagIndentationData>(); 107 List <TagIndentationData>matchedOpeningTags = new ArrayList <TagIndentationData>(); 108 doc.atomicLock(); 109 TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc); 110 111 if (tokenHierarchy == null){ 112 logger.severe("Could not retrieve TokenHierarchy for document " + doc); 113 return null; 114 } 115 116 try{ 117 int lastLine = Utilities.getLineOffset(doc, doc.getLength()); 118 int firstRefBlockLine = Utilities.getLineOffset(doc, startOffset); 119 int lastRefBlockLine = Utilities.getLineOffset(doc, endOffset); 120 int firstUnformattableLine = -1; 121 122 boolean unformattableLines[] = new boolean[lastLine + 1]; 123 int indentsWithinTags[] = new int[lastLine + 1]; 124 125 TokenSequence tokenSequence = tokenHierarchy.tokenSequence(); 126 tokenSequence.moveStart(); 127 boolean thereAreMoreTokens = tokenSequence.moveNext(); 128 129 130 if (tokenSequence != null){ 131 do{ 133 boolean isOpenTag = isOpeningTag(tokenHierarchy, tokenSequence.offset()); 134 boolean isCloseTag = isClosingTag(tokenHierarchy, tokenSequence.offset()); 135 136 if (isOpenTag || isCloseTag){ 137 138 String tagName = extractTagName(tokenHierarchy, tokenSequence.offset()); 139 int tagEndOffset = getTagEndOffset(tokenHierarchy, tokenSequence.offset()); 140 int lastTagLine = Utilities.getLineOffset(doc, tagEndOffset); 141 142 if (isOpenTag){ 143 144 TagIndentationData tagData = new TagIndentationData(tagName, lastTagLine); 145 unprocessedOpeningTags.add(tagData); 146 147 int firstTagLine = Utilities.getLineOffset(doc, tokenSequence.offset()); 149 150 if (firstTagLine < lastTagLine){ int indentWithinTag = getIndentForTagParameter(tokenHierarchy, tokenSequence.offset()); 152 153 for (int i = firstTagLine + 1; i <= lastTagLine; i ++){ 154 indentsWithinTags[i] = indentWithinTag; 155 } 156 157 thereAreMoreTokens &= tokenSequence.moveNext(); 159 160 while (Utilities.getLineOffset(doc, tokenSequence.offset()) < lastTagLine 161 || isWSTag(tokenSequence.token())){ 162 163 tokenSequence.moveNext(); 164 } 165 166 if (tokenSequence.offset() == tagEndOffset){ 167 indentsWithinTags[lastTagLine] = 0; 168 } 169 } 170 } else { 171 LinkedList <TagIndentationData>tagsToBeRemoved = new LinkedList <TagIndentationData>(); 173 174 while (!unprocessedOpeningTags.isEmpty()){ 175 TagIndentationData processedTD = unprocessedOpeningTags.removeLast(); 176 177 if (areTagNamesEqual(tagName, processedTD.getTagName())){ 178 processedTD.setClosedOnLine(lastTagLine); 179 matchedOpeningTags.add(processedTD); 180 181 if (isUnformattableTag(tagName)){ 183 for (int i = lastTagLine - 1; i > processedTD.getLine(); i --){ 184 unformattableLines[i] = true; 185 } 186 } 187 188 tagsToBeRemoved.clear(); 190 break; 191 } else{ 192 tagsToBeRemoved.add(processedTD); 193 } 194 } 195 196 unprocessedOpeningTags.addAll(tagsToBeRemoved); 198 } 199 } 200 201 boolean wasPreviousTokenUnformattable = isUnformattableToken(tokenHierarchy, tokenSequence.offset()); 202 203 if (wasPreviousTokenUnformattable && firstUnformattableLine == -1){ 204 firstUnformattableLine = Utilities.getLineOffset(doc, tokenSequence.offset()); 205 } 206 207 thereAreMoreTokens &= tokenSequence.moveNext(); 208 209 if (firstUnformattableLine > -1 211 && (!wasPreviousTokenUnformattable || !thereAreMoreTokens)){ 212 213 int lastUnformattableLine = thereAreMoreTokens ? 214 Utilities.getLineOffset(doc, tokenSequence.offset() - 1) : lastLine; 215 216 for (int i = firstUnformattableLine + 1; i < lastUnformattableLine; i ++){ 217 unformattableLines[i] = true; 218 } 219 220 firstUnformattableLine = -1; 221 } 222 } 223 while (thereAreMoreTokens); 224 } 225 226 int indentLevels[] = new int[lastLine + 1]; 229 Arrays.fill(indentLevels, 0); 230 231 for (TagIndentationData td : matchedOpeningTags){ 232 235 for (int i = td.getLine() + 1; i <= td.getClosedOnLine() - 1; i ++){ 236 indentLevels[i] ++; 237 } 238 } 239 240 InitialIndentData initialIndentData = new InitialIndentData(doc, indentLevels, 243 indentsWithinTags, firstRefBlockLine, lastRefBlockLine); 244 245 for (int line = firstRefBlockLine; line <= lastRefBlockLine; line ++){ 247 int lineStart = Utilities.getRowStartFromLineOffset(doc, line); 248 249 if (!unformattableLines[line] && initialIndentData.isEligibleToIndent(line)){ 250 changeRowIndent(doc, lineStart, initialIndentData.getIndent(line)); 251 } 252 } 253 } finally{ 254 doc.atomicUnlock(); 255 } 256 257 return null; 258 } 259 260 protected void enterPressed(JTextComponent txtComponent, int dotPos) throws BadLocationException { 261 BaseDocument doc = Utilities.getDocument(txtComponent); 262 int lineNumber = Utilities.getLineOffset(doc, dotPos); 263 int initialIndent = getInitialIndentFromPreviousLine(doc, lineNumber); 264 int endOfPreviousLine = Utilities.getFirstNonWhiteBwd(doc, dotPos); 265 endOfPreviousLine = endOfPreviousLine == -1 ? 0 : endOfPreviousLine; 266 TokenHierarchy tokenHierarchy = TokenHierarchy.get(doc); 267 268 if (lineNumber == Utilities.getLineOffset(doc, endOfPreviousLine)){ 270 return; 271 } 272 273 int openingTagOffset = getTagEndingAtPosition(tokenHierarchy, endOfPreviousLine); 274 275 if (isOpeningTag(tokenHierarchy, openingTagOffset)){ 276 int closingTagOffset = getNextClosingTagOffset(tokenHierarchy, dotPos + 1); 277 278 if (closingTagOffset != -1){ 279 int matchingOpeningTagOffset = getMatchingOpeningTagStart(tokenHierarchy, closingTagOffset); 280 281 if (openingTagOffset == matchingOpeningTagOffset){ 282 283 int openingTagLine = Utilities.getLineOffset(doc, openingTagOffset); 284 int closingTagLine = Utilities.getLineOffset(doc, closingTagOffset); 285 286 if (closingTagLine == Utilities.getLineOffset(doc, dotPos)){ 287 288 if (openingTagLine == closingTagLine - 1){ 289 292 Position closingTagPos = doc.createPosition(getOpeningSymbolOffset(tokenHierarchy, closingTagOffset)); 293 changeRowIndent(doc, dotPos, initialIndent + getShiftWidth()); 294 doc.insertString(closingTagPos.getOffset(), "\n", null); int newCaretPos = closingTagPos.getOffset() - 1; 296 changeRowIndent(doc, closingTagPos.getOffset() + 1, initialIndent); 297 newCaretPos = Utilities.getRowEnd(doc, newCaretPos); 298 txtComponent.setCaretPosition(newCaretPos); 299 } else{ 300 304 changeRowIndent(doc, dotPos, initialIndent); 305 } 306 } 307 } 308 309 int indent = initialIndent; 310 311 if (isClosingTagRequired(doc, extractTagName(tokenHierarchy, openingTagOffset))){ 312 indent += getShiftWidth(); 313 } 314 315 changeRowIndent(doc, dotPos, indent); 316 } 317 } else{ 318 int indent = initialIndent; 319 320 if (isJustBeforeClosingTag(tokenHierarchy, dotPos)){ 321 indent -= getShiftWidth(); 322 indent = indent < 0 ? 0 : indent; 323 } 324 325 changeRowIndent(doc, dotPos, indent); 327 } 328 } 329 330 @Override public int[] getReformatBlock(JTextComponent target, String typedText) { 331 TokenHierarchy tokenHierarchy = TokenHierarchy.get(target.getDocument()); 332 if (tokenHierarchy == null){ 333 logger.severe("Could not retrieve TokenHierarchy for document " + target.getDocument()); 334 return null; 335 } 336 char lastChar = typedText.charAt(typedText.length() - 1); 337 338 try{ 339 int dotPos = target.getCaret().getDot(); 340 341 if (lastChar == '>') { 342 int precedingTokenOffset = getTagEndingAtPosition(tokenHierarchy, dotPos - 1); 343 344 if (isClosingTag(tokenHierarchy, precedingTokenOffset)){ 345 348 int openingTagOffset = getMatchingOpeningTagStart(tokenHierarchy, precedingTokenOffset); 349 350 if (openingTagOffset != -1){ 351 BaseDocument doc = Utilities.getDocument(target); 352 int openingTagLine = Utilities.getLineOffset(doc, openingTagOffset); 353 int closingTagSymbolLine = Utilities.getLineOffset(doc, dotPos); 354 355 if(openingTagLine != closingTagSymbolLine){ 356 return new int[]{precedingTokenOffset, dotPos}; 357 } 358 } 359 } 360 } 361 362 else if(lastChar == '\n') { 363 enterPressed(target, dotPos); 365 } 366 367 } catch (Exception e){ 368 logger.log(Level.SEVERE, "Exception during code formatting", e); } 370 371 return null; 372 } 373 374 protected int getMatchingOpeningTagStart(TokenHierarchy tokenHierarchy, int closingTagOffset){ 375 TokenSequence tokenSequence = tokenHierarchy.tokenSequence(); 376 tokenSequence.move(closingTagOffset); 377 378 String searchedTagName = extractTagName(tokenHierarchy, closingTagOffset); 379 int balance = 0; 380 381 while (tokenSequence.movePrevious()){ 382 int currentTokenOffset = tokenSequence.offset(); 383 if (areTagNamesEqual(searchedTagName, extractTagName(tokenHierarchy, currentTokenOffset))){ 384 if (isOpeningTag(tokenHierarchy, currentTokenOffset)){ 385 if (balance == 0){ 386 return currentTokenOffset; 387 } 388 389 balance --; 390 } else if (isClosingTag(tokenHierarchy, currentTokenOffset)){ 391 balance ++; 392 } 393 } 394 } 395 396 return -1; 397 } 398 399 protected int getInitialIndentFromPreviousLine(final BaseDocument doc, final int line) throws BadLocationException { 400 401 int initialIndent = 0; 403 404 if (line > 0){ 405 int lineStart = Utilities.getRowStartFromLineOffset(doc, line); 406 int previousNonWhiteLineEnd = Utilities.getFirstNonWhiteBwd(doc, lineStart); 407 408 if (previousNonWhiteLineEnd > 0){ 409 initialIndent = Utilities.getRowIndent(doc, previousNonWhiteLineEnd); 410 } 411 } 412 413 return initialIndent; 414 } 415 416 private int getInitialIndentFromNextLine(final BaseDocument doc, final int line) throws BadLocationException { 417 418 int initialIndent = 0; 420 421 int lineStart = Utilities.getRowStartFromLineOffset(doc, line); 422 int lineEnd = Utilities.getRowEnd(doc, lineStart); 423 int nextNonWhiteLineStart = Utilities.getFirstNonWhiteFwd(doc, lineEnd); 424 425 if (nextNonWhiteLineStart > 0){ 426 initialIndent = Utilities.getRowIndent(doc, nextNonWhiteLineStart, true); 427 } 428 429 return initialIndent; 430 } 431 432 protected static int getNumberOfLines(BaseDocument doc) throws BadLocationException { 433 return Utilities.getLineOffset(doc, doc.getLength() - 1) + 1; 434 } 435 436 protected int getNextClosingTagOffset(TokenHierarchy tokenHierarchy, int offset) throws BadLocationException { 437 TokenSequence tokenSequence = tokenHierarchy.tokenSequence(); 438 tokenSequence.move(offset); 439 int currentOffset; 440 441 while (tokenSequence.moveNext()) { 442 currentOffset = tokenSequence.offset(); 443 444 if (isClosingTag(tokenHierarchy, currentOffset)){ 445 return currentOffset; 446 } 447 448 } 449 450 return -1; 451 } 452 453 protected boolean isJustBeforeClosingTag(TokenHierarchy tokenHierarchy, int pos) throws BadLocationException { 454 if (isClosingTag(tokenHierarchy, pos)){ 456 return true; 457 } 458 459 return false; 460 } 461 462 protected Token getTokenAtOffset(TokenHierarchy tokenHierarchy, int tagTokenOffset){ 463 TokenSequence tokenSequence = tokenHierarchy.tokenSequence(); 464 465 if (tokenSequence != null) { 466 tokenSequence.move(tagTokenOffset); 467 if (tokenSequence.moveNext()) 468 return tokenSequence.token(); 469 } 470 471 return null; 472 } 473 474 protected class InitialIndentData{ 475 private final int indentLevelBias; 476 private final int indentBias; 477 private final int indentLevels[]; 478 private final int indentsWithinTags[]; 479 480 public InitialIndentData(BaseDocument doc, int indentLevels[], int indentsWithinTags[], 481 int firstRefBlockLine, int lastRefBlockLine) throws BadLocationException { 482 483 int initialIndent = getInitialIndentFromPreviousLine(doc, firstRefBlockLine); 484 int indentLevelBiasFromTheTop = initialIndent / getShiftWidth() - (firstRefBlockLine > 0 ? indentLevels[firstRefBlockLine - 1] : 0); 485 486 int initialIndentFromTheBottom = getInitialIndentFromNextLine(doc, lastRefBlockLine); 487 int indentLevelBiasFromTheBottom = initialIndentFromTheBottom / getShiftWidth() - (lastRefBlockLine < getNumberOfLines(doc) - 1 ? indentLevels[lastRefBlockLine + 1] : 0); 488 489 if (indentLevelBiasFromTheBottom > indentLevelBiasFromTheTop){ 490 indentLevelBias = indentLevelBiasFromTheBottom; 491 initialIndent = initialIndentFromTheBottom; 492 } 493 else{ 494 indentLevelBias = indentLevelBiasFromTheTop; 495 } 496 497 indentBias = initialIndent % getShiftWidth(); 498 this.indentLevels = indentLevels; 499 this.indentsWithinTags = indentsWithinTags; 500 } 501 502 public boolean isEligibleToIndent(int line){ 503 return getActualIndentLevel(line) >= 0; 504 } 505 506 public int getIndent(int line){ 507 return indentBias + indentsWithinTags[line] + getActualIndentLevel(line) * getShiftWidth(); 508 } 509 510 private int getActualIndentLevel(int line){ 511 return indentLevels[line] + indentLevelBias; 512 } 513 } 514 515 protected static class TagIndentationData{ 516 private final String tagName; 517 private final int line; 518 private int closedOnLine; 519 520 public TagIndentationData(String tagName, int line){ 521 this.tagName = tagName; 522 this.line = line; 523 } 524 525 public String getTagName() { 526 return tagName; 527 } 528 529 public int getLine() { 530 return line; 531 } 532 533 public int getClosedOnLine() { 534 return closedOnLine; 535 } 536 537 public void setClosedOnLine(int closedOnLine) { 538 this.closedOnLine = closedOnLine; 539 } 540 } 541 } 542 | Popular Tags |