/*******************************************************************************
* Copyright (c) 2013, 2015, 2016, 2017 Zend Techologies Ltd.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Zend Technologies Ltd. - initial API and implementation
*******************************************************************************/
package org.eclipse.php.formatter.core;
import java.io.Reader;
import java.io.StringReader;
import java.util.*;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.dltk.annotations.NonNull;
import org.eclipse.dltk.ast.references.SimpleReference;
import org.eclipse.dltk.ast.references.TypeReference;
import org.eclipse.jface.text.*;
import org.eclipse.php.core.PHPVersion;
import org.eclipse.php.core.ast.nodes.*;
import org.eclipse.php.core.ast.visitor.AbstractVisitor;
import org.eclipse.php.core.compiler.ast.nodes.PHPDocBlock;
import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag;
import org.eclipse.php.core.compiler.ast.nodes.PHPDocTag.TagKind;
import org.eclipse.php.core.compiler.ast.nodes.VarComment;
import org.eclipse.php.formatter.core.profiles.CodeFormatterPreferences;
import org.eclipse.php.internal.core.Constants;
import org.eclipse.php.internal.core.ast.scanner.AstLexer;
import org.eclipse.php.internal.core.compiler.ast.parser.php56.CompilerParserConstants;
import org.eclipse.php.internal.core.compiler.ast.parser.php56.PHPTokenNames;
import org.eclipse.php.internal.core.documentModel.parser.PHPRegionContext;
import org.eclipse.php.internal.core.documentModel.partitioner.PHPPartitionTypes;
import org.eclipse.php.internal.core.documentModel.partitioner.PHPStructuredTextPartitioner;
import org.eclipse.php.internal.core.format.ICodeFormattingProcessor;
import org.eclipse.php.internal.core.util.MagicMemberUtil;
import org.eclipse.php.internal.formatter.core.DocumentReader;
import org.eclipse.php.internal.formatter.core.Logger;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.wst.sse.core.internal.provisional.text.*;
import java_cup.runtime.Symbol;
/**
* Description: This class formats a given {@link StructuredDocument}
*
* @author moshe, 2007
*/
public class CodeFormatterVisitor extends AbstractVisitor implements ICodeFormattingProcessor {
public static final int NO_LINE_WRAP = 0;
public static final int FIRST_WRAP_WHEN_NECESSARY = 1;
public static final int WRAP_FIRST_ELEMENT = 2;
public static final int WRAP_ALL_ELEMENTS = 3;
public static final int WRAP_ALL_ELEMENTS_NO_INDENT_FIRST = 4;
public static final int WRAP_ALL_ELEMENTS_EXCEPT_FIRST = 5;
public static final int ALWAYS_WRAP_ELEMENT = 6;
public static final int WRAP_WHEN_NECESSARY = 7;
private static final int NO_LINE_WRAP_INDENT = -1;
private static final int DEFAULT_INDENTATION = 0;
private static final int INDENT_ON_COLUMN = 1;
private static final int INDENT_ONE = 2;
private static final String EMPTY_STRING = ""; //$NON-NLS-1$
private static final String FUNCTION_NAME_PRINT = "print"; //$NON-NLS-1$
private static final byte PHP_OPEN_TAG = 0;
private static final byte PHP_OPEN_SHORT_TAG = 1;
private static final char PHP_OPEN_SHORT_TAG_WITH_EQUAL = 2;
private static final char PHP_OPEN_ASP_TAG = 3;
private static final char EQUAL = '=';
private static final String KEY_VALUE_OPERATOR = "=>"; //$NON-NLS-1$
private static final char OPEN_PARN = '(';
private static final char CLOSE_PARN = ')';
private static final char OPEN_CURLY = '{';
private static final char CLOSE_CURLY = '}';
private static final char OPEN_BRACKET = '[';
private static final char CLOSE_BRACKET = ']';
private static final char COLON = ':';
private static final char SEMICOLON = ';';
private static final char SPACE = ' ';
private static final char COMMA = ',';
private static final char QUESTION_MARK = '?';
private static final String ELLIPSIS = "..."; //$NON-NLS-1$
private static final String FROM = "from"; //$NON-NLS-1$
private String lineSeparator;
private CodeFormatterPreferences preferences;
private final IDocument document;
private PHPVersion phpVersion;
private boolean useShortTags;
private int indentationLevel;
private boolean indentationLevelDescending = false;
private AstLexer astLexer;
private boolean isPhpEqualTag = false;
private int startRegionPosition = -1;
private int endRegionPosition = Integer.MAX_VALUE;
private boolean isPrevSpace = false;
private boolean isHeredocSemicolon = false;
private int lineWidth = 0;
private int binaryExpressionLineWrapPolicy = -1;// use this as local since
// it changes its state
private int binaryExpressionIndentGap = 0;// use this as local since it
// changes its state
private boolean wasBinaryExpressionWrapped = false;
private String binaryExpressionSavedBuffer = null;
private InfixExpression binaryExpressionSavedNode = null;
private int binaryExpressionSavedChangesIndex = -1;
private int binaryExpressionRevertPolicy = -1;
private boolean isBinaryExpressionExtraIndentation = false;
// append chars to buffer through insertSpace or appendToBuffer
private StringBuilder replaceBuffer = new StringBuilder();
private List<Symbol> tokens = new ArrayList<Symbol>();
/**
* list of <ReplaceEdit>
*/
private List<ReplaceEdit> changes = new LinkedList<ReplaceEdit>();
private int stInScriptin = -1;
private int stWhile = -1;
private int stElse = -1;
private int stElseIf = -1;
private Stack<Integer> chainStack = new Stack<Integer>();
private Integer peek;
private Set<IfStatement> processedIfStatements = new HashSet<IfStatement>();
private boolean newLineOfComment;
private List<String> commentWords;
/** disabling */
boolean editsEnabled;
boolean useTags;
int tagsKind;
private String disablingTag, enablingTag;
// this is for never indent at first line
private boolean doNotIndent = false;
boolean inComment = false;
private int indentLengthForComment = -1;
private String indentStringForComment = null;
private boolean blockEnd;
private boolean recordCommentIndentVariables = false;
// for block comment,multiline comment at the end of break statement of case
// statement
private List<Integer> indentationLevelList = new ArrayList<Integer>();
Stack<CommentIndentationObject> commentIndentationStack = new Stack<CodeFormatterVisitor.CommentIndentationObject>();
private boolean ignoreEmptyLineSetting = false;
public CodeFormatterVisitor(IDocument document, CodeFormatterPreferences codeFormatterPreferences,
String lineSeparator, PHPVersion phpVersion, boolean useShortTags, IRegion region) throws Exception {
this(document, codeFormatterPreferences, lineSeparator, phpVersion, useShortTags, region, 0);
}
// public CodeFormatterVisitor(IDocument document, String lineSeparator,
// PHPVersion phpVersion, boolean useShortTags,
// IRegion region) throws Exception {
// this(document, CodeFormatterPreferences.getDefaultPreferences(),
// lineSeparator, phpVersion, useShortTags,
// region, 0);
// }
public CodeFormatterVisitor(IDocument document, CodeFormatterPreferences codeFormatterPreferences,
String lineSeparator, PHPVersion phpVersion, boolean useShortTags, IRegion region, int indentationLevel)
throws Exception {
this.phpVersion = phpVersion;
this.useShortTags = useShortTags;
this.document = document;
this.lineSeparator = lineSeparator;
this.indentationLevel = indentationLevel;
this.preferences = codeFormatterPreferences;
this.startRegionPosition = region.getOffset();
this.endRegionPosition = startRegionPosition + region.getLength();
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
Program program = null;
try {
final Reader reader = new StringReader(document.get());
program = ASTParser.newParser(reader, phpVersion, true).createAST(new NullProgressMonitor());
} catch (Exception e) {
Logger.log(Logger.INFO, "Parsing error, file could not be formatted.");//$NON-NLS-1$
}
this.useTags = preferences.use_tags;
this.tagsKind = 0;
if (this.useTags) {
if (preferences.disabling_tag != null && preferences.disabling_tag.length > 0) {
this.disablingTag = new String(preferences.disabling_tag);
}
if (preferences.enabling_tag != null && preferences.enabling_tag.length > 0) {
this.enablingTag = new String(preferences.enabling_tag);
}
}
this.editsEnabled = true;
if (program != null) {
program.accept(this);
}
}
// insert chars to the buffer
private void appendToBuffer(Object obj) {
isPrevSpace = false;
if (obj == null)
return;
replaceBuffer.append(obj);
if (!lineSeparator.equals(obj)) {
lineWidth += obj.toString().length();
}
}
private int countStrInBuffer(String str) {
int count = 0;
int index = replaceBuffer.indexOf(str);
while (index >= 0 && index < replaceBuffer.length()) {
index = replaceBuffer.indexOf(str, index + 1);
count++;
}
return count;
}
private char getBufferFirstChar(int position) throws BadLocationException {
for (int offset = position; offset < replaceBuffer.length(); offset++) {
char currChar = replaceBuffer.charAt(offset);
if (currChar != ' ' && currChar != '\t' && currChar != '\r' && currChar != '\n') {
// not empty line
return currChar;
}
}
return '\0';
}
public List<ReplaceEdit> getChanges() {
IRegion[] regions = new IRegion[0];
try {
regions = getAllSingleLine(TextUtilities.computePartitioning(document,
IStructuredPartitioning.DEFAULT_STRUCTURED_PARTITIONING, 0, document.getLength(), false));
} catch (BadLocationException e) {
Logger.logException(e);
}
List<ReplaceEdit> allChanges = Collections.unmodifiableList(changes);
List<ReplaceEdit> result = new ArrayList<ReplaceEdit>();
for (ReplaceEdit edit : allChanges) {
if (isInSingleLine(edit, regions)) {
continue;
}
result.add(edit);
}
return Collections.unmodifiableList(result);
}
private int getCharPosition(int start, int end, char c) {
try {
for (int index = 0; start + index < end; index++) {
if (document.getChar(start + index) == c) {
return start + index;
}
}
} catch (BadLocationException e) {
Logger.logException(e);
}
return -1;
}
private String getDocumentString(int start, int end) {
char[] result = new char[end - start];
try {
for (int index = 0; start + index < end; index++) {
result[index] = document.getChar(start + index);
}
} catch (BadLocationException e) {
Logger.logException(e);
}
return new String(result);
}
private AstLexer getLexer(Reader reader) throws Exception {
AstLexer result = null;
if (PHPVersion.PHP5.equals(phpVersion)) {
result = new org.eclipse.php.internal.core.compiler.ast.parser.php5.CompilerAstLexer(reader);
((org.eclipse.php.internal.core.compiler.ast.parser.php5.CompilerAstLexer) result)
.setAST(new AST(reader, PHPVersion.PHP5, false, useShortTags));
stInScriptin = org.eclipse.php.internal.core.compiler.ast.parser.php5.CompilerAstLexer.ST_IN_SCRIPTING; // save
// the
// initial
// state
// for
// reset
// operation
stWhile = org.eclipse.php.internal.core.ast.scanner.php5.ParserConstants.T_WHILE;
stElse = org.eclipse.php.internal.core.ast.scanner.php5.ParserConstants.T_ELSE;
stElseIf = org.eclipse.php.internal.core.ast.scanner.php5.ParserConstants.T_ELSEIF;
} else if (PHPVersion.PHP5_3.equals(phpVersion)) {
result = new org.eclipse.php.internal.core.compiler.ast.parser.php53.CompilerAstLexer(reader);
((org.eclipse.php.internal.core.compiler.ast.parser.php53.CompilerAstLexer) result)
.setAST(new AST(reader, PHPVersion.PHP5_3, false, useShortTags));
stInScriptin = org.eclipse.php.internal.core.compiler.ast.parser.php53.CompilerAstLexer.ST_IN_SCRIPTING; // save
// the
// initial
// state
// for
// reset
// operation
stWhile = org.eclipse.php.internal.core.ast.scanner.php53.ParserConstants.T_WHILE;
stElse = org.eclipse.php.internal.core.ast.scanner.php53.ParserConstants.T_ELSE;
stElseIf = org.eclipse.php.internal.core.ast.scanner.php53.ParserConstants.T_ELSEIF;
} else if (PHPVersion.PHP5_4.equals(phpVersion)) {
result = new org.eclipse.php.internal.core.compiler.ast.parser.php54.CompilerAstLexer(reader);
((org.eclipse.php.internal.core.compiler.ast.parser.php54.CompilerAstLexer) result)
.setAST(new AST(reader, PHPVersion.PHP5_4, false, useShortTags));
stInScriptin = org.eclipse.php.internal.core.compiler.ast.parser.php54.CompilerAstLexer.ST_IN_SCRIPTING; // save
// the
// initial
// state
// for
// reset
// operation
stWhile = org.eclipse.php.internal.core.ast.scanner.php54.ParserConstants.T_WHILE;
stElse = org.eclipse.php.internal.core.ast.scanner.php54.ParserConstants.T_ELSE;
stElseIf = org.eclipse.php.internal.core.ast.scanner.php54.ParserConstants.T_ELSEIF;
} else if (PHPVersion.PHP5_5.equals(phpVersion)) {
result = new org.eclipse.php.internal.core.compiler.ast.parser.php55.CompilerAstLexer(reader);
((org.eclipse.php.internal.core.compiler.ast.parser.php55.CompilerAstLexer) result)
.setAST(new AST(reader, PHPVersion.PHP5_5, false, useShortTags));
stInScriptin = org.eclipse.php.internal.core.compiler.ast.parser.php55.CompilerAstLexer.ST_IN_SCRIPTING; // save
// the
// initial
// state
// for
// reset
// operation
stWhile = org.eclipse.php.internal.core.ast.scanner.php55.ParserConstants.T_WHILE;
stElse = org.eclipse.php.internal.core.ast.scanner.php55.ParserConstants.T_ELSE;
stElseIf = org.eclipse.php.internal.core.ast.scanner.php55.ParserConstants.T_ELSEIF;
} else if (PHPVersion.PHP5_6.equals(phpVersion)) {
result = new org.eclipse.php.internal.core.compiler.ast.parser.php56.CompilerAstLexer(reader);
((org.eclipse.php.internal.core.compiler.ast.parser.php56.CompilerAstLexer) result)
.setAST(new AST(reader, PHPVersion.PHP5_6, false, useShortTags));
stInScriptin = org.eclipse.php.internal.core.compiler.ast.parser.php56.CompilerAstLexer.ST_IN_SCRIPTING; // save
// the
// initial
// state
// for
// reset
// operation
stWhile = org.eclipse.php.internal.core.ast.scanner.php56.ParserConstants.T_WHILE;
stElse = org.eclipse.php.internal.core.ast.scanner.php56.ParserConstants.T_ELSE;
stElseIf = org.eclipse.php.internal.core.ast.scanner.php56.ParserConstants.T_ELSEIF;
} else if (PHPVersion.PHP7_0.equals(phpVersion)) {
result = new org.eclipse.php.internal.core.compiler.ast.parser.php7.CompilerAstLexer(reader);
((org.eclipse.php.internal.core.compiler.ast.parser.php7.CompilerAstLexer) result)
.setAST(new AST(reader, PHPVersion.PHP7_0, false, useShortTags));
stInScriptin = org.eclipse.php.internal.core.compiler.ast.parser.php7.CompilerAstLexer.ST_IN_SCRIPTING; // save
// the
// initial
// state
// for
// reset
// operation
stWhile = org.eclipse.php.internal.core.ast.scanner.php7.ParserConstants.T_WHILE;
stElse = org.eclipse.php.internal.core.ast.scanner.php7.ParserConstants.T_ELSE;
stElseIf = org.eclipse.php.internal.core.ast.scanner.php7.ParserConstants.T_ELSEIF;
} else if (PHPVersion.PHP7_1.equals(phpVersion)) {
result = new org.eclipse.php.internal.core.compiler.ast.parser.php71.CompilerAstLexer(reader);
((org.eclipse.php.internal.core.compiler.ast.parser.php71.CompilerAstLexer) result)
.setAST(new AST(reader, PHPVersion.PHP7_1, false, useShortTags));
stInScriptin = org.eclipse.php.internal.core.compiler.ast.parser.php71.CompilerAstLexer.ST_IN_SCRIPTING; // save
// the
// initial
// state
// for
// reset
// operation
stWhile = org.eclipse.php.internal.core.ast.scanner.php71.ParserConstants.T_WHILE;
stElse = org.eclipse.php.internal.core.ast.scanner.php71.ParserConstants.T_ELSE;
stElseIf = org.eclipse.php.internal.core.ast.scanner.php71.ParserConstants.T_ELSEIF;
} else {
throw new IllegalArgumentException("unrecognized version " //$NON-NLS-1$
+ phpVersion);
}
return result;
}
private byte getPhpStartTag(int offset) {
try {
// 6 = "<?php".length() + 1
String text = document.get(offset, Math.min(6, document.getLength() - offset)).toLowerCase();
if (text.startsWith("<%")) { //$NON-NLS-1$
return PHP_OPEN_ASP_TAG;
}
if (text.startsWith("<?=")) { //$NON-NLS-1$
return PHP_OPEN_SHORT_TAG_WITH_EQUAL;
}
if (text.startsWith("<?")) { //$NON-NLS-1$
if (text.startsWith("<?php") && text.length() >= 6) { //$NON-NLS-1$
char separatorChar = text.charAt(5);
// the definition of token PHP_START in
// PHPTokenizer.jflex tells us that "<?php" must ALWAYS
// be followed by a whitespace character to be recognized as
// a PHP_OPEN_TAG
if (separatorChar == ' ' || separatorChar == '\t' || separatorChar == '\r'
|| separatorChar == '\n') {
return PHP_OPEN_TAG;
}
}
// Short tag "<?".
// But also (for example) "<?phpXYZ :" must be handled as
// a PHP_OPEN_SHORT_TAG followed by label "XYZ".
return PHP_OPEN_SHORT_TAG;
}
} catch (Exception e) {
Logger.logException(e);
}
return -1;
}
private int getPhpTagIndentationLevel(int offset) {
try {
final int line = document.getLineOfOffset(offset);
final int startLineOffset = document.getLineOffset(line);
int diff = 0;
for (int i = startLineOffset; i < offset; i++) {
if (document.getChar(i) == '\t' || document.getChar(i) == ' ') {
diff++;
} else {
break;
}
}
if (preferences.indentationChar == CodeFormatterPreferences.TAB_CHAR) {
return diff;
} else {
if (preferences.indentationSize <= 0) {
return -1;
}
return diff / preferences.indentationSize;
}
} catch (Exception e) {
Logger.logException(e);
}
return -1;
}
/**
* handle the action of if, while, do while, for statements.
*/
private void handleAction(int lastPosition, Statement action, boolean addNewlineBeforeAction) {
boolean isIndentationAdded = false;
if (action.getType() == ASTNode.BLOCK) {
isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_block,
this.preferences.insert_space_before_opening_brace_in_block);
} else if (action.getType() == ASTNode.EMPTY_STATEMENT) {
// This is an empty statement
if (this.preferences.new_line_for_empty_statement) {
insertNewLine();
indentationLevel++;
indent();
isIndentationAdded = true;
}
} else {
if (addNewlineBeforeAction) {
// single statement should indent
indentationLevel++;
insertNewLine();
indent();
isIndentationAdded = true;
} else {
insertSpace();
}
}
handleChars(lastPosition, action.getStart());
action.accept(this);
if (isIndentationAdded) {
indentationLevel--;
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=511502
if (action.getType() == ASTNode.BLOCK) {
indentationLevelDescending = true;
}
}
}
/**
* handle the '{' of a block
*
* @param bracePosition
* one of Sameline
* @param placeSpaceBeforeOpenCurly
* @return
*/
private boolean handleBlockOpenBrace(byte bracePosition, boolean placeSpaceBeforeOpenCurly) {
boolean isIndentationAdded = false;
switch (bracePosition) {
case CodeFormatterPreferences.NEXT_LINE_INDENT:
indentationLevel++;
isIndentationAdded = true;
case CodeFormatterPreferences.NEXT_LINE:
insertNewLine();
indent();
break;
default:
case CodeFormatterPreferences.SAME_LINE:
if (placeSpaceBeforeOpenCurly) {
insertSpace();
}
break;
}
return isIndentationAdded;
}
@SuppressWarnings("unchecked")
private void handleChars(int offset, int end) {
try {
// check if the changed region is in the formatting requested region
if (startRegionPosition < end && endRegionPosition >= end) {
boolean hasComments = hasComments(offset, end);
if (hasComments) {
// handle the comments
handleComments(offset, end, astLexer.getCommentList(), false, 0);
} else {
handleCharsWithoutComments(offset, end);
}
}
// clear the buffer
replaceBuffer.setLength(0);
} catch (Exception e) {
Logger.logException(e);
}
}
@SuppressWarnings("unchecked")
private void handleChars1(int offset, int end, boolean isIndented, int indentGap) {
try {
// check if the changed region is in the formatting requested region
if (startRegionPosition < end && endRegionPosition >= end) {
boolean hasComments = hasComments(offset, end);
if (hasComments) {
// handle the comments
handleComments(offset, end, astLexer.getCommentList(), isIndented, indentGap);
} else {
handleCharsWithoutComments(offset, end);
}
}
// clear the buffer
replaceBuffer.setLength(0);
} catch (Exception e) {
Logger.logException(e);
}
}
private boolean hasComments(int offset, int end) throws Exception {
scan(offset, end);
assert astLexer != null;
boolean hasComments = astLexer.getCommentList().size() > 0;
return hasComments;
}
private int getFirstTokenOffset(int start, int end, int tokenId, boolean shouldScan) throws Exception {
if (shouldScan) {
scan(start, end);
}
assert astLexer != null;
for (Symbol token : tokens) {
if (token.sym == tokenId) {
return start + token.left;
}
}
return -1;
}
private void handleCharsWithoutComments(int offset, int end) throws BadLocationException {
handleCharsWithoutComments(offset, end, false);
}
private void handleCharsWithoutComments(int offset, int end, boolean isComment) throws BadLocationException {
String content = document.get(offset, end - offset).toLowerCase();
int phpTagOpenIndex = -1;
if (!isComment && ((phpTagOpenIndex = content.indexOf("<?")) != -1 //$NON-NLS-1$
|| (phpTagOpenIndex = content.indexOf("<%")) != -1)) { //$NON-NLS-1$
handleSplittedPhpBlock(offset + phpTagOpenIndex, end);
}
else {
// reset the isPrevSpace while replacing the chars
isPrevSpace = false;
int startLine = document.getLineOfOffset(offset);
int endLine = document.getLineOfOffset(end);
int emptyLines = 0;
if (!ignoreEmptyLineSetting) {
// count empty lines
for (int line = startLine; line < endLine; line++) {
if (isEmptyLine(line)) {
emptyLines++;
}
}
// set the preserve empty lines
if (emptyLines > preferences.blank_line_preserve_empty_lines) {
emptyLines = preferences.blank_line_preserve_empty_lines;
}
int newLinesInBuffer = countStrInBuffer(lineSeparator);
// add empty lines
if (emptyLines > 0 && newLinesInBuffer < emptyLines + 1) {
// If newLinesInBuffer is = 0, it *could* mean that the
// buffer was already (partly or completely) flushed (so
// there are big chances that we already inserted a
// newline), increase newLinesInBuffer by one to only insert
// emptyLines - 1 newlines.
// If newLinesInBuffer is > 0, first newline from buffer is
// ignored because it either ends a non-empty line or some
// formatter preference already forced the insertion of a
// newline.
for (int line = newLinesInBuffer == 0 ? 1 : newLinesInBuffer; line < emptyLines + 1; line++) {
insertNewLine();
}
if (inComment) {
if (!doNotIndent) {
indentForComment(indentationLevelDescending);
}
} else {
indent();
}
}
}
ignoreEmptyLineSetting = false;
// check if the replacement and the origin string are the same
boolean needToReplace = true;
if (end - offset == replaceBuffer.length()) {
// in case the buffer is empty and the doc length is 0
// no need to replace
if (end - offset == 0 && replaceBuffer.length() == 0) {
needToReplace = false;
} else {
// the buffer and document segment length are the same
// in case of 2 different chars we need to replace the
// document segment
needToReplace = false;
for (int index = 0; offset + index < end; index++) {
char docChar = document.getChar(offset + index);
char bufferChar = replaceBuffer.charAt(index);
if (docChar != bufferChar) {
needToReplace = true;
break;
}
}
}
}
if (needToReplace && editsEnabled) {
insertString(offset, end, replaceBuffer.toString());
}
if (recordCommentIndentVariables) {
recordCommentIndentVariables = false;
indentLengthForComment = lineWidth;
String afterNewLine = EMPTY_STRING;
int position = replaceBuffer.lastIndexOf(lineSeparator);
if (position >= 0) {
if (getBufferFirstChar(position + lineSeparator.length()) == '\0') {
afterNewLine = replaceBuffer.substring(position + lineSeparator.length(),
replaceBuffer.length());
}
} else {
if (getBufferFirstChar(0) == '\0') {
afterNewLine = replaceBuffer.toString();
}
}
indentStringForComment = afterNewLine;
}
indentationLevelDescending = false;
// clear the buffer
replaceBuffer.setLength(0);
}
}
class CommentIndentationObject {
boolean indented;
}
/**
* handle comma list (e.g. 1,2,3)
*
* @param array
* ASTNode array
* @param lastPosition
* the position of the last ASTNode
* @param insertSpaceBeforeComma
* @param insertSpaceAfterComma
* @param b
* @param k
* @param j
* @return the last element end position
*/
private int handleCommaList(ASTNode[] array, int lastPosition, boolean insertSpaceBeforeComma,
boolean insertSpaceAfterComma, int lineWrapPolicy, int indentGap, boolean forceSplit) {
int oldIndentationLevel = indentationLevel;
boolean wasBinaryExpressionWrapped = this.wasBinaryExpressionWrapped;
if (array.length == 0) {
return lastPosition;
}
// save the changes index position
String savedBuffer = replaceBuffer.toString();
int changesIndex = changes.size() - 1;
int savedLastPosition = lastPosition;
boolean isExtraIndentation = false;
// Map<Integer, CommentIndentationObject> commentIndentationMap = new
// HashMap<Integer, CommentIndentationObject>();
CommentIndentationObject cio = new CommentIndentationObject();
commentIndentationStack.add(cio);
// commentIndentationMap.put(array., cio);
boolean isFirst = true;
for (int i = 0; i < array.length; i++) {
if (!isFirst) {
if (insertSpaceBeforeComma) {
insertSpace();
}
appendToBuffer(COMMA);
if (insertSpaceAfterComma) {
insertSpace();
}
}
// after the first element and wrap policy is except first element
if (i == 1 && lineWrapPolicy == WRAP_ALL_ELEMENTS_EXCEPT_FIRST) {
savedBuffer = replaceBuffer.toString();
changesIndex = changes.size() - 1;
savedLastPosition = lastPosition;
}
switch (lineWrapPolicy) {
case NO_LINE_WRAP:
break;
case FIRST_WRAP_WHEN_NECESSARY:
if (lineWidth + array[i].getLength() > this.preferences.line_wrap_line_split) {
lineWrapPolicy = WRAP_WHEN_NECESSARY;
insertNewLine();
if (!cio.indented) {
indentationLevel += indentGap;
}
indent();
}
break;
case WRAP_WHEN_NECESSARY:
if (lineWidth + array[i].getLength() > this.preferences.line_wrap_line_split) {
insertNewLine();
indent();
}
break;
case WRAP_FIRST_ELEMENT:
if (forceSplit || lineWidth + array[i].getLength() > this.preferences.line_wrap_line_split) {
revert(savedBuffer, changesIndex);
lastPosition = savedLastPosition;
i = 0;
lineWrapPolicy = WRAP_WHEN_NECESSARY;
insertNewLine();
if (!cio.indented) {
indentationLevel += indentGap;
}
indent();
}
break;
case WRAP_ALL_ELEMENTS:
if (forceSplit || lineWidth + array[i].getLength() > this.preferences.line_wrap_line_split) {
revert(savedBuffer, changesIndex);
lastPosition = savedLastPosition;
i = 0;
lineWrapPolicy = ALWAYS_WRAP_ELEMENT;
insertNewLine();
if (!cio.indented) {
indentationLevel += indentGap;
}
indent();
}
break;
case WRAP_ALL_ELEMENTS_NO_INDENT_FIRST:
if (forceSplit || lineWidth + array[i].getLength() > this.preferences.line_wrap_line_split) {
// revert the buffer
revert(savedBuffer, changesIndex);
lastPosition = savedLastPosition;
i = 0;
lineWrapPolicy = ALWAYS_WRAP_ELEMENT;
insertNewLine();
if (!cio.indented) {
indentationLevel += indentGap;
}
indent();
// increase the indentation level after the first element
indentationLevel++;
isExtraIndentation = true;
}
break;
case WRAP_ALL_ELEMENTS_EXCEPT_FIRST:
if (forceSplit || lineWidth + array[i].getLength() > this.preferences.line_wrap_line_split) {
// revert
revert(savedBuffer, changesIndex);
lastPosition = savedLastPosition;
i = (i > 0) ? 1 : 0;
lineWrapPolicy = ALWAYS_WRAP_ELEMENT;
insertNewLine();
if (!cio.indented) {
indentationLevel += indentGap;
}
indent();
}
break;
case ALWAYS_WRAP_ELEMENT:
insertNewLine();
indent();
break;
}
handleChars1(lastPosition, array[i].getStart(), oldIndentationLevel != indentationLevel, indentGap);
array[i].accept(this);
lastPosition = array[i].getEnd();
isFirst = false;
}
commentIndentationStack.pop();
if (isExtraIndentation) {
indentationLevel--;
}
if (oldIndentationLevel != indentationLevel) {
indentationLevel = oldIndentationLevel;
}
if (wasBinaryExpressionWrapped != this.wasBinaryExpressionWrapped) {
this.wasBinaryExpressionWrapped = wasBinaryExpressionWrapped;
}
return lastPosition;
}
// TODO: Do correct comment placement
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=440209
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=440820
private void handleComments(int offset, int end, List<org.eclipse.php.core.compiler.ast.nodes.Comment> commentList,
boolean isIndented, int indentGap) throws Exception {
boolean oldIgnoreEmptyLineSetting = ignoreEmptyLineSetting;
ignoreEmptyLineSetting = false;
int startLine = document.getLineOfOffset(offset);
int start = offset;
boolean needIndentNewLine = false;
boolean indentationLevelDescending = this.indentationLevelDescending;
inComment = true;
boolean previousCommentIsSingleLine = false;
resetCommentIndentVariables();
comments: for (Iterator<org.eclipse.php.core.compiler.ast.nodes.Comment> iter = commentList.iterator(); iter
.hasNext();) {
org.eclipse.php.core.compiler.ast.nodes.Comment comment = iter.next();
int commentStartLine = document.getLineOfOffset(comment.sourceStart() + offset);
int position = replaceBuffer.lastIndexOf(lineSeparator);
boolean startAtFirstColumn = (document.getLineOffset(commentStartLine) == comment.sourceStart() + offset);
boolean endWithNewLineIndent = endWithNewLineIndent(replaceBuffer.toString());
String afterNewLine = EMPTY_STRING;
boolean indentOnFirstColumn;
String commentContent;
switch (comment.getCommentType()) {
case org.eclipse.php.core.compiler.ast.nodes.Comment.TYPE_SINGLE_LINE:
indentOnFirstColumn = !startAtFirstColumn
|| !this.preferences.never_indent_line_comments_on_first_column;
if (startLine == commentStartLine) {
indentOnFirstColumn = false;
IRegion startLinereg = document.getLineInformation(startLine);
// TODO: Do line width calculation based on the
// formatted content instead of the original content
lineWidth = comment.sourceStart() + offset - startLinereg.getOffset();
if (position >= 0) {
if (getBufferFirstChar(position + lineSeparator.length()) == '\0') {
afterNewLine = replaceBuffer.substring(position + lineSeparator.length(),
replaceBuffer.length());
replaceBuffer.replace(position, replaceBuffer.length(), ""); //$NON-NLS-1$
insertSpaces(1);
} else {
insertSpace();
}
} else {
if (getBufferFirstChar(0) == '\0') {
replaceBuffer.setLength(0);
insertSpaces(1);
} else {
insertSpace();
}
}
} else {
if (getBufferFirstChar(0) == '\0') {
if (position >= 0) {
if (getBufferFirstChar(position + lineSeparator.length()) == '\0') {
replaceBuffer.replace(position, replaceBuffer.length(), ""); //$NON-NLS-1$
}
insertNewLine();
} else {
replaceBuffer.setLength(0);
lineWidth = 0;
}
} else {
if (position >= 0 && getBufferFirstChar(position + lineSeparator.length()) == '\0') {
replaceBuffer.replace(position, replaceBuffer.length(), ""); //$NON-NLS-1$
}
insertNewLine();
if (!isIndented && !commentIndentationStack.isEmpty()) {
CommentIndentationObject cio = commentIndentationStack.peek();
if (!cio.indented) {
cio.indented = true;
indentationLevel += indentGap;
}
}
// TODO should add indent level
}
if (indentationLevelDescending || blockEnd) {
for (int i = 0; i < preferences.indentationSize; i++) {
appendToBuffer(preferences.indentationChar);
lineWidth += (preferences.indentationChar == CodeFormatterPreferences.SPACE_CHAR) ? 0 : 3;
}
}
needIndentNewLine = true;
}
if (!previousCommentIsSingleLine) {
resetCommentIndentVariables();
}
doNotIndent = true;
if (indentOnFirstColumn) {
if (indentLengthForComment >= 0) {
appendToBuffer(indentStringForComment);
// adjust lineWidth, because indentLengthForComment may
// contain '\t'
lineWidth = indentLengthForComment;
} else {
indent();
}
doNotIndent = false;
if (lineWidth > 0) {
startAtFirstColumn = false;
}
}
previousCommentIsSingleLine = true;
handleCharsWithoutComments(start, comment.sourceStart() + offset);
doNotIndent = false;
resetEnableStatus(
document.get(comment.sourceStart() + offset, comment.sourceEnd() - comment.sourceStart()));
if (this.editsEnabled && this.preferences.comment_format_line_comment
&& (startAtFirstColumn && this.preferences.comment_format_line_comment_starting_on_first_column
|| !startAtFirstColumn)) {
if (startLine == commentStartLine) {
initCommentIndentVariables(offset, startLine, comment, endWithNewLineIndent);
lineWidth = indentLengthForComment;
}
if (startAtFirstColumn && this.preferences.never_indent_line_comments_on_first_column) {
indentLengthForComment = 0;
indentStringForComment = ""; //$NON-NLS-1$
}
commentContent = document.get(comment.sourceStart() + offset,
comment.sourceEnd() - comment.sourceStart());
boolean needInsertNewLine = commentContent.endsWith(lineSeparator);
if (!needInsertNewLine) {
String[] delimiters = document.getLegalLineDelimiters();
for (int i = 0; i < delimiters.length; i++) {
needInsertNewLine = commentContent.endsWith(delimiters[i]);
if (needInsertNewLine) {
break;
}
}
}
int commentTokLen = commentContent.startsWith("#") ? 1 : 2;//$NON-NLS-1$
commentWords = Arrays.asList(
MagicMemberUtil.WHITESPACE_SEPERATOR.split(commentContent.substring(commentTokLen).trim()));
commentWords = removeEmptyString(commentWords);
commentContent = join(commentWords, " "); //$NON-NLS-1$
commentContent = commentContent.trim();
boolean newLineStart = true;
appendToBuffer("//"); //$NON-NLS-1$
for (String word : commentWords) {
if (this.preferences.comment_line_length != 9999 && !newLineStart
&& (lineWidth + 1 + word.length() > this.preferences.comment_line_length)) {
insertNewLine();
// start at first column, and more than
// comment_line_length
if (!startAtFirstColumn || (startAtFirstColumn && indentOnFirstColumn)) {
if (indentLengthForComment >= 0) {
appendToBuffer(indentStringForComment);
} else {
indent();
}
}
appendToBuffer("//"); //$NON-NLS-1$
insertSpaces(1);
appendToBuffer(word);
} else {
insertSpaces(1);
appendToBuffer(word);
newLineStart = false;
}
}
handleCharsWithoutComments(comment.sourceStart() + offset, comment.sourceEnd() + offset, true);
if (needInsertNewLine) {
insertNewLine();
needInsertNewLine = false;
} else {
insertSpaces(1);
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=489488
needIndentNewLine = false;
afterNewLine = EMPTY_STRING;
}
} else {
commentContent = document.get(comment.sourceStart() + offset,
comment.sourceEnd() - comment.sourceStart());
boolean needInsertNewLine = commentContent.endsWith(lineSeparator);
if (!needInsertNewLine) {
String[] delimiters = document.getLegalLineDelimiters();
for (int i = 0; i < delimiters.length; i++) {
needInsertNewLine = commentContent.endsWith(delimiters[i]);
if (needInsertNewLine) {
break;
}
}
}
if (needInsertNewLine) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=441825
lineWidth = 0;
needInsertNewLine = false;
} else {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=489488
needIndentNewLine = false;
afterNewLine = EMPTY_STRING;
}
}
start = comment.sourceEnd() + offset;
break;
case org.eclipse.php.core.compiler.ast.nodes.Comment.TYPE_PHPDOC:
previousCommentIsSingleLine = false;
inComment = false;
handleCharsWithoutComments(start, comment.sourceStart() + offset);
inComment = true;
resetEnableStatus(
document.get(comment.sourceStart() + offset, comment.sourceEnd() - comment.sourceStart()));
String codeBeforeComment = document.get(0, comment.sourceStart() + offset).trim();
boolean isHeaderComment = codeBeforeComment.equals("<?") //$NON-NLS-1$
|| codeBeforeComment.equals("<?php"); //$NON-NLS-1$
if ((!isHeaderComment || this.preferences.comment_format_header) && this.editsEnabled
&& this.preferences.comment_format_javadoc_comment
&& canHandlePHPDocComment((PHPDocBlock) comment, offset)) {
PHPDocBlock block = (PHPDocBlock) comment;
newLineOfComment = false;
appendToBuffer("/**"); //$NON-NLS-1$
commentWords = new ArrayList<>();
org.eclipse.php.core.compiler.ast.nodes.Scalar[] texts = block.getTexts()
.toArray(new org.eclipse.php.core.compiler.ast.nodes.Scalar[block.getTexts().size()]);
PHPDocTag[] tags = block.getTags();
if (tags == null || tags.length == 0) {
texts = getNonblankScalars(texts);
}
boolean lastLineIsBlank = false;
boolean isFirst = true;
// description is blank
if (getNonblankScalars(texts).length == 0) {
texts = new org.eclipse.php.core.compiler.ast.nodes.Scalar[0];
}
if (this.preferences.comment_new_lines_at_javadoc_boundaries) {
insertNewLineForPHPDoc();
// description is blank
if (texts.length == 0) {
lastLineIsBlank = true;
}
}
int textsLength = texts.length;
for (int j = 0; j < textsLength; j++) {
org.eclipse.php.core.compiler.ast.nodes.Scalar scalar = texts[j];
String word = scalar.getValue();
if (word.trim().length() > 0) {
commentWords.add(word);
if (this.preferences.join_lines_in_comments) {
if (!isFirst) {
insertNewLineForPHPDoc();
}
isFirst = false;
initCommentWords();
formatPHPDocText(commentWords, null, false, false);
commentWords = new ArrayList<String>();
lastLineIsBlank = false;
}
} else if (!this.preferences.comment_clear_blank_lines_in_javadoc_comment) {
// don't duplicate first blank line
if (isFirst && this.preferences.comment_new_lines_at_javadoc_boundaries
&& commentWords.isEmpty()) {
isFirst = false;
lastLineIsBlank = true;
continue;
}
isFirst = false;
initCommentWords();
formatPHPDocText(commentWords, null, false, false);
insertNewLineForPHPDoc();
commentWords = new ArrayList<String>();
lastLineIsBlank = true;
}
}
if (!commentWords.isEmpty()) {
initCommentWords();
formatPHPDocText(commentWords, null, false, false);
lastLineIsBlank = false;
}
if (tags != null && tags.length > 0) {
if (this.preferences.comment_insert_empty_line_before_root_tags && !lastLineIsBlank) {
insertNewLineForPHPDoc();
appendToBuffer(" "); //$NON-NLS-1$
}
for (int i = 0; i < tags.length; i++) {
PHPDocTag phpDocTag = tags[i];
boolean insertTag = true;
String[] words = phpDocTag.getDescTexts().toArray(new String[0]);
if ((i == tags.length - 1) && !this.preferences.comment_new_lines_at_javadoc_boundaries) {
words = getNonblankWords(words);
}
commentWords = new ArrayList<>();
if (getNonblankWords(words).length == 0) {
boolean hasRefs = phpDocTag.getAllReferencesWithOrigOrder().size() != 0;
int nbLines = words.length;
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=433938
if (!hasRefs && nbLines >= 1) {
nbLines--;
}
// insert several lines
formatCommentWords(phpDocTag, insertTag, false);
for (int j = 0; j < nbLines; j++) {
insertNewLineForPHPDoc();
}
} else {
for (int j = 0; j < words.length; j++) {
String word = words[j];
if (word.trim().length() > 0) {
commentWords.add(word);
if (this.preferences.join_lines_in_comments) {
formatCommentWords(phpDocTag, insertTag, true);
insertTag = false;
}
} else if (!this.preferences.comment_clear_blank_lines_in_javadoc_comment
&& !insertTag) {
formatCommentWords(phpDocTag, insertTag, true);
insertTag = false;
}
}
if (!commentWords.isEmpty() || insertTag) {
formatCommentWords(phpDocTag, insertTag, !commentWords.isEmpty());
}
}
}
lastLineIsBlank = false;
}
if (this.preferences.comment_new_lines_at_javadoc_boundaries && !lastLineIsBlank) {
insertNewLineForPHPDoc();
appendToBuffer("/"); //$NON-NLS-1$
} else if (lastLineIsBlank) {
appendToBuffer("/"); //$NON-NLS-1$
} else {
indertWordToComment("*/"); //$NON-NLS-1$
}
handleCharsWithoutComments(comment.sourceStart() + offset, comment.sourceEnd() + offset, true);
} else {
commentContent = document.get(comment.sourceStart() + offset,
comment.sourceEnd() - comment.sourceStart());
List<String> lines = Arrays.asList(commentContent.split("\r\n?|\n", -1)); //$NON-NLS-1$
appendToBuffer(lines.get(0));
// indent all lines, even empty lines
for (int i = 1; i < lines.size(); i++) {
insertNewLineForPHPDoc(false);
appendToBuffer(lines.get(i).replaceFirst("^\\p{javaWhitespace}+", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
handleCharsWithoutComments(comment.sourceStart() + offset, comment.sourceEnd() + offset, true);
}
start = comment.sourceEnd() + offset;
insertNewLine();
indent();
break;
case org.eclipse.php.core.compiler.ast.nodes.Comment.TYPE_MULTILINE:
previousCommentIsSingleLine = false;
// ignore multi line comments in the middle of code
// example while /* kuku */ ( /* kuku */$a > 0 )
if (getBufferFirstChar(0) != '\0') {
replaceBuffer.setLength(0);
IRegion reg = document.getLineInformationOfOffset(end);
// TODO: Do line width calculation based on the
// formatted content instead of the original content
lineWidth = end - reg.getOffset();
resetEnableStatus(
document.get(comment.sourceStart() + offset, comment.sourceEnd() - comment.sourceStart()));
for (; iter.hasNext();) {
org.eclipse.php.core.compiler.ast.nodes.Comment nextComment = iter.next();
resetEnableStatus(document.get(nextComment.sourceStart() + offset,
nextComment.sourceEnd() - nextComment.sourceStart()));
}
start = end;
break comments;
}
// buffer contains only whitespace chars
indentOnFirstColumn = !startAtFirstColumn
|| !this.preferences.never_indent_block_comments_on_first_column;
if (startLine == commentStartLine) {
indentOnFirstColumn = false;
IRegion startLinereg = document.getLineInformation(startLine);
// TODO: Do line width calculation based on the
// formatted content instead of the original content
lineWidth = comment.sourceStart() + offset - startLinereg.getOffset();
if (position >= 0) {
// if (getBufferFirstChar(position
// + lineSeparator.length()) == '\0') {
afterNewLine = replaceBuffer.substring(position + lineSeparator.length(),
replaceBuffer.length());
replaceBuffer.replace(position, replaceBuffer.length(), ""); //$NON-NLS-1$
insertSpaces(1);
// } else {
// insertSpace();
// }
} else {
// if (getBufferFirstChar(0) == '\0') {
replaceBuffer.setLength(0);
insertSpaces(1);
// } else {
// insertSpace();
// }
}
} else {
if (position >= 0) {
// if (getBufferFirstChar(position
// + lineSeparator.length()) == '\0') {
replaceBuffer.replace(position + lineSeparator.length(), replaceBuffer.length(), ""); //$NON-NLS-1$
lineWidth = 0;
// } else {
// insertNewLine();
// }
} else {
// if (getBufferFirstChar(0) == '\0') {
replaceBuffer.setLength(0);
lineWidth = 0;
// } else {
// insertNewLine();
// }
}
if (indentationLevelDescending || blockEnd) {
// add single indentationChar * indentationSize
// Because the comment is the previous
// indentation
// level
for (int i = 0; i < preferences.indentationSize; i++) {
appendToBuffer(preferences.indentationChar);
lineWidth += (preferences.indentationChar == CodeFormatterPreferences.SPACE_CHAR) ? 0 : 3;
}
}
needIndentNewLine = true;
}
resetCommentIndentVariables();
if (startLine != commentStartLine && blockEnd) {
recordCommentIndentVariables = true;
}
doNotIndent = true;
if (indentOnFirstColumn) {
indent();
doNotIndent = false;
if (lineWidth > 0) {
startAtFirstColumn = false;
}
}
handleCharsWithoutComments(start, comment.sourceStart() + offset);
doNotIndent = false;
start = comment.sourceEnd() + offset;
resetEnableStatus(
document.get(comment.sourceStart() + offset, comment.sourceEnd() - comment.sourceStart()));
if (startLine == commentStartLine) {
initCommentIndentVariables(offset, startLine, comment, endWithNewLineIndent);
lineWidth = indentLengthForComment;
}
if (startAtFirstColumn && this.preferences.never_indent_block_comments_on_first_column) {
indentLengthForComment = 0;
indentStringForComment = ""; //$NON-NLS-1$
}
if (this.editsEnabled && this.preferences.comment_format_block_comment
&& !(comment instanceof VarComment)) {
appendToBuffer("/*"); //$NON-NLS-1$
commentContent = document.get(comment.sourceStart() + offset,
comment.sourceEnd() - comment.sourceStart());
// boolean needInsertNewLine = commentContent
// .endsWith(lineSeparator);
// if (!needInsertNewLine) {
// String[] delimiters = document.getLegalLineDelimiters();
// for (int i = 0; i < delimiters.length; i++) {
// needInsertNewLine = commentContent
// .endsWith(delimiters[i]);
// if (needInsertNewLine) {
// break;
// }
// }
// }
commentContent = commentContent.trim();
commentContent = commentContent.substring(2, commentContent.length() - 2);
List<String> lines = Arrays.asList(commentContent.split("\r\n?|\n", -1)); //$NON-NLS-1$
commentWords = new ArrayList<>();
if (lines.size() == 1) {
String word = lines.get(0).trim();
commentWords.add(word);
initCommentWords();
StringBuilder sb = new StringBuilder();
for (String w : commentWords) {
if (StringUtils.isBlank(w)) {
continue;
}
sb.append(w).append(" "); //$NON-NLS-1$
}
// +1 means ' ' after "/*",+2 means "*/"
if (this.preferences.comment_line_length == 9999
|| lineWidth + 1 + sb.length() + 2 <= this.preferences.comment_line_length) {
appendToBuffer(" "); //$NON-NLS-1$
appendToBuffer(sb.toString());
appendToBuffer("*/"); //$NON-NLS-1$
commentWords = new ArrayList<>();
handleCharsWithoutComments(comment.sourceStart() + offset, comment.sourceEnd() + offset,
true);
// if (needInsertNewLine) {
insertNewLine();
// needInsertNewLine = false;
// } else {
// IRegion reg = document
// .getLineInformation(commentEndLine);
// int lengthAfterCommentEnd = reg.getOffset()
// + reg.getLength()
// - (comment.sourceEnd() + offset);
// if (lengthAfterCommentEnd <= 0) {
// insertNewLine();
// } else {
// String stringAfterCommentEnd = document
// .get(comment.sourceEnd() + offset,
// lengthAfterCommentEnd);
// if (stringAfterCommentEnd.trim().length() == 0) {
// insertNewLine();
// } else {
// insertSpaces(1);
// needIndentNewLine = false;
// afterNewLine = EMPTY_STRING;
// }
// }
// }
break;
}
commentWords = new ArrayList<>();
}
newLineOfComment = false;
if (this.preferences.comment_new_lines_at_block_boundaries) {
insertNewLineForPHPBlockComment(indentLengthForComment, indentStringForComment);
newLineOfComment = true;
}
boolean isFirst = true;
for (int j = 0; j < lines.size(); j++) {
String word = lines.get(j).trim();
if (word.startsWith("*")) { //$NON-NLS-1$
word = word.substring(1);
}
if (word.length() > 0) {
commentWords.add(word);
if (this.preferences.join_lines_in_comments) {
if (!isFirst) {
insertNewLineForPHPBlockComment(indentLengthForComment, indentStringForComment);
newLineOfComment = true;
}
isFirst = false;
formatCommentBlockWords(indentLengthForComment, indentStringForComment);
}
} else if (!this.preferences.comment_clear_blank_lines_in_block_comment) {
if (j != 0 && j != lines.size() - 1) {
formatCommentBlockWords(indentLengthForComment, indentStringForComment);
// don't duplicate first blank line
if (isFirst && this.preferences.comment_new_lines_at_block_boundaries) {
newLineOfComment = true;
isFirst = false;
continue;
}
insertNewLineForPHPBlockComment(indentLengthForComment, indentStringForComment);
newLineOfComment = true;
isFirst = false;
}
}
}
if (!commentWords.isEmpty()) {
formatCommentBlockWords(indentLengthForComment, indentStringForComment);
isFirst = false;
}
if (isFirst && this.preferences.comment_new_lines_at_block_boundaries) {
appendToBuffer("/"); //$NON-NLS-1$
} else if (newLineOfComment || this.preferences.comment_new_lines_at_block_boundaries) {
insertNewLine();
if (indentLengthForComment >= 0) {
appendToBuffer(indentStringForComment);
} else {
indent();
}
appendToBuffer(" */"); //$NON-NLS-1$
} else {
indertWordToComment("*/"); //$NON-NLS-1$
}
newLineOfComment = false;
handleCharsWithoutComments(comment.sourceStart() + offset, comment.sourceEnd() + offset, true);
} else {
commentContent = document.get(comment.sourceStart() + offset,
comment.sourceEnd() - comment.sourceStart());
List<String> lines = Arrays.asList(commentContent.split("\r\n?|\n", -1)); //$NON-NLS-1$
appendToBuffer(lines.get(0));
// indent all lines, even empty lines
for (int i = 1; i < lines.size(); i++) {
insertNewLineForPHPBlockComment(indentLengthForComment, indentStringForComment, false);
appendToBuffer(lines.get(i).replaceFirst("^\\p{javaWhitespace}+", "")); //$NON-NLS-1$ //$NON-NLS-2$
}
handleCharsWithoutComments(comment.sourceStart() + offset, comment.sourceEnd() + offset, true);
}
insertNewLine();
break;
}
if (needIndentNewLine) {
indent();
needIndentNewLine = false;
afterNewLine = EMPTY_STRING;
}
appendToBuffer(afterNewLine);
}
inComment = false;
ignoreEmptyLineSetting = oldIgnoreEmptyLineSetting;
resetCommentIndentVariables();
handleCharsWithoutComments(start, end);
}
private boolean canHandlePHPDocComment(PHPDocBlock comment, int offset) throws BadLocationException {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=474332
// do not handle single-line PHPDoc comment with @var tag inside
PHPDocTag[] varTags = comment.getTags(TagKind.VAR);
if (varTags.length != 1) {
return true;
}
int commentStartLine = document.getLineOfOffset(comment.sourceStart() + offset);
int commentEndLine = document.getLineOfOffset(comment.sourceEnd() + offset);
return commentStartLine != commentEndLine;
}
private boolean endWithNewLineIndent(String string) {
String indent = getIndent();
return string.endsWith(lineSeparator + indent);
}
private String getIndent() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < indentationLevel * preferences.indentationSize; i++) {
sb.append(preferences.indentationChar);
}
return sb.toString();
}
private void resetCommentIndentVariables() {
indentLengthForComment = -1;
indentStringForComment = null;
}
private void indentForComment(boolean indentationLevelDescending) {
indent();
if (indentationLevelDescending || blockEnd) {
for (int i = 0; i < preferences.indentationSize; i++) {
appendToBuffer(preferences.indentationChar);
lineWidth += (preferences.indentationChar == CodeFormatterPreferences.SPACE_CHAR) ? 0 : 3;
}
}
}
private void initCommentIndentVariables(int offset, int startLine,
org.eclipse.php.core.compiler.ast.nodes.Comment comment, boolean endWithNewLineIndent)
throws BadLocationException {
// TODO the value should be calculated from ReplaceEdit changes
indentLengthForComment = 0;
indentStringForComment = ""; //$NON-NLS-1$
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=461701
// Indentation is never set and used when inside of <?= ?> tags.
// See also method indent().
if (isPhpEqualTag) {
return;
}
IRegion startRegion = document.getLineInformation(startLine);
String startLineContent = document
.get(startRegion.getOffset(), comment.sourceStart() + offset - startRegion.getOffset()).trim();
// indentStringForComment = FormatterUtils.getLineBlanks(document,
// startRegion);
StringBuilder sb = new StringBuilder();
int lastIndentationLevel = indentationLevel;
if (endWithNewLineIndent) {
if (indentationLevelList.size() >= 2) {
lastIndentationLevel = indentationLevelList.get(indentationLevelList.size() - 2);
} else {
lastIndentationLevel = indentationLevelList.get(indentationLevelList.size() - 1);
}
} else {
lastIndentationLevel = indentationLevelList.get(indentationLevelList.size() - 1);
}
for (int i = 0; i < lastIndentationLevel * preferences.indentationSize; i++) {
sb.append(preferences.indentationChar);
}
for (int i = 0; i < startLineContent.length(); i++) {
sb.append(" "); //$NON-NLS-1$
}
if (startLineContent.length() > 0) {
sb.append(" "); //$NON-NLS-1$
}
indentStringForComment = sb.toString();
char[] blankArray = indentStringForComment.toCharArray();
for (int i = 0; i < blankArray.length; i++) {
if (blankArray[i] == '\t') {
indentLengthForComment += 3;
} else {
indentLengthForComment++;
}
}
}
public static List<String> removeEmptyString(List<String> commentWords) {
List<String> result = new ArrayList<>();
for (int i = 0; i < commentWords.size(); i++) {
String word = commentWords.get(i);
if (word.trim().length() != 0) {
result.add(word);
}
}
return result;
}
private org.eclipse.php.core.compiler.ast.nodes.Scalar[] getNonblankScalars(
org.eclipse.php.core.compiler.ast.nodes.Scalar[] texts) {
int end = texts.length;
for (int i = texts.length - 1; i >= 0; i--) {
if (StringUtils.isBlank(texts[i].getValue())) {
if (end > 0) {
end--;
}
} else {
break;
}
}
if (end == 0) {
return new org.eclipse.php.core.compiler.ast.nodes.Scalar[0];
}
int start = 0;
for (int i = 0; i < texts.length; i++) {
if (StringUtils.isBlank(texts[i].getValue())) {
if (start < texts.length - 1) {
start++;
}
} else {
break;
}
}
org.eclipse.php.core.compiler.ast.nodes.Scalar[] result = new org.eclipse.php.core.compiler.ast.nodes.Scalar[end
- start];
System.arraycopy(texts, start, result, 0, end - start);
return result;
}
private String[] getNonblankWords(String[] words) {
int length = words.length;
for (int i = words.length - 1; i >= 0; i--) {
if (StringUtils.isBlank(words[i])) {
length--;
} else {
break;
}
}
String[] result = new String[length];
System.arraycopy(words, 0, result, 0, length);
return result;
}
private void resetEnableStatus(String content) {
int enablingTagIndex = -1;
int disablingTagIndex = -1;
if (this.useTags) {
if (this.disablingTag != null) {
disablingTagIndex = content.lastIndexOf(disablingTag);
}
if (this.enablingTag != null) {
enablingTagIndex = content.lastIndexOf(enablingTag);
}
if (enablingTagIndex < disablingTagIndex) {
this.editsEnabled = false;
} else if (enablingTagIndex > disablingTagIndex) {
this.editsEnabled = true;
}
}
}
private void formatCommentBlockWords(int indentLength, String blanks) {
initCommentWords();
for (String word : commentWords) {
if (StringUtils.isBlank(word)) {
continue;
}
indertWordToCommentBlock(word, indentLength, blanks);
}
commentWords = new ArrayList<>();
}
private void formatCommentWords(PHPDocTag phpDocTag, boolean insertTag, boolean hasDesc) {
initCommentWords();
insertNewLineForPHPDoc();
formatPHPDocText(commentWords, phpDocTag, insertTag, hasDesc);
commentWords = new ArrayList<String>();
}
private void initCommentWords() {
String commentContent = join(commentWords, " "); //$NON-NLS-1$
commentContent = commentContent.trim();
commentWords = Arrays.asList(MagicMemberUtil.WHITESPACE_SEPERATOR.split(commentContent));
commentWords = removeEmptyString(commentWords);
}
private void insertNewLineForPHPBlockComment(int indentLength, String blanks) {
insertNewLineForPHPBlockComment(indentLength, blanks, true);
}
private void insertNewLineForPHPBlockComment(int indentLength, String blanks, boolean addCommentSymbol) {
insertNewLine();
if (indentLength >= 0) {
appendToBuffer(blanks);
lineWidth = lineWidth + (indentLength - blanks.length());
} else {
indent();
}
if (addCommentSymbol) {
appendToBuffer(" *"); //$NON-NLS-1$
} else {
appendToBuffer(" "); //$NON-NLS-1$
}
}
private void insertNewLineForPHPDoc() {
insertNewLineForPHPDoc(true);
}
private void insertNewLineForPHPDoc(boolean addCommentSymbol) {
insertNewLine();
indent();
if (addCommentSymbol) {
appendToBuffer(" *"); //$NON-NLS-1$
} else {
appendToBuffer(" "); //$NON-NLS-1$
}
}
private void formatPHPDocText(List<String> words, PHPDocTag phpDocTag, boolean insertTag, boolean hasDesc) {
boolean insertSpace = true;
String tag = ""; //$NON-NLS-1$
// int indentLength = 0;
if (phpDocTag != null) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=469402
tag = phpDocTag.getMatchedTag();
// if (indentationLevelDescending) {
// for (int i = 0; i < preferences.indentationSize; i++) {
// indentLength += (preferences.indentationChar ==
// CodeFormatterPreferences.SPACE_CHAR) ? 1
// : 4;
// }
// }
}
int tagLength = tag.length() + 1;
newLineOfComment = true;
if (phpDocTag != null) {
if (insertTag) {
insertSpaces(1);
String reference = getTagReference(phpDocTag);
appendToBuffer(tag);
appendToBuffer(reference);
}
newLineOfComment = false;
if (this.preferences.comment_insert_new_line_for_parameter && phpDocTag.getTagKind() == TagKind.PARAM) {
if (insertTag && hasDesc) {
insertNewLineForPHPDoc();
}
// we do no extra indentation (after a tag reference) when
// inserting a tag (i.e. the tag name and the tag reference
// parts) having no tag description (because no newline was
// inserted after the tag reference)
boolean doIndent = !(insertTag && !hasDesc);
if (doIndent) {
if (this.preferences.comment_indent_root_tags) {
insertSpaces(tagLength);
}
insertSpaces(1);
insertSpace = false;
newLineOfComment = true;
if (this.preferences.comment_indent_root_tags
&& this.preferences.comment_indent_parameter_description) {
for (int i = 0; i < preferences.indentationSize; i++) {
appendToBuffer(preferences.indentationChar);
lineWidth += (preferences.indentationChar == CodeFormatterPreferences.SPACE_CHAR) ? 0 : 3;
}
}
}
} else if (!insertTag && this.preferences.comment_indent_root_tags) {
insertSpaces(tagLength);
}
}
for (String word : words) {
if (StringUtils.isBlank(word)) {
continue;
}
indertWordToComment(phpDocTag, tagLength, word, insertSpace);
insertSpace = true;
}
}
private String getTagReference(PHPDocTag phpDocTag) {
SimpleReference[] reference = phpDocTag.getAllReferencesWithOrigOrder().toArray(new SimpleReference[0]);
StringBuilder sb = new StringBuilder();
for (int i = 0; i < reference.length; i++) {
if (i > 0 && reference[i - 1] instanceof TypeReference && reference[i] instanceof TypeReference) {
sb.append(Constants.TYPE_SEPARATOR_CHAR).append(reference[i].getName());
} else {
sb.append(" ").append(reference[i].getName()); //$NON-NLS-1$
}
}
return sb.toString();
}
private void indertWordToComment(String word) {
indertWordToComment(null, 0, word, true);
}
private void indertWordToCommentBlock(String word, int indentLength, String blanks) {
if (this.preferences.comment_line_length != 9999 && !newLineOfComment
&& (lineWidth + 1 + word.length() > this.preferences.comment_line_length)) {
insertNewLine();
if (indentLength >= 0) {
appendToBuffer(blanks);
lineWidth = lineWidth + (indentLength - blanks.length());
} else {
indent();
}
appendToBuffer(" * "); //$NON-NLS-1$
appendToBuffer(word);
} else {
insertSpaces(1);
appendToBuffer(word);
newLineOfComment = false;
}
}
private void indertWordToComment(PHPDocTag phpDocTag, int tagLength, String word, boolean insertSpace) {
word = word.trim();
if (this.preferences.comment_line_length != 9999 && !newLineOfComment
&& (lineWidth + 1 + word.length() > this.preferences.comment_line_length)) {
insertNewLineForPHPDoc();
appendToBuffer(" "); //$NON-NLS-1$
if (phpDocTag != null) {
if (this.preferences.comment_indent_root_tags) {
insertSpaces(tagLength);
}
if (this.preferences.comment_indent_root_tags && this.preferences.comment_indent_parameter_description
&& phpDocTag.getTagKind() == TagKind.PARAM) {
for (int i = 0; i < preferences.indentationSize; i++) {
appendToBuffer(preferences.indentationChar);
lineWidth += (preferences.indentationChar == CodeFormatterPreferences.SPACE_CHAR) ? 0 : 3;
}
}
}
appendToBuffer(word);
} else {
if (insertSpace) {
insertSpaces(1);
}
appendToBuffer(word);
newLineOfComment = false;
}
}
private void insertSpaces(int size) {
for (int i = 0; i < size; i++) {
replaceBuffer.append(SPACE);
lineWidth++;
isPrevSpace = true;
}
}
private void handleForSemicolon(Expression[] beforExpressions, Expression[] afterExpressions) {
if (this.preferences.insert_space_before_semicolon_in_for && beforExpressions.length > 0) {
insertSpace();
}
appendToBuffer(SEMICOLON);
if (this.preferences.insert_space_after_semicolon_in_for && afterExpressions.length > 0) {
insertSpace();
}
}
/**
* handle the PHP end tag
*
* @param start
* the end position of the ASTNode before the semicolon
* @param end
* the position of the semicolon -1
*/
private void handlePhpEndTag(int start, int end, String endTagStr) {
appendToBuffer(endTagStr);
try {
boolean foundTag = false;
for (int index = 0; start + index < end; index++) {
char currentChar = document.getChar(start + index);
if (foundTag) {
appendToBuffer(currentChar);
} else if (currentChar == '>') {
foundTag = true;
}
}
} catch (BadLocationException e) {
Logger.logException(e);
}
}
/**
* handle the last semicolon of statement
*
* @param start
* the end position of the ASTNode before the semicolon
* @param end
* the position of the semicolon -1
*/
private void handleSemicolon(int start, int end) {
if (this.preferences.insert_space_before_semicolon && !isHeredocSemicolon) {
insertSpace();
}
// check if the statement end with ; or ?>
if (isContainChar(start, end, SEMICOLON)) {
appendToBuffer(SEMICOLON);
if (isHeredocSemicolon && isPhpEqualTag) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=411322
// always insert a new line after the closing HEREDOC tag
isPhpEqualTag = false;
insertNewLine();
isPhpEqualTag = true;
}
isHeredocSemicolon = false;
} else if (isContainChar(start, end, QUESTION_MARK)) {
handlePhpEndTag(start, end, "?>"); //$NON-NLS-1$
} else {
handlePhpEndTag(start, end, "%>"); //$NON-NLS-1$
}
// till the semicolon
handleChars(start, end);
binaryExpressionLineWrapPolicy = -1;// reset the policy for binary
// expression states
if (wasBinaryExpressionWrapped) {
indentationLevel -= binaryExpressionIndentGap;
wasBinaryExpressionWrapped = false;
}
binaryExpressionIndentGap = 0;
binaryExpressionSavedBuffer = null;
if (isBinaryExpressionExtraIndentation) {
indentationLevel--;
isBinaryExpressionExtraIndentation = false;
}
}
/**
* indent and add the indentation level to the indentation level list
*
*/
private void indent() {
if (!isPhpEqualTag) {
indentationLevelList.add(indentationLevel);
for (int i = 0; i < indentationLevel * preferences.indentationSize; i++) {
appendToBuffer(preferences.indentationChar);
lineWidth += (preferences.indentationChar == CodeFormatterPreferences.SPACE_CHAR) ? 0 : 3;
}
}
}
// this method calculates the delta of lines width for AST
// nodes such as
// scalars, html in-lines etc... since these types can have multiple lines
// we still need to check tabs length
private void updateLinesWidth(ASTNode node) {
try {
int lineForStart = document.getLineOfOffset(node.getStart());
int lineForEnd = document.getLineOfOffset(node.getEnd());
if (lineForStart == lineForEnd) {
lineWidth += node.getLength();
} else {
lineWidth = document.getLineLength(lineForEnd);
}
} catch (BadLocationException e) {
Logger.logException(e);
}
}
private void insertNewLine() {
if (!isPhpEqualTag) {
appendToBuffer(lineSeparator);
lineWidth = 0;
}
}
private void insertNewLines(Statement statement) {
int numberOfLines = getNumbreOfLines(statement);
for (int i = 0; i < numberOfLines; i++) {
insertNewLine();
}
}
private int getNumbreOfLines(Statement statement) {
int numberOfLines = 1;
switch (statement.getType()) {
case ASTNode.NAMESPACE:
numberOfLines = this.preferences.blank_lines_before_namespace + 1;
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
break;
case ASTNode.FUNCTION_DECLARATION:
case ASTNode.METHOD_DECLARATION:
numberOfLines = this.preferences.blank_line_before_method_declaration + 1;
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
break;
case ASTNode.FIELD_DECLARATION:
numberOfLines = this.preferences.blank_line_before_field_declaration + 1;
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
break;
case ASTNode.CLASS_DECLARATION:
case ASTNode.INTERFACE_DECLARATION:
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
numberOfLines = this.preferences.blank_line_before_class_declaration + 1;
break;
case ASTNode.CONSTANT_DECLARATION:
numberOfLines = this.preferences.blank_line_before_constant_declaration + 1;
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
break;
default:
// no empty lines
numberOfLines = 1;
break;
}
return numberOfLines;
}
private void handleSplittedPhpBlock(int offset, int end) throws BadLocationException {
IRegion lineRegion = document.getLineInformationOfOffset(offset);
switch (getPhpStartTag(offset)) {
case PHP_OPEN_ASP_TAG:
case PHP_OPEN_SHORT_TAG:
if (document.get(offset + 2, lineRegion.getOffset() + lineRegion.getLength() - (offset + 2)).trim()
.length() != 0) {
insertNewLine();
}
handleCharsWithoutComments(offset + 2, end);
break;
case PHP_OPEN_SHORT_TAG_WITH_EQUAL:
handleCharsWithoutComments(offset + 3, end);
break;
case PHP_OPEN_TAG:
if (document.get(offset + 5, lineRegion.getOffset() + lineRegion.getLength() - (offset + 5)).trim()
.length() != 0) {
insertNewLine();
}
handleCharsWithoutComments(offset + 5, end);
break;
}
}
private void insertSpace() {
if (!isPrevSpace) {
replaceBuffer.append(SPACE);
lineWidth++;
isPrevSpace = true;
}
}
private void insertString(int offset, int end, String content) {
assert end >= offset;
ReplaceEdit replaceEdit = new ReplaceEdit(offset, end - offset, content);
changes.add(replaceEdit);
}
private boolean isContainChar(int start, int end, char c) {
try {
for (int index = 0; start + index < end; index++) {
if (document.getChar(start + index) == c) {
return true;
}
}
} catch (BadLocationException e) {
Logger.logException(e);
}
return false;
}
private boolean isEmptyLine(int line) throws BadLocationException {
int lineStart = document.getLineOffset(line);
int lineEnd = lineStart + document.getLineLength(line);
for (int offset = lineStart; offset < lineEnd; offset++) {
char currChar = document.getChar(offset);
if (currChar != ' ' && currChar != '\t' && currChar != '\r' && currChar != '\n') {
// not empty line
return false;
}
}
return true;
}
// this operation "reverts" the visitor into the last "saved" state of the
// changes
// since when we need to go back to first element within comma-separated
// list
// after we already added the formatting changes into the buffer.
private void revert(String savedBuffer, int changesIndex) {
replaceBuffer.setLength(0);
replaceBuffer.append(savedBuffer);
for (int index = changes.size() - 1; index > changesIndex; index--) {
changes.remove(index);
}
}
private void scan(final int offset, final int end) throws Exception {
final Reader reader = new DocumentReader(document, offset, end - offset);
if (astLexer == null) {
// create the lexer for the first time
astLexer = getLexer(reader);
} else {
// reset the lexer
astLexer.yyreset(reader);
astLexer.resetCommentList();
}
// ST_IN_SCRIPTING
assert stInScriptin != -1;
astLexer.yybegin(stInScriptin);
tokens.clear();
try {
Symbol symbol = null;
do {
symbol = astLexer.next_token();
tokens.add(symbol);
} while (symbol != null && symbol.sym != 0);
} catch (Exception e) {
Logger.logException(e);
}
}
private void setSpaceAfterBlock(Statement statement) {
if (statement instanceof Block && !((Block) statement).isCurly()) {
// do not insert a space when handling "if :" and "else :" blocks
return;
}
if (this.preferences.insert_space_after_closing_brace_in_block) {
insertSpace();
}
}
@Override
public boolean visit(ArrayAccess arrayAccess) {
Expression variableName = arrayAccess.getName();
variableName.accept(this);
if (this.preferences.insert_space_before_opening_bracket_in_array) {
insertSpace();
}
if (arrayAccess.getArrayType() == ArrayAccess.VARIABLE_ARRAY) {
appendToBuffer(OPEN_BRACKET);
} else {
appendToBuffer(OPEN_CURLY);
}
int lastPosition = variableName.getEnd();
if (arrayAccess.getIndex() == null) {
if (this.preferences.insert_space_between_empty_brackets) {
insertSpace();
}
} else {
if (this.preferences.insert_space_after_opening_bracket_in_array) {
insertSpace();
}
handleChars(lastPosition, arrayAccess.getIndex().getStart());
arrayAccess.getIndex().accept(this);
if (this.preferences.insert_space_before_closing_bracket_in_array) {
insertSpace();
}
lastPosition = arrayAccess.getIndex().getEnd();
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=468155
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=439568
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=440209
// https://bugs.eclipse.org/bugs/attachment.cgi?id=245293
// if (arrayAccess.getArrayType() == ArrayAccess.VARIABLE_ARRAY) {
// appendToBuffer(CLOSE_BRACKET);
// } else {
// appendToBuffer(CLOSE_CURLY);
// }
// handleChars(lastPosition, arrayAccess.getEnd());
indentationLevelDescending = true;
handleChars(lastPosition, arrayAccess.getEnd() - 1);
lineWidth++;// we need to add the closing bracket/curly
return false;
}
@Override
public boolean visit(ArrayCreation arrayCreation) {
if (this.preferences.insert_space_before_opening_paren_in_array) {
insertSpace();
}
int lastPosition; // array
if (arrayCreation.isHasArrayKey()) {
appendToBuffer(OPEN_PARN);
lastPosition = arrayCreation.getStart() + 5;
} else {
// appendToBuffer(OPEN_BRACKET);
lastPosition = arrayCreation.getStart() + 1;
}
List<ArrayElement> eleList = arrayCreation.elements();
ArrayElement[] elements = new ArrayElement[eleList.size()];
elements = eleList.toArray(elements);
if (elements.length > 0) {
if (this.preferences.insert_space_after_opening_paren_in_array) {
insertSpace();
}
if (this.preferences.new_line_after_open_array_parenthesis) {
insertNewLine();
indentationLevel++;
indent();
indentationLevel--;
}
lineWidth += 5;
int indentationGap = calculateIndentGap(this.preferences.line_wrap_expressions_in_array_init_indent_policy,
this.preferences.line_wrap_array_init_indentation);
// work around for close bracket.
lineWidth++;
lastPosition = handleCommaList(elements, lastPosition,
this.preferences.insert_space_before_list_comma_in_array,
this.preferences.insert_space_after_list_comma_in_array,
this.preferences.line_wrap_expressions_in_array_init_line_wrap_policy, indentationGap,
this.preferences.line_wrap_expressions_in_array_init_force_split);
// work around for close bracket.
lineWidth--;
if (this.preferences.insert_space_before_closing_paren_in_array) {
insertSpace();
}
if (this.preferences.new_line_before_close_array_parenthesis_array) {
insertNewLine();
indent();
}
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=468155
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=439568
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=440209
// https://bugs.eclipse.org/bugs/attachment.cgi?id=245293
// if (arrayCreation.isHasArrayKey()) {
// appendToBuffer(CLOSE_PARN);
// } else {
// appendToBuffer(CLOSE_BRACKET);
// }
// handleChars(lastPosition, arrayCreation.getEnd());
indentationLevelDescending = true;
handleChars(lastPosition, arrayCreation.getEnd() - 1);
lineWidth++;// we need to add the closing bracket/parenthesis
return false;
}
private int calculateIndentGap(int indentationPolicy, int defaultIndentation) {
switch (indentationPolicy) {
case DEFAULT_INDENTATION:
return defaultIndentation;
case INDENT_ON_COLUMN:
// gap is the number of indentation for all the line
// we increase the indentationLevel to get the gap
int lineIndentation;
if (this.preferences.indentationChar == '\t') {
lineIndentation = (int) Math.ceil(lineWidth / 4);
} else {
lineIndentation = (int) Math.ceil(lineWidth / this.preferences.indentationSize);
}
return lineIndentation - indentationLevel;
case INDENT_ONE:
return 1;
default:
return NO_LINE_WRAP_INDENT;
}
}
@Override
public boolean visit(ArrayElement arrayElement) {
if (arrayElement.getKey() != null) {
arrayElement.getKey().accept(this);
if (this.preferences.insert_space_before_arrow_in_array) {
insertSpace();
}
appendToBuffer(KEY_VALUE_OPERATOR);
if (this.preferences.insert_space_after_arrow_in_array) {
insertSpace();
}
handleChars(arrayElement.getKey().getEnd(), arrayElement.getValue().getStart());
}
arrayElement.getValue().accept(this);
return false;
}
@Override
public boolean visit(Assignment assignment) {
VariableBase leftSide = assignment.getLeftHandSide();
leftSide.accept(this);
if (this.preferences.insert_space_before_assignment) {
insertSpace();
}
appendToBuffer(Assignment.getOperator(assignment.getOperator()));
if (this.preferences.insert_space_after_assignment) {
insertSpace();
}
// handle the chars between the variable to the value
Expression rightSideValue = assignment.getRightHandSide();
handleChars(leftSide.getEnd(), rightSideValue.getStart());
rightSideValue.accept(this);
return false;
}
@Override
public boolean visit(ASTError astError) {
updateLinesWidth(astError);
return false;
}
@Override
public boolean visit(BackTickExpression backTickExpression) {
updateLinesWidth(backTickExpression);
return false;
}
@Override
public boolean visit(Block block) {
boolean blockIndentation = false;
boolean isPhpMode = true;
boolean isEmptyBlockNewLine = true;
boolean isUnbracketedNamespace = false;
boolean isNamespace = false;
boolean isClassDeclaration = false;
boolean isFunctionDeclaration = false;
switch (block.getParent().getType()) {
case ASTNode.NAMESPACE:
isNamespace = true;
if (!block.isCurly()) {
isEmptyBlockNewLine = false;
isUnbracketedNamespace = true;
if (block.statements().size() > 0) {
Statement statement = block.statements().get(0);
// need check how many new lines will the next statement
// insert
int numberOfLines = getNumbreOfLines(statement) - 1;
numberOfLines = this.preferences.blank_lines_after_namespace - numberOfLines;
if (numberOfLines > 0) {
for (int j = 0; j < numberOfLines; j++) {
insertNewLine();
}
}
} else {
for (int i = 0; i < this.preferences.blank_lines_after_namespace; i++) {
insertNewLine();
}
}
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
break;
}
if (block.statements().size() > 0) {
Statement statement = block.statements().get(0);
// need check how many new lines will the next statement insert
int numberOfLines = getNumbreOfLines(statement) - 1;
numberOfLines = this.preferences.blank_lines_after_namespace - numberOfLines;
if (numberOfLines > 0) {
for (int j = 0; j < numberOfLines; j++) {
insertNewLine();
}
}
} else {
for (int i = 0; i < this.preferences.blank_lines_after_namespace; i++) {
insertNewLine();
}
}
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
case ASTNode.CLASS_DECLARATION:
case ASTNode.INTERFACE_DECLARATION:
isEmptyBlockNewLine = preferences.new_line_in_empty_class_body;
blockIndentation = this.preferences.indent_statements_within_type_declaration;
isClassDeclaration = true;
break;
case ASTNode.SWITCH_STATEMENT:
blockIndentation = this.preferences.indent_statements_within_switch;
break;
case ASTNode.FUNCTION_DECLARATION:
isEmptyBlockNewLine = preferences.new_line_in_empty_method_body;
blockIndentation = this.preferences.indent_statements_within_function;
for (int i = 0; i < this.preferences.blank_line_at_begin_of_method; i++) {
insertNewLine();
}
isFunctionDeclaration = true;
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
break;
default:
isEmptyBlockNewLine = preferences.new_line_in_empty_block;
blockIndentation = this.preferences.indent_statements_within_block;
break;
}
if (blockIndentation) {
indentationLevel++;
}
int lastStatementEndOffset;
if (isUnbracketedNamespace) {
lastStatementEndOffset = block.getStart();
} else {
// start after curly position
lastStatementEndOffset = block.getStart() + 1;
}
List<Statement> statementsList = block.statements();
Statement[] statements = new Statement[statementsList.size()];
statements = block.statements().toArray(statements);
for (int i = 0; i < statements.length; i++) {
boolean isHtmlStatement = statements[i].getType() == ASTNode.IN_LINE_HTML;
boolean isASTError = statements[i].getType() == ASTNode.AST_ERROR;
// fixed bug 441419
// in case of previous statement is an error there is no need for
// new lines
// because the lastStatementEndOffset position move to the current
// statement start position
boolean isStatementAfterError = i > 0 ? statements[i - 1].getType() == ASTNode.AST_ERROR : false;
if (isASTError && i + 1 < statements.length) {
// move the lastStatementEndOffset position to the start of the
// next statement start position
lastStatementEndOffset = statements[i + 1].getStart();
} else {
if (isPhpMode && !isHtmlStatement) {
// PHP -> PHP
if (!isStatementAfterError && getPhpStartTag(lastStatementEndOffset) != -1) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=489361
// if previous statement was in a <?= ?> section and
// now we have a statement in a <?php ?> section,
// we're still in the PHP -> PHP case, but
// isPhpEqualTag is outdated
isPhpEqualTag = getPhpStartTag(lastStatementEndOffset) == PHP_OPEN_SHORT_TAG_WITH_EQUAL;
insertNewLine();
}
if (isThrowOrReturnFormatCase(statements)) {
// do nothing... This is a Throw/Return case
} else {
if (!isStatementAfterError) {
insertNewLines(statements[i]);
indent();
}
}
if (lastStatementEndOffset <= statements[i].getStart()) {
handleChars(lastStatementEndOffset, statements[i].getStart());
}
} else if (isPhpMode && isHtmlStatement) {
// PHP -> HTML
isPhpMode = false;
} else if (!isPhpMode && !isHtmlStatement) {
// HTML -> PHP
if (!isStatementAfterError) {
isPhpEqualTag = getPhpStartTag(lastStatementEndOffset) == PHP_OPEN_SHORT_TAG_WITH_EQUAL;
insertNewLines(statements[i]);
indent();
}
if (lastStatementEndOffset <= statements[i].getStart()) {
handleChars(lastStatementEndOffset, statements[i].getStart());
}
isPhpMode = true;
} else {
// HTML -> HTML
assert false;
}
statements[i].accept(this);
lastStatementEndOffset = statements[i].getEnd();
if (isNamespace && i + 1 < statements.length && statements[i].getType() == ASTNode.USE_STATEMENT) {
if (statements[i + 1].getType() == ASTNode.USE_STATEMENT) {
// for (int j = 0; j <
// this.preferences.blank_lines_between_use_statements;
// j++) {
// insertNewLine();
// }
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
} else {
// need check how many new lines will the next statement
// insert
int numberOfLines = getNumbreOfLines(statements[i + 1]) - 1;
numberOfLines = this.preferences.blank_lines_after_use_statements - numberOfLines;
if (numberOfLines > 0) {
for (int j = 0; j < numberOfLines; j++) {
insertNewLine();
}
}
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
}
}
}
}
// in case of the last statement is html statement
if (!isPhpMode) {
isPhpEqualTag = false;
}
// set the block end
if (blockIndentation) {
indentationLevel--;
indentationLevelDescending = true;
}
int endPosition = block.getEnd();
boolean hasComments = false;
if (startRegionPosition < endPosition && endRegionPosition >= endPosition) {
try {
hasComments = hasComments(lastStatementEndOffset, endPosition);
} catch (Exception e) {
Logger.logException(e);
}
}
if (statements.length > 0 || isEmptyBlockNewLine || hasComments) {
if (isUnbracketedNamespace || isThrowOrReturnFormatCase(statements)) {
// do not add new line... Throw/Return Statements within an If
// Statement block
} else {
// if ((statements.length > 0 || hasComments)
// && (isClassDeclaration || isFunctionDeclaration)) {
// if (isClassDeclaration) {
// for (int j = 0; j < preferences.blank_line_at_end_of_class;
// j++) {
// insertNewLine();
// }
// if (preferences.blank_line_at_end_of_class > 0) {
// indent();
// }
// } else {
// for (int j = 0; j < preferences.blank_line_at_end_of_method;
// j++) {
// insertNewLine();
// }
// if (preferences.blank_line_at_end_of_method > 0) {
// indent();
// }
// }
// } else {
insertNewLine();
indent();
// }
}
}
if (endPosition > lastStatementEndOffset) {
// exclude closing curly
int end = endPosition - 1;
if (!block.isCurly()) {
switch (block.getParent().getType()) {
case ASTNode.SWITCH_STATEMENT:
end = endPosition - "endswitch".length();//$NON-NLS-1$
break;
case ASTNode.WHILE_STATEMENT:
end = endPosition - "endwhile".length();//$NON-NLS-1$
break;
case ASTNode.FOR_STATEMENT:
end = endPosition - "endfor".length();//$NON-NLS-1$
break;
case ASTNode.FOR_EACH_STATEMENT:
end = endPosition - "endforeach".length();//$NON-NLS-1$
break;
case ASTNode.DECLARE_STATEMENT:
end = endPosition - "enddeclare".length();//$NON-NLS-1$
break;
case ASTNode.IF_STATEMENT:
end = endPosition;
break;
}
}
if (/*
* (statements.length > 0 || hasComments) &&
*/(isClassDeclaration || isFunctionDeclaration)) {
if (isClassDeclaration) {
for (int j = 0; j < preferences.blank_line_at_end_of_class; j++) {
insertNewLine();
}
if (preferences.blank_line_at_end_of_class > 0) {
indent();
}
} else {
for (int j = 0; j < preferences.blank_line_at_end_of_method; j++) {
insertNewLine();
}
if (preferences.blank_line_at_end_of_method > 0) {
indent();
}
}
}
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
blockEnd = true;
handleChars(lastStatementEndOffset, end);
blockEnd = false;
if (block.isCurly()) {
lineWidth++;// closing curly
}
}
return false;
}
// this checks whether it is an IF block (with curly) with one line and the
// line
// is either return OR throw expression AND the FORMAT flag is ON
private boolean isThrowOrReturnFormatCase(Statement[] statements) {
return preferences.control_statement_keep_guardian_on_one_line && (statements.length == 1)
&& (statements[0].getParent().getParent() instanceof IfStatement)
&& (((IfStatement) statements[0].getParent().getParent()).getFalseStatement() == null)
&& (statements[0].getType() == ASTNode.RETURN_STATEMENT
|| statements[0].getType() == ASTNode.YIELD_STATEMENT
|| statements[0].getType() == ASTNode.THROW_STATEMENT);
}
@Override
public boolean visit(BreakStatement breakStatement) {
int lastPosition = breakStatement.getStart() + 5;
lineWidth += 5;
Expression expression = breakStatement.getExpression();
if (expression != null) {
insertSpace();
handleChars(lastPosition, expression.getStart());
expression.accept(this);
lastPosition = expression.getEnd();
}
handleSemicolon(lastPosition, breakStatement.getEnd());
return false;
}
@Override
public boolean visit(CastExpression castExpression) {
// (type) expression
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_before_cast_type) {
insertSpace();
}
// get the exact cast type
String type = EMPTY_STRING;
int start = castExpression.getStart();
int end = castExpression.getExpression().getStart();
switch (castExpression.getCastingType()) {
case CastExpression.TYPE_INT:
if (isContainChar(start, end, 'e')) {
type = "integer"; //$NON-NLS-1$
} else {
type = "int"; //$NON-NLS-1$
}
break;
case CastExpression.TYPE_REAL:
if (isContainChar(start, end, 'f')) {
type = "float"; //$NON-NLS-1$
} else if (isContainChar(start, end, 'r')) {
type = "real"; //$NON-NLS-1$
} else {
type = "double"; //$NON-NLS-1$
}
break;
case CastExpression.TYPE_STRING:
type = "string"; //$NON-NLS-1$
break;
case CastExpression.TYPE_ARRAY:
type = "array"; //$NON-NLS-1$
break;
case CastExpression.TYPE_OBJECT:
type = "object"; //$NON-NLS-1$
break;
case CastExpression.TYPE_BOOL:
if (isContainChar(start, end, 'e')) {
type = "boolean"; //$NON-NLS-1$
} else {
type = "bool"; //$NON-NLS-1$
}
break;
case CastExpression.TYPE_UNSET:
type = "unset"; //$NON-NLS-1$
break;
}
appendToBuffer(type);
if (this.preferences.insert_space_after_cast_type) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
if (this.preferences.insert_space_after_cast_expression) {
insertSpace();
}
// till the expression
handleChars(start, end);
castExpression.getExpression().accept(this);
return false;
}
@Override
public boolean visit(CatchClause catchClause) {
// handle the chars between the 'catch' and the identifier start
// position
if (this.preferences.insert_space_before_opening_paren_in_catch) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_opening_paren_in_catch) {
insertSpace();
}
lineWidth += 5;
handleChars(catchClause.getStart() + 5, catchClause.getClassNames().get(0).getStart());
// handle the catch identifiers
catchClause.getClassNames().get(0).accept(this);
for (int i = 1; i < catchClause.getClassNames().size(); i++) {
handleChars(catchClause.getClassNames().get(i - 1).getEnd(), catchClause.getClassNames().get(i).getStart());
insertSpace();
appendToBuffer('|');
insertSpace();
catchClause.getClassNames().get(i).accept(this);
}
insertSpace();
handleChars(catchClause.getClassNames().get(catchClause.getClassNames().size() - 1).getEnd(),
catchClause.getVariable().getStart());
catchClause.getVariable().accept(this);
// set the catch closing parn spaces
if (this.preferences.insert_space_before_closing_paren_in_catch) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_block,
this.preferences.insert_space_before_opening_brace_in_block);
handleChars(catchClause.getVariable().getEnd(), catchClause.getBody().getStart());
catchClause.getBody().accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
return false;
}
@Override
public boolean visit(ConstantDeclaration classConstantDeclaration) {
boolean isFirst = true;
// handle modifier
String modifier = classConstantDeclaration.getModifierString();
appendToBuffer(modifier);
if (modifier != null && !modifier.isEmpty()) {
insertSpace();
}
appendToBuffer("const"); //$NON-NLS-1$
insertSpace();
int lastPosition = classConstantDeclaration.getStart();
List<Identifier> names = classConstantDeclaration.names();
Identifier[] variableNames = new Identifier[names.size()];
variableNames = names.toArray(variableNames);
for (int i = 0; i < variableNames.length; i++) {
// handle comma between variables
if (!isFirst) {
if (this.preferences.insert_space_before_comma_in_class_constant) {
insertSpace();
}
appendToBuffer(COMMA);
if (this.preferences.insert_space_after_comma_in_class_constant) {
insertSpace();
}
}
handleChars(lastPosition, variableNames[i].getStart());
variableNames[i].accept(this);
lastPosition = variableNames[i].getEnd();
// handle initial assignments
if (this.preferences.insert_space_before_assignment) {
insertSpace();
}
appendToBuffer(EQUAL);
if (this.preferences.insert_space_after_assignment) {
insertSpace();
}
List<Expression> initializers = classConstantDeclaration.initializers();
Expression[] constantValues = new Expression[initializers.size()];
constantValues = initializers.toArray(constantValues);
handleChars(lastPosition, constantValues[i].getStart());
constantValues[i].accept(this);
lastPosition = constantValues[i].getEnd();
isFirst = false;
}
handleSemicolon(lastPosition, classConstantDeclaration.getEnd());
return false;
}
@Override
public boolean visit(ClassDeclaration classDeclaration) {
// handle spaces between modifier, 'class' and class name
String modifier = ClassDeclaration.getModifier(classDeclaration.getModifier());
if (!modifier.equals(EMPTY_STRING)) {
appendToBuffer(modifier);
insertSpace();
}
if (classDeclaration instanceof TraitDeclaration) {
appendToBuffer("trait"); //$NON-NLS-1$
} else {
appendToBuffer("class"); //$NON-NLS-1$
}
insertSpace();
handleChars(classDeclaration.getStart(), classDeclaration.getName().getStart());
classDeclaration.getName().accept(this);
int lastPosition = classDeclaration.getName().getEnd();
Expression superClass = classDeclaration.getSuperClass();
// handle super class
if (superClass != null) {
appendToBuffer(" extends "); //$NON-NLS-1$
handleChars(lastPosition, superClass.getStart());
classDeclaration.getSuperClass().accept(this);
lastPosition = classDeclaration.getSuperClass().getEnd();
}
List<Identifier> interfacesList = classDeclaration.interfaces();
Identifier[] interfaces = new Identifier[interfacesList.size()];
interfaces = interfacesList.toArray(interfaces);
// handle class implements
if (interfaces != null && interfaces.length > 0) {
appendToBuffer(" implements "); //$NON-NLS-1$
int indentationGap = calculateIndentGap(
this.preferences.line_wrap_superinterfaces_in_type_declaration_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
lastPosition = handleCommaList(interfaces, lastPosition,
this.preferences.insert_space_before_comma_in_implements,
this.preferences.insert_space_after_comma_in_implements,
this.preferences.line_wrap_superinterfaces_in_type_declaration_line_wrap_policy, indentationGap,
this.preferences.line_wrap_superinterfaces_in_type_declaration_force_split);
}
// handle class body
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_class,
this.preferences.insert_space_before_opening_brace_in_class);
handleChars(lastPosition, classDeclaration.getBody().getStart());
classDeclaration.getBody().accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
return false;
}
@Override
public boolean visit(ClassInstanceCreation classInstanceCreation) {
// insertSpace();
appendToBuffer("new "); //$NON-NLS-1$
handleChars(classInstanceCreation.getStart(), classInstanceCreation.getClassName().getStart());
classInstanceCreation.getClassName().accept(this);
if (this.preferences.insert_space_before_opening_paren_in_function) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
List<Expression> ctorParams = classInstanceCreation.ctorParams();
int numberOfCtorParameters = ctorParams.size();
if (numberOfCtorParameters == 0 && this.preferences.insert_space_between_empty_paren_in_function) {
insertSpace();
}
if (numberOfCtorParameters > 0 && this.preferences.insert_space_after_opening_paren_in_function) {
insertSpace();
}
Expression[] arrayOfParameters = (Expression[]) ctorParams.toArray(new Expression[ctorParams.size()]);
int indentationGap = calculateIndentGap(
this.preferences.line_wrap_arguments_in_allocation_expression_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
int lastPosition = handleCommaList(arrayOfParameters, classInstanceCreation.getClassName().getEnd(),
this.preferences.insert_space_before_comma_in_function,
this.preferences.insert_space_after_comma_in_function,
this.preferences.line_wrap_arguments_in_allocation_expression_line_wrap_policy, indentationGap,
this.preferences.line_wrap_arguments_in_allocation_expression_force_split);
if (numberOfCtorParameters > 0 && this.preferences.insert_space_before_closing_paren_in_function) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
if (classInstanceCreation.getAnonymousClassDeclaration() != null) {
AnonymousClassDeclaration acd = classInstanceCreation.getAnonymousClassDeclaration();
if (acd.getSuperClass() != null) {
appendToBuffer(" extends "); //$NON-NLS-1$
handleChars(lastPosition, acd.getSuperClass().getStart());
acd.getSuperClass().accept(this);
lastPosition = acd.getSuperClass().getEnd();
}
if (acd.getInterfaces() != null && acd.getInterfaces().size() > 0) {
appendToBuffer(" implements "); //$NON-NLS-1$
indentationGap = calculateIndentGap(
this.preferences.line_wrap_superinterfaces_in_type_declaration_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
ASTNode[] nodes = acd.getInterfaces().toArray(new ASTNode[0]);
lastPosition = handleCommaList(nodes, lastPosition,
this.preferences.insert_space_before_comma_in_implements,
this.preferences.insert_space_after_comma_in_implements,
this.preferences.line_wrap_superinterfaces_in_type_declaration_line_wrap_policy, indentationGap,
this.preferences.line_wrap_superinterfaces_in_type_declaration_force_split);
}
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_lambda_function,
this.preferences.insert_space_before_opening_brace_in_function);
handleChars(lastPosition, acd.getBody().getStart());
acd.getBody().accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
lastPosition = acd.getBody().getEnd();
}
handleChars(lastPosition, classInstanceCreation.getEnd());
return false;
}
@Override
public boolean visit(ClassName className) {
className.getName().accept(this);
return false;
}
@Override
public boolean visit(CloneExpression cloneExpression) {
insertSpace();
lineWidth += 5;// the 'clone'
// till the expression
Expression expression = cloneExpression.getExpression();
handleChars(cloneExpression.getStart() + 5, expression.getStart());
expression.accept(this);
return false;
}
@Override
public boolean visit(Comment comment) {
// do nothing
return false;
}
@Override
public boolean visit(ConditionalExpression conditionalExpression) {
boolean isTernaryOperator = conditionalExpression.getOperatorType() == ConditionalExpression.OP_TERNARY;
// start
// condition ? true : false
conditionalExpression.getCondition().accept(this);
// condition -> if true
if (this.preferences.insert_space_before_conditional_question_mark) {
insertSpace();
}
appendToBuffer(QUESTION_MARK);
if (!isTernaryOperator) {
appendToBuffer(QUESTION_MARK);
}
Expression ifTrue = conditionalExpression.getIfTrue();
Expression ifFalse = conditionalExpression.getIfFalse();
int offset = conditionalExpression.getCondition().getStart();
int colonOffset = 0;
if (ifTrue != null) {
if (this.preferences.insert_space_after_conditional_question_mark) {
insertSpace();
}
handleChars(conditionalExpression.getCondition().getEnd(), ifTrue.getStart());
ifTrue.accept(this);
// iftrue -> iffalse
if (this.preferences.insert_space_before_conditional_colon) {
insertSpace();
}
} else {
int length = offset;
if (ifFalse != null) {
length = ifFalse.getStart();
}
colonOffset = getCharPosition(conditionalExpression.getCondition().getEnd(), length, COLON);
handleChars(conditionalExpression.getCondition().getEnd(), colonOffset);
}
if (isTernaryOperator) {
appendToBuffer(COLON);
if (this.preferences.insert_space_after_conditional_colon) {
insertSpace();
}
if (ifTrue != null && ifFalse != null) {
handleChars(ifTrue.getEnd(), conditionalExpression.getIfFalse().getStart());
} else if (ifTrue == null && ifFalse != null) {
handleChars(colonOffset, conditionalExpression.getIfFalse().getStart());
} else if (ifTrue != null && ifFalse == null) {
handleChars(ifTrue.getEnd(), colonOffset);
}
} else if (ifTrue != null) {
handleChars(ifTrue.getEnd(), colonOffset);
}
if (ifFalse != null && isTernaryOperator) {
ifFalse.accept(this);
}
// end
return false;
}
@Override
public boolean visit(ContinueStatement continueStatement) {
int lastPosition = continueStatement.getStart() + 8;
lineWidth += 8;
Expression expression = continueStatement.getExpression();
if (expression != null) {
insertSpace();
handleChars(lastPosition, expression.getStart());
expression.accept(this);
lastPosition = expression.getEnd();
}
handleSemicolon(lastPosition, continueStatement.getEnd());
return false;
}
@Override
public boolean visit(DeclareStatement declareStatement) {
boolean isFirst = true;
if (this.preferences.insert_space_before_opening_paren_in_declare) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_opening_paren_in_declare) {
insertSpace();
}
int lastPosition = declareStatement.getStart() + 7;
lineWidth += 7;
List<Identifier> direciveNameList = declareStatement.directiveNames();
Identifier[] directiveNames = new Identifier[direciveNameList.size()];
directiveNames = direciveNameList.toArray(directiveNames);
for (int i = 0; i < directiveNames.length; i++) {
// handle comma between variables
if (!isFirst) {
if (this.preferences.insert_space_before_comma_in_class_variable) {
insertSpace();
}
appendToBuffer(COMMA);
if (this.preferences.insert_space_after_comma_in_class_variable) {
insertSpace();
}
}
handleChars(lastPosition, directiveNames[i].getStart());
directiveNames[i].accept(this);
lastPosition = directiveNames[i].getEnd();
// handle initial assignments
if (this.preferences.insert_space_before_assignment) {
insertSpace();
}
appendToBuffer(EQUAL);
if (this.preferences.insert_space_after_assignment) {
insertSpace();
}
List<Expression> directiveValuesList = declareStatement.directiveValues();
Expression[] directiveValues = new Expression[directiveValuesList.size()];
directiveValues = directiveValuesList.toArray(directiveValues);
handleChars(lastPosition, directiveValues[i].getStart());
directiveValues[i].accept(this);
lastPosition = directiveValues[i].getEnd();
isFirst = false;
}
if (this.preferences.insert_space_before_closing_paren_in_declare) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
handleAction(lastPosition, declareStatement.getBody(), true);
return false;
}
@Override
public boolean visit(DoStatement doStatement) {
// do-while body
lineWidth += 2;
Statement body = doStatement.getBody();
handleAction(doStatement.getStart() + 2, body, true);
if (preferences.control_statement_insert_newline_before_while_in_do) {
insertNewLine();
indent();
} else {
setSpaceAfterBlock(body);
}
int positionOfWhile = -1;
int lastPosition = body.getEnd();
try {
positionOfWhile = getFirstTokenOffset(lastPosition, doStatement.getCondition().getStart(), stWhile, true);
} catch (Exception e) {
Logger.logException(e);
return false;
}
if (positionOfWhile > lastPosition) {
handleChars(lastPosition, positionOfWhile + 5); // 5 =
// "while".length()
appendToBuffer("while"); //$NON-NLS-1$
handleChars(positionOfWhile + 5, positionOfWhile + 5);
lastPosition = positionOfWhile + 5;
} else {
appendToBuffer("while"); //$NON-NLS-1$
}
// handle the chars between the 'while' and the condition start position
if (this.preferences.insert_space_before_opening_paren_in_while) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_opening_paren_in_while) {
insertSpace();
}
handleChars(lastPosition, doStatement.getCondition().getStart());
// handle the while condition
doStatement.getCondition().accept(this);
// set the while closing paren spaces
if (this.preferences.insert_space_before_closing_paren_in_while) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
handleSemicolon(doStatement.getCondition().getEnd(), doStatement.getEnd());
return false;
}
@Override
public boolean visit(EchoStatement echoStatement) {
int lastPosition = echoStatement.getStart();
List<Expression> expressionList = echoStatement.expressions();
Expression[] expressions = new Expression[expressionList.size()];
expressions = expressionList.toArray(expressions);
// check if short echo syntax (<?=)
if (expressions.length > 0 && echoStatement.getStart() != expressions[0].getStart()) {
lastPosition += 4;
lineWidth += 4;
insertSpace();
}
lastPosition = handleCommaList(expressions, lastPosition, this.preferences.insert_space_before_comma_in_echo,
this.preferences.insert_space_after_comma_in_echo, NO_LINE_WRAP, NO_LINE_WRAP_INDENT, false);
handleSemicolon(lastPosition, echoStatement.getEnd());
return false;
}
@Override
public boolean visit(EmptyStatement emptyStatement) {
int start = emptyStatement.getStart();
int end = emptyStatement.getEnd();
if (isContainChar(start, end, '?')) {
handlePhpEndTag(start, end, "?>"); //$NON-NLS-1$
} else if (isContainChar(start, end, '%')) {
handlePhpEndTag(start, end, "%>"); //$NON-NLS-1$
} else {
appendToBuffer(SEMICOLON);
}
handleChars(start, end);
return false;
}
@Override
public boolean visit(ExpressionStatement expressionStatement) {
Expression expression = expressionStatement.getExpression();
expression.accept(this);
handleSemicolon(expression.getEnd(), expressionStatement.getEnd());
return false;
}
@Override
public boolean visit(FieldAccess fieldAccess) {
fieldAccess.getDispatcher().accept(this);
if (this.preferences.insert_space_before_arrow_in_field_access) {
insertSpace();
}
appendToBuffer("->"); //$NON-NLS-1$
if (this.preferences.insert_space_after_arrow_in_field_access) {
insertSpace();
}
// handle the chars between the dispatcher to the property
handleChars(fieldAccess.getDispatcher().getEnd(), fieldAccess.getField().getStart());
fieldAccess.getField().accept(this);
return false;
}
@Override
public boolean visit(FieldsDeclaration fieldsDeclaration) {
boolean isFirst = true;
Variable[] variableNames = fieldsDeclaration.getVariableNames();
Expression[] initialValues = fieldsDeclaration.getInitialValues();
int lastPosition = variableNames[0].getStart();
// handle field modifiers
String modifier = fieldsDeclaration.getModifierString();
char firstChar = SPACE;
try {
firstChar = document.getChar(fieldsDeclaration.getStart());
} catch (BadLocationException e) {
Logger.logException(e);
}
if (modifier.equalsIgnoreCase("public") //$NON-NLS-1$
&& (firstChar == 'v' || firstChar == 'V')) {
modifier = "var"; //$NON-NLS-1$
}
appendToBuffer(modifier);
insertSpace();
handleChars(fieldsDeclaration.getStart(), lastPosition);
for (int i = 0; i < variableNames.length; i++) {
// handle comma between variables
if (!isFirst) {
if (this.preferences.insert_space_before_comma_in_class_variable) {
insertSpace();
}
appendToBuffer(COMMA);
if (this.preferences.insert_space_after_comma_in_class_variable) {
insertSpace();
}
}
handleChars(lastPosition, variableNames[i].getStart());
variableNames[i].accept(this);
lastPosition = variableNames[i].getEnd();
if (initialValues[i] != null) {
// handle initial assignments
if (this.preferences.insert_space_before_assignment) {
insertSpace();
}
appendToBuffer(EQUAL);
if (this.preferences.insert_space_after_assignment) {
insertSpace();
}
handleChars(lastPosition, initialValues[i].getStart());
initialValues[i].accept(this);
lastPosition = initialValues[i].getEnd();
}
isFirst = false;
}
handleSemicolon(lastPosition, fieldsDeclaration.getEnd());
return false;
}
@Override
public boolean visit(ForEachStatement forEachStatement) {
if (this.preferences.insert_space_before_open_paren_in_foreach) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_open_paren_in_foreach) {
insertSpace();
}
lineWidth += 7;
handleChars(forEachStatement.getStart() + 7, forEachStatement.getExpression().getStart());
// handle [as key => value] or just [as value]
forEachStatement.getExpression().accept(this);
appendToBuffer(" as "); //$NON-NLS-1$
int lastPosition = forEachStatement.getExpression().getEnd();
if (forEachStatement.getKey() != null) {
handleChars(forEachStatement.getExpression().getEnd(), forEachStatement.getKey().getStart());
forEachStatement.getKey().accept(this);
if (this.preferences.insert_space_before_arrow_in_foreach) {
insertSpace();
}
appendToBuffer(KEY_VALUE_OPERATOR);
if (this.preferences.insert_space_after_arrow_in_foreach) {
insertSpace();
}
lastPosition = forEachStatement.getKey().getEnd();
}
handleChars(lastPosition, forEachStatement.getValue().getStart());
forEachStatement.getValue().accept(this);
if (this.preferences.insert_space_before_close_paren_in_foreach) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
handleAction(forEachStatement.getValue().getEnd(), forEachStatement.getStatement(), true);
return false;
}
@Override
public boolean visit(FormalParameter formalParameter) {
// handle const in PHP4
int lastPosition = formalParameter.getStart();
if (formalParameter.isMandatory()) {
// the word 'const'
lastPosition += 5;
lineWidth += 5;
}
// handle type
Expression parameterType = formalParameter.getParameterType();
if (parameterType != null) {
if (parameterType instanceof Identifier && ((Identifier) parameterType).isNullable()) {
appendToBuffer(QUESTION_MARK);
}
handleChars(formalParameter.getStart(), parameterType.getStart());
parameterType.accept(this);
lastPosition = parameterType.getEnd();
insertSpace();
}
if (formalParameter.isVariadic() && formalParameter.getParameterName() instanceof Variable) {
appendToBuffer(ELLIPSIS);
}
handleChars(lastPosition, formalParameter.getParameterName().getStart());
formalParameter.getParameterName().accept(this);
if (formalParameter.hasDefaultValue()) {
if (this.preferences.insert_space_before_assignment) {
insertSpace();
}
appendToBuffer(EQUAL);
if (this.preferences.insert_space_after_assignment) {
insertSpace();
}
// handle the chars between the variable to the value
handleChars(formalParameter.getParameterName().getEnd(), formalParameter.getDefaultValue().getStart());
}
return false;
}
@Override
public boolean visit(ForStatement forStatement) {
int lastPosition = forStatement.getStart() + 3;
lineWidth += 3;
if (this.preferences.insert_space_before_open_paren_in_for) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
List<Expression> forExpressions = null;
// handle initializers
forExpressions = forStatement.initializers();
Expression[] initializations = new Expression[forExpressions.size()];
forExpressions.toArray(initializations);
// handle conditions
forExpressions = forStatement.conditions();
Expression[] conditions = new Expression[forExpressions.size()];
forExpressions.toArray(conditions);
// handle updaters/increasements
forExpressions = forStatement.updaters();
Expression[] increasements = new Expression[forExpressions.size()];
forExpressions.toArray(increasements);
if (this.preferences.insert_space_after_open_paren_in_for && initializations.length > 0) {
insertSpace();
}
lastPosition = handleCommaList(initializations, lastPosition, this.preferences.insert_space_before_comma_in_for,
this.preferences.insert_space_after_comma_in_for, NO_LINE_WRAP, NO_LINE_WRAP_INDENT, false);
handleForSemicolon(initializations, conditions);
lastPosition = handleCommaList(conditions, lastPosition, this.preferences.insert_space_before_comma_in_for,
this.preferences.insert_space_after_comma_in_for, NO_LINE_WRAP, NO_LINE_WRAP_INDENT, false);
handleForSemicolon(conditions, increasements);
lastPosition = handleCommaList(increasements, lastPosition, this.preferences.insert_space_before_comma_in_for,
this.preferences.insert_space_after_comma_in_for, NO_LINE_WRAP, NO_LINE_WRAP_INDENT, false);
if (this.preferences.insert_space_before_close_paren_in_for && increasements.length > 0) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
// for body
handleAction(lastPosition, forStatement.getBody(), true);
return false;
}
@Override
public boolean visit(FunctionDeclaration functionDeclaration) {
StringBuilder buffer = new StringBuilder();
buffer.append(getDocumentString(functionDeclaration.getStart(), functionDeclaration.getStart() + 8));// append
// 'function'
// handle referenced function with '&'
if (functionDeclaration.isReference()) {
buffer.append(" &"); //$NON-NLS-1$
} else {
buffer.append(SPACE);
}
buffer.append(functionDeclaration.getFunctionName().getName());
appendToBuffer(buffer.toString());
handleChars(functionDeclaration.getStart(), functionDeclaration.getFunctionName().getEnd());
if (this.preferences.insert_space_before_opening_paren_in_function_declaration) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
int lastPosition = functionDeclaration.getFunctionName().getEnd();
if (functionDeclaration.formalParameters().size() > 0) {
if (this.preferences.insert_space_after_opening_paren_in_function_declaration) {
insertSpace();
}
int indentationGap = calculateIndentGap(
this.preferences.line_wrap_parameters_in_method_declaration_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
List<FormalParameter> parameterList = functionDeclaration.formalParameters();
FormalParameter[] parameters = new FormalParameter[parameterList.size()];
parameters = parameterList.toArray(parameters);
lastPosition = handleCommaList(parameters, lastPosition,
this.preferences.insert_space_before_comma_in_function_declaration,
this.preferences.insert_space_after_comma_in_function_declaration,
this.preferences.line_wrap_parameters_in_method_declaration_line_wrap_policy, indentationGap,
this.preferences.line_wrap_parameters_in_method_declaration_force_split);
if (this.preferences.insert_space_before_closing_paren_in_function_declaration) {
insertSpace();
}
} else {
if (this.preferences.insert_space_between_empty_paren_in_function_declaration) {
insertSpace();
}
}
appendToBuffer(CLOSE_PARN);
if (functionDeclaration.getReturnType() != null) {
appendToBuffer(COLON);
insertSpace();
if (functionDeclaration.getReturnType().isNullable()) {
appendToBuffer(QUESTION_MARK);
}
handleChars(lastPosition, functionDeclaration.getReturnType().getStart());
functionDeclaration.getReturnType().accept(this);
lastPosition = functionDeclaration.getReturnType().getEnd();
}
// handle function body
if (functionDeclaration.getBody() != null) {
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_function,
this.preferences.insert_space_before_opening_brace_in_function);
handleChars(lastPosition, functionDeclaration.getBody().getStart());
functionDeclaration.getBody().accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
} else {
handleSemicolon(lastPosition, functionDeclaration.getEnd());
}
return false;
}
@Override
public boolean visit(FunctionInvocation functionInvocation) {
// in case of function print there no need for parenthesis
Expression functionName = functionInvocation.getFunctionName().getName();
if (functionName.getType() == ASTNode.IDENTIFIER) {
final String name = ((Identifier) functionName).getName();
if (FUNCTION_NAME_PRINT.equalsIgnoreCase(name)) {
handlePrintCall(functionInvocation);
return false;
}
}
innerVisit(functionInvocation);
return false;
}
private void innerVisit(FunctionInvocation functionInvocation) {
innerVisit(functionInvocation, true);
}
private void innerVisit(FunctionInvocation functionInvocation, boolean addParen) {
Expression functionName = functionInvocation.getFunctionName().getName();
functionName.accept(this);
if (this.preferences.insert_space_before_opening_paren_in_function) {
insertSpace();
}
if (addParen) {
appendToBuffer(OPEN_PARN);
}
int lastPosition = functionName.getEnd();
if (functionInvocation.parameters().size() > 0) {
if (this.preferences.insert_space_after_opening_paren_in_function) {
insertSpace();
}
int indentationGap = calculateIndentGap(
this.preferences.line_wrap_arguments_in_method_invocation_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
List<Expression> parametersList = functionInvocation.parameters();
Expression[] parameters = new Expression[parametersList.size()];
parameters = parametersList.toArray(parameters);
// work around. count close bracket now.
if (addParen) {
lineWidth++;
}
// work around. count space now.
if (this.preferences.insert_space_before_closing_paren_in_function) {
lineWidth++;
}
lastPosition = handleCommaList(parameters, lastPosition,
this.preferences.insert_space_before_comma_in_function,
this.preferences.insert_space_after_comma_in_function,
this.preferences.line_wrap_arguments_in_method_invocation_line_wrap_policy, indentationGap,
this.preferences.line_wrap_arguments_in_method_invocation_force_split);
if (this.preferences.insert_space_before_closing_paren_in_function) {
// work around. count space now.
lineWidth--;
insertSpace();
}
// work around. count close bracket now.
if (addParen) {
lineWidth--;
}
} else {
if (this.preferences.insert_space_between_empty_paren_in_function) {
insertSpace();
}
}
if (addParen) {
appendToBuffer(CLOSE_PARN);
}
handleChars(lastPosition, functionInvocation.getEnd());
}
private void handlePrintCall(FunctionInvocation functionInvocation) {
List<Expression> parametersList = functionInvocation.parameters();
Expression[] parameters = new Expression[parametersList.size()];
parameters = parametersList.toArray(parameters);
boolean hasParenthesis = parameters[0].getType() == ASTNode.PARENTHESIS_EXPRESSION; // print
// always
// have
// one
// parameter.
if (hasParenthesis) {
innerVisit(functionInvocation, false);
return;
}
insertSpace();
Expression functionName = functionInvocation.getFunctionName().getName();
int lastPosition = functionName.getEnd();
int indentationGap = calculateIndentGap(this.preferences.line_wrap_arguments_in_method_invocation_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
lastPosition = handleCommaList(parameters, lastPosition, this.preferences.insert_space_before_comma_in_function,
this.preferences.insert_space_after_comma_in_function,
this.preferences.line_wrap_arguments_in_method_invocation_line_wrap_policy, indentationGap,
this.preferences.line_wrap_arguments_in_method_invocation_force_split);
handleChars(lastPosition, functionInvocation.getEnd());
}
@Override
public boolean visit(FunctionName functionName) {
return true;
}
@Override
public boolean visit(GlobalStatement globalStatement) {
int lastPosition = globalStatement.getStart() + 6;
lineWidth += 6;// the word 'global'
insertSpace();
List<Variable> varList = globalStatement.variables();
Expression[] variables = new Expression[varList.size()];
variables = varList.toArray(variables);
lastPosition = handleCommaList(variables, lastPosition, this.preferences.insert_space_before_comma_in_global,
this.preferences.insert_space_after_comma_in_global, NO_LINE_WRAP, NO_LINE_WRAP_INDENT, false);
handleSemicolon(lastPosition, globalStatement.getEnd());
return false;
}
@Override
public boolean visit(Identifier identifier) {
lineWidth += identifier.getLength();
return false;
}
@Override
public boolean visit(IfStatement ifStatement) {
int len;
try {
// looking for 'if' or 'elseif'
len = getFirstTokenOffset(ifStatement.getStart(), ifStatement.getCondition().getStart(), stElseIf,
true) != -1 ? 6 : 2;
} catch (Exception e) {
Logger.logException(e);
return false;
}
// handle the chars between the 'while' and the condition start position
if (this.preferences.insert_space_before_opening_paren_in_if) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_opening_paren_in_if) {
insertSpace();
}
lineWidth += len; // add the word 'if' OR 'elseif'
handleChars(ifStatement.getStart() + len, ifStatement.getCondition().getStart());
// handle the if condition
ifStatement.getCondition().accept(this);
if (wasBinaryExpressionWrapped) {
indentationLevel -= binaryExpressionIndentGap;
wasBinaryExpressionWrapped = false;
}
// handle the chars between the condition end position and action start
// position
// set the while closing paren spaces
if (this.preferences.insert_space_before_closing_paren_in_if) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
// action
boolean addNewlineBeforeAction = true;
if (ifStatement.getTrueStatement().getType() != ASTNode.BLOCK) {
if (len == 2) {// if
addNewlineBeforeAction = !(preferences.control_statement_keep_then_on_same_line
|| (preferences.control_statement_keep_simple_if_on_one_line
&& ifStatement.getFalseStatement() == null));
} else if (len == 6) {// elseif
addNewlineBeforeAction = !preferences.control_statement_keep_then_on_same_line;
}
}
handleAction(ifStatement.getCondition().getEnd(), ifStatement.getTrueStatement(), addNewlineBeforeAction);
if (ifStatement.getFalseStatement() == null || ifStatement.getFalseStatement().getType() == ASTNode.AST_ERROR) {
return false;
}
if (ifStatement.getFalseStatement().getType() == ASTNode.IF_STATEMENT) {
try {
handleElseIfCases(ifStatement);
} catch (BadLocationException ble) {
Logger.logException(ble);
return false;
}
} else { // the false statement is not 'elseif' or 'else if'
if (ifStatement.getTrueStatement().getType() == ASTNode.BLOCK) {
if (preferences.control_statement_insert_newline_before_else_and_elseif_in_if) {
insertNewLine();
indent();
} else {
setSpaceAfterBlock(ifStatement.getTrueStatement());
}
try {
int lastPosition = internalHandleElse(ifStatement);
handleAction(lastPosition, ifStatement.getFalseStatement(), true);
} catch (BadLocationException ble) {
Logger.logException(ble);
return false;
}
boolean processed = isProcessed(ifStatement);
if (!((Block) ifStatement.getTrueStatement()).isCurly() && !processed) {
handleChars(ifStatement.getFalseStatement().getEnd(), ifStatement.getEnd());
appendToBuffer("endif;"); //$NON-NLS-1$
handleChars(ifStatement.getEnd(), ifStatement.getEnd());
}
} else { // if the true statement is not a block then we should add
// new line
insertNewLine();
indent();
try {
int lastPosition = internalHandleElse(ifStatement);
boolean elseActionInSameLine = preferences.control_statement_keep_else_on_same_line;
handleAction(lastPosition, ifStatement.getFalseStatement(), !elseActionInSameLine);
} catch (BadLocationException ble) {
Logger.logException(ble);
return false;
}
}
}
return false;
}
private boolean isProcessed(IfStatement ifStatement) {
boolean precessed = true;
if (!processedIfStatements.contains(ifStatement)) {
addAllIfStatements(ifStatement);
precessed = false;
}
return precessed;
}
private void handleElseIfCases(IfStatement ifStatement) throws BadLocationException {
IfStatement falseIfStatement = (IfStatement) ifStatement.getFalseStatement();
boolean elseIndentationLevelChanged = false;
int trueStatementEnd = ifStatement.getTrueStatement().getEnd();
boolean isIfToken = false;
int positionOfElse = -1;
try {
// looking for 'if' or 'elseif'
isIfToken = getFirstTokenOffset(falseIfStatement.getStart(), falseIfStatement.getCondition().getStart(),
stElseIf, true) == -1;
// looking for 'else' or 'elseif'
positionOfElse = getFirstTokenOffset(trueStatementEnd, ifStatement.getFalseStatement().getStart(), stElse,
true);
if (positionOfElse == -1) {
positionOfElse = getFirstTokenOffset(trueStatementEnd, ifStatement.getFalseStatement().getStart(),
stElseIf, false /* no need to re-scan same range */);
}
} catch (Exception e) {
Logger.logException(e);
return;
}
if (ifStatement.getTrueStatement().getType() == ASTNode.BLOCK) {
if (preferences.control_statement_insert_newline_before_else_and_elseif_in_if) {
insertNewLine();
indent();
} else {
if (isIfToken) {
setSpaceAfterBlock(ifStatement.getTrueStatement());
}
}
if (!isIfToken) {// elseif case
if (positionOfElse > trueStatementEnd) {
handleChars(trueStatementEnd, ifStatement.getFalseStatement().getStart());
} else {
// fix for setSpaceAfterBlock when no space is required
// before 'elseif'
if (!preferences.control_statement_insert_newline_before_else_and_elseif_in_if
&& preferences.insert_space_after_closing_brace_in_block) {
insertSpace();
}
handleChars(trueStatementEnd, ifStatement.getFalseStatement().getStart());
}
} else {
if (positionOfElse > trueStatementEnd) {
handleChars(trueStatementEnd, positionOfElse);
appendToBuffer("else "); //$NON-NLS-1$
if (!preferences.control_statement_keep_else_if_on_same_line) {
insertNewLine();
indentationLevel++;
elseIndentationLevelChanged = true;
indent();
}
handleChars(positionOfElse, ifStatement.getFalseStatement().getStart());
} else {
appendToBuffer("else "); //$NON-NLS-1$
if (!preferences.control_statement_keep_else_if_on_same_line) {
insertNewLine();
indentationLevel++;
elseIndentationLevelChanged = true;
indent();
}
// the following line also handles the case : '}else' when
// setSpaceAfterBlock() is called and offset is set to +1
handleChars(trueStatementEnd, ifStatement.getFalseStatement().getStart());
}
}
} else { // if the true statement is not a block then we should add new
// line
insertNewLine();
indent();
if (positionOfElse > trueStatementEnd) {
handleChars(trueStatementEnd, positionOfElse);
appendToBuffer("else "); //$NON-NLS-1$
if (!preferences.control_statement_keep_else_if_on_same_line) {
insertNewLine();
indentationLevel++;
elseIndentationLevelChanged = true;
indent();
}
handleChars(positionOfElse, ifStatement.getFalseStatement().getStart());
} else {
appendToBuffer(isIfToken ? "else " : EMPTY_STRING); //$NON-NLS-1$
if (isIfToken && !preferences.control_statement_keep_else_if_on_same_line) {
insertNewLine();
indentationLevel++;
elseIndentationLevelChanged = true;
indent();
}
// in case of: STATEMENT;elseif ...
handleChars(trueStatementEnd, ifStatement.getFalseStatement().getStart());
}
}
boolean processed = isProcessed(ifStatement);
ifStatement.getFalseStatement().accept(this);
if (elseIndentationLevelChanged) {
indentationLevel--;
}
if (ifStatement.getTrueStatement().getType() == ASTNode.BLOCK
&& !((Block) ifStatement.getTrueStatement()).isCurly() && !processed) {
handleChars(ifStatement.getFalseStatement().getEnd(), ifStatement.getEnd());
appendToBuffer("endif;"); //$NON-NLS-1$
handleChars(ifStatement.getEnd(), ifStatement.getEnd());
}
}
private void addAllIfStatements(IfStatement ifStatement) {
processedIfStatements.add(ifStatement);
Statement falseIfStatement;
while ((falseIfStatement = ifStatement.getFalseStatement()) instanceof IfStatement) {
ifStatement = (IfStatement) falseIfStatement;
processedIfStatements.add(ifStatement);
}
}
// this will perform handleChars() between the statement's end AND the
// 'else'
private int internalHandleElse(IfStatement ifStatement) throws BadLocationException {
int lastPosition = ifStatement.getTrueStatement().getEnd();
int positionOfElse = -1;
try {
// information needed to handleChars between "if" statement end to
// the "else"...
positionOfElse = getFirstTokenOffset(lastPosition, ifStatement.getFalseStatement().getStart(), stElse,
true);
} catch (Exception e) {
Logger.logException(e);
return lastPosition;
}
if (positionOfElse > lastPosition) {
handleChars(lastPosition, positionOfElse + 4); // 4 =
// "else".length()
appendToBuffer("else"); //$NON-NLS-1$
handleChars(positionOfElse + 4, positionOfElse + 4);
lastPosition = positionOfElse + 4;
} else {
appendToBuffer("else"); //$NON-NLS-1$
}
return lastPosition;
}
@Override
public boolean visit(IgnoreError ignoreError) {
lineWidth++;// the '@' sign
ignoreError.getExpression().accept(this);
return false;
}
@Override
public boolean visit(Include include) {
int lastPosition = include.getStart();
int len = (include.getIncludeType() == Include.IT_INCLUDE || include.getIncludeType() == Include.IT_REQUIRE) ? 7
: 12;
lastPosition += len;
lineWidth += len;// add 'include' 'require' 'require_once'
insertSpace();
handleChars(lastPosition, include.getExpression().getStart());
include.getExpression().accept(this);
return false;
}
@Override
public boolean visit(InfixExpression infixExpression) {
int oldIndentationLevel = indentationLevel;
boolean oldWasBinaryExpressionWrapped = wasBinaryExpressionWrapped;
boolean forceSplit = this.preferences.line_wrap_binary_expression_force_split;
if (binaryExpressionLineWrapPolicy == -1) {// not initialized
binaryExpressionLineWrapPolicy = this.preferences.line_wrap_binary_expression_line_wrap_policy;
binaryExpressionIndentGap = calculateIndentGap(this.preferences.line_wrap_binary_expression_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
}
if (binaryExpressionSavedBuffer == null) {
binaryExpressionSavedBuffer = replaceBuffer.toString();
binaryExpressionSavedNode = infixExpression;
binaryExpressionSavedChangesIndex = changes.size() - 1;
binaryExpressionRevertPolicy = -1;
}
infixExpression.getLeft().accept(this);
int operator = infixExpression.getOperator();
boolean isStringOperator = ((operator == InfixExpression.OP_STRING_AND)
|| (operator == InfixExpression.OP_STRING_OR) || (operator == InfixExpression.OP_STRING_XOR));
if (isStringOperator || this.preferences.insert_space_before_binary_operation) {
insertSpace();
}
appendToBuffer(InfixExpression.getOperator(operator));
// Need consider the right expression at first,because the right
// expression might be
// long enough to expend the line width.
// This should cause the line wrap.
int lineW = calcLinesWidth(infixExpression.getRight());
switch (binaryExpressionLineWrapPolicy) {
case NO_LINE_WRAP:
// no_wrap
break;
case FIRST_WRAP_WHEN_NECESSARY:
if (lineW > this.preferences.line_wrap_line_split) {
binaryExpressionLineWrapPolicy = WRAP_WHEN_NECESSARY;
insertNewLine();
indentationLevel += binaryExpressionIndentGap;
indent();
wasBinaryExpressionWrapped = true;
}
break;
case WRAP_WHEN_NECESSARY:
if (lineW > this.preferences.line_wrap_line_split) {
insertNewLine();
indent();
wasBinaryExpressionWrapped = true;
}
break;
case WRAP_FIRST_ELEMENT:
if (forceSplit || lineW > this.preferences.line_wrap_line_split) {
if (binaryExpressionRevertPolicy != -1) {
binaryExpressionRevertPolicy = -1;
binaryExpressionLineWrapPolicy = WRAP_WHEN_NECESSARY;
insertNewLine();
indentationLevel += binaryExpressionIndentGap;
indent();
wasBinaryExpressionWrapped = true;
} else {
binaryExpressionRevertPolicy = WRAP_FIRST_ELEMENT;
binaryExpressionLineWrapPolicy = NO_LINE_WRAP;
}
}
break;
case WRAP_ALL_ELEMENTS:
if (forceSplit || lineW > this.preferences.line_wrap_line_split) {
if (binaryExpressionRevertPolicy != -1) {
binaryExpressionRevertPolicy = -1;
binaryExpressionLineWrapPolicy = ALWAYS_WRAP_ELEMENT;
insertNewLine();
indentationLevel += binaryExpressionIndentGap;
indent();
wasBinaryExpressionWrapped = true;
} else {
binaryExpressionRevertPolicy = WRAP_ALL_ELEMENTS;
binaryExpressionLineWrapPolicy = NO_LINE_WRAP;
}
}
break;
case WRAP_ALL_ELEMENTS_NO_INDENT_FIRST:
if (forceSplit || lineW > this.preferences.line_wrap_line_split) {
if (binaryExpressionRevertPolicy != -1) {
binaryExpressionRevertPolicy = -1;
binaryExpressionLineWrapPolicy = ALWAYS_WRAP_ELEMENT;
insertNewLine();
indentationLevel += binaryExpressionIndentGap;
indent();
wasBinaryExpressionWrapped = true;
// increase the indentation level after the first element
indentationLevel++;
isBinaryExpressionExtraIndentation = true;
} else {
binaryExpressionRevertPolicy = WRAP_ALL_ELEMENTS_NO_INDENT_FIRST;
binaryExpressionLineWrapPolicy = NO_LINE_WRAP;
}
}
break;
case WRAP_ALL_ELEMENTS_EXCEPT_FIRST:
if (forceSplit || lineW > this.preferences.line_wrap_line_split) {
if (binaryExpressionRevertPolicy != -1) {
binaryExpressionLineWrapPolicy = WRAP_ALL_ELEMENTS;
} else {
binaryExpressionRevertPolicy = WRAP_ALL_ELEMENTS_EXCEPT_FIRST;
binaryExpressionLineWrapPolicy = NO_LINE_WRAP;
}
}
break;
case ALWAYS_WRAP_ELEMENT:
insertNewLine();
indent();
wasBinaryExpressionWrapped = true;
break;
}
if (isStringOperator || this.preferences.insert_space_after_binary_operation) {
insertSpace();
}
// handle the chars between the variable to the value
handleChars(infixExpression.getLeft().getEnd(), infixExpression.getRight().getStart());
if (binaryExpressionRevertPolicy != -1 && infixExpression == binaryExpressionSavedNode) {
if (binaryExpressionLineWrapPolicy == WRAP_ALL_ELEMENTS
&& binaryExpressionRevertPolicy == WRAP_ALL_ELEMENTS_EXCEPT_FIRST) {
infixExpression.getRight().accept(this);
} else {
revert(binaryExpressionSavedBuffer, binaryExpressionSavedChangesIndex);
binaryExpressionLineWrapPolicy = binaryExpressionRevertPolicy;
// undo everything
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=506488
binaryExpressionSavedBuffer = replaceBuffer.toString();
// must be null to avoid infinite loop (note that
// binaryExpressionSavedBuffer must stay non-null here to avoid
// resetting binaryExpressionSavedNode to a non-null value later
// again)
binaryExpressionSavedNode = null;
binaryExpressionSavedChangesIndex = changes.size() - 1;
binaryExpressionRevertPolicy = -1;
indentationLevel = oldIndentationLevel;
wasBinaryExpressionWrapped = oldWasBinaryExpressionWrapped;
infixExpression.accept(this);
}
} else {
infixExpression.getRight().accept(this);
}
return false;
}
private int calcLinesWidth(ASTNode node) {
int lineW = lineWidth;
try {
int lineForStart = document.getLineOfOffset(node.getStart());
int lineForEnd = document.getLineOfOffset(node.getEnd());
if (lineForStart == lineForEnd) {
lineW += node.getLength();
} else {
lineW = document.getLineLength(lineForEnd);
}
} catch (BadLocationException e) {
Logger.logException(e);
}
return lineW;
}
@Override
public boolean visit(InLineHtml inLineHtml) {
updateLinesWidth(inLineHtml);
return false;
}
@Override
public boolean visit(InstanceOfExpression instanceOfExpression) {
instanceOfExpression.getExpression().accept(this);
if (this.preferences.insert_space_before_binary_operation) {
insertSpace();
}
appendToBuffer("instanceof"); //$NON-NLS-1$
if (this.preferences.insert_space_after_binary_operation) {
insertSpace();
}
// handle the chars between the variable to the value
handleChars(instanceOfExpression.getExpression().getEnd(), instanceOfExpression.getClassName().getStart());
instanceOfExpression.getClassName().accept(this);
return false;
}
@Override
public boolean visit(InterfaceDeclaration interfaceDeclaration) {
insertSpace();
lineWidth += 9;// interface
handleChars(interfaceDeclaration.getStart() + 9, interfaceDeclaration.getName().getStart());
interfaceDeclaration.getName().accept(this);
int lastPosition = interfaceDeclaration.getName().getEnd();
List<Identifier> interfaceList = interfaceDeclaration.interfaces();
Identifier[] interfaces = new Identifier[interfaceList.size()];
interfaces = interfaceList.toArray(interfaces);
if (interfaces.length > 0) {
appendToBuffer(" extends "); //$NON-NLS-1$
int indentationGap = calculateIndentGap(
this.preferences.line_wrap_superinterfaces_in_type_declaration_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
lastPosition = handleCommaList(interfaces, lastPosition,
this.preferences.insert_space_before_comma_in_implements,
this.preferences.insert_space_after_comma_in_implements,
this.preferences.line_wrap_superinterfaces_in_type_declaration_line_wrap_policy, indentationGap,
this.preferences.line_wrap_superinterfaces_in_type_declaration_force_split);
}
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_class,
this.preferences.insert_space_before_opening_brace_in_class);
handleChars(lastPosition, interfaceDeclaration.getBody().getStart());
interfaceDeclaration.getBody().accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
return false;
}
@Override
public boolean visit(ListVariable listVariable) {
if (this.preferences.insert_space_before_opening_paren_in_list) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_opening_paren_in_list) {
insertSpace();
}
int lastPosition = listVariable.getStart() + 4;
lineWidth += 4;
List<Expression> variables = listVariable.variables();
// XXX: variablesArray will contain one empty Variable object (i.e. with
// zero-length name) to represent empty list() statements.
Expression[] variablesArray = variables.toArray(new Expression[variables.size()]);
lastPosition = handleCommaList(variablesArray, lastPosition, this.preferences.insert_space_before_comma_in_list,
this.preferences.insert_space_after_comma_in_list, NO_LINE_WRAP, NO_LINE_WRAP_INDENT, false);
if (this.preferences.insert_space_before_closing_paren_in_list) {
if (variablesArray[variablesArray.length - 1].getLength() == 0
&& this.preferences.insert_space_after_comma_in_list) {
// in the following case list($a,) we don't want the second
// empty parameter to have two spaces. (like this: list( $a, ))
} else {
insertSpace();
}
}
appendToBuffer(CLOSE_PARN);
handleChars(lastPosition, listVariable.getEnd());
return false;
}
@Override
public boolean visit(MethodDeclaration classMethodDeclaration) {
// handle method modifiers
String originalModifier = getDocumentString(classMethodDeclaration.getStart(),
classMethodDeclaration.getFunction().getStart()).trim();
StringTokenizer tokenizer = new StringTokenizer(originalModifier);
StringBuilder strBuffer = new StringBuilder();
while (tokenizer.hasMoreTokens()) {
strBuffer.append(tokenizer.nextToken() + " "); //$NON-NLS-1$
}
int len;
String formattedModifier = ""; //$NON-NLS-1$
if ((len = strBuffer.length()) > 0) { /* trim trailing space */
formattedModifier = strBuffer.toString().substring(0, len - 1);
}
appendToBuffer(formattedModifier);
if (formattedModifier.length() > 0) {
insertSpace();
}
handleChars(classMethodDeclaration.getStart(), classMethodDeclaration.getFunction().getStart());
classMethodDeclaration.getFunction().accept(this);
return false;
}
@Override
public boolean visit(MethodInvocation methodInvocation) {
VariableBase dispatch = methodInvocation.getDispatcher();
// 0029458: [Ticket 188445][Roman][Feature] Method chaining in Code
// Formatter
// Track the chain level here.
if (this.preferences.new_line_in_second_invoke > 0) {
if (chainStack.isEmpty() || chainStack.peek() == -1) {
chainStack.push(1);
} else {
int value = chainStack.pop();
chainStack.push(++value);
}
if (!(dispatch instanceof MethodInvocation)) {
chainStack.push(-1);
}
}
dispatch.accept(this);
if (this.preferences.insert_space_before_arrow_in_method_invocation) {
insertSpace();
}
if (this.preferences.new_line_in_second_invoke > 0) {
// 0029458: [Ticket 188445][Roman][Feature] Method chaining in Code
// Formatter
// calculate the peek chain level.
if (peek == null) {
peek = -1;
}
if (chainStack.peek() == -1) {
chainStack.pop();
}
peek = peek > chainStack.peek() ? peek : chainStack.peek();
// if peek chain level bigger than the preference, wrap them
if (peek > this.preferences.new_line_in_second_invoke - 1) {
if (dispatch instanceof MethodInvocation) {
insertNewLine();
indentationLevel++;
indent();
indentationLevel--;
}
}
}
appendToBuffer("->"); //$NON-NLS-1$
if (this.preferences.insert_space_after_arrow_in_method_invocation) {
insertSpace();
}
if (this.preferences.insert_space_after_arrow_in_method_invocation) {
insertSpace();
}
// handle the chars between the dispatcher to the property
handleChars(methodInvocation.getDispatcher().getEnd(), methodInvocation.getMethod().getStart());
methodInvocation.getMethod().accept(this);
// 0029458: [Ticket 188445][Roman][Feature] Method chaining in Code
// Formatter
// Track the chain level.
if (this.preferences.new_line_in_second_invoke > 0) {
if (!chainStack.isEmpty()) {
int value = chainStack.pop();
value -= 1;
if (value > 0) {
chainStack.push(value);
} else {
if (!chainStack.isEmpty()) {
peek = chainStack.peek();
} else {
peek = -1;
}
}
}
}
return false;
}
@Override
public boolean visit(ParenthesisExpression parenthesisExpression) {
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_open_paren_in_parenthesis_expression) {
insertSpace();
}
int lastPosition = parenthesisExpression.getStart();
Expression expression = parenthesisExpression.getExpression();
if (expression != null) {
// till the expression
handleChars(lastPosition, expression.getStart());
expression.accept(this);
lastPosition = expression.getEnd();
}
if (this.preferences.insert_space_before_close_paren_in_parenthesis_expression) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
handleChars(lastPosition, parenthesisExpression.getEnd());
return false;
}
@Override
public boolean visit(PostfixExpression postfixExpressions) {
postfixExpressions.getVariable().accept(this);
if (this.preferences.insert_space_before_postfix_expression) {
insertSpace();
}
appendToBuffer(PostfixExpression.getOperator(postfixExpressions.getOperator()));
if (this.preferences.insert_space_before_prefix_expression) {
insertSpace();
}
// handle the chars between the variable to the value
handleChars(postfixExpressions.getVariable().getEnd(), postfixExpressions.getEnd());
return false;
}
@Override
public boolean visit(PrefixExpression prefixExpression) {
if (prefixExpression.getOperator() != PrefixExpression.OP_UNPACK
&& this.preferences.insert_space_before_prefix_expression) {
insertSpace();
}
appendToBuffer(PrefixExpression.getOperator(prefixExpression.getOperator()));
if (prefixExpression.getOperator() != PrefixExpression.OP_UNPACK
&& this.preferences.insert_space_after_prefix_expression) {
insertSpace();
}
// handle the chars between the variable to the value
handleChars(prefixExpression.getStart(), prefixExpression.getVariable().getStart());
prefixExpression.getVariable().accept(this);
return false;
}
@Override
public boolean visit(Program program) {
isPhpEqualTag = false;
int lastStatementEndOffset = 0;
boolean isPhpMode = false;
List<Statement> statementList = program.statements();
Statement[] statements = new Statement[statementList.size()];
statements = statementList.toArray(statements);
// FIXME if the php file only contains comments,the comments will not be
// formatted
// if (statements.length == 0 && !program.comments().isEmpty()) {
// try {
// Comment comment = program.comments().get(
// program.comments().size() - 1);
// boolean hasComments = hasComments(program.getStart(),
// comment.getEnd());
// if (hasComments) {
// // handle the comments
// handleComments(program.getStart(), program.getEnd(),
// astLexer.getCommentList());
// } else {
// }
// } catch (Exception e) {
// Logger.logException(e);
// }
//
// }
for (int i = 0; i < statements.length; i++) {
boolean isHtmlStatement = statements[i].getType() == ASTNode.IN_LINE_HTML;
boolean isASTError = statements[i].getType() == ASTNode.AST_ERROR;
// fixed bug 0015682
// in case of previous statement is an error there is no need for
// new lines
// because the lastStatementEndOffset position move to the current
// statement start position
boolean isStatementAfterError = i > 0 ? statements[i - 1].getType() == ASTNode.AST_ERROR : false;
if (isASTError && i + 1 < statements.length) {
// move the lastStatementEndOffset position to the start of the
// next statement start position
lastStatementEndOffset = statements[i + 1].getStart();
} else {
if (isPhpMode && !isHtmlStatement) {
// PHP -> PHP
if (lastStatementEndOffset > 0) {
if (!isStatementAfterError && getPhpStartTag(lastStatementEndOffset) != -1) {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=489361
// if previous statement was in a <?= ?> section and
// now we have a statement in a <?php ?> section,
// we're still in the PHP -> PHP case, but
// isPhpEqualTag and indentationLevel are outdated
if (isPhpEqualTag) {
// no need to recalculate the indentation if
// previous statement was in a <?php ?> section
// (i.e. previous value of isPhpEqualTag was
// false)
indentationLevel = getPhpTagIndentationLevel(lastStatementEndOffset);
}
isPhpEqualTag = getPhpStartTag(lastStatementEndOffset) == PHP_OPEN_SHORT_TAG_WITH_EQUAL;
insertNewLine();
}
if (!isStatementAfterError) {
insertNewLines(statements[i]);
indent();
}
if (lastStatementEndOffset <= statements[i].getStart()) {
handleChars(lastStatementEndOffset, statements[i].getStart());
}
}
} else if (isPhpMode && isHtmlStatement) {
// PHP -> HTML
if (lastStatementEndOffset > 0) {
if (lastStatementEndOffset <= statements[i].getStart()) {
handleChars(lastStatementEndOffset, statements[i].getStart());
}
}
isPhpMode = false;
} else if (!isPhpMode && !isHtmlStatement) {
// HTML -> PHP
if (!isStatementAfterError) {
isPhpEqualTag = getPhpStartTag(lastStatementEndOffset) == PHP_OPEN_SHORT_TAG_WITH_EQUAL;
indentationLevel = getPhpTagIndentationLevel(lastStatementEndOffset);
insertNewLines(statements[i]);
indent();
}
if (lastStatementEndOffset <= statements[i].getStart()) {
handleChars(lastStatementEndOffset, statements[i].getStart());
}
isPhpMode = true;
} else {
// first HTML
isPhpMode = false;
}
statements[i].accept(this);
lastStatementEndOffset = statements[i].getEnd();
// need check how many new lines will the next statement
// insert
if (i + 1 < statements.length && statements[i].getType() == ASTNode.NAMESPACE
&& statements[i + 1].getType() == ASTNode.NAMESPACE) {
int numberOfLines = getNumbreOfLines(statements[i + 1]) - 1;
numberOfLines = this.preferences.blank_lines_between_namespaces - numberOfLines;
if (numberOfLines > 0) {
for (int j = 0; j < numberOfLines; j++) {
insertNewLine();
}
}
// ignoreEmptyLineSetting = true;
ignoreEmptyLineSetting = !preferences.indent_empty_lines;
}
}
}
return false;
}
@Override
public boolean visit(Quote quote) {
updateLinesWidth(quote);
if (quote.getQuoteType() == Quote.QT_HEREDOC) {
int i = quote.getEnd();
if (isContainChar(i, i + 1, SEMICOLON)) {
isHeredocSemicolon = true;
} else {
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=411322
// always insert a new line after the closing HEREDOC tag
boolean isPhpEqualTagOld = isPhpEqualTag;
isPhpEqualTag = false;
insertNewLine();
isPhpEqualTag = isPhpEqualTagOld;
}
}
return false;
}
@Override
public boolean visit(Reference reference) {
lineWidth++;// &$a
reference.getExpression().accept(this);
return false;
}
@Override
public boolean visit(ReflectionVariable reflectionVariable) {
lineWidth++;// $$a
reflectionVariable.getName().accept(this);
return false;
}
@Override
public boolean visit(ReturnStatement returnStatement) {
int lastPosition = returnStatement.getStart() + 6;
lineWidth += 6;
Expression expression = returnStatement.getExpression();
if (expression != null) {
insertSpace();
handleChars(lastPosition, expression.getStart());
expression.accept(this);
lastPosition = expression.getEnd();
}
handleSemicolon(lastPosition, returnStatement.getEnd());
return false;
}
@Override
public boolean visit(YieldExpression yieldExpression) {
lineWidth += 5;
// handle [key => expr] or just [expr]
int lastPosition = yieldExpression.getStart() + 5;
insertSpace();
if (yieldExpression.getKey() != null) {
handleChars(lastPosition, yieldExpression.getKey().getStart());
yieldExpression.getKey().accept(this);
if (this.preferences.insert_space_before_arrow_in_yield) {
insertSpace();
}
appendToBuffer(KEY_VALUE_OPERATOR);
if (this.preferences.insert_space_after_arrow_in_yield) {
insertSpace();
}
lastPosition = yieldExpression.getKey().getEnd();
} else if (yieldExpression.getOperator() == YieldExpression.OP_FROM) {
insertSpace();
appendToBuffer(FROM);
insertSpace();
}
Expression expression = yieldExpression.getExpression();
if (expression != null) {
handleChars(lastPosition, expression.getStart());
expression.accept(this);
lastPosition = expression.getEnd();
}
return false;
}
@Override
public boolean visit(Scalar scalar) {
updateLinesWidth(scalar);
return false;
}
@Override
public boolean visit(StaticConstantAccess staticConstantAccess) {
staticConstantAccess.getClassName().accept(this);
if (this.preferences.insert_space_before_coloncolon_in_field_access) {
insertSpace();
}
appendToBuffer(COLON);
appendToBuffer(COLON);
if (this.preferences.insert_space_after_coloncolon_in_field_access) {
insertSpace();
}
handleChars(staticConstantAccess.getClassName().getEnd(), staticConstantAccess.getConstant().getStart());
staticConstantAccess.getConstant().accept(this);
return false;
}
@Override
public boolean visit(StaticFieldAccess staticFieldAccess) {
staticFieldAccess.getClassName().accept(this);
if (this.preferences.insert_space_before_coloncolon_in_field_access) {
insertSpace();
}
appendToBuffer(COLON);
appendToBuffer(COLON);
if (this.preferences.insert_space_after_coloncolon_in_field_access) {
insertSpace();
}
handleChars(staticFieldAccess.getClassName().getEnd(), staticFieldAccess.getField().getStart());
staticFieldAccess.getField().accept(this);
return false;
}
@Override
public boolean visit(StaticMethodInvocation staticMethodInvocation) {
staticMethodInvocation.getClassName().accept(this);
if (this.preferences.insert_space_before_coloncolon_in_method_invocation) {
insertSpace();
}
appendToBuffer(COLON);
appendToBuffer(COLON);
if (this.preferences.insert_space_after_coloncolon_in_method_invocation) {
insertSpace();
}
handleChars(staticMethodInvocation.getClassName().getEnd(), staticMethodInvocation.getMethod().getStart());
staticMethodInvocation.getMethod().accept(this);
return false;
}
@Override
public boolean visit(StaticStatement staticStatement) {
int lastPosition = staticStatement.getStart() + 6;
lineWidth += 6;
insertSpace();
List<Expression> expList = staticStatement.expressions();
Expression[] expressions = new Expression[expList.size()];
expressions = expList.toArray(expressions);
lastPosition = handleCommaList(expressions, lastPosition, this.preferences.insert_space_before_comma_in_static,
this.preferences.insert_space_after_comma_in_static, NO_LINE_WRAP, NO_LINE_WRAP_INDENT, false);
handleSemicolon(lastPosition, staticStatement.getEnd());
return false;
}
@Override
public boolean visit(SwitchCase switchCase) {
// handle the chars between the 'case'/'default' and the condition start
// position/ first statement
int lastStatementEndOffset = 0;
if (switchCase.isDefault()) {
if (this.preferences.insert_space_after_switch_default) {
insertSpace();
}
lastStatementEndOffset = switchCase.getStart() + 7;
lineWidth += 7;// the word 'default'
} else {
insertSpace();
lineWidth += 4;// the word 'case'
handleChars(switchCase.getStart() + 4, switchCase.getValue().getStart());
switchCase.getValue().accept(this);
if (this.preferences.insert_space_after_switch_case_value) {
insertSpace();
}
lastStatementEndOffset = switchCase.getValue().getEnd();
}
appendToBuffer(COLON);
int regularStatementIndentation = 0;
int breakStatementIndentation = 0;
if (this.preferences.indent_statements_within_case) {
regularStatementIndentation++;
}
if (this.preferences.indent_break_statements_within_case) {
breakStatementIndentation++;
}
Statement[] actions = new Statement[switchCase.actions().size()];
switchCase.actions().toArray(actions);
if (actions.length == 0) {
handleChars(lastStatementEndOffset, switchCase.getEnd());
} else {
for (int i = 0; i < actions.length; i++) {
if (actions[i].getType() == ASTNode.IN_LINE_HTML) {
handleChars(lastStatementEndOffset, lastStatementEndOffset);
lastStatementEndOffset = actions[i].getEnd();
continue;
}
boolean isBreakStatement = actions[i].getType() == ASTNode.BREAK_STATEMENT;
this.indentationLevel += isBreakStatement ? breakStatementIndentation : regularStatementIndentation;
insertNewLine();
indent();
handleChars(lastStatementEndOffset, actions[i].getStart());
actions[i].accept(this);
lastStatementEndOffset = actions[i].getEnd();
this.indentationLevel -= isBreakStatement ? breakStatementIndentation : regularStatementIndentation;
}
}
return false;
}
@Override
public boolean visit(SwitchStatement switchStatement) {
// handle the chars between the 'switch' and the expr start position
if (this.preferences.insert_space_before_opening_paren_in_switch) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_opening_paren_in_switch) {
insertSpace();
}
lineWidth += 6;
Expression expression = switchStatement.getExpression();
handleChars(switchStatement.getStart() + 6, expression.getStart());
// handle the switch expr
expression.accept(this);
// handle the chars between the expression end position and action start
// position
// set the switch closing parn spaces
if (this.preferences.insert_space_before_closing_paren_in_switch) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
// switch body
boolean isIndentationAdded = false;
isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_switch,
this.preferences.insert_space_before_opening_brace_in_switch);
Block body = switchStatement.getBody();
handleChars(expression.getEnd(), body.getStart());
body.accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
return false;
}
@Override
public boolean visit(ThrowStatement throwStatement) {
insertSpace();
lineWidth += 5;
Expression expr = throwStatement.getExpression();
handleChars(throwStatement.getStart() + 5, expr.getStart());
expr.accept(this);
handleSemicolon(expr.getEnd(), throwStatement.getEnd());
return false;
}
@Override
public boolean visit(TryStatement tryStatement) {
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_block,
this.preferences.insert_space_before_opening_brace_in_block);
lineWidth += 3;
Block body = tryStatement.getBody();
handleChars(tryStatement.getStart() + 3, body.getStart());
body.accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
int lastStatementEndOffset = body.getEnd();
List<CatchClause> clausesList = tryStatement.catchClauses();
CatchClause[] catchClauses = new CatchClause[clausesList.size()];
catchClauses = clausesList.toArray(catchClauses);
for (int i = 0; i < catchClauses.length; i++) {
if (preferences.control_statement_insert_newline_before_catch_in_try) {
insertNewLine();
indent();
} else {
if (this.preferences.insert_space_after_closing_brace_in_block) {
insertSpace();
}
}
handleChars(lastStatementEndOffset, catchClauses[i].getStart());
catchClauses[i].accept(this);
lastStatementEndOffset = catchClauses[i].getEnd();
}
if (tryStatement.finallyClause() != null) {
if (preferences.control_statement_insert_newline_before_finally_in_try) {
insertNewLine();
indent();
} else {
if (this.preferences.insert_space_after_closing_brace_in_block) {
insertSpace();
}
}
handleChars(lastStatementEndOffset, tryStatement.finallyClause().getStart());
tryStatement.finallyClause().accept(this);
lastStatementEndOffset = tryStatement.finallyClause().getEnd();
}
return false;
}
@Override
public boolean visit(UnaryOperation unaryOperation) {
if (this.preferences.insert_space_before_unary_expression) {
insertSpace();
}
appendToBuffer(UnaryOperation.getOperator(unaryOperation.getOperator()));
if (this.preferences.insert_space_after_unary_expression) {
insertSpace();
}
// handle the chars between the variable to the value
Expression expr = unaryOperation.getExpression();
handleChars(unaryOperation.getStart(), expr.getStart());
expr.accept(this);
return false;
}
@Override
public boolean visit(Variable variable) {
if (variable.isDollared()) {
lineWidth++;
}
variable.getName().accept(this);
return false;
}
@Override
public boolean visit(WhileStatement whileStatement) {
// handle the chars between the 'while' and the condition start position
if (this.preferences.insert_space_before_opening_paren_in_while) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_after_opening_paren_in_while) {
insertSpace();
}
lineWidth += 5;
handleChars(whileStatement.getStart() + 5, whileStatement.getCondition().getStart());
// handle the while condition
whileStatement.getCondition().accept(this);
// handle the chars between the condition end position and action start
// position
// set the while closing paren spaces
if (this.preferences.insert_space_before_closing_paren_in_while) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
// while action
final int lastPosition = whileStatement.getCondition().getEnd();
handleAction(lastPosition, whileStatement.getBody(), true);
return false;
}
// PHP 5.3 new nodes:
@Override
public boolean visit(NamespaceDeclaration namespaceDeclaration) {
appendToBuffer("namespace"); //$NON-NLS-1$
insertSpace();
int lastPosition = namespaceDeclaration.getStart();
if (namespaceDeclaration.getName() != null) {
handleChars(namespaceDeclaration.getStart(), namespaceDeclaration.getName().getStart());
namespaceDeclaration.getName().accept(this);
lastPosition = namespaceDeclaration.getName().getEnd();
}
if (namespaceDeclaration.isBracketed()) {
// handle class body
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_class,
this.preferences.insert_space_before_opening_brace_in_class);
handleChars(lastPosition, namespaceDeclaration.getBody().getStart());
namespaceDeclaration.getBody().accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
} else {
handleSemicolon(lastPosition, namespaceDeclaration.getBody().getStart());
namespaceDeclaration.getBody().accept(this);
}
return false;
}
@Override
public boolean visit(NamespaceName namespaceName) {
if (namespaceName.isGlobal()) {
appendToBuffer("\\"); //$NON-NLS-1$
}
if (namespaceName.isCurrent()) {
appendToBuffer("namespace\\"); //$NON-NLS-1$
}
List<Identifier> segments = namespaceName.segments();
if (segments.size() > 0) {
handleChars(namespaceName.getStart(), segments.get(0).getStart());
Iterator<Identifier> it = segments.iterator();
Identifier prev = null;
while (it.hasNext()) {
Identifier identifier = it.next();
if (prev != null) {
handleChars(prev.getEnd(), identifier.getStart());
}
identifier.accept(this);
if (it.hasNext()) {
appendToBuffer("\\"); //$NON-NLS-1$
prev = identifier;
}
}
} else {
handleChars(namespaceName.getStart(), namespaceName.getEnd());
}
return false;
}
@Override
public boolean visit(UseStatement useStatement) {
int lastPosition = useStatement.getStart() + 3;
lineWidth += 3;// the word 'use'
insertSpace();
appendStatementType(useStatement.getStatementType());
int lineWrapPolicy = NO_LINE_WRAP;
int indentationGap = NO_LINE_WRAP_INDENT;
boolean spaceBeforeComma = this.preferences.insert_space_before_comma_in_global;
boolean spaceAfterComma = this.preferences.insert_space_after_comma_in_global;
boolean forceSplit = false;
if (useStatement.getNamespace() != null) {
insertSpace();
handleChars(lastPosition, useStatement.getNamespace().getStart());
useStatement.getNamespace().accept(this);
lastPosition = useStatement.getNamespace().getEnd();
insertSpace();
appendToBuffer(OPEN_CURLY);
lineWrapPolicy = WRAP_ALL_ELEMENTS;
forceSplit = true;
indentationGap = 1;
spaceBeforeComma = false;
spaceAfterComma = false;
}
List<UseStatementPart> parts = useStatement.parts();
lastPosition = handleCommaList(parts.toArray(new ASTNode[parts.size()]), lastPosition, spaceBeforeComma,
spaceAfterComma, lineWrapPolicy, indentationGap, forceSplit);
if (useStatement.getNamespace() != null) {
insertNewLine();
indent();
appendToBuffer(CLOSE_CURLY);
}
handleSemicolon(lastPosition, useStatement.getEnd());
return false;
}
@Override
public boolean visit(UseStatementPart useStatementPart) {
appendStatementType(useStatementPart.getStatementType());
useStatementPart.getName().accept(this);
Identifier alias = useStatementPart.getAlias();
if (alias != null) {
insertSpace();
appendToBuffer("as"); //$NON-NLS-1$
insertSpace();
handleChars(useStatementPart.getName().getEnd(), alias.getStart());
alias.accept(this);
}
return false;
}
private void appendStatementType(int statementType) {
if (statementType == UseStatement.T_FUNCTION) {
appendToBuffer(PHPTokenNames.getName(CompilerParserConstants.T_FUNCTION));
insertSpace();
} else if (statementType == UseStatement.T_CONST) {
appendToBuffer(PHPTokenNames.getName(CompilerParserConstants.T_CONST));
insertSpace();
}
}
@Override
public boolean visit(LambdaFunctionDeclaration lambdaFunctionDeclaration) {
StringBuilder buffer = new StringBuilder();
if (lambdaFunctionDeclaration.isStatic()) {
buffer.append("static "); //$NON-NLS-1$
}
buffer.append(
getDocumentString(lambdaFunctionDeclaration.getStart(), lambdaFunctionDeclaration.getStart() + 8));// append
// 'function'
// handle referenced function with '&'
if (lambdaFunctionDeclaration.isReference()) {
buffer.append(" &"); //$NON-NLS-1$
}
appendToBuffer(buffer.toString());
handleChars(lambdaFunctionDeclaration.getStart(), lambdaFunctionDeclaration.getStart() + 8);
if (this.preferences.insert_space_before_opening_paren_in_function_declaration
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=492770
|| !lambdaFunctionDeclaration.isReference()) {
insertSpace();
}
appendToBuffer(OPEN_PARN);
List<FormalParameter> formalParameters = lambdaFunctionDeclaration.formalParameters();
ASTNode[] params = (FormalParameter[]) formalParameters.toArray(new FormalParameter[formalParameters.size()]);
int lastPosition = lambdaFunctionDeclaration.getStart() + 8;
if (params.length > 0) {
if (this.preferences.insert_space_after_opening_paren_in_function_declaration) {
insertSpace();
}
int indentationGap = calculateIndentGap(
this.preferences.line_wrap_parameters_in_method_declaration_indent_policy,
this.preferences.line_wrap_wrapped_lines_indentation);
lastPosition = handleCommaList(params, lastPosition,
this.preferences.insert_space_before_comma_in_function_declaration,
this.preferences.insert_space_after_comma_in_function_declaration,
this.preferences.line_wrap_parameters_in_method_declaration_line_wrap_policy, indentationGap,
this.preferences.line_wrap_parameters_in_method_declaration_force_split);
if (this.preferences.insert_space_before_closing_paren_in_function_declaration) {
insertSpace();
}
} else {
if (this.preferences.insert_space_between_empty_paren_in_function_declaration) {
insertSpace();
}
}
appendToBuffer(CLOSE_PARN);
List<Expression> variables = lambdaFunctionDeclaration.lexicalVariables();
if (variables.size() > 0) {
// TODO: Added a new preference?
insertSpace();
appendToBuffer("use"); //$NON-NLS-1$
insertSpace();
appendToBuffer(OPEN_PARN);
if (this.preferences.insert_space_before_opening_paren_in_function_declaration) {
insertSpace();
}
ASTNode[] vars = (Expression[]) variables.toArray(new Expression[variables.size()]);
lastPosition = handleCommaList(vars, lastPosition,
this.preferences.insert_space_before_comma_in_function_declaration,
this.preferences.insert_space_after_comma_in_function_declaration,
this.preferences.line_wrap_parameters_in_method_declaration_line_wrap_policy, 0,
this.preferences.line_wrap_parameters_in_method_declaration_force_split);
if (this.preferences.insert_space_before_closing_paren_in_function_declaration) {
insertSpace();
}
appendToBuffer(CLOSE_PARN);
}
if (lambdaFunctionDeclaration.getReturnType() != null) {
appendToBuffer(COLON);
insertSpace();
if (lambdaFunctionDeclaration.getReturnType().isNullable()) {
appendToBuffer(QUESTION_MARK);
}
handleChars(lastPosition, lambdaFunctionDeclaration.getReturnType().getStart());
lambdaFunctionDeclaration.getReturnType().accept(this);
lastPosition = lambdaFunctionDeclaration.getReturnType().getEnd();
}
// handle function body
if (lambdaFunctionDeclaration.getBody() != null) {
boolean isIndentationAdded = handleBlockOpenBrace(this.preferences.brace_position_for_lambda_function,
this.preferences.insert_space_before_opening_brace_in_function);
handleChars(lastPosition, lambdaFunctionDeclaration.getBody().getStart());
lambdaFunctionDeclaration.getBody().accept(this);
if (isIndentationAdded) {
indentationLevel--;
indentationLevelDescending = true;
}
} else {
handleSemicolon(lastPosition, lambdaFunctionDeclaration.getEnd());
}
return false;
}
@Override
public boolean visit(TraitUseStatement node) {
if (node.getTraitList().size() > 0) {
// int lastPosition = node.getStart() + 3;
lineWidth += 3;// the word 'use'
insertSpace();
handleChars(node.getStart() + 3, node.getTraitList().get(0).getStart());
}
return true;
}
/*
* (non-Javadoc)
*
* @see org.eclipse.php.internal.core.format.ICodeFormattingProcessor#
* createIndentationString(int)
*/
@Override
public @NonNull String createIndentationString(int indentationUnits) {
if (indentationUnits < 0) {
throw new IllegalArgumentException();
}
int tabs = 0;
tabs = indentationUnits;
if (tabs == 0) {
return ""; //$NON-NLS-1$
}
StringBuilder buffer = new StringBuilder(tabs * preferences.indentationSize);
for (int i = 0; i < tabs * preferences.indentationSize; i++) {
buffer.append(preferences.indentationChar);
}
return buffer.toString();
}
/*
* (non-Javadoc)
*
* @see org.eclipse.php.internal.core.format.ICodeFormattingProcessor#
* getTextEdits ()
*/
@Override
public @NonNull MultiTextEdit getTextEdits() {
List<ReplaceEdit> allChanges = getChanges();
MultiTextEdit rootEdit = new MultiTextEdit();
for (ReplaceEdit edit : allChanges) {
TextEdit textEdit = new ReplaceEdit(edit.getOffset(), edit.getLength(), edit.getText());
rootEdit.addChild(textEdit);
}
return rootEdit;
}
private boolean isInSingleLine(ReplaceEdit edit, IRegion[] partitions) {
for (int i = 0; i < partitions.length; i++) {
IRegion iTypedRegion = partitions[i];
if (edit.getOffset() >= iTypedRegion.getOffset()
&& edit.getOffset() + edit.getLength() <= iTypedRegion.getOffset() + iTypedRegion.getLength()) {
return true;
}
}
return false;
}
private List<IRegion> getAllPhpRegionsInContainer(int containerOffset, ITextRegionContainer container,
ITypedRegion partition) {
int start = partition.getOffset();
int end = partition.getOffset() + partition.getLength();
List<IRegion> result = new ArrayList<>();
Iterator<?> regionsIt = container.getRegions().iterator();
IRegion current = null;
while (regionsIt.hasNext()) {
ITextRegion region = (ITextRegion) regionsIt.next();
if (containerOffset + region.getStart() < start) {
continue;
}
if (containerOffset + region.getStart() + region.getLength() > end) {
break;
}
if (PHPRegionContext.PHP_OPEN.equals(region.getType())) {
if (current != null) {
result.add(current);
}
current = new Region(containerOffset + region.getStart(), region.getLength());
} else if (PHPRegionContext.PHP_CONTENT.equals(region.getType())) {
if (current != null) {
if (current.getOffset() + current.getLength() == containerOffset + region.getStart()) {
current = new Region(current.getOffset(), current.getLength() + region.getLength());
} else {
result.add(current);
current = new Region(containerOffset + region.getStart(), region.getLength());
}
} else {
current = new Region(containerOffset + region.getStart(), region.getLength());
}
} else if (PHPRegionContext.PHP_CLOSE.equals(region.getType())) {
if (current != null) {
if (current.getOffset() + current.getLength() == containerOffset + region.getStart()) {
result.add(new Region(current.getOffset(), current.getLength() + region.getLength()));
} else {
result.add(current);
result.add(new Region(containerOffset + region.getStart(), region.getLength()));
}
} else {
result.add(new Region(containerOffset + region.getStart(), region.getLength()));
}
current = null;
}
}
if (current != null) {
result.add(current);
}
return result;
}
/**
* PHP Partitions can contain contiguous <?php ?> regions (see
* {@link PHPStructuredTextPartitioner#computePartitioning(int, int)}), we
* have to split them manually.
*
* @param partition
* PHP Partition
* @return individual <?php ?> regions
*/
private List<IRegion> getAllPhpRegionsInPhpPartition(ITypedRegion partition) {
assert document instanceof IStructuredDocument;
assert PHPPartitionTypes.PHP_DEFAULT.equals(partition.getType());
List<IRegion> regions = new ArrayList<>();
int offset = partition.getOffset();
int end = partition.getOffset() + partition.getLength();
while (offset < end) {
IStructuredDocumentRegion sdRegion = ((IStructuredDocument) document).getRegionAtCharacterOffset(offset);
if (sdRegion == null) {
return regions;
}
ITextRegion phpScriptRegion = sdRegion.getRegionAtCharacterOffset(offset);
if (phpScriptRegion instanceof ITextRegionContainer) {
regions.addAll(getAllPhpRegionsInContainer(sdRegion.getStartOffset() + phpScriptRegion.getStart(),
(ITextRegionContainer) phpScriptRegion, partition));
offset = sdRegion.getStartOffset() + phpScriptRegion.getStart() + phpScriptRegion.getLength();
} else {
// sdRegion contains opening PHP tag, PHP content and closing
// PHP tag
regions.add(new Region(sdRegion.getStartOffset(), sdRegion.getLength()));
offset = sdRegion.getStartOffset() + sdRegion.getLength();
}
}
return regions;
}
private IRegion[] getAllSingleLine(ITypedRegion[] partitions) throws BadLocationException {
List<IRegion> result = new ArrayList<>();
if (document instanceof IStructuredDocument) {
for (int i = 0; i < partitions.length; i++) {
ITypedRegion partition = partitions[i];
if (PHPPartitionTypes.PHP_DEFAULT.equals(partition.getType())) {
for (IRegion phpRegion : getAllPhpRegionsInPhpPartition(partition)) {
if (isPhpRegionOnSingleLine(phpRegion.getOffset(), phpRegion.getLength())) {
result.add(phpRegion);
}
}
}
}
}
return result.toArray(new IRegion[result.size()]);
}
private boolean isPhpRegionOnSingleLine(int start, int length) throws BadLocationException {
assert length >= 0;
int endTagLength = "?>".length(); //$NON-NLS-1$
if (length < endTagLength || document.getLineOfOffset(start) != document.getLineOfOffset(start + length - 1)) {
return false;
}
// https://bugs.eclipse.org/bugs/show_bug.cgi?id=418959
// Tag "?>" is optional to end a PHP region.
// If this tag is missing at the very end of a PHP region,
// this region will not be considered as a single line.
return "?>".equals(document.get(start + length - endTagLength, //$NON-NLS-1$
endTagLength));
}
public static String join(Collection<String> s, String delimiter) {
if (s == null || s.isEmpty())
return ""; //$NON-NLS-1$
Iterator<String> iter = s.iterator();
StringBuilder builder = new StringBuilder(iter.next());
while (iter.hasNext()) {
builder.append(delimiter).append(iter.next());
}
return builder.toString();
}
}