1995a11ea44426a9b42a15ef2b347d6ffe4cdfe7
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpdt / internal / ui / text / phpdoc / JavaDocAutoIndentStrategy.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials 
4  * are made available under the terms of the Common Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/cpl-v10.html
7  * 
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11
12 package net.sourceforge.phpdt.internal.ui.text.phpdoc;
13
14 import java.text.BreakIterator;
15
16 import net.sourceforge.phpdt.core.ICompilationUnit;
17 //import net.sourceforge.phpdt.core.IJavaElement;
18 import net.sourceforge.phpdt.core.IMethod;
19 import net.sourceforge.phpdt.core.ISourceRange;
20 import net.sourceforge.phpdt.core.IType;
21 import net.sourceforge.phpdt.internal.corext.util.Strings;
22 import net.sourceforge.phpdt.ui.CodeGeneration;
23 import net.sourceforge.phpdt.ui.IWorkingCopyManager;
24 import net.sourceforge.phpdt.ui.PreferenceConstants;
25 //import net.sourceforge.phpeclipse.PHPeclipsePlugin;
26 import net.sourceforge.phpeclipse.ui.WebUI;
27
28 import org.eclipse.core.runtime.CoreException;
29 import org.eclipse.core.runtime.Preferences;
30 import org.eclipse.jface.preference.IPreferenceStore;
31 import org.eclipse.jface.text.BadLocationException;
32 import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
33 import org.eclipse.jface.text.DocumentCommand;
34 import org.eclipse.jface.text.IDocument;
35 import org.eclipse.jface.text.IRegion;
36 import org.eclipse.jface.text.ITypedRegion;
37 import org.eclipse.jface.text.TextUtilities;
38 import org.eclipse.ui.IEditorPart;
39 import org.eclipse.ui.IWorkbenchPage;
40 import org.eclipse.ui.IWorkbenchWindow;
41 import org.eclipse.ui.PlatformUI;
42 import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants;
43 import org.eclipse.ui.texteditor.ITextEditorExtension3;
44
45 /**
46  * Auto indent strategy for java doc comments
47  */
48 public class JavaDocAutoIndentStrategy extends
49                 DefaultIndentLineAutoEditStrategy {
50
51         private String fPartitioning;
52
53         /**
54          * Creates a new Javadoc auto indent strategy for the given document
55          * partitioning.
56          * 
57          * @param partitioning
58          *            the document partitioning
59          */
60         public JavaDocAutoIndentStrategy(String partitioning) {
61                 fPartitioning = partitioning;
62         }
63
64         private static String getLineDelimiter(IDocument document) {
65                 try {
66                         if (document.getNumberOfLines() > 1)
67                                 return document.getLineDelimiter(0);
68                 } catch (BadLocationException e) {
69                         WebUI.log(e);
70                 }
71
72                 return System.getProperty("line.separator"); //$NON-NLS-1$
73         }
74
75         /**
76          * Copies the indentation of the previous line and add a star. If the
77          * javadoc just started on this line add standard method tags and close the
78          * javadoc.
79          * 
80          * @param d
81          *            the document to work on
82          * @param c
83          *            the command to deal with
84          */
85         private void jdocIndentAfterNewLine(IDocument d, DocumentCommand c) {
86
87                 if (c.offset == -1 || d.getLength() == 0)
88                         return;
89
90                 try {
91                         // find start of line
92                         int p = (c.offset == d.getLength() ? c.offset - 1 : c.offset);
93                         IRegion info = d.getLineInformationOfOffset(p);
94                         int start = info.getOffset();
95
96                         // find white spaces
97                         int end = findEndOfWhiteSpace(d, start, c.offset);
98
99                         StringBuffer buf = new StringBuffer(c.text);
100                         if (end >= start) { // 1GEYL1R: ITPJUI:ALL - java doc edit smartness
101                                                                 // not work for class comments
102                                 // append to input
103                                 String indentation = jdocExtractLinePrefix(d, d
104                                                 .getLineOfOffset(c.offset));
105                                 buf.append(indentation);
106                                 if (end < c.offset) {
107                                         if (d.getChar(end) == '/') {
108                                                 // javadoc started on this line
109                                                 buf.append(" * "); //$NON-NLS-1$
110
111                                                 if (WebUI
112                                                                 .getDefault()
113                                                                 .getPreferenceStore()
114                                                                 .getBoolean(
115                                                                                 PreferenceConstants.EDITOR_CLOSE_JAVADOCS)
116                                                                 && isNewComment(d, c.offset, fPartitioning)) {
117                                                         String lineDelimiter = getLineDelimiter(d);
118
119                                                         String endTag = lineDelimiter + indentation + " */"; //$NON-NLS-1$
120                                                         d.replace(c.offset, 0, endTag); //$NON-NLS-1$
121                                                         // evaluate method signature
122                                                         ICompilationUnit unit = getCompilationUnit();
123
124                                                         // if
125                                                         // (PHPeclipsePlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_ADD_JAVADOC_TAGS)
126                                                         // &&
127                                                         // unit != null)
128                                                         // {
129                                                         // try {
130                                                         // JavaModelUtil.reconcile(unit);
131                                                         // String string= createJavaDocTags(d, c,
132                                                         // indentation, lineDelimiter, unit);
133                                                         // if (string != null) {
134                                                         // d.replace(c.offset, 0, string);
135                                                         // }
136                                                         // } catch (CoreException e) {
137                                                         // // ignore
138                                                         // }
139                                                         // }
140                                                 }
141
142                                         }
143                                 }
144                         }
145
146                         c.text = buf.toString();
147
148                 } catch (BadLocationException excp) {
149                         // stop work
150                 }
151         }
152
153 //      private String createJavaDocTags(IDocument document,
154 //                      DocumentCommand command, String indentation, String lineDelimiter,
155 //                      ICompilationUnit unit) throws CoreException, BadLocationException {
156 //              IJavaElement element = unit.getElementAt(command.offset);
157 //              if (element == null)
158 //                      return null;
159 //
160 //              switch (element.getElementType()) {
161 //              case IJavaElement.TYPE:
162 //                      return createTypeTags(document, command, indentation,
163 //                                      lineDelimiter, (IType) element);
164 //
165 //              case IJavaElement.METHOD:
166 //                      return createMethodTags(document, command, indentation,
167 //                                      lineDelimiter, (IMethod) element);
168 //
169 //              default:
170 //                      return null;
171 //              }
172 //      }
173
174         /*
175          * Removes start and end of a comment and corrects indentation and line
176          * delimiters.
177          */
178         private String prepareTemplateComment(String comment, String indentation,
179                         String lineDelimiter) {
180                 // trim comment start and end if any
181                 if (comment.endsWith("*/")) //$NON-NLS-1$
182                         comment = comment.substring(0, comment.length() - 2);
183                 comment = comment.trim();
184                 if (comment.startsWith("/*")) { //$NON-NLS-1$
185                         if (comment.length() > 2 && comment.charAt(2) == '*') {
186                                 comment = comment.substring(3); // remove '/**'
187                         } else {
188                                 comment = comment.substring(2); // remove '/*'
189                         }
190                 }
191                 // return Strings.changeIndent(comment, 0,
192                 // CodeFormatterUtil.getTabWidth(), indentation, lineDelimiter);
193                 return Strings.changeIndent(comment, 0, getTabWidth(), indentation,
194                                 lineDelimiter);
195         }
196
197         public static int getTabWidth() {
198                 Preferences preferences = WebUI.getDefault()
199                                 .getPluginPreferences();
200                 return preferences
201                                 .getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
202         }
203
204         private String createTypeTags(IDocument document, DocumentCommand command,
205                         String indentation, String lineDelimiter, IType type)
206                         throws CoreException {
207                 String comment = CodeGeneration.getTypeComment(type
208                                 .getCompilationUnit(), type.getTypeQualifiedName('.'),
209                                 lineDelimiter);
210                 if (comment != null) {
211                         return prepareTemplateComment(comment.trim(), indentation,
212                                         lineDelimiter);
213                 }
214                 return null;
215         }
216
217         private String createMethodTags(IDocument document,
218                         DocumentCommand command, String indentation, String lineDelimiter,
219                         IMethod method) throws CoreException, BadLocationException {
220                 IRegion partition = TextUtilities.getPartition(document, fPartitioning,
221                                 command.offset, false);
222                 ISourceRange sourceRange = method.getSourceRange();
223                 if (sourceRange == null
224                                 || sourceRange.getOffset() != partition.getOffset())
225                         return null;
226
227                 // IMethod inheritedMethod= getInheritedMethod(method);
228                 // String comment= CodeGeneration.getMethodComment(method,
229                 // inheritedMethod, lineDelimiter);
230                 // if (comment != null) {
231                 // comment= comment.trim();
232                 // boolean javadocComment= comment.startsWith("/**"); //$NON-NLS-1$
233                 // boolean isJavaDoc= partition.getLength() >= 3 &&
234                 // document.get(partition.getOffset(), 3).equals("/**"); //$NON-NLS-1$
235                 // if (javadocComment == isJavaDoc) {
236                 // return prepareTemplateComment(comment, indentation, lineDelimiter);
237                 // }
238                 // }
239                 return null;
240         }
241
242         /**
243          * Returns the method inherited from, <code>null</code> if method is newly
244          * defined.
245          */
246         // private static IMethod getInheritedMethod(IMethod method) throws
247         // JavaModelException {
248         // IType declaringType= method.getDeclaringType();
249         // ITypeHierarchy typeHierarchy=
250         // SuperTypeHierarchyCache.getTypeHierarchy(declaringType);
251         // return JavaModelUtil.findMethodDeclarationInHierarchy(typeHierarchy,
252         // declaringType,
253         // method.getElementName(), method.getParameterTypes(),
254         // method.isConstructor());
255         // }
256         protected void jdocIndentForCommentEnd(IDocument d, DocumentCommand c) {
257                 if (c.offset < 2 || d.getLength() == 0) {
258                         return;
259                 }
260                 try {
261                         if ("* ".equals(d.get(c.offset - 2, 2))) { //$NON-NLS-1$
262                                 // modify document command
263                                 c.length++;
264                                 c.offset--;
265                         }
266                 } catch (BadLocationException excp) {
267                         // stop work
268                 }
269         }
270
271         /**
272          * Guesses if the command operates within a newly created javadoc comment or
273          * not. If in doubt, it will assume that the javadoc is new.
274          */
275         private static boolean isNewComment(IDocument document, int commandOffset,
276                         String partitioning) {
277
278                 try {
279                         int lineIndex = document.getLineOfOffset(commandOffset) + 1;
280                         if (lineIndex >= document.getNumberOfLines())
281                                 return true;
282
283                         IRegion line = document.getLineInformation(lineIndex);
284                         ITypedRegion partition = TextUtilities.getPartition(document,
285                                         partitioning, commandOffset, false);
286                         int partitionEnd = partition.getOffset() + partition.getLength();
287                         if (line.getOffset() >= partitionEnd)
288                                 return false;
289
290                         if (document.getLength() == partitionEnd)
291                                 return true; // partition goes to end of document - probably
292                                                                 // a new comment
293
294                         String comment = document.get(partition.getOffset(), partition
295                                         .getLength());
296                         if (comment.indexOf("/*", 2) != -1) //$NON-NLS-1$
297                                 return true; // enclosed another comment -> probably a new
298                                                                 // comment
299
300                         return false;
301
302                 } catch (BadLocationException e) {
303                         return false;
304                 }
305         }
306
307         private boolean isSmartMode() {
308                 IWorkbenchPage page = WebUI.getActivePage();
309                 if (page != null) {
310                         IEditorPart part = page.getActiveEditor();
311                         if (part instanceof ITextEditorExtension3) {
312                                 ITextEditorExtension3 extension = (ITextEditorExtension3) part;
313                                 return extension.getInsertMode() == ITextEditorExtension3.SMART_INSERT;
314                         }
315                 }
316                 return false;
317         }
318
319         /*
320          * @see IAutoIndentStrategy#customizeDocumentCommand
321          */
322         public void customizeDocumentCommand(IDocument document,
323                         DocumentCommand command) {
324
325                 if (!isSmartMode())
326                         return;
327
328                 try {
329
330                         if (command.text != null && command.length == 0) {
331                                 String[] lineDelimiters = document.getLegalLineDelimiters();
332                                 int index = TextUtilities
333                                                 .endsWith(lineDelimiters, command.text);
334                                 if (index > -1) {
335                                         // ends with line delimiter
336                                         if (lineDelimiters[index].equals(command.text))
337                                                 // just the line delimiter
338                                                 jdocIndentAfterNewLine(document, command);
339                                         return;
340                                 }
341                         }
342
343                         if (command.text != null && command.text.equals("/")) { //$NON-NLS-1$
344                                 jdocIndentForCommentEnd(document, command);
345                                 return;
346                         }
347
348                         ITypedRegion partition = TextUtilities.getPartition(document,
349                                         fPartitioning, command.offset, true);
350                         int partitionStart = partition.getOffset();
351                         int partitionEnd = partition.getLength() + partitionStart;
352
353                         String text = command.text;
354                         int offset = command.offset;
355                         int length = command.length;
356
357                         // partition change
358                         final int PREFIX_LENGTH = "/*".length(); //$NON-NLS-1$
359                         final int POSTFIX_LENGTH = "*/".length(); //$NON-NLS-1$
360                         if ((offset < partitionStart + PREFIX_LENGTH || offset + length > partitionEnd
361                                         - POSTFIX_LENGTH)
362                                         || text != null
363                                         && text.length() >= 2
364                                         && ((text.indexOf("*/") != -1) || (document.getChar(offset) == '*' && text.startsWith("/")))) //$NON-NLS-1$ //$NON-NLS-2$
365                                 return;
366
367                         if (command.text == null || command.text.length() == 0)
368                                 jdocHandleBackspaceDelete(document, command);
369
370                         else if (command.text != null && command.length == 0
371                                         && command.text.length() > 0)
372                                 jdocWrapParagraphOnInsert(document, command);
373
374                 } catch (BadLocationException e) {
375                         WebUI.log(e);
376                 }
377         }
378
379         private void flushCommand(IDocument document, DocumentCommand command)
380                         throws BadLocationException {
381
382                 if (!command.doit)
383                         return;
384
385                 document.replace(command.offset, command.length, command.text);
386
387                 command.doit = false;
388                 if (command.text != null)
389                         command.offset += command.text.length();
390                 command.length = 0;
391                 command.text = null;
392         }
393
394         protected void jdocWrapParagraphOnInsert(IDocument document,
395                         DocumentCommand command) throws BadLocationException {
396
397                 // Assert.isTrue(command.length == 0);
398                 // Assert.isTrue(command.text != null && command.text.length() == 1);
399
400                 if (!getPreferenceStore().getBoolean(
401                                 PreferenceConstants.EDITOR_FORMAT_JAVADOCS))
402                         return;
403
404                 int line = document.getLineOfOffset(command.offset);
405                 IRegion region = document.getLineInformation(line);
406                 int lineOffset = region.getOffset();
407                 int lineLength = region.getLength();
408
409                 String lineContents = document.get(lineOffset, lineLength);
410                 StringBuffer buffer = new StringBuffer(lineContents);
411                 int start = command.offset - lineOffset;
412                 int end = command.length + start;
413                 buffer.replace(start, end, command.text);
414
415                 // handle whitespace
416                 if (command.text != null && command.text.length() != 0
417                                 && command.text.trim().length() == 0) {
418
419                         String endOfLine = document.get(command.offset, lineOffset
420                                         + lineLength - command.offset);
421
422                         // end of line
423                         if (endOfLine.length() == 0) {
424                                 // move caret to next line
425                                 flushCommand(document, command);
426
427                                 if (isLineTooShort(document, line)) {
428                                         int[] caretOffset = { command.offset };
429                                         jdocWrapParagraphFromLine(document, line, caretOffset,
430                                                         false);
431                                         command.offset = caretOffset[0];
432                                         return;
433                                 }
434
435                                 // move caret to next line if possible
436                                 if (line < document.getNumberOfLines() - 1
437                                                 && isJavaDocLine(document, line + 1)) {
438                                         String lineDelimiter = document.getLineDelimiter(line);
439                                         String nextLinePrefix = jdocExtractLinePrefix(document,
440                                                         line + 1);
441                                         command.offset += lineDelimiter.length()
442                                                         + nextLinePrefix.length();
443                                 }
444                                 return;
445
446                                 // inside whitespace at end of line
447                         } else if (endOfLine.trim().length() == 0) {
448                                 // simply insert space
449                                 return;
450                         }
451                 }
452
453                 // change in prefix region
454                 String prefix = jdocExtractLinePrefix(document, line);
455                 boolean wrapAlways = command.offset >= lineOffset
456                                 && command.offset <= lineOffset + prefix.length();
457
458                 // must insert the text now because it may include whitepace
459                 flushCommand(document, command);
460
461                 if (wrapAlways
462                                 || calculateDisplayedWidth(buffer.toString()) > getMargin()
463                                 || isLineTooShort(document, line)) {
464                         int[] caretOffset = { command.offset };
465                         jdocWrapParagraphFromLine(document, line, caretOffset, wrapAlways);
466
467                         if (!wrapAlways)
468                                 command.offset = caretOffset[0];
469                 }
470         }
471
472         /**
473          * Method jdocWrapParagraphFromLine.
474          * 
475          * @param document
476          * @param line
477          * @param always
478          */
479         private void jdocWrapParagraphFromLine(IDocument document, int line,
480                         int[] caretOffset, boolean always) throws BadLocationException {
481
482                 String indent = jdocExtractLinePrefix(document, line);
483                 if (!always) {
484                         if (!indent.trim().startsWith("*")) //$NON-NLS-1$
485                                 return;
486
487                         if (indent.trim().startsWith("*/")) //$NON-NLS-1$
488                                 return;
489
490                         if (!isLineTooLong(document, line)
491                                         && !isLineTooShort(document, line))
492                                 return;
493                 }
494
495                 boolean caretRelativeToParagraphOffset = false;
496                 int caret = caretOffset[0];
497
498                 int caretLine = document.getLineOfOffset(caret);
499                 int lineOffset = document.getLineOffset(line);
500                 int paragraphOffset = lineOffset + indent.length();
501                 if (paragraphOffset < caret) {
502                         caret -= paragraphOffset;
503                         caretRelativeToParagraphOffset = true;
504                 } else {
505                         caret -= lineOffset;
506                 }
507
508                 StringBuffer buffer = new StringBuffer();
509                 int currentLine = line;
510                 while (line == currentLine || isJavaDocLine(document, currentLine)) {
511
512                         if (buffer.length() != 0
513                                         && !Character.isWhitespace(buffer
514                                                         .charAt(buffer.length() - 1))) {
515                                 buffer.append(' ');
516                                 if (currentLine <= caretLine) {
517                                         // in this case caretRelativeToParagraphOffset is always
518                                         // true
519                                         ++caret;
520                                 }
521                         }
522
523                         String string = getLineContents(document, currentLine);
524                         buffer.append(string);
525                         currentLine++;
526                 }
527                 String paragraph = buffer.toString();
528
529                 if (paragraph.trim().length() == 0)
530                         return;
531
532                 caretOffset[0] = caretRelativeToParagraphOffset ? caret : 0;
533                 String delimiter = document.getLineDelimiter(0);
534                 String wrapped = formatParagraph(paragraph, caretOffset, indent,
535                                 delimiter, getMargin());
536
537                 int beginning = document.getLineOffset(line);
538                 int end = document.getLineOffset(currentLine);
539                 document.replace(beginning, end - beginning, wrapped.toString());
540
541                 caretOffset[0] = caretRelativeToParagraphOffset ? caretOffset[0]
542                                 + beginning : caret + beginning;
543         }
544
545         /**
546          * Line break iterator to handle whitespaces as first class citizens.
547          */
548         private static class LineBreakIterator {
549
550                 private final String fString;
551
552                 private final BreakIterator fIterator = BreakIterator.getLineInstance();
553
554                 private int fStart;
555
556                 private int fEnd;
557
558                 private int fBufferedEnd;
559
560                 public LineBreakIterator(String string) {
561                         fString = string;
562                         fIterator.setText(string);
563                 }
564
565                 public int first() {
566                         fBufferedEnd = -1;
567                         fStart = fIterator.first();
568                         return fStart;
569                 }
570
571                 public int next() {
572
573                         if (fBufferedEnd != -1) {
574                                 fStart = fEnd;
575                                 fEnd = fBufferedEnd;
576                                 fBufferedEnd = -1;
577                                 return fEnd;
578                         }
579
580                         fStart = fEnd;
581                         fEnd = fIterator.next();
582
583                         if (fEnd == BreakIterator.DONE)
584                                 return fEnd;
585
586                         final String string = fString.substring(fStart, fEnd);
587
588                         // whitespace
589                         if (string.trim().length() == 0)
590                                 return fEnd;
591
592                         final String word = string.trim();
593                         if (word.length() == string.length())
594                                 return fEnd;
595
596                         // suspected whitespace
597                         fBufferedEnd = fEnd;
598                         return fStart + word.length();
599                 }
600         }
601
602         /**
603          * Formats a paragraph, using break iterator.
604          * 
605          * @param offset
606          *            an offset within the paragraph, which will be updated with
607          *            respect to formatting.
608          */
609         private static String formatParagraph(String paragraph, int[] offset,
610                         String prefix, String lineDelimiter, int margin) {
611
612                 LineBreakIterator iterator = new LineBreakIterator(paragraph);
613
614                 StringBuffer paragraphBuffer = new StringBuffer();
615                 StringBuffer lineBuffer = new StringBuffer();
616                 StringBuffer whiteSpaceBuffer = new StringBuffer();
617
618                 int index = offset[0];
619                 int indexBuffer = -1;
620
621                 // line delimiter could be null
622                 if (lineDelimiter == null)
623                         lineDelimiter = ""; //$NON-NLS-1$
624
625                 for (int start = iterator.first(), end = iterator.next(); end != BreakIterator.DONE; start = end, end = iterator
626                                 .next()) {
627
628                         String word = paragraph.substring(start, end);
629
630                         // word is whitespace
631                         if (word.trim().length() == 0) {
632                                 whiteSpaceBuffer.append(word);
633
634                                 // first word of line is always appended
635                         } else if (lineBuffer.length() == 0) {
636                                 lineBuffer.append(prefix);
637                                 lineBuffer.append(whiteSpaceBuffer.toString());
638                                 lineBuffer.append(word);
639
640                         } else {
641                                 String line = lineBuffer.toString()
642                                                 + whiteSpaceBuffer.toString() + word.toString();
643
644                                 // margin exceeded
645                                 if (calculateDisplayedWidth(line) > margin) {
646                                         // flush line buffer and wrap paragraph
647                                         paragraphBuffer.append(lineBuffer.toString());
648                                         paragraphBuffer.append(lineDelimiter);
649                                         lineBuffer.setLength(0);
650                                         lineBuffer.append(prefix);
651                                         lineBuffer.append(word);
652
653                                         // flush index buffer
654                                         if (indexBuffer != -1) {
655                                                 offset[0] = indexBuffer;
656                                                 // correct for caret in whitespace at the end of line
657                                                 if (whiteSpaceBuffer.length() != 0 && index < start
658                                                                 && index >= start - whiteSpaceBuffer.length())
659                                                         offset[0] -= (index - (start - whiteSpaceBuffer
660                                                                         .length()));
661                                                 indexBuffer = -1;
662                                         }
663
664                                         whiteSpaceBuffer.setLength(0);
665
666                                         // margin not exceeded
667                                 } else {
668                                         lineBuffer.append(whiteSpaceBuffer.toString());
669                                         lineBuffer.append(word);
670                                         whiteSpaceBuffer.setLength(0);
671                                 }
672                         }
673
674                         if (index >= start && index < end) {
675                                 indexBuffer = paragraphBuffer.length() + lineBuffer.length()
676                                                 + (index - start);
677                                 if (word.trim().length() != 0)
678                                         indexBuffer -= word.length();
679                         }
680                 }
681
682                 // flush line buffer
683                 paragraphBuffer.append(lineBuffer.toString());
684                 paragraphBuffer.append(lineDelimiter);
685
686                 // flush index buffer
687                 if (indexBuffer != -1)
688                         offset[0] = indexBuffer;
689
690                 // last position is not returned by break iterator
691                 else if (offset[0] == paragraph.length())
692                         offset[0] = paragraphBuffer.length() - lineDelimiter.length();
693
694                 return paragraphBuffer.toString();
695         }
696
697         private static IPreferenceStore getPreferenceStore() {
698                 return WebUI.getDefault().getPreferenceStore();
699         }
700
701         /**
702          * Returns the displayed width of a string, taking in account the displayed
703          * tab width. The result can be compared against the print margin.
704          */
705         private static int calculateDisplayedWidth(String string) {
706
707                 int tabWidth = getPreferenceStore()
708                                 .getInt(
709                                                 AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
710                 if (tabWidth <= 0) {
711                         tabWidth = 2;
712                 }
713                 int column = 0;
714                 for (int i = 0; i < string.length(); i++)
715                         if ('\t' == string.charAt(i))
716                                 column += tabWidth - (column % tabWidth);
717                         else
718                                 column++;
719
720                 return column;
721         }
722
723         private String jdocExtractLinePrefix(IDocument d, int line)
724                         throws BadLocationException {
725
726                 IRegion region = d.getLineInformation(line);
727                 int lineOffset = region.getOffset();
728                 int index = findEndOfWhiteSpace(d, lineOffset, lineOffset
729                                 + d.getLineLength(line));
730                 if (d.getChar(index) == '*') {
731                         index++;
732                         if (index != lineOffset + region.getLength()
733                                         && d.getChar(index) == ' ')
734                                 index++;
735                 }
736                 return d.get(lineOffset, index - lineOffset);
737         }
738
739         private String getLineContents(IDocument d, int line)
740                         throws BadLocationException {
741                 int offset = d.getLineOffset(line);
742                 int length = d.getLineLength(line);
743                 String lineDelimiter = d.getLineDelimiter(line);
744                 if (lineDelimiter != null)
745                         length = length - lineDelimiter.length();
746                 String lineContents = d.get(offset, length);
747                 int trim = jdocExtractLinePrefix(d, line).length();
748                 return lineContents.substring(trim);
749         }
750
751         private static String getLine(IDocument document, int line)
752                         throws BadLocationException {
753                 IRegion region = document.getLineInformation(line);
754                 return document.get(region.getOffset(), region.getLength());
755         }
756
757         /**
758          * Returns <code>true</code> if the javadoc line is too short,
759          * <code>false</code> otherwise.
760          */
761         private boolean isLineTooShort(IDocument document, int line)
762                         throws BadLocationException {
763
764                 if (!isJavaDocLine(document, line + 1))
765                         return false;
766
767                 String nextLine = getLineContents(document, line + 1);
768                 if (nextLine.trim().length() == 0)
769                         return false;
770
771                 return true;
772         }
773
774         /**
775          * Returns <code>true</code> if the line is too long, <code>false</code>
776          * otherwise.
777          */
778         private boolean isLineTooLong(IDocument document, int line)
779                         throws BadLocationException {
780                 String lineContents = getLine(document, line);
781                 return calculateDisplayedWidth(lineContents) > getMargin();
782         }
783
784         private static int getMargin() {
785                 return getPreferenceStore()
786                                 .getInt(
787                                                 AbstractDecoratedTextEditorPreferenceConstants.EDITOR_PRINT_MARGIN_COLUMN);
788         }
789
790         private static final String[] fgInlineTags = {
791                         "<b>", "<i>", "<em>", "<strong>", "<code>" //$NON-NLS-1$  //$NON-NLS-2$  //$NON-NLS-3$  //$NON-NLS-4$ //$NON-NLS-5$
792         };
793
794         private boolean isInlineTag(String string) {
795                 for (int i = 0; i < fgInlineTags.length; i++)
796                         if (string.startsWith(fgInlineTags[i]))
797                                 return true;
798                 return false;
799         }
800
801         /**
802          * returns true if the specified line is part of a paragraph and should be
803          * merged with the previous line.
804          */
805         private boolean isJavaDocLine(IDocument document, int line)
806                         throws BadLocationException {
807
808                 if (document.getNumberOfLines() < line)
809                         return false;
810
811                 int offset = document.getLineOffset(line);
812                 int length = document.getLineLength(line);
813                 int firstChar = findEndOfWhiteSpace(document, offset, offset + length);
814                 length -= firstChar - offset;
815                 String lineContents = document.get(firstChar, length);
816
817                 String prefix = lineContents.trim();
818                 if (!prefix.startsWith("*") || prefix.startsWith("*/")) //$NON-NLS-1$ //$NON-NLS-2$
819                         return false;
820
821                 lineContents = lineContents.substring(1).trim().toLowerCase();
822
823                 // preserve empty lines
824                 if (lineContents.length() == 0)
825                         return false;
826
827                 // preserve @TAGS
828                 if (lineContents.startsWith("@")) //$NON-NLS-1$
829                         return false;
830
831                 // preserve HTML tags which are not inline
832                 if (lineContents.startsWith("<") && !isInlineTag(lineContents)) //$NON-NLS-1$
833                         return false;
834
835                 return true;
836         }
837
838         protected void jdocHandleBackspaceDelete(IDocument document,
839                         DocumentCommand c) {
840
841                 if (!getPreferenceStore().getBoolean(
842                                 PreferenceConstants.EDITOR_FORMAT_JAVADOCS))
843                         return;
844
845                 try {
846                         String text = document.get(c.offset, c.length);
847                         int line = document.getLineOfOffset(c.offset);
848                         int lineOffset = document.getLineOffset(line);
849
850                         // erase line delimiter
851                         String lineDelimiter = document.getLineDelimiter(line);
852                         if (lineDelimiter != null && lineDelimiter.equals(text)) {
853
854                                 String prefix = jdocExtractLinePrefix(document, line + 1);
855
856                                 // strip prefix if any
857                                 if (prefix.length() > 0) {
858                                         int length = document.getLineDelimiter(line).length()
859                                                         + prefix.length();
860                                         document.replace(c.offset, length, null);
861
862                                         c.doit = false;
863                                         c.length = 0;
864                                         return;
865                                 }
866
867                                 // backspace: beginning of a javadoc line
868                         } else if (document.getChar(c.offset - 1) == '*'
869                                         && jdocExtractLinePrefix(document, line).length() - 1 >= c.offset
870                                                         - lineOffset) {
871
872                                 lineDelimiter = document.getLineDelimiter(line - 1);
873                                 String prefix = jdocExtractLinePrefix(document, line);
874                                 int length = (lineDelimiter != null ? lineDelimiter.length()
875                                                 : 0)
876                                                 + prefix.length();
877                                 document.replace(c.offset - length + 1, length, null);
878
879                                 c.doit = false;
880                                 c.offset -= length - 1;
881                                 c.length = 0;
882                                 return;
883
884                         } else {
885                                 document.replace(c.offset, c.length, null);
886                                 c.doit = false;
887                                 c.length = 0;
888                         }
889
890                 } catch (BadLocationException e) {
891                         WebUI.log(e);
892                 }
893
894                 try {
895                         int line = document.getLineOfOffset(c.offset);
896                         int lineOffset = document.getLineOffset(line);
897                         String prefix = jdocExtractLinePrefix(document, line);
898                         boolean always = c.offset > lineOffset
899                                         && c.offset <= lineOffset + prefix.length();
900                         int[] caretOffset = { c.offset };
901                         jdocWrapParagraphFromLine(document, document
902                                         .getLineOfOffset(c.offset), caretOffset, always);
903                         c.offset = caretOffset[0];
904
905                 } catch (BadLocationException e) {
906                         WebUI.log(e);
907                 }
908         }
909
910         /**
911          * Returns the compilation unit of the CompilationUnitEditor invoking the
912          * AutoIndentStrategy, might return <code>null</code> on error.
913          */
914         private static ICompilationUnit getCompilationUnit() {
915
916                 IWorkbenchWindow window = PlatformUI.getWorkbench()
917                                 .getActiveWorkbenchWindow();
918                 if (window == null)
919                         return null;
920
921                 IWorkbenchPage page = window.getActivePage();
922                 if (page == null)
923                         return null;
924
925                 IEditorPart editor = page.getActiveEditor();
926                 if (editor == null)
927                         return null;
928
929                 IWorkingCopyManager manager = WebUI.getDefault()
930                                 .getWorkingCopyManager();
931                 ICompilationUnit unit = manager.getWorkingCopy(editor.getEditorInput());
932                 if (unit == null)
933                         return null;
934
935                 return unit;
936         }
937
938 }