Skip to main content
aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMickael Istria2019-12-03 08:58:49 +0000
committerMickael Istria2021-09-07 10:25:05 +0000
commitbf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9 (patch)
treedf95e1fd56beedf3560e058dad6ea2278d6cf977
parent118d3512d9b32f1219e5358a6ae5ad212ede861b (diff)
downloadeclipse.platform.text-bf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9.tar.gz
eclipse.platform.text-bf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9.tar.xz
eclipse.platform.text-bf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9.zip
carets/cursors/selections for text manipulation + Made line & word start/end commands capable of multi-carets + Support pasting multiple tokens + Added command to turn block selection into multi selection Change-Id: Ibb0c1819a197ca409fadfe2a0b8ce133762f6d9e Reviewed-on: https://git.eclipse.org/r/c/platform/eclipse.platform.text/+/153689 Reviewed-by: Mickael Istria <mistria@redhat.com> Tested-by: Mickael Istria <mistria@redhat.com>
-rw-r--r--org.eclipse.jface.text.tests/META-INF/MANIFEST.MF4
-rw-r--r--org.eclipse.jface.text.tests/pom.xml2
-rw-r--r--org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java2
-rw-r--r--org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java193
-rw-r--r--org.eclipse.jface.text/META-INF/MANIFEST.MF4
-rw-r--r--org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java480
-rw-r--r--org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java2
-rw-r--r--org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java29
-rw-r--r--org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java111
-rw-r--r--org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java7
-rw-r--r--org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java223
-rw-r--r--org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF4
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.properties2
-rw-r--r--org.eclipse.ui.workbench.texteditor/plugin.xml25
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java60
-rw-r--r--org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java217
16 files changed, 1044 insertions, 321 deletions
diff --git a/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF b/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
index 2384b19902f..8414bda1711 100644
--- a/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
+++ b/org.eclipse.jface.text.tests/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %Plugin.name
Bundle-SymbolicName: org.eclipse.jface.text.tests
-Bundle-Version: 3.12.200.qualifier
+Bundle-Version: 3.12.300.qualifier
Bundle-Vendor: %Plugin.providerName
Bundle-Localization: plugin
Export-Package:
@@ -16,7 +16,7 @@ Export-Package:
org.eclipse.jface.text.tests.templates.persistence,
org.eclipse.jface.text.tests.util
Require-Bundle:
- org.eclipse.jface.text;bundle-version="[3.16.0,4.0.0)",
+ org.eclipse.jface.text;bundle-version="[3.19.0,4.0.0)",
org.eclipse.jface;bundle-version="[3.5.0,4.0.0)",
org.junit;bundle-version="4.12.0",
org.eclipse.text.tests;bundle-version="[3.5.0,4.0.0)",
diff --git a/org.eclipse.jface.text.tests/pom.xml b/org.eclipse.jface.text.tests/pom.xml
index 4e14159dc32..47a4392682a 100644
--- a/org.eclipse.jface.text.tests/pom.xml
+++ b/org.eclipse.jface.text.tests/pom.xml
@@ -19,7 +19,7 @@
</parent>
<groupId>org.eclipse.jface</groupId>
<artifactId>org.eclipse.jface.text.tests</artifactId>
- <version>3.12.200-SNAPSHOT</version>
+ <version>3.12.300-SNAPSHOT</version>
<packaging>eclipse-test-plugin</packaging>
<properties>
<testSuite>${project.artifactId}</testSuite>
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java
index 29c36033029..011bf026dd1 100644
--- a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/JFaceTextTestSuite.java
@@ -73,8 +73,8 @@ import org.eclipse.jface.text.tests.templates.persistence.TemplatePersistenceDat
CodeMiningProjectionViewerTest.class,
TabsToSpacesConverterTest.class,
-
DefaultTextDoubleClickStrategyTest.class,
+ MultiSelectionTest.class
})
public class JFaceTextTestSuite {
// see @SuiteClasses
diff --git a/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java
new file mode 100644
index 00000000000..1c779680bce
--- /dev/null
+++ b/org.eclipse.jface.text.tests/src/org/eclipse/jface/text/tests/MultiSelectionTest.java
@@ -0,0 +1,193 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Red Hat, Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.jface.text.tests;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Event;
+import org.eclipse.swt.widgets.Shell;
+
+import org.eclipse.text.edits.MalformedTreeException;
+
+import org.eclipse.jface.internal.text.SelectionProcessor;
+import org.eclipse.jface.layout.GridDataFactory;
+import org.eclipse.jface.viewers.ISelection;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.Document;
+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.jface.text.TextViewer;
+import org.eclipse.jface.text.tests.util.DisplayHelper;
+
+public class MultiSelectionTest {
+
+ @Test
+ public void testSelectionProcessor() throws MalformedTreeException, BadLocationException {
+ Shell shell= new Shell();
+ TextViewer textViewer= new TextViewer(shell, SWT.NONE);
+ String content = "ababa\nbaba";
+ Document document= new Document(content);
+ List<Region> regions = new ArrayList<>();
+ int index = 0;
+ while ((index = document.get().indexOf('a', index)) >= 0) {
+ regions.add(new Region(index, 1));
+ index++;
+ }
+ textViewer.setDocument(document);
+ SelectionProcessor selectionProcessor = new SelectionProcessor(textViewer);
+ MultiTextSelection selection = new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()]));
+ assertEquals(2, selectionProcessor.getCoveredLines(selection));
+ assertEquals("aaaaa", selectionProcessor.getText(selection));
+ //
+ document.set(content);
+ selectionProcessor.doDelete(selection);
+ assertEquals("bb\nbb", document.get());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) },
+ ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions());
+ //
+ document.set(content);
+ selectionProcessor.doBackspace(selection);
+ assertEquals("bb\nbb", document.get());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) },
+ ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions());
+ //
+ document.set(content);
+ selectionProcessor.doReplace(selection, "cc");
+ assertEquals("ccbccbcc\nbccbcc", document.get());
+ assertArrayEquals(new IRegion[] { new Region(2, 0), new Region(5, 0), new Region(8, 0), new Region(12, 0), new Region(15, 0) },
+ ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions());
+ //
+ selection = new MultiTextSelection(document, regions.stream().map(region -> new Region(region.getOffset(), 0)).toArray(IRegion[]::new));
+ document.set(content);
+ selectionProcessor.doDelete(selection);
+ assertEquals("bb\nbb", document.get());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) },
+ ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions());
+ //
+ document.set(content);
+ selectionProcessor.doBackspace(selection);
+ assertEquals("aaa\naa", document.get());
+ assertArrayEquals(new IRegion[] { new Region(0, 0), new Region(1, 0), new Region(2, 0), new Region(4, 0), new Region(5, 0) },
+ ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions());
+ //
+ document.set(content);
+ selectionProcessor.doReplace(selection, "cc");
+ assertEquals("ccabccabcca\nbccabcca", document.get());
+ assertArrayEquals(new IRegion[] { new Region(2, 0), new Region(6, 0), new Region(10, 0), new Region(15, 0), new Region(19, 0) },
+ ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions());
+ }
+
+ @Test
+ public void testCopyPaste() throws MalformedTreeException, BadLocationException {
+ Shell shell= new Shell();
+ TextViewer textViewer= new TextViewer(shell, SWT.NONE);
+ String content= "ababa\nbaba";
+ Document document= new Document(content);
+ List<Region> regions= new ArrayList<>();
+ int index= 0;
+ while ((index= document.get().indexOf('a', index)) >= 0) {
+ regions.add(new Region(index, 0));
+ index++;
+ }
+ textViewer.setDocument(document);
+ SelectionProcessor selectionProcessor= new SelectionProcessor(textViewer);
+ MultiTextSelection selection= new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()]));
+ //
+ AtomicInteger idx= new AtomicInteger();
+ selectionProcessor.doReplace(selection,
+ Arrays.stream(selection.getRegions()).mapToInt(r -> idx.getAndIncrement()).mapToObj(Integer::toString).collect(Collectors.joining(System.lineSeparator())));
+ assertEquals("0ab1ab2a\nb3ab4a", document.get());
+ assertArrayEquals(new IRegion[] { new Region(1, 0), new Region(4, 0), new Region(7, 0), new Region(11, 0), new Region(14, 0) },
+ ((IMultiTextSelection) textViewer.getSelectionProvider().getSelection()).getRegions());
+ }
+
+ @Test
+ public void testBackspace() throws MalformedTreeException {
+ Shell shell= new Shell();
+ TextViewer textViewer= new TextViewer(shell, SWT.NONE);
+ String content = "ababa\nbaba";
+ Document document= new Document(content);
+ List<Region> regions = new ArrayList<>();
+ int index = 0;
+ while ((index = document.get().indexOf('a', index)) >= 0) {
+ regions.add(new Region(index + 1, 0));
+ index++;
+ }
+ textViewer.setDocument(document);
+ IMultiTextSelection selection = new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()]));
+ textViewer.setSelection(selection);
+ Event keyEvent = new Event();
+ keyEvent.type = SWT.KeyDown;
+ keyEvent.widget = textViewer.getTextWidget();
+ keyEvent.display = textViewer.getTextWidget().getDisplay();
+ keyEvent.doit = true;
+ keyEvent.keyCode = SWT.BS;
+ keyEvent.character = 0;
+ textViewer.getTextWidget().notifyListeners(SWT.KeyDown, keyEvent);
+ assertEquals("bb\nbb", textViewer.getDocument().get());
+ ISelection sel = textViewer.getSelection();
+ assertTrue(sel instanceof IMultiTextSelection);
+ selection = (IMultiTextSelection)sel;
+ assertArrayEquals(new IRegion[] {
+ new Region(0, 0),
+ new Region(1, 0),
+ new Region(2, 0),
+ new Region(4, 0),
+ new Region(5, 0)},
+ selection.getRegions());
+ }
+
+ @Test
+ @Ignore(value = "this is currently for manual testing")
+ public void testViewer() {
+ Shell shell= new Shell();
+ Button b = new Button(shell, SWT.PUSH);
+ b.setText("Reset selection");
+ TextViewer textViewer= new TextViewer(shell, SWT.NONE);
+ String content = "ababa\nbaba";
+ Document document= new Document(content);
+ List<Region> regions = new ArrayList<>();
+ int index = 0;
+ while ((index = document.get().indexOf('a', index)) >= 0) {
+ regions.add(new Region(index, 1));
+ index++;
+ }
+ MultiTextSelection selection = new MultiTextSelection(document, regions.toArray(new IRegion[regions.size()]));
+ textViewer.setDocument(document);
+ shell.setLayout(new GridLayout(1, false));
+ GridDataFactory.fillDefaults().grab(true, false).applyTo(shell);
+ shell.pack();
+ shell.setVisible(true);
+ b.addSelectionListener(SelectionListener.widgetSelectedAdapter(e -> {
+ textViewer.setSelection(selection);
+ textViewer.getTextWidget().setFocus();
+ }));
+ DisplayHelper.sleep(textViewer.getTextWidget().getDisplay(), 1000000);
+ }
+}
diff --git a/org.eclipse.jface.text/META-INF/MANIFEST.MF b/org.eclipse.jface.text/META-INF/MANIFEST.MF
index 28416d19f49..947f041b2db 100644
--- a/org.eclipse.jface.text/META-INF/MANIFEST.MF
+++ b/org.eclipse.jface.text/META-INF/MANIFEST.MF
@@ -2,7 +2,7 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.jface.text
-Bundle-Version: 3.18.100.qualifier
+Bundle-Version: 3.19.0.qualifier
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package:
@@ -35,7 +35,7 @@ Export-Package:
Require-Bundle:
org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)",
org.eclipse.text;bundle-version="[3.8.0,4.0.0)";visibility:=reexport,
- org.eclipse.swt;bundle-version="[3.110.100,4.0.0)",
+ org.eclipse.swt;bundle-version="[3.117.0,4.0.0)",
org.eclipse.jface;bundle-version="[3.19.0,4.0.0)"
Import-Package: com.ibm.icu.text
Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java
index f0dac1f9aba..8dfadea9616 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/internal/text/SelectionProcessor.java
@@ -13,7 +13,11 @@
*******************************************************************************/
package org.eclipse.jface.internal.text;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.GC;
@@ -34,6 +38,7 @@ import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.BlockTextSelection;
import org.eclipse.jface.text.IBlockTextSelection;
import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IMultiTextSelection;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ITextSelection;
@@ -41,6 +46,7 @@ import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.MultiStringMatcher;
import org.eclipse.jface.text.MultiStringMatcher.Match;
+import org.eclipse.jface.text.MultiTextSelection;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.text.TextUtilities;
@@ -51,7 +57,7 @@ import org.eclipse.jface.text.TextUtilities;
* @since 3.5
*/
public final class SelectionProcessor {
- private static class Implementation {
+ private static class Implementation<T extends ISelection> {
/**
* Returns a text edit describing the text modification that would be executed if the given
* selection was replaced by <code>replacement</code>.
@@ -61,7 +67,7 @@ public final class SelectionProcessor {
* @return a text edit describing the operation needed to replace <code>selection</code>
* @throws BadLocationException if computing the edit failed
*/
- TextEdit replace(ISelection selection, String replacement) throws BadLocationException {
+ TextEdit replace(T selection, String replacement) throws BadLocationException {
return new MultiTextEdit();
}
@@ -72,7 +78,7 @@ public final class SelectionProcessor {
* @return the text covered by <code>selection</code>
* @throws BadLocationException if computing the edit failed
*/
- String getText(ISelection selection) throws BadLocationException {
+ String getText(T selection) throws BadLocationException {
return ""; //$NON-NLS-1$
}
@@ -86,7 +92,7 @@ public final class SelectionProcessor {
* <code>false</code> otherwise
* @throws BadLocationException if accessing the document failed
*/
- boolean isEmpty(ISelection selection) throws BadLocationException {
+ boolean isEmpty(T selection) throws BadLocationException {
return selection.isEmpty();
}
@@ -99,17 +105,17 @@ public final class SelectionProcessor {
* <code>false</code> otherwise
* @throws BadLocationException if selection is not a valid selection on the target document
*/
- boolean isMultiline(ISelection selection) throws BadLocationException {
+ boolean isMultiline(T selection) throws BadLocationException {
if (selection == null)
throw new NullPointerException();
return false;
}
- TextEdit delete(ISelection selection) throws BadLocationException {
+ TextEdit delete(T selection) throws BadLocationException {
return replace(selection, ""); //$NON-NLS-1$
}
- TextEdit backspace(ISelection selection) throws BadLocationException {
+ TextEdit backspace(T selection) throws BadLocationException {
return replace(selection, ""); //$NON-NLS-1$
}
@@ -124,7 +130,7 @@ public final class SelectionProcessor {
* @return an empty variant of <code>selection</code>
* @throws BadLocationException if accessing the document failed
*/
- ISelection makeEmpty(ISelection selection, boolean beginning) throws BadLocationException {
+ T makeEmpty(T selection, boolean beginning) throws BadLocationException {
return selection;
}
@@ -135,7 +141,7 @@ public final class SelectionProcessor {
* @return the text regions corresponding to <code>selection</code>
* @throws BadLocationException if accessing the document failed
*/
- IRegion[] getRanges(ISelection selection) throws BadLocationException {
+ IRegion[] getRanges(T selection) throws BadLocationException {
return new IRegion[0];
}
@@ -146,7 +152,7 @@ public final class SelectionProcessor {
* @return the number of lines touched by <code>selection</code>
* @throws BadLocationException if accessing the document failed
*/
- int getCoveredLines(ISelection selection) throws BadLocationException {
+ int getCoveredLines(T selection) throws BadLocationException {
return 0;
}
@@ -158,90 +164,311 @@ public final class SelectionProcessor {
* @return the selection that the user expects after the specified replacement operation
* @throws BadLocationException if accessing the document failed
*/
- ISelection makeReplaceSelection(ISelection selection, String replacement) throws BadLocationException {
+ T makeReplaceSelection(T selection, String replacement) throws BadLocationException {
return makeEmpty(selection, false);
}
+
+ /**
+ * Returns the selection after hitting backspace.
+ *
+ * @param selection the selection to be replaced
+ * @return the selection that the user expects after the specified backspace operation
+ * @throws BadLocationException if accessing the document failed
+ */
+ public ISelection makeBackspaceSelection(T selection) throws BadLocationException {
+ return makeEmpty(selection, true);
+ }
+
+ /**
+ * Returns the selection after hitting delete.
+ *
+ * @param selection the selection to be replaced
+ * @return the selection that the user expects after the specified backspace operation
+ * @throws BadLocationException if accessing the document failed
+ */
+ public ISelection makeDeleteSelection(T selection) throws BadLocationException {
+ return makeEmpty(selection, true);
+ }
}
- private final Implementation NULL_IMPLEMENTATION= new Implementation();
+ private final Implementation<ISelection> NULL_IMPLEMENTATION= new Implementation<>();
- private final Implementation RANGE_IMPLEMENTATION= new Implementation() {
+ private final Implementation<ITextSelection> RANGE_IMPLEMENTATION= new Implementation<ITextSelection>() {
@Override
- TextEdit replace(ISelection selection, String replacement) {
- ITextSelection ts= (ITextSelection)selection;
- return new ReplaceEdit(ts.getOffset(), ts.getLength(), replacement);
+ TextEdit replace(ITextSelection selection, String replacement) {
+ return new ReplaceEdit(selection.getOffset(), selection.getLength(), replacement);
}
@Override
- String getText(ISelection selection) {
- ITextSelection ts= (ITextSelection)selection;
- return ts.getText();
+ String getText(ITextSelection selection) {
+ return selection.getText();
}
@Override
- boolean isEmpty(ISelection selection) {
- ITextSelection ts= (ITextSelection)selection;
- return ts.getLength() <= 0;
+ boolean isEmpty(ITextSelection selection) {
+ return selection.getLength() <= 0;
}
@Override
- boolean isMultiline(ISelection selection) throws BadLocationException {
- ITextSelection ts= (ITextSelection)selection;
- return fDocument.getLineOfOffset(ts.getOffset()) < fDocument.getLineOfOffset(ts.getOffset() + ts.getLength());
+ boolean isMultiline(ITextSelection selection) throws BadLocationException {
+ return fDocument.getLineOfOffset(selection.getOffset()) < fDocument.getLineOfOffset(selection.getOffset() + selection.getLength());
}
@Override
- TextEdit delete(ISelection selection) {
- ITextSelection ts= (ITextSelection)selection;
- if (isEmpty(selection))
- return new DeleteEdit(ts.getOffset(), 1);
- return new DeleteEdit(ts.getOffset(), ts.getLength());
+ TextEdit delete(ITextSelection selection) {
+ return isEmpty(selection) ? new DeleteEdit(selection.getOffset(), 1) : new DeleteEdit(selection.getOffset(), selection.getLength());
}
@Override
- TextEdit backspace(ISelection selection) throws BadLocationException {
- ITextSelection ts= (ITextSelection)selection;
- if (isEmpty(selection))
- return new DeleteEdit(ts.getOffset() - 1, 1);
- return new DeleteEdit(ts.getOffset(), ts.getLength());
+ TextEdit backspace(ITextSelection selection) throws BadLocationException {
+ return isEmpty(selection) ? new DeleteEdit(selection.getOffset() - 1, 1) : new DeleteEdit(selection.getOffset(), selection.getLength());
}
@Override
- ISelection makeEmpty(ISelection selection, boolean beginning) {
- ITextSelection ts= (ITextSelection)selection;
+ ITextSelection makeEmpty(ITextSelection selection, boolean beginning) {
return beginning ?
- new TextSelection(fDocument, ts.getOffset(), 0)
- : new TextSelection(fDocument, ts.getOffset() + ts.getLength(), 0);
+ new TextSelection(fDocument, selection.getOffset(), 0) : new TextSelection(fDocument, selection.getOffset() + selection.getLength(), 0);
+ }
+
+ @Override
+ IRegion[] getRanges(ITextSelection selection) {
+ return new IRegion[] { new Region(selection.getOffset(), selection.getLength()) };
+ }
+
+ @Override
+ int getCoveredLines(ITextSelection selection) throws BadLocationException {
+ return selection.getEndLine() - selection.getStartLine() + 1;
+ }
+
+ @Override
+ ITextSelection makeReplaceSelection(ITextSelection selection, String replacement) {
+ return new TextSelection(fDocument, selection.getOffset() + replacement.length(), 0);
+ }
+
+ @Override
+ public ISelection makeBackspaceSelection(ITextSelection selection) throws BadLocationException {
+ if (isEmpty(selection)) {
+ return new TextSelection(Math.max(0, selection.getOffset() - 1), selection.getLength());
+ }
+ return makeEmpty(selection, true);
+ }
+ };
+
+ private final Implementation<IMultiTextSelection> RANGES_IMPLEMENTATION= new Implementation<IMultiTextSelection>() {
+
+ private MultiTextEdit rangeEdits(IMultiTextSelection selection, Function<IRegion, TextEdit> regionToTextEdit) {
+ MultiTextEdit res= new MultiTextEdit();
+ Arrays.stream(selection.getRegions())
+ .map(regionToTextEdit)
+ .filter(Objects::nonNull)
+ .forEach(res::addChild);
+ return res;
+ }
+
+ @Override
+ TextEdit replace(IMultiTextSelection selection, String replacement) {
+ if (replacement.isBlank() || !replacement.contains(System.lineSeparator())) { // simple edit
+ return rangeEdits(selection, region -> new ReplaceEdit(region.getOffset(), region.getLength(), replacement));
+ } else { // paste
+ MultiTextEdit root;
+ root= new MultiTextEdit();
+ String[] delimiters= fDocument.getLegalLineDelimiters();
+ MultiStringMatcher delimiterMatcher= MultiStringMatcher.create(delimiters);
+
+ int lastDelim= 0;
+ for (IRegion region : selection.getRegions()) {
+ String string;
+ if (lastDelim == -1) {
+ string= ""; //$NON-NLS-1$
+ } else {
+ Match m= delimiterMatcher.indexOf(replacement, lastDelim);
+ if (m == null) {
+ string= replacement.substring(lastDelim);
+ lastDelim= -1;
+ } else {
+ string= replacement.substring(lastDelim, m.getOffset());
+ lastDelim= m.getOffset() + m.getText().length();
+ }
+ }
+ TextEdit replace= new ReplaceEdit(region.getOffset(), region.getLength(), string);
+ root.addChild(replace);
+ }
+ // while (lastDelim != -1) {
+ // // more stuff to insert
+ // String string;
+ // Match m= delimiterMatcher.indexOf(replacement, lastDelim);
+ // if (m == null) {
+ // string= replacement.substring(lastDelim);
+ // lastDelim= -1;
+ // } else {
+ // string= replacement.substring(lastDelim, m.getOffset());
+ // lastDelim= m.getOffset() + m.getText().length();
+ // }
+ // endLine++;
+ // TextEdit edit;
+ // if (endLine < fDocument.getNumberOfLines()) {
+ // edit= createReplaceEdit(endLine, visualStartColumn, visualEndColumn, string, delete);
+ // } else {
+ // // insertion reaches beyond the last line
+ // int insertLocation= root.getExclusiveEnd();
+ // int spaces= visualStartColumn;
+ // char[] array= new char[spaces];
+ // Arrays.fill(array, ' ');
+ // string= TextUtilities.getDefaultLineDelimiter(fDocument) + String.valueOf(array) + string;
+ // edit= new InsertEdit(insertLocation, string);
+ // insertLocation+= string.length();
+ // }
+ // root.addChild(edit);
+ // }
+ return root;
+ }
+ }
+
+ @Override
+ String getText(IMultiTextSelection selection) throws BadLocationException {
+ StringBuilder builder = new StringBuilder();
+ for (IRegion region : selection.getRegions()) {
+ builder.append(fDocument.get(region.getOffset(), region.getLength()));
+ }
+ return builder.toString();
}
@Override
- IRegion[] getRanges(ISelection selection) {
- ITextSelection ts= (ITextSelection)selection;
- return new IRegion[] { new Region(ts.getOffset(), ts.getLength()) };
+ boolean isEmpty(IMultiTextSelection selection) {
+ return Arrays.stream(selection.getRegions()).allMatch(r -> r.getLength() == 0);
}
@Override
- int getCoveredLines(ISelection selection) throws BadLocationException {
- ITextSelection ts= (ITextSelection)selection;
- return ts.getEndLine() - ts.getStartLine() + 1;
+ boolean isMultiline(IMultiTextSelection selection) throws BadLocationException {
+ int line = -1;
+ for (IRegion region : selection.getRegions()) {
+ if (line == -1) {
+ line = fDocument.getLineOfOffset(region.getOffset());
+ } else if (
+ line != fDocument.getLineOfOffset(region.getOffset()) ||
+ line != fDocument.getLineOfOffset(region.getOffset() + region.getLength())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ TextEdit delete(IMultiTextSelection selection) {
+ if (isEmpty(selection)) {
+ return rangeEdits(selection, region -> new DeleteEdit(region.getOffset(), 1));
+ }
+ return rangeEdits(selection, region -> new DeleteEdit(region.getOffset(), region.getLength()));
+ }
+
+ @Override
+ TextEdit backspace(IMultiTextSelection selection) throws BadLocationException {
+ if (isEmpty(selection)) {
+ return rangeEdits(selection, region -> region.getOffset() == 0 ? null : new DeleteEdit(region.getOffset() - 1, 1));
+ }
+ return rangeEdits(selection, region -> {
+ if (region.getLength() > 0) {
+ return new DeleteEdit(region.getOffset(), region.getLength());
+ } else if (region.getOffset() > 0) {
+ return new DeleteEdit(region.getOffset() - 1, 1);
+ } else {
+ return null;
+ }
+ });
+ }
+
+ @Override
+ IMultiTextSelection makeEmpty(IMultiTextSelection selection, boolean beginning) {
+ int[] deletedCount= new int[] { 0 };
+ return new MultiTextSelection(fDocument, Arrays.stream(selection.getRegions()).map(region -> {
+ Region res= beginning
+ ? new Region(region.getOffset() - deletedCount[0], 0)
+ : new Region(region.getOffset() - deletedCount[0] + region.getLength(), 0);
+ deletedCount[0]+= region.getLength();
+ return res;
+ }).toArray(Region[]::new));
+ }
+
+ @Override
+ IRegion[] getRanges(IMultiTextSelection selection) {
+ return selection.getRegions().clone();
+ }
+
+ @Override
+ int getCoveredLines(IMultiTextSelection selection) throws BadLocationException {
+ int res = 0;
+ int lastLine = -1;
+ for (IRegion region : selection.getRegions()) {
+ if (lastLine == fDocument.getLineOfOffset(region.getOffset())) {
+ res--; // ignore 1st line if already processed
+ }
+ res++; // at least 1 line for the range
+ res+= (fDocument.getLineOfOffset(region.getOffset() + region.getLength()) - fDocument.getLineOfOffset(region.getOffset()));
+ lastLine = fDocument.getLineOfOffset(region.getOffset() + region.getLength());
+ }
+ return res;
+ }
+
+ @Override
+ IMultiTextSelection makeReplaceSelection(IMultiTextSelection selection, String replacement) {
+ if (!replacement.contains(System.lineSeparator())) { // simple edit
+ int[] offset= new int[] { 0 };
+ return new MultiTextSelection(fDocument,
+ Arrays.stream(selection.getRegions()).map(region -> {
+ Region res= new Region(region.getOffset() + offset[0] + replacement.length(), 0);
+ offset[0]+= (replacement.length() - region.getLength());
+ return res;
+ }).toArray(Region[]::new));
+ } else { // paste
+ TextEdit edit= replace(selection, replacement);
+ if (edit instanceof MultiTextEdit) {
+ int offsetDelta= 0;
+ List<IRegion> afterEdit= new ArrayList<>(Math.min(edit.getLength(), selection.getLength()));
+ for (int i= 0; i < Math.min(edit.getChildrenSize(), selection.getLength()); i++) {
+ ReplaceEdit currentEdit= (ReplaceEdit) edit.getChildren()[i];
+ offsetDelta+= currentEdit.getText().length() - currentEdit.getRegion().getLength();
+ afterEdit.add(new Region(currentEdit.getOffset() + offsetDelta, 0));
+ }
+ return new MultiTextSelection(fDocument, afterEdit.toArray(IRegion[]::new));
+ } else {
+ return new TextSelection(fDocument, edit.getOffset() + replacement.length() - edit.getRegion().getLength(), 0);
+ }
+ }
}
@Override
- ISelection makeReplaceSelection(ISelection selection, String replacement) {
- ITextSelection ts= (ITextSelection)selection;
- return new TextSelection(fDocument, ts.getOffset() + replacement.length(), 0);
+ public ISelection makeBackspaceSelection(IMultiTextSelection selection) throws BadLocationException {
+ int[] removedChars= { 0 };
+ return new MultiTextSelection(fDocument,
+ Arrays.stream(selection.getRegions()).map(region -> {
+ int length= region.getLength() != 0 ? region.getLength() : (region.getOffset() != 0 ? 1 : 0);
+ Region res= new Region(Math.max(0, region.getOffset() - removedChars[0] - (region.getLength() == 0 ? length : 0)), 0);
+ removedChars[0]+= length;
+ return res;
+ }).toArray(Region[]::new));
+ }
+
+ @Override
+ public ISelection makeDeleteSelection(IMultiTextSelection selection) throws BadLocationException {
+ int[] removedChars= { 0 };
+ return new MultiTextSelection(fDocument,
+ Arrays.stream(selection.getRegions()).map(region -> {
+ int length= region.getLength() != 0 ? region.getLength() : 1;
+ Region res= new Region(Math.max(0, region.getOffset() - removedChars[0]), 0);
+ removedChars[0]+= length;
+ return res;
+ }).toArray(Region[]::new));
}
};
- private final Implementation COLUMN_IMPLEMENTATION= new Implementation() {
- private TextEdit replace(ISelection selection, String replacement, boolean delete) throws BadLocationException {
+ private final Implementation<IBlockTextSelection> COLUMN_IMPLEMENTATION= new Implementation<IBlockTextSelection>() {
+ private TextEdit replace(IBlockTextSelection selection, String replacement, boolean delete) throws BadLocationException {
try {
MultiTextEdit root;
- IBlockTextSelection cts= (IBlockTextSelection)selection;
- int startLine= cts.getStartLine();
- int endLine= cts.getEndLine();
- int startColumn= cts.getStartColumn();
- int endColumn= cts.getEndColumn();
+ int startLine= selection.getStartLine();
+ int endLine= selection.getEndLine();
+ int startColumn= selection.getStartColumn();
+ int endColumn= selection.getEndColumn();
int visualStartColumn= computeVisualColumn(startLine, startColumn);
int visualEndColumn= computeVisualColumn(endLine, endColumn);
root= new MultiTextEdit();
@@ -301,18 +528,17 @@ public final class SelectionProcessor {
}
@Override
- TextEdit replace(ISelection selection, String replacement) throws BadLocationException {
+ TextEdit replace(IBlockTextSelection selection, String replacement) throws BadLocationException {
return replace(selection, replacement, false);
}
@Override
- String getText(ISelection selection) throws BadLocationException {
- IBlockTextSelection cts= (IBlockTextSelection)selection;
- StringBuilder buf= new StringBuilder(cts.getLength());
- int startLine= cts.getStartLine();
- int endLine= cts.getEndLine();
- int startColumn= cts.getStartColumn();
- int endColumn= cts.getEndColumn();
+ String getText(IBlockTextSelection selection) throws BadLocationException {
+ StringBuilder buf= new StringBuilder(selection.getLength());
+ int startLine= selection.getStartLine();
+ int endLine= selection.getEndLine();
+ int startColumn= selection.getStartColumn();
+ int endColumn= selection.getEndColumn();
int visualStartColumn= computeVisualColumn(startLine, startColumn);
int visualEndColumn= computeVisualColumn(endLine, endColumn);
@@ -326,79 +552,81 @@ public final class SelectionProcessor {
}
@Override
- boolean isEmpty(ISelection selection) throws BadLocationException {
- IBlockTextSelection cts= (IBlockTextSelection)selection;
- int startLine= cts.getStartLine();
- int endLine= cts.getEndLine();
- int startColumn= cts.getStartColumn();
- int endColumn= cts.getEndColumn();
+ boolean isEmpty(IBlockTextSelection selection) throws BadLocationException {
+ int startLine= selection.getStartLine();
+ int endLine= selection.getEndLine();
+ int startColumn= selection.getStartColumn();
+ int endColumn= selection.getEndColumn();
int visualStartColumn= computeVisualColumn(startLine, startColumn);
int visualEndColumn= computeVisualColumn(endLine, endColumn);
return visualEndColumn == visualStartColumn;
}
@Override
- boolean isMultiline(ISelection selection) {
- ITextSelection ts= (ITextSelection)selection;
- return ts.getEndLine() > ts.getStartLine();
+ boolean isMultiline(IBlockTextSelection selection) {
+ return selection.getEndLine() > selection.getStartLine();
}
@Override
- TextEdit delete(ISelection selection) throws BadLocationException {
+ TextEdit delete(IBlockTextSelection selection) throws BadLocationException {
if (isEmpty(selection)) {
- IBlockTextSelection cts= (IBlockTextSelection)selection;
- selection= new BlockTextSelection(fDocument, cts.getStartLine(), cts.getStartColumn(), cts.getEndLine(), cts.getEndColumn() + 1, fTabWidth);
+ selection= new BlockTextSelection(fDocument, selection.getStartLine(), selection.getStartColumn(), selection.getEndLine(), selection.getEndColumn() + 1, fTabWidth);
}
return replace(selection, "", true); //$NON-NLS-1$
}
@Override
- TextEdit backspace(ISelection selection) throws BadLocationException {
- IBlockTextSelection cts= (IBlockTextSelection)selection;
- if (isEmpty(selection) && cts.getStartColumn() > 0) {
- selection= new BlockTextSelection(fDocument, cts.getStartLine(), cts.getStartColumn() - 1, cts.getEndLine(), cts.getEndColumn(), fTabWidth);
+ TextEdit backspace(IBlockTextSelection selection) throws BadLocationException {
+ if (isEmpty(selection) && selection.getStartColumn() > 0) {
+ selection= new BlockTextSelection(fDocument, selection.getStartLine(), selection.getStartColumn() - 1, selection.getEndLine(), selection.getEndColumn(), fTabWidth);
}
return replace(selection, ""); //$NON-NLS-1$
}
@Override
- ISelection makeEmpty(ISelection selection, boolean beginning) throws BadLocationException {
- IBlockTextSelection cts= (IBlockTextSelection)selection;
+ IBlockTextSelection makeEmpty(IBlockTextSelection selection, boolean beginning) throws BadLocationException {
int startLine, startColumn, endLine, endColumn;
if (beginning) {
- startLine= cts.getStartLine();
- startColumn= cts.getStartColumn();
- endLine= cts.getEndLine();
+ startLine= selection.getStartLine();
+ startColumn= selection.getStartColumn();
+ endLine= selection.getEndLine();
endColumn= computeCharacterColumn(endLine, computeVisualColumn(startLine, startColumn));
} else {
- endLine= cts.getEndLine();
- endColumn= cts.getEndColumn();
- startLine= cts.getStartLine();
+ endLine= selection.getEndLine();
+ endColumn= selection.getEndColumn();
+ startLine= selection.getStartLine();
startColumn= computeCharacterColumn(startLine, computeVisualColumn(endLine, endColumn));
}
return new BlockTextSelection(fDocument, startLine, startColumn, endLine, endColumn, fTabWidth);
}
@Override
- ISelection makeReplaceSelection(ISelection selection, String replacement) throws BadLocationException {
- IBlockTextSelection bts= (IBlockTextSelection)selection;
+ IBlockTextSelection makeReplaceSelection(IBlockTextSelection selection, String replacement) throws BadLocationException {
Match m= MultiStringMatcher.indexOf(replacement, 0, fDocument.getLegalLineDelimiters());
int length= m != null ? m.getOffset() : replacement.length();
- int startLine= bts.getStartLine();
- int column= bts.getStartColumn() + length;
- int endLine= bts.getEndLine();
+ int startLine= selection.getStartLine();
+ int column= selection.getStartColumn() + length;
+ int endLine= selection.getEndLine();
int endColumn= computeCharacterColumn(endLine, computeVisualColumn(startLine, column));
return new BlockTextSelection(fDocument, startLine, column, endLine, endColumn, fTabWidth);
}
@Override
- IRegion[] getRanges(ISelection selection) throws BadLocationException {
- IBlockTextSelection cts= (IBlockTextSelection)selection;
- final int startLine= cts.getStartLine();
- final int endLine= cts.getEndLine();
- int visualStartColumn= computeVisualColumn(startLine, cts.getStartColumn());
- int visualEndColumn= computeVisualColumn(endLine, cts.getEndColumn());
+ public ISelection makeBackspaceSelection(IBlockTextSelection selection) throws BadLocationException {
+ if (!isEmpty(selection)) {
+ return makeEmpty(selection, true);
+ }
+ int column= Math.max(0, selection.getStartColumn());
+ return new BlockTextSelection(fDocument, selection.getStartLine(), column, selection.getEndLine(), column, fTabWidth);
+ }
+
+ @Override
+ IRegion[] getRanges(IBlockTextSelection selection) throws BadLocationException {
+ final int startLine= selection.getStartLine();
+ final int endLine= selection.getEndLine();
+ int visualStartColumn= computeVisualColumn(startLine, selection.getStartColumn());
+ int visualEndColumn= computeVisualColumn(endLine, selection.getEndColumn());
IRegion[] ranges= new IRegion[endLine - startLine + 1];
for (int line= startLine; line <= endLine; line++) {
@@ -415,9 +643,8 @@ public final class SelectionProcessor {
}
@Override
- int getCoveredLines(ISelection selection) throws BadLocationException {
- ITextSelection ts= (ITextSelection)selection;
- return ts.getEndLine() - ts.getStartLine() + 1;
+ int getCoveredLines(IBlockTextSelection selection) throws BadLocationException {
+ return selection.getEndLine() - selection.getStartLine() + 1;
}
private TextEdit createReplaceEdit(int line, int visualStartColumn, int visualEndColumn, String replacement, boolean delete) throws BadLocationException {
@@ -691,6 +918,14 @@ public final class SelectionProcessor {
return getImplementation(selection).makeReplaceSelection(selection, replacement);
}
+ private ISelection makeBackspaceSelection(ISelection selection) throws BadLocationException {
+ return getImplementation(selection).makeBackspaceSelection(selection);
+ }
+
+ private ISelection makeDeleteSelection(ISelection selection) throws BadLocationException {
+ return getImplementation(selection).makeDeleteSelection(selection);
+ }
+
/**
* Convenience method that applies the edit returned from {@link #delete(ISelection)} to the
* underlying document.
@@ -706,7 +941,7 @@ public final class SelectionProcessor {
try {
edit.apply(fDocument, TextEdit.UPDATE_REGIONS);
if (fSelectionProvider != null) {
- ISelection empty= makeEmpty(selection, true);
+ ISelection empty= makeDeleteSelection(selection);
fSelectionProvider.setSelection(empty);
}
} finally {
@@ -716,6 +951,30 @@ public final class SelectionProcessor {
}
/**
+ * Convenience method that applies the edit returned from {@link #backspace(ISelection)} to the
+ * underlying document.
+ *
+ * @param selection the selection to delete
+ * @throws BadLocationException if accessing the document failed
+ */
+ public void doBackspace(ISelection selection) throws BadLocationException {
+ TextEdit edit= backspace(selection);
+ boolean complex= edit.hasChildren();
+ if (complex && fRewriteTarget != null)
+ fRewriteTarget.beginCompoundChange();
+ try {
+ ISelection newSelection= makeBackspaceSelection(selection);
+ edit.apply(fDocument, TextEdit.UPDATE_REGIONS);
+ if (fSelectionProvider != null) {
+ fSelectionProvider.setSelection(newSelection);
+ }
+ } finally {
+ if (complex && fRewriteTarget != null)
+ fRewriteTarget.endCompoundChange();
+ }
+ }
+
+ /**
* Convenience method that applies the edit returned from {@link #replace(ISelection, String)}
* to the underlying document and adapts the selection accordingly.
*
@@ -770,12 +1029,15 @@ public final class SelectionProcessor {
* @param selection the selection
* @return the corresponding processor implementation
*/
- private Implementation getImplementation(ISelection selection) {
- if (selection instanceof IBlockTextSelection)
- return COLUMN_IMPLEMENTATION;
- else if (selection instanceof ITextSelection)
- return RANGE_IMPLEMENTATION;
- else
- return NULL_IMPLEMENTATION;
+ @SuppressWarnings("unchecked")
+ private <T extends ISelection> Implementation<T> getImplementation(ISelection selection) {
+ if (selection instanceof IBlockTextSelection) {
+ return (Implementation<T>) COLUMN_IMPLEMENTATION;
+ } else if (selection instanceof IMultiTextSelection) {
+ return (Implementation<T>) RANGES_IMPLEMENTATION;
+ } else if (selection instanceof ITextSelection) {
+ return (Implementation<T>) RANGE_IMPLEMENTATION;
+ }
+ return (Implementation<T>) NULL_IMPLEMENTATION;
}
}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java
index 5e931d2a83e..3a4b6494646 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IBlockTextSelection.java
@@ -71,11 +71,13 @@ public interface IBlockTextSelection extends ITextSelection {
*/
@Override
String getText();
+
/**
* Returns a non-empty array containing the selected text range for each line covered by the
* selection.
*
* @return an array containing a the covered text range for each line covered by the receiver
+ * @see IMultiTextSelection#getRegions()
*/
IRegion[] getRegions();
}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java
new file mode 100644
index 00000000000..d208cc75262
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/IMultiTextSelection.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2019 Red Hat Inc. and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.jface.text;
+
+/**
+ * This interface represents a textual selection that can be made of multiple discontinued selected
+ * ranges.
+ *
+ * @since 3.19
+ */
+public interface IMultiTextSelection extends ITextSelection {
+
+ /**
+ * Returns a non-empty array containing the selected text range for each line covered by the
+ * selection.
+ *
+ * @return an array containing a the covered text range for each line covered by the receiver
+ */
+ IRegion[] getRegions();
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java
new file mode 100644
index 00000000000..87eec04bf1e
--- /dev/null
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/MultiTextSelection.java
@@ -0,0 +1,111 @@
+/*******************************************************************************
+ * Copyright (c) 2019 IBM Corporation and others.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.jface.text;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.stream.Collectors;
+
+import org.eclipse.core.runtime.Assert;
+
+/**
+ *
+ * @since 3.19
+ */
+public class MultiTextSelection implements IMultiTextSelection {
+
+ private final IDocument fDocument;
+
+ private final IRegion[] fRegions;
+
+ private final int fLength;
+
+ private final int fStartLine;
+
+ private final int fLastLine;
+
+ private final String fText;
+
+ public MultiTextSelection(IDocument document, IRegion[] regions) {
+ Assert.isNotNull(document);
+ Assert.isNotNull(regions);
+ fDocument= document;
+ fRegions= Arrays.copyOf(regions, regions.length);
+ Arrays.sort(fRegions, Comparator.comparingInt(IRegion::getOffset).thenComparingInt(IRegion::getLength));
+ if (fRegions != null && fRegions.length > 0) {
+ IRegion lastRegion= fRegions[fRegions.length - 1];
+ fLength= lastRegion.getOffset() + lastRegion.getLength() - fRegions[0].getOffset();
+ fStartLine= getLineOfOffset(document, fRegions[0].getOffset());
+ fLastLine= getLineOfOffset(document, lastRegion.getOffset() + lastRegion.getLength());
+ fText= Arrays.stream(fRegions)
+ .map(region -> {
+ try {
+ return fDocument.get(region.getOffset(), region.getLength());
+ } catch (BadLocationException e) {
+ return e.getMessage();
+ }
+ })
+ .collect(Collectors.joining());
+ } else {
+ fLength= 0;
+ fStartLine= 0;
+ fLastLine= 0;
+ fText= null;
+ }
+ }
+
+ private static int getLineOfOffset(IDocument document, int offset) {
+ try {
+ return document.getLineOfOffset(offset);
+ } catch (BadLocationException e) {
+ return 0;
+ }
+ }
+
+ @Override
+ public int getOffset() {
+ if (fRegions.length > 0) {
+ return fRegions[0].getOffset();
+ }
+ return 0;
+ }
+
+ @Override
+ public int getLength() {
+ return fLength;
+ }
+
+ @Override
+ public int getStartLine() {
+ return fStartLine;
+ }
+
+ @Override
+ public int getEndLine() {
+ return fLastLine;
+ }
+
+ @Override
+ public String getText() {
+ return fText;
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return Arrays.stream(fRegions).allMatch(region -> region.getLength() == 0);
+ }
+
+ @Override
+ public IRegion[] getRegions() {
+ return fRegions;
+ }
+
+}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java
index 4352ed95a3d..4472bcf0374 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextSelection.java
@@ -25,7 +25,7 @@ import org.eclipse.core.runtime.Platform;
* generated from a selection provider, it only remembers its offset and length
* and computes the remaining information on request.</p>
*/
-public class TextSelection implements ITextSelection {
+public class TextSelection implements ITextSelection, IMultiTextSelection {
/**
* Debug option for asserting valid offset and length.
@@ -225,5 +225,10 @@ public class TextSelection implements ITextSelection {
sb.append("]"); //$NON-NLS-1$
return sb.toString();
}
+
+ @Override
+ public IRegion[] getRegions() {
+ return new IRegion[] { new Region(getOffset(), getLength()) };
+ }
}
diff --git a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java
index 099931c4833..9af2c0aaa2f 100644
--- a/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java
+++ b/org.eclipse.jface.text/src/org/eclipse/jface/text/TextViewer.java
@@ -24,6 +24,7 @@ import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.Set;
import java.util.regex.PatternSyntaxException;
@@ -66,8 +67,6 @@ import org.eclipse.swt.widgets.Shell;
import org.eclipse.core.runtime.Assert;
-import org.eclipse.text.edits.TextEdit;
-
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.internal.text.NonDeletingPositionUpdater;
import org.eclipse.jface.internal.text.SelectionProcessor;
@@ -1077,7 +1076,7 @@ public class TextViewer extends Viewer implements
*/
private final class ViewerState {
/** The position tracking the selection. */
- private Position fSelection;
+ private Position[] fSelections;
/** <code>true</code> if {@link #fSelection} was originally backwards. */
private boolean fReverseSelection;
/** <code>true</code> if the selection has been updated while in redraw(off) mode. */
@@ -1108,24 +1107,26 @@ public class TextViewer extends Viewer implements
*
* @return the normalized selection
*/
- public Point getSelection() {
- if (fSelection == null)
- return new Point(-1, -1);
- return new Point(fSelection.getOffset(), fSelection.getLength());
+ public Point[] getSelection() {
+ if (fSelections == null)
+ return new Point[0];
+ return Arrays.stream(fSelections).map(position -> new Point(position.getOffset(), position.getLength())).toArray(Point[]::new);
}
/**
* Updates the selection.
*
- * @param offset the new selection offset
- * @param length the new selection length
+ * @param selections new selections
*/
- public void updateSelection(int offset, int length) {
+ public void updateSelection(Position[] selections) {
fSelectionSet= true;
- if (fSelection == null)
- fSelection= new Position(offset, length);
- else
- updatePosition(fSelection, offset, length);
+ if (fSelections == null) {
+ fSelections= Arrays.copyOf(selections, selections.length);
+ } else {
+ fSelections= Arrays.stream(selections)
+ .map(position -> new Position(position.getOffset(), position.getLength())) /*force deleted=false*/
+ .toArray(Position[]::new);
+ }
}
/**
@@ -1138,18 +1139,18 @@ public class TextViewer extends Viewer implements
public void restore(boolean restoreViewport) {
if (isConnected())
disconnect();
- if (fSelection != null) {
- if (fSelection instanceof ColumnPosition) {
- ColumnPosition cp= (ColumnPosition)fSelection;
+ if (fSelections != null && fSelections.length > 0) {
+ if (fSelections[0] instanceof ColumnPosition) {
+ ColumnPosition cp= (ColumnPosition) fSelections[0];
IDocument document= fDocument;
try {
- int startLine= document.getLineOfOffset(fSelection.getOffset());
+ int startLine= document.getLineOfOffset(cp.getOffset());
int startLineOffset= document.getLineOffset(startLine);
- int selectionEnd= fSelection.getOffset() + fSelection.getLength();
+ int selectionEnd= cp.getOffset() + cp.getLength();
int endLine= document.getLineOfOffset(selectionEnd);
int endLineOffset= document.getLineOffset(endLine);
int tabs= getTextWidget().getTabs();
- int startColumn= fSelection.getOffset() - startLineOffset + cp.fStartColumn;
+ int startColumn= cp.getOffset() - startLineOffset + cp.fStartColumn;
int endColumn= selectionEnd - endLineOffset + cp.fEndColumn;
setSelection(new BlockTextSelection(document, startLine, startColumn, endLine, endColumn, tabs));
} catch (BadLocationException e) {
@@ -1157,13 +1158,11 @@ public class TextViewer extends Viewer implements
setSelectedRange(cp.getOffset(), cp.getLength());
}
} else {
- int offset= fSelection.getOffset();
- int length= fSelection.getLength();
- if (fReverseSelection) {
- offset+= length;
- length= -length;
- }
- setSelectedRange(offset, length);
+ setSelectedRanges(Arrays.stream(fSelections)
+ .map(position -> fReverseSelection
+ ? new Region(position.getOffset() + position.getLength(), -position.getLength())
+ : new Region(position.getOffset(), position.getLength()))
+ .toArray(IRegion[]::new));
}
if (restoreViewport)
updateViewport();
@@ -1177,7 +1176,7 @@ public class TextViewer extends Viewer implements
*/
private void updateViewport() {
if (fSelectionSet) {
- revealRange(fSelection.getOffset(), fSelection.getLength());
+ revealRange(fSelections[0].getOffset(), fSelections[0].getLength());
} else if (fStableLine != null) {
int stableLine;
try {
@@ -1216,17 +1215,25 @@ public class TextViewer extends Viewer implements
IBlockTextSelection bts= (IBlockTextSelection) selection;
int startVirtual= Math.max(0, bts.getStartColumn() - document.getLineInformationOfOffset(bts.getOffset()).getLength());
int endVirtual= Math.max(0, bts.getEndColumn() - document.getLineInformationOfOffset(bts.getOffset() + bts.getLength()).getLength());
- fSelection= new ColumnPosition(bts.getOffset(), bts.getLength(), startVirtual, endVirtual);
+ fSelections= new Position[] { new ColumnPosition(bts.getOffset(), bts.getLength(), startVirtual, endVirtual) };
+ } else if (selection instanceof IMultiTextSelection && ((IMultiTextSelection) selection).getRegions().length > 1) {
+ IMultiTextSelection multiSelection= (IMultiTextSelection) selection;
+ fSelections= Arrays.stream(multiSelection.getRegions())
+ .map(region -> new Position(region.getOffset(), region.getLength()))
+ .toArray(Position[]::new);
+ fReverseSelection= (fTextWidget.getCaretOffset() == fSelections[0].getOffset());
} else {
Point range= fTextWidget.getSelectionRange();
int caretOffset= fTextWidget.getCaretOffset();
fReverseSelection= caretOffset == range.x;
Point selectionRange= getSelectedRange();
- fSelection= new Position(selectionRange.x, selectionRange.y);
+ fSelections= new Position[] { new Position(selectionRange.x, selectionRange.y) };
}
fSelectionSet= false;
- fUpdaterDocument.addPosition(fUpdaterCategory, fSelection);
+ for (Position position : fSelections) {
+ fUpdaterDocument.addPosition(fUpdaterCategory, position);
+ }
int stableLine= getStableLine();
int stableWidgetLine= modelLine2WidgetLine(stableLine);
@@ -1245,20 +1252,6 @@ public class TextViewer extends Viewer implements
}
/**
- * Updates a position with the given information and clears its deletion state.
- *
- * @param position the position to update
- * @param offset the new selection offset
- * @param length the new selection length
- */
- private void updatePosition(Position position, int offset, int length) {
- position.setOffset(offset);
- position.setLength(length);
- // http://bugs.eclipse.org/bugs/show_bug.cgi?id=32795
- position.isDeleted= false;
- }
-
- /**
* Returns the document line to keep visually stable. If the caret line is (partially)
* visible, it is returned, otherwise the topmost (partially) visible line is returned.
*
@@ -1291,7 +1284,9 @@ public class TextViewer extends Viewer implements
private void disconnect() {
Assert.isTrue(isConnected());
try {
- fUpdaterDocument.removePosition(fUpdaterCategory, fSelection);
+ for (Position selection : fSelections) {
+ fUpdaterDocument.removePosition(fUpdaterCategory, selection);
+ }
fUpdaterDocument.removePosition(fUpdaterCategory, fStableLine);
fUpdaterDocument.removePositionUpdater(fUpdater);
fUpdater= null;
@@ -1513,7 +1508,7 @@ public class TextViewer extends Viewer implements
* Last selection range sent to selection change listeners.
* @since 3.0
*/
- private IRegion fLastSentSelectionChange;
+ private IRegion[] fLastSentSelectionChange;
/**
* The registered post selection changed listeners.
* @since 3.0
@@ -2251,9 +2246,13 @@ public class TextViewer extends Viewer implements
@Override
public Point getSelectedRange() {
-
- if (!redraws() && fViewerState != null)
- return fViewerState.getSelection();
+ // TODO multi-cursor: this method is by designed single selection
+ if (!redraws() && fViewerState != null) {
+ Point[] selections= fViewerState.getSelection();
+ if (selections.length > 0) {
+ return selections[0];
+ }
+ }
if (fTextWidget != null) {
Point p= fTextWidget.getSelectionRange();
@@ -2267,26 +2266,46 @@ public class TextViewer extends Viewer implements
@Override
public void setSelectedRange(int selectionOffset, int selectionLength) {
+ setSelectedRanges(new IRegion[] { new Region(selectionOffset, selectionLength) });
+ }
+
+ private static Position toPosition(IRegion region) {
+ return region.getLength() < 0 ? new Position(region.getOffset() + region.getLength(), -region.getLength()) : new Position(region.getOffset(), region.getLength());
+ }
+ /**
+ *
+ * @param ranges are in model (document) domain
+ */
+ private void setSelectedRanges(IRegion[] ranges) {
if (!redraws()) {
if (fViewerState != null)
- fViewerState.updateSelection(selectionOffset, selectionLength);
+ fViewerState.updateSelection(Arrays.stream(ranges).map(TextViewer::toPosition).toArray(Position[]::new));
return;
}
if (fTextWidget == null)
return;
- IRegion widgetSelection= modelRange2ClosestWidgetRange(new Region(selectionOffset, selectionLength));
- if (widgetSelection != null) {
-
- int[] selectionRange= new int[] { widgetSelection.getOffset(), widgetSelection.getLength() };
- validateSelectionRange(selectionRange);
- if (selectionRange[0] >= 0) {
- fTextWidget.setSelectionRange(selectionRange[0], selectionRange[1]);
- selectionChanged(selectionRange[0], selectionRange[1]);
- }
+ IRegion[] widgetSelection= Arrays.stream(ranges)
+ .map(range -> new Region(range.getOffset(), range.getLength()))
+ .map(this::modelRange2ClosestWidgetRange)
+ .filter(Objects::nonNull)
+ .map(range -> new int[] { range.getOffset(), range.getLength() })
+ .map(range -> {
+ validateSelectionRange(range);
+ return range;
+ })
+ .map(rangeAsArray -> new Region(rangeAsArray[0], rangeAsArray[1]))
+ .filter(widgetRange -> widgetRange.getOffset() >= 0)
+ .toArray(IRegion[]::new);
+ int[] widgetRanges= new int[2 * widgetSelection.length];
+ for (int i= 0; i < widgetSelection.length; i++) {
+ widgetRanges[2 * i]= widgetSelection[i].getOffset();
+ widgetRanges[2 * i + 1]= widgetSelection[i].getLength();
}
+ fTextWidget.setSelectionRanges(widgetRanges);
+ selectionChanged(widgetSelection);
}
/**
@@ -2415,6 +2434,14 @@ public class TextViewer extends Viewer implements
}
if (reveal)
revealRange(s.getOffset(), s.getLength());
+ } else if (selection instanceof IMultiTextSelection) {
+ IMultiTextSelection multiSelection= (IMultiTextSelection) selection;
+ setSelectedRanges(Arrays.stream(multiSelection.getRegions())
+ .map(region -> new Region(region.getOffset(), region.getLength()))
+ .toArray(IRegion[]::new));
+ if (reveal && multiSelection.getRegions().length > 0) {
+ revealRange(multiSelection.getRegions()[0].getOffset(), multiSelection.getRegions()[0].getLength());
+ }
} else if (selection instanceof ITextSelection) {
ITextSelection s= (ITextSelection) selection;
setSelectedRange(s.getOffset(), s.getLength());
@@ -2466,11 +2493,14 @@ public class TextViewer extends Viewer implements
}
}
- Point p= getSelectedRange();
- if (p.x == -1 || p.y == -1)
- return TextSelection.emptySelection();
-
- return new TextSelection(getDocument(), p.x, p.y);
+ int[] ranges= fTextWidget.getSelectionRanges();
+ IRegion[] selectedRanges= new IRegion[ranges.length / 2];
+ for (int i= 0; i < selectedRanges.length; i++) {
+ int start= widgetOffset2ModelOffset(ranges[2 * i]);
+ int end= widgetOffset2ModelOffset(ranges[2 * i] + ranges[2 * i + 1]);
+ selectedRanges[i]= new Region(start, end - start);
+ }
+ return toSelection(selectedRanges);
}
/**
@@ -2595,9 +2625,13 @@ public class TextViewer extends Viewer implements
* @param length the length of the newly selected range in the visible document
*/
protected void selectionChanged(int offset, int length) {
+ selectionChanged(new IRegion[] { new Region(offset, length) });
+ }
+
+ private void selectionChanged(IRegion[] widgetRanges) {
updateSelectionCache();
queuePostSelectionChanged(true);
- fireSelectionChanged(offset, length);
+ fireSelectionChanged(widgetRanges);
}
/**
@@ -2608,21 +2642,34 @@ public class TextViewer extends Viewer implements
* @since 3.0
*/
protected void fireSelectionChanged(int offset, int length) {
+ fireSelectionChanged(new Region[] { new Region(offset, length) });
+ }
+
+ private void fireSelectionChanged(IRegion[] widgetRanges) {
if (redraws()) {
- if (length < 0) {
- length= -length;
- offset= offset + length;
- }
- IRegion r= widgetRange2ModelRange(new Region(offset, length));
- if ((r != null && !r.equals(fLastSentSelectionChange)) || r == null) {
- fLastSentSelectionChange= r;
- ISelection selection= r != null ? new TextSelection(getDocument(), r.getOffset(), r.getLength()) : TextSelection.emptySelection();
- SelectionChangedEvent event= new SelectionChangedEvent(this, selection);
+ IRegion[] ranges = Arrays.stream(widgetRanges)
+ .map(range -> range.getLength() < 0 ? new Region(range.getOffset() + range.getLength(), -range.getLength()) : range)
+ .map(this::widgetRange2ModelRange)
+ .filter(Objects::nonNull)
+ .toArray(IRegion[]::new);
+ if (ranges.length == 0 || !Arrays.equals(ranges, fLastSentSelectionChange)) {
+ fLastSentSelectionChange= ranges;
+ SelectionChangedEvent event= new SelectionChangedEvent(this, toSelection(ranges));
fireSelectionChanged(event);
}
}
}
+ private ITextSelection toSelection(IRegion[] ranges) {
+ ITextSelection selection= TextSelection.emptySelection();
+ if (ranges.length == 1) {
+ selection= new TextSelection(getDocument(), ranges[0].getOffset(), ranges[0].getLength());
+ } else if (ranges.length > 1) {
+ selection= new MultiTextSelection(getDocument(), ranges);
+ }
+ return selection;
+ }
+
/**
* Sends the given event to all registered post selection changed listeners.
*
@@ -3646,12 +3693,16 @@ public class TextViewer extends Viewer implements
return;
}
+ ITextSelection selection= (ITextSelection)getSelection();
if (fTextWidget.getBlockSelection() && (e.text == null || e.text.length() < 2)) {
Point sel = fTextWidget.getSelection();
if (fTextWidget.getLineAtOffset(sel.x) != fTextWidget.getLineAtOffset(sel.y)) {
- verifyEventInBlockSelection(e);
+ verifyEventInMultiOrBlockSelection(e);
return;
}
+ } else if (selection instanceof IMultiTextSelection && ((IMultiTextSelection) selection).getRegions().length > 1) {
+ verifyEventInMultiOrBlockSelection(e);
+ return;
}
IRegion modelRange= event2ModelRange(e);
@@ -3716,9 +3767,8 @@ public class TextViewer extends Viewer implements
* Simulates typing behavior in block selection mode.
*
* @param e the verify event.
- * @since 3.5
*/
- private void verifyEventInBlockSelection(final VerifyEvent e) {
+ private void verifyEventInMultiOrBlockSelection(final VerifyEvent e) {
/*
Implementation Note: StyledText sends a sequence of n events
for a single character typed, where n is the number of affected lines. Since
@@ -3739,12 +3789,8 @@ public class TextViewer extends Viewer implements
ISelection selection= getSelection();
int length= e.text.length();
if (length == 0 && e.character == '\0') {
- // backspace in StyledText block selection mode...
- TextEdit edit= processor.backspace(selection);
- edit.apply(fDocument, TextEdit.UPDATE_REGIONS);
- ISelection empty= processor.makeEmpty(selection, true);
- setSelection(empty);
- } else {
+ processor.doBackspace(selection);
+ } else if (selection instanceof IBlockTextSelection) {
int lines= processor.getCoveredLines(selection);
String delim= fDocument.getLegalLineDelimiters()[0];
StringBuilder text= new StringBuilder(lines * length + (lines - 1) * delim.length());
@@ -3754,6 +3800,8 @@ public class TextViewer extends Viewer implements
text.append(e.text);
}
processor.doReplace(selection, text.toString());
+ } else if (selection instanceof IMultiTextSelection) {
+ processor.doReplace(selection, e.text);
}
} catch (BadLocationException x) {
if (TRACE_ERRORS)
@@ -3901,8 +3949,7 @@ public class TextViewer extends Viewer implements
// Workaround to fix bug 434791 during 4.4 RC2. Will be replaced by official API during 4.5.
case -100:
if (fLastSentSelectionChange != null) {
- ISelection lastSelection= new TextSelection(getDocument(), fLastSentSelectionChange.getOffset(), fLastSentSelectionChange.getLength());
- fireSelectionChanged(new SelectionChangedEvent(this, lastSelection));
+ fireSelectionChanged(new SelectionChangedEvent(this, toSelection(fLastSentSelectionChange)));
}
return;
@@ -3910,7 +3957,7 @@ public class TextViewer extends Viewer implements
}
private void delete() {
- if (!fTextWidget.getBlockSelection()) {
+ if (!fTextWidget.getBlockSelection() && fTextWidget.getSelectionRanges().length < 2) {
fTextWidget.invokeAction(ST.DELETE_NEXT);
} else {
wrapCompoundChange(() -> {
@@ -3928,7 +3975,7 @@ public class TextViewer extends Viewer implements
private void paste() {
// ignoreAutoEditStrategies(true);
- if (!fTextWidget.getBlockSelection()) {
+ if (fTextWidget.getSelectionRanges().length < 2) { // single selection
fTextWidget.paste();
} else {
wrapCompoundChange(() -> {
diff --git a/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF b/org.eclipse.ui.workbench.texteditor/META-INF/MANIFEST.MF
index a2c871e90c1..450b0b3ad98 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.200.qualifier
+Bundle-Version: 3.16.300.qualifier
Bundle-Activator: org.eclipse.ui.internal.texteditor.TextEditorPlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: %providerName
@@ -27,7 +27,7 @@ Require-Bundle:
org.eclipse.core.runtime;bundle-version="[3.5.0,4.0.0)",
org.eclipse.compare.core;bundle-version="[3.5.0,4.0.0)",
org.eclipse.core.expressions;bundle-version="[3.4.100,4.0.0)",
- org.eclipse.jface.text;bundle-version="[3.8.0,4.0.0)",
+ org.eclipse.jface.text;bundle-version="[3.19.0,4.0.0)",
org.eclipse.swt;bundle-version="[3.107.0,4.0.0)",
org.eclipse.ui;bundle-version="[3.5.0,4.0.0)"
Bundle-RequiredExecutionEnvironment: JavaSE-11
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.properties b/org.eclipse.ui.workbench.texteditor/plugin.properties
index 3f412390127..f2b4831e9a5 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.properties
+++ b/org.eclipse.ui.workbench.texteditor/plugin.properties
@@ -43,6 +43,8 @@ showInformation.description= Displays information for the current caret location
toggleBlockSelectionMode.label= Toggle Block Selection
toggleBlockSelectionMode.tooltip= Toggle Block Selection
toggleBlockSelectionMode.description= Toggle block / column selection in the current text editor
+toMultiSelection.label=To multi-selection
+toMultiSelection.description=Turn current selection into multiple text selections
toggleWordWrap.label= Toggle Word Wrap
toggleWordWrap.tooltip= Toggle Word Wrap
diff --git a/org.eclipse.ui.workbench.texteditor/plugin.xml b/org.eclipse.ui.workbench.texteditor/plugin.xml
index 7705130fa0b..18c9f928a8b 100644
--- a/org.eclipse.ui.workbench.texteditor/plugin.xml
+++ b/org.eclipse.ui.workbench.texteditor/plugin.xml
@@ -425,6 +425,12 @@
id="org.eclipse.ui.edit.text.toggleBlockSelectionMode">
</command>
<command
+ name="%toMultiSelection.label"
+ description="%toMultiSelection.description"
+ categoryId="org.eclipse.ui.category.edit"
+ id="org.eclipse.ui.edit.text.toMultiSelection">
+ </command>
+ <command
name="%toggleWordWrap.label"
description="%toggleWordWrap.description"
categoryId="org.eclipse.ui.category.edit"
@@ -1347,6 +1353,18 @@
class="org.eclipse.ui.texteditor.TextZoomOutHandler"
commandId="org.eclipse.ui.edit.text.zoomOut">
</handler>
+ <handler
+ class="org.eclipse.ui.internal.texteditor.ToMultiSelectionHandler"
+ commandId="org.eclipse.ui.edit.text.toMultiSelection">
+ <enabledWhen>
+ <with
+ variable="activeEditor">
+ <instanceof
+ value="org.eclipse.ui.texteditor.ITextEditor">
+ </instanceof>
+ </with>
+ </enabledWhen>
+ </handler>
</extension>
<extension
point="org.eclipse.ui.menus">
@@ -1360,6 +1378,13 @@
checkEnabled="true">
</visibleWhen>
</command>
+ <command
+ commandId="org.eclipse.ui.edit.text.toMultiSelection"
+ style="push">
+ <visibleWhen
+ checkEnabled="true">
+ </visibleWhen>
+ </command>
</menuContribution>
</extension>
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java
new file mode 100644
index 00000000000..0fda4098ec4
--- /dev/null
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/internal/texteditor/ToMultiSelectionHandler.java
@@ -0,0 +1,60 @@
+/*******************************************************************************
+ * Copyright (c) 2021 Red Hat Inc.
+ *
+ * This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License 2.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-2.0/
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ *******************************************************************************/
+package org.eclipse.ui.internal.texteditor;
+
+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.IBlockTextSelection;
+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.ui.IEditorPart;
+import org.eclipse.ui.handlers.HandlerUtil;
+
+import org.eclipse.ui.texteditor.ITextEditor;
+import org.eclipse.ui.texteditor.ITextEditorExtension5;
+
+public class ToMultiSelectionHandler extends AbstractHandler {
+
+ @Override
+ public Object execute(ExecutionEvent event) throws ExecutionException {
+ IEditorPart editor = HandlerUtil.getActiveEditor(event);
+ if (!(editor instanceof ITextEditor)) {
+ return null;
+ }
+ ITextEditor textEditor = (ITextEditor) editor;
+ ISelection selection = textEditor.getSelectionProvider().getSelection();
+ if (!(selection instanceof IBlockTextSelection)) {
+ return null;
+ }
+ IBlockTextSelection blockSelection = (IBlockTextSelection) selection;
+ IRegion[] initialRegions = ((IMultiTextSelection) blockSelection).getRegions();
+ IDocument document = textEditor.getDocumentProvider().getDocument(editor.getEditorInput());
+ if (document == null) {
+ return null;
+ }
+ IMultiTextSelection newSelection = new MultiTextSelection(document, initialRegions);
+ if (!(editor instanceof ITextEditorExtension5)) {
+ return null;
+ }
+ ITextEditorExtension5 ext = (ITextEditorExtension5) editor;
+ ext.setBlockSelectionMode(false);
+ textEditor.getSelectionProvider().setSelection(newSelection);
+ return newSelection;
+ }
+
+}
diff --git a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
index cda1fcfdb73..01641869670 100644
--- a/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
+++ b/org.eclipse.ui.workbench.texteditor/src/org/eclipse/ui/texteditor/AbstractTextEditor.java
@@ -33,6 +33,7 @@ import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.osgi.framework.Bundle;
@@ -131,6 +132,7 @@ import org.eclipse.jface.text.IFindReplaceTarget;
import org.eclipse.jface.text.IFindReplaceTargetExtension;
import org.eclipse.jface.text.IInformationControlCreator;
import org.eclipse.jface.text.IMarkRegionTarget;
+import org.eclipse.jface.text.IMultiTextSelection;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.IRewriteTarget;
import org.eclipse.jface.text.ISelectionValidator;
@@ -1190,72 +1192,65 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
StyledText st= fSourceViewer.getTextWidget();
if (st == null || st.isDisposed())
return;
- int caretOffset= st.getCaretOffset();
- int lineNumber= st.getLineAtOffset(caretOffset);
- int lineOffset= st.getOffsetAtLine(lineNumber);
+ boolean caretAtBeginningOfSelection = st.getCaretOffset() == st.getSelection().x;
+ Point firstSelection = st.getSelection();
+ int[] ranges = st.getSelectionRanges();
+ List<Point> newSelection = new ArrayList<>(ranges.length / 2);
+ for (int j = 0; j < ranges.length; j += 2) {
+ int offset = ranges[j];
+ int length = ranges[j + 1];
+ int caretOffset = caretAtBeginningOfSelection ? offset : offset + length;
+ int lineNumber = st.getLineAtOffset(caretOffset);
+ int lineOffset = st.getOffsetAtLine(lineNumber);
+ int lineLength;
+ int caretOffsetInDocument;
+ final IDocument document = fSourceViewer.getDocument();
- int lineLength;
- int caretOffsetInDocument;
- final IDocument document= fSourceViewer.getDocument();
-
- try {
- caretOffsetInDocument= widgetOffset2ModelOffset(fSourceViewer, caretOffset);
- lineLength= document.getLineInformationOfOffset(caretOffsetInDocument).getLength();
- } catch (BadLocationException ex) {
- return;
- }
- int lineEndOffset= lineOffset + lineLength;
-
- int delta= lineEndOffset - st.getCharCount();
- if (delta > 0) {
- lineEndOffset -= delta;
- lineLength -= delta;
- }
-
- String line= ""; //$NON-NLS-1$
- if (lineLength > 0)
- line= st.getText(lineOffset, lineEndOffset - 1);
-
- // Remember current selection
- Point oldSelection= st.getSelection();
-
- // The new caret position
- int newCaretOffset= -1;
-
- if (isSmartHomeEndEnabled) {
- // Compute the line end offset
- int i= getLineEndPosition(document, line, lineLength, caretOffsetInDocument);
+ try {
+ caretOffsetInDocument = widgetOffset2ModelOffset(fSourceViewer, caretOffset);
+ lineLength = document.getLineInformationOfOffset(caretOffsetInDocument).getLength();
+ } catch (BadLocationException ex) {
+ return;
+ }
+ int lineEndOffset = lineOffset + lineLength;
- if (caretOffset - lineOffset == i)
- // to end of line
- newCaretOffset= lineEndOffset;
- else
- // to end of text
- newCaretOffset= lineOffset + i;
+ int delta = lineEndOffset - st.getCharCount();
+ if (delta > 0) {
+ lineEndOffset -= delta;
+ lineLength -= delta;
+ }
+ String line = ""; //$NON-NLS-1$
+ if (lineLength > 0)
+ line = st.getText(lineOffset, lineEndOffset - 1);
+
+ // Remember current selection
+ Point oldSelection = new Point(offset, offset + length);
+
+ // The new caret position
+ int newCaretOffset = -1;
+
+ if (isSmartHomeEndEnabled) {
+ // Compute the line end offset
+ int i = getLineEndPosition(document, line, lineLength, caretOffsetInDocument);
+ newCaretOffset = (caretOffset - lineOffset == i) ? lineEndOffset : lineOffset + i;
+ } else if (caretOffset < lineEndOffset) {
+ // to end of line
+ newCaretOffset = lineEndOffset;
+ }
- } else {
+ if (newCaretOffset == -1) {
+ newCaretOffset = caretOffset;
+ }
- if (caretOffset < lineEndOffset)
- // to end of line
- newCaretOffset= lineEndOffset;
+ newSelection.add(new Point(
+ fDoSelect ? (caretOffset < oldSelection.y ? oldSelection.y : oldSelection.x) : newCaretOffset,
+ newCaretOffset));
}
-
- if (newCaretOffset == -1)
- newCaretOffset= caretOffset;
- else
- st.setCaretOffset(newCaretOffset);
-
- st.setCaretOffset(newCaretOffset);
- if (fDoSelect) {
- if (caretOffset < oldSelection.y)
- st.setSelection(oldSelection.y, newCaretOffset);
- else
- st.setSelection(oldSelection.x, newCaretOffset);
- } else
- st.setSelection(newCaretOffset);
-
- fireSelectionChanged(oldSelection);
+ st.setSelectionRanges(newSelection.stream().flatMapToInt(
+ p -> caretAtBeginningOfSelection ? IntStream.of(p.y, p.x - p.y) : IntStream.of(p.x, p.y - p.x))
+ .toArray());
+ fireSelectionChanged(firstSelection);
}
}
@@ -1326,70 +1321,60 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
StyledText st= fSourceViewer.getTextWidget();
if (st == null || st.isDisposed())
return;
+ boolean caretAtBeginningOfSelection = st.getCaretOffset() == st.getSelection().x;
+ Point firstSelection = st.getSelection();
+ int[] ranges = st.getSelectionRanges();
+ List<Point> newSelection = new ArrayList<>(ranges.length / 2);
+ for (int j = 0; j < ranges.length; j += 2) {
+ int offset = ranges[j];
+ int length = ranges[j + 1];
+ int caretOffset = caretAtBeginningOfSelection ? offset : offset + length;
+ int lineNumber = st.getLineAtOffset(caretOffset);
+ int lineOffset = st.getOffsetAtLine(lineNumber);
+ int lineLength;
+ int caretOffsetInDocument;
+ final IDocument document = fSourceViewer.getDocument();
- int caretOffset= st.getCaretOffset();
- int lineNumber= st.getLineAtOffset(caretOffset);
- int lineOffset= st.getOffsetAtLine(lineNumber);
-
- int lineLength;
- int caretOffsetInDocument;
- final IDocument document= fSourceViewer.getDocument();
-
- try {
- caretOffsetInDocument= widgetOffset2ModelOffset(fSourceViewer, caretOffset);
- lineLength= document.getLineInformationOfOffset(caretOffsetInDocument).getLength();
- } catch (BadLocationException ex) {
- return;
- }
-
- String line= ""; //$NON-NLS-1$
- if (lineLength > 0) {
- int end= lineOffset + lineLength - 1;
- end= Math.min(end, st.getCharCount() -1);
- line= st.getText(lineOffset, end);
- }
-
- // Remember current selection
- Point oldSelection= st.getSelection();
+ try {
+ caretOffsetInDocument = widgetOffset2ModelOffset(fSourceViewer, caretOffset);
+ lineLength = document.getLineInformationOfOffset(caretOffsetInDocument).getLength();
+ } catch (BadLocationException ex) {
+ return;
+ }
+ int lineEndOffset = lineOffset + lineLength;
- // The new caret position
- int newCaretOffset= -1;
+ String line = ""; //$NON-NLS-1$
+ if (lineLength > 0) {
+ int end = lineOffset + lineLength - 1;
+ end = Math.min(end, st.getCharCount() - 1);
+ line = st.getText(lineOffset, end);
+ }
- if (isSmartHomeEndEnabled) {
+ // Remember current selection
+ Point oldSelection = new Point(offset, offset + length);
- // Compute the line start offset
- int index= getLineStartPosition(document, line, lineLength, caretOffsetInDocument);
+ // The new caret position
+ int newCaretOffset = -1;
- if (caretOffset - lineOffset == index)
+ if (isSmartHomeEndEnabled) {
+ // Compute the line start offset
+ int index = getLineStartPosition(document, line, lineLength, caretOffsetInDocument);
+ newCaretOffset = (caretOffset - lineOffset == index) ? lineOffset : lineOffset + index;
+ } else if (caretOffset > lineOffset) {
// to beginning of line
- newCaretOffset= lineOffset;
- else
- // to beginning of text
- newCaretOffset= lineOffset + index;
-
- } else {
+ newCaretOffset = lineOffset;
+ }
- if (caretOffset > lineOffset)
- // to beginning of line
- newCaretOffset= lineOffset;
+ newSelection.add(new Point(
+ fDoSelect ? (caretOffset < oldSelection.y ? oldSelection.y : oldSelection.x) : newCaretOffset,
+ newCaretOffset));
}
+ st.setSelectionRanges(newSelection.stream().flatMapToInt(
+ p -> caretAtBeginningOfSelection ? IntStream.of(p.y, p.x - p.y) : IntStream.of(p.x, p.y - p.x))
+ .toArray());
- if (newCaretOffset == -1)
- newCaretOffset= caretOffset;
- else
- st.setCaretOffset(newCaretOffset);
-
- if (fDoSelect) {
- if (caretOffset < oldSelection.y)
- st.setSelection(oldSelection.y, newCaretOffset);
- else
- st.setSelection(oldSelection.x, newCaretOffset);
- } else
- st.setSelection(newCaretOffset);
-
- fireSelectionChanged(oldSelection);
+ fireSelectionChanged(firstSelection);
}
-
}
/**
@@ -2944,7 +2929,9 @@ public abstract class AbstractTextEditor extends EditorPart implements ITextEdit
* @since 2.1
*/
protected void doSetSelection(ISelection selection) {
- if (selection instanceof ITextSelection) {
+ if (selection instanceof IMultiTextSelection && ((IMultiTextSelection) selection).getRegions().length > 1) {
+ getSourceViewer().getSelectionProvider().setSelection(selection);
+ } else if (selection instanceof ITextSelection) {
ITextSelection textSelection= (ITextSelection) selection;
selectAndReveal(textSelection.getOffset(), textSelection.getLength());
}

Back to the top