Bug 577727: Fix select line start

Change-Id: I16e74319b9e3d61b020ff260661836795300c8e1
Signed-off-by: Stephan Wahlbrink <sw@wahlbrink.eu>
Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.text/+/188716
Reviewed-by: Mickael Istria <mistria@redhat.com>
Tested-by: Platform Bot <platform-bot@eclipse.org>
diff --git a/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF b/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF
index 665d367..47dd5a5 100644
--- a/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@
 Bundle-ManifestVersion: 2
 Bundle-Name: %Plugin.name
 Bundle-SymbolicName: org.eclipse.ui.editors.tests;singleton:=true
-Bundle-Version: 3.12.300.qualifier
+Bundle-Version: 3.12.400.qualifier
 Bundle-Vendor: %Plugin.providerName
 Bundle-Localization: plugin
 Export-Package: org.eclipse.ui.editors.tests
diff --git a/org.eclipse.ui.editors.tests/pom.xml b/org.eclipse.ui.editors.tests/pom.xml
index fee685a..dbbbfe8 100644
--- a/org.eclipse.ui.editors.tests/pom.xml
+++ b/org.eclipse.ui.editors.tests/pom.xml
@@ -19,7 +19,7 @@
   </parent>
   <groupId>org.eclipse.ui</groupId>
   <artifactId>org.eclipse.ui.editors.tests</artifactId>
-  <version>3.12.300-SNAPSHOT</version>
+  <version>3.12.400-SNAPSHOT</version>
   <packaging>eclipse-test-plugin</packaging>
   <properties>
   	<testSuite>${project.artifactId}</testSuite>
diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretNavigationTest.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretNavigationTest.java
new file mode 100644
index 0000000..eee388d
--- /dev/null
+++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretNavigationTest.java
@@ -0,0 +1,129 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ ********************************************************************************/
+package org.eclipse.ui.editors.tests;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.core.filesystem.EFS;
+
+import org.eclipse.core.runtime.CoreException;
+
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IMultiTextSelection;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.MultiTextSelection;
+import org.eclipse.jface.text.Region;
+
+import org.eclipse.ui.PartInitException;
+import org.eclipse.ui.PlatformUI;
+import org.eclipse.ui.ide.IDE;
+
+import org.eclipse.ui.texteditor.AbstractTextEditor;
+import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
+
+/*
+ * Note: this test would better fit in the org.eclipse.ui.workbench.texteditor bundle, but initializing
+ * and editor from this bundle is quite tricky without the IDE and EFS utils.
+ */
+public class TextMultiCaretNavigationTest {
+
+	private static File file;
+	private static AbstractTextEditor editor;
+	private static StyledText widget;
+
+	@Before
+	public void setUpBeforeClass() throws IOException, PartInitException, CoreException {
+		file = File.createTempFile(TextMultiCaretNavigationTest.class.getName(), ".txt");
+		Files.write(file.toPath(), "  abc\n    1234\nxyz".getBytes());
+		editor = (AbstractTextEditor)IDE.openEditorOnFileStore(PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(), EFS.getStore(file.toURI()));
+		widget = (StyledText) editor.getAdapter(Control.class);
+	}
+
+	@After
+	public void tearDown() {
+		editor.dispose();
+		file.delete();
+	}
+
+
+	@Test
+	public void testShiftHome() {
+		IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
+		editor.getSelectionProvider().setSelection(new MultiTextSelection(document,
+				new IRegion[] { new Region(5, 0), new Region(14, 0), new Region(18, 0), }));
+		assertEquals(5, widget.getCaretOffset());
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run();
+		IMultiTextSelection selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection();
+		assertArrayEquals(new IRegion[] { new Region(2, 3), new Region(10, 4), new Region(15, 3) },
+				selection.getRegions());
+		assertEquals(2, widget.getCaretOffset());
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run();
+		selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection();
+		assertArrayEquals(new IRegion[] { new Region(0, 5), new Region(6, 8), new Region(15, 3) },
+				selection.getRegions());
+		assertEquals(0, widget.getCaretOffset());
+	}
+
+	@Test
+	public void testShiftEnd() {
+		IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
+		editor.getSelectionProvider().setSelection(new MultiTextSelection(document,
+				new IRegion[] { new Region(0, 0), new Region(6, 0), new Region(15, 0), }));
+		assertEquals(0, widget.getCaretOffset());
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END).run();
+		IMultiTextSelection selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection();
+		assertArrayEquals(new IRegion[] { new Region(0, 5), new Region(6, 8), new Region(15, 3) },
+				selection.getRegions());
+		assertEquals(5, widget.getCaretOffset());
+	}
+
+	@Test
+	public void testShiftEndHomeHome() {
+		IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
+		editor.getSelectionProvider().setSelection(new MultiTextSelection(document,
+				new IRegion[] { new Region(0, 0), new Region(6, 0), new Region(15, 0), }));
+		assertEquals(0, widget.getCaretOffset());
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END).run();
+		IMultiTextSelection selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection();
+		assertArrayEquals(new IRegion[] { new Region(0, 5), new Region(6, 8), new Region(15, 3) },
+				selection.getRegions());
+		assertEquals(5, widget.getCaretOffset());
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run();
+		selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection();
+		assertArrayEquals(new IRegion[] { new Region(0, 2), new Region(6, 4), new Region(15, 0) },
+				selection.getRegions());
+		assertEquals(2, widget.getCaretOffset()); // Bug 577727
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run();
+		selection = (IMultiTextSelection) editor.getSelectionProvider().getSelection();
+		assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(6, 0), new Region(15, 0) },
+				selection.getRegions());
+		assertEquals(0, widget.getCaretOffset());
+	}
+
+}
diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java
index 9449786..2ae93a4 100644
--- a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java
+++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextNavigationTest.java
@@ -1,5 +1,5 @@
 /*******************************************************************************
- * Copyright (c) 2021 Red Hat Inc.
+ * Copyright (c) 2021 Red Hat Inc. and others.
  *
  * This program and the accompanying materials
  * are made available under the terms of the Eclipse Public License 2.0
@@ -116,6 +116,30 @@
 	}
 
 	@Test
+	public void testShiftEndHomeHome() {
+		editor.getSelectionProvider().setSelection(new TextSelection(0, 0));
+		assertEquals(0, widget.getCaretOffset());
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_END).run();
+		ITextSelection selection = (ITextSelection) editor.getSelectionProvider().getSelection();
+		assertEquals(0, selection.getOffset());
+		assertEquals(5, selection.getLength());
+		assertEquals(5, widget.getCaretOffset());
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run();
+		selection = (ITextSelection) editor.getSelectionProvider().getSelection();
+		assertEquals(0, selection.getOffset());
+		assertEquals(2, selection.getLength());
+		assertEquals(2, widget.getCaretOffset()); // Bug 577727
+
+		editor.getAction(ITextEditorActionDefinitionIds.SELECT_LINE_START).run();
+		selection = (ITextSelection) editor.getSelectionProvider().getSelection();
+		assertEquals(0, selection.getOffset());
+		assertEquals(0, selection.getLength());
+		assertEquals(0, widget.getCaretOffset());
+	}
+
+	@Test
 	public void testEndHomeRevealCaret() {
 		editor.getSelectionProvider().setSelection(new TextSelection(0, 0));
 		IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
index 29fc5c4..6ae6eb0 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
@@ -1352,9 +1352,6 @@
 					line = st.getText(lineOffset, end);
 				}
 
-				// Remember current selection
-				Point oldSelection = new Point(offset, offset + length);
-
 				// The new caret position
 				int newCaretOffset = -1;
 
@@ -1367,16 +1364,12 @@
 					newCaretOffset = lineOffset;
 				}
 
-				newSelection.add(new Point(
-						fDoSelect
-								? (caretAtBeginningOfSelection ? Math.max(oldSelection.x, oldSelection.y)
-										: Math.min(oldSelection.x, oldSelection.y))
-								: newCaretOffset,
-					newCaretOffset));
+				newSelection.add(
+						new Point(fDoSelect ? (caretAtBeginningOfSelection ? offset + length : offset) : newCaretOffset,
+								newCaretOffset));
 			}
 			st.setSelectionRanges(newSelection.stream().flatMapToInt(
-					p -> IntStream.of(Math.max(p.x, p.y), -Math.abs(p.x - p.y))) // negative length to put cursor at
-																					// beginning of selection
+					p -> IntStream.of(p.x, p.y - p.x))
 					.toArray());
 			if (newSelection.size() == 1) {
 				st.showSelection();