From 3664362a42bc2b136d8d38a2949ca5b327c30d7c Mon Sep 17 00:00:00 2001 From: Dmitry Batrak Date: Thu, 18 Dec 2014 12:36:43 +0300 Subject: [PATCH] IDEA-121925 Allow to do multiple selections w/wout multiple carets per selection --- .../openapi/actionSystem/IdeActions.java | 1 + .../editor/EditorModificationUtil.java | 45 +++++ .../intellij/openapi/keymap/KeymapUtil.java | 35 ++++ ...RectangularSelectionOnMouseDragAction.java | 31 +++ .../openapi/editor/impl/EditorImpl.java | 97 ++++++---- .../editor/impl/SelectionModelImpl.java | 38 +--- .../impl/ActionShortcutRestrictionsImpl.java | 3 +- .../src/messages/ActionsBundle.properties | 1 + .../src/idea/Keymap_Default.xml | 3 + .../src/idea/PlatformActions.xml | 1 + .../openapi/editor/EditorMultiCaretTest.java | 178 +++++++++--------- .../fixtures/EditorMouseFixture.java | 5 + 12 files changed, 282 insertions(+), 156 deletions(-) create mode 100644 platform/platform-impl/src/com/intellij/openapi/editor/actions/AddRectangularSelectionOnMouseDragAction.java diff --git a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java index b3ffc2f3308b..82c28b83969b 100644 --- a/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java +++ b/platform/platform-api/src/com/intellij/openapi/actionSystem/IdeActions.java @@ -58,6 +58,7 @@ public interface IdeActions { @NonNls String ACTION_EDITOR_USE_SOFT_WRAPS = "EditorToggleUseSoftWraps"; @NonNls String ACTION_EDITOR_ADD_OR_REMOVE_CARET= "EditorAddOrRemoveCaret"; @NonNls String ACTION_EDITOR_CREATE_RECTANGULAR_SELECTION= "EditorCreateRectangularSelection"; + @NonNls String ACTION_EDITOR_ADD_RECTANGULAR_SELECTION_ON_MOUSE_DRAG= "EditorAddRectangularSelectionOnMouseDrag"; @NonNls String ACTION_EDITOR_CLONE_CARET_BELOW= "EditorCloneCaretBelow"; @NonNls String ACTION_EDITOR_CLONE_CARET_ABOVE= "EditorCloneCaretAbove"; @NonNls String ACTION_EDITOR_TOGGLE_STICKY_SELECTION= "EditorToggleStickySelection"; diff --git a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java index 3ab8280d929a..288e8fdde281 100644 --- a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java @@ -32,6 +32,8 @@ import java.awt.datatransfer.DataFlavor; import java.awt.datatransfer.Transferable; import java.awt.datatransfer.UnsupportedFlavorException; import java.io.IOException; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; public class EditorModificationUtil { @@ -484,4 +486,47 @@ public class EditorModificationUtil { editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } } + + @NotNull + public static List calcBlockSelectionState(@NotNull Editor editor, + @NotNull LogicalPosition blockStart, @NotNull LogicalPosition blockEnd) { + int startLine = Math.max(Math.min(blockStart.line, editor.getDocument().getLineCount() - 1), 0); + int endLine = Math.max(Math.min(blockEnd.line, editor.getDocument().getLineCount() - 1), 0); + int step = endLine < startLine ? -1 : 1; + int count = 1 + Math.abs(endLine - startLine); + List caretStates = new LinkedList(); + boolean hasSelection = false; + for (int line = startLine, i = 0; i < count; i++, line += step) { + int startColumn = blockStart.column; + int endColumn = blockEnd.column; + int lineEndOffset = editor.getDocument().getLineEndOffset(line); + LogicalPosition lineEndPosition = editor.offsetToLogicalPosition(lineEndOffset); + int lineWidth = lineEndPosition.column; + if (startColumn > lineWidth && endColumn > lineWidth && !editor.isColumnMode()) { + LogicalPosition caretPos = new LogicalPosition(line, Math.min(startColumn, endColumn)); + caretStates.add(new CaretState(caretPos, + lineEndPosition, + lineEndPosition)); + } + else { + LogicalPosition startPos = new LogicalPosition(line, editor.isColumnMode() ? startColumn : Math.min(startColumn, lineWidth)); + LogicalPosition endPos = new LogicalPosition(line, editor.isColumnMode() ? endColumn : Math.min(endColumn, lineWidth)); + int startOffset = editor.logicalPositionToOffset(startPos); + int endOffset = editor.logicalPositionToOffset(endPos); + caretStates.add(new CaretState(endPos, startPos, endPos)); + hasSelection |= startOffset != endOffset; + } + } + if (hasSelection && !editor.isColumnMode()) { // filtering out lines without selection + Iterator caretStateIterator = caretStates.iterator(); + while(caretStateIterator.hasNext()) { + CaretState state = caretStateIterator.next(); + //noinspection ConstantConditions + if (state.getSelectionStart().equals(state.getSelectionEnd())) { + caretStateIterator.remove(); + } + } + } + return caretStates; + } } diff --git a/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java b/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java index 3584b859f3e4..9e36c8a2e195 100644 --- a/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java +++ b/platform/platform-api/src/com/intellij/openapi/keymap/KeymapUtil.java @@ -387,4 +387,39 @@ public class KeymapUtil { } return false; } + + /** + * Checks whether mouse event's button and modifiers match a shortcut configured in active keymap for given action id. + * Only shortcuts having click count of 1 can be matched, mouse event's click count is ignored. + */ + public static boolean isMouseActionEvent(@NotNull MouseEvent e, @NotNull String actionId) { + KeymapManager keymapManager = KeymapManager.getInstance(); + if (keymapManager == null) { + return false; + } + Keymap keymap = keymapManager.getActiveKeymap(); + if (keymap == null) { + return false; + } + int button = e.getButton(); + int modifiers = e.getModifiersEx(); + if (button == MouseEvent.NOBUTTON && e.getID() == MouseEvent.MOUSE_DRAGGED) { + // mouse drag events don't have button field set due to some reason + if ((modifiers & InputEvent.BUTTON1_DOWN_MASK) != 0) { + button = MouseEvent.BUTTON1; + } else if ((modifiers & InputEvent.BUTTON2_DOWN_MASK) != 0) { + button = MouseEvent.BUTTON2; + } + } + String[] actionIds = keymap.getActionIds(new MouseShortcut(button, modifiers, 1)); + if (actionIds == null) { + return false; + } + for (String id : actionIds) { + if (actionId.equals(id)) { + return true; + } + } + return false; + } } diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/AddRectangularSelectionOnMouseDragAction.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/AddRectangularSelectionOnMouseDragAction.java new file mode 100644 index 000000000000..53e6024de28f --- /dev/null +++ b/platform/platform-impl/src/com/intellij/openapi/editor/actions/AddRectangularSelectionOnMouseDragAction.java @@ -0,0 +1,31 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.intellij.openapi.editor.actions; + +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; + +public class AddRectangularSelectionOnMouseDragAction extends AnAction { + @Override + public void actionPerformed(AnActionEvent e) { + // actual logic is implemented in EditorImpl + } + + @Override + public void update(AnActionEvent e) { + e.getPresentation().setEnabled(false); + } +} diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java index 89f8133449de..3c9225a4a6c6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java @@ -54,8 +54,7 @@ import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory; import com.intellij.openapi.fileEditor.impl.EditorsSplitters; -import com.intellij.openapi.keymap.Keymap; -import com.intellij.openapi.keymap.KeymapManager; +import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Queryable; @@ -288,9 +287,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private final EditorSizeAdjustmentStrategy mySizeAdjustmentStrategy = new EditorSizeAdjustmentStrategy(); private final Disposable myDisposable = Disposer.newDisposable(); + private List myCaretStateBeforeLastPress; private LogicalPosition myLastMousePressedLocation; private VisualPosition myTargetMultiSelectionPosition; private boolean myMultiSelectionInProgress; + private boolean myRectangularSelectionInProgress; private boolean myLastPressCreatedCaret; // Set when the selection (normal or block one) initiated by mouse drag becomes noticeable (at least one character is selected). // Reset on mouse press event. @@ -4177,6 +4178,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myDragOnGutterSelectionStartLine = - 1; } + boolean columnSelectionDragEvent = isColumnSelectionDragEvent(e); + boolean toggleCaretEvent = isToggleCaretEvent(e); + boolean addRectangularSelectionEvent = isAddRectangularSelectionEvent(e); + boolean columnSelectionDrag = isColumnMode() && !myLastPressCreatedCaret || columnSelectionDragEvent; + if (!columnSelectionDragEvent && toggleCaretEvent && !myLastPressCreatedCaret) { + return; // ignoring drag after removing a caret + } + Rectangle visibleArea = getScrollingModel().getVisibleArea(); int x = e.getX(); @@ -4214,10 +4223,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi VisualPosition oldVisLeadSelectionStart = leadCaret.getLeadSelectionPosition(); int oldCaretOffset = getCaretModel().getOffset(); LogicalPosition oldLogicalCaret = getCaretModel().getLogicalPosition(); - boolean multiCaretSelection = myCaretModel.supportsMultipleCarets() && (isColumnMode() || e.isAltDown()); + boolean multiCaretSelection = myCaretModel.supportsMultipleCarets() && (columnSelectionDrag || toggleCaretEvent); LogicalPosition newLogicalCaret = getLogicalPositionForScreenPos(x, y, !multiCaretSelection); if (multiCaretSelection) { myMultiSelectionInProgress = true; + myRectangularSelectionInProgress = columnSelectionDrag || addRectangularSelectionEvent; myTargetMultiSelectionPosition = xyToVisualPosition(new Point(Math.max(x, 0), Math.max(y, 0))); getScrollingModel().scrollTo(newLogicalCaret, ScrollType.RELATIVE); } @@ -4235,15 +4245,18 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi selectionModel.setSelection(oldSelectionStart, newCaretOffset); } else { - if (isColumnMode() || e.isAltDown()) { + if (columnSelectionDrag || toggleCaretEvent) { if (myCaretModel.supportsMultipleCarets()) { if (myLastMousePressedLocation != null && (myCurrentDragIsSubstantial || !newLogicalCaret.equals(myLastMousePressedLocation))) { - setBlockSelectionAndBlockActions(e, myLastMousePressedLocation, newLogicalCaret); + createSelectionTill(newLogicalCaret); + blockActionsIfNeeded(e, myLastMousePressedLocation, newLogicalCaret); } } else { final LogicalPosition blockStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : oldLogicalCaret; if (blockStart != null) { - setBlockSelectionAndBlockActions(e, blockStart, getCaretModel().getLogicalPosition()); + LogicalPosition endPosition = getCaretModel().getLogicalPosition(); + mySelectionModel.setBlockSelection(blockStart, endPosition); + blockActionsIfNeeded(e, blockStart, endPosition); } } } @@ -4332,6 +4345,39 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } + private void createSelectionTill(@NotNull LogicalPosition targetPosition) { + List caretStates = new ArrayList(myCaretStateBeforeLastPress); + if (myRectangularSelectionInProgress) { + caretStates.addAll(EditorModificationUtil.calcBlockSelectionState(this, myLastMousePressedLocation, targetPosition)); + } + else { + LogicalPosition selectionStart = myLastMousePressedLocation; + LogicalPosition selectionEnd = targetPosition; + if (getMouseSelectionState() != MOUSE_SELECTION_STATE_NONE) { + int newCaretOffset = logicalPositionToOffset(targetPosition); + if (newCaretOffset < mySavedSelectionStart) { + selectionStart = offsetToLogicalPosition(mySavedSelectionEnd); + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { + targetPosition = selectionEnd = visualToLogicalPosition(new VisualPosition(offsetToVisualLine(newCaretOffset), 0)); + } + } + else { + selectionStart = offsetToLogicalPosition(mySavedSelectionStart); + int selectionEndOffset = Math.max(newCaretOffset, mySavedSelectionEnd); + if (getMouseSelectionState() == MOUSE_SELECTION_STATE_WORD_SELECTED) { + targetPosition = selectionEnd = offsetToLogicalPosition(selectionEndOffset); + } + else if (getMouseSelectionState() == MOUSE_SELECTION_STATE_LINE_SELECTED) { + targetPosition = selectionEnd = visualToLogicalPosition(new VisualPosition(offsetToVisualLine(selectionEndOffset) + 1, 0)); + } + } + cancelAutoResetForMouseSelectionState(); + } + caretStates.add(new CaretState(targetPosition, selectionStart, selectionEnd)); + } + myCaretModel.setCaretsAndSelections(caretStates); + } + private Caret getLeadCaret() { List allCarets = myCaretModel.getAllCarets(); Caret firstCaret = allCarets.get(0); @@ -4357,8 +4403,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } - private void setBlockSelectionAndBlockActions(@NotNull MouseEvent mouseDragEvent, @NotNull LogicalPosition startPosition, @NotNull LogicalPosition endPosition) { - mySelectionModel.setBlockSelection(startPosition, endPosition); + private void blockActionsIfNeeded(@NotNull MouseEvent mouseDragEvent, @NotNull LogicalPosition startPosition, @NotNull LogicalPosition endPosition) { if (myCurrentDragIsSubstantial || !startPosition.equals(endPosition)) { onSubstantialDrag(mouseDragEvent); } @@ -4801,7 +4846,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi myTargetMultiSelectionPosition = pos; LogicalPosition newLogicalPosition = visualToLogicalPosition(pos); getScrollingModel().scrollTo(newLogicalPosition, ScrollType.RELATIVE); - mySelectionModel.setBlockSelection(myLastMousePressedLocation, newLogicalPosition); + createSelectionTill(newLogicalPosition); } else if (mySelectionModel.hasBlockSelection()) { mySelectionModel.setBlockSelection(mySelectionModel.getBlockStart(), getCaretModel().getLogicalPosition()); @@ -5521,6 +5566,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi private void runMousePressedCommand(@NotNull final MouseEvent e) { myLastMousePressedLocation = xyToLogicalPosition(e.getPoint()); + myCaretStateBeforeLastPress = isToggleCaretEvent(e) ? myCaretModel.getCaretsAndSelections() : Collections.emptyList(); myCurrentDragIsSubstantial = false; final int clickOffset = logicalPositionToOffset(myLastMousePressedLocation); @@ -5831,33 +5877,20 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi } } + private static boolean isColumnSelectionDragEvent(@NotNull MouseEvent e) { + return e.isAltDown() && !e.isShiftDown() && !e.isControlDown() && !e.isMetaDown(); + } + private static boolean isToggleCaretEvent(@NotNull MouseEvent e) { - return isMouseActionEvent(e, IdeActions.ACTION_EDITOR_ADD_OR_REMOVE_CARET); + return KeymapUtil.isMouseActionEvent(e, IdeActions.ACTION_EDITOR_ADD_OR_REMOVE_CARET) || isAddRectangularSelectionEvent(e); + } + + private static boolean isAddRectangularSelectionEvent(@NotNull MouseEvent e) { + return KeymapUtil.isMouseActionEvent(e, IdeActions.ACTION_EDITOR_ADD_RECTANGULAR_SELECTION_ON_MOUSE_DRAG); } private static boolean isCreateRectangularSelectionEvent(@NotNull MouseEvent e) { - return isMouseActionEvent(e, IdeActions.ACTION_EDITOR_CREATE_RECTANGULAR_SELECTION); - } - - private static boolean isMouseActionEvent(@NotNull MouseEvent e, @NotNull String actionId) { - KeymapManager keymapManager = KeymapManager.getInstance(); - if (keymapManager == null) { - return false; - } - Keymap keymap = keymapManager.getActiveKeymap(); - if (keymap == null) { - return false; - } - String[] actionIds = keymap.getActionIds(new MouseShortcut(e.getButton(), e.getModifiersEx(), 1)); - if (actionIds == null) { - return false; - } - for (String id : actionIds) { - if (actionId.equals(id)) { - return true; - } - } - return false; + return KeymapUtil.isMouseActionEvent(e, IdeActions.ACTION_EDITOR_CREATE_RECTANGULAR_SELECTION); } private void selectWordAtCaret(boolean honorCamelCase) { diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java index 574780a661c2..4e95b35878c7 100644 --- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java @@ -211,43 +211,7 @@ public class SelectionModelImpl implements SelectionModel, PrioritizedDocumentLi @Override public void setBlockSelection(@NotNull LogicalPosition blockStart, @NotNull LogicalPosition blockEnd) { if (myEditor.getCaretModel().supportsMultipleCarets()) { - int startLine = Math.max(Math.min(blockStart.line, myEditor.getDocument().getLineCount() - 1), 0); - int endLine = Math.max(Math.min(blockEnd.line, myEditor.getDocument().getLineCount() - 1), 0); - int step = endLine < startLine ? -1 : 1; - int count = 1 + Math.abs(endLine - startLine); - List caretStates = new LinkedList(); - boolean hasSelection = false; - for (int line = startLine, i = 0; i < count; i++, line += step) { - int startColumn = blockStart.column; - int endColumn = blockEnd.column; - int lineEndOffset = myEditor.getDocument().getLineEndOffset(line); - LogicalPosition lineEndPosition = myEditor.offsetToLogicalPosition(lineEndOffset); - int lineWidth = lineEndPosition.column; - if (startColumn > lineWidth && endColumn > lineWidth && !myEditor.isColumnMode()) { - LogicalPosition caretPos = new LogicalPosition(line, Math.min(startColumn, endColumn)); - caretStates.add(new CaretState(caretPos, - lineEndPosition, - lineEndPosition)); - } - else { - LogicalPosition startPos = new LogicalPosition(line, myEditor.isColumnMode() ? startColumn : Math.min(startColumn, lineWidth)); - LogicalPosition endPos = new LogicalPosition(line, myEditor.isColumnMode() ? endColumn : Math.min(endColumn, lineWidth)); - int startOffset = myEditor.logicalPositionToOffset(startPos); - int endOffset = myEditor.logicalPositionToOffset(endPos); - caretStates.add(new CaretState(endPos, startPos, endPos)); - hasSelection |= startOffset != endOffset; - } - } - if (hasSelection && !myEditor.isColumnMode()) { // filtering out lines without selection - Iterator caretStateIterator = caretStates.iterator(); - while(caretStateIterator.hasNext()) { - CaretState state = caretStateIterator.next(); - //noinspection ConstantConditions - if (state.getSelectionStart().equals(state.getSelectionEnd())) { - caretStateIterator.remove(); - } - } - } + List caretStates = EditorModificationUtil.calcBlockSelectionState(myEditor, blockStart, blockEnd); myEditor.getCaretModel().setCaretsAndSelections(caretStates); } else { diff --git a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ActionShortcutRestrictionsImpl.java b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ActionShortcutRestrictionsImpl.java index cdb03f920abf..1efdb2015a1e 100644 --- a/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ActionShortcutRestrictionsImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/keymap/impl/ActionShortcutRestrictionsImpl.java @@ -26,7 +26,8 @@ public class ActionShortcutRestrictionsImpl extends ActionShortcutRestrictions { @NotNull public ShortcutRestrictions getForActionId(String actionId) { if (IdeActions.ACTION_EDITOR_ADD_OR_REMOVE_CARET.equals(actionId) || - IdeActions.ACTION_EDITOR_CREATE_RECTANGULAR_SELECTION.equals(actionId)) { + IdeActions.ACTION_EDITOR_CREATE_RECTANGULAR_SELECTION.equals(actionId) || + IdeActions.ACTION_EDITOR_ADD_RECTANGULAR_SELECTION_ON_MOUSE_DRAG.equals(actionId)) { return MOUSE_SINGLE_CLICK_ONLY; } if (IdeActions.ACTION_EXPAND_LIVE_TEMPLATE_BY_TAB.equals(actionId)) { diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 21f9050a6312..c0f2d9819cac 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -112,6 +112,7 @@ action.EditorMoveUpAndScrollWithSelection.text=Move Up and Scroll with Selection action.EditorMoveDownAndScrollWithSelection.text=Move Down and Scroll with Selection action.EditorAddOrRemoveCaret.text=Add or Remove Caret action.EditorCreateRectangularSelection.text=Create Rectangular Selection +action.EditorAddRectangularSelectionOnMouseDrag.text=Add Rectangular Selection on Mouse Drag action.EditorCloneCaretBelow.text=Clone Caret Below action.EditorCloneCaretAbove.text=Clone Caret Above action.SelectNextOccurrence.text=Add Selection for Next Occurrence diff --git a/platform/platform-resources/src/idea/Keymap_Default.xml b/platform/platform-resources/src/idea/Keymap_Default.xml index 248fc702bbb4..8cd7594ebaaa 100644 --- a/platform/platform-resources/src/idea/Keymap_Default.xml +++ b/platform/platform-resources/src/idea/Keymap_Default.xml @@ -121,6 +121,9 @@ + + + diff --git a/platform/platform-resources/src/idea/PlatformActions.xml b/platform/platform-resources/src/idea/PlatformActions.xml index 5aef9075f588..e9ae4512d7bb 100644 --- a/platform/platform-resources/src/idea/PlatformActions.xml +++ b/platform/platform-resources/src/idea/PlatformActions.xml @@ -89,6 +89,7 @@ + diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java index 8c5d88c9f385..6820f870e6c4 100644 --- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java +++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java @@ -17,12 +17,12 @@ package com.intellij.openapi.editor; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.actionSystem.MouseShortcut; +import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.EditorSettingsExternalizable; import com.intellij.openapi.editor.impl.AbstractEditorTest; import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.testFramework.EditorTestUtil; -import com.intellij.testFramework.TestFileType; import com.intellij.util.ThrowableRunnable; import java.awt.event.InputEvent; @@ -43,11 +43,10 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCaretAddingAndRemoval() throws Exception { - init("some text\n" + - "another line", - TestFileType.TEXT); + initText("some text\n" + + "another line"); - mouse().alt().shift().clickAt(1,1); // alt-shift-click in a 'free space' + mouse().alt().shift().clickAt(1, 1); // alt-shift-click in a 'free space' checkResultByText("some text\n" + "another line"); @@ -76,7 +75,7 @@ public class EditorMultiCaretTest extends AbstractEditorTest { doWithAltClickShortcut(new ThrowableRunnable() { @Override public void run() throws IOException { - init("text", TestFileType.TEXT); + initText("text"); mouse().alt().clickAt(0, 2); checkResultByText("text"); } @@ -87,7 +86,7 @@ public class EditorMultiCaretTest extends AbstractEditorTest { doWithAltClickShortcut(new ThrowableRunnable() { @Override public void run() throws IOException { - init("some text", TestFileType.TEXT); + initText("some text"); mouse().alt().clickAt(0, 6); mouse().alt().clickAt(0, 6); checkResultByText("some text"); @@ -96,12 +95,11 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testAltDragStartingFromWithinLine() throws Exception { - init("line\n" + - "long line\n" + - "very long line\n" + - "long line\n" + - "line", - TestFileType.TEXT); + initText("line\n" + + "long line\n" + + "very long line\n" + + "long line\n" + + "line"); setEditorVisibleSize(1000, 1000); mouse().alt().pressAt(1, 6); @@ -134,12 +132,11 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testMiddleButtonDragStartingFromVirtualSpace() throws Exception { - init("line\n" + - "long line\n" + - "very long line\n" + - "long line\n" + - "line", - TestFileType.TEXT); + initText("line\n" + + "long line\n" + + "very long line\n" + + "long line\n" + + "line"); setEditorVisibleSize(1000, 1000); mouse().middle().pressAt(1, 17); @@ -172,13 +169,12 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testAltOnOffWhileDragging() throws Exception { - init("line1\n" + - "line2\n" + - "line3", - TestFileType.TEXT); + initText("line1\n" + + "line2\n" + + "line3"); setEditorVisibleSize(1000, 1000); - mouse().clickAt(0, 1).dragTo(1, 2); + mouse().pressAt(0, 1).dragTo(1, 2); checkResultByText("line1\n" + "line2\n" + "line3"); @@ -193,11 +189,9 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testTyping() throws Exception { - init("some text\n" + - "some other text\n" + - "another line", - TestFileType.TEXT - ); + initText("some text\n" + + "some other text\n" + + "another line"); type('A'); checkResultByText("someA textA\n" + "some A A\n" + @@ -205,9 +199,8 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCopyPaste() throws Exception { - init("one two \n" + - "three four ", - TestFileType.TEXT); + initText("one two \n" + + "three four "); executeAction("EditorCopy"); executeAction("EditorLineEnd"); executeAction("EditorPaste"); @@ -216,10 +209,8 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCutAndPaste() throws Exception { - init("one two \n" + - "three four ", - TestFileType.TEXT - ); + initText("one two \n" + + "three four "); executeAction("EditorCut"); executeAction("EditorLineEnd"); executeAction("EditorPaste"); @@ -228,9 +219,8 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testPasteSingleItem() throws Exception { - init("one two \n" + - "three four ", - TestFileType.TEXT); + initText("one two \n" + + "three four "); executeAction("EditorCopy"); executeAction("EditorCloneCaretBelow"); executeAction("EditorLineEnd"); @@ -240,12 +230,10 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCutAndPasteMultiline() throws Exception { - init("one two \n" + - "three four \n" + - "five six \n" + - "seven eight", - TestFileType.TEXT - ); + initText("one two \n" + + "three four \n" + + "five six \n" + + "seven eight"); executeAction("EditorCut"); executeAction("EditorLineEnd"); executeAction("EditorPaste"); @@ -256,11 +244,10 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCopyMultilineFromOneCaretPasteIntoTwo() throws Exception { - init("one\n" + - "two\n" + - "three\n" + - "four", - TestFileType.TEXT); + initText("one\n" + + "two\n" + + "three\n" + + "four"); executeAction("EditorCopy"); executeAction("EditorTextStart"); executeAction("EditorCloneCaretBelow"); @@ -274,11 +261,10 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCopyPasteDoesNothingWithUnevenSelection() throws Exception { - init("one\n" + - "two\n" + - "three\n" + - "four", - TestFileType.TEXT); + initText("one\n" + + "two\n" + + "three\n" + + "four"); executeAction("EditorCopy"); executeAction("EditorPaste"); checkResultByText("one\n" + @@ -288,50 +274,43 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testEscapeAfterDragDown() throws Exception { - init("line1\n" + - "line2", - TestFileType.TEXT - ); + initText("line1\n" + + "line2"); setEditorVisibleSize(1000, 1000); - mouse().alt().clickAt(0, 1).dragTo(1, 2).release(); + mouse().alt().pressAt(0, 1).dragTo(1, 2).release(); executeAction("EditorEscape"); checkResultByText("line1\n" + "line2"); } public void testEscapeAfterDragUp() throws Exception { - init("line1\n" + - "line2", - TestFileType.TEXT); + initText("line1\n" + + "line2"); setEditorVisibleSize(1000, 1000); - mouse().alt().clickAt(1, 1).dragTo(0, 2).release(); + mouse().alt().pressAt(1, 1).dragTo(0, 2).release(); executeAction("EditorEscape"); checkResultByText("line1\n" + "line2"); } public void testAltShiftDoubleClick() throws Exception { - init("quick brown fox", - TestFileType.TEXT); + initText("quick brown fox"); mouse().alt().shift().doubleClickAt(0, 8); checkResultByText("quick brown fox"); } public void testAltShiftDoubleClickAtExistingCaret() throws Exception { - init("quick brown fox", - TestFileType.TEXT); + initText("quick brown fox"); mouse().alt().shift().doubleClickAt(0, 8); checkResultByText("quick brown fox"); } public void testAltShiftTripleClick() throws Exception { - init("quick\n" + - "brown\n" + - "fox", - TestFileType.TEXT - ); + initText("quick\n" + + "brown\n" + + "fox"); mouse().alt().shift().tripleClickAt(1, 2); checkResultByText("quick\n" + "brown\n" + @@ -339,10 +318,9 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testAltShiftTripleClickAtExistingCaret() throws Exception { - init("quick\n" + - "brown\n" + - "fox", - TestFileType.TEXT); + initText("quick\n" + + "brown\n" + + "fox"); mouse().alt().shift().tripleClickAt(1, 2); checkResultByText("quick\n" + "brown\n" + @@ -350,9 +328,9 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCaretPositionsRecalculationOnDocumentChange() throws Exception { - init("\n" + - "word\n" + - "some long prefix word-suffix", TestFileType.TEXT); + initText("\n" + + "word\n" + + "some long prefix word-suffix"); EditorTestUtil.configureSoftWraps(myEditor, 17); // wrapping right before 'word-suffix' delete(); @@ -364,12 +342,11 @@ public class EditorMultiCaretTest extends AbstractEditorTest { } public void testCreateRectangularSelectionWithMouseClicks() throws Exception { - init("line\n" + - "long line\n" + - "very long line\n" + - "long line\n" + - "line", - TestFileType.TEXT); + initText("line\n" + + "long line\n" + + "very long line\n" + + "long line\n" + + "line"); mouse().alt().shift().middle().clickAt(2, 2); checkResultByText("line\n" + "long line\n" + @@ -378,6 +355,35 @@ public class EditorMultiCaretTest extends AbstractEditorTest { "line"); } + public void testAddingMultipleSelectionsUsingMouse() throws Exception { + initText("some text\nother text"); + setEditorVisibleSize(1000, 1000); + mouse().alt().shift().pressAt(0, 5).dragTo(1, 2).release(); + checkResultByText("some text\nother text"); + } + + public void testAddingMultipleSelectionsUsingMouseInColumnSelectionMode() throws Exception { + initText("some text\nother text"); + setEditorVisibleSize(1000, 1000); + ((EditorEx)myEditor).setColumnMode(true); + mouse().alt().shift().pressAt(0, 5).dragTo(1, 2).release(); + checkResultByText("some text\nother text"); + } + + public void testAltShiftDragAfterRemovingCaret() throws Exception { + initText("abracadabra"); + setEditorVisibleSize(1000, 1000); + mouse().alt().shift().pressAt(0, 2).dragTo(0, 3).release(); + checkResultByText("abracadabra"); + } + + public void testAddingRectangualSelectionUsingMouse() throws Exception { + initText("some text\nother text"); + setEditorVisibleSize(1000, 1000); + mouse().ctrl().alt().shift().pressAt(0, 7).dragTo(1, 5).release(); + checkResultByText("some text\nother text"); + } + private static void doWithAltClickShortcut(ThrowableRunnable runnable) throws Throwable { Keymap keymap = KeymapManager.getInstance().getActiveKeymap(); MouseShortcut shortcut = new MouseShortcut(1, InputEvent.ALT_DOWN_MASK, 1); diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/EditorMouseFixture.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/EditorMouseFixture.java index e7808af7df85..81e6c7007ce3 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/EditorMouseFixture.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/EditorMouseFixture.java @@ -112,6 +112,11 @@ public class EditorMouseFixture { return this; } + public EditorMouseFixture ctrl() { + myModifiers |= InputEvent.CTRL_DOWN_MASK; + return this; + } + public EditorMouseFixture alt() { myModifiers |= InputEvent.ALT_DOWN_MASK; return this;