KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > armedbear > j > WrapText


1 /*
2  * WrapText.java
3  *
4  * Copyright (C) 1998-2004 Peter Graves
5  * $Id: WrapText.java,v 1.11 2004/09/20 00:13:44 piso Exp $
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */

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 JavaDoc;
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                 // Skip headers and header separator line.
65
while (line != null) {
66                     String JavaDoc 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 JavaDoc 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         // Don't try to wrap header lines in mail composition buffers!
109
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 JavaDoc 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 JavaDoc commentStart = null;
153         final Line dotLine = dot.getLine();
154         final String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc prefix)
209     {
210         try {
211             r.getBuffer().lockWrite();
212         }
213         catch (InterruptedException JavaDoc 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 JavaDoc 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 JavaDoc prefix, boolean wrap)
243     {
244         // Remember original contents of region.
245
final String JavaDoc 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         // Save undo information before detabbing region (which may also move
255
// dot).
256
CompoundEdit JavaDoc compoundEdit = new CompoundEdit JavaDoc();
257         compoundEdit.addEdit(new UndoMove(editor));
258         editor.setMark(null);
259
260         // Detab region (may move dot).
261
detab(r);
262
263         int savedOffset = buffer.getAbsoluteOffset(dot);
264
265         final String JavaDoc detabbed = r.toString();
266
267         // Working copy.
268
String JavaDoc s = detabbed;
269
270         // Remove trailing '\n'.
271
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             // If not specified, prefix is whitespace at start of first line.
280
for (int i = 0; i < s.length(); i++) {
281                 char c = s.charAt(i);
282                 if (c == '\t') {
283                     // String should be detabbed at this point.
284
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             // Replace prefix with spaces.
302
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                     // Line ends with '-'. We want to remove the '\n' and skip
326
// leading spaces on the next line. We can accomplish this
327
// by pretending the '-' is a space...
328
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                 // Not a space.
342
sb.append(c);
343                 lastChar = c;
344             }
345         }
346
347         savedOffset -= numSkipped;
348
349         final String JavaDoc unwrapped = sb.toString();
350
351         String JavaDoc 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 JavaDoc remaining = unwrapped;
361             int where = start;
362             int startCol = 0;
363             int adjust = 0;
364
365             // All the tabs have been replaced with spaces, so we can just use
366
// the length of the remaining string.
367
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             // No change. Restore status quo.
398
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 has not been modified.
408
buffer.setModCount(originalModificationCount);
409             return;
410         }
411
412         // Commit undo information.
413
buffer.addEdit(compoundEdit);
414
415         dot.moveTo(r.getBegin());
416         editor.addUndoDeleteRegion(r);
417         r.delete();
418         editor.addUndo(SimpleEdit.INSERT_STRING);
419
420         // This leaves dot at the end of the inserted string.
421
buffer.insertString(dot, toBeInserted);
422
423         // If we don't need to entab, we no longer need r. If we do need to
424
// entab, the bounds of the region may have changed, so we reconstruct
425
// it here.
426
r = buffer.getUseTabs() ? new Region(buffer, r.getBegin(), dot) : null;
427
428         // Move dot where it needs to go before entabbing, so the entabbing
429
// code can update it correctly.
430
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 JavaDoc 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 JavaDoc 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                 // Avoid end tags on the first pass.
470
if (i < s.length()-1 && (c = s.charAt(i+1)) != '/') {
471                     // Start tag.
472
breakOffset = i;
473                     // Avoid breaks within <a> tags if possible.
474
if (c == 'a' || c == 'A') {
475                         if (s.regionMatches(true, i, "<a ", 0, 3)) {
476                             // It's an <a> tag. Look for end tag.
477
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                                 // Don't break at the space after "<a".
483
i += 2;
484                             }
485                         }
486                     }
487                 }
488             }
489         }
490         if (breakOffset == 0 && isHtml) {
491             // No break found. Now we'll settle for an end tag.
492
for (int i = 0; i < limit; i++) {
493                 if (s.charAt(i) == '<')
494                     breakOffset = i;
495             }
496         }
497         if (breakOffset == 0) // No tabs or spaces fouund.
498
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 JavaDoc 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 JavaDoc s = line.trim().toLowerCase();
533             // Honor HTML breaks.
534
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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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; // Blank line (except for prefix string).
617
start = line;
618         }
619         return new Position(start, 0);
620     }
621
622     private static Position findEndOfQuotedText(Position pos, String JavaDoc 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; // Blank line (except for prefix string).
631
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         // Don't assume dot.line has been detabbed (dot might not be in the
652
// detabbed region).
653
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