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