Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDirk Steinkamp2022-03-13 18:03:44 +0000
committerMickael Istria2022-03-15 09:56:00 +0000
commit13e3e7a5866fb13f479253382030907238a54ee2 (patch)
treeb2215f6e60d4e9b53550c1cb6e7af8bbf307a0cb
parent579ca5b681fe05936c8272af9109c79c4e4fb1a0 (diff)
downloadeclipse.platform.text-13e3e7a5866fb13f479253382030907238a54ee2.tar.gz
eclipse.platform.text-13e3e7a5866fb13f479253382030907238a54ee2.tar.xz
eclipse.platform.text-13e3e7a5866fb13f479253382030907238a54ee2.zip
Bug 576377 - Provide shortcuts/commands for incremental
multiselection/multiple carets in text editors Expansion of the multi-selection commands to consider the first selection range as an anchor to which then subsequent command calls relate. Thus it's now also possible to create a multi-selection incrementally "upwards" from the initial anchor selection, and also revert downwards. Change-Id: Ica3e444064df9373fee65bd5f4b4bcf2cb146750 Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.text/+/191824 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.java131
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.properties8
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.xml20
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java175
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java9
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java (renamed from org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddNextMatchToMultiSelectionHandler.java)20
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java55
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/RemoveLastMatchFromMultiSelectionHandler.java29
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java1
9 files changed, 340 insertions, 108 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 4ddeb9f2db5..3165209eb5d 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
@@ -52,9 +52,9 @@ import org.eclipse.ui.texteditor.AbstractTextEditor;
* an editor from this bundle is quite tricky without the IDE and EFS utils.
*/
public class TextMultiCaretSelectionCommandsTest {
- private static final String ADD_NEXT_MATCH_TO_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.addNextMatchToMultiSelection";
+ private static final String MULTI_SELECTION_DOWN = "org.eclipse.ui.edit.text.select.selectMultiSelectionDown";
private static final String ADD_ALL_MATCHES_TO_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.addAllMatchesToMultiSelection";
- private static final String REMOVE_LAST_MATCH_FROM_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.removeLastMatchFromMultiSelection";
+ 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 LINE_1 = "private static String a;\n";
@@ -88,16 +88,17 @@ public class TextMultiCaretSelectionCommandsTest {
}
@Test
- public void testAddNextMatch_withFirstIdentifierSelected_addsIdenticalIdentifiersToSelection() throws Exception {
+ public void testMultiSelectionDown_withFirstIdentifierSelected_addsIdenticalIdentifiersToSelection()
+ throws Exception {
setSelection(new IRegion[] { new Region(0, 7) });
assertEquals(7, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
assertEquals(7, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) }, getSelection());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ 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) },
@@ -109,24 +110,24 @@ public class TextMultiCaretSelectionCommandsTest {
}
@Test
- public void testAddNextMatch_withSecondIdentifierSelectedIdentifier_addsNextOccurenceToSelection()
+ public void testMultiSelectionDown_withSecondIdentifierSelectedIdentifier_addsNextOccurenceToSelection()
throws Exception {
setSelection(new IRegion[] { new Region(8, 6) });
assertEquals(14, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
assertEquals(14, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection());
}
@Test
- public void testAddNextMatch_withSelectionInSecondRow_addsIdenticalIdentifierInThirdRowToSelection()
+ public void testMultiSelectionDown_withSelectionInSecondRow_addsIdenticalIdentifierInThirdRowToSelection()
throws Exception {
setSelection(new IRegion[] { new Region(L1_LEN + 8, 6) });
assertEquals(L1_LEN + 14, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ 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) },
@@ -134,67 +135,99 @@ public class TextMultiCaretSelectionCommandsTest {
}
@Test
- public void testAddNextMatch_withCaretInFirstIdentifier_selectsFullIdentifier() throws Exception {
+ public void testMultiSelectionDown_withTwoSelectionsAndAnchorBelow_reducesSelection() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + 8, 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(8, 6), new Region(L1_LEN + 8, 6) }, getSelection());
+
+ executeCommand(MULTI_SELECTION_DOWN);
+
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testMultiSelectionDown_withTwoSelectionsAndAnchorAbove_extendsSelection() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + 8, 6) });
+ // 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) },
+ getSelection());
+
+ executeCommand(MULTI_SELECTION_DOWN);
+
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6),
+ new Region(L1_LEN + L2_LEN + L3_LEN + 8, 6) }, getSelection());
+ }
+
+ // Caret-related tests for ADD_NEXT_MATCH_TO_MULTI_SELECTION
+ // that check how the selection is expanded
+
+ @Test
+ public void testMultiSelectionDown_withCaretInFirstIdentifier_selectsFullIdentifier() throws Exception {
setSelection(new IRegion[] { new Region(1, 0) });
assertEquals(1, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
assertEquals(7, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(0, 7) }, getSelection());
}
@Test
- public void testAddNextMatch_withCaretInSecondIdentifier_selectsFullIdentifier() throws Exception {
+ public void testMultiSelectionDown_withCaretInSecondIdentifier_selectsFullIdentifier() throws Exception {
setSelection(new IRegion[] { new Region(11, 0) });
assertEquals(11, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
assertEquals(14, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection());
}
@Test
- public void testAddNextMatch_withCaretBetweenIdentifierCharAndNonIdentifierChar_selectsFullIdentifier()
+ public void testMultiSelectionDown_withCaretBetweenIdentifierCharAndNonIdentifierChar_selectsFullIdentifier()
throws Exception {
setSelection(new IRegion[] { new Region(23, 0) });
assertEquals(23, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
assertEquals(23, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(22, 1) }, getSelection());
}
@Test
- public void testAddNextMatch_withCaretInSecondRow_selectsFullIdentifier() throws Exception {
+ public void testMultiSelectionDown_withCaretInSecondRow_selectsFullIdentifier() throws Exception {
setSelection(new IRegion[] { new Region(L1_LEN + 11, 0) });
assertEquals(L1_LEN + 11, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
assertEquals(L1_LEN + 14, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6) }, getSelection());
}
@Test
- public void testAddNextMatch_withCaretInIdentifierWithNoFollowingMatch_selectsFullIdentifier() throws Exception {
+ public void testMultiSelectionDown_withCaretInIdentifierWithNoFollowingMatch_selectsFullIdentifier()
+ throws Exception {
setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + 11, 0) });
assertEquals(L1_LEN + L2_LEN + L3_LEN + 11, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
assertEquals(L1_LEN + L2_LEN + L3_LEN + 14, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(L1_LEN + L2_LEN + L3_LEN + 8, 6) }, getSelection());
}
@Test
- public void testAddNextMatch_withCaretAtEndOfDocument_selectsFullIdentifier() throws Exception {
+ public void testMultiSelectionDown_withCaretAtEndOfDocument_selectsFullIdentifier() 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());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_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 - 1, 1) }, getSelection());
@@ -248,50 +281,82 @@ public class TextMultiCaretSelectionCommandsTest {
}
@Test
- public void testRemoveLastMatchFromMultiSelection_withCaretInIdentifier_doesNothing() throws Exception {
+ public void testMultiSelectionUp_withCaretInIdentifier_selectsFullIdentifier() throws Exception {
setSelection(new IRegion[] { new Region(L1_LEN + 11, 0) });
assertEquals(L1_LEN + 11, widget.getCaretOffset());
- executeCommand(REMOVE_LAST_MATCH_FROM_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_UP);
- assertEquals(L1_LEN + 11, widget.getCaretOffset());
- assertArrayEquals(new IRegion[] { new Region(L1_LEN + 11, 0) }, getSelection());
+ assertEquals(L1_LEN + 8 + 6, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6) }, getSelection());
}
@Test
- public void testRemoveLastMatchFromMultiSelection_withSingleSelection_doesNothing() throws Exception {
+ public void testMultiSelectionUp_withSingleSelectionAndNoPreviousMatch_doesNothing()
+ throws Exception {
setSelection(new IRegion[] { new Region(8, 6) });
assertEquals(14, widget.getCaretOffset());
- executeCommand(REMOVE_LAST_MATCH_FROM_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_UP);
assertEquals(14, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection());
}
@Test
- public void testRemoveLastMatchFromMultiSelection_withTwoSelections_removesSecondSelection() throws Exception {
+ public void testMultiSelectionUp_withTwoSelections_removesSecondSelection() throws Exception {
setSelection(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) });
assertEquals(14, widget.getCaretOffset());
- executeCommand(REMOVE_LAST_MATCH_FROM_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_UP);
assertEquals(14, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection());
}
@Test
- public void testRemoveLastMatchFromMultiSelection_withThreeSelections_removesThirdSelection() throws Exception {
+ 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) });
assertEquals(14, widget.getCaretOffset());
- executeCommand(REMOVE_LAST_MATCH_FROM_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_UP);
assertEquals(14, widget.getCaretOffset());
assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection());
}
@Test
+ 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
+ executeCommand(MULTI_SELECTION_DOWN);
+ assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection());
+
+ executeCommand(MULTI_SELECTION_UP);
+
+ assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testMultiSelectionUp_withTwoSelectionsAndAnchorBelow_extendsSelection()
+ throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + L2_LEN + 8, 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) },
+ 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) },
+ getSelection());
+ }
+
+ @Test
public void testStopMultiSelection_withSingleSelection_doesNotChangeSelectionOrCaretOffset() throws Exception {
setSelection(new IRegion[] { new Region(0, 7) });
@@ -330,8 +395,8 @@ public class TextMultiCaretSelectionCommandsTest {
setSelection(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7), new Region(L1_LEN + L2_LEN, 7) });
assertEquals(7, widget.getCaretOffset());
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
- executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+ executeCommand(MULTI_SELECTION_DOWN);
+ executeCommand(MULTI_SELECTION_DOWN);
// TODO How to place the caret at the end without dismissing the
// selection? Should rather be 57
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.properties b/org.eclipse.ui.workbench.texteditor/plugin.properties
index f7bf56102e9..2e204f51a14 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.properties
+++ b/org.eclipse.ui.workbench.texteditor/plugin.properties
@@ -161,10 +161,10 @@ command.selectWindowStart.description = Select to the start of the window
command.selectWindowStart.name = Select Window Start
command.selectAddAllMatchesToMultiSelection.description = Looks for all regions matching the current selection or identifier and adds them to a multi-selection
command.selectAddAllMatchesToMultiSelection.name = Add all matches to multi-selection
-command.selectAddNextMatchToMultiSelection.description = Looks for the next region matching the current selection and adds it to a multi-selection
-command.selectAddNextMatchToMultiSelection.name = Add next match to multi-selection
-command.selectRemoveLastMatchFromMultiSelection.description = Reduces the current matching regions of a multi-selection by one
-command.selectRemoveLastMatchFromMultiSelection.name = Remove last match from multi-selection
+command.selectMultiSelectionDown.description = Search next matching region and add it to the current selection, or remove first element from current multi-selection
+command.selectMultiSelectionDown.name = Multi selection down relative to anchor selection
+command.selectMultiSelectionUp.description = Search next matching region above and add it to the current selection, or remove last element from current multi-selection
+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.selectWordNext.description = Select the next word
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.xml b/org.eclipse.ui.workbench.texteditor/plugin.xml
index f8d54ff2e24..70d47cf569d 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.xml
+++ b/org.eclipse.ui.workbench.texteditor/plugin.xml
@@ -311,16 +311,16 @@
id="org.eclipse.ui.edit.text.select.addAllMatchesToMultiSelection">
</command>
<command
- name="%command.selectAddNextMatchToMultiSelection.name"
- description="%command.selectAddNextMatchToMultiSelection.description"
+ name="%command.selectMultiSelectionDown.name"
+ description="%command.selectMultiSelectionDown.description"
categoryId="org.eclipse.ui.category.textEditor"
- id="org.eclipse.ui.edit.text.select.addNextMatchToMultiSelection">
+ id="org.eclipse.ui.edit.text.select.selectMultiSelectionDown">
</command>
<command
- name="%command.selectRemoveLastMatchFromMultiSelection.name"
- description="%command.selectAddNextMatchToMultiSelection.description"
+ name="%command.selectMultiSelectionUp.name"
+ description="%command.selectMultiSelectionUp.description"
categoryId="org.eclipse.ui.category.textEditor"
- id="org.eclipse.ui.edit.text.select.removeLastMatchFromMultiSelection">
+ id="org.eclipse.ui.edit.text.select.selectMultiSelectionUp">
</command>
<command
name="%command.stopMultiSelection.name"
@@ -1402,8 +1402,8 @@
</enabledWhen>
</handler>
<handler
- class="org.eclipse.ui.internal.texteditor.multiselection.AddNextMatchToMultiSelectionHandler"
- commandId="org.eclipse.ui.edit.text.select.addNextMatchToMultiSelection">
+ class="org.eclipse.ui.internal.texteditor.multiselection.MultiSelectionDownHandler"
+ commandId="org.eclipse.ui.edit.text.select.selectMultiSelectionDown">
<enabledWhen>
<with
variable="activeEditor">
@@ -1414,8 +1414,8 @@
</enabledWhen>
</handler>
<handler
- class="org.eclipse.ui.internal.texteditor.multiselection.RemoveLastMatchFromMultiSelectionHandler"
- commandId="org.eclipse.ui.edit.text.select.removeLastMatchFromMultiSelection">
+ class="org.eclipse.ui.internal.texteditor.multiselection.MultiSelectionUpHandler"
+ commandId="org.eclipse.ui.edit.text.select.selectMultiSelectionUp">
<enabledWhen>
<with
variable="activeEditor">
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 f54d153cf7b..ba42de96dc7 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
@@ -46,11 +46,16 @@ import org.eclipse.ui.texteditor.ITextEditorExtension5;
* initialized.
*
* @see AddAllMatchesToMultiSelectionHandler
- * @see AddNextMatchToMultiSelectionHandler
- * @see RemoveLastMatchFromMultiSelectionHandler
+ * @see MultiSelectionDownHandler
+ * @see MultiSelectionUpHandler
* @see StopMultiSelectionHandler
*/
abstract class AbstractMultiSelectionHandler extends AbstractHandler {
+ /**
+ * Each widget can have a different anchor selection, that is stored in the
+ * widget's data with this key.
+ */
+ private static final String ANCHOR_REGION_KEY = "org.eclipse.ui.internal.texteditor.multiselection.AbstractMultiSelectionHandler.anchorRegion"; //$NON-NLS-1$
private ExecutionEvent event;
private ITextEditor textEditor;
private IDocument document;
@@ -58,7 +63,7 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
/**
* This method needs to be overwritten from subclasses to handle the event.
*
- * @throws ExecutionException
+ * @throws ExecutionException an Exception the event handler might throw
*/
public abstract void execute() throws ExecutionException;
@@ -81,7 +86,7 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
protected boolean nothingSelected() {
IRegion[] regions = getSelectedRegions();
- return regions == null || regions.length == 0 || regions[0].getLength() == 0;
+ return regions == null || regions.length == 0 || (regions.length == 1 && regions[0].getLength() == 0);
}
protected IRegion[] getSelectedRegions() {
@@ -95,34 +100,74 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
}
protected IRegion offsetAsCaretRegion(int offset) {
- return new Region(offset, 0);
+ return createRegionIfValid(offset, 0);
}
- protected void selectRegion(IRegion region) throws ExecutionException {
+ protected void selectRegion(IRegion region) {
selectRegions(new IRegion[] { region });
}
- protected void selectRegions(IRegion[] regions) throws ExecutionException {
+ protected void selectRegions(IRegion[] regions) {
setBlockSelectionMode(false);
ISelection newSelection = new MultiTextSelection(document, regions);
textEditor.getSelectionProvider().setSelection(newSelection);
}
- protected void selectIdentifierUnderCaret() throws ExecutionException {
+ protected void selectIdentifierUnderCaret() {
int offset = getCaretOffset();
Region identifierRegion = getIdentifierUnderCaretRegion(offset);
- if (identifierRegion != null)
+ if (identifierRegion != null) {
selectRegion(identifierRegion);
+ setAnchorRegion(identifierRegion);
+ }
+ }
+
+ protected void selectCaretPosition() {
+ IRegion caretRegion = offsetAsCaretRegion(getCaretOffset());
+ selectRegion(caretRegion);
+ setAnchorRegion(caretRegion);
}
protected boolean allRegionsHaveSameText() {
- if (nothingSelected())
- return false;
return allRegionsHaveSameText(getSelectedRegions());
}
+ protected boolean allRegionsEmpty() {
+ IRegion[] selectedRegions = getSelectedRegions();
+ if (selectedRegions == null)
+ return true;
+ return isEmpty(selectedRegions[0]) && allRegionsHaveSameText(selectedRegions);
+ }
+
+ protected boolean isEmpty(IRegion region) {
+ return region == null || region.getLength() == 0;
+ }
+
+ protected IRegion getAnchorRegion() {
+ return (IRegion) getWidget().getData(ANCHOR_REGION_KEY);
+ }
+
+ protected void setAnchorRegion(IRegion selection) {
+ if (selection == null) {
+ getWidget().setData(ANCHOR_REGION_KEY, null);
+ } else {
+ getWidget().setData(ANCHOR_REGION_KEY, selection);
+ }
+ }
+
+ private void initAnchorRegion() {
+ IRegion[] regions = getSelectedRegions();
+ if ((regions != null && regions.length == 1) || !contains(regions, getAnchorRegion())) {
+ setAnchorRegion(regions[0]);
+ }
+ }
+
+ private boolean contains(IRegion[] regions, IRegion region) {
+ return Arrays.asList(regions).contains(region);
+ }
+
private boolean allRegionsHaveSameText(IRegion[] regions) {
if (regions == null || regions.length == 1)
return true;
@@ -163,6 +208,16 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
return Arrays.copyOf(regions, regions.length - 1);
}
+ protected IRegion[] removeFirstRegionButOne(IRegion[] regions) {
+ if (regions == null || regions.length == 0)
+ return null;
+ if (regions.length == 1) {
+ return regions;
+ }
+
+ return Arrays.copyOfRange(regions, 1, regions.length);
+ }
+
protected int getCaretOffset() {
return getWidget().getCaretOffset();
}
@@ -172,24 +227,49 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
}
protected IRegion findNextMatch(IRegion region) throws ExecutionException {
- String fullText = getFullText();
try {
- String searchString = getTextOfRegion(region);
-
- int matchPos = fullText.indexOf(searchString, offsetAfter(region));
- if (matchPos < 0)
- return null;
-
- return new Region(matchPos, region.getLength());
+ if (region.getLength() == 0) {
+ return offsetAsCaretRegion(offsetInNextLine(region.getOffset()));
+ } else {
+ String searchString = getTextOfRegion(region);
+
+ String fullText = getFullText();
+ int matchPos = fullText.indexOf(searchString, offsetAfter(region));
+ return createRegionIfValid(matchPos, region.getLength());
+ }
} catch (BadLocationException e) {
throw new ExecutionException("Internal error in findNextMatch", e);
}
}
+ protected IRegion findPreviousMatch(IRegion region) throws ExecutionException {
+ try {
+ if (region.getLength() == 0) {
+ return offsetAsCaretRegion(offsetInPreviousLine(region.getOffset()));
+ } else {
+ String searchString = getTextOfRegion(region);
+
+ String fullText = getFullText();
+ int matchPos = fullText.lastIndexOf(searchString, region.getOffset() - 1);
+ return createRegionIfValid(matchPos, region.getLength());
+ }
+ } catch (BadLocationException e) {
+ throw new ExecutionException("Internal error in findPreviousMatch", e);
+ }
+ }
+
+ private IRegion createRegionIfValid(int offset, int length) {
+ if ((offset < 0) || (offset > document.getLength()))
+ return null;
+
+ return new Region(offset, Math.min(length, document.getLength() - offset));
+ }
+
protected IRegion[] findAllMatches(IRegion region) throws ExecutionException {
try {
- String fullText = getFullText();
String searchString = getTextOfRegion(region);
+
+ String fullText = getFullText();
List<IRegion> regions = findAllMatches(fullText, searchString);
return toArray(regions);
} catch (BadLocationException e) {
@@ -208,18 +288,39 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
return regions;
}
+ private int offsetInNextLine(int offset) throws BadLocationException {
+ return moveOffsetByLines(offset, 1);
+ }
+
+ private 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;
+ if ((newLineNo < 0) || (newLineNo >= document.getNumberOfLines()))
+ return -1;
+
+ int newLineOffset = document.getLineOffset(newLineNo);
+ int delta = offset - document.getLineOffset(lineNo);
+
+ return newLineOffset + delta;
+ }
+
private boolean initFrom(ExecutionEvent event) {
this.event = event;
- textEditor = getTextEditor(event);
+ initTextEditor();
if (textEditor == null)
return false;
document = getDocument();
+ initAnchorRegion();
return true;
}
- private ITextEditor getTextEditor(ExecutionEvent event) {
+ private void initTextEditor() {
IEditorPart editor = HandlerUtil.getActiveEditor(event);
- return editor instanceof ITextEditor ? (ITextEditor) editor : null;
+ textEditor = editor instanceof ITextEditor ? (ITextEditor) editor : null;
}
private IDocument getDocument() {
@@ -291,4 +392,32 @@ abstract class AbstractMultiSelectionHandler extends AbstractHandler {
ITextEditorExtension5 ext = (ITextEditorExtension5) textEditor;
ext.setBlockSelectionMode(blockSelectionMode);
}
+
+ protected boolean selectionIsAboveAnchorRegion() {
+ IRegion[] selectedRegions = getSelectedRegions();
+ if (selectedRegions == null || selectedRegions.length == 1)
+ return false;
+ return isLastRegion(getAnchorRegion(), selectedRegions);
+ }
+
+ protected boolean selectionIsBelowAnchorRegion() {
+ IRegion[] selectedRegions = getSelectedRegions();
+ if (selectedRegions == null || selectedRegions.length == 1)
+ return false;
+ return isFirstRegion(getAnchorRegion(), selectedRegions);
+ }
+
+ private boolean isLastRegion(IRegion region, IRegion[] regions) {
+ if (region == null || regions == null || regions.length == 0)
+ return false;
+
+ return region.equals(regions[regions.length - 1]);
+ }
+
+ private boolean isFirstRegion(IRegion region, IRegion[] regions) {
+ if (region == null || regions == null || regions.length == 0)
+ return false;
+
+ return region.equals(regions[0]);
+ }
}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java
index b9d09fc7cf1..189ea105aa6 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java
@@ -11,12 +11,10 @@
* Contributors:
* Dirk Steinkamp <dirk.steinkamp@gmx.de> - initial API and implementation
*******************************************************************************/
- package org.eclipse.ui.internal.texteditor.multiselection;
+package org.eclipse.ui.internal.texteditor.multiselection;
import org.eclipse.core.commands.ExecutionException;
-import org.eclipse.jface.text.IRegion;
-
/**
* Handler to extend the current selection to all found matches in the document.
* If nothing is selected, an implicit selection of the word under the cursor is
@@ -34,8 +32,9 @@ public class AddAllMatchesToMultiSelectionHandler extends AbstractMultiSelection
private void extendSelectionToAllMatches() throws ExecutionException {
if (allRegionsHaveSameText()) {
- IRegion[] regions = getSelectedRegions();
- selectRegions(findAllMatches(regions[0]));
+ if (!isEmpty(getAnchorRegion())) {
+ selectRegions(findAllMatches(getAnchorRegion()));
+ }
}
}
}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddNextMatchToMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java
index 16f9d479d40..678077e20a9 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddNextMatchToMultiSelectionHandler.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionDownHandler.java
@@ -18,16 +18,22 @@ import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.text.IRegion;
/**
- * Handler to extend the current selection to the next found match below the
- * selection. If no word is selected, an implicit selection of the word under
- * the cursor is performed.
+ * 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>
+ * If no word is selected, an implicit selection of the word under the cursor is
+ * performed.
*/
-public class AddNextMatchToMultiSelectionHandler extends AbstractMultiSelectionHandler {
+public class MultiSelectionDownHandler extends AbstractMultiSelectionHandler {
@Override
public void execute() throws ExecutionException {
if (nothingSelected()) {
selectIdentifierUnderCaret();
+ } else if (selectionIsAboveAnchorRegion()) {
+ removeFirstRegionFromSelection();
} else {
extendSelectionToNextMatch();
}
@@ -40,4 +46,10 @@ public class AddNextMatchToMultiSelectionHandler extends AbstractMultiSelectionH
selectRegions(addRegion(regions, nextMatch));
}
}
+
+ private void removeFirstRegionFromSelection() {
+ if (allRegionsHaveSameText()) {
+ selectRegions(removeFirstRegionButOne(getSelectedRegions()));
+ }
+ }
}
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
new file mode 100644
index 00000000000..8205b3a296c
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/MultiSelectionUpHandler.java
@@ -0,0 +1,55 @@
+/*******************************************************************************
+ * 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.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>
+ * If no word is selected, an implicit selection of the word under the cursor is
+ * performed.
+ */
+public class MultiSelectionUpHandler extends AbstractMultiSelectionHandler {
+
+ @Override
+ public void execute() throws ExecutionException {
+ if (nothingSelected()) {
+ selectIdentifierUnderCaret();
+ } else if (selectionIsBelowAnchorRegion()) {
+ removeLastRegionFromSelection();
+ } else {
+ extendSelectionToPreviousMatch();
+ }
+ }
+
+ private void extendSelectionToPreviousMatch() throws ExecutionException {
+ if (allRegionsHaveSameText()) {
+ IRegion[] regions = getSelectedRegions();
+ IRegion nextMatch = findPreviousMatch(regions[0]);
+ selectRegions(addRegion(regions, nextMatch));
+ }
+ }
+
+ private void removeLastRegionFromSelection() {
+ if (allRegionsHaveSameText()) {
+ selectRegions(removeLastRegionButOne(getSelectedRegions()));
+ }
+ }
+}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/RemoveLastMatchFromMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/RemoveLastMatchFromMultiSelectionHandler.java
deleted file mode 100644
index 35be36dd579..00000000000
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/RemoveLastMatchFromMultiSelectionHandler.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*******************************************************************************
- * 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;
-
-/**
- * Removes last selection region from a multi-selection.
- */
-public class RemoveLastMatchFromMultiSelectionHandler extends AbstractMultiSelectionHandler {
-
- @Override
- public void execute() throws ExecutionException {
- if (allRegionsHaveSameText()) {
- selectRegions(removeLastRegionButOne(getSelectedRegions()));
- }
- }
-}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java
index fc28e7c922e..3672da17c69 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java
@@ -33,5 +33,6 @@ public class StopMultiSelectionHandler extends AbstractMultiSelectionHandler {
int caretOffset = getCaretOffset();
selectRegion(offsetAsCaretRegion(caretOffset));
setCaretOffset(caretOffset);
+ setAnchorRegion(null);
}
}

Back to the top