Bug 472815 - [formatter] 'Indent Empty lines' option doesn't work inside empty blocks
Change-Id: I938313d2cdce400420075dbaeb1b4163baf38ce7
Signed-off-by: Mateusz Matela <mateusz.matela@gmail.com>
diff --git a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
index b4f540d..a846069 100644
--- a/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
+++ b/org.eclipse.jdt.core.tests.model/src/org/eclipse/jdt/core/tests/formatter/FormatterBugsTests.java
@@ -11463,4 +11463,163 @@
"}";
formatSource(source);
}
+/**
+ * https://bugs.eclipse.org/472815 - [formatter] 'Indent Empty lines' option doesn't work inside empty blocks
+ */
+public void testBug472815() {
+ this.formatterPrefs.number_of_empty_lines_to_preserve = 2;
+ String source =
+ "public class Snippet {\r\n" +
+ "\r\n" +
+ " int[] a1 = { };\r\n" +
+ " int[] a2 = {\r\n" +
+ " };\r\n" +
+ " int[] a3 = {\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ " int[] a4 = {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ " int[] a5 = {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ "\r\n" +
+ " void f1() { }\r\n" +
+ " void f2() {\r\n" +
+ " }\r\n" +
+ " void f3() {\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ " void f4() {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ " void f5() {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ "}";
+ formatSource(source,
+ "public class Snippet {\r\n" +
+ "\r\n" +
+ " int[] a1 = {};\r\n" +
+ " int[] a2 = {};\r\n" +
+ " int[] a3 = {\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ " int[] a4 = {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ " int[] a5 = {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ "\r\n" +
+ " void f1() {\r\n" +
+ " }\r\n" +
+ "\r\n" +
+ " void f2() {\r\n" +
+ " }\r\n" +
+ "\r\n" +
+ " void f3() {\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ "\r\n" +
+ " void f4() {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ "\r\n" +
+ " void f5() {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ "}"
+ );
+}
+/**
+ * https://bugs.eclipse.org/472815 - [formatter] 'Indent Empty lines' option doesn't work inside empty blocks
+ */
+public void testBug472815b() {
+ this.formatterPrefs.number_of_empty_lines_to_preserve = 2;
+ this.formatterPrefs.indent_empty_lines = true;
+ String source =
+ "public class Snippet {\r\n" +
+ "\r\n" +
+ " int[] a1 = { };\r\n" +
+ " int[] a2 = {\r\n" +
+ " };\r\n" +
+ " int[] a3 = {\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ " int[] a4 = {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ " int[] a5 = {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " };\r\n" +
+ "\r\n" +
+ " void f1() { }\r\n" +
+ " void f2() {\r\n" +
+ " }\r\n" +
+ " void f3() {\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ " void f4() {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ " void f5() {\r\n" +
+ "\r\n" +
+ "\r\n" +
+ "\r\n" +
+ " }\r\n" +
+ "}";
+ formatSource(source,
+ "public class Snippet {\r\n" +
+ " \r\n" +
+ " int[] a1 = {};\r\n" +
+ " int[] a2 = {};\r\n" +
+ " int[] a3 = {\r\n" +
+ " \r\n" +
+ " };\r\n" +
+ " int[] a4 = {\r\n" +
+ " \r\n" +
+ " \r\n" +
+ " };\r\n" +
+ " int[] a5 = {\r\n" +
+ " \r\n" +
+ " \r\n" +
+ " };\r\n" +
+ " \r\n" +
+ " void f1() {\r\n" +
+ " }\r\n" +
+ " \r\n" +
+ " void f2() {\r\n" +
+ " }\r\n" +
+ " \r\n" +
+ " void f3() {\r\n" +
+ " \r\n" +
+ " }\r\n" +
+ " \r\n" +
+ " void f4() {\r\n" +
+ " \r\n" +
+ " \r\n" +
+ " }\r\n" +
+ " \r\n" +
+ " void f5() {\r\n" +
+ " \r\n" +
+ " \r\n" +
+ " }\r\n" +
+ "}"
+ );
+}
}
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java
index ae1ef58..92b756b 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/LineBreaksPreparator.java
@@ -361,27 +361,28 @@
@Override
public boolean visit(ArrayInitializer node) {
int openBraceIndex = this.tm.firstIndexIn(node, TokenNameLBRACE);
- Token afterOpenBraceToken = this.tm.get(openBraceIndex + 1);
- boolean isEmpty = afterOpenBraceToken.tokenType == TokenNameRBRACE;
- if (isEmpty && this.options.keep_empty_array_initializer_on_one_line)
- return true;
+ boolean isEmpty = handleEmptyLinesIndentation(openBraceIndex);
- Token openBraceToken = this.tm.get(openBraceIndex);
int closeBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE);
- handleBracePosition(openBraceToken, closeBraceIndex, this.options.brace_position_for_array_initializer);
+ Token openBraceToken = this.tm.get(openBraceIndex);
+ Token closeBraceToken = this.tm.get(closeBraceIndex);
+
+ if (!(node.getParent() instanceof ArrayInitializer)) {
+ Token afterOpenBraceToken = this.tm.get(openBraceIndex + 1);
+ for (int i = 0; i < this.options.continuation_indentation_for_array_initializer; i++) {
+ afterOpenBraceToken.indent();
+ closeBraceToken.unindent();
+ }
+ }
+
+ if (!isEmpty || !this.options.keep_empty_array_initializer_on_one_line)
+ handleBracePosition(openBraceToken, closeBraceIndex, this.options.brace_position_for_array_initializer);
if (!isEmpty) {
- Token closeBraceToken = this.tm.get(closeBraceIndex);
if (this.options.insert_new_line_after_opening_brace_in_array_initializer)
openBraceToken.breakAfter();
if (this.options.insert_new_line_before_closing_brace_in_array_initializer)
closeBraceToken.breakBefore();
- if (!(node.getParent() instanceof ArrayInitializer)) {
- for (int i = 0; i < this.options.continuation_indentation_for_array_initializer; i++) {
- afterOpenBraceToken.indent();
- closeBraceToken.unindent();
- }
- }
}
return true;
}
@@ -550,6 +551,7 @@
: this.tm.firstIndexAfter(nodeBeforeOpenBrace, TokenNameLBRACE);
int closeBraceIndex = this.tm.lastIndexIn(node, TokenNameRBRACE);
Token openBraceToken = this.tm.get(openBraceIndex);
+ Token closeBraceToken = this.tm.get(closeBraceIndex);
handleBracePosition(openBraceToken, closeBraceIndex, bracePosition);
boolean isEmpty = true;
@@ -559,13 +561,16 @@
break;
}
}
+
+ handleEmptyLinesIndentation(openBraceIndex);
+
if (!isEmpty || newLineInEmpty) {
openBraceToken.breakAfter();
- this.tm.get(closeBraceIndex).breakBefore();
+ closeBraceToken.breakBefore();
}
if (indentBody) {
this.tm.get(openBraceIndex + 1).indent();
- this.tm.get(closeBraceIndex).unindent();
+ closeBraceToken.unindent();
}
}
@@ -582,6 +587,31 @@
}
}
+ private boolean handleEmptyLinesIndentation(int openBraceIndex) {
+ Token open = this.tm.get(openBraceIndex);
+ Token next = this.tm.get(openBraceIndex + 1);
+ boolean isEmpty = next.tokenType == TokenNameRBRACE;
+ if (!isEmpty || this.tm.countLineBreaksBetween(open, next) < 2 || !this.options.indent_empty_lines)
+ return isEmpty;
+
+ // find a line break and make a token out of it
+ for (int i = open.originalEnd + 1; i < next.originalStart; i++) {
+ char c = this.tm.charAt(i);
+ char c2 = this.tm.charAt(i + 1);
+ int lineBreakStart = (c == '\r' || c == '\n') ? i : -1;
+ int lineBreakEnd = ((c2 == '\r' || c2 == '\n') && c2 != c) ? i + 1 : lineBreakStart;
+ if (lineBreakStart >= 0) {
+ Token emptyLineToken = new Token(lineBreakStart, lineBreakEnd, Token.TokenNameEMPTY_LINE);
+ emptyLineToken.breakBefore();
+ emptyLineToken.breakAfter();
+ emptyLineToken.setToEscape(true); // force text builder to use toString()
+ this.tm.insert(openBraceIndex + 1, emptyLineToken);
+ return true;
+ }
+ }
+ return true;
+ }
+
private void indent(ASTNode node) {
int startIndex = this.tm.firstIndexIn(node, -1);
while (startIndex > 0 && this.tm.get(startIndex - 1).isComment())
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/Token.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/Token.java
index 66dfafb..762edd0 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/Token.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/Token.java
@@ -70,6 +70,9 @@
}
}
+ /** Special token type used to mark tokens that store empty line indentation */
+ public static final int TokenNameEMPTY_LINE = 10000;
+
/** Position in source of the first character. */
public final int originalStart;
/** Position in source of the last character (this position is included in the token). */
@@ -276,6 +279,8 @@
}
public String toString(String source) {
+ if (this.tokenType == TokenNameEMPTY_LINE)
+ return ""; //$NON-NLS-1$
return source.substring(this.originalStart, this.originalEnd + 1);
}
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
index 4d70102..51c8b38 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapPreparator.java
@@ -740,45 +740,25 @@
@Override
protected boolean token(Token token, int index) {
- int lineBreaks = getLineBreaksBetween(getPrevious(), token);
- if (index > WrapPreparator.this.importsStart && index < WrapPreparator.this.importsEnd) {
- lineBreaks = lineBreaks > 1 ? (this.options2.blank_lines_between_import_groups + 1) : 0;
- } else {
- lineBreaks = Math.min(lineBreaks, this.options2.number_of_empty_lines_to_preserve + 1);
- }
+ boolean isBetweenImports = index > WrapPreparator.this.importsStart && index < WrapPreparator.this.importsEnd;
+ int lineBreaks = getLineBreaksToPreserve(getPrevious(), token, isBetweenImports);
if (lineBreaks <= getLineBreaksBefore())
return true;
- if (!this.options2.join_wrapped_lines && token.isWrappable() && lineBreaks == 1) {
- token.breakBefore();
+ if (lineBreaks == 1) {
+ if ((!this.options2.join_wrapped_lines && token.isWrappable()) || index == 0)
+ token.breakBefore();
} else if (lineBreaks > 1) {
- if (index == 0)
- lineBreaks--;
token.putLineBreaksBefore(lineBreaks);
}
return true;
}
- private int getLineBreaksBetween(Token token1, Token token2) {
- if (token1 != null) {
- List<Token> structure1 = token1.getInternalStructure();
- if (structure1 != null && !structure1.isEmpty())
- token1 = structure1.get(structure1.size() - 1);
- }
- List<Token> structure2 = token2.getInternalStructure();
- if (structure2 != null && !structure2.isEmpty())
- token2 = structure2.get(0);
- int lineBreaks = WrapPreparator.this.tm.countLineBreaksBetween(token1, token2);
- if (token1 == null)
- lineBreaks++;
- return lineBreaks;
- }
});
Token last = this.tm.get(this.tm.size() - 1);
last.clearLineBreaksAfter();
- int endingBreaks = this.tm.countLineBreaksBetween(last, null);
- endingBreaks = Math.min(endingBreaks, this.options.number_of_empty_lines_to_preserve);
+ int endingBreaks = getLineBreaksToPreserve(last, null, false);
if (endingBreaks > 0) {
last.putLineBreaksAfter(endingBreaks);
} else if ((this.kind & CodeFormatter.K_COMPILATION_UNIT) != 0
@@ -787,6 +767,29 @@
}
}
+ int getLineBreaksToPreserve(Token token1, Token token2, boolean isBetweenImports) {
+ if (token1 != null) {
+ List<Token> structure = token1.getInternalStructure();
+ if (structure != null && !structure.isEmpty())
+ token1 = structure.get(structure.size() - 1);
+ }
+ if (token2 != null) {
+ List<Token> structure = token2.getInternalStructure();
+ if (structure != null && !structure.isEmpty())
+ token2 = structure.get(0);
+ }
+ int lineBreaks = WrapPreparator.this.tm.countLineBreaksBetween(token1, token2);
+ if (isBetweenImports)
+ return lineBreaks > 1 ? (this.options.blank_lines_between_import_groups + 1) : 0;
+
+ int toPreserve = this.options.number_of_empty_lines_to_preserve;
+ if (token1 != null && token2 != null)
+ toPreserve++; // n empty lines = n+1 line breaks, except for file start and end
+ if (token1 != null && token1.tokenType == Token.TokenNameEMPTY_LINE)
+ toPreserve--;
+ return Math.min(lineBreaks, toPreserve);
+ }
+
private void wrapComments() {
CommentWrapExecutor commentWrapper = new CommentWrapExecutor(this.tm, this.options);
boolean isNLSTagInLine = false;