KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > eclipse > jdt > internal > ui > actions > IndentAction


1 /*******************************************************************************
2  * Copyright (c) 2000, 2006 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  *******************************************************************************/

11 package org.eclipse.jdt.internal.ui.actions;
12
13 import java.util.ResourceBundle JavaDoc;
14
15 import org.eclipse.core.runtime.Assert;
16 import org.eclipse.core.runtime.IStatus;
17 import org.eclipse.core.runtime.Status;
18
19 import org.eclipse.swt.custom.BusyIndicator;
20 import org.eclipse.swt.widgets.Display;
21
22 import org.eclipse.jface.viewers.ISelection;
23 import org.eclipse.jface.viewers.ISelectionProvider;
24
25 import org.eclipse.jface.text.BadLocationException;
26 import org.eclipse.jface.text.IDocument;
27 import org.eclipse.jface.text.IRegion;
28 import org.eclipse.jface.text.IRewriteTarget;
29 import org.eclipse.jface.text.ITextSelection;
30 import org.eclipse.jface.text.ITypedRegion;
31 import org.eclipse.jface.text.Position;
32 import org.eclipse.jface.text.TextSelection;
33 import org.eclipse.jface.text.TextUtilities;
34 import org.eclipse.jface.text.source.ISourceViewer;
35
36 import org.eclipse.ui.IEditorInput;
37 import org.eclipse.ui.texteditor.IDocumentProvider;
38 import org.eclipse.ui.texteditor.ITextEditor;
39 import org.eclipse.ui.texteditor.ITextEditorExtension3;
40 import org.eclipse.ui.texteditor.TextEditorAction;
41
42 import org.eclipse.jdt.core.ICompilationUnit;
43 import org.eclipse.jdt.core.IJavaProject;
44 import org.eclipse.jdt.core.JavaCore;
45 import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
46
47 import org.eclipse.jdt.ui.text.IJavaPartitions;
48
49 import org.eclipse.jdt.internal.ui.JavaPlugin;
50 import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
51 import org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner;
52 import org.eclipse.jdt.internal.ui.text.JavaIndenter;
53
54
55 /**
56  * Indents a line or range of lines in a Java document to its correct position. No complete
57  * AST must be present, the indentation is computed using heuristics. The algorithm used is fast for
58  * single lines, but does not store any information and therefore not so efficient for large line
59  * ranges.
60  *
61  * @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner
62  * @see org.eclipse.jdt.internal.ui.text.JavaIndenter
63  * @since 3.0
64  */

65 public class IndentAction extends TextEditorAction {
66     
67     /** The caret offset after an indent operation. */
68     private int fCaretOffset;
69     
70     /**
71      * Whether this is the action invoked by TAB. When <code>true</code>, indentation behaves
72      * differently to accommodate normal TAB operation.
73      */

74     private final boolean fIsTabAction;
75     
76     /**
77      * Creates a new instance.
78      *
79      * @param bundle the resource bundle
80      * @param prefix the prefix to use for keys in <code>bundle</code>
81      * @param editor the text editor
82      * @param isTabAction whether the action should insert tabs if over the indentation
83      */

84     public IndentAction(ResourceBundle JavaDoc bundle, String JavaDoc prefix, ITextEditor editor, boolean isTabAction) {
85         super(bundle, prefix, editor);
86         fIsTabAction= isTabAction;
87     }
88     
89     /*
90      * @see org.eclipse.jface.action.Action#run()
91      */

92     public void run() {
93         // update has been called by the framework
94
if (!isEnabled() || !validateEditorInputState())
95             return;
96         
97         ITextSelection selection= getSelection();
98         final IDocument document= getDocument();
99         
100         if (document != null) {
101             
102             final int offset= selection.getOffset();
103             final int length= selection.getLength();
104             final Position end= new Position(offset + length);
105             final int firstLine, nLines;
106             fCaretOffset= -1;
107             
108             try {
109                 document.addPosition(end);
110                 firstLine= document.getLineOfOffset(offset);
111                 // check for marginal (zero-length) lines
112
int minusOne= length == 0 ? 0 : 1;
113                 nLines= document.getLineOfOffset(offset + length - minusOne) - firstLine + 1;
114             } catch (BadLocationException e) {
115                 // will only happen on concurrent modification
116
JavaPlugin.log(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "", e)); //$NON-NLS-1$
117
return;
118             }
119             
120             Runnable JavaDoc runnable= new Runnable JavaDoc() {
121                 public void run() {
122                     IRewriteTarget target= (IRewriteTarget)getTextEditor().getAdapter(IRewriteTarget.class);
123                     if (target != null)
124                         target.beginCompoundChange();
125                     
126                     try {
127                         JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
128                         JavaIndenter indenter= new JavaIndenter(document, scanner, getJavaProject());
129                         final boolean multiLine= nLines > 1;
130                         boolean hasChanged= false;
131                         for (int i= 0; i < nLines; i++) {
132                             hasChanged |= indentLine(document, firstLine + i, offset, indenter, scanner, multiLine);
133                         }
134                         
135                         // update caret position: move to new position when indenting just one line
136
// keep selection when indenting multiple
137
int newOffset, newLength;
138                         if (!fIsTabAction && multiLine) {
139                             newOffset= offset;
140                             newLength= end.getOffset() - offset;
141                         } else {
142                             newOffset= fCaretOffset;
143                             newLength= 0;
144                         }
145                         
146                         // always reset the selection if anything was replaced
147
// but not when we had a single line non-tab invocation
148
if (newOffset != -1 && (hasChanged || newOffset != offset || newLength != length))
149                             selectAndReveal(newOffset, newLength);
150                         
151                         document.removePosition(end);
152                     } catch (BadLocationException e) {
153                         // will only happen on concurrent modification
154
JavaPlugin.log(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "ConcurrentModification in IndentAction", e)); //$NON-NLS-1$
155

156                     } finally {
157                         if (target != null)
158                             target.endCompoundChange();
159                     }
160                 }
161             };
162             
163             if (nLines > 50) {
164                 Display display= getTextEditor().getEditorSite().getWorkbenchWindow().getShell().getDisplay();
165                 BusyIndicator.showWhile(display, runnable);
166             } else
167                 runnable.run();
168             
169         }
170     }
171     
172     /**
173      * Selects the given range on the editor.
174      *
175      * @param newOffset the selection offset
176      * @param newLength the selection range
177      */

178     private void selectAndReveal(int newOffset, int newLength) {
179         Assert.isTrue(newOffset >= 0);
180         Assert.isTrue(newLength >= 0);
181         ITextEditor editor= getTextEditor();
182         if (editor instanceof JavaEditor) {
183             ISourceViewer viewer= ((JavaEditor)editor).getViewer();
184             if (viewer != null)
185                 viewer.setSelectedRange(newOffset, newLength);
186         } else
187             // this is too intrusive, but will never get called anyway
188
getTextEditor().selectAndReveal(newOffset, newLength);
189             
190     }
191
192     /**
193      * Indents a single line using the java heuristic scanner. Javadoc and multiline comments are
194      * indented as specified by the <code>JavaDocAutoIndentStrategy</code>.
195      *
196      * @param document the document
197      * @param line the line to be indented
198      * @param caret the caret position
199      * @param indenter the java indenter
200      * @param scanner the heuristic scanner
201      * @param multiLine <code>true</code> if more than one line is being indented
202      * @return <code>true</code> if <code>document</code> was modified, <code>false</code> otherwise
203      * @throws BadLocationException if the document got changed concurrently
204      */

205     private boolean indentLine(IDocument document, int line, int caret, JavaIndenter indenter, JavaHeuristicScanner scanner, boolean multiLine) throws BadLocationException {
206         IRegion currentLine= document.getLineInformation(line);
207         int offset= currentLine.getOffset();
208         int wsStart= offset; // where we start searching for non-WS; after the "//" in single line comments
209

210         String JavaDoc indent= null;
211         if (offset < document.getLength()) {
212             ITypedRegion partition= TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, true);
213             ITypedRegion startingPartition= TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, false);
214             String JavaDoc type= partition.getType();
215             if (type.equals(IJavaPartitions.JAVA_DOC) || type.equals(IJavaPartitions.JAVA_MULTI_LINE_COMMENT)) {
216                 indent= computeJavadocIndent(document, line, scanner, startingPartition);
217             } else if (!fIsTabAction && startingPartition.getOffset() == offset && startingPartition.getType().equals(IJavaPartitions.JAVA_SINGLE_LINE_COMMENT)) {
218                 
219                 // line comment starting at position 0 -> indent inside
220
int max= document.getLength() - offset;
221                 int slashes= 2;
222                 while (slashes < max - 1 && document.get(offset + slashes, 2).equals("//")) //$NON-NLS-1$
223
slashes+= 2;
224                 
225                 wsStart= offset + slashes;
226                 
227                 StringBuffer JavaDoc computed= indenter.computeIndentation(offset);
228                 if (computed == null)
229                     computed= new StringBuffer JavaDoc(0);
230                 int tabSize= getTabSize();
231                 while (slashes > 0 && computed.length() > 0) {
232                     char c= computed.charAt(0);
233                     if (c == '\t')
234                         if (slashes > tabSize)
235                             slashes-= tabSize;
236                         else
237                             break;
238                     else if (c == ' ')
239                         slashes--;
240                     else break;
241                     
242                     computed.deleteCharAt(0);
243                 }
244                 
245                 indent= document.get(offset, wsStart - offset) + computed;
246                 
247             }
248         }
249         
250         // standard java indentation
251
if (indent == null) {
252             StringBuffer JavaDoc computed= indenter.computeIndentation(offset);
253             if (computed != null)
254                 indent= computed.toString();
255             else
256                 indent= ""; //$NON-NLS-1$
257
}
258         
259         // change document:
260
// get current white space
261
int lineLength= currentLine.getLength();
262         int end= scanner.findNonWhitespaceForwardInAnyPartition(wsStart, offset + lineLength);
263         if (end == JavaHeuristicScanner.NOT_FOUND) {
264             // an empty line
265
end= offset + lineLength;
266             if (multiLine && !indentEmptyLines())
267                 indent= ""; //$NON-NLS-1$
268
}
269         int length= end - offset;
270         String JavaDoc currentIndent= document.get(offset, length);
271         
272         // if we are right before the text start / line end, and already after the insertion point
273
// then just insert a tab.
274
if (fIsTabAction && caret == end && whiteSpaceLength(currentIndent) >= whiteSpaceLength(indent)) {
275             String JavaDoc tab= getTabEquivalent();
276             document.replace(caret, 0, tab);
277             fCaretOffset= caret + tab.length();
278             return true;
279         }
280         
281         // set the caret offset so it can be used when setting the selection
282
if (caret >= offset && caret <= end)
283             fCaretOffset= offset + indent.length();
284         else
285             fCaretOffset= -1;
286         
287         // only change the document if it is a real change
288
if (!indent.equals(currentIndent)) {
289             document.replace(offset, length, indent);
290             return true;
291         } else
292             return false;
293     }
294
295     /**
296      * Computes and returns the indentation for a javadoc line. The line
297      * must be inside a javadoc comment.
298      *
299      * @param document the document
300      * @param line the line in document
301      * @param scanner the scanner
302      * @param partition the javadoc partition
303      * @return the indent, or <code>null</code> if not computable
304      * @throws BadLocationException
305      * @since 3.1
306      */

307     private String JavaDoc computeJavadocIndent(IDocument document, int line, JavaHeuristicScanner scanner, ITypedRegion partition) throws BadLocationException {
308         if (line == 0) // impossible - the first line is never inside a javadoc comment
309
return null;
310         
311         // don't make any assumptions if the line does not start with \s*\* - it might be
312
// commented out code, for which we don't want to change the indent
313
final IRegion lineInfo= document.getLineInformation(line);
314         final int lineStart= lineInfo.getOffset();
315         final int lineLength= lineInfo.getLength();
316         final int lineEnd= lineStart + lineLength;
317         int nonWS= scanner.findNonWhitespaceForwardInAnyPartition(lineStart, lineEnd);
318         if (nonWS == JavaHeuristicScanner.NOT_FOUND || document.getChar(nonWS) != '*') {
319             if (nonWS == JavaHeuristicScanner.NOT_FOUND)
320                 return document.get(lineStart, lineLength);
321             return document.get(lineStart, nonWS - lineStart);
322         }
323         
324         // take the indent from the previous line and reuse
325
IRegion previousLine= document.getLineInformation(line - 1);
326         int previousLineStart= previousLine.getOffset();
327         int previousLineLength= previousLine.getLength();
328         int previousLineEnd= previousLineStart + previousLineLength;
329         
330         StringBuffer JavaDoc buf= new StringBuffer JavaDoc();
331         int previousLineNonWS= scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
332         if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND || document.getChar(previousLineNonWS) != '*') {
333             // align with the comment start if the previous line is not an asterisked line
334
previousLine= document.getLineInformationOfOffset(partition.getOffset());
335             previousLineStart= previousLine.getOffset();
336             previousLineLength= previousLine.getLength();
337             previousLineEnd= previousLineStart + previousLineLength;
338             previousLineNonWS= scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
339             if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND)
340                 previousLineNonWS= previousLineEnd;
341             
342             // add the initial space
343
// TODO this may be controlled by a formatter preference in the future
344
buf.append(' ');
345         }
346         
347         String JavaDoc indentation= document.get(previousLineStart, previousLineNonWS - previousLineStart);
348         buf.insert(0, indentation);
349         return buf.toString();
350     }
351     
352     /**
353      * Returns the size in characters of a string. All characters count one, tabs count the editor's
354      * preference for the tab display
355      *
356      * @param indent the string to be measured.
357      * @return the size in characters of a string
358      */

359     private int whiteSpaceLength(String JavaDoc indent) {
360         if (indent == null)
361             return 0;
362         else {
363             int size= 0;
364             int l= indent.length();
365             int tabSize= getTabSize();
366             
367             for (int i= 0; i < l; i++)
368                 size += indent.charAt(i) == '\t' ? tabSize : 1;
369             return size;
370         }
371     }
372
373     /**
374      * Returns a tab equivalent, either as a tab character or as spaces, depending on the editor and
375      * formatter preferences.
376      *
377      * @return a string representing one tab in the editor, never <code>null</code>
378      */

379     private String JavaDoc getTabEquivalent() {
380         String JavaDoc tab;
381         if (JavaCore.SPACE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
382             int size= getTabSize();
383             StringBuffer JavaDoc buf= new StringBuffer JavaDoc();
384             for (int i= 0; i< size; i++)
385                 buf.append(' ');
386             tab= buf.toString();
387         } else
388             tab= "\t"; //$NON-NLS-1$
389

390         return tab;
391     }
392     
393     /**
394      * Returns the tab size used by the java editor, which is deduced from the
395      * formatter preferences.
396      *
397      * @return the tab size as defined in the current formatter preferences
398      */

399     private int getTabSize() {
400         return getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, 4);
401     }
402
403     /**
404      * Returns <code>true</code> if empty lines should be indented, false otherwise.
405      *
406      * @return <code>true</code> if empty lines should be indented, false otherwise
407      * @since 3.2
408      */

409     private boolean indentEmptyLines() {
410         return DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_EMPTY_LINES));
411     }
412     
413     /**
414      * Returns the possibly project-specific core preference defined under <code>key</code>.
415      *
416      * @param key the key of the preference
417      * @return the value of the preference
418      * @since 3.1
419      */

420     private String JavaDoc getCoreFormatterOption(String JavaDoc key) {
421         IJavaProject project= getJavaProject();
422         if (project == null)
423             return JavaCore.getOption(key);
424         return project.getOption(key, true);
425     }
426
427     /**
428      * Returns the possibly project-specific core preference defined under <code>key</code>, or
429      * <code>def</code> if the value is not a integer.
430      *
431      * @param key the key of the preference
432      * @param def the default value
433      * @return the value of the preference
434      * @since 3.1
435      */

436     private int getCoreFormatterOption(String JavaDoc key, int def) {
437         try {
438             return Integer.parseInt(getCoreFormatterOption(key));
439         } catch (NumberFormatException JavaDoc e) {
440             return def;
441         }
442     }
443
444     /**
445      * Returns the <code>IJavaProject</code> of the current editor input, or
446      * <code>null</code> if it cannot be found.
447      *
448      * @return the <code>IJavaProject</code> of the current editor input, or
449      * <code>null</code> if it cannot be found
450      * @since 3.1
451      */

452     private IJavaProject getJavaProject() {
453         ITextEditor editor= getTextEditor();
454         if (editor == null)
455             return null;
456         
457         ICompilationUnit cu= JavaPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editor.getEditorInput());
458         if (cu == null)
459             return null;
460         return cu.getJavaProject();
461     }
462
463     /**
464      * Returns the editor's selection provider.
465      *
466      * @return the editor's selection provider or <code>null</code>
467      */

468     private ISelectionProvider getSelectionProvider() {
469         ITextEditor editor= getTextEditor();
470         if (editor != null) {
471             return editor.getSelectionProvider();
472         }
473         return null;
474     }
475     
476     /*
477      * @see org.eclipse.ui.texteditor.IUpdate#update()
478      */

479     public void update() {
480         super.update();
481         
482         if (isEnabled())
483             if (fIsTabAction)
484                 setEnabled(canModifyEditor() && isSmartMode() && isValidSelection());
485             else
486                 setEnabled(canModifyEditor() && !getSelection().isEmpty());
487     }
488     
489     /**
490      * Returns if the current selection is valid, i.e. whether it is empty and the caret in the
491      * whitespace at the start of a line, or covers multiple lines.
492      *
493      * @return <code>true</code> if the selection is valid for an indent operation
494      */

495     private boolean isValidSelection() {
496         ITextSelection selection= getSelection();
497         if (selection.isEmpty())
498             return false;
499         
500         int offset= selection.getOffset();
501         int length= selection.getLength();
502         
503         IDocument document= getDocument();
504         if (document == null)
505             return false;
506         
507         try {
508             IRegion firstLine= document.getLineInformationOfOffset(offset);
509             int lineOffset= firstLine.getOffset();
510             
511             // either the selection has to be empty and the caret in the WS at the line start
512
// or the selection has to extend over multiple lines
513
if (length == 0)
514                 return document.get(lineOffset, offset - lineOffset).trim().length() == 0;
515             else
516 // return lineOffset + firstLine.getLength() < offset + length;
517
return false; // only enable for empty selections for now
518

519         } catch (BadLocationException e) {
520         }
521         
522         return false;
523     }
524     
525     /**
526      * Returns the smart preference state.
527      *
528      * @return <code>true</code> if smart mode is on, <code>false</code> otherwise
529      */

530     private boolean isSmartMode() {
531         ITextEditor editor= getTextEditor();
532         
533         if (editor instanceof ITextEditorExtension3)
534             return ((ITextEditorExtension3) editor).getInsertMode() == ITextEditorExtension3.SMART_INSERT;
535         
536         return false;
537     }
538     
539     /**
540      * Returns the document currently displayed in the editor, or <code>null</code> if none can be
541      * obtained.
542      *
543      * @return the current document or <code>null</code>
544      */

545     private IDocument getDocument() {
546         
547         ITextEditor editor= getTextEditor();
548         if (editor != null) {
549             
550             IDocumentProvider provider= editor.getDocumentProvider();
551             IEditorInput input= editor.getEditorInput();
552             if (provider != null && input != null)
553                 return provider.getDocument(input);
554             
555         }
556         return null;
557     }
558     
559     /**
560      * Returns the selection on the editor or an invalid selection if none can be obtained. Returns
561      * never <code>null</code>.
562      *
563      * @return the current selection, never <code>null</code>
564      */

565     private ITextSelection getSelection() {
566         ISelectionProvider provider= getSelectionProvider();
567         if (provider != null) {
568             
569             ISelection selection= provider.getSelection();
570             if (selection instanceof ITextSelection)
571                 return (ITextSelection) selection;
572         }
573         
574         // null object
575
return TextSelection.emptySelection();
576     }
577     
578 }
579
Popular Tags