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 	 */
JavaDocAutoIndentStrategy(String partitioning)59 	public JavaDocAutoIndentStrategy(String partitioning) {
60 		fPartitioning = partitioning;
61 	}
62 
getLineDelimiter(IDocument document)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 	 */
jdocIndentAfterNewLine(IDocument d, DocumentCommand c)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 
createJavaDocTags(IDocument document, DocumentCommand command, String indentation, String lineDelimiter, ICompilationUnit unit)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 	 */
prepareTemplateComment(String comment, String indentation, String lineDelimiter)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 
getTabWidth()196 	public static int getTabWidth() {
197 		Preferences preferences = PHPeclipsePlugin.getDefault()
198 				.getPluginPreferences();
199 		return preferences
200 				.getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
201 	}
202 
createTypeTags(IDocument document, DocumentCommand command, String indentation, String lineDelimiter, IType type)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 
createMethodTags(IDocument document, DocumentCommand command, String indentation, String lineDelimiter, IMethod method)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 	// }
jdocIndentForCommentEnd(IDocument d, DocumentCommand c)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 	 */
isNewComment(IDocument document, int commandOffset, String partitioning)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 
isSmartMode()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 	 */
customizeDocumentCommand(IDocument document, DocumentCommand command)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 
flushCommand(IDocument document, DocumentCommand command)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 
jdocWrapParagraphOnInsert(IDocument document, DocumentCommand command)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 	 */
jdocWrapParagraphFromLine(IDocument document, int line, int[] caretOffset, boolean always)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 
LineBreakIterator(String string)559 		public LineBreakIterator(String string) {
560 			fString = string;
561 			fIterator.setText(string);
562 		}
563 
first()564 		public int first() {
565 			fBufferedEnd = -1;
566 			fStart = fIterator.first();
567 			return fStart;
568 		}
569 
next()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 	 */
formatParagraph(String paragraph, int[] offset, String prefix, String lineDelimiter, int margin)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 
getPreferenceStore()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 	 */
calculateDisplayedWidth(String string)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 
jdocExtractLinePrefix(IDocument d, int line)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 
getLineContents(IDocument d, int line)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 
getLine(IDocument document, int line)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 	 */
isLineTooShort(IDocument document, int line)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 	 */
isLineTooLong(IDocument document, int line)777 	private boolean isLineTooLong(IDocument document, int line)
778 			throws BadLocationException {
779 		String lineContents = getLine(document, line);
780 		return calculateDisplayedWidth(lineContents) > getMargin();
781 	}
782 
getMargin()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 
isInlineTag(String string)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 	 */
isJavaDocLine(IDocument document, int line)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 
jdocHandleBackspaceDelete(IDocument document, DocumentCommand c)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 	 */
getCompilationUnit()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 }
938