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