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