Bug 563487 - [formatter] Wrong indentation with "Format edited lines"
save action in lambda body after wrap

Change-Id: I00627614a9b18fedd62b8de4309a05380a5f0a44
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 1fd9b9a..84dab8e 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
@@ -13126,4 +13126,73 @@
 		"	}\n" +
 		"}");
 }
+public void testBug563487a() {
+	formatSource(
+		"class A {\n" +
+		"	protected void f() {\n" +
+		"		cccccccccccccc\n" +
+		"				// \n" +
+		"				.forEach(c -> {\n" +
+		"					aaaaaa();\n" +
+		"[#					bbbbbb();#]\n" +
+		"				});\n" +
+		"	}\n" +
+		"}",
+		"class A {\n" +
+		"	protected void f() {\n" +
+		"		cccccccccccccc\n" +
+		"				// \n" +
+		"				.forEach(c -> {\n" +
+		"					aaaaaa();\n" +
+		"					bbbbbb();\n" +
+		"				});\n" +
+		"	}\n" +
+		"}");
+}
+public void testBug563487b() {
+	formatSource(
+		"class A {\n" +
+		"	protected void f() {\n" +
+		"		cccccccccccccc\n" +
+		"				// \n" +
+		"					.forEach(c -> {\n" +
+		"						aaaaaa();\n" +
+		"[#					bbbbbb();#]\n" +
+		"					});\n" +
+		"	}\n" +
+		"}",
+		"class A {\n" +
+		"	protected void f() {\n" +
+		"		cccccccccccccc\n" +
+		"				// \n" +
+		"					.forEach(c -> {\n" +
+		"						aaaaaa();\n" +
+		"					bbbbbb();\n" +
+		"					});\n" +
+		"	}\n" +
+		"}");
+}
+public void testBug563487c() {
+	formatSource(
+		"class A {\n" +
+		"protected void f() {\n" +
+		"cccccccccccccc\n" +
+		"		// \n" +
+		"		.forEach(c -> {\n" +
+		"			aaaaaa();\n" +
+		"[#			bbbbbb();#]\n" +
+		"		});\n" +
+		"}\n" +
+		"}",
+		"class A {\n" +
+		"protected void f() {\n" +
+		"cccccccccccccc\n" +
+		"		// \n" +
+		"		.forEach(c -> {\n" +
+		"			aaaaaa();\n" +
+		"			bbbbbb();\n" +
+		"		});\n" +
+		"}\n" +
+		"}");
+}
 }
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatter.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatter.java
index 5afa975..6d94c68 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatter.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/DefaultCodeFormatter.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+ * Copyright (c) 2000, 2020 IBM Corporation and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -416,7 +416,7 @@
 	private void prepareLineBreaks() {
 		LineBreaksPreparator breaksPreparator = new LineBreaksPreparator(this.tokenManager, this.workingOptions);
 		this.astRoot.accept(breaksPreparator);
-		breaksPreparator.finishUp(this.formatRegions);
+		breaksPreparator.finishUp();
 		this.astRoot.accept(new OneLineEnforcer(this.tokenManager, this.workingOptions));
 	}
 
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 9add4c0..d6e62fe 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
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2014, 2019 Mateusz Matela and others.
+ * Copyright (c) 2014, 2020 Mateusz Matela and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -85,7 +85,6 @@
 import org.eclipse.jdt.internal.formatter.DefaultCodeFormatterOptions.Alignment;
 import org.eclipse.jdt.internal.formatter.Token.WrapMode;
 import org.eclipse.jdt.internal.formatter.Token.WrapPolicy;
-import org.eclipse.jface.text.IRegion;
 
 public class LineBreaksPreparator extends ASTVisitor {
 	final private TokenManager tm;
@@ -808,37 +807,13 @@
 			this.tm.get(lastIndex + 1).unindent();
 	}
 
-	public void finishUp(List<IRegion> regions) {
+	public void finishUp() {
 		// the visits only noted where indents increase and decrease,
-		// now prepare actual indent values, preserving indents outside formatting regions
-		int currentIndent = this.options.initial_indentation_level * this.options.indentation_size;
-		Token previous = null;
+		// now prepare actual indent values
+		int currentIndent = this.options.initial_indentation_level;
 		for (Token token : this.tm) {
-			if (isFixedLineStart(token, previous, regions)) {
-				currentIndent = this.tm.findSourcePositionInLine(token.originalStart);
-			} else {
-				currentIndent = Math.max(currentIndent + token.getIndent() * this.options.indentation_size, 0);
-			}
-			token.setIndent(currentIndent);
-			previous = token;
+			currentIndent += token.getIndent();
+			token.setIndent(currentIndent * this.options.indentation_size);
 		}
 	}
-
-	private boolean isFixedLineStart(Token token, Token previous, List<IRegion> regions) {
-		if (previous == null && this.options.initial_indentation_level >0)
-			return false; // must be handling ast rewrite
-		if (previous != null && this.tm.countLineBreaksBetween(previous, token) == 0)
-			return false;
-		if (token.getLineBreaksBefore() == 0 && (previous == null || previous.getLineBreaksAfter() == 0))
-			return false;
-		int lineStart = token.originalStart;
-		char c;
-		while (lineStart > 0 && (c = this.tm.charAt(lineStart - 1)) != '\r' && c != '\n')
-			lineStart--;
-		for (IRegion r : regions) {
-			if (token.originalStart >= r.getOffset() && lineStart <= r.getOffset() + r.getLength())
-				return false;
-		}
-		return true;
-	}
 }
diff --git a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
index 276da6c..b347880 100644
--- a/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
+++ b/org.eclipse.jdt.core/formatter/org/eclipse/jdt/internal/formatter/linewrap/WrapExecutor.java
@@ -232,6 +232,7 @@
 		private ArrayDeque<Token> stack = new ArrayDeque<>();
 		private int initialIndent;
 		private int currentIndent;
+		private int fixedIndentDelta;
 		private WrapInfo nextWrap;
 
 		public WrapsApplier() {
@@ -297,7 +298,13 @@
 				setIndent(token, getWrapIndent(token));
 				this.stack.push(token);
 			} else if (this.stack.isEmpty()) {
-				this.initialIndent = token.getIndent();
+				if (isFixedLineStart(token, index)) {
+					int fixedIndent = this.tm2.findSourcePositionInLine(token.originalStart);
+					this.initialIndent = fixedIndent;
+					this.fixedIndentDelta = fixedIndent - token.getIndent();
+				} else {
+					this.initialIndent = Math.max(0, token.getIndent() + this.fixedIndentDelta);
+				}
 				WrapExecutor.this.wrapSearchResults.clear();
 			}
 
@@ -305,6 +312,21 @@
 			setIndent(token, this.currentIndent);
 			this.nextWrap = findWrapsCached(index, this.currentIndent).nextWrap;
 		}
+
+		private boolean isFixedLineStart(Token token, int index) {
+			if (WrapExecutor.this.options.initial_indentation_level > 0)
+				return false; // must be handling ast rewrite
+			if (index > 0 && this.tm2.countLineBreaksBetween(getPrevious(), token) == 0)
+				return false;
+			if (isWrapInsideFormatRegion(index))
+				return false;
+			int start = token.originalStart;
+			boolean inDisableFormat = this.tm2.getDisableFormatTokenPairs().stream()
+					.anyMatch(p -> p[0].originalStart <= start && p[1].originalStart >= start);
+			if (inDisableFormat)
+				return false;
+			return true;
+		}
 	}
 
 	private class NLSTagHandler extends TokenTraverser {
@@ -679,7 +701,7 @@
 	}
 
 	boolean isWrapInsideFormatRegion(int tokenIndex) {
-		int pos1 = this.tm.get(tokenIndex - 1).originalEnd;
+		int pos1 = tokenIndex == 0 ? 0 : this.tm.get(tokenIndex - 1).originalEnd;
 		int pos2 = this.tm.get(tokenIndex).originalStart;
 		return this.regions.stream().anyMatch(r -> (pos1 >= r.getOffset() && pos1 < r.getOffset() + r.getLength())
 				|| (pos2 >= r.getOffset() && pos2 < r.getOffset() + r.getLength()));