Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDirk Steinkamp2022-03-04 20:14:28 +0000
committerMickael Istria2022-03-11 12:32:34 +0000
commit1a27e19c6ff1267be05cb2b833b2f977d7da2dfc (patch)
treecc5ea01ddd300274783fbee6bf5da00ab13cfca7
parent38f7a5e579ba49611f47d47b1749b0fe148d34f6 (diff)
downloadeclipse.platform.text-1a27e19c6ff1267be05cb2b833b2f977d7da2dfc.tar.gz
eclipse.platform.text-1a27e19c6ff1267be05cb2b833b2f977d7da2dfc.tar.xz
eclipse.platform.text-1a27e19c6ff1267be05cb2b833b2f977d7da2dfc.zip
multiselection/multiple carets in text editors Add various commands for multi-selection (intended for keyboard usage, keyboard shortcuts are just suggestions): - AddAllMatchesToMultiSelection (e.g. CTRL-ALT-SHIFT-J) - AddNextMatchToMultiSelection (e.g. ALT-J) - RemoveLastMatchFromMultiSelectionHandler (e.g. ALT-SHIFT-J) - StopMultiSelectionHandler (e.g. ESC) Change-Id: Id9add4daad15495ee00c76d8a5a7c5dc5608f506 Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.text/+/191500 Tested-by: Platform Bot <platform-bot@eclipse.org> Reviewed-by: Mickael Istria <mistria@redhat.com>
-rw-r--r--org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF4
-rw-r--r--org.eclipse.ui.editors.tests/pom.xml2
-rw-r--r--org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java11
-rw-r--r--org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java360
-rw-r--r--org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF2
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.properties11
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.xml72
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java294
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java41
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddNextMatchToMultiSelectionHandler.java43
-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.java37
12 files changed, 897 insertions, 9 deletions
diff --git a/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF b/org.eclipse.ui.editors.tests/META-INF/MANIFEST.MF
index 47dd5a5b647..5ece30fa9b8 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 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Plugin.name
Bundle-SymbolicName: org.eclipse.ui.editors.tests;singleton:=true
-Bundle-Version: 3.12.400.qualifier
+Bundle-Version: 3.12.500.qualifier
Bundle-Vendor: %Plugin.providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.ui.editors.tests
@@ -11,7 +11,7 @@ Require-Bundle:
org.junit;bundle-version="4.12.0",
org.eclipse.jface;bundle-version="[3.5.0,4.0.0)",
org.eclipse.text;bundle-version="[3.5.0,4.0.0)",
- org.eclipse.ui.workbench.texteditor;bundle-version="[3.5.0,4.0.0)",
+ org.eclipse.ui.workbench.texteditor;bundle-version="[3.16.500,4.0.0)",
org.eclipse.ui.editors;bundle-version="[3.5.0,4.0.0)",
org.eclipse.ui.workbench;bundle-version="[3.5.0,4.0.0)",
org.eclipse.core.resources;bundle-version="[3.14.0,4.0.0)",
diff --git a/org.eclipse.ui.editors.tests/pom.xml b/org.eclipse.ui.editors.tests/pom.xml
index 81baf23f58e..26ee84a16a6 100644
--- a/org.eclipse.ui.editors.tests/pom.xml
+++ b/org.eclipse.ui.editors.tests/pom.xml
@@ -18,7 +18,7 @@
<relativePath>../tests-pom/</relativePath>
</parent>
<artifactId>org.eclipse.ui.editors.tests</artifactId>
- <version>3.12.400-SNAPSHOT</version>
+ <version>3.12.500-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/EditorsTestSuite.java b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java
index 74c52d215c7..4dea7310cc7 100644
--- a/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java
+++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/EditorsTestSuite.java
@@ -1,5 +1,5 @@
-/*******************************************************************************
- * Copyright (c) 2000, 2019 IBM Corporation and others.
+/************************************************************************************************
+ * Copyright (c) 2000, 2022 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
@@ -11,7 +11,8 @@
* Contributors:
* IBM Corporation - initial API and implementation
* Mickael Istria (Red Hat Inc.) - [484157] Add zoom test
- *******************************************************************************/
+ * Dirk Steinkamp <dirk.steinkamp@gmx.de> - [576377] Add multi caret selection commands test
+ ************************************************************************************************/
package org.eclipse.ui.editors.tests;
import org.junit.runner.RunWith;
@@ -36,7 +37,9 @@ import org.junit.runners.Suite.SuiteClasses;
TextFileDocumentProviderTest.class,
StatusEditorTest.class,
TextNavigationTest.class,
- LargeFileTest.class, CaseActionTest.class
+ LargeFileTest.class, CaseActionTest.class,
+ TextMultiCaretNavigationTest.class,
+ TextMultiCaretSelectionCommandsTest.class,
})
public class EditorsTestSuite {
// see @SuiteClasses
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
new file mode 100644
index 00000000000..4ddeb9f2db5
--- /dev/null
+++ b/org.eclipse.ui.editors.tests/src/org/eclipse/ui/editors/tests/TextMultiCaretSelectionCommandsTest.java
@@ -0,0 +1,360 @@
+/*******************************************************************************
+ * 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.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 java.util.Collections;
+
+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.commands.Command;
+import org.eclipse.core.commands.ExecutionEvent;
+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.commands.ICommandService;
+import org.eclipse.ui.ide.IDE;
+
+import org.eclipse.ui.texteditor.AbstractTextEditor;
+
+/*
+ * Note: this test would better fit in the org.eclipse.ui.workbench.texteditor bundle, but initializing
+ * 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 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 STOP_MULTI_SELECTION = "org.eclipse.ui.edit.text.select.stopMultiSelection";
+
+ 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_4 = "private static String d";
+
+ private static final int L1_LEN = LINE_1.length();
+ private static final int L2_LEN = LINE_2.length();
+ private static final int L3_LEN = LINE_3.length();
+ private static final int L4_LEN = LINE_4.length();
+
+ private static File file;
+ private static AbstractTextEditor editor;
+ private static StyledText widget;
+
+ @Before
+ public void setUpBeforeClass() throws IOException, PartInitException, CoreException {
+ file = File.createTempFile(TextMultiCaretSelectionCommandsTest.class.getName(), ".txt");
+ Files.write(file.toPath(), (LINE_1 + LINE_2 + LINE_3 + LINE_4) //
+ .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 testAddNextMatch_withFirstIdentifierSelected_addsIdenticalIdentifiersToSelection() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 7) });
+ assertEquals(7, widget.getCaretOffset());
+
+ executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+
+ assertEquals(7, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) }, getSelection());
+
+ executeCommand(ADD_NEXT_MATCH_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) },
+ getSelection());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(7, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection());
+ }
+
+ @Test
+ public void testAddNextMatch_withSecondIdentifierSelectedIdentifier_addsNextOccurenceToSelection()
+ throws Exception {
+ setSelection(new IRegion[] { new Region(8, 6) });
+ assertEquals(14, widget.getCaretOffset());
+
+ executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+
+ assertEquals(14, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testAddNextMatch_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);
+
+ assertEquals(L1_LEN + 14, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6), new Region(L1_LEN + L2_LEN + 8, 6) },
+ getSelection());
+ }
+
+ @Test
+ public void testAddNextMatch_withCaretInFirstIdentifier_selectsFullIdentifier() throws Exception {
+ setSelection(new IRegion[] { new Region(1, 0) });
+ assertEquals(1, widget.getCaretOffset());
+
+ executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+
+ assertEquals(7, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 7) }, getSelection());
+ }
+
+ @Test
+ public void testAddNextMatch_withCaretInSecondIdentifier_selectsFullIdentifier() throws Exception {
+ setSelection(new IRegion[] { new Region(11, 0) });
+ assertEquals(11, widget.getCaretOffset());
+
+ executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+
+ assertEquals(14, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testAddNextMatch_withCaretBetweenIdentifierCharAndNonIdentifierChar_selectsFullIdentifier()
+ throws Exception {
+ setSelection(new IRegion[] { new Region(23, 0) });
+ assertEquals(23, widget.getCaretOffset());
+
+ executeCommand(ADD_NEXT_MATCH_TO_MULTI_SELECTION);
+
+ assertEquals(23, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(22, 1) }, getSelection());
+ }
+
+ @Test
+ public void testAddNextMatch_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);
+
+ assertEquals(L1_LEN + 14, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testAddNextMatch_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);
+
+ 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 {
+ 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);
+
+ 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());
+ }
+
+ @Test
+ public void testAddAllMatches_withSingleSelection_selectsAllOccurences() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 7) });
+ assertEquals(7, widget.getCaretOffset());
+
+ 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),
+ new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection());
+ }
+
+ @Test
+ public void testAddAllMatches_withDoubleSelectionOfSameText_selectsAllOccurences() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) });
+ assertEquals(7, widget.getCaretOffset());
+
+ 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),
+ new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection());
+ }
+
+ @Test
+ public void testAddAllMatches_withDoubleSelectionOfDifferentTexts_doesNotChangeSelection() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 7), new Region(8, 7) });
+ assertEquals(7, widget.getCaretOffset());
+
+ executeCommand(ADD_ALL_MATCHES_TO_MULTI_SELECTION);
+
+ assertEquals(7, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 7), new Region(8, 7) }, getSelection());
+ }
+
+ @Test
+ public void testAddAllMatches_withCaretInIdentifier_selectsAllOccurencesOfIdentifier() throws Exception {
+ setSelection(new IRegion[] { new Region(2, 0) });
+ assertEquals(2, widget.getCaretOffset());
+
+ 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),
+ new Region(L1_LEN + L2_LEN + L3_LEN, 7) }, getSelection());
+ }
+
+ @Test
+ public void testRemoveLastMatchFromMultiSelection_withCaretInIdentifier_doesNothing() throws Exception {
+ setSelection(new IRegion[] { new Region(L1_LEN + 11, 0) });
+ assertEquals(L1_LEN + 11, widget.getCaretOffset());
+
+ executeCommand(REMOVE_LAST_MATCH_FROM_MULTI_SELECTION);
+
+ assertEquals(L1_LEN + 11, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(L1_LEN + 11, 0) }, getSelection());
+ }
+
+ @Test
+ public void testRemoveLastMatchFromMultiSelection_withSingleSelection_doesNothing() throws Exception {
+ setSelection(new IRegion[] { new Region(8, 6) });
+ assertEquals(14, widget.getCaretOffset());
+
+ executeCommand(REMOVE_LAST_MATCH_FROM_MULTI_SELECTION);
+
+ assertEquals(14, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testRemoveLastMatchFromMultiSelection_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);
+
+ assertEquals(14, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testRemoveLastMatchFromMultiSelection_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);
+
+ assertEquals(14, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(8, 6), new Region(L1_LEN + 8, 6) }, getSelection());
+ }
+
+ @Test
+ public void testStopMultiSelection_withSingleSelection_doesNotChangeSelectionOrCaretOffset() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 7) });
+
+ assertEquals(7, widget.getCaretOffset());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(7, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 7) }, getSelection());
+ }
+
+ @Test
+ public void testStopMultiSelection_withMultiSelection_revokesSelectionAndKeepsFirstCaretOffset() throws Exception {
+ setSelection(new IRegion[] { new Region(0, 7), new Region(L1_LEN, 7) });
+
+ assertEquals(7, widget.getCaretOffset());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(7, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection());
+ }
+
+ @Test
+ public void testStopMultiSelection_withMultiSelectionAndCaretAtBeginning_revokesSelectionAndKeepsFirstCaretOffset()
+ throws Exception {
+ setSelection(new IRegion[] { new Region(0, 0), new Region(0, 7), new Region(L1_LEN, 7) });
+ assertEquals(0, widget.getCaretOffset());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(0, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(0, 0) }, getSelection());
+ }
+
+ @Test
+ public void testStopMultiSelection_withMultiSelectionAndCaretAfterLastSelection_revokesSelectionAndKeepsCaretOffset()
+ throws Exception {
+ 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);
+
+ // TODO How to place the caret at the end without dismissing the
+ // selection? Should rather be 57
+ assertEquals(7, widget.getCaretOffset());
+
+ executeCommand(STOP_MULTI_SELECTION);
+ assertEquals(7, widget.getCaretOffset());
+ assertArrayEquals(new IRegion[] { new Region(7, 0) }, getSelection());
+ }
+
+ // Helper methods
+
+ private void executeCommand(String commandId) throws Exception {
+ Command command = PlatformUI.getWorkbench().getService(ICommandService.class).getCommand(commandId);
+ command.executeWithChecks(new ExecutionEvent(command, Collections.EMPTY_MAP, null, null));
+ }
+
+ private void setSelection(IRegion[] regions) {
+ IDocument document = editor.getDocumentProvider().getDocument(editor.getEditorInput());
+ editor.getSelectionProvider().setSelection(new MultiTextSelection(document, regions));
+ }
+
+ private IRegion[] getSelection() {
+ return ((IMultiTextSelection) editor.getSelectionProvider().getSelection()).getRegions();
+ }
+}
diff --git a/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF b/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF
index 64ccad74f0f..de37f77a8d1 100644
--- a/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF
+++ b/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.ui.workbench.texteditor; singleton:=true
-Bundle-Version: 3.16.400.qualifier
+Bundle-Version: 3.16.500.qualifier
Bundle-Activator: org.eclipse.ui.internal.texteditor.TextEditorPlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.properties b/org.eclipse.ui.workbench.texteditor/plugin.properties
index f2b4831e9a5..f7bf56102e9 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.properties
+++ b/org.eclipse.ui.workbench.texteditor/plugin.properties
@@ -1,5 +1,5 @@
###############################################################################
-# Copyright (c) 2000, 2015 IBM Corporation and others.
+# Copyright (c) 2000, 2022 IBM Corporation and others.
#
# This program and the accompanying materials
# are made available under the terms of the Eclipse Public License 2.0
@@ -14,6 +14,7 @@
# Daesung Ha <nberserk@gmail.com> - update recenter command description
# Mickael Istria (Red Hat Inc.) - 469918 Zoom In/Out
# Angelo Zerr <angelo.zerr@gmail.com> - [CodeMining] Provide extension point for CodeMining - Bug 528419
+# Dirk Steinkamp <dirk.steinkamp@gmx.de> - [576377] Add multi caret selection commands
###############################################################################
pluginName= Text Editor Framework
providerName= Eclipse.org
@@ -158,6 +159,14 @@ command.selectWindowEnd.description = Select to the end of the window
command.selectWindowEnd.name = Select Window End
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.stopMultiSelection.description = Unselects all multi-selections returning to a single cursor
+command.stopMultiSelection.name = End multi-selection
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 18c9f928a8b..f8d54ff2e24 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.xml
+++ b/org.eclipse.ui.workbench.texteditor/plugin.xml
@@ -305,6 +305,30 @@
id="org.eclipse.ui.edit.text.select.windowEnd">
</command>
<command
+ name="%command.selectAddAllMatchesToMultiSelection.name"
+ description="%command.selectAddAllMatchesToMultiSelection.description"
+ categoryId="org.eclipse.ui.category.textEditor"
+ id="org.eclipse.ui.edit.text.select.addAllMatchesToMultiSelection">
+ </command>
+ <command
+ name="%command.selectAddNextMatchToMultiSelection.name"
+ description="%command.selectAddNextMatchToMultiSelection.description"
+ categoryId="org.eclipse.ui.category.textEditor"
+ id="org.eclipse.ui.edit.text.select.addNextMatchToMultiSelection">
+ </command>
+ <command
+ name="%command.selectRemoveLastMatchFromMultiSelection.name"
+ description="%command.selectAddNextMatchToMultiSelection.description"
+ categoryId="org.eclipse.ui.category.textEditor"
+ id="org.eclipse.ui.edit.text.select.removeLastMatchFromMultiSelection">
+ </command>
+ <command
+ name="%command.stopMultiSelection.name"
+ description="%command.stopMultiSelection.description"
+ categoryId="org.eclipse.ui.category.textEditor"
+ id="org.eclipse.ui.edit.text.select.stopMultiSelection">
+ </command>
+ <command
name="%command.deletePrevious.name"
description="%command.deletePrevious.description"
categoryId="org.eclipse.ui.category.textEditor"
@@ -1365,6 +1389,54 @@
</with>
</enabledWhen>
</handler>
+ <handler
+ class="org.eclipse.ui.internal.texteditor.multiselection.AddAllMatchesToMultiSelectionHandler"
+ commandId="org.eclipse.ui.edit.text.select.addAllMatchesToMultiSelection">
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <instanceof
+ value="org.eclipse.ui.texteditor.ITextEditor">
+ </instanceof>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.ui.internal.texteditor.multiselection.AddNextMatchToMultiSelectionHandler"
+ commandId="org.eclipse.ui.edit.text.select.addNextMatchToMultiSelection">
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <instanceof
+ value="org.eclipse.ui.texteditor.ITextEditor">
+ </instanceof>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.ui.internal.texteditor.multiselection.RemoveLastMatchFromMultiSelectionHandler"
+ commandId="org.eclipse.ui.edit.text.select.removeLastMatchFromMultiSelection">
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <instanceof
+ value="org.eclipse.ui.texteditor.ITextEditor">
+ </instanceof>
+ </with>
+ </enabledWhen>
+ </handler>
+ <handler
+ class="org.eclipse.ui.internal.texteditor.multiselection.StopMultiSelectionHandler"
+ commandId="org.eclipse.ui.edit.text.select.stopMultiSelection">
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <instanceof
+ value="org.eclipse.ui.texteditor.ITextEditor">
+ </instanceof>
+ </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
new file mode 100644
index 00000000000..f54d153cf7b
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AbstractMultiSelectionHandler.java
@@ -0,0 +1,294 @@
+/*******************************************************************************
+ * 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 java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+import org.eclipse.swt.custom.StyledText;
+import org.eclipse.swt.widgets.Control;
+
+import org.eclipse.core.commands.AbstractHandler;
+import org.eclipse.core.commands.ExecutionEvent;
+import org.eclipse.core.commands.ExecutionException;
+
+import org.eclipse.jface.viewers.ISelection;
+
+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.MultiTextSelection;
+import org.eclipse.jface.text.Region;
+
+import org.eclipse.ui.IEditorPart;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.ITextEditorExtension5;
+
+/**
+ * Common super class for Multi-Selection-actions, containing various helper
+ * methods. Subclasses need to overwrite {@link #execute()}, which is only
+ * invoked if the {@link #textEditor} and {@link #document} could be properly
+ * initialized.
+ *
+ * @see AddAllMatchesToMultiSelectionHandler
+ * @see AddNextMatchToMultiSelectionHandler
+ * @see RemoveLastMatchFromMultiSelectionHandler
+ * @see StopMultiSelectionHandler
+ */
+abstract class AbstractMultiSelectionHandler extends AbstractHandler {
+ private ExecutionEvent event;
+ private ITextEditor textEditor;
+ private IDocument document;
+
+ /**
+ * This method needs to be overwritten from subclasses to handle the event.
+ *
+ * @throws ExecutionException
+ */
+ public abstract void execute() throws ExecutionException;
+
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ if (initFrom(event)) {
+ execute();
+ }
+ return null;
+ }
+
+ public ExecutionEvent getEvent() {
+ return event;
+ }
+
+ protected boolean isMultiSelectionActive() {
+ IRegion[] regions = getSelectedRegions();
+ return regions != null && regions.length > 1;
+ }
+
+ protected boolean nothingSelected() {
+ IRegion[] regions = getSelectedRegions();
+ return regions == null || regions.length == 0 || regions[0].getLength() == 0;
+ }
+
+ protected IRegion[] getSelectedRegions() {
+ ISelection selection = textEditor.getSelectionProvider().getSelection();
+
+ if (!(selection instanceof IMultiTextSelection)) {
+ return null;
+ }
+
+ return ((IMultiTextSelection) selection).getRegions();
+ }
+
+ protected IRegion offsetAsCaretRegion(int offset) {
+ return new Region(offset, 0);
+ }
+
+ protected void selectRegion(IRegion region) throws ExecutionException {
+ selectRegions(new IRegion[] { region });
+ }
+
+ protected void selectRegions(IRegion[] regions) throws ExecutionException {
+ setBlockSelectionMode(false);
+
+ ISelection newSelection = new MultiTextSelection(document, regions);
+ textEditor.getSelectionProvider().setSelection(newSelection);
+ }
+
+ protected void selectIdentifierUnderCaret() throws ExecutionException {
+ int offset = getCaretOffset();
+
+ Region identifierRegion = getIdentifierUnderCaretRegion(offset);
+ if (identifierRegion != null)
+ selectRegion(identifierRegion);
+ }
+
+ protected boolean allRegionsHaveSameText() {
+ if (nothingSelected())
+ return false;
+ return allRegionsHaveSameText(getSelectedRegions());
+ }
+
+ private boolean allRegionsHaveSameText(IRegion[] regions) {
+ if (regions == null || regions.length == 1)
+ return true;
+
+ try {
+ return allRegionsHaveText(regions, regionAsString(regions[0]));
+ } catch (BadLocationException e) {
+ return false;
+ }
+ }
+
+ private boolean allRegionsHaveText(IRegion[] regions, String text) throws BadLocationException {
+ for (IRegion iRegion : regions) {
+ if (!text.equals(regionAsString(iRegion))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ protected IRegion[] addRegion(IRegion[] regions, IRegion newRegion) {
+ if (newRegion != null) {
+ IRegion[] newRegions = Arrays.copyOf(regions, regions.length + 1);
+ newRegions[newRegions.length - 1] = newRegion;
+ return newRegions;
+ } else {
+ return regions;
+ }
+ }
+
+ protected IRegion[] removeLastRegionButOne(IRegion[] regions) {
+ if (regions == null || regions.length == 0)
+ return null;
+ if (regions.length == 1) {
+ return regions;
+ }
+
+ return Arrays.copyOf(regions, regions.length - 1);
+ }
+
+ protected int getCaretOffset() {
+ return getWidget().getCaretOffset();
+ }
+
+ protected void setCaretOffset(int offset) {
+ getWidget().setCaretOffset(offset);
+ }
+
+ 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());
+ } catch (BadLocationException e) {
+ throw new ExecutionException("Internal error in findNextMatch", e);
+ }
+ }
+
+ protected IRegion[] findAllMatches(IRegion region) throws ExecutionException {
+ try {
+ String fullText = getFullText();
+ String searchString = getTextOfRegion(region);
+ List<IRegion> regions = findAllMatches(fullText, searchString);
+ return toArray(regions);
+ } catch (BadLocationException e) {
+ throw new ExecutionException("Internal error in findAllMatches", e);
+ }
+ }
+
+ private List<IRegion> findAllMatches(String fullText, String searchString) {
+ List<IRegion> regions = new ArrayList<>();
+ int length = searchString.length();
+ int matchPos = 0;
+ while ((matchPos = fullText.indexOf(searchString, matchPos)) >= 0) {
+ regions.add(new Region(matchPos, length));
+ matchPos += length;
+ }
+ return regions;
+ }
+
+ private boolean initFrom(ExecutionEvent event) {
+ this.event = event;
+ textEditor = getTextEditor(event);
+ if (textEditor == null)
+ return false;
+ document = getDocument();
+ return true;
+ }
+
+ private ITextEditor getTextEditor(ExecutionEvent event) {
+ IEditorPart editor = HandlerUtil.getActiveEditor(event);
+ return editor instanceof ITextEditor ? (ITextEditor) editor : null;
+ }
+
+ private IDocument getDocument() {
+ return textEditor.getDocumentProvider().getDocument(textEditor.getEditorInput());
+ }
+
+ private IRegion[] toArray(List<IRegion> regions) {
+ return regions.toArray(new IRegion[regions.size()]);
+ }
+
+ private int offsetAfter(IRegion region) {
+ return region.getOffset() + region.getLength();
+ }
+
+ private String getTextOfRegion(IRegion region) throws BadLocationException {
+ return document.get(region.getOffset(), region.getLength());
+ }
+
+ private String getFullText() {
+ return document.get();
+ }
+
+ private String regionAsString(IRegion region) throws BadLocationException {
+ return document.get(region.getOffset(), region.getLength());
+ }
+
+ private Region getIdentifierUnderCaretRegion(int offset) {
+ try {
+ int startOffset = findStartOfIdentifier(offset);
+ int endOffset = findEndOfIdentifier(startOffset);
+ Region identifierRegion = new Region(startOffset, endOffset - startOffset);
+ return identifierRegion;
+ } catch (BadLocationException e) {
+ return null;
+ }
+ }
+
+ private int findStartOfIdentifier(int offset) throws BadLocationException {
+ for (int i = offset - 1; i >= 0; i--) {
+ if (!isJavaIdentifierCharAtPos(i)) {
+ return i + 1;
+ }
+ }
+ return 0; // start of document reached
+ }
+
+ private int findEndOfIdentifier(int offset) throws BadLocationException {
+ for (int i = offset; i <= document.getLength(); i++) {
+ if (i == document.getLength() || !isJavaIdentifierCharAtPos(i)) {
+ return i;
+ }
+ }
+ return offset;
+ }
+
+ private boolean isJavaIdentifierCharAtPos(int i) throws BadLocationException {
+ return Character.isJavaIdentifierStart(document.getChar(i))
+ || Character.isJavaIdentifierPart(document.getChar(i));
+ }
+
+ private StyledText getWidget() {
+ return (StyledText) textEditor.getAdapter(Control.class);
+ }
+
+ private void setBlockSelectionMode(boolean blockSelectionMode) {
+ if (!(textEditor instanceof ITextEditorExtension5)) {
+ return;
+ }
+ ITextEditorExtension5 ext = (ITextEditorExtension5) textEditor;
+ ext.setBlockSelectionMode(blockSelectionMode);
+ }
+}
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
new file mode 100644
index 00000000000..b9d09fc7cf1
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddAllMatchesToMultiSelectionHandler.java
@@ -0,0 +1,41 @@
+/*******************************************************************************
+ * 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 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
+ * performed and the selection performed with this.
+ */
+public class AddAllMatchesToMultiSelectionHandler extends AbstractMultiSelectionHandler {
+
+ @Override
+ public void execute() throws ExecutionException {
+ if (nothingSelected()) {
+ selectIdentifierUnderCaret();
+ }
+ extendSelectionToAllMatches();
+ }
+
+ private void extendSelectionToAllMatches() throws ExecutionException {
+ if (allRegionsHaveSameText()) {
+ IRegion[] regions = getSelectedRegions();
+ selectRegions(findAllMatches(regions[0]));
+ }
+ }
+}
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/AddNextMatchToMultiSelectionHandler.java
new file mode 100644
index 00000000000..16f9d479d40
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/AddNextMatchToMultiSelectionHandler.java
@@ -0,0 +1,43 @@
+/*******************************************************************************
+ * 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 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.
+ */
+public class AddNextMatchToMultiSelectionHandler extends AbstractMultiSelectionHandler {
+
+ @Override
+ public void execute() throws ExecutionException {
+ if (nothingSelected()) {
+ selectIdentifierUnderCaret();
+ } else {
+ extendSelectionToNextMatch();
+ }
+ }
+
+ private void extendSelectionToNextMatch() throws ExecutionException {
+ if (allRegionsHaveSameText()) {
+ IRegion[] regions = getSelectedRegions();
+ IRegion nextMatch = findNextMatch(regions[regions.length - 1]);
+ selectRegions(addRegion(regions, nextMatch));
+ }
+ }
+}
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
new file mode 100644
index 00000000000..35be36dd579
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/RemoveLastMatchFromMultiSelectionHandler.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * 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
new file mode 100644
index 00000000000..fc28e7c922e
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/multiselection/StopMultiSelectionHandler.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * 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;
+
+/**
+ * Handler to stop multi-selection mode. If more than one selection is active,
+ * all selections are revoked, and the caret is positioned at position of the
+ * first caret.
+ */
+public class StopMultiSelectionHandler extends AbstractMultiSelectionHandler {
+
+ @Override
+ public void execute() throws ExecutionException {
+ if (isMultiSelectionActive()) {
+ stopMultiSelection();
+ }
+ }
+
+ private void stopMultiSelection() throws ExecutionException {
+ int caretOffset = getCaretOffset();
+ selectRegion(offsetAsCaretRegion(caretOffset));
+ setCaretOffset(caretOffset);
+ }
+}

Back to the top