]> git.uio.no Git - ifi-stolz-refaktor.git/blob - case-study/jdt-after/ui/org/eclipse/jdt/internal/ui/actions/IndentAction.java
Case Study: adding data and statistics
[ifi-stolz-refaktor.git] / case-study / jdt-after / ui / org / eclipse / jdt / internal / ui / actions / IndentAction.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2011 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *     Tom Eicher (Avaloq Evolution AG) - block selection mode
11  *******************************************************************************/
12 package org.eclipse.jdt.internal.ui.actions;
13
14 import java.util.ArrayList;
15 import java.util.Iterator;
16 import java.util.ResourceBundle;
17
18 import org.eclipse.swt.custom.BusyIndicator;
19 import org.eclipse.swt.widgets.Display;
20
21 import org.eclipse.core.runtime.Assert;
22 import org.eclipse.core.runtime.IStatus;
23 import org.eclipse.core.runtime.Status;
24
25 import org.eclipse.text.edits.MultiTextEdit;
26 import org.eclipse.text.edits.ReplaceEdit;
27 import org.eclipse.text.edits.TextEdit;
28
29 import org.eclipse.jface.viewers.ISelection;
30 import org.eclipse.jface.viewers.ISelectionProvider;
31
32 import org.eclipse.jface.text.BadLocationException;
33 import org.eclipse.jface.text.IDocument;
34 import org.eclipse.jface.text.IRegion;
35 import org.eclipse.jface.text.IRewriteTarget;
36 import org.eclipse.jface.text.ITextSelection;
37 import org.eclipse.jface.text.ITypedRegion;
38 import org.eclipse.jface.text.Position;
39 import org.eclipse.jface.text.TextSelection;
40 import org.eclipse.jface.text.TextUtilities;
41 import org.eclipse.jface.text.source.ISourceViewer;
42
43 import org.eclipse.ui.IEditorInput;
44
45 import org.eclipse.ui.texteditor.IDocumentProvider;
46 import org.eclipse.ui.texteditor.ITextEditor;
47 import org.eclipse.ui.texteditor.ITextEditorExtension3;
48 import org.eclipse.ui.texteditor.TextEditorAction;
49
50 import org.eclipse.jdt.core.ICompilationUnit;
51 import org.eclipse.jdt.core.IJavaProject;
52 import org.eclipse.jdt.core.JavaCore;
53 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
54
55 import org.eclipse.jdt.ui.text.IJavaPartitions;
56
57 import org.eclipse.jdt.internal.ui.JavaPlugin;
58 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
59 import org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner;
60 import org.eclipse.jdt.internal.ui.text.JavaIndenter;
61
62
63 /**
64  * Indents a line or range of lines in a Java document to its correct position. No complete
65  * AST must be present, the indentation is computed using heuristics. The algorithm used is fast for
66  * single lines, but does not store any information and therefore not so efficient for large line
67  * ranges.
68  *
69  * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner
70  * @see org.eclipse.jdt.internal.ui.text.JavaIndenter
71  * @since 3.0
72  */
73 public class IndentAction extends TextEditorAction {
74
75         /**
76          * @since 3.4
77          */
78         static final class ReplaceData {
79
80                 /**
81                  * The replacement
82                  */
83                 public final String indent;
84
85                 /**
86                  * The start of the replacement
87                  */
88                 public final int offset;
89
90                 /**
91                  * The end of the replacement
92                  */
93                 public final int end;
94
95                 /**
96                  * Replace string in document from offset to end with indent
97                  * @param offset the start of the replacement
98                  * @param end the end of the replacement
99                  * @param indent the replacement
100                  */
101                 public ReplaceData(int offset, int end, String indent) {
102                         this.indent= indent;
103                         this.end= end;
104                         this.offset= offset;
105                 }
106
107                 public boolean generated_7289008201151769652(IDocument document, int caret, IJavaProject project, IndentAction indentaction) throws BadLocationException {
108                         String indent= indent;
109                         int end= end;
110                         int offset= offset;
111                 
112                         int length= end - offset;
113                         String currentIndent= document.get(offset, length);
114                 
115                         // if we are right before the text start / line end, and already after the insertion point
116                         // then just insert a tab.
117                         if (indentaction.fIsTabAction && caret == end && IndentAction.whiteSpaceLength(currentIndent, project) >= IndentAction.whiteSpaceLength(indent, project)) {
118                                 String tab= IndentAction.getTabEquivalent(project);
119                                 document.replace(caret, 0, tab);
120                                 indentaction.fCaretOffset= caret + tab.length();
121                                 return true;
122                         }
123                 
124                         // set the caret offset so it can be used when setting the selection
125                         if (caret >= offset && caret <= end)
126                                 indentaction.fCaretOffset= offset + indent.length();
127                         else
128                                 indentaction.fCaretOffset= -1;
129                 
130                         // only change the document if it is a real change
131                         if (!indent.equals(currentIndent)) {
132                                 document.replace(offset, length, indent);
133                                 return true;
134                         } else
135                                 return false;
136                 }
137
138         }
139
140         /** The caret offset after an indent operation. */
141         private int fCaretOffset;
142
143         /**
144          * Whether this is the action invoked by TAB. When <code>true</code>, indentation behaves
145          * differently to accommodate normal TAB operation.
146          */
147         private final boolean fIsTabAction;
148
149         /**
150          * Creates a new instance.
151          *
152          * @param bundle the resource bundle
153          * @param prefix the prefix to use for keys in <code>bundle</code>
154          * @param editor the text editor
155          * @param isTabAction whether the action should insert tabs if over the indentation
156          */
157         public IndentAction(ResourceBundle bundle, String prefix, ITextEditor editor, boolean isTabAction) {
158                 super(bundle, prefix, editor);
159                 fIsTabAction= isTabAction;
160         }
161
162         /*
163          * @see org.eclipse.jface.action.Action#run()
164          */
165         @Override
166         public void run() {
167                 // update has been called by the framework
168                 if (!isEnabled() || !validateEditorInputState())
169                         return;
170
171                 ITextSelection selection= getSelection();
172                 final IDocument document= getDocument();
173
174                 if (document != null) {
175
176                         final int offset= selection.getOffset();
177                         final int length= selection.getLength();
178                         final Position end= new Position(offset + length);
179                         final int firstLine, nLines;
180                         fCaretOffset= -1;
181
182                         try {
183                                 document.addPosition(end);
184                                 firstLine= selection.getStartLine();
185                                 nLines= selection.getEndLine() - firstLine + 1;
186                         } catch (BadLocationException e) {
187                                 // will only happen on concurrent modification
188                                 JavaPlugin.log(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "", e)); //$NON-NLS-1$
189                                 return;
190                         }
191
192                         Runnable runnable= new Runnable() {
193                                 public void run() {
194                                         IRewriteTarget target= (IRewriteTarget)getTextEditor().getAdapter(IRewriteTarget.class);
195                                         if (target != null)
196                                                 target.beginCompoundChange();
197
198                                         try {
199                                                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
200                                                 JavaIndenter indenter= new JavaIndenter(document, scanner, getJavaProject());
201                                                 final boolean multiLine= nLines > 1;
202                                                 boolean hasChanged= false;
203                                                 for (int i= 0; i < nLines; i++) {
204                                                         hasChanged |= indentLine(document, firstLine + i, offset, indenter, scanner, multiLine);
205                                                 }
206
207                                                 // update caret position: move to new position when indenting just one line
208                                                 // keep selection when indenting multiple
209                                                 int newOffset, newLength;
210                                                 if (!fIsTabAction && multiLine) {
211                                                         newOffset= offset;
212                                                         newLength= end.getOffset() - offset;
213                                                 } else {
214                                                         newOffset= fCaretOffset;
215                                                         newLength= 0;
216                                                 }
217
218                                                 // always reset the selection if anything was replaced
219                                                 // but not when we had a single line non-tab invocation
220                                                 if (newOffset != -1 && (hasChanged || newOffset != offset || newLength != length))
221                                                         selectAndReveal(newOffset, newLength);
222
223                                                 document.removePosition(end);
224                                         } catch (BadLocationException e) {
225                                                 // will only happen on concurrent modification
226                                                 JavaPlugin.log(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "ConcurrentModification in IndentAction", e)); //$NON-NLS-1$
227
228                                         } finally {
229                                                 if (target != null)
230                                                         target.endCompoundChange();
231                                         }
232                                 }
233                         };
234
235                         if (nLines > 50) {
236                                 Display display= getTextEditor().getEditorSite().getWorkbenchWindow().getShell().getDisplay();
237                                 BusyIndicator.showWhile(display, runnable);
238                         } else
239                                 runnable.run();
240
241                 }
242         }
243
244         /**
245          * Selects the given range on the editor.
246          *
247          * @param newOffset the selection offset
248          * @param newLength the selection range
249          */
250         private void selectAndReveal(int newOffset, int newLength) {
251                 Assert.isTrue(newOffset >= 0);
252                 Assert.isTrue(newLength >= 0);
253                 ITextEditor editor= getTextEditor();
254                 if (editor instanceof JavaEditor) {
255                         ISourceViewer viewer= ((JavaEditor)editor).getViewer();
256                         if (viewer != null)
257                                 viewer.setSelectedRange(newOffset, newLength);
258                 } else
259                         // this is too intrusive, but will never get called anyway
260                         getTextEditor().selectAndReveal(newOffset, newLength);
261
262         }
263
264         /**
265          * Indent the given <code>document</code> based on the <code>project</code> settings and
266          * return a text edit describing the changes applied to the document. Returns <b>null</b>
267          * if no changes have been applied.
268          * <p>
269          * WARNING: This method does change the content of the given document.
270          * </p>
271          * <p>
272          * This method is for internal use only, it should not be called.
273          * </p>
274          *
275          * @param document the document to indent must have a java partitioning installed
276          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
277          * @return a text edit describing the changes or <b>null</b> if no changes required
278          * @throws BadLocationException if the document got modified concurrently
279          *
280          * @since 3.4
281          */
282         public static TextEdit indent(IDocument document, IJavaProject project) throws BadLocationException {
283                 int offset= 0;
284                 int length= document.getLength();
285
286                 JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
287                 JavaIndenter indenter= new JavaIndenter(document, scanner, project);
288
289                 ArrayList<ReplaceEdit> edits= new ArrayList<ReplaceEdit>();
290
291                 int firstLine= document.getLineOfOffset(offset);
292                 // check for marginal (zero-length) lines
293                 int minusOne= length == 0 ? 0 : 1;
294                 int numberOfLines= document.getLineOfOffset(offset + length - minusOne) - firstLine + 1;
295
296                 int shift= 0;
297                 for (int i= 0; i < numberOfLines; i++) {
298                         ReplaceData data= computeReplaceData(document, firstLine + i, indenter, scanner, numberOfLines > 1, false, project);
299
300                         int replaceLength= data.end - data.offset;
301                         String currentIndent= document.get(data.offset, replaceLength);
302
303                         // only change the document if it is a real change
304                         if (!data.indent.equals(currentIndent)) {
305                                 edits.add(new ReplaceEdit(data.offset + shift, replaceLength, data.indent));
306                                 //We need to change the document, the indenter depends on it.
307                                 document.replace(data.offset, replaceLength, data.indent);
308                                 shift-= data.indent.length() - replaceLength;
309                         }
310                 }
311
312                 if (edits.size() == 0)
313                         return null;
314
315                 if (edits.size() == 1)
316                         return edits.get(0);
317
318                 MultiTextEdit result= new MultiTextEdit();
319                 for (Iterator<ReplaceEdit> iterator= edits.iterator(); iterator.hasNext();) {
320                         TextEdit edit= iterator.next();
321                         result.addChild(edit);
322                 }
323
324                 return result;
325         }
326
327         /**
328          * Indents a single line using the java heuristic scanner. Javadoc and multiline comments are
329          * indented as specified by the <code>JavaDocAutoIndentStrategy</code>.
330          *
331          * @param document the document
332          * @param line the line to be indented
333          * @param indenter the java indenter
334          * @param scanner the heuristic scanner
335          * @param multiLine <code>true</code> if more than one line is being indented
336          * @param isTabAction <code>true</code> if this action has been invoked by TAB
337          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
338          * @return <code>true</code> if <code>document</code> was modified, <code>false</code> otherwise
339          * @throws BadLocationException if the document got changed concurrently
340          */
341         private static ReplaceData computeReplaceData(IDocument document, int line, JavaIndenter indenter, JavaHeuristicScanner scanner, boolean multiLine, boolean isTabAction, IJavaProject project) throws BadLocationException {
342                 IRegion currentLine= document.getLineInformation(line);
343                 int offset= currentLine.getOffset();
344                 int wsStart= offset; // where we start searching for non-WS; after the "//" in single line comments
345
346                 String indent= null;
347                 if (offset < document.getLength()) {
348                         ITypedRegion partition= TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, true);
349                         String type= partition.getType();
350                         ITypedRegion startingPartition= TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, false);
351                         String startingType= startingPartition.getType();
352                         boolean isCommentStart= startingPartition.getOffset() == offset;
353                         if (isDontIndentMultiLineCommentOnFirstColumn(project) && isCommentStart && IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(startingType)) {
354                                 indent= ""; //$NON-NLS-1$
355                         } else if (IJavaPartitions.JAVA_DOC.equals(type) || IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(type)) {
356                                 indent= computeJavadocIndent(document, line, scanner, startingPartition);
357                         } else if (!isTabAction && isCommentStart && IJavaPartitions.JAVA_SINGLE_LINE_COMMENT.equals(startingType)) {
358                                 // line comment starting at position 0
359                                 if (multiLine) {
360                                         //Do what the formatter does
361                                         if (isDontIndentSingleLineCommentOnFirstColumn(project))
362                                                 indent= ""; //$NON-NLS-1$
363                                 } else {
364                                         //indent inside -> add/remove indent such that user can start typing at correct position
365                                         int slashes= countLeadingSlashPairs(document, offset) * 2;
366                                         wsStart= offset + slashes;
367
368                                         StringBuffer computed= indenter.computeIndentation(offset);
369                                         if (computed == null)
370                                                 computed= new StringBuffer(0);
371
372                                         removeIndentations(slashes, getTabSize(project), computed);
373                                         indent= document.get(offset, wsStart - offset) + computed;
374                                 }
375                         }
376                 }
377
378                 // standard java indentation
379                 if (indent == null) {
380                         StringBuffer computed= indenter.computeIndentation(offset);
381                         if (computed != null)
382                                 indent= computed.toString();
383                         else
384                                 indent= ""; //$NON-NLS-1$
385                 }
386
387                 // change document:
388                 // get current white space
389                 int lineLength= currentLine.getLength();
390                 int end= scanner.findNonWhitespaceForwardInAnyPartition(wsStart, offset + lineLength);
391                 if (end == JavaHeuristicScanner.NOT_FOUND) {
392                         // an empty line
393                         end= offset + lineLength;
394                         if (multiLine && !indentEmptyLines(project))
395                                 indent= ""; //$NON-NLS-1$
396                 }
397
398                 return new ReplaceData(offset, end, indent);
399         }
400
401         /**
402          * Removes <code>count</code> indentations from start
403          * of <code>buffer</code>. The size of a space character
404          * is 1 and the size of a tab character is <code>tabSize</code>.
405          *
406          * @param count the number of indentations to remove
407          * @param tabSize the size of a tab character
408          * @param buffer the buffer to modify
409          * @since 3.4
410          */
411         private static void removeIndentations(int count, int tabSize, StringBuffer buffer) {
412                 while (count > 0 && buffer.length() > 0) {
413                         char c= buffer.charAt(0);
414                         if (c == '\t')
415                                 if (count > tabSize)
416                                         count-= tabSize;
417                                 else
418                                         break;
419                         else if (c == ' ')
420                                 count--;
421                         else break;
422
423                         buffer.deleteCharAt(0);
424                 }
425         }
426
427         /**
428          * Returns number of continuous slashes pairs ('//') starting at <code>offset</code>
429          * in <code>document</code>
430          *
431          * @param document the document to inspect
432          * @param offset the offset where to start looking for slash pairs
433          * @return the number of slash pairs.
434          * @throws BadLocationException
435          * @since 3.4
436          */
437         private static int countLeadingSlashPairs(IDocument document, int offset) throws BadLocationException {
438                 IRegion lineInfo= document.getLineInformationOfOffset(offset);
439                 int max= lineInfo.getOffset() + lineInfo.getLength() - 1;
440
441                 int pairCount= 0;
442                 while (offset < max && document.get(offset, 2).equals("//")) { //$NON-NLS-1$
443                         pairCount++;
444                         offset= offset + 2;
445                 }
446
447                 return pairCount;
448         }
449
450         /**
451          * Indents a single line using the java heuristic scanner. Javadoc and multiline comments are
452          * indented as specified by the <code>JavaDocAutoIndentStrategy</code>.
453          *
454          * @param document the document
455          * @param line the line to be indented
456          * @param caret the caret position
457          * @param indenter the java indenter
458          * @param scanner the heuristic scanner
459          * @param multiLine <code>true</code> if more than one line is being indented
460          * @return <code>true</code> if <code>document</code> was modified, <code>false</code> otherwise
461          * @throws BadLocationException if the document got changed concurrently
462          */
463         private boolean indentLine(IDocument document, int line, int caret, JavaIndenter indenter, JavaHeuristicScanner scanner, boolean multiLine) throws BadLocationException {
464                 IJavaProject project= getJavaProject();
465                 ReplaceData data= computeReplaceData(document, line, indenter, scanner, multiLine, fIsTabAction, project);
466
467                 return data.generated_7289008201151769652(document, caret, project, this);
468         }
469
470         /**
471          * Computes and returns the indentation for a javadoc line. The line
472          * must be inside a javadoc comment.
473          *
474          * @param document the document
475          * @param line the line in document
476          * @param scanner the scanner
477          * @param partition the javadoc partition
478          * @return the indent, or <code>null</code> if not computable
479          * @throws BadLocationException
480          * @since 3.1
481          */
482         private static String computeJavadocIndent(IDocument document, int line, JavaHeuristicScanner scanner, ITypedRegion partition) throws BadLocationException {
483                 if (line == 0) // impossible - the first line is never inside a javadoc comment
484                         return null;
485
486                 // don't make any assumptions if the line does not start with \s*\* - it might be
487                 // commented out code, for which we don't want to change the indent
488                 final IRegion lineInfo= document.getLineInformation(line);
489                 final int lineStart= lineInfo.getOffset();
490                 final int lineLength= lineInfo.getLength();
491                 final int lineEnd= lineStart + lineLength;
492                 int nonWS= scanner.findNonWhitespaceForwardInAnyPartition(lineStart, lineEnd);
493                 if (nonWS == JavaHeuristicScanner.NOT_FOUND || document.getChar(nonWS) != '*') {
494                         if (nonWS == JavaHeuristicScanner.NOT_FOUND)
495                                 return document.get(lineStart, lineLength);
496                         return document.get(lineStart, nonWS - lineStart);
497                 }
498
499                 // take the indent from the previous line and reuse
500                 IRegion previousLine= document.getLineInformation(line - 1);
501                 int previousLineStart= previousLine.getOffset();
502                 int previousLineLength= previousLine.getLength();
503                 int previousLineEnd= previousLineStart + previousLineLength;
504
505                 StringBuffer buf= new StringBuffer();
506                 int previousLineNonWS= scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
507                 if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND || document.getChar(previousLineNonWS) != '*') {
508                         // align with the comment start if the previous line is not an asterisked line
509                         previousLine= document.getLineInformationOfOffset(partition.getOffset());
510                         previousLineStart= previousLine.getOffset();
511                         previousLineLength= previousLine.getLength();
512                         previousLineEnd= previousLineStart + previousLineLength;
513                         previousLineNonWS= scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
514                         if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND)
515                                 previousLineNonWS= previousLineEnd;
516
517                         // add the initial space
518                         // TODO this may be controlled by a formatter preference in the future
519                         buf.append(' ');
520                 }
521
522                 String indentation= document.get(previousLineStart, previousLineNonWS - previousLineStart);
523                 buf.insert(0, indentation);
524                 return buf.toString();
525         }
526
527         /**
528          * Returns the size in characters of a string. All characters count one, tabs count the editor's
529          * preference for the tab display
530          *
531          * @param indent the string to be measured.
532          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
533          * @return the size in characters of a string
534          */
535         private static int whiteSpaceLength(String indent, IJavaProject project) {
536                 if (indent == null)
537                         return 0;
538                 else {
539                         int size= 0;
540                         int l= indent.length();
541                         int tabSize= getTabSize(project);
542
543                         for (int i= 0; i < l; i++)
544                                 size += indent.charAt(i) == '\t' ? tabSize : 1;
545                         return size;
546                 }
547         }
548
549         /**
550          * Returns a tab equivalent, either as a tab character or as spaces, depending on the editor and
551          * formatter preferences.
552          *
553          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
554          * @return a string representing one tab in the editor, never <code>null</code>
555          */
556         private static String getTabEquivalent(IJavaProject project) {
557                 String tab;
558                 if (JavaCore.SPACE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, project))) {
559                         int size= getTabSize(project);
560                         StringBuffer buf= new StringBuffer();
561                         for (int i= 0; i< size; i++)
562                                 buf.append(' ');
563                         tab= buf.toString();
564                 } else
565                         tab= "\t"; //$NON-NLS-1$
566
567                 return tab;
568         }
569
570         /**
571          * Returns the tab size used by the java editor, which is deduced from the
572          * formatter preferences.
573          *
574          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
575          * @return the tab size as defined in the current formatter preferences
576          */
577         private static int getTabSize(IJavaProject project) {
578                 return getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, 4, project);
579         }
580
581         /**
582          * Returns <code>true</code> if empty lines should be indented, <code>false</code> otherwise.
583          *
584          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
585          * @return <code>true</code> if empty lines should be indented, <code>false</code> otherwise
586          * @since 3.2
587          */
588         private static boolean indentEmptyLines(IJavaProject project) {
589                 return DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_EMPTY_LINES, project));
590         }
591
592         /**
593          * Returns <code>true</code> if multi line comments which start at first column
594          * should not be indented, <code>false</code> otherwise.
595          *
596          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
597          * @return <code>true</code> if such multi line comments should be indented, <code>false</code> otherwise
598          * @since 3.4
599          */
600         private static boolean isDontIndentMultiLineCommentOnFirstColumn(IJavaProject project) {
601                 return DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_NEVER_INDENT_BLOCK_COMMENTS_ON_FIRST_COLUMN, project));
602         }
603
604         /**
605          * Returns <code>true</code> if single line comments which start at first column
606          * should not be indented, <code>false</code> otherwise.
607          *
608          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
609          * @return <code>true</code> if such single line comments should be indented, <code>false</code> otherwise
610          * @since 3.4
611          */
612         private static boolean isDontIndentSingleLineCommentOnFirstColumn(IJavaProject project) {
613                 return DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_NEVER_INDENT_LINE_COMMENTS_ON_FIRST_COLUMN, project));
614         }
615
616         /**
617          * Returns the possibly project-specific core preference defined under <code>key</code>.
618          *
619          * @param key the key of the preference
620          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
621          * @return the value of the preference
622          * @since 3.1
623          */
624         private static String getCoreFormatterOption(String key, IJavaProject project) {
625                 if (project == null)
626                         return JavaCore.getOption(key);
627                 return project.getOption(key, true);
628         }
629
630         /**
631          * Returns the possibly project-specific core preference defined under <code>key</code>, or
632          * <code>def</code> if the value is not a integer.
633          *
634          * @param key the key of the preference
635          * @param def the default value
636          * @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
637          * @return the value of the preference
638          * @since 3.1
639          */
640         private static int getCoreFormatterOption(String key, int def, IJavaProject project) {
641                 try {
642                         return Integer.parseInt(getCoreFormatterOption(key, project));
643                 } catch (NumberFormatException e) {
644                         return def;
645                 }
646         }
647
648         /**
649          * Returns the <code>IJavaProject</code> of the current editor input, or
650          * <code>null</code> if it cannot be found.
651          *
652          * @return the <code>IJavaProject</code> of the current editor input, or
653          *         <code>null</code> if it cannot be found
654          * @since 3.1
655          */
656         private IJavaProject getJavaProject() {
657                 ITextEditor editor= getTextEditor();
658                 if (editor == null)
659                         return null;
660
661                 ICompilationUnit cu= JavaPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editor.getEditorInput());
662                 if (cu == null)
663                         return null;
664                 return cu.getJavaProject();
665         }
666
667         /**
668          * Returns the editor's selection provider.
669          *
670          * @return the editor's selection provider or <code>null</code>
671          */
672         private ISelectionProvider getSelectionProvider() {
673                 ITextEditor editor= getTextEditor();
674                 if (editor != null) {
675                         return editor.getSelectionProvider();
676                 }
677                 return null;
678         }
679
680         /*
681          * @see org.eclipse.ui.texteditor.IUpdate#update()
682          */
683         @Override
684         public void update() {
685                 super.update();
686
687                 if (isEnabled())
688                         if (fIsTabAction)
689                                 setEnabled(canModifyEditor() && isSmartMode() && isValidSelection());
690                         else
691                                 setEnabled(canModifyEditor() && !getSelection().isEmpty());
692         }
693
694         /**
695          * Returns if the current selection is valid, i.e. whether it is empty and the caret in the
696          * whitespace at the start of a line, or covers multiple lines.
697          *
698          * @return <code>true</code> if the selection is valid for an indent operation
699          */
700         private boolean isValidSelection() {
701                 ITextSelection selection= getSelection();
702                 if (selection.isEmpty())
703                         return false;
704
705                 int offset= selection.getOffset();
706                 int length= selection.getLength();
707
708                 IDocument document= getDocument();
709                 if (document == null)
710                         return false;
711
712                 try {
713                         IRegion firstLine= document.getLineInformationOfOffset(offset);
714                         int lineOffset= firstLine.getOffset();
715
716                         // either the selection has to be empty and the caret in the WS at the line start
717                         // or the selection has to extend over multiple lines
718                         if (length == 0)
719                                 return document.get(lineOffset, offset - lineOffset).trim().length() == 0;
720                         else
721 //                              return lineOffset + firstLine.getLength() < offset + length;
722                                 return false; // only enable for empty selections for now
723
724                 } catch (BadLocationException e) {
725                 }
726
727                 return false;
728         }
729
730         /**
731          * Returns the smart preference state.
732          *
733          * @return <code>true</code> if smart mode is on, <code>false</code> otherwise
734          */
735         private boolean isSmartMode() {
736                 ITextEditor editor= getTextEditor();
737
738                 if (editor instanceof ITextEditorExtension3)
739                         return ((ITextEditorExtension3) editor).getInsertMode() == ITextEditorExtension3.SMART_INSERT;
740
741                 return false;
742         }
743
744         /**
745          * Returns the document currently displayed in the editor, or <code>null</code> if none can be
746          * obtained.
747          *
748          * @return the current document or <code>null</code>
749          */
750         private IDocument getDocument() {
751
752                 ITextEditor editor= getTextEditor();
753                 if (editor != null) {
754
755                         IDocumentProvider provider= editor.getDocumentProvider();
756                         IEditorInput input= editor.getEditorInput();
757                         if (provider != null && input != null)
758                                 return provider.getDocument(input);
759
760                 }
761                 return null;
762         }
763
764         /**
765          * Returns the selection on the editor or an invalid selection if none can be obtained. Returns
766          * never <code>null</code>.
767          *
768          * @return the current selection, never <code>null</code>
769          */
770         private ITextSelection getSelection() {
771                 ISelectionProvider provider= getSelectionProvider();
772                 if (provider != null) {
773
774                         ISelection selection= provider.getSelection();
775                         if (selection instanceof ITextSelection)
776                                 return (ITextSelection) selection;
777                 }
778
779                 // null object
780                 return TextSelection.emptySelection();
781         }
782
783 }