/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Common Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/cpl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package net.sourceforge.phpdt.internal.ui.text; import net.sourceforge.phpdt.core.JavaCore; import net.sourceforge.phpdt.core.formatter.DefaultCodeFormatterConstants; import net.sourceforge.phpeclipse.PHPeclipsePlugin; import org.eclipse.core.runtime.Plugin; import org.eclipse.jface.text.Assert; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants; /** * Uses the {@link net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner}to * get the indentation level for a certain position in a document. * *
* An instance holds some internal position in the document and is therefore * not threadsafe. *
* * @since 3.0 */ public class JavaIndenter { /** The document being scanned. */ private IDocument fDocument; /** The indentation accumulated byfindPreviousIndenationUnit
. */
private int fIndent;
/**
* The absolute (character-counted) indentation offset for special cases
* (method defs, array initializers)
*/
private int fAlign;
/** The stateful scanposition for the indentation methods. */
private int fPosition;
/** The previous position. */
private int fPreviousPos;
/** The most recent token. */
private int fToken;
/** The line of fPosition
. */
private int fLine;
/**
* The scanner we will use to scan the document. It has to be installed
* on the same document as the one we get.
*/
private JavaHeuristicScanner fScanner;
/**
* Creates a new instance.
*
* @param document the document to scan
* @param scanner the {@link JavaHeuristicScanner} to be used for scanning
* the document. It must be installed on the same IDocument
.
*/
public JavaIndenter(IDocument document, JavaHeuristicScanner scanner) {
Assert.isNotNull(document);
Assert.isNotNull(scanner);
fDocument= document;
fScanner= scanner;
}
/**
* Computes the indentation at the reference point of position
.
*
* @param offset the offset in the document
* @return a String which reflects the indentation at the line in which the
* reference position to offset
resides, or null
* if it cannot be determined
*/
public StringBuffer getReferenceIndentation(int offset) {
return getReferenceIndentation(offset, false);
}
/**
* Computes the indentation at the reference point of position
.
*
* @param offset the offset in the document
* @param assumeOpeningBrace true
if an opening brace should be assumed
* @return a String which reflects the indentation at the line in which the
* reference position to offset
resides, or null
* if it cannot be determined
*/
private StringBuffer getReferenceIndentation(int offset, boolean assumeOpeningBrace) {
int unit;
if (assumeOpeningBrace)
unit= findReferencePosition(offset, Symbols.TokenLBRACE);
else
unit= findReferencePosition(offset, peekChar(offset));
// if we were unable to find anything, return null
if (unit == JavaHeuristicScanner.NOT_FOUND)
return null;
return getLeadingWhitespace(unit);
}
/**
* Computes the indentation at offset
.
*
* @param offset the offset in the document
* @return a String which reflects the correct indentation for the line in
* which offset resides, or null
if it cannot be
* determined
*/
public StringBuffer computeIndentation(int offset) {
return computeIndentation(offset, false);
}
/**
* Computes the indentation at offset
.
*
* @param offset the offset in the document
* @param assumeOpeningBrace true
if an opening brace should be assumed
* @return a String which reflects the correct indentation for the line in
* which offset resides, or null
if it cannot be
* determined
*/
public StringBuffer computeIndentation(int offset, boolean assumeOpeningBrace) {
StringBuffer indent= getReferenceIndentation(offset, assumeOpeningBrace);
// handle special alignment
if (fAlign != JavaHeuristicScanner.NOT_FOUND) {
try {
// a special case has been detected.
IRegion line= fDocument.getLineInformationOfOffset(fAlign);
int lineOffset= line.getOffset();
return createIndent(lineOffset, fAlign);
} catch (BadLocationException e) {
return null;
}
}
if (indent == null)
return null;
// add additional indent
indent.append(createIndent(fIndent));
if (fIndent < 0)
unindent(indent);
return indent;
}
/**
* Returns the indentation of the line at offset
as a
* StringBuffer
. If the offset is not valid, the empty string
* is returned.
*
* @param offset the offset in the document
* @return the indentation (leading whitespace) of the line in which
* offset
is located
*/
private StringBuffer getLeadingWhitespace(int offset) {
StringBuffer indent= new StringBuffer();
try {
IRegion line= fDocument.getLineInformationOfOffset(offset);
int lineOffset= line.getOffset();
int nonWS= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, lineOffset + line.getLength());
indent.append(fDocument.get(lineOffset, nonWS - lineOffset));
return indent;
} catch (BadLocationException e) {
return indent;
}
}
/**
* Reduces indentation in indent
by one indentation unit.
*
* @param indent the indentation to be modified
*/
private void unindent(StringBuffer indent) {
CharSequence oneIndent= createIndent();
int i= indent.lastIndexOf(oneIndent.toString()); //$NON-NLS-1$
if (i != -1) {
indent.delete(i, i + oneIndent.length());
}
}
/**
* Creates an indentation string of the length indent - start + 1,
* consisting of the content in fDocument
in the range
* [start, indent), with every character replaced by a space except for
* tabs, which are kept as such.
*
* Every run of the number of spaces that make up a tab are replaced * by a tab character.
* * @return the indentation corresponding to the document content specified * bystart
and indent
*/
private StringBuffer createIndent(int start, int indent) {
final int tabLen= prefTabLength();
StringBuffer ret= new StringBuffer();
try {
int spaces= 0;
while (start < indent) {
char ch= fDocument.getChar(start);
if (ch == '\t') {
ret.append('\t');
spaces= 0;
} else if (tabLen == -1){
ret.append(' ');
} else {
spaces++;
if (spaces == tabLen) {
ret.append('\t');
spaces= 0;
}
}
start++;
}
// remainder
if (spaces == tabLen)
ret.append('\t');
else
while (spaces-- > 0)
ret.append(' ');
} catch (BadLocationException e) {
}
return ret;
}
/**
* Creates a string that represents the given number of indents (can be
* spaces or tabs..)
*
* @param indent the requested indentation level.
*
* @return the indentation specified by indent
*/
public StringBuffer createIndent(int indent) {
StringBuffer oneIndent= createIndent();
StringBuffer ret= new StringBuffer();
while (indent-- > 0)
ret.append(oneIndent);
return ret;
}
/**
* Creates a string that represents one indent (can be
* spaces or tabs..)
*
* @return one indentation
*/
private StringBuffer createIndent() {
// get a sensible default when running without the infrastructure for testing
StringBuffer oneIndent= new StringBuffer();
// JavaCore plugin= JavaCore.getJavaCore();
PHPeclipsePlugin plugin = PHPeclipsePlugin.getDefault();
if (plugin == null) {
oneIndent.append('\t');
} else {
if (JavaCore.SPACE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR))) {
int tabLen= Integer.parseInt(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE));
for (int i= 0; i < tabLen; i++)
oneIndent.append(' ');
} else if (JavaCore.TAB.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
oneIndent.append('\t');
else
oneIndent.append('\t'); // default
}
return oneIndent;
}
/**
* Returns the reference position regarding to indentation for offset
,
* or NOT_FOUND
. This method calls
* {@link #findReferencePosition(int, int) findReferencePosition(offset, nextChar)} where
* nextChar
is the next character after offset
.
*
* @param offset the offset for which the reference is computed
* @return the reference statement relative to which offset
* should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
*/
public int findReferencePosition(int offset) {
return findReferencePosition(offset, peekChar(offset));
}
/**
* Peeks the next char in the document that comes after offset
* on the same line as offset
.
*
* @param offset the offset into document
* @return the token symbol of the next element, or TokenEOF if there is none
*/
private int peekChar(int offset) {
if (offset < fDocument.getLength()) {
try {
IRegion line= fDocument.getLineInformationOfOffset(offset);
int lineOffset= line.getOffset();
int next= fScanner.nextToken(offset, lineOffset + line.getLength());
return next;
} catch (BadLocationException e) {
}
}
return Symbols.TokenEOF;
}
/**
* Returns the reference position regarding to indentation for position
,
* or NOT_FOUND
.
*
* If peekNextChar
is true
, the next token after
* offset
is read and taken into account when computing the
* indentation. Currently, if the next token is the first token on the line
* (i.e. only preceded by whitespace), the following tokens are specially
* handled:
*
switch
labels are indented relative to the switch blockelse
keyword is aligned with its if
, anything
* else is aligned normally (i.e. with the base of any introducing statements).offset
, the indentation
* is the same as for an else
keywordoffset
* should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
*/
public int findReferencePosition(int offset, int nextToken) {
boolean danglingElse= false;
boolean unindent= false;
boolean indent= false;
boolean matchBrace= false;
boolean matchParen= false;
boolean matchCase= false;
// account for unindenation characters already typed in, but after position
// if they are on a line by themselves, the indentation gets adjusted
// accordingly
//
// also account for a dangling else
if (offset < fDocument.getLength()) {
try {
IRegion line= fDocument.getLineInformationOfOffset(offset);
int lineOffset= line.getOffset();
int prevPos= Math.max(offset - 1, 0);
boolean isFirstTokenOnLine= fDocument.get(lineOffset, prevPos + 1 - lineOffset).trim().length() == 0;
int prevToken= fScanner.previousToken(prevPos, JavaHeuristicScanner.UNBOUND);
boolean bracelessBlockStart= fScanner.isBracelessBlockStart(prevPos, JavaHeuristicScanner.UNBOUND);
switch (nextToken) {
case Symbols.TokenEOF:
case Symbols.TokenELSE:
danglingElse= true;
break;
case Symbols.TokenCASE:
case Symbols.TokenDEFAULT:
if (isFirstTokenOnLine)
matchCase= true;
break;
case Symbols.TokenLBRACE: // for opening-brace-on-new-line style
// if (bracelessBlockStart && !prefIndentBracesForBlocks())
// unindent= true;
// else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) && !prefIndentBracesForArrays())
// unindent= true;
// else if (!bracelessBlockStart && prefIndentBracesForMethods())
// indent= true;
// break;
if (bracelessBlockStart )
unindent= true;
else if ((prevToken == Symbols.TokenCOLON || prevToken == Symbols.TokenEQUAL || prevToken == Symbols.TokenRBRACKET) )
unindent= true;
else if (!bracelessBlockStart)
indent= true;
break;
case Symbols.TokenRBRACE: // closing braces get unindented
if (isFirstTokenOnLine)
matchBrace= true;
break;
case Symbols.TokenRPAREN:
if (isFirstTokenOnLine)
matchParen= true;
break;
}
} catch (BadLocationException e) {
}
} else {
// assume an else could come if we are at the end of file
danglingElse= true;
}
int ref= findReferencePosition(offset, danglingElse, matchBrace, matchParen, matchCase);
if (unindent)
fIndent--;
if (indent)
fIndent++;
return ref;
}
/**
* Returns the reference position regarding to indentation for position
,
* or NOT_FOUND
.fIndent
will contain the
* relative indentation (in indentation units, not characters) after the
* call. If there is a special alignment (e.g. for a method declaration
* where parameters should be aligned), fAlign
will contain
* the absolute position of the alignment reference in fDocument
,
* otherwise fAlign
is set to JavaHeuristicScanner.NOT_FOUND
.
*
* @param offset the offset for which the reference is computed
* @param danglingElse whether a dangling else should be assumed at position
* @param matchBrace whether the position of the matching brace should be
* returned instead of doing code analysis
* @param matchParen whether the position of the matching parenthesis
* should be returned instead of doing code analysis
* @param matchCase whether the position of a switch statement reference
* should be returned (either an earlier case statement or the
* switch block brace)
* @return the reference statement relative to which position
* should be indented, or {@link JavaHeuristicScanner#NOT_FOUND}
*/
public int findReferencePosition(int offset, boolean danglingElse, boolean matchBrace, boolean matchParen, boolean matchCase) {
fIndent= 0; // the indentation modification
fAlign= JavaHeuristicScanner.NOT_FOUND;
fPosition= offset;
// forward cases
// an unindentation happens sometimes if the next token is special, namely on braces, parens and case labels
// align braces, but handle the case where we align with the method declaration start instead of
// the opening brace.
// if (matchBrace) {
// if (skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE)) {
// try {
// // align with the opening brace that is on a line by its own
// int lineOffset= fDocument.getLineOffset(fLine);
// if (lineOffset <= fPosition && fDocument.get(lineOffset, fPosition - lineOffset).trim().length() == 0)
// return fPosition;
// } catch (BadLocationException e) {
// // concurrent modification - walk default path
// }
// // if the opening brace is not on the start of the line, skip to the start
// int pos= skipToStatementStart(true, true);
// fIndent= 0; // indent is aligned with reference position
// return pos;
// } else {
// // if we can't find the matching brace, the heuristic is to unindent
// // by one against the normal position
// int pos= findReferencePosition(offset, danglingElse, false, matchParen, matchCase);
// fIndent--;
// return pos;
// }
// }
// align parenthesis'
if (matchParen) {
if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN))
return fPosition;
else {
// if we can't find the matching paren, the heuristic is to unindent
// by one against the normal position
int pos= findReferencePosition(offset, danglingElse, matchBrace, false, matchCase);
fIndent--;
return pos;
}
}
// the only reliable way to get case labels aligned (due to many different styles of using braces in a block)
// is to go for another case statement, or the scope opening brace
// if (matchCase) {
// return matchCaseAlignment();
// }
nextToken();
switch (fToken) {
case Symbols.TokenRBRACE:
// skip the block and fall through
// if we can't complete the scope, reset the scan position
int pos= fPosition;
if (!skipScope())
fPosition= pos;
case Symbols.TokenSEMICOLON:
// this is the 90% case: after a statement block
// the end of the previous statement / block previous.end
// search to the end of the statement / block before the previous; the token just after that is previous.start
return skipToStatementStart(danglingElse, false);
// scope introduction: special treat who special is
case Symbols.TokenLPAREN:
case Symbols.TokenLBRACE:
case Symbols.TokenLBRACKET:
return handleScopeIntroduction(offset + 1);
case Symbols.TokenEOF:
// trap when hitting start of document
return 0;
case Symbols.TokenEQUAL:
// indent assignments
fIndent= prefAssignmentIndent();
return fPosition;
case Symbols.TokenCOLON:
// TODO handle ternary deep indentation
fIndent= prefCaseBlockIndent();
return fPosition;
case Symbols.TokenQUESTIONMARK:
if (prefTernaryDeepAlign()) {
setFirstElementAlignment(fPosition, offset + 1);
return fPosition;
} else {
fIndent= prefTernaryIndent();
return fPosition;
}
// indentation for blockless introducers:
case Symbols.TokenDO:
case Symbols.TokenWHILE:
case Symbols.TokenELSE:
fIndent= prefSimpleIndent();
return fPosition;
case Symbols.TokenRPAREN:
int line= fLine;
if (skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN)) {
int scope= fPosition;
nextToken();
if (fToken == Symbols.TokenIF || fToken == Symbols.TokenWHILE || fToken == Symbols.TokenFOR) {
fIndent= prefSimpleIndent();
return fPosition;
}
fPosition= scope;
if (looksLikeMethodDecl()) {
return skipToStatementStart(danglingElse, false);
}
}
// restore
fPosition= offset;
fLine= line;
// else: fall through to default
case Symbols.TokenCOMMA:
// inside a list of some type
// easy if there is already a list item before with its own indentation - we just align
// if not: take the start of the list ( LPAREN, LBRACE, LBRACKET ) and either align or
// indent by list-indent
default:
// inside whatever we don't know about: similar to the list case:
// if we are inside a continued expression, then either align with a previous line that has indentation
// or indent from the expression start line (either a scope introducer or the start of the expr).
return skipToPreviousListItemOrListStart();
}
}
/**
* Skips to the start of a statement that ends at the current position.
*
* @param danglingElse whether to indent aligned with the last if
* @param isInBlock whether the current position is inside a block, which limits the search scope to the next scope introducer
* @return the reference offset of the start of the statement
*/
private int skipToStatementStart(boolean danglingElse, boolean isInBlock) {
while (true) {
nextToken();
if (isInBlock) {
switch (fToken) {
// exit on all block introducers
case Symbols.TokenIF:
case Symbols.TokenELSE:
case Symbols.TokenSYNCHRONIZED:
case Symbols.TokenCOLON:
case Symbols.TokenSTATIC:
case Symbols.TokenCATCH:
case Symbols.TokenDO:
case Symbols.TokenWHILE:
case Symbols.TokenFINALLY:
case Symbols.TokenFOR:
case Symbols.TokenTRY:
return fPosition;
case Symbols.TokenSWITCH:
fIndent= prefCaseIndent();
return fPosition;
}
}
switch (fToken) {
// scope introduction through: LPAREN, LBRACE, LBRACKET
// search stop on SEMICOLON, RBRACE, COLON, EOF
// -> the next token is the start of the statement (i.e. previousPos when backward scanning)
case Symbols.TokenLPAREN:
case Symbols.TokenLBRACE:
case Symbols.TokenLBRACKET:
case Symbols.TokenSEMICOLON:
case Symbols.TokenEOF:
return fPreviousPos;
case Symbols.TokenCOLON:
int pos= fPreviousPos;
if (!isConditional())
return pos;
break;
case Symbols.TokenRBRACE:
// RBRACE is a little tricky: it can be the end of an array definition, but
// usually it is the end of a previous block
pos= fPreviousPos; // store state
if (skipScope() && looksLikeArrayInitializerIntro())
continue; // it's an array
else
return pos; // it's not - do as with all the above
// scopes: skip them
case Symbols.TokenRPAREN:
case Symbols.TokenRBRACKET:
pos= fPreviousPos;
if (skipScope())
break;
else
return pos;
// IF / ELSE: align the position after the conditional block with the if
// so we are ready for an else, except if danglingElse is false
// in order for this to work, we must skip an else to its if
case Symbols.TokenIF:
if (danglingElse)
return fPosition;
else
break;
case Symbols.TokenELSE:
// skip behind the next if, as we have that one covered
pos= fPosition;
if (skipNextIF())
break;
else
return pos;
case Symbols.TokenDO:
// align the WHILE position with its do
return fPosition;
case Symbols.TokenWHILE:
// this one is tricky: while can be the start of a while loop
// or the end of a do - while
pos= fPosition;
if (hasMatchingDo()) {
// continue searching from the DO on
break;
} else {
// continue searching from the WHILE on
fPosition= pos;
break;
}
default:
// keep searching
}
}
}
/**
* Returns true if the colon at the current position is part of a conditional
* (ternary) expression, false otherwise.
*
* @return true if the colon at the current position is part of a conditional
*/
private boolean isConditional() {
while (true) {
nextToken();
switch (fToken) {
// search for case, otherwise return true
case Symbols.TokenIDENT:
continue;
case Symbols.TokenCASE:
return false;
default:
return true;
}
}
}
/**
* Returns as a reference any previous switch
labels (case
* or default
) or the offset of the brace that scopes the switch
* statement. Sets fIndent
to prefCaseIndent
upon
* a match.
*
* @return the reference offset for a switch
label
*/
// private int matchCaseAlignment() {
// while (true) {
// nextToken();
// switch (fToken) {
// // invalid cases: another case label or an LBRACE must come before a case
// // -> bail out with the current position
// case Symbols.TokenLPAREN:
// case Symbols.TokenLBRACKET:
// case Symbols.TokenEOF:
// return fPosition;
// case Symbols.TokenLBRACE:
// // opening brace of switch statement
// fIndent= prefCaseIndent();
// return fPosition;
// case Symbols.TokenCASE:
// case Symbols.TokenDEFAULT:
// // align with previous label
// fIndent= 0;
// return fPosition;
//
// // scopes: skip them
// case Symbols.TokenRPAREN:
// case Symbols.TokenRBRACKET:
// case Symbols.TokenRBRACE:
// skipScope();
// break;
//
// default:
// // keep searching
// continue;
//
// }
// }
// }
/**
* Returns the reference position for a list element. The algorithm
* tries to match any previous indentation on the same list. If there is none,
* the reference position returned is determined depending on the type of list:
* The indentation will either match the list scope introducer (e.g. for
* method declarations), so called deep indents, or simply increase the
* indentation by a number of standard indents. See also {@link #handleScopeIntroduction(int)}.
*
* @return the reference position for a list item: either a previous list item
* that has its own indentation, or the list introduction start.
*/
private int skipToPreviousListItemOrListStart() {
int startLine= fLine;
int startPosition= fPosition;
while (true) {
nextToken();
// if any line item comes with its own indentation, adapt to it
if (fLine < startLine) {
try {
int lineOffset= fDocument.getLineOffset(startLine);
int bound= Math.min(fDocument.getLength(), startPosition + 1);
fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(lineOffset, bound);
} catch (BadLocationException e) {
// ignore and return just the position
}
return startPosition;
}
switch (fToken) {
// scopes: skip them
case Symbols.TokenRPAREN:
case Symbols.TokenRBRACKET:
case Symbols.TokenRBRACE:
skipScope();
break;
// scope introduction: special treat who special is
case Symbols.TokenLPAREN:
case Symbols.TokenLBRACE:
case Symbols.TokenLBRACKET:
return handleScopeIntroduction(startPosition + 1);
case Symbols.TokenSEMICOLON:
return fPosition;
case Symbols.TokenQUESTIONMARK:
if (prefTernaryDeepAlign()) {
setFirstElementAlignment(fPosition - 1, fPosition + 1);
return fPosition;
} else {
fIndent= prefTernaryIndent();
return fPosition;
}
case Symbols.TokenEOF:
return 0;
}
}
}
/**
* Skips a scope and positions the cursor (fPosition
) on the
* token that opens the scope. Returns true
if a matching peer
* could be found, false
otherwise. The current token when calling
* must be one out of Symbols.TokenRPAREN
, Symbols.TokenRBRACE
,
* and Symbols.TokenRBRACKET
.
*
* @return true
if a matching peer was found, false
otherwise
*/
private boolean skipScope() {
switch (fToken) {
case Symbols.TokenRPAREN:
return skipScope(Symbols.TokenLPAREN, Symbols.TokenRPAREN);
case Symbols.TokenRBRACKET:
return skipScope(Symbols.TokenLBRACKET, Symbols.TokenRBRACKET);
case Symbols.TokenRBRACE:
return skipScope(Symbols.TokenLBRACE, Symbols.TokenRBRACE);
default:
Assert.isTrue(false);
return false;
}
}
/**
* Handles the introduction of a new scope. The current token must be one out
* of Symbols.TokenLPAREN
, Symbols.TokenLBRACE
,
* and Symbols.TokenLBRACKET
. Returns as the reference position
* either the token introducing the scope or - if available - the first
* java token after that.
*
* Depending on the type of scope introduction, the indentation will align
* (deep indenting) with the reference position (fAlign
will be
* set to the reference position) or fIndent
will be set to
* the number of indentation units.
*
fAlign
) to either the offset
* right after scopeIntroducerOffset
or - if available - the
* first Java token after scopeIntroducerOffset
, but before
* bound
.
*
* @param scopeIntroducerOffset the offset of the scope introducer
* @param bound the bound for the search for another element
* @return the reference position
*/
private int setFirstElementAlignment(int scopeIntroducerOffset, int bound) {
int firstPossible= scopeIntroducerOffset + 1; // align with the first position after the scope intro
fAlign= fScanner.findNonWhitespaceForwardInAnyPartition(firstPossible, bound);
if (fAlign == JavaHeuristicScanner.NOT_FOUND)
fAlign= firstPossible;
return fAlign;
}
/**
* Returns true
if the next token received after calling
* nextToken
is either an equal sign or an array designator ('[]').
*
* @return true
if the next elements look like the start of an array definition
*/
private boolean looksLikeArrayInitializerIntro() {
nextToken();
if (fToken == Symbols.TokenEQUAL || skipBrackets()) {
return true;
}
return false;
}
/**
* Skips over the next if
keyword. The current token when calling
* this method must be an else
keyword. Returns true
* if a matching if
could be found, false
otherwise.
* The cursor (fPosition
) is set to the offset of the if
* token.
*
* @return true
if a matching if
token was found, false
otherwise
*/
private boolean skipNextIF() {
Assert.isTrue(fToken == Symbols.TokenELSE);
while (true) {
nextToken();
switch (fToken) {
// scopes: skip them
case Symbols.TokenRPAREN:
case Symbols.TokenRBRACKET:
case Symbols.TokenRBRACE:
skipScope();
break;
case Symbols.TokenIF:
// found it, return
return true;
case Symbols.TokenELSE:
// recursively skip else-if blocks
skipNextIF();
break;
// shortcut scope starts
case Symbols.TokenLPAREN:
case Symbols.TokenLBRACE:
case Symbols.TokenLBRACKET:
case Symbols.TokenEOF:
return false;
}
}
}
/**
* while(condition); is ambiguous when parsed backwardly, as it is a valid
* statement by its own, so we have to check whether there is a matching
* do. A do
can either be separated from the while by a
* block, or by a single statement, which limits our search distance.
*
* @return true
if the while
currently in
* fToken
has a matching do
.
*/
private boolean hasMatchingDo() {
Assert.isTrue(fToken == Symbols.TokenWHILE);
nextToken();
switch (fToken) {
case Symbols.TokenRBRACE:
skipScope(); // and fall thru
case Symbols.TokenSEMICOLON:
skipToStatementStart(false, false);
return fToken == Symbols.TokenDO;
}
return false;
}
/**
* Skips brackets if the current token is a RBRACKET. There can be nothing
* but whitespace in between, this is only to be used for []
elements.
*
* @return true
if a []
could be scanned, the
* current token is left at the LBRACKET.
*/
private boolean skipBrackets() {
if (fToken == Symbols.TokenRBRACKET) {
nextToken();
if (fToken == Symbols.TokenLBRACKET) {
return true;
}
}
return false;
}
/**
* Reads the next token in backward direction from the heuristic scanner
* and sets the fields fToken, fPreviousPosition
and fPosition
* accordingly.
*/
private void nextToken() {
nextToken(fPosition);
}
/**
* Reads the next token in backward direction of start
from
* the heuristic scanner and sets the fields fToken, fPreviousPosition
* and fPosition
accordingly.
*/
private void nextToken(int start) {
fToken= fScanner.previousToken(start - 1, JavaHeuristicScanner.UNBOUND);
fPreviousPos= start;
fPosition= fScanner.getPosition() + 1;
try {
fLine= fDocument.getLineOfOffset(fPosition);
} catch (BadLocationException e) {
fLine= -1;
}
}
/**
* Returns true
if the current tokens look like a method
* declaration header (i.e. only the return type and method name). The
* heuristic calls nextToken
and expects an identifier
* (method name) and a type declaration (an identifier with optional
* brackets) which also covers the visibility modifier of constructors; it
* does not recognize package visible constructors.
*
* @return true
if the current position looks like a method
* declaration header.
*/
private boolean looksLikeMethodDecl() {
/*
* TODO This heuristic does not recognize package private constructors
* since those do have neither type nor visibility keywords.
* One option would be to go over the parameter list, but that might
* be empty as well - hard to do without an AST...
*/
nextToken();
if (fToken == Symbols.TokenIDENT) { // method name
do nextToken();
while (skipBrackets()); // optional brackets for array valued return types
return fToken == Symbols.TokenIDENT; // type name
}
return false;
}
/**
* Returns true
if the current tokens look like a method
* call header (i.e. an identifier as opposed to a keyword taking parenthesized
* parameters such as if
).
* The heuristic calls nextToken
and expects an identifier
* (method name).
*
* @return true
if the current position looks like a method call
* header.
*/
private boolean looksLikeMethodCall() {
nextToken();
return fToken == Symbols.TokenIDENT; // method name
}
/**
* Scans tokens for the matching opening peer. The internal cursor
* (fPosition
) is set to the offset of the opening peer if found.
*
* @return true
if a matching token was found, false
* otherwise
*/
private boolean skipScope(int openToken, int closeToken) {
int depth= 1;
while (true) {
nextToken();
if (fToken == closeToken) {
depth++;
} else if (fToken == openToken) {
depth--;
if (depth == 0)
return true;
} else if (fToken == Symbols.TokenEOF) {
return false;
}
}
}
// TODO adjust once there are per-project settings
private int prefTabLength() {
int tabLen;
// JavaCore core= JavaCore.getJavaCore();
PHPeclipsePlugin plugin= PHPeclipsePlugin.getDefault();
// if (core != null && plugin != null)
if (plugin != null)
if (JavaCore.SPACE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR)))
// if the formatter uses chars to mark indentation, then don't substitute any chars
tabLen= -1; // results in no tabs being substituted for space runs
else
// if the formatter uses tabs to mark indentations, use the visual setting from the editor
// to get nicely aligned indentations
tabLen= plugin.getPreferenceStore().getInt(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_TAB_WIDTH);
else
tabLen= 4; // sensible default for testing
return tabLen;
}
private boolean prefArrayDimensionsDeepIndent() {
return true; // sensible default
}
private int prefArrayIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
try {
if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
return 1;
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return prefContinuationIndent(); // default
}
private boolean prefArrayDeepIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_EXPRESSIONS_IN_ARRAY_INITIALIZER);
try {
return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return true;
}
private boolean prefTernaryDeepAlign() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
try {
return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return false;
}
private int prefTernaryIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_CONDITIONAL_EXPRESSION);
try {
if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
return 1;
else
return prefContinuationIndent();
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return prefContinuationIndent();
}
private int prefCaseIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
if (DefaultCodeFormatterConstants.TRUE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_SWITCH)))
return prefBlockIndent();
else
return 0;
}
return 0; // sun standard
}
private int prefAssignmentIndent() {
return prefBlockIndent();
}
private int prefCaseBlockIndent() {
if (true)
return prefBlockIndent();
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
if (DefaultCodeFormatterConstants.TRUE.equals(JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_INDENT_SWITCHSTATEMENTS_COMPARE_TO_CASES)))
return prefBlockIndent();
else
return 0;
}
return prefBlockIndent(); // sun standard
}
private int prefSimpleIndent() {
return prefBlockIndent();
}
private int prefBracketIndent() {
return prefBlockIndent();
}
private boolean prefMethodDeclDeepIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
try {
return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return true;
}
private int prefMethodDeclIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_PARAMETERS_IN_METHOD_DECLARATION);
try {
if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
return 1;
else
return prefContinuationIndent();
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return 1;
}
private boolean prefMethodCallDeepIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
try {
return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return false; // sensible default
}
private int prefMethodCallIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_ALIGNMENT_FOR_ARGUMENTS_IN_METHOD_INVOCATION);
try {
if (DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_BY_ONE)
return 1;
else
return prefContinuationIndent();
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return 1; // sensible default
}
private boolean prefParenthesisDeepIndent() {
if (true) // don't do parenthesis deep indentation
return false;
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
try {
return DefaultCodeFormatterConstants.getIndentStyle(option) == DefaultCodeFormatterConstants.INDENT_ON_COLUMN;
} catch (IllegalArgumentException e) {
// ignore and return default
}
}
return false; // sensible default
}
private int prefParenthesisIndent() {
return prefContinuationIndent();
}
private int prefBlockIndent() {
return 1; // sensible default
}
private boolean prefIndentBracesForBlocks() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_BLOCK);
return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
}
return false; // sensible default
}
private boolean prefIndentBracesForArrays() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_ARRAY_INITIALIZER);
return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
}
return false; // sensible default
}
private boolean prefIndentBracesForMethods() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_BRACE_POSITION_FOR_METHOD_DECLARATION);
return option.equals(DefaultCodeFormatterConstants.NEXT_LINE_SHIFTED);
}
return false; // sensible default
}
private int prefContinuationIndent() {
Plugin plugin= JavaCore.getPlugin();
if (plugin != null) {
String option= JavaCore.getOption(DefaultCodeFormatterConstants.FORMATTER_CONTINUATION_INDENTATION);
try {
return Integer.parseInt(option);
} catch (NumberFormatException e) {
// ignore and return default
}
}
return 2; // sensible default
}
}