1 21 22 package org.armedbear.j; 23 24 import gnu.regexp.RE; 25 import gnu.regexp.REMatch; 26 import gnu.regexp.UncheckedRE; 27 import javax.swing.undo.CompoundEdit ; 28 import org.armedbear.j.mail.SendMail; 29 30 public final class WrapText implements Constants 31 { 32 private final Editor editor; 33 private final Buffer buffer; 34 private Position dot; 35 private Position mark; 36 private final int wrapCol; 37 private final int tabWidth; 38 private final boolean isHtml; 39 40 public WrapText(Editor editor) 41 { 42 this.editor = editor; 43 buffer = editor.getBuffer(); 44 dot = editor.getDot(); 45 mark = editor.getMark(); 46 wrapCol = buffer.getIntegerProperty(Property.WRAP_COL); 47 tabWidth = buffer.getTabWidth(); 48 isHtml = buffer.getModeId() == HTML_MODE; 49 } 50 51 public void wrapRegion() 52 { 53 wrapRegion(new Region(buffer, dot, mark)); 54 } 55 56 public void wrapParagraphsInRegion() 57 { 58 final Region r; 59 if (dot != null && mark != null) { 60 r = new Region(buffer, dot, mark); 61 } else { 62 Line line = buffer.getFirstLine(); 63 if (buffer instanceof SendMail) { 64 while (line != null) { 66 String s = line.trim(); 67 line = line.next(); 68 if (s.equals(SendMail.getHeaderSeparator())) 69 break; 70 } 71 } 72 if (line == null) 73 return; 74 r = new Region(buffer, new Position(line, 0), buffer.getEnd()); 75 } 76 Position savedDot = dot.copy(); 77 boolean seenDot = false; 78 CompoundEdit compoundEdit = buffer.beginCompoundEdit(); 79 editor.moveDotTo(new Position(r.getBeginLine(), 0)); 80 while (true) { 81 while (dot.getLine().isBlank() && dot.getNextLine() != null) 82 dot.setLine(dot.getNextLine()); 83 Position start = dot.copy(); 84 Position end = findEndOfParagraph(dot); 85 if (!seenDot && !savedDot.isBefore(start) && savedDot.isBefore(end)) { 86 editor.moveDotTo(savedDot); 87 wrapParagraph(); 88 savedDot = dot.copy(); 89 seenDot = true; 90 } else 91 wrapParagraph(); 92 if (buffer.needsRenumbering()) 93 buffer.renumber(); 94 Position pos = findEndOfParagraph(dot); 95 if (!pos.getLine().isBefore(r.getEndLine())) 96 break; 97 if (pos.getLine() == dot.getLine()) 98 break; 99 editor.moveDotTo(pos); 100 } 101 if (buffer.contains(savedDot.getLine())) 102 editor.moveDotTo(savedDot); 103 buffer.endCompoundEdit(compoundEdit); 104 } 105 106 public void wrapLine() 107 { 108 if (buffer instanceof SendMail) 110 if (((SendMail)buffer).isHeaderLine(dot.getLine())) 111 return; 112 Position begin = new Position(dot.getLine(), 0); 113 Position end; 114 if (dot.getNextLine() != null) 115 end = new Position(dot.getNextLine(), 0); 116 else 117 end = new Position(dot.getLine(), dot.getLineLength()); 118 Region r = new Region(buffer, begin, end); 119 wrapRegion(r); 120 } 121 122 public void wrapParagraph() 123 { 124 String prefix = getPrefix(dot.getLine()); 125 final Position begin, end; 126 if (prefix != null) { 127 int prefixLength = prefix.length(); 128 begin = findStartOfQuotedText(dot, prefix, prefixLength); 129 end = findEndOfQuotedText(dot, prefix, prefixLength); 130 } else { 131 begin = findStartOfParagraph(dot); 132 end = findEndOfParagraph(dot); 133 } 134 if (begin != null && end != null) { 135 Region r = new Region(buffer, begin, end); 136 wrapRegion(r, prefix); 137 } 138 } 139 140 public void unwrapParagraph() 141 { 142 Position begin = findStartOfParagraph(dot); 143 Position end = findEndOfParagraph(dot); 144 if (begin != null && end != null) { 145 Region r = new Region(buffer, begin, end); 146 unwrapRegion(r); 147 } 148 } 149 150 private void wrapCommentInternal() 151 { 152 String commentStart = null; 153 final Line dotLine = dot.getLine(); 154 final String trim = dotLine.trim(); 155 156 switch (buffer.getModeId()) { 157 case JAVA_MODE: 158 case JAVASCRIPT_MODE: 159 case C_MODE: 160 case CPP_MODE: 161 case VERILOG_MODE: 162 if (trim.startsWith("// ")) 163 commentStart = "// "; 164 else if (trim.startsWith("* ")) 165 commentStart = "* "; 166 break; 167 case PERL_MODE: 168 case PROPERTIES_MODE: 169 if (trim.startsWith("# ")) 170 commentStart = "# "; 171 break; 172 default: 173 commentStart = buffer.getMode().getCommentStart(); 174 break; 175 } 176 177 if (commentStart != null) { 178 int index = dotLine.getText().indexOf(commentStart); 179 if (index < 0) 180 return; 181 String prefix = 182 dotLine.getText().substring(0, index + commentStart.length()); 183 Position begin = findStartOfComment(dot, commentStart); 184 Position end = findEndOfComment(dot, commentStart); 185 if (begin != null && end != null) { 186 try { 187 buffer.lockWrite(); 188 } 189 catch (InterruptedException e) { 190 Log.error(e); 191 return; 192 } 193 try { 194 processRegion(new Region(buffer, begin, end), prefix, true); 195 } 196 finally { 197 buffer.unlockWrite(); 198 } 199 } 200 } 201 } 202 203 private void wrapRegion(Region r) 204 { 205 wrapRegion(r, null); 206 } 207 208 private void wrapRegion(Region r, String prefix) 209 { 210 try { 211 r.getBuffer().lockWrite(); 212 } 213 catch (InterruptedException e) { 214 Log.error(e); 215 return; 216 } 217 try { 218 processRegion(r, prefix, true); 219 } 220 finally { 221 r.getBuffer().unlockWrite(); 222 } 223 } 224 225 private void unwrapRegion(Region r) 226 { 227 try { 228 r.getBuffer().lockWrite(); 229 } 230 catch (InterruptedException e) { 231 Log.error(e); 232 return; 233 } 234 try { 235 processRegion(r, null, false); 236 } 237 finally { 238 r.getBuffer().unlockWrite(); 239 } 240 } 241 242 private void processRegion(Region r, String prefix, boolean wrap) 243 { 244 final String before = r.toString(); 246 247 if (before.length() == 0) 248 return; 249 250 int offsetBefore = buffer.getAbsoluteOffset(dot); 251 252 int originalModificationCount = buffer.getModCount(); 253 254 CompoundEdit compoundEdit = new CompoundEdit (); 257 compoundEdit.addEdit(new UndoMove(editor)); 258 editor.setMark(null); 259 260 detab(r); 262 263 int savedOffset = buffer.getAbsoluteOffset(dot); 264 265 final String detabbed = r.toString(); 266 267 String s = detabbed; 269 270 if (s.charAt(s.length() - 1) == '\n') 272 s = s.substring(0, s.length() - 1); 273 274 FastStringBuffer sb = new FastStringBuffer(); 275 276 if (prefix != null) { 277 prefix = Utilities.detab(prefix, tabWidth); 278 } else { 279 for (int i = 0; i < s.length(); i++) { 281 char c = s.charAt(i); 282 if (c == '\t') { 283 Log.error("tab found unexpectedly at offset " + i); 285 Debug.assertTrue(false); 286 } 287 if (c == ' ') 288 sb.append(c); 289 else 290 break; 291 } 292 prefix = sb.toString(); 293 } 294 295 final int prefixLength = prefix.length(); 296 297 if (prefixLength > 0) 298 s = s.substring(prefixLength); 299 300 if (!Utilities.isWhitespace(prefix)) { 301 sb.setLength(0); 303 int index; 304 int begin = 0; 305 while ((index = s.indexOf("\n" + prefix, begin)) >= 0) { 306 sb.append(s.substring(begin, index)); 307 sb.append('\n'); 308 sb.append(Utilities.spaces(prefixLength)); 309 begin = index + prefixLength + 1; 310 } 311 sb.append(s.substring(begin)); 312 s = sb.toString(); 313 } 314 315 sb.setLength(0); 316 sb.append(prefix); 317 final int start = buffer.getAbsoluteOffset(r.getBegin()); 318 char lastChar = '\0'; 319 int numSkipped = 0; 320 final int limit = s.length(); 321 for (int i = 0; i < limit; i++) { 322 char c = s.charAt(i); 323 if (c == '\n') { 324 if (lastChar == '-') { 325 lastChar = ' '; 329 } 330 c = ' '; 331 } 332 if (c == ' ') { 333 if (lastChar == ' ') { 334 if (start + prefixLength + i <= savedOffset) 335 ++numSkipped; 336 } else { 337 sb.append(c); 338 lastChar = c; 339 } 340 } else { 341 sb.append(c); 343 lastChar = c; 344 } 345 } 346 347 savedOffset -= numSkipped; 348 349 final String unwrapped = sb.toString(); 350 351 String toBeInserted = null; 352 353 if (wrap) { 354 if (getCol(prefix, prefixLength) >= wrapCol) { 355 editor.status("Can't wrap (indentation extends beyond wrap column)"); 356 return; 357 } 358 359 sb.setLength(0); 360 String remaining = unwrapped; 361 int where = start; 362 int startCol = 0; 363 int adjust = 0; 364 365 while (remaining.length() > wrapCol - startCol) { 368 int breakOffset = findBreak(remaining, wrapCol - startCol); 369 sb.append(remaining.substring(0, breakOffset)); 370 where += breakOffset; 371 sb.append('\n'); 372 if (where <= savedOffset) 373 ++adjust; 374 remaining = remaining.substring(breakOffset); 375 if (remaining.length() > 0 && remaining.charAt(0) == ' ') { 376 remaining = remaining.substring(1); 377 if (where <= savedOffset) 378 --adjust; 379 ++where; 380 } 381 if (prefix.length() > 0) { 382 sb.append(prefix); 383 if (where <= savedOffset) 384 adjust += prefixLength; 385 startCol = getCol(prefix, prefixLength); 386 } 387 } 388 sb.append(remaining); 389 toBeInserted = sb.toString(); 390 savedOffset += adjust; 391 } else 392 toBeInserted = unwrapped; 393 394 toBeInserted += "\n"; 395 396 if (toBeInserted.equals(detabbed)) { 397 dot.moveTo(r.getBegin()); 399 r.delete(); 400 buffer.insertString(dot, before); 401 Position pos = buffer.getPosition(offsetBefore); 402 Debug.assertTrue(pos != null); 403 if (pos != null) 404 editor.getDot().moveTo(pos); 405 editor.getDisplay().setShift(0); 406 407 buffer.setModCount(originalModificationCount); 409 return; 410 } 411 412 buffer.addEdit(compoundEdit); 414 415 dot.moveTo(r.getBegin()); 416 editor.addUndoDeleteRegion(r); 417 r.delete(); 418 editor.addUndo(SimpleEdit.INSERT_STRING); 419 420 buffer.insertString(dot, toBeInserted); 422 423 r = buffer.getUseTabs() ? new Region(buffer, r.getBegin(), dot) : null; 427 428 Position pos = buffer.getPosition(savedOffset); 431 Debug.assertTrue(pos != null); 432 if (pos != null) 433 editor.moveDotTo(pos); 434 435 if (r != null) 436 entab(r); 437 438 editor.getDisplay().setShift(0); 439 editor.moveCaretToDotCol(); 440 buffer.endCompoundEdit(compoundEdit); 441 } 442 443 private int getCol(String s, int offset) 444 { 445 if (offset > s.length()) 446 offset = s.length(); 447 int col = 0; 448 for (int i = 0; i < offset; i++) { 449 if (s.charAt(i) == '\t') 450 col += tabWidth - col % tabWidth; 451 else 452 ++col; 453 } 454 return col; 455 } 456 457 private int findBreak(String s, int maxLength) 458 { 459 int breakOffset = 0; 460 final int limit = Math.min(s.length(), maxLength); 461 for (int i = 0; i < limit; i++) { 462 char c = s.charAt(i); 463 if (c == ' ') 464 breakOffset = i; 465 else if (c == '-') { 466 if (i < limit-1) 467 breakOffset = i+1; 468 } else if (isHtml && c == '<') { 469 if (i < s.length()-1 && (c = s.charAt(i+1)) != '/') { 471 breakOffset = i; 473 if (c == 'a' || c == 'A') { 475 if (s.regionMatches(true, i, "<a ", 0, 3)) { 476 int index = s.toLowerCase().indexOf("</a>", i+3); 478 if (index >= 0 && index+4 < limit) { 479 breakOffset = index+4; 480 i = breakOffset; 481 } else { 482 i += 2; 484 } 485 } 486 } 487 } 488 } 489 } 490 if (breakOffset == 0 && isHtml) { 491 for (int i = 0; i < limit; i++) { 493 if (s.charAt(i) == '<') 494 breakOffset = i; 495 } 496 } 497 if (breakOffset == 0) breakOffset = maxLength; 499 return breakOffset; 500 } 501 502 private static Position findStartOfParagraph(Position startingPoint) 503 { 504 Position pos = new Position(startingPoint); 505 while (true) { 506 Line previousLine = pos.getPreviousLine(); 507 if (previousLine == null || previousLine.isBlank()) 508 break; 509 String s = previousLine.trim(); 510 if (s.equals(SendMail.getHeaderSeparator())) 511 break; 512 if (s.endsWith(">")) 513 break; 514 pos.setLine(previousLine); 515 s = s.toLowerCase(); 516 if (s.startsWith("<p>") || s.startsWith("<br>")) 517 break; 518 } 519 pos.setOffset(0); 520 return pos; 521 } 522 523 private static Position findEndOfParagraph(Position startingPoint) 524 { 525 Line line = startingPoint.getLine(); 526 while (true) { 527 if (line.next() == null) 528 return new Position(line, line.length()); 529 line = line.next(); 530 if (line.isBlank()) 531 return new Position(line, 0); 532 String s = line.trim().toLowerCase(); 533 if (s.startsWith("<p>") || 535 s.startsWith("</p>") || 536 s.startsWith("<br>") || 537 s.startsWith("<li>") || 538 s.startsWith("</li>") || 539 s.startsWith("<dl>") || 540 s.startsWith("</dl>") || 541 s.startsWith("</body>") || 542 s.startsWith("<pre>")) { 543 return new Position(line, 0); 544 } 545 if (s.endsWith("<p>") || 546 s.endsWith("</p>") || 547 s.endsWith("<br>") || 548 s.endsWith("</li>")) { 549 if (line.next() != null) 550 return new Position(line.next(), 0); 551 return new Position(line, line.length()); 552 } 553 } 554 } 555 556 private static Position findStartOfComment(Position startingPoint, String commentStart) 557 { 558 Line beginLine = null; 559 for (Line line = startingPoint.getLine(); line != null; line = line.previous()) { 560 if (!line.trim().startsWith(commentStart)) 561 break; 562 int index = line.getText().indexOf(commentStart); 563 String remaining = line.substring(index + commentStart.length()).trim(); 564 if (remaining.endsWith("</pre>")) 565 break; 566 if (remaining.endsWith("<p>")) 567 break; 568 if (remaining.startsWith("<p>")) { 569 beginLine = line; 570 break; 571 } 572 beginLine = line; 573 } 574 if (beginLine != null) 575 return new Position(beginLine, 0); 576 return null; 577 } 578 579 private static Position findEndOfComment(Position startingPoint, String commentStart) 580 { 581 Line endLine = null; 582 for (Line line = startingPoint.getLine(); line != null; line = line.next()) { 583 if (!line.trim().startsWith(commentStart)) 584 break; 585 int index = line.getText().indexOf(commentStart); 586 String remaining = line.substring(index + commentStart.length()).trim(); 587 if (remaining.startsWith("<p>")) 588 break; 589 if (remaining.startsWith("<pre>")) 590 break; 591 endLine = line; 592 } 593 if (endLine == null) 594 return null; 595 if (endLine.next() != null) 596 return new Position(endLine.next(), 0); 597 return new Position(endLine, endLine.length()); 598 } 599 600 private static final RE prefixRE = new UncheckedRE("^>[> ]*"); 601 602 private static String getPrefix(Line line) 603 { 604 REMatch match = prefixRE.getMatch(line.getText()); 605 return match != null ? match.toString() : null; 606 } 607 608 private static Position findStartOfQuotedText(Position pos, String prefix, 609 int prefixLength) 610 { 611 Line start = pos.getLine(); 612 for (Line line = start.previous(); line != null; line = line.previous()) { 613 if (!prefix.equals(getPrefix(line))) 614 break; 615 if (line.substring(prefixLength).trim().length() == 0) 616 break; start = line; 618 } 619 return new Position(start, 0); 620 } 621 622 private static Position findEndOfQuotedText(Position pos, String prefix, 623 int prefixLength) 624 { 625 Line end = pos.getLine(); 626 for (Line line = end.next(); line != null; line = line.next()) { 627 if (!prefix.equals(getPrefix(line))) 628 break; 629 if (line.substring(prefixLength).trim().length() == 0) 630 break; end = line; 632 } 633 if (end.next() != null) 634 return new Position(end.next(), 0); 635 return new Position(end, end.length()); 636 } 637 638 private void detab(Region r) 639 { 640 Debug.assertTrue(r.getBeginOffset() == 0); 641 Debug.assertTrue(r.getEndOffset() == 0 || r.getEndOffset() == r.getEndLine().length()); 642 Debug.assertTrue(buffer == r.getBuffer()); 643 int dotCol = buffer.getCol(dot); 644 Line line = r.getBeginLine(); 645 while (line != null && line != r.getEndLine()) { 646 line.setText(Utilities.detab(line.getText(), tabWidth)); 647 line = line.next(); 648 } 649 if (line == r.getEndLine() && r.getEndOffset() == r.getEndLine().length()) 650 line.setText(Utilities.detab(line.getText(), tabWidth)); 651 dot.moveToCol(dotCol, tabWidth); 654 } 655 656 private void entab(Region r) 657 { 658 Debug.assertTrue(r.getBeginOffset() == 0); 659 Debug.assertTrue(r.getEndOffset() == 0 || 660 r.getEndOffset() == r.getEndLine().length()); 661 Debug.assertTrue(buffer == r.getBuffer()); 662 int dotCol = buffer.getCol(dot); 663 Line line = r.getBeginLine(); 664 while (line != null && line != r.getEndLine()) { 665 line.setText(Utilities.entab(line.getText(), tabWidth)); 666 line = line.next(); 667 } 668 if (line == r.getEndLine() && r.getEndOffset() == r.getEndLine().length()) 669 line.setText(Utilities.entab(line.getText(), tabWidth)); 670 dot.moveToCol(dotCol, tabWidth); 671 } 672 673 public static void wrapComment() 674 { 675 final Editor editor = Editor.currentEditor(); 676 if (!editor.checkReadOnly()) 677 return; 678 new WrapText(editor).wrapCommentInternal(); 679 } 680 681 public static void toggleWrap() 682 { 683 final Buffer buffer = Editor.currentEditor().getBuffer(); 684 boolean b = !buffer.getBooleanProperty(Property.WRAP); 685 buffer.setProperty(Property.WRAP, b); 686 buffer.saveProperties(); 687 } 688 } 689 | Popular Tags |