blob: 22a48702de49afbf35644b8f2cd740d9d7943042 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2019 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* This is an implementation of an early-draft specification developed under the Java
* Community Process (JCP) and is made available for testing and evaluation purposes
* only. The code is not compatible with any specification of the JCP.
*
* Contributors:
* IBM Corporation - initial API and implementation
* Tom Eicher (Avaloq Evolution AG) - block selection mode
*******************************************************************************/
package org.eclipse.jdt.internal.ui.actions;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.ResourceBundle;
import org.eclipse.swt.custom.BusyIndicator;
import org.eclipse.swt.widgets.Display;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.texteditor.IDocumentProvider;
import org.eclipse.ui.texteditor.ITextEditor;
import org.eclipse.ui.texteditor.ITextEditorExtension3;
import org.eclipse.ui.texteditor.TextEditorAction;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.formatter.DefaultCodeFormatterConstants;
import org.eclipse.jdt.core.formatter.IndentManipulation;
import org.eclipse.jdt.internal.core.manipulation.util.Strings;
import org.eclipse.jdt.internal.corext.util.CodeFormatterUtil;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner;
import org.eclipse.jdt.internal.ui.text.JavaIndenter;
/**
* Indents a line or range of lines in a Java document to its correct position. No complete
* AST must be present, the indentation is computed using heuristics. The algorithm used is fast for
* single lines, but does not store any information and therefore not so efficient for large line
* ranges.
*
* @see org.eclipse.jdt.internal.ui.text.JavaHeuristicScanner
* @see org.eclipse.jdt.internal.ui.text.JavaIndenter
* @since 3.0
*/
public class IndentAction extends TextEditorAction {
/**
* @since 3.4
*/
private static final class ReplaceData {
/**
* The replacement
*/
public final String indent;
/**
* The start of the replacement
*/
public final int offset;
/**
* The end of the replacement
*/
public final int end;
/**
* Replace string in document from offset to end with indent
* @param offset the start of the replacement
* @param end the end of the replacement
* @param indent the replacement
*/
public ReplaceData(int offset, int end, String indent) {
this.indent= indent;
this.end= end;
this.offset= offset;
}
}
/** The caret offset after an indent operation. */
private int fCaretOffset;
/**
* Whether this is the action invoked by TAB. When <code>true</code>, indentation behaves
* differently to accommodate normal TAB operation.
*/
private final boolean fIsTabAction;
public static String TEXT_BLOCK_STR= "\"\"\""; //$NON-NLS-1$
public static String SPACE_STR= " "; //$NON-NLS-1$
public static String EMPTY_STR= ""; //$NON-NLS-1$
/**
* Creates a new instance.
*
* @param bundle the resource bundle
* @param prefix the prefix to use for keys in <code>bundle</code>
* @param editor the text editor
* @param isTabAction whether the action should insert tabs if over the indentation
*/
public IndentAction(ResourceBundle bundle, String prefix, ITextEditor editor, boolean isTabAction) {
super(bundle, prefix, editor);
fIsTabAction= isTabAction;
}
/*
* @see org.eclipse.jface.action.Action#run()
*/
@Override
public void run() {
// update has been called by the framework
if (!isEnabled() || !validateEditorInputState())
return;
ITextSelection selection= getSelection();
final IDocument document= getDocument();
if (document != null) {
final int offset= selection.getOffset();
final int length= selection.getLength();
final Position end= new Position(offset + length);
final int firstLine, nLines;
fCaretOffset= -1;
try {
document.addPosition(end);
firstLine= selection.getStartLine();
nLines= selection.getEndLine() - firstLine + 1;
} catch (BadLocationException e) {
// will only happen on concurrent modification
JavaPlugin.log(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "", e)); //$NON-NLS-1$
return;
}
Runnable runnable= new Runnable() {
@Override
public void run() {
IRewriteTarget target= getTextEditor().getAdapter(IRewriteTarget.class);
if (target != null)
target.beginCompoundChange();
try {
JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
JavaIndenter indenter= new JavaIndenter(document, scanner, getJavaProject());
final boolean multiLine= nLines > 1;
boolean hasChanged= false;
for (int i= 0; i < nLines; i++) {
hasChanged |= indentLine(document, firstLine + i, offset, indenter, scanner, multiLine);
}
// update caret position: move to new position when indenting just one line
// keep selection when indenting multiple
int newOffset, newLength;
if (!fIsTabAction && multiLine) {
newOffset= offset;
newLength= end.getOffset() - offset;
} else {
newOffset= fCaretOffset;
newLength= 0;
}
// always reset the selection if anything was replaced
// but not when we had a single line non-tab invocation
if (newOffset != -1 && (hasChanged || newOffset != offset || newLength != length))
selectAndReveal(newOffset, newLength);
document.removePosition(end);
} catch (BadLocationException e) {
// will only happen on concurrent modification
JavaPlugin.log(new Status(IStatus.ERROR, JavaPlugin.getPluginId(), IStatus.OK, "ConcurrentModification in IndentAction", e)); //$NON-NLS-1$
} finally {
if (target != null)
target.endCompoundChange();
}
}
};
if (nLines > 50) {
Display display= getTextEditor().getEditorSite().getWorkbenchWindow().getShell().getDisplay();
BusyIndicator.showWhile(display, runnable);
} else
runnable.run();
}
}
/**
* Selects the given range on the editor.
*
* @param newOffset the selection offset
* @param newLength the selection range
*/
private void selectAndReveal(int newOffset, int newLength) {
Assert.isTrue(newOffset >= 0);
Assert.isTrue(newLength >= 0);
ITextEditor editor= getTextEditor();
if (editor instanceof JavaEditor) {
ISourceViewer viewer= ((JavaEditor)editor).getViewer();
if (viewer != null)
viewer.setSelectedRange(newOffset, newLength);
} else
// this is too intrusive, but will never get called anyway
getTextEditor().selectAndReveal(newOffset, newLength);
}
/**
* Indent the given <code>document</code> based on the <code>project</code> settings and
* return a text edit describing the changes applied to the document. Returns <b>null</b>
* if no changes have been applied.
* <p>
* WARNING: This method does change the content of the given document.
* </p>
* <p>
* This method is for internal use only, it should not be called.
* </p>
*
* @param document the document to indent must have a java partitioning installed
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return a text edit describing the changes or <b>null</b> if no changes required
* @throws BadLocationException if the document got modified concurrently
*
* @since 3.4
*/
public static TextEdit indent(IDocument document, IJavaProject project) throws BadLocationException {
int offset= 0;
int length= document.getLength();
JavaHeuristicScanner scanner= new JavaHeuristicScanner(document);
JavaIndenter indenter= new JavaIndenter(document, scanner, project);
ArrayList<ReplaceEdit> edits= new ArrayList<>();
int firstLine= document.getLineOfOffset(offset);
// check for marginal (zero-length) lines
int minusOne= length == 0 ? 0 : 1;
int numberOfLines= document.getLineOfOffset(offset + length - minusOne) - firstLine + 1;
int shift= 0;
for (int i= 0; i < numberOfLines; i++) {
ReplaceData data= computeReplaceData(document, firstLine + i, indenter, scanner, numberOfLines > 1, false, project);
int replaceLength= data.end - data.offset;
String currentIndent= document.get(data.offset, replaceLength);
// only change the document if it is a real change
if (!data.indent.equals(currentIndent)) {
edits.add(new ReplaceEdit(data.offset + shift, replaceLength, data.indent));
//We need to change the document, the indenter depends on it.
document.replace(data.offset, replaceLength, data.indent);
shift-= data.indent.length() - replaceLength;
}
}
if (edits.size() == 0)
return null;
if (edits.size() == 1)
return edits.get(0);
MultiTextEdit result= new MultiTextEdit();
for (Iterator<ReplaceEdit> iterator= edits.iterator(); iterator.hasNext();) {
TextEdit edit= iterator.next();
result.addChild(edit);
}
return result;
}
/**
* Indents a single line using the java heuristic scanner. Javadoc and multiline comments are
* indented as specified by the <code>JavaDocAutoIndentStrategy</code>.
*
* @param document the document
* @param line the line to be indented
* @param indenter the java indenter
* @param scanner the heuristic scanner
* @param multiLine <code>true</code> if more than one line is being indented
* @param isTabAction <code>true</code> if this action has been invoked by TAB
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return <code>true</code> if <code>document</code> was modified, <code>false</code> otherwise
* @throws BadLocationException if the document got changed concurrently
*/
private static ReplaceData computeReplaceData(IDocument document, int line, JavaIndenter indenter, JavaHeuristicScanner scanner, boolean multiLine, boolean isTabAction, IJavaProject project) throws BadLocationException {
IRegion currentLine= document.getLineInformation(line);
int offset= currentLine.getOffset();
int wsStart= offset; // where we start searching for non-WS; after the "//" in single line comments
String indent= null;
if (offset < document.getLength()) {
ITypedRegion partition= TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, true);
String type= partition.getType();
ITypedRegion startingPartition= TextUtilities.getPartition(document, IJavaPartitions.JAVA_PARTITIONING, offset, false);
String startingType= startingPartition.getType();
boolean isCommentStart= startingPartition.getOffset() == offset;
if (isDontIndentMultiLineCommentOnFirstColumn(project) && isCommentStart && IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(startingType)) {
indent= ""; //$NON-NLS-1$
} else if (IJavaPartitions.JAVA_DOC.equals(type) || IJavaPartitions.JAVA_MULTI_LINE_COMMENT.equals(type)) {
indent= computeJavadocIndent(document, line, scanner, startingPartition);
} else if (!isTabAction && isCommentStart && IJavaPartitions.JAVA_SINGLE_LINE_COMMENT.equals(startingType)) {
// line comment starting at position 0
if (multiLine) {
//Do what the formatter does
if (isDontIndentSingleLineCommentOnFirstColumn(project))
indent= ""; //$NON-NLS-1$
} else {
//indent inside -> add/remove indent such that user can start typing at correct position
int slashes= countLeadingSlashPairs(document, offset) * 2;
wsStart= offset + slashes;
StringBuffer computed= indenter.computeIndentation(offset);
if (computed == null)
computed= new StringBuffer(0);
removeIndentations(slashes, getTabSize(project), computed);
indent= document.get(offset, wsStart - offset) + computed;
}
} else if (IJavaPartitions.JAVA_MULTI_LINE_STRING.equals(type)) {
String fullStrNoTrim= document.get(currentLine.getOffset(), currentLine.getLength());
String fullStr= document.get(currentLine.getOffset(), currentLine.getLength()).trim();
int length= IndentManipulation.measureIndentInSpaces(fullStrNoTrim, CodeFormatterUtil.getTabWidth(project));
int partitionOffset= partition.getOffset();
IRegion PartitionStartline= document.getLineInformationOfOffset(partitionOffset);
String PartitionStartStrNoTrim= document.get(PartitionStartline.getOffset(), PartitionStartline.getLength());
int startIndex= PartitionStartStrNoTrim.lastIndexOf(TEXT_BLOCK_STR);
if (!fullStrNoTrim.equals(PartitionStartStrNoTrim) && startIndex != -1) {
int partitionStartLength= measureLengthInSpaces(PartitionStartStrNoTrim.substring(0, startIndex), CodeFormatterUtil.getTabWidth(project));
boolean calculateIndent= false;
String str= EMPTY_STR;
int strLength= 0;
if (partitionStartLength > length) {
calculateIndent= true;
strLength= length;
} else if (fullStr.startsWith(TEXT_BLOCK_STR) && partitionStartLength != length) {
calculateIndent= true;
strLength= partitionStartLength;
} else {
indent= getLineIndentation(document, currentLine.getOffset());
}
if (calculateIndent) {
for (int i= 0; i < strLength; i++) {
str+= SPACE_STR;
}
int units= Strings.computeIndentUnits(str, project);
String newStr= CodeFormatterUtil.createIndentString(units, project);
int newLength= IndentManipulation.measureIndentInSpaces(newStr, CodeFormatterUtil.getTabWidth(project));
if (newLength < partitionStartLength) {
for (int i= newLength; i < partitionStartLength; i++) {
newStr+= SPACE_STR;
}
}
indent= newStr;
}
}
}
}
// standard java indentation
if (indent == null) {
StringBuffer computed= indenter.computeIndentation(offset);
if (computed != null)
indent= computed.toString();
else
indent= ""; //$NON-NLS-1$
}
// change document:
// get current white space
int lineLength= currentLine.getLength();
int end= scanner.findNonWhitespaceForwardInAnyPartition(wsStart, offset + lineLength);
if (end == JavaHeuristicScanner.NOT_FOUND) {
// an empty line
end= offset + lineLength;
if (multiLine && !indentEmptyLines(project))
indent= ""; //$NON-NLS-1$
}
return new ReplaceData(offset, end, indent);
}
private static String getLineIndentation(IDocument document, int offset) throws BadLocationException {
// find start of line
int adjustedOffset= (offset == document.getLength() ? offset - 1 : offset);
IRegion line= document.getLineInformationOfOffset(adjustedOffset);
int start= line.getOffset();
// find white spaces
int end= findEndOfWhiteSpace(document, start, offset + line.getLength());
return document.get(start, end - start);
}
private static int findEndOfWhiteSpace(IDocument document, int offset, int end) throws BadLocationException {
while (offset < end) {
char c= document.getChar(offset);
if (c != ' ' && c != '\t') {
return offset;
}
offset++;
}
return end;
}
public static int measureLengthInSpaces(CharSequence line, int tabWidth) {
if (tabWidth < 0 || line == null) {
throw new IllegalArgumentException();
}
int length= 0;
int max= line.length();
for (int i= 0; i < max; i++) {
char ch= line.charAt(i);
if (ch == '\t') {
length= calculateSpaceEquivalents(tabWidth, length);
} else {
length++;
}
}
return length;
}
private static int calculateSpaceEquivalents(int tabWidth, int spaceEquivalents) {
if (tabWidth == 0) {
return spaceEquivalents;
}
int remainder= spaceEquivalents % tabWidth;
spaceEquivalents+= tabWidth - remainder;
return spaceEquivalents;
}
/**
* Removes <code>count</code> indentations from start
* of <code>buffer</code>. The size of a space character
* is 1 and the size of a tab character is <code>tabSize</code>.
*
* @param count the number of indentations to remove
* @param tabSize the size of a tab character
* @param buffer the buffer to modify
* @since 3.4
*/
private static void removeIndentations(int count, int tabSize, StringBuffer buffer) {
while (count > 0 && buffer.length() > 0) {
char c= buffer.charAt(0);
if (c == '\t')
if (count > tabSize)
count-= tabSize;
else
break;
else if (c == ' ')
count--;
else break;
buffer.deleteCharAt(0);
}
}
/**
* Returns number of continuous slashes pairs ('//') starting at <code>offset</code>
* in <code>document</code>
*
* @param document the document to inspect
* @param offset the offset where to start looking for slash pairs
* @return the number of slash pairs.
* @throws BadLocationException
* @since 3.4
*/
private static int countLeadingSlashPairs(IDocument document, int offset) throws BadLocationException {
IRegion lineInfo= document.getLineInformationOfOffset(offset);
int max= lineInfo.getOffset() + lineInfo.getLength() - 1;
int pairCount= 0;
while (offset < max && document.get(offset, 2).equals("//")) { //$NON-NLS-1$
pairCount++;
offset= offset + 2;
}
return pairCount;
}
/**
* Indents a single line using the java heuristic scanner. Javadoc and multiline comments are
* indented as specified by the <code>JavaDocAutoIndentStrategy</code>.
*
* @param document the document
* @param line the line to be indented
* @param caret the caret position
* @param indenter the java indenter
* @param scanner the heuristic scanner
* @param multiLine <code>true</code> if more than one line is being indented
* @return <code>true</code> if <code>document</code> was modified, <code>false</code> otherwise
* @throws BadLocationException if the document got changed concurrently
*/
private boolean indentLine(IDocument document, int line, int caret, JavaIndenter indenter, JavaHeuristicScanner scanner, boolean multiLine) throws BadLocationException {
IJavaProject project= getJavaProject();
ReplaceData data= computeReplaceData(document, line, indenter, scanner, multiLine, fIsTabAction, project);
String indent= data.indent;
int end= data.end;
int offset= data.offset;
int length= end - offset;
String currentIndent= document.get(offset, length);
// if we are right before the text start / line end, and already after the insertion point
// then just insert a tab.
if (fIsTabAction && caret == end && whiteSpaceLength(currentIndent, project) >= whiteSpaceLength(indent, project)) {
String tab= getTabEquivalent(project);
document.replace(caret, 0, tab);
fCaretOffset= caret + tab.length();
return true;
}
// set the caret offset so it can be used when setting the selection
if (caret >= offset && caret <= end)
fCaretOffset= offset + indent.length();
else
fCaretOffset= -1;
// only change the document if it is a real change
if (!indent.equals(currentIndent)) {
document.replace(offset, length, indent);
return true;
} else
return false;
}
/**
* Computes and returns the indentation for a javadoc line. The line
* must be inside a javadoc comment.
*
* @param document the document
* @param line the line in document
* @param scanner the scanner
* @param partition the javadoc partition
* @return the indent, or <code>null</code> if not computable
* @throws BadLocationException
* @since 3.1
*/
private static String computeJavadocIndent(IDocument document, int line, JavaHeuristicScanner scanner, ITypedRegion partition) throws BadLocationException {
if (line == 0) // impossible - the first line is never inside a javadoc comment
return null;
// don't make any assumptions if the line does not start with \s*\* - it might be
// commented out code, for which we don't want to change the indent
final IRegion lineInfo= document.getLineInformation(line);
final int lineStart= lineInfo.getOffset();
final int lineLength= lineInfo.getLength();
final int lineEnd= lineStart + lineLength;
int nonWS= scanner.findNonWhitespaceForwardInAnyPartition(lineStart, lineEnd);
if (nonWS == JavaHeuristicScanner.NOT_FOUND || document.getChar(nonWS) != '*') {
if (nonWS == JavaHeuristicScanner.NOT_FOUND)
return document.get(lineStart, lineLength);
return document.get(lineStart, nonWS - lineStart);
}
// take the indent from the previous line and reuse
IRegion previousLine= document.getLineInformation(line - 1);
int previousLineStart= previousLine.getOffset();
int previousLineLength= previousLine.getLength();
int previousLineEnd= previousLineStart + previousLineLength;
StringBuilder buf= new StringBuilder();
int previousLineNonWS= scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND || document.getChar(previousLineNonWS) != '*') {
// align with the comment start if the previous line is not an asterisked line
previousLine= document.getLineInformationOfOffset(partition.getOffset());
previousLineStart= previousLine.getOffset();
previousLineLength= previousLine.getLength();
previousLineEnd= previousLineStart + previousLineLength;
previousLineNonWS= scanner.findNonWhitespaceForwardInAnyPartition(previousLineStart, previousLineEnd);
if (previousLineNonWS == JavaHeuristicScanner.NOT_FOUND)
previousLineNonWS= previousLineEnd;
// add the initial space
// TODO this may be controlled by a formatter preference in the future
buf.append(' ');
}
String indentation= document.get(previousLineStart, previousLineNonWS - previousLineStart);
buf.insert(0, indentation);
return buf.toString();
}
/**
* Returns the size in characters of a string. All characters count one, tabs count the editor's
* preference for the tab display
*
* @param indent the string to be measured.
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return the size in characters of a string
*/
private static int whiteSpaceLength(String indent, IJavaProject project) {
if (indent == null)
return 0;
else {
int size= 0;
int l= indent.length();
int tabSize= getTabSize(project);
for (int i= 0; i < l; i++)
size += indent.charAt(i) == '\t' ? tabSize : 1;
return size;
}
}
/**
* Returns a tab equivalent, either as a tab character or as spaces, depending on the editor and
* formatter preferences.
*
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return a string representing one tab in the editor, never <code>null</code>
*/
private static String getTabEquivalent(IJavaProject project) {
String tab;
if (JavaCore.SPACE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR, project))) {
int size= getTabSize(project);
StringBuilder buf= new StringBuilder();
for (int i= 0; i< size; i++)
buf.append(' ');
tab= buf.toString();
} else
tab= "\t"; //$NON-NLS-1$
return tab;
}
/**
* Returns the tab size used by the java editor, which is deduced from the
* formatter preferences.
*
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return the tab size as defined in the current formatter preferences
*/
private static int getTabSize(IJavaProject project) {
return getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE, 4, project);
}
/**
* Returns <code>true</code> if empty lines should be indented, <code>false</code> otherwise.
*
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return <code>true</code> if empty lines should be indented, <code>false</code> otherwise
* @since 3.2
*/
private static boolean indentEmptyLines(IJavaProject project) {
return DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_EMPTY_LINES, project));
}
/**
* Returns <code>true</code> if multi line comments which start at first column
* should not be indented, <code>false</code> otherwise.
*
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return <code>true</code> if such multi line comments should be indented, <code>false</code> otherwise
* @since 3.4
*/
private static boolean isDontIndentMultiLineCommentOnFirstColumn(IJavaProject project) {
return DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_NEVER_INDENT_BLOCK_COMMENTS_ON_FIRST_COLUMN, project));
}
/**
* Returns <code>true</code> if single line comments which start at first column
* should not be indented, <code>false</code> otherwise.
*
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return <code>true</code> if such single line comments should be indented, <code>false</code> otherwise
* @since 3.4
*/
private static boolean isDontIndentSingleLineCommentOnFirstColumn(IJavaProject project) {
return DefaultCodeFormatterConstants.TRUE.equals(getCoreFormatterOption(DefaultCodeFormatterConstants.FORMATTER_NEVER_INDENT_LINE_COMMENTS_ON_FIRST_COLUMN, project));
}
/**
* Returns the possibly project-specific core preference defined under <code>key</code>.
*
* @param key the key of the preference
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return the value of the preference
* @since 3.1
*/
private static String getCoreFormatterOption(String key, IJavaProject project) {
if (project == null)
return JavaCore.getOption(key);
return project.getOption(key, true);
}
/**
* Returns the possibly project-specific core preference defined under <code>key</code>, or
* <code>def</code> if the value is not a integer.
*
* @param key the key of the preference
* @param def the default value
* @param project the project to retrieve the indentation settings from, <b>null</b> for workspace settings
* @return the value of the preference
* @since 3.1
*/
private static int getCoreFormatterOption(String key, int def, IJavaProject project) {
try {
return Integer.parseInt(getCoreFormatterOption(key, project));
} catch (NumberFormatException e) {
return def;
}
}
/**
* Returns the <code>IJavaProject</code> of the current editor input, or
* <code>null</code> if it cannot be found.
*
* @return the <code>IJavaProject</code> of the current editor input, or
* <code>null</code> if it cannot be found
* @since 3.1
*/
private IJavaProject getJavaProject() {
ITextEditor editor= getTextEditor();
if (editor == null)
return null;
ICompilationUnit cu= JavaPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(editor.getEditorInput());
if (cu == null)
return null;
return cu.getJavaProject();
}
/**
* Returns the editor's selection provider.
*
* @return the editor's selection provider or <code>null</code>
*/
private ISelectionProvider getSelectionProvider() {
ITextEditor editor= getTextEditor();
if (editor != null) {
return editor.getSelectionProvider();
}
return null;
}
/*
* @see org.eclipse.ui.texteditor.IUpdate#update()
*/
@Override
public void update() {
super.update();
if (isEnabled())
if (fIsTabAction)
setEnabled(canModifyEditor() && isSmartMode() && isValidSelection());
else
setEnabled(canModifyEditor() && !getSelection().isEmpty());
}
/**
* Returns if the current selection is valid, i.e. whether it is empty and the caret in the
* whitespace at the start of a line, or covers multiple lines.
*
* @return <code>true</code> if the selection is valid for an indent operation
*/
private boolean isValidSelection() {
ITextSelection selection= getSelection();
if (selection.isEmpty())
return false;
int offset= selection.getOffset();
int length= selection.getLength();
IDocument document= getDocument();
if (document == null)
return false;
try {
IRegion firstLine= document.getLineInformationOfOffset(offset);
int lineOffset= firstLine.getOffset();
// either the selection has to be empty and the caret in the WS at the line start
// or the selection has to extend over multiple lines
if (length == 0)
return document.get(lineOffset, offset - lineOffset).trim().length() == 0;
else
// return lineOffset + firstLine.getLength() < offset + length;
return false; // only enable for empty selections for now
} catch (BadLocationException e) {
}
return false;
}
/**
* Returns the smart preference state.
*
* @return <code>true</code> if smart mode is on, <code>false</code> otherwise
*/
private boolean isSmartMode() {
ITextEditor editor= getTextEditor();
if (editor instanceof ITextEditorExtension3)
return ((ITextEditorExtension3) editor).getInsertMode() == ITextEditorExtension3.SMART_INSERT;
return false;
}
/**
* Returns the document currently displayed in the editor, or <code>null</code> if none can be
* obtained.
*
* @return the current document or <code>null</code>
*/
private IDocument getDocument() {
ITextEditor editor= getTextEditor();
if (editor != null) {
IDocumentProvider provider= editor.getDocumentProvider();
IEditorInput input= editor.getEditorInput();
if (provider != null && input != null)
return provider.getDocument(input);
}
return null;
}
/**
* Returns the selection on the editor or an invalid selection if none can be obtained. Returns
* never <code>null</code>.
*
* @return the current selection, never <code>null</code>
*/
private ITextSelection getSelection() {
ISelectionProvider provider= getSelectionProvider();
if (provider != null) {
ISelection selection= provider.getSelection();
if (selection instanceof ITextSelection)
return (ITextSelection) selection;
}
// null object
return TextSelection.emptySelection();
}
}