Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDirk Steinkamp2022-03-24 00:18:25 +0000
committerMickael Istria2022-03-25 12:44:29 +0000
commit150da4b2675d18f53a2087b74f6e6bed65ce0472 (patch)
tree4fad9af8105ae50ee6b1b458eb786f52af58d7af
parent2f55fffaedd81f5cdd632687fb0f9a6c2eeea0b7 (diff)
downloadeclipse.platform.text-master.tar.gz
eclipse.platform.text-master.tar.xz
eclipse.platform.text-master.zip
Bug 576377 - Provide shortcuts/commands for incrementalHEADmaster
multiselection/multiple carets in text editors Added two new commands that add a caret/selection in the line above/below and the reverse operation. This operation is similar to what is possible with block selection, but currently treats positions after end of line differently (currently no automatic padding to fill up shorter lines is performed). moveOffsetByLines now respects the visual positions instead of purely working with document offsets, which improves usability in case of a different number of leading tabs in adjacent lines. Change-Id: I01563e6ab148655d0dd6c34e2a89776a1ea0f9a9 Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.text/+/192202 Tested-by: Mickael Istria <mistria@redhat.com> Reviewed-by: Mickael Istria <mistria@redhat.com>
-rw-r--r--org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java297
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.properties4
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.xml30
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java55
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java59
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java59
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java7
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java7
8 files changed, 482 insertions, 36 deletions
diff --git a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java
index 1f700b1672c..85b43634ae8 100644
--- a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java
+++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java
@@ -56,10 +56,12 @@ public class TextMultiCaretSelectionCommandsTest {
private static final String ADD_ALL_MATCHES_TO_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.addAllMatchesToMultiSelection";
private static final String MULTI_SELECTION_UP = "org.eclipse.ui.edit.text.select.selectMultiSelectionUp";
private static final String STOP_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.stopMultiSelection";
+ private static final String MULTI_CARET_DOWN = "org.eclipse.ui.edit.text.select.multiCaretDown";
+ private static final String MULTI_CARET_UP = "org.eclipse.ui.edit.text.select.multiCaretUp";
private static final String LINE_1 = "private static String a;\n";
- private static final String LINE_2 = "private static String b;\n";
- private static final String LINE_3 = "private static String c;\n";
+ private static final String LINE_2 = "private static String b; // this is a little longer\n";
+ private static final String LINE_3 = "\t\tprivate static String c;\n";
private static final String LINE_4 = "private static String d";
private static final int L1_LEN = LINE_1.length();
@@ -101,7 +103,7 @@ public class TextMultiCaretSelectionCommandsTest {
executeCommand(MULTI_SELECTION_DOWN);
assertEquals(7, widget.getCaretOffset());
- assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN, 7) },
+ assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7) },
getSelection());
executeCommand(STOP_MULTI_SELECTION);
@@ -130,7 +132,7 @@ public class TextMultiCaretSelectionCommandsTest {
executeCommand(MULTI_SELECTION_DOWN);
assertEquals(L1_LEN + 14, widget.getCaretOffset());
- assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6) },
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) },
getSelection());
}
@@ -153,12 +155,12 @@ public class TextMultiCaretSelectionCommandsTest {
// It is important here to build up the selection in steps, so the
// handler can determine an anchor region
executeCommand(MULTI_SELECTION_DOWN);
- assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6) },
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) },
getSelection());
executeCommand(MULTI_SELECTION_DOWN);
- assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6),
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6),
new Region(L1_LEN + L2_LEN + L3_LEN + 8, 6) }, getSelection());
}
@@ -241,7 +243,7 @@ public class TextMultiCaretSelectionCommandsTest {
executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION);
assertEquals(7, widget.getCaretOffset());
- assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN, 7),
+ assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7),
new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection());
}
@@ -253,7 +255,7 @@ public class TextMultiCaretSelectionCommandsTest {
executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION);
assertEquals(7, widget.getCaretOffset());
- assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN, 7),
+ assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7),
new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection());
}
@@ -276,7 +278,7 @@ public class TextMultiCaretSelectionCommandsTest {
executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION);
assertEquals(7, widget.getCaretOffset());
- assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN, 7),
+ assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN + 2, 7),
new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection());
}
@@ -292,8 +294,7 @@ public class TextMultiCaretSelectionCommandsTest {
}
@Test
- public void testMultiSelectionUp_withSingleSelectionAndNoPreviousMatch_doesNothing()
- throws Exception {
+ public void testMultiSelectionUp_withSingleSelectionAndNoPreviousMatch_doesNothing() throws Exception {
setSelection(new IRegion[] { new Region(8, 6) });
assertEquals(14, widget.getCaretOffset());
@@ -316,7 +317,8 @@ public class TextMultiCaretSelectionCommandsTest {
@Test
public void testMultiSelectionUp_withThreeSelections_removesThirdSelection() throws Exception {
- setSelection(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6) });
+ setSelection(
+ new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) });
assertEquals(14, widget.getCaretOffset());
executeCommand(MULTI_SELECTION_UP);
@@ -326,8 +328,7 @@ public class TextMultiCaretSelectionCommandsTest {
}
@Test
- public void testMultiSelectionUp_withTwoSelectionsAndAnchorAbove_reducesSelection()
- throws Exception {
+ public void testMultiSelectionUp_withTwoSelectionsAndAnchorAbove_reducesSelection() throws Exception {
setSelection(new IRegion[] { new Region(8, 6) });
// It is important here to build up the selection in steps, so the
// handler can determine an anchor region
@@ -340,19 +341,18 @@ public class TextMultiCaretSelectionCommandsTest {
}
@Test
- public void testMultiSelectionUp_withTwoSelectionsAndAnchorBelow_extendsSelection()
- throws Exception {
- setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 8, 6) });
+ public void testMultiSelectionUp_withTwoSelectionsAndAnchorBelow_extendsSelection() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 10, 6) });
// It is important here to build up the selection in steps, so the
// handler can determine an anchor region
executeCommand(MULTI_SELECTION_UP);
- assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6) },
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) },
getSelection());
executeCommand(MULTI_SELECTION_UP);
assertArrayEquals(
- new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6) },
+ new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 10, 6) },
getSelection());
}
@@ -407,6 +407,265 @@ public class TextMultiCaretSelectionCommandsTest {
assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection());
}
+ @Test
+ public void testMultiCaretDown_withCaret_addsCaretsInNextLines() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 0) });
+ assertEquals(0, widget.getCaretOffset());
+
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0) }, getSelection());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretDown_withTwoCaretsAndAnchorRegionBelow_removesFirstCaret() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN, 0) });
+ assertEquals(L1_LEN, widget.getCaretOffset());
+
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0) }, getSelection());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(L1_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0) }, getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretDown_withSingleSelection_addsSelectionInNextLine() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 3) });
+ assertEquals(3, widget.getCaretOffset());
+
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(3, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 3), new Region(L1_LEN, 3) }, getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(3, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(3, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretDown_withSingleCaretAtEndOfLongerLine_addsCaretAtEndOfNextLine() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) });
+ assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN, 0), new Region(L1_LEN + L2_LEN + L3_LEN, 0) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretDown_withSingleCaretInLineAboveLineWithTabs_addsCaretInNextLineRespectingTabs()
+ throws Exception {
+ widget.setTabs(4); // Make default explicit
+ setSelection(new IRegion[] { new Region(L1_LEN + 8, 0) });
+ assertEquals(L1_LEN + 8, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(L1_LEN + 8, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0), new Region(L1_LEN + L2_LEN + 2, 0) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN + 8, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretDown_withSingleCaretInLineWithTabs_addsCaretInNextLineRespectingTabs() throws Exception {
+ widget.setTabs(4); // Make default explicit
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) });
+ assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset());
+ assertArrayEquals(
+ new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0), new Region(L1_LEN + L2_LEN + L3_LEN + 8, 0) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretDown_withSingleCaretAtEndOfText_doesNotChangeCaret() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN, 0) });
+ assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN, 0) }, getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN + L2_LEN + L3_LEN + L4_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + L4_LEN, 0) }, getSelection());
+ }
+
+ /////////////////////////////////////////////////////
+ @Test
+ public void testMultiCaretUp_withCaret_addsCaretsInPreviousLines() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) });
+ assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset());
+
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(L1_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) }, getSelection());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretUp_withTwoCaretsAndAnchorRegionAbove_removesLastCaret() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 0) });
+ assertEquals(0, widget.getCaretOffset());
+
+ executeCommand(MULTI_CARET_DOWN);
+
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(L1_LEN, 0) }, getSelection());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretUp_withSingleSelection_addsSelectionInPreviousLine() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN, 3) });
+ assertEquals(L1_LEN + 3, widget.getCaretOffset());
+
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(3, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 3), new Region(L1_LEN, 3) }, getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(3, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(3, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretUp_withSingleCaretAtEndOfLongerLine_addsCaretAtEndOfPreviousLine() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN, 0) });
+ assertEquals(L1_LEN + L2_LEN, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(L1_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0), new Region(L1_LEN + L2_LEN, 0) }, getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretUp_withSingleCaretInLineBelowLineWithTabs_addsCaretInPreviousLineRespectingTabs()
+ throws Exception {
+ widget.setTabs(4); // Make default explicit
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + 8, 0) });
+ assertEquals(L1_LEN + L2_LEN + L3_LEN + 8, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset());
+ assertArrayEquals(
+ new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0), new Region(L1_LEN + L2_LEN + L3_LEN + 8, 0) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretUp_withSingleCaretInLineWithTabs_addsCaretInPreviousLineRespectingTabs()
+ throws Exception {
+ widget.setTabs(4); // Make default explicit
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 2, 0) });
+ assertEquals(L1_LEN + L2_LEN + 2, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(L1_LEN + 8, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0), new Region(L1_LEN + L2_LEN + 2, 0) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(L1_LEN + 8, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 0) }, getSelection());
+ }
+
+ @Test
+ public void testMultiCaretUp_withSingleCaretAtBeginningOfText_doesNotChangeCaret() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 0) });
+ assertEquals(0, widget.getCaretOffset());
+
+ widget.setSize(800, 500); // make sure the widget is not size (0,0)
+ executeCommand(MULTI_CARET_UP);
+
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection());
+ }
+
// Helper methods
private void executeCommand(String commandId) throws Exception {
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.properties b/org.eclipse.ui.workbench.texteditor/plugin.properties
index 2e204f51a14..f647d810d8d 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.properties
+++ b/org.eclipse.ui.workbench.texteditor/plugin.properties
@@ -167,6 +167,10 @@ command.selectMultiSelectionUp.description = Search next matching region above a
command.selectMultiSelectionUp.name = Multi selection up relative to anchor selection
command.stopMultiSelection.description = Unselects all multi-selections returning to a single cursor
command.stopMultiSelection.name = End multi-selection
+command.multiCaretUp.description=Add a new caret/multi selection above the current line, or remove the last caret/multi selection
+command.multiCaretUp.name=Multi caret up
+command.multiCaretDown.description=Add a new caret/multi selection below the current line, or remove the first caret/multi selection
+command.multiCaretDown.name=Multi caret down
command.selectWordNext.description = Select the next word
command.selectWordNext.name = Select Next Word
command.selectWordPrevious.description = Select the previous word
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.xml b/org.eclipse.ui.workbench.texteditor/plugin.xml
index 9dea32dcd87..9c861c23c22 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.xml
+++ b/org.eclipse.ui.workbench.texteditor/plugin.xml
@@ -329,6 +329,18 @@
id="org.eclipse.ui.edit.text.select.stopMultiSelection">
</command>
<command
+ name="%command.multiCaretUp.name"
+ description="%command.multiCaretUp.description"
+ categoryId="org.eclipse.ui.category.textEditor"
+ id="org.eclipse.ui.edit.text.select.multiCaretUp">
+ </command>
+ <command
+ name="%command.multiCaretDown.name"
+ description="%command.multiCaretDown.description"
+ categoryId="org.eclipse.ui.category.textEditor"
+ id="org.eclipse.ui.edit.text.select.multiCaretDown">
+ </command>
+ <command
name="%command.deletePrevious.name"
description="%command.deletePrevious.description"
categoryId="org.eclipse.ui.category.textEditor"
@@ -1422,6 +1434,24 @@
</with>
</enabledWhen>
</handler>
+ <handler
+ class="org.eclipse.ui.internal.texteditor.multiselection.MultiCaretUpHandler"
+ commandId="org.eclipse.ui.edit.text.select.multiCaretUp">
+ <enabledWhen>
+ <with variable="activeEditor">
+ <adapt type="org.eclipse.ui.texteditor.ITextEditor"/>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.ui.internal.texteditor.multiselection.MultiCaretDownHandler"
+ commandId="org.eclipse.ui.edit.text.select.multiCaretDown">
+ <enabledWhen>
+ <with variable="activeEditor">
+ <adapt type="org.eclipse.ui.texteditor.ITextEditor"/>
+ </with>
+ </enabledWhen>
+ </handler>
</extension>
<extension
point="org.eclipse.ui.menus">
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java
index e8f7a40eab1..986afd6c063 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java
@@ -18,6 +18,7 @@ import java.util.Arrays;
import java.util.List;
import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Control;
import org.eclipse.core.commands.AbstractHandler;
@@ -32,6 +33,8 @@ import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IMultiTextSelection;
import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITextViewer;
+import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.MultiTextSelection;
import org.eclipse.jface.text.Region;
@@ -61,6 +64,11 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
private ExecutionEvent event;
private ITextEditor textEditor;
private IDocument document;
+ /**
+ * SourceViewer Might be <code>null</code>, if {@link #textEditor} doesn't
+ * implement this interface.
+ */
+ private ITextViewerExtension5 sourceViewer;
/**
* This method needs to be overwritten from subclasses to handle the event.
@@ -264,7 +272,7 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
}
}
- private IRegion createRegionIfValid(int offset, int length) {
+ protected IRegion createRegionIfValid(int offset, int length) {
if ((offset < 0) || (offset > document.getLength()))
return null;
@@ -294,24 +302,43 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
return regions;
}
- private int offsetInNextLine(int offset) throws BadLocationException {
+ protected int offsetInNextLine(int offset) throws BadLocationException {
return moveOffsetByLines(offset, 1);
}
- private int offsetInPreviousLine(int offset) throws BadLocationException {
+ protected int offsetInPreviousLine(int offset) throws BadLocationException {
return moveOffsetByLines(offset, -1);
}
private int moveOffsetByLines(int offset, int lineDelta) throws BadLocationException {
- int lineNo = document.getLineOfOffset(offset);
- int newLineNo = lineNo + lineDelta;
+ int newLineNo = document.getLineOfOffset(offset) + lineDelta;
if ((newLineNo < 0) || (newLineNo >= document.getNumberOfLines()))
return -1;
- int newLineOffset = document.getLineOffset(newLineNo);
- int delta = offset - document.getLineOffset(lineNo);
+ int newOffset;
+ if (sourceViewer == null) {
+ // we don't have a sourceViewer and thus as a fallback
+ // assume the widget offsets are identical to the document offsets
+ newOffset = moveWidgetOffsetByLines(offset, lineDelta);
+ } else {
+ int widgetOffset = sourceViewer.modelOffset2WidgetOffset(offset);
+ int newWidgetOffset = moveWidgetOffsetByLines(widgetOffset, lineDelta);
+ newOffset = sourceViewer.widgetOffset2ModelOffset(newWidgetOffset);
+ }
+ if (newOffset == -1) {
+ return endOfLineOffset(newLineNo);
+ }
+ return newOffset;
+ }
+
+ private int moveWidgetOffsetByLines(int widgetOffset, int lineDelta) throws BadLocationException {
+ Point location = getWidget().getLocationAtOffset(widgetOffset);
+ Point newLocation = new Point(location.x, location.y + lineDelta * getWidget().getLineHeight(widgetOffset));
+ return getWidget().getOffsetAtPoint(newLocation);
+ }
- return newLineOffset + delta;
+ private int endOfLineOffset(int lineNo) throws BadLocationException {
+ return document.getLineOffset(lineNo) + document.getLineInformation(lineNo).getLength();
}
private boolean initFrom(ExecutionEvent event) {
@@ -319,7 +346,8 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
initTextEditor();
if (textEditor == null)
return false;
- document = getDocument();
+ initDocument();
+ initSourceViewer();
initAnchorRegion();
return true;
}
@@ -329,8 +357,13 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
textEditor = Adapters.adapt(editor, ITextEditor.class);
}
- private IDocument getDocument() {
- return textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
+ private void initDocument() {
+ document = textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
+ }
+
+ private void initSourceViewer() {
+ ITextViewer textViewer = textEditor.getAdapter(ITextViewer.class);
+ sourceViewer = Adapters.adapt(textViewer, ITextViewerExtension5.class);
}
private IRegion[] toArray(List<IRegion> regions) {
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java
new file mode 100644
index 00000000000..6d445421550
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretDownHandler.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Dirk Steinkamp
+ *
+ * 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
+ *
+ * Contributors:
+ * Dirk Steinkamp <dirk.steinkamp@gmx.de> - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.internal.texteditor.multiselection;
+
+import org.eclipse.core.commands.ExecutionException;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IRegion;
+
+/**
+ * Handler to change the current set of multi carets/selections downwards. This
+ * might either mean to add a caret/selection below by adding a new
+ * caret/selection at the same line offset in the next line, or reduce the
+ * number of carets/selections by removing the first caret/selection range. This
+ * depends on the selection a multi caret/selection command was invoked with the
+ * first time -- that selection is remembered as an "anchor" to which successive
+ * calls are related as reference selection.<br>
+ */
+public class MultiCaretDownHandler extends AbstractMultiSelectionHandler {
+
+ @Override
+ public void execute() throws ExecutionException {
+ if (selectionIsAboveAnchorRegion()) {
+ removeFirstRegionFromSelection();
+ } else {
+ extendSelectionWithSamePositionInNextLine();
+ }
+ }
+
+ private void extendSelectionWithSamePositionInNextLine() throws ExecutionException {
+ IRegion[] regions = getSelectedRegions();
+ if (regions == null || regions.length == 0) {
+ return;
+ }
+ try {
+ IRegion lastRegion = regions[regions.length - 1];
+ int newOffset = offsetInNextLine(lastRegion.getOffset());
+ IRegion nextLineRegion = createRegionIfValid(newOffset, lastRegion.getLength());
+ selectRegions(addRegion(regions, nextLineRegion));
+ } catch (BadLocationException e) {
+ throw new ExecutionException("Internal error in extendSelectionWithSamePositionInNextLine", e);
+ }
+ }
+
+ private void removeFirstRegionFromSelection() {
+ selectRegions(removeFirstRegionButOne(getSelectedRegions()));
+ }
+}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java
new file mode 100644
index 00000000000..1f12b4cd361
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiCaretUpHandler.java
@@ -0,0 +1,59 @@
+/*******************************************************************************
+ * Copyright (c) 2022 Dirk Steinkamp
+ *
+ * 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
+ *
+ * Contributors:
+ * Dirk Steinkamp <dirk.steinkamp@gmx.de> - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.ui.internal.texteditor.multiselection;
+
+import org.eclipse.core.commands.ExecutionException;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.IRegion;
+
+/**
+ * Handler to change the current set of multi carets/selections upwards. This
+ * might either mean to add a caret/selection above by adding a new
+ * caret/selection at the same line offset in the previous line, or reduce the
+ * number of carets/selections by removing the last caret/selection range. This
+ * depends on the selection a multi caret/selection command was invoked with the
+ * first time -- that selection is remembered as an "anchor" to which successive
+ * calls are related as reference selection.<br>
+ */
+public class MultiCaretUpHandler extends AbstractMultiSelectionHandler {
+
+ @Override
+ public void execute() throws ExecutionException {
+ if (selectionIsBelowAnchorRegion()) {
+ removeLastRegionFromSelection();
+ } else {
+ extendSelectionWithSamePositionInPreviousLine();
+ }
+ }
+
+ private void extendSelectionWithSamePositionInPreviousLine() throws ExecutionException {
+ IRegion[] regions = getSelectedRegions();
+ if (regions == null || regions.length == 0) {
+ return;
+ }
+ try {
+ IRegion firstRegion = regions[0];
+ int newOffset = offsetInPreviousLine(firstRegion.getOffset());
+ IRegion previousLineRegion = createRegionIfValid(newOffset, firstRegion.getLength());
+ selectRegions(addRegion(regions, previousLineRegion));
+ } catch (BadLocationException e) {
+ throw new ExecutionException("Internal error in extendSelectionWithSamePositionInPreviousLine", e);
+ }
+ }
+
+ private void removeLastRegionFromSelection() {
+ selectRegions(removeLastRegionButOne(getSelectedRegions()));
+ }
+}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java
index 678077e20a9..20ed24c66f2 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java
@@ -20,9 +20,10 @@ import org.eclipse.jface.text.IRegion;
/**
* Handler to change the current multi selection downwards. This might either
* mean to extend the selection by adding a match below, or shrink the selection
- * by removing the first selection range. This depends on the selection the
- * command was invoked with the first time -- that selection is remembered as an
- * "anchor" to which successive calls are related as reference selection.<br>
+ * by removing the first selection range. This depends on the selection a multi
+ * caret/selection command was invoked with the first time -- that selection is
+ * remembered as an "anchor" to which successive calls are related as reference
+ * selection.<br>
* If no word is selected, an implicit selection of the word under the cursor is
* performed.
*/
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java
index 8205b3a296c..6a31f556dce 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java
@@ -20,9 +20,10 @@ import org.eclipse.jface.text.IRegion;
/**
* Handler to change the current multi selection upwards. This might either mean
* to extend the selection by adding a match above, or shrink the selection by
- * removing the last selection range. This depends on the selection the command
- * was invoked with the first time -- that selection is remembered as an
- * "anchor" to which successive calls are related as reference selection.<br>
+ * removing the last selection range. This depends on the selection a multi
+ * caret/selection command was invoked with the first time -- that selection is
+ * remembered as an "anchor" to which successive calls are related as reference
+ * selection.<br>
* If no word is selected, an implicit selection of the word under the cursor is
* performed.
*/

Back to the top