diff options
author | Mickael Istria | 2019-12-03 08:58:49 +0000 |
---|---|---|
committer | Mickael Istria | 2021-09-07 10:25:05 +0000 |
commit | bf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9 (patch) | |
tree | df95e1fd56beedf3560e058dad6ea2278d6cf977 | |
parent | 118d3512d9b32f1219e5358a6ae5ad212ede861b (diff) | |
download | eclipse.platform.text-bf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9.tar.gz eclipse.platform.text-bf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9.tar.xz eclipse.platform.text-bf5933129b8eb15b5b7cdefa781eacc1c4a5e3b9.zip |
Bug 466532 -Support multipleI20210908-1800I20210907-1800
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>
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()); } |