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 javax.swing.text.BadLocationException ; 29 import javax.swing.text.JTextComponent ; 30 import javax.swing.text.Position ; 31 import org.netbeans.editor.BaseDocument; 32 import org.netbeans.editor.TokenItem; 33 import org.netbeans.editor.Utilities; 34 import org.netbeans.editor.ext.ExtFormatter; 35 import org.netbeans.editor.ext.ExtSyntaxSupport; 36 import org.openide.ErrorManager; 37 38 42 @Deprecated public abstract class TagBasedFormatter extends ExtFormatter { 44 45 46 public TagBasedFormatter(Class kitClass) { 47 super(kitClass); 48 } 49 50 protected abstract ExtSyntaxSupport getSyntaxSupport(BaseDocument doc); 51 protected abstract boolean isClosingTag(TokenItem token); 52 protected abstract boolean isUnformattableToken(TokenItem token); 53 protected abstract boolean isUnformattableTag(String tag); 54 protected abstract boolean isOpeningTag(TokenItem token); 55 protected abstract String extractTagName(TokenItem tknTag); 56 protected abstract boolean areTagNamesEqual(String tagName1, String tagName2); 57 protected abstract boolean isClosingTagRequired(BaseDocument doc, String tagName); 58 protected abstract int getOpeningSymbolOffset(TokenItem tknTag); 59 protected abstract TokenItem getTagTokenEndingAtPosition(BaseDocument doc, int position) throws BadLocationException ; 60 protected abstract int getTagEndOffset(TokenItem token); 61 62 protected Writer extFormatterReformat(final BaseDocument doc, final int startOffset, final int endOffset, 63 final boolean indentOnly) throws BadLocationException , IOException { 64 return super.reformat(doc, startOffset, endOffset, indentOnly); 65 } 66 67 protected boolean isWSTag(TokenItem tag){ 68 char chars[] = tag.getImage().toCharArray(); 69 70 for (char c : chars){ 71 if (!Character.isWhitespace(c)){ 72 return false; 73 } 74 } 75 76 return true; 77 } 78 79 protected int getIndentForTagParameter(BaseDocument doc, TokenItem tag) throws BadLocationException { 80 int tagStartLine = Utilities.getLineOffset(doc, tag.getOffset()); 81 TokenItem currentToken = tag.getNext(); 82 83 88 while (currentToken != null && isWSTag(currentToken) && tagStartLine == Utilities.getLineOffset(doc, currentToken.getOffset())){ 89 currentToken = currentToken.getNext(); 90 } 91 92 if (tag != null && !isWSTag(currentToken) && tagStartLine == Utilities.getLineOffset(doc, currentToken.getOffset())){ 93 return currentToken.getOffset() - Utilities.getRowIndent(doc, currentToken.getOffset()) - Utilities.getRowStart(doc, currentToken.getOffset()); 94 } 95 96 return getShiftWidth(); } 98 99 @Override public Writer reformat(BaseDocument doc, int startOffset, int endOffset, 100 boolean indentOnly) throws BadLocationException , IOException { 101 102 if (!hasValidSyntaxSupport(doc)){ 103 return null; 104 } 105 106 LinkedList <TagIndentationData>unprocessedOpeningTags = new LinkedList <TagIndentationData>(); 107 List <TagIndentationData>matchedOpeningTags = new ArrayList <TagIndentationData>(); 108 doc.atomicLock(); 109 110 try{ 111 int lastLine = Utilities.getLineOffset(doc, doc.getLength()); 112 int firstRefBlockLine = Utilities.getLineOffset(doc, startOffset); 113 int lastRefBlockLine = Utilities.getLineOffset(doc, endOffset); 114 int firstUnformattableLine = -1; 115 116 boolean unformattableLines[] = new boolean[lastLine + 1]; 117 int indentsWithinTags[] = new int[lastLine + 1]; 118 119 ExtSyntaxSupport sup = getSyntaxSupport(doc); 120 TokenItem token = sup.getTokenChain(0, doc.getLength() - 1); 121 122 if (token != null){ 123 do{ 125 boolean isOpenTag = isOpeningTag(token); 126 boolean isCloseTag = isClosingTag(token); 127 128 if (isOpenTag || isCloseTag){ 129 130 String tagName = extractTagName(token); 131 int tagEndOffset = getTagEndOffset(token); 132 int lastTagLine = Utilities.getLineOffset(doc, tagEndOffset); 133 134 if (isOpenTag){ 135 136 TagIndentationData tagData = new TagIndentationData(tagName, lastTagLine); 137 unprocessedOpeningTags.add(tagData); 138 139 int firstTagLine = Utilities.getLineOffset(doc, token.getOffset()); 141 142 if (firstTagLine < lastTagLine){ int indentWithinTag = getIndentForTagParameter(doc, token); 144 145 for (int i = firstTagLine + 1; i <= lastTagLine; i ++){ 146 indentsWithinTags[i] = indentWithinTag; 147 } 148 149 TokenItem currentToken = token.getNext(); 151 while (Utilities.getLineOffset(doc, currentToken.getOffset()) < lastTagLine 152 || isWSTag(currentToken)){ 153 154 currentToken = currentToken.getNext(); 155 } 156 157 if (currentToken.getOffset() == tagEndOffset){ 158 indentsWithinTags[lastTagLine] = 0; 159 } 160 } 161 } else { 162 LinkedList <TagIndentationData>tagsToBeRemoved = new LinkedList <TagIndentationData>(); 164 165 while (!unprocessedOpeningTags.isEmpty()){ 166 TagIndentationData processedTD = unprocessedOpeningTags.removeLast(); 167 168 if (areTagNamesEqual(tagName, processedTD.getTagName())){ 169 processedTD.setClosedOnLine(lastTagLine); 170 matchedOpeningTags.add(processedTD); 171 172 if (isUnformattableTag(tagName)){ 174 for (int i = lastTagLine - 1; i > processedTD.getLine(); i --){ 175 unformattableLines[i] = true; 176 } 177 } 178 179 tagsToBeRemoved.clear(); 181 break; 182 } else{ 183 tagsToBeRemoved.add(processedTD); 184 } 185 } 186 187 unprocessedOpeningTags.addAll(tagsToBeRemoved); 189 } 190 } 191 192 boolean wasPreviousTokenUnformattable = isUnformattableToken(token); 193 194 if (wasPreviousTokenUnformattable && firstUnformattableLine == -1){ 195 firstUnformattableLine = Utilities.getLineOffset(doc, token.getOffset()); 196 } 197 198 token = token.getNext(); 199 200 if (firstUnformattableLine > -1 202 && (!wasPreviousTokenUnformattable || token == null)){ 203 204 int lastUnformattableLine = token == null ? lastLine : 205 Utilities.getLineOffset(doc, token.getOffset() - 1); 206 207 for (int i = firstUnformattableLine + 1; i < lastUnformattableLine; i ++){ 208 unformattableLines[i] = true; 209 } 210 211 firstUnformattableLine = -1; 212 } 213 } 214 while (token != null); 215 } 216 217 int indentLevels[] = new int[lastLine + 1]; 220 Arrays.fill(indentLevels, 0); 221 222 for (TagIndentationData td : matchedOpeningTags){ 223 226 for (int i = td.getLine() + 1; i <= td.getClosedOnLine() - 1; i ++){ 227 indentLevels[i] ++; 228 } 229 } 230 231 InitialIndentData initialIndentData = new InitialIndentData(doc, indentLevels, 234 indentsWithinTags, firstRefBlockLine, lastRefBlockLine); 235 236 for (int line = firstRefBlockLine; line <= lastRefBlockLine; line ++){ 238 int lineStart = Utilities.getRowStartFromLineOffset(doc, line); 239 240 if (!unformattableLines[line] && initialIndentData.isEligibleToIndent(line)){ 241 changeRowIndent(doc, lineStart, initialIndentData.getIndent(line)); 242 } 243 } 244 } finally{ 245 doc.atomicUnlock(); 246 } 247 248 return null; 249 } 250 251 protected void enterPressed(JTextComponent txtComponent, int dotPos) throws BadLocationException { 252 BaseDocument doc = Utilities.getDocument(txtComponent); 253 int lineNumber = Utilities.getLineOffset(doc, dotPos); 254 int initialIndent = getInitialIndentFromPreviousLine(doc, lineNumber); 255 int endOfPreviousLine = Utilities.getFirstNonWhiteBwd(doc, dotPos); 256 endOfPreviousLine = endOfPreviousLine == -1 ? 0 : endOfPreviousLine; 257 258 if (lineNumber == Utilities.getLineOffset(doc, endOfPreviousLine)){ 260 return; 261 } 262 263 TokenItem tknOpeningTag = getTagTokenEndingAtPosition(doc, endOfPreviousLine); 264 265 if (isOpeningTag(tknOpeningTag)){ 266 TokenItem tknClosingTag = getNextClosingTag(doc, dotPos + 1); 267 268 if (tknClosingTag != null){ 269 TokenItem tknMatchingOpeningTag = getMatchingOpeningTag(tknClosingTag); 270 271 if (tknMatchingOpeningTag != null 272 && tknMatchingOpeningTag.getOffset() == tknOpeningTag.getOffset()){ 273 274 int openingTagLine = Utilities.getLineOffset(doc, tknOpeningTag.getOffset()); 275 int closingTagLine = Utilities.getLineOffset(doc, tknClosingTag.getOffset()); 276 277 if (closingTagLine == Utilities.getLineOffset(doc, dotPos)){ 278 279 if (openingTagLine == closingTagLine - 1){ 280 283 Position closingTagPos = doc.createPosition(getOpeningSymbolOffset(tknClosingTag)); 284 changeRowIndent(doc, dotPos, initialIndent + getShiftWidth()); 285 doc.insertString(closingTagPos.getOffset(), "\n", null); int newCaretPos = closingTagPos.getOffset() - 1; 287 changeRowIndent(doc, closingTagPos.getOffset() + 1, initialIndent); 288 newCaretPos = Utilities.getRowEnd(doc, newCaretPos); 289 txtComponent.setCaretPosition(newCaretPos); 290 } else{ 291 295 changeRowIndent(doc, dotPos, initialIndent); 296 } 297 } 298 } 299 300 int indent = initialIndent; 301 302 if (isClosingTagRequired(doc, extractTagName(tknOpeningTag))){ 303 indent += getShiftWidth(); 304 } 305 306 changeRowIndent(doc, dotPos, indent); 307 } 308 } else{ 309 int indent = initialIndent; 310 311 if (isJustBeforeClosingTag(doc, dotPos)){ 312 indent -= getShiftWidth(); 313 indent = indent < 0 ? 0 : indent; 314 } 315 316 changeRowIndent(doc, dotPos, indent); 318 } 319 } 320 321 @Override public int[] getReformatBlock(JTextComponent target, String typedText) { 322 BaseDocument doc = Utilities.getDocument(target); 323 324 if (!hasValidSyntaxSupport(doc)){ 325 return null; 326 } 327 328 char lastChar = typedText.charAt(typedText.length() - 1); 329 330 try{ 331 int dotPos = target.getCaret().getDot(); 332 333 if (lastChar == '>') { 334 TokenItem tknPrecedingToken = getTagTokenEndingAtPosition(doc, dotPos - 1); 335 336 if (isClosingTag(tknPrecedingToken)){ 337 340 TokenItem tknOpeningTag = getMatchingOpeningTag(tknPrecedingToken); 341 342 if (tknOpeningTag != null){ 343 int openingTagLine = Utilities.getLineOffset(doc, tknOpeningTag.getOffset()); 344 int closingTagSymbolLine = Utilities.getLineOffset(doc, dotPos); 345 346 if(openingTagLine != closingTagSymbolLine){ 347 return new int[]{tknPrecedingToken.getOffset(), dotPos}; 348 } 349 } 350 } 351 } 352 353 else if(lastChar == '\n') { 354 enterPressed(target, dotPos); 356 } 357 358 } catch (Exception e){ 359 ErrorManager.getDefault().notify(ErrorManager.WARNING, e); 360 } 361 362 return null; 363 } 364 365 protected TokenItem getMatchingOpeningTag(TokenItem tknClosingTag){ 366 String searchedTagName = extractTagName(tknClosingTag); 367 TokenItem token = tknClosingTag.getPrevious(); 368 int balance = 0; 369 370 while (token != null){ 371 if (areTagNamesEqual(searchedTagName, extractTagName(token))){ 372 if (isOpeningTag(token)){ 373 if (balance == 0){ 374 return token; 375 } 376 377 balance --; 378 } else if (isClosingTag(token)){ 379 balance ++; 380 } 381 } 382 383 token = token.getPrevious(); 384 } 385 386 return null; 387 } 388 389 protected int getInitialIndentFromPreviousLine(final BaseDocument doc, final int line) throws BadLocationException { 390 391 int initialIndent = 0; 393 394 if (line > 0){ 395 int lineStart = Utilities.getRowStartFromLineOffset(doc, line); 396 int previousNonWhiteLineEnd = Utilities.getFirstNonWhiteBwd(doc, lineStart); 397 398 if (previousNonWhiteLineEnd > 0){ 399 initialIndent = Utilities.getRowIndent(doc, previousNonWhiteLineEnd); 400 } 401 } 402 403 return initialIndent; 404 } 405 406 private int getInitialIndentFromNextLine(final BaseDocument doc, final int line) throws BadLocationException { 407 408 int initialIndent = 0; 410 411 int lineStart = Utilities.getRowStartFromLineOffset(doc, line); 412 int lineEnd = Utilities.getRowEnd(doc, lineStart); 413 int nextNonWhiteLineStart = Utilities.getFirstNonWhiteFwd(doc, lineEnd); 414 415 if (nextNonWhiteLineStart > 0){ 416 initialIndent = Utilities.getRowIndent(doc, nextNonWhiteLineStart, true); 417 } 418 419 return initialIndent; 420 } 421 422 private boolean hasValidSyntaxSupport(BaseDocument doc){ 423 ExtSyntaxSupport sup = getSyntaxSupport(doc); 424 425 if (sup == null){ 426 ErrorManager.getDefault().log(ErrorManager.WARNING, 427 "TagBasedFormatter: failed to retrieve SyntaxSupport for document;" + " probably attempt to use incompatible indentation engine"); 430 return false; 431 } 432 433 return true; 434 } 435 436 protected static int getNumberOfLines(BaseDocument doc) throws BadLocationException { 437 return Utilities.getLineOffset(doc, doc.getLength() - 1) + 1; 438 } 439 440 protected TokenItem getNextClosingTag(BaseDocument doc, int offset) throws BadLocationException { 441 ExtSyntaxSupport sup = getSyntaxSupport(doc); 442 TokenItem token = sup.getTokenChain(offset, offset + 1); 443 444 while (token != null){ 445 if (isClosingTag(token)){ 446 return token; 447 } 448 449 token = token.getNext(); 450 } 451 452 return null; 453 } 454 455 protected boolean isJustBeforeClosingTag(BaseDocument doc, int pos) throws BadLocationException { 456 ExtSyntaxSupport sup = getSyntaxSupport(doc); 457 TokenItem tknTag = sup.getTokenChain(pos, pos + 1); 458 459 if (isClosingTag(tknTag)){ 460 return true; 461 } 462 463 return false; 464 } 465 466 protected class InitialIndentData{ 467 private final int indentLevelBias; 468 private final int indentBias; 469 private final int indentLevels[]; 470 private final int indentsWithinTags[]; 471 472 public InitialIndentData(BaseDocument doc, int indentLevels[], int indentsWithinTags[], 473 int firstRefBlockLine, int lastRefBlockLine) throws BadLocationException { 474 475 int initialIndent = getInitialIndentFromPreviousLine(doc, firstRefBlockLine); 476 int indentLevelBiasFromTheTop = initialIndent / getShiftWidth() - (firstRefBlockLine > 0 ? indentLevels[firstRefBlockLine - 1] : 0); 477 478 int initialIndentFromTheBottom = getInitialIndentFromNextLine(doc, lastRefBlockLine); 479 int indentLevelBiasFromTheBottom = initialIndentFromTheBottom / getShiftWidth() - (lastRefBlockLine < getNumberOfLines(doc) - 1 ? indentLevels[lastRefBlockLine + 1] : 0); 480 481 if (indentLevelBiasFromTheBottom > indentLevelBiasFromTheTop){ 482 indentLevelBias = indentLevelBiasFromTheBottom; 483 initialIndent = initialIndentFromTheBottom; 484 } 485 else{ 486 indentLevelBias = indentLevelBiasFromTheTop; 487 } 488 489 indentBias = initialIndent % getShiftWidth(); 490 this.indentLevels = indentLevels; 491 this.indentsWithinTags = indentsWithinTags; 492 } 493 494 public boolean isEligibleToIndent(int line){ 495 return getActualIndentLevel(line) >= 0; 496 } 497 498 public int getIndent(int line){ 499 return indentBias + indentsWithinTags[line] + getActualIndentLevel(line) * getShiftWidth(); 500 } 501 502 private int getActualIndentLevel(int line){ 503 return indentLevels[line] + indentLevelBias; 504 } 505 } 506 507 protected static class TagIndentationData{ 508 private final String tagName; 509 private final int line; 510 private int closedOnLine; 511 512 public TagIndentationData(String tagName, int line){ 513 this.tagName = tagName; 514 this.line = line; 515 } 516 517 public String getTagName() { 518 return tagName; 519 } 520 521 public int getLine() { 522 return line; 523 } 524 525 public int getClosedOnLine() { 526 return closedOnLine; 527 } 528 529 public void setClosedOnLine(int closedOnLine) { 530 this.closedOnLine = closedOnLine; 531 } 532 } 533 } 534 | Popular Tags |