improvements for multi-caret implementation of column mode (IDEA-80056)
authorDmitry Batrak <Dmitry.Batrak@jetbrains.com>
Wed, 5 Mar 2014 07:07:32 +0000 (11:07 +0400)
committerDmitry Batrak <Dmitry.Batrak@jetbrains.com>
Wed, 5 Mar 2014 07:08:01 +0000 (11:08 +0400)
20 files changed:
platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java
platform/editor-ui-api/src/com/intellij/openapi/editor/CaretModel.java
platform/editor-ui-api/src/com/intellij/openapi/editor/CaretState.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java
platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretLeftOrRightHandler.java
platform/platform-impl/src/com/intellij/openapi/editor/ex/util/EditorUtil.java
platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java
platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretModelImpl.java
platform/platform-impl/src/com/intellij/openapi/editor/impl/EditorImpl.java
platform/platform-impl/src/com/intellij/openapi/editor/impl/IterationState.java
platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java
platform/platform-impl/src/com/intellij/openapi/editor/textarea/TextComponentCaretModel.java
platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorProvider.java
platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorState.java
platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretColumnModeTest.java
platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretUndoRedoTest.java
platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/AbstractEditorTest.java
platform/platform-tests/testSrc/com/intellij/openapi/editor/impl/IterationStateTest.java
platform/testFramework/src/com/intellij/testFramework/EditorTestUtil.java

index 3648e477da8316289bd147af480b5c6c133eed51..43b5e75e08df4afb3d7052248d5d02f42476c15e 100644 (file)
@@ -202,6 +202,8 @@ public interface Caret extends UserDataHolderEx, Disposable {
    * Selects target range providing information about visual boundary of selection end.
    * <p/>
    * That is the case for soft wraps-aware processing where the whole soft wraps virtual space is matched to the same offset.
+   * <p/>
+   * Also, in column mode this method allows to create selection spanning virtual space after the line end.
    *
    * @param startOffset     start selection offset
    * @param endPosition     end visual position of the text range to select (<code>null</code> argument means that
@@ -214,6 +216,8 @@ public interface Caret extends UserDataHolderEx, Disposable {
    * Selects target range based on its visual boundaries.
    * <p/>
    * That is the case for soft wraps-aware processing where the whole soft wraps virtual space is matched to the same offset.
+   * <p/>
+   * Also, in column mode this method allows to create selection spanning virtual space after the line end.
    *
    * @param startPosition   start visual position of the text range to select (<code>null</code> argument means that
    *                        no specific visual position should be used)
@@ -249,8 +253,8 @@ public interface Caret extends UserDataHolderEx, Disposable {
    * selection will be set for the new caret.
    *
    * @param above if <code>true</code>, new caret will be created at the previous line, if <code>false</code> - on the next line
-   * @return newly created caret instance, or null if the caret cannot be created because it already exists at the new location or caret
-   * model doesn't support multiple carets.
+   * @return newly created caret instance, or <code>null</code> if the caret cannot be created because it already exists at the new location
+   * or caret model doesn't support multiple carets.
    */
   @Nullable
   Caret clone(boolean above);
index 17ddb3f820b3289372200d79bb86de980b18f2c4..81ecddbdd0f66cdcda6a02de98cfc0e5fbd19337 100644 (file)
@@ -17,7 +17,6 @@ package com.intellij.openapi.editor;
 
 import com.intellij.openapi.editor.event.CaretListener;
 import com.intellij.openapi.editor.markup.TextAttributes;
-import com.intellij.openapi.util.Segment;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -214,14 +213,14 @@ public interface CaretModel {
   void removeSecondaryCarets();
 
   /**
-   * Sets the number of carets, their positions and selection ranges according to the provided parameters. Null values in any of the
-   * collections will mean that corresponding caret's position and/or selection won't be changed.
+   * Sets the number of carets, their positions and selection ranges according to the provided data. Null values for caret position or
+   * selection boundaries will mean that corresponding caret's position and/or selection won't be changed.
    * <p>
    * If multiple carets are not supported, the behaviour is unspecified.
    *
    * @see #supportsMultipleCarets()
    */
-  void setCaretsAndSelections(@NotNull List<LogicalPosition> caretPositions, @NotNull List<? extends Segment> selections);
+  void setCaretsAndSelections(@NotNull List<CaretState> caretStates);
 
   /**
    * Executes the given task for each existing caret. Carets are iterated in their position order. Set of carets to iterate over is
diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/CaretState.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/CaretState.java
new file mode 100644 (file)
index 0000000..d7588f1
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ * 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;
+
+import org.jetbrains.annotations.Nullable;
+
+public class CaretState {
+  private final LogicalPosition caretPosition;
+  private final LogicalPosition selectionStart;
+  private final LogicalPosition selectionEnd;
+
+  public CaretState(@Nullable LogicalPosition position, @Nullable LogicalPosition start, @Nullable LogicalPosition end) {
+    caretPosition = position;
+    selectionStart = start;
+    selectionEnd = end;
+  }
+
+  @Nullable
+  public LogicalPosition getCaretPosition(){
+    return caretPosition;
+  }
+
+  @Nullable
+  public LogicalPosition getSelectionStart() {
+    return selectionStart;
+  }
+
+  @Nullable
+  public LogicalPosition getSelectionEnd() {
+    return selectionEnd;
+  }
+
+  @Override
+  public String toString() {
+    return "CaretState{" +
+           "caretPosition=" + caretPosition +
+           ", selectionStart=" + selectionStart +
+           ", selectionEnd=" + selectionEnd +
+           '}';
+  }
+}
index cee529aecccf5d2d54134216ad773244af50e409..368c9f4082ceb77dd2252bc95f1052280fc9b067 100644 (file)
@@ -21,12 +21,13 @@ import com.intellij.openapi.editor.event.CaretEvent;
 import com.intellij.openapi.editor.event.CaretListener;
 import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.editor.markup.TextAttributes;
-import com.intellij.openapi.util.Segment;
-import com.intellij.openapi.util.TextRange;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.util.*;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
 
 /**
  * @author Alexey
@@ -204,17 +205,14 @@ public class CaretModelWindow implements CaretModel {
   }
 
   @Override
-  public void setCaretsAndSelections(@NotNull List<LogicalPosition> caretPositions, @NotNull List<? extends Segment> selections) {
-    List<LogicalPosition> convertedPositions = new ArrayList<LogicalPosition>(caretPositions);
-    for (LogicalPosition position : caretPositions) {
-      convertedPositions.add(myEditorWindow.injectedToHost(position));
+  public void setCaretsAndSelections(@NotNull List<CaretState> caretStates) {
+    List<CaretState> convertedStates = new ArrayList<CaretState>(caretStates.size());
+    for (CaretState state : caretStates) {
+      convertedStates.add(new CaretState(state.getCaretPosition() == null ? null : myEditorWindow.injectedToHost(state.getCaretPosition()),
+                                         state.getSelectionStart() == null ? null : myEditorWindow.injectedToHost(state.getSelectionStart()),
+                                         state.getSelectionEnd() == null ? null : myEditorWindow.injectedToHost(state.getSelectionEnd())));
     }
-    List<Segment> convertedSelections = new ArrayList<Segment>(selections.size());
-    for (Segment selection : selections) {
-      convertedSelections.add(new TextRange(myEditorWindow.getDocument().injectedToHost(selection.getStartOffset()),
-                                            myEditorWindow.getDocument().injectedToHost(selection.getEndOffset())));
-    }
-    myDelegate.setCaretsAndSelections(convertedPositions, convertedSelections);
+    myDelegate.setCaretsAndSelections(convertedStates);
   }
 
   private InjectedCaret createInjectedCaret(Caret caret) {
index cf36d1831b5388c41123fc2086cb4643cdf2cca0..008ed7d5f02eb1d323c67266c75aa2a5228b173b 100644 (file)
@@ -46,7 +46,13 @@ public class EditorModificationUtil {
     int selectionStart = selectionModel.getSelectionStart();
     int selectionEnd = selectionModel.getSelectionEnd();
 
-    editor.getCaretModel().moveToOffset(selectionStart);
+    VisualPosition selectionStartPosition = selectionModel.getSelectionStartPosition();
+    if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets() && selectionStartPosition != null) {
+      editor.getCaretModel().moveToVisualPosition(selectionStartPosition);
+    }
+    else {
+      editor.getCaretModel().moveToOffset(selectionStart);
+    }
     selectionModel.removeSelection();
     editor.getDocument().deleteString(selectionStart, selectionEnd);
     editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
@@ -102,7 +108,13 @@ public class EditorModificationUtil {
   public static int insertStringAtCaret(Editor editor, @NotNull String s, boolean toProcessOverwriteMode, boolean toMoveCaret, int caretShift) {
     final SelectionModel selectionModel = editor.getSelectionModel();
     if (selectionModel.hasSelection()) {
-      editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart(), true);
+      VisualPosition startPosition = selectionModel.getSelectionStartPosition();
+      if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets() && startPosition != null) {
+        editor.getCaretModel().moveToVisualPosition(startPosition);
+      }
+      else {
+        editor.getCaretModel().moveToOffset(selectionModel.getSelectionStart(), true);
+      }
     }
 
     // There is a possible case that particular soft wraps become hard wraps if the caret is located at soft wrap-introduced virtual
index 1883cb55387c882e21f4d255b470f20d1dd5446d..182884b25020c3fa9143f1b24be64e0a1b0a1043 100644 (file)
@@ -49,13 +49,19 @@ class MoveCaretLeftOrRightHandler extends EditorActionHandler {
         int start = selectionModel.getSelectionStart();
         int end = selectionModel.getSelectionEnd();
         int caretOffset = caretModel.getOffset();
+        VisualPosition targetPosition = myDirection == Direction.RIGHT ? selectionModel.getSelectionEndPosition() : selectionModel.getSelectionStartPosition();
 
         //int leftGuard = start + (myDirection == Direction.LEFT ? 1 : 0);
         //int rightGuard = end - (myDirection == Direction.RIGHT ? 1 : 0);
         //if (TextRange.from(leftGuard, rightGuard - leftGuard + 1).contains(caretModel.getOffset())) { // See IDEADEV-36957
         if (start <= caretOffset && end >= caretOffset) { // See IDEADEV-36957
           selectionModel.removeSelection();
-          caretModel.moveToOffset(myDirection == Direction.RIGHT ? end : start);
+          if (caretModel.supportsMultipleCarets() && editor.isColumnMode() && targetPosition != null) {
+            caretModel.moveToVisualPosition(targetPosition);
+          }
+          else {
+            caretModel.moveToOffset(myDirection == Direction.RIGHT ? end : start);
+          }
           scrollingModel.scrollToCaret(ScrollType.RELATIVE);
           return;
         }
index 012410d34fe3d85dace99102067a7b66c994c705..41a833b3bffb89f9a369d30f8e904823d5461b2c 100644 (file)
@@ -806,6 +806,15 @@ public final class EditorUtil {
     int line = y / editor.getLineHeight();
     return line > 0 ? editor.visualToLogicalPosition(new VisualPosition(line, 0)).line : 0;
   }
+
+  public static boolean isAtLineEnd(@NotNull Editor editor, int offset) {
+    Document document = editor.getDocument();
+    if (offset < 0 || offset > document.getTextLength()) {
+      return false;
+    }
+    int line = document.getLineNumber(offset);
+    return offset == document.getLineEndOffset(line);
+  }
 }
 
 
index 1b79d356cfb64d94ac6d19be025a81d6fc9c9416..f6f030c67ee8908f824a7d27468bb21a400b3e7c 100644 (file)
@@ -94,6 +94,10 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
   private int startBefore;
   private int endBefore;
   boolean myUnknownDirection;
+  // offsets of selection start/end position relative to end of line - can be non-zero in column selection mode
+  // these are non-negative values, myStartVirtualOffset is always less or equal to myEndVirtualOffset
+  private int myStartVirtualOffset;
+  private int myEndVirtualOffset;
 
   CaretImpl(EditorImpl editor) {
     myEditor = editor;
@@ -144,10 +148,18 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
       if (marker.isValid()) {
         startAfter = marker.getStartOffset();
         endAfter = marker.getEndOffset();
+        if (myEndVirtualOffset > 0 && (!isVirtualSelectionEnabled()
+                                       || !EditorUtil.isAtLineEnd(myEditor, endAfter)
+                                       || myEditor.getDocument().getLineNumber(startAfter) != myEditor.getDocument().getLineNumber(endAfter))) {
+          myStartVirtualOffset = 0;
+          myEndVirtualOffset = 0;
+        }
       }
       else {
         startAfter = endAfter = getOffset();
         marker.release();
+        myStartVirtualOffset = 0;
+        myEndVirtualOffset = 0;
         mySelectionMarker = null;
       }
 
@@ -235,7 +247,8 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
     myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
       public void run() {
         SelectionModelImpl selectionModel = myEditor.getSelectionModel();
-        final int leadSelectionOffset = selectionModel.getLeadSelectionOffset();
+        final int leadSelectionOffset = getLeadSelectionOffset();
+        final VisualPosition leadSelectionPosition = getLeadSelectionPosition();
         LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection()
                                               ? selectionModel.getBlockStart()
                                               : getLogicalPosition();
@@ -349,24 +362,43 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
           }
           else {
             if (selectToDocumentStart) {
-              selectionModel.setSelection(leadSelectionOffset, 0);
+              if (myEditor.getCaretModel().supportsMultipleCarets()) {
+                setSelection(leadSelectionPosition, leadSelectionOffset, myEditor.offsetToVisualPosition(0), 0);
+              }
+              else {
+                setSelection(leadSelectionOffset, 0);
+              }
             }
             else if (pos.line >= myEditor.getVisibleLineCount()) {
-              if (leadSelectionOffset < document.getTextLength()) {
-                selectionModel.setSelection(leadSelectionOffset, document.getTextLength());
+              int endOffset = document.getTextLength();
+              if (leadSelectionOffset < endOffset) {
+                if (myEditor.getCaretModel().supportsMultipleCarets()) {
+                  setSelection(leadSelectionPosition, leadSelectionOffset, myEditor.offsetToVisualPosition(endOffset), endOffset);
+                }
+                else {
+                  setSelection(leadSelectionOffset, endOffset);
+                }
               }
             }
             else {
               int selectionStartToUse = leadSelectionOffset;
-              if (selectionModel.isUnknownDirection()) {
-                if (getOffset() > leadSelectionOffset) {
-                  selectionStartToUse = Math.min(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
+              VisualPosition selectionStartPositionToUse = leadSelectionPosition;
+              if (isUnknownDirection()) {
+                if (getOffset() > leadSelectionOffset ^ getSelectionStart() < getSelectionEnd()) {
+                  selectionStartToUse = getSelectionEnd();
+                  selectionStartPositionToUse = getSelectionEndPosition();
                 }
                 else {
-                  selectionStartToUse = Math.max(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
+                  selectionStartToUse = getSelectionStart();
+                  selectionStartPositionToUse = getSelectionStartPosition();
                 }
               }
-              selectionModel.setSelection(selectionStartToUse, getVisualPosition(), getOffset());
+              if (myEditor.getCaretModel().supportsMultipleCarets()) {
+                setSelection(selectionStartPositionToUse, selectionStartToUse, getVisualPosition(), getOffset());
+              }
+              else {
+                setSelection(selectionStartToUse, getVisualPosition(), getOffset());
+              }
             }
           }
         }
@@ -933,22 +965,32 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
     int lineShift = above ? -1 : 1;
     final CaretImpl clone = cloneWithoutSelection();
     final int newSelectionStartOffset, newSelectionEndOffset;
+    final VisualPosition newSelectionStartPosition, newSelectionEndPosition;
     final boolean hasNewSelection;
     if (hasSelection()) {
-      LogicalPosition selectionStart = myEditor.offsetToLogicalPosition(getSelectionStart());
-      LogicalPosition selectionEnd = myEditor.offsetToLogicalPosition(getSelectionEnd());
-      LogicalPosition newSelectionStart = new LogicalPosition(selectionStart.line + lineShift, selectionStart.column);
-      LogicalPosition newSelectionEnd = new LogicalPosition(selectionEnd.line + lineShift, selectionEnd.column);
-      newSelectionStartOffset = getTruncatedOffset(newSelectionStart);
-      newSelectionEndOffset = getTruncatedOffset(newSelectionEnd);
-      hasNewSelection = newSelectionStartOffset != newSelectionEndOffset;
+      VisualPosition startPosition = getSelectionStartPosition();
+      VisualPosition endPosition = getSelectionEndPosition();
+      VisualPosition leadPosition = getLeadSelectionPosition();
+      boolean leadIsStart = leadPosition.equals(startPosition);
+      boolean leadIsEnd = leadPosition.equals(endPosition);
+      LogicalPosition selectionStart = myEditor.visualToLogicalPosition(leadIsStart || leadIsEnd ? leadPosition : startPosition);
+      LogicalPosition selectionEnd = myEditor.visualToLogicalPosition(leadIsEnd ? startPosition : endPosition);
+      LogicalPosition newSelectionStart = truncate(new LogicalPosition(selectionStart.line + lineShift, selectionStart.column));
+      LogicalPosition newSelectionEnd = truncate(new LogicalPosition(selectionEnd.line + lineShift, selectionEnd.column));
+      newSelectionStartOffset = myEditor.logicalPositionToOffset(newSelectionStart);
+      newSelectionEndOffset = myEditor.logicalPositionToOffset(newSelectionEnd);
+      newSelectionStartPosition = myEditor.logicalToVisualPosition(newSelectionStart);
+      newSelectionEndPosition = myEditor.logicalToVisualPosition(newSelectionEnd);
+      hasNewSelection = !newSelectionStart.equals(newSelectionEnd);
     }
     else {
       newSelectionStartOffset = 0;
       newSelectionEndOffset = 0;
+      newSelectionStartPosition = null;
+      newSelectionEndPosition = null;
       hasNewSelection = false;
     }
-    LogicalPosition oldPosition = hasSelection() && !hasNewSelection ? myEditor.offsetToLogicalPosition(getSelectionStart()) : getLogicalPosition();
+    LogicalPosition oldPosition = getLogicalPosition();
     int newLine = oldPosition.line + lineShift;
     if (newLine < 0 || newLine >= myEditor.getDocument().getLineCount()) {
       Disposer.dispose(clone);
@@ -956,11 +998,11 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
     }
     clone.moveToLogicalPosition(new LogicalPosition(newLine, oldPosition.column), false, null, false);
     if (myEditor.getCaretModel().addCaret(clone)) {
-      if (hasSelection() && hasNewSelection) {
+      if (hasNewSelection) {
         myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
           @Override
           public void run() {
-            clone.setSelection(Math.min(newSelectionStartOffset, newSelectionEndOffset), Math.max(newSelectionStartOffset, newSelectionEndOffset));
+            clone.setSelection(newSelectionStartPosition, newSelectionStartOffset, newSelectionEndPosition, newSelectionEndOffset);
           }
         });
         if (!clone.isValid()) {
@@ -976,15 +1018,15 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
     }
   }
 
-  private int getTruncatedOffset(LogicalPosition position) {
+  private LogicalPosition truncate(LogicalPosition position) {
     if (position.line < 0) {
-      return 0;
+      return new LogicalPosition(0, 0);
     }
     else if (position.line >= myEditor.getDocument().getLineCount()) {
-      return myEditor.getDocument().getTextLength();
+      return myEditor.offsetToLogicalPosition(myEditor.getDocument().getTextLength());
     }
     else {
-      return myEditor.logicalPositionToOffset(position);
+      return position;
     }
   }
 
@@ -1029,18 +1071,21 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
   @NotNull
   @Override
   public VisualPosition getSelectionStartPosition() {
-    VisualPosition defaultPosition = myEditor.offsetToVisualPosition(getSelectionStart());
-    if (!hasSelection()) {
-      return defaultPosition;
+    validateContext(false);
+    VisualPosition position;
+    if (hasSelection() && mySelectionMarker != null) {
+      position = mySelectionMarker.getStartPosition();
+      if (position == null) {
+        position = myEditor.offsetToVisualPosition(mySelectionMarker.getStartOffset());
+      }
     }
-
-    MyRangeMarker marker = mySelectionMarker;
-    if (marker == null) {
-      return defaultPosition;
+    else {
+      position = isVirtualSelectionEnabled() ? getVisualPosition() : myEditor.offsetToVisualPosition(getOffset());
     }
-
-    VisualPosition result = marker.getStartPosition();
-    return result == null ? defaultPosition : result;
+    if (hasVirtualSelection()) {
+      position = new VisualPosition(position.line, position.column + myStartVirtualOffset);
+    }
+    return position;
   }
 
   @Override
@@ -1058,25 +1103,29 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
   @NotNull
   @Override
   public VisualPosition getSelectionEndPosition() {
-    VisualPosition defaultPosition = myEditor.offsetToVisualPosition(getSelectionEnd());
-    if (!hasSelection()) {
-      return defaultPosition;
+    validateContext(false);
+    VisualPosition position;
+    if (hasSelection() && mySelectionMarker != null) {
+      position = mySelectionMarker.getEndPosition();
+      if (position == null) {
+        position = myEditor.offsetToVisualPosition(mySelectionMarker.getEndOffset());
+      }
     }
-
-    MyRangeMarker marker = mySelectionMarker;
-    if (marker == null) {
-      return defaultPosition;
+    else {
+      position = isVirtualSelectionEnabled() ? getVisualPosition() : myEditor.offsetToVisualPosition(getOffset());
     }
-
-    VisualPosition result = marker.getEndPosition();
-    return result == null ? defaultPosition : result;
+    if (hasVirtualSelection()) {
+      position = new VisualPosition(position.line, position.column + myEndVirtualOffset);
+    }
+    return position;
   }
 
   @Override
   public boolean hasSelection() {
     validateContext(false);
     MyRangeMarker marker = mySelectionMarker;
-    return marker != null && marker.isValid() && marker.getEndOffset() > marker.getStartOffset();
+    return marker != null && marker.isValid() && (marker.getEndOffset() > marker.getStartOffset()
+                                                  || isVirtualSelectionEnabled() && myEndVirtualOffset > myStartVirtualOffset);
   }
 
   @Override
@@ -1162,10 +1211,12 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
         }
 
     /* Normalize selection */
+        boolean switchedOffsets = false;
         if (startOffset > endOffset) {
           int tmp = startOffset;
           startOffset = endOffset;
           endOffset = tmp;
+          switchedOffsets = true;
         }
 
         FoldingModelEx foldingModel = myEditor.getFoldingModel();
@@ -1200,6 +1251,8 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
         }
 
         marker = new MyRangeMarker((DocumentEx)doc, startOffset, endOffset);
+        myStartVirtualOffset = 0;
+        myEndVirtualOffset = 0;
         if (visualPositionAware) {
           if (endPosition.after(startPosition)) {
             marker.setStartPosition(startPosition);
@@ -1211,6 +1264,17 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
             marker.setEndPosition(startPosition);
             marker.setEndPositionIsLead(true);
           }
+
+          if (isVirtualSelectionEnabled() &&
+              myEditor.getDocument().getLineNumber(startOffset) == myEditor.getDocument().getLineNumber(endOffset)) {
+            int endLineColumn = myEditor.offsetToVisualPosition(endOffset).column;
+            int startDiff =
+              EditorUtil.isAtLineEnd(myEditor, switchedOffsets ? endOffset : startOffset) ? startPosition.column - endLineColumn : 0;
+            int endDiff =
+              EditorUtil.isAtLineEnd(myEditor, switchedOffsets ? startOffset : endOffset) ? endPosition.column - endLineColumn : 0;
+            myStartVirtualOffset = Math.max(0, Math.min(startDiff, endDiff));
+            myEndVirtualOffset = Math.max(0, Math.max(startDiff, endDiff));
+          }
         }
         mySelectionMarker = marker;
 
@@ -1248,6 +1312,8 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
           int endOffset = marker.getEndOffset();
           marker.release();
           mySelectionMarker = null;
+          myStartVirtualOffset = 0;
+          myEndVirtualOffset = 0;
           myEditor.getSelectionModel().fireSelectionChanged(startOffset, endOffset, caretOffset, caretOffset);
         }
       }
@@ -1293,17 +1359,36 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
   public VisualPosition getLeadSelectionPosition() {
     MyRangeMarker marker = mySelectionMarker;
     VisualPosition caretPosition = getVisualPosition();
+    if (isVirtualSelectionEnabled() && !hasSelection()) {
+      return caretPosition;
+    }
     if (marker == null) {
       return caretPosition;
     }
 
     if (marker.isEndPositionIsLead()) {
       VisualPosition result = marker.getEndPosition();
-      return result == null ? getSelectionEndPosition() : result;
+      if (result == null) {
+        return getSelectionEndPosition();
+      }
+      else {
+        if (hasVirtualSelection()) {
+          result = new VisualPosition(result.line, result.column + myEndVirtualOffset);
+        }
+        return result;
+      }
     }
     else {
       VisualPosition result = marker.getStartPosition();
-      return result == null ? getSelectionStartPosition() : result;
+      if (result == null) {
+        return getSelectionStartPosition();
+      }
+      else {
+        if (hasVirtualSelection()) {
+          result = new VisualPosition(result.line, result.column + myStartVirtualOffset);
+        }
+        return result;
+      }
     }
   }
 
@@ -1367,6 +1452,16 @@ public class CaretImpl extends UserDataHolderBase implements Caret {
     }
   }
 
+  private boolean isVirtualSelectionEnabled() {
+    return myEditor.isColumnMode() && myEditor.getCaretModel().supportsMultipleCarets();
+  }
+
+  boolean hasVirtualSelection() {
+    validateContext(false);
+    MyRangeMarker marker = mySelectionMarker;
+    return marker != null && marker.isValid() && isVirtualSelectionEnabled() && myEndVirtualOffset > myStartVirtualOffset;
+  }
+
   /**
    * Encapsulates information about target vertical range info - its <code>'y'</code> coordinate and height in pixels.
    */
index 72de1c92bbabd65bdab4c826e97b91af5745c5b4..e6f07a5d9c3412110eca150749c217c12badae38 100644 (file)
@@ -37,7 +37,6 @@ import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
 import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
 import com.intellij.openapi.editor.markup.TextAttributes;
 import com.intellij.openapi.util.Disposer;
-import com.intellij.openapi.util.Segment;
 import com.intellij.openapi.util.registry.Registry;
 import com.intellij.util.EventDispatcher;
 import org.jetbrains.annotations.NotNull;
@@ -403,8 +402,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
       }
       CaretImpl currCaret = it.next();
       if (prevCaret != null && (currCaret.getVisualPosition().equals(prevCaret.getVisualPosition())
-                                || regionsIntersect(currCaret.getSelectionStart(), currCaret.getSelectionEnd(),
-                                                    prevCaret.getSelectionStart(), prevCaret.getSelectionEnd()))) {
+                                || selectionsIntersect(currCaret, prevCaret))) {
         int newSelectionStart = Math.min(currCaret.getSelectionStart(), prevCaret.getSelectionStart());
         int newSelectionEnd = Math.max(currCaret.getSelectionEnd(), prevCaret.getSelectionEnd());
         CaretImpl toRetain, toRemove;
@@ -429,10 +427,12 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
     }
   }
 
-  private static boolean regionsIntersect(int firstStart, int firstEnd, int secondStart, int secondEnd) {
-    return firstStart < secondStart && firstEnd > secondStart
-      || firstStart > secondStart && firstStart < secondEnd
-      || firstStart == secondStart && secondEnd > secondStart && firstEnd > firstStart;
+  private static boolean selectionsIntersect(CaretImpl firstCaret, CaretImpl secondCaret) {
+    return firstCaret.getSelectionStart() < secondCaret.getSelectionStart() && firstCaret.getSelectionEnd() > secondCaret.getSelectionStart()
+      || firstCaret.getSelectionStart() > secondCaret.getSelectionStart() && firstCaret.getSelectionStart() < secondCaret.getSelectionEnd()
+      || firstCaret.getSelectionStart() == secondCaret.getSelectionStart() && secondCaret.getSelectionEnd() > secondCaret.getSelectionStart() && firstCaret.getSelectionEnd() > firstCaret.getSelectionStart()
+      || (firstCaret.getSelectionStart() == firstCaret.getSelectionEnd() && firstCaret.hasVirtualSelection() || secondCaret.getSelectionStart() == secondCaret.getSelectionEnd() && secondCaret.hasVirtualSelection())
+         && (firstCaret.getSelectionStart() == secondCaret.getSelectionStart() || firstCaret.getSelectionEnd() == secondCaret.getSelectionEnd());
   }
 
   void doWithCaretMerging(Runnable runnable) {
@@ -452,21 +452,17 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
   }
 
   @Override
-  public void setCaretsAndSelections(@NotNull final List<LogicalPosition> caretPositions, @NotNull final List<? extends Segment> selections) {
+  public void setCaretsAndSelections(@NotNull final List<CaretState> caretStates) {
     myEditor.assertIsDispatchThread();
-    if (caretPositions.isEmpty()) {
+    if (caretStates.isEmpty()) {
       throw new IllegalArgumentException("At least one caret should exist");
     }
-    if (caretPositions.size() != selections.size()) {
-      throw new IllegalArgumentException("Position and selection lists are of different size");
-    }
     doWithCaretMerging(new Runnable() {
       public void run() {
         int index = 0;
         int oldCaretCount = myCarets.size();
         Iterator<CaretImpl> caretIterator = myCarets.iterator();
-        Iterator<? extends Segment> selectionIterator = selections.iterator();
-        for (LogicalPosition caretPosition : caretPositions) {
+        for (CaretState caretState : caretStates) {
           CaretImpl caret;
           boolean caretAdded;
           if (index++ < oldCaretCount) {
@@ -475,8 +471,8 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
           }
           else {
             caret = new CaretImpl(myEditor);
-            if (caretPosition != null) {
-              caret.moveToLogicalPosition(caretPosition, false, null, false);
+            if (caretState != null && caretState.getCaretPosition() != null) {
+              caret.moveToLogicalPosition(caretState.getCaretPosition(), false, null, false);
             }
             synchronized (myCarets) {
               myCarets.add(caret);
@@ -484,15 +480,16 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
             fireCaretAdded(caret);
             caretAdded = true;
           }
-          if (caretPosition != null && !caretAdded) {
-            caret.moveToLogicalPosition(caretPosition);
+          if (caretState != null && caretState.getCaretPosition() != null && !caretAdded) {
+            caret.moveToLogicalPosition(caretState.getCaretPosition());
           }
-          Segment selection = selectionIterator.next();
-          if (selection != null) {
-            caret.setSelection(selection.getStartOffset(), selection.getEndOffset());
+          if (caretState != null && caretState.getSelectionStart() != null && caretState.getSelectionEnd() != null) {
+            caret.setSelection(myEditor.logicalToVisualPosition(caretState.getSelectionStart()), myEditor.logicalPositionToOffset(caretState.getSelectionStart()),
+                               myEditor.logicalToVisualPosition(caretState.getSelectionEnd()), myEditor.logicalPositionToOffset(
+              caretState.getSelectionEnd()));
           }
         }
-        int caretsToRemove = myCarets.size() - caretPositions.size();
+        int caretsToRemove = myCarets.size() - caretStates.size();
         for (int i = 0; i < caretsToRemove; i++) {
           CaretImpl caret;
           synchronized (myCarets) {
index aa04b488cf29ace4b4377ba45113b6d1c28f1751..25f559eb5961e6139afd573049fa6174d338d21a 100644 (file)
@@ -2289,6 +2289,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
             position.x = drawSoftWrapAwareBackground(g, backColor, text, start, lEnd - lIterator.getSeparatorLength(), position, fontType,
                                                      defaultBackground, clip, softWrapsToSkip, caretRowPainted);
 
+            paintAfterLineEndBackgroundSegments(g, iterationState, position, defaultBackground, lineHeight);
+
             if (lIterator.getLineNumber() < lastLineIndex) {
               if (backColor != null && !backColor.equals(defaultBackground)) {
                 g.setColor(backColor);
@@ -2296,6 +2298,9 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
               }
             }
             else {
+              if (iterationState.hasPastFileEndBackgroundSegments()) {
+                paintAfterLineEndBackgroundSegments(g, iterationState, position, defaultBackground, lineHeight);
+              }
               paintAfterFileEndBackground(iterationState,
                                           g,
                                           position, clip,
@@ -2380,6 +2385,24 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
     }
   }
 
+  private void paintAfterLineEndBackgroundSegments(@NotNull Graphics g,
+                                                   @NotNull IterationState iterationState,
+                                                   @NotNull Point position,
+                                                   @NotNull Color defaultBackground,
+                                                   int lineHeight) {
+    while (iterationState.hasPastLineEndBackgroundSegment()) {
+      TextAttributes backgroundAttributes = iterationState.getPastLineEndBackgroundAttributes();
+      int width = EditorUtil.getSpaceWidth(backgroundAttributes.getFontType(), this) * iterationState.getPastLineEndBackgroundSegmentWidth();
+      Color color = getBackgroundColor(backgroundAttributes);
+      if (color != null && !color.equals(defaultBackground)) {
+        g.setColor(color);
+        g.fillRect(position.x, position.y, width, lineHeight);
+      }
+      position.x += width;
+      iterationState.advanceToNextPastLineEndBackgroundSegment();
+    }
+  }
+
   private void paintRectangularSelection(@NotNull Graphics g) {
     final SelectionModel model = getSelectionModel();
     if (!model.hasBlockSelection()) return;
index bec264521612ee77764efe7daaf8747c435a35e2..4bd96fdda625bb0a793407d53905d0d1a8539bec 100644 (file)
@@ -17,6 +17,7 @@ package com.intellij.openapi.editor.impl;
 
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.Caret;
 import com.intellij.openapi.editor.CaretModel;
 import com.intellij.openapi.editor.FoldRegion;
 import com.intellij.openapi.editor.RangeMarker;
@@ -92,7 +93,13 @@ public final class IterationState {
 
   private final int[] mySelectionStarts;
   private final int[] mySelectionEnds;
+  private final int[] myVirtualSelectionStarts;
+  private final int[] myVirtualSelectionEnds;
   private int myCurrentSelectionIndex = 0;
+  private int myCurrentVirtualSelectionIndex = 0;
+  private boolean myCurrentLineHasVirtualSelection;
+  private int myCurrentPastLineEndBackgroundSegment; // 0 - before selection, 1 - in selection, 2 - after selection
+  private Color myCurrentBackgroundColor;
 
   private final List<RangeHighlighterEx> myCurrentHighlighters = new ArrayList<RangeHighlighterEx>();
 
@@ -126,8 +133,32 @@ public final class IterationState {
     myHighlighterIterator = editor.getHighlighter().createIterator(start);
 
     boolean hasSelection = useCaretAndSelection && (editor.getCaretModel().supportsMultipleCarets() || editor.getSelectionModel().hasSelection() || editor.getSelectionModel().hasBlockSelection());
-    mySelectionStarts = hasSelection ? editor.getSelectionModel().getBlockSelectionStarts() : ArrayUtilRt.EMPTY_INT_ARRAY;
-    mySelectionEnds = hasSelection ? editor.getSelectionModel().getBlockSelectionEnds() : ArrayUtilRt.EMPTY_INT_ARRAY;
+    if (!hasSelection) {
+      mySelectionStarts = ArrayUtilRt.EMPTY_INT_ARRAY;
+      mySelectionEnds = ArrayUtilRt.EMPTY_INT_ARRAY;
+      myVirtualSelectionStarts = ArrayUtilRt.EMPTY_INT_ARRAY;
+      myVirtualSelectionEnds = ArrayUtilRt.EMPTY_INT_ARRAY;
+    }
+    else if (editor.getCaretModel().supportsMultipleCarets()) {
+      List<Caret> carets = editor.getCaretModel().getAllCarets();
+      mySelectionStarts = new int[carets.size()];
+      mySelectionEnds = new int[carets.size()];
+      myVirtualSelectionStarts = new int[carets.size()];
+      myVirtualSelectionEnds = new int[carets.size()];
+      for (int i = 0; i < carets.size(); i++) {
+        Caret caret = carets.get(i);
+        mySelectionStarts[i] = caret.getSelectionStart();
+        mySelectionEnds[i] = caret.getSelectionEnd();
+        myVirtualSelectionStarts[i] = caret.getSelectionStartPosition().column - editor.offsetToVisualPosition(mySelectionStarts[i]).column;
+        myVirtualSelectionEnds[i] = caret.getSelectionEndPosition().column - editor.offsetToVisualPosition(mySelectionEnds[i]).column;
+      }
+    }
+    else {
+      mySelectionStarts = editor.getSelectionModel().getBlockSelectionStarts();
+      mySelectionEnds = editor.getSelectionModel().getBlockSelectionEnds();
+      myVirtualSelectionStarts = new int[mySelectionStarts.length];
+      myVirtualSelectionEnds = new int[mySelectionEnds.length];
+    }
 
     myFoldingModel = editor.getFoldingModel();
     myFoldTextAttributes = myFoldingModel.getPlaceholderAttributes();
@@ -229,6 +260,7 @@ public final class IterationState {
     myStartOffset = myEndOffset;
     advanceSegmentHighlighters();
     advanceCurrentSelectionIndex();
+    advanceCurrentVirtualSelectionIndex();
 
     myCurrentFold = myFoldingModel.fetchOutermost(myStartOffset);
     if (myCurrentFold != null) {
@@ -290,6 +322,13 @@ public final class IterationState {
     }
   }
 
+  private void advanceCurrentVirtualSelectionIndex() {
+    while (myCurrentVirtualSelectionIndex < mySelectionEnds.length
+           && (myStartOffset > mySelectionEnds[myCurrentVirtualSelectionIndex] || myVirtualSelectionEnds[myCurrentVirtualSelectionIndex] <= 0)) {
+      myCurrentVirtualSelectionIndex++;
+    }
+  }
+
   private int getSelectionEnd() {
     if (myCurrentSelectionIndex >= mySelectionStarts.length) {
       return myEnd;
@@ -394,12 +433,17 @@ public final class IterationState {
     List<TextAttributes> cachedAttributes = myCachedAttributesList;
     cachedAttributes.clear();
 
+    int selectionAttributesIndex = -1; // a 'would-be' or real position of selection attributes in attributes list
+
     //noinspection ForLoopReplaceableByForEach
     for (int i = 0; i < size; i++) {
       RangeHighlighterEx highlighter = myCurrentHighlighters.get(i);
-      if (selection != null && highlighter.getLayer() < HighlighterLayer.SELECTION) {
-        cachedAttributes.add(selection);
-        selection = null;
+      if (highlighter.getLayer() < HighlighterLayer.SELECTION) {
+        selectionAttributesIndex = cachedAttributes.size();
+        if (selection != null) {
+          cachedAttributes.add(selection);
+          selection = null;
+        }
       }
 
       if (syntax != null && highlighter.getLayer() < HighlighterLayer.SYNTAX) {
@@ -428,6 +472,9 @@ public final class IterationState {
       }
     }
 
+    if (selectionAttributesIndex < 0) {
+      selectionAttributesIndex = cachedAttributes.size();
+    }
     if (selection != null) cachedAttributes.add(selection);
     if (fold != null) cachedAttributes.add(fold);
     if (guard != null) cachedAttributes.add(guard);
@@ -440,6 +487,8 @@ public final class IterationState {
     EffectType effectType = null;
     int fontType = 0;
 
+    boolean selectionBackgroundIsPotentiallyVisible = cachedAttributes.isEmpty();
+
     //noinspection ForLoopReplaceableByForEach
     for (int i = 0; i < cachedAttributes.size(); i++) {
       TextAttributes attrs = cachedAttributes.get(i);
@@ -449,6 +498,9 @@ public final class IterationState {
       }
 
       if (back == null) {
+        if (isInSelection && i == selectionAttributesIndex || !isInSelection && i >= selectionAttributesIndex) {
+          selectionBackgroundIsPotentiallyVisible = true;
+        }
         back = ifDiffers(attrs.getBackgroundColor(), myDefaultBackground);
       }
 
@@ -467,6 +519,16 @@ public final class IterationState {
     if (effectType == null) effectType = EffectType.BOXED;
 
     myMergedAttributes.setAttributes(fore, back, effect, null, effectType, fontType);
+
+    myCurrentBackgroundColor = back;
+    if (selectionBackgroundIsPotentiallyVisible && myCurrentVirtualSelectionIndex < mySelectionStarts.length && myStartOffset == mySelectionEnds[myCurrentVirtualSelectionIndex]) {
+      myCurrentLineHasVirtualSelection = true;
+      myCurrentPastLineEndBackgroundSegment = myVirtualSelectionStarts[myCurrentVirtualSelectionIndex] > 0 ? 0 : 1;
+    }
+    else {
+      myCurrentLineHasVirtualSelection = false;
+      myCurrentPastLineEndBackgroundSegment = 0;
+    }
   }
 
   @Nullable
@@ -496,6 +558,41 @@ public final class IterationState {
     return myCurrentFold;
   }
 
+  public boolean hasPastLineEndBackgroundSegment() {
+    return myCurrentLineHasVirtualSelection && myCurrentPastLineEndBackgroundSegment < 2;
+  }
+
+  public int getPastLineEndBackgroundSegmentWidth() {
+    switch (myCurrentPastLineEndBackgroundSegment) {
+      case 0: return myVirtualSelectionStarts[myCurrentVirtualSelectionIndex];
+      case 1: return myVirtualSelectionEnds[myCurrentVirtualSelectionIndex] - myVirtualSelectionStarts[myCurrentVirtualSelectionIndex];
+      default: return 0;
+    }
+  }
+
+  @NotNull
+  public TextAttributes getPastLineEndBackgroundAttributes() {
+    myMergedAttributes.setBackgroundColor(myCurrentPastLineEndBackgroundSegment == 1 ? mySelectionAttributes.getBackgroundColor() : myCurrentBackgroundColor);
+    return myMergedAttributes;
+  }
+
+  public void advanceToNextPastLineEndBackgroundSegment() {
+    myCurrentPastLineEndBackgroundSegment++;
+  }
+
+  public boolean hasPastFileEndBackgroundSegments() {
+    myCurrentLineHasVirtualSelection = myVirtualSelectionEnds.length > 0
+                && myVirtualSelectionEnds[myVirtualSelectionEnds.length - 1] > 0
+                && myEndOffset == myEnd
+                && mySelectionEnds[mySelectionStarts.length - 1] == myEndOffset;
+    if (myCurrentLineHasVirtualSelection) {
+      myCurrentVirtualSelectionIndex = myVirtualSelectionStarts.length - 1;
+      myCurrentPastLineEndBackgroundSegment = myVirtualSelectionStarts[myCurrentVirtualSelectionIndex] > 0 ? 0 : 1;
+      myCurrentBackgroundColor = myEndOffset >= myCaretRowStart ? myCaretRowAttributes.getBackgroundColor() : myDefaultBackground;
+    }
+    return myCurrentLineHasVirtualSelection;
+  }
+
   @Nullable
   public Color getPastFileEndBackground() {
     boolean isInCaretRow = myEditor.getCaretModel().getLogicalPosition().line >= myDocument.getLineCount() - 1;
index 46be8b85a5cf969e0d9533dac5a1547c8d2d3a16..7ee0e4d2668d840c4953a0fd8164f1fe96b55211 100644 (file)
@@ -38,7 +38,6 @@ import com.intellij.openapi.editor.ex.util.EditorUtil;
 import com.intellij.openapi.editor.markup.TextAttributes;
 import com.intellij.openapi.ide.CopyPasteManager;
 import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.containers.ContainerUtil;
@@ -47,7 +46,10 @@ import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import java.awt.datatransfer.StringSelection;
-import java.util.*;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
 
 public class SelectionModelImpl implements SelectionModel, PrioritizedDocumentListener {
   private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.SelectionModelImpl");
@@ -214,42 +216,40 @@ public class SelectionModelImpl implements SelectionModel, PrioritizedDocumentLi
       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<LogicalPosition> positions = new LinkedList<LogicalPosition>();
-      List<TextRange> selections = new LinkedList<TextRange>();
+      List<CaretState> caretStates = new LinkedList<CaretState>();
       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);
-        int lineWidth = myEditor.offsetToLogicalPosition(lineEndOffset).column;
-        if (startColumn > lineWidth && endColumn > lineWidth) {
+        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));
-          positions.add(caretPos);
-          selections.add(new TextRange(lineEndOffset, lineEndOffset));
+          caretStates.add(new CaretState(caretPos,
+                                         lineEndPosition,
+                                         lineEndPosition));
         }
         else {
-          LogicalPosition startPos = new LogicalPosition(line, Math.min(startColumn, lineWidth));
-          LogicalPosition endPos = new LogicalPosition(line, Math.min(endColumn, lineWidth));
+          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);
-          positions.add(endPos);
-          selections.add(new TextRange(Math.min(startOffset, endOffset), Math.max(startOffset, endOffset)));
+          caretStates.add(new CaretState(endPos, startPos, endPos));
           hasSelection |= startOffset != endOffset;
         }
       }
       if (hasSelection && !myEditor.isColumnMode()) { // filtering out lines without selection
-        Iterator<LogicalPosition> positionIterator = positions.iterator();
-        Iterator<TextRange> selectionIterator = selections.iterator();
-        while(selectionIterator.hasNext()) {
-          TextRange selection = selectionIterator.next();
-          positionIterator.next();
-          if (selection.isEmpty()) {
-            selectionIterator.remove();
-            positionIterator.remove();
+        Iterator<CaretState> caretStateIterator = caretStates.iterator();
+        while(caretStateIterator.hasNext()) {
+          CaretState state = caretStateIterator.next();
+          //noinspection ConstantConditions
+          if (state.getSelectionStart().equals(state.getSelectionEnd())) {
+            caretStateIterator.remove();
           }
         }
       }
-      myEditor.getCaretModel().setCaretsAndSelections(positions, selections);
+      myEditor.getCaretModel().setCaretsAndSelections(caretStates);
     }
     else {
       removeSelection();
index 607e182dd3cbc96815ca3ad85bb08c556d842bc8..5d99e598423a64bbfc63667e4179e4824f9724fb 100644 (file)
@@ -18,7 +18,6 @@ package com.intellij.openapi.editor.textarea;
 import com.intellij.openapi.editor.*;
 import com.intellij.openapi.editor.event.CaretListener;
 import com.intellij.openapi.editor.markup.TextAttributes;
-import com.intellij.openapi.util.Segment;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -179,7 +178,7 @@ public class TextComponentCaretModel implements CaretModel {
   }
 
   @Override
-  public void setCaretsAndSelections(@NotNull List<LogicalPosition> caretPositions, @NotNull List<? extends Segment> selections) {
+  public void setCaretsAndSelections(@NotNull List<CaretState> caretStates) {
     throw new UnsupportedOperationException("Multiple carets are not supported");
   }
 
index 4514ee8fbe589ba109af16b38508c3e79ccc27a4..47c0088fa2ce7ad779b28e39a8425fdf70347e3f 100644 (file)
@@ -57,8 +57,10 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware {
   @NonNls private static final String TYPE_ID                         = "text-editor";
   @NonNls private static final String LINE_ATTR                       = "line";
   @NonNls private static final String COLUMN_ATTR                     = "column";
-  @NonNls private static final String SELECTION_START_ATTR            = "selection-start";
-  @NonNls private static final String SELECTION_END_ATTR              = "selection-end";
+  @NonNls private static final String SELECTION_START_LINE_ATTR       = "selection-start-line";
+  @NonNls private static final String SELECTION_START_COLUMN_ATTR     = "selection-start-column";
+  @NonNls private static final String SELECTION_END_LINE_ATTR         = "selection-end-line";
+  @NonNls private static final String SELECTION_END_COLUMN_ATTR       = "selection-end-column";
   @NonNls private static final String VERTICAL_SCROLL_PROPORTION_ATTR = "vertical-scroll-proportion";
   @NonNls private static final String VERTICAL_OFFSET_ATTR            = "vertical-offset";
   @NonNls private static final String MAX_VERTICAL_OFFSET_ATTR        = "max-vertical-offset";
@@ -100,10 +102,10 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware {
 
     try {
       List<Element> caretElements = element.getChildren(CARET_ELEMENT);
-      if (caretElements.isEmpty()) { // legacy format
+      if (caretElements.isEmpty()) {
         state.CARETS = new TextEditorState.CaretState[] {readCaretInfo(element)};
       }
-      else { // new format
+      else {
         state.CARETS = new TextEditorState.CaretState[caretElements.size()];
         for (int i = 0; i < caretElements.size(); i++) {
           state.CARETS[i] = readCaretInfo(caretElements.get(i));
@@ -129,8 +131,10 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware {
     TextEditorState.CaretState caretState = new TextEditorState.CaretState();
     caretState.LINE = parseWithDefault(element, LINE_ATTR);
     caretState.COLUMN = parseWithDefault(element, COLUMN_ATTR);
-    caretState.SELECTION_START = parseWithDefault(element, SELECTION_START_ATTR);
-    caretState.SELECTION_END = parseWithDefault(element, SELECTION_END_ATTR);
+    caretState.SELECTION_START_LINE = parseWithDefault(element, SELECTION_START_LINE_ATTR);
+    caretState.SELECTION_START_COLUMN = parseWithDefault(element, SELECTION_START_COLUMN_ATTR);
+    caretState.SELECTION_END_LINE = parseWithDefault(element, SELECTION_END_LINE_ATTR);
+    caretState.SELECTION_END_COLUMN = parseWithDefault(element, SELECTION_END_COLUMN_ATTR);
     return caretState;
   }
 
@@ -151,8 +155,10 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware {
         Element e = new Element(CARET_ELEMENT);
         e.setAttribute(LINE_ATTR, Integer.toString(caretState.LINE));
         e.setAttribute(COLUMN_ATTR, Integer.toString(caretState.COLUMN));
-        e.setAttribute(SELECTION_START_ATTR, Integer.toString(caretState.SELECTION_START));
-        e.setAttribute(SELECTION_END_ATTR, Integer.toString(caretState.SELECTION_END));
+        e.setAttribute(SELECTION_START_LINE_ATTR, Integer.toString(caretState.SELECTION_START_LINE));
+        e.setAttribute(SELECTION_START_COLUMN_ATTR, Integer.toString(caretState.SELECTION_START_COLUMN));
+        e.setAttribute(SELECTION_END_LINE_ATTR, Integer.toString(caretState.SELECTION_END_LINE));
+        e.setAttribute(SELECTION_END_COLUMN_ATTR, Integer.toString(caretState.SELECTION_END_COLUMN));
         element.addContent(e);
       }
     }
@@ -227,8 +233,12 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware {
       state.CARETS[i] = new TextEditorState.CaretState();
       state.CARETS[i].LINE = caret.getLogicalPosition().line;
       state.CARETS[i].COLUMN = caret.getLogicalPosition().column;
-      state.CARETS[i].SELECTION_START = caret.getSelectionStart();
-      state.CARETS[i++].SELECTION_END = caret.getSelectionEnd();
+      LogicalPosition selectionStartPosition = editor.visualToLogicalPosition(caret.getSelectionStartPosition());
+      LogicalPosition selectionEndPosition = editor.visualToLogicalPosition(caret.getSelectionEndPosition());
+      state.CARETS[i].SELECTION_START_LINE = selectionStartPosition.line;
+      state.CARETS[i].SELECTION_START_COLUMN = selectionStartPosition.column;
+      state.CARETS[i].SELECTION_END_LINE = selectionEndPosition.line;
+      state.CARETS[i++].SELECTION_END_COLUMN = selectionEndPosition.column;
     }
 
     // Saving scrolling proportion on UNDO may cause undesirable results of undo action fails to perform since
@@ -246,13 +256,13 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware {
   protected void setStateImpl(final Project project, final Editor editor, final TextEditorState state){
     if (editor.getCaretModel().supportsMultipleCarets()) {
       CaretModel caretModel = editor.getCaretModel();
-      ArrayList<LogicalPosition> positions = new ArrayList<LogicalPosition>();
-      ArrayList<Segment> selections = new ArrayList<Segment>();
+      List<CaretState> states = new ArrayList<CaretState>(state.CARETS.length);
       for (TextEditorState.CaretState caretState : state.CARETS) {
-        positions.add(new LogicalPosition(caretState.LINE, caretState.COLUMN));
-        selections.add(new TextRange(caretState.SELECTION_START, caretState.SELECTION_END));
+        states.add(new CaretState(new LogicalPosition(caretState.LINE, caretState.COLUMN),
+                                  new LogicalPosition(caretState.SELECTION_START_LINE, caretState.SELECTION_START_COLUMN),
+                                  new LogicalPosition(caretState.SELECTION_END_LINE, caretState.SELECTION_END_COLUMN)));
       }
-      caretModel.setCaretsAndSelections(positions, selections);
+      caretModel.setCaretsAndSelections(states);
     } else {
       LogicalPosition pos = new LogicalPosition(state.CARETS[0].LINE, state.CARETS[0].COLUMN);
       editor.getCaretModel().moveToLogicalPosition(pos);
@@ -275,15 +285,14 @@ public class TextEditorProvider implements FileEditorProvider, DumbAware {
       }
     }
 
-    final Document document = editor.getDocument();
-
     if (!editor.getCaretModel().supportsMultipleCarets()) {
-      if (state.CARETS[0].SELECTION_START == state.CARETS[0].SELECTION_END) {
+      if (state.CARETS[0].SELECTION_START_LINE == state.CARETS[0].SELECTION_END_LINE
+          && state.CARETS[0].SELECTION_START_COLUMN == state.CARETS[0].SELECTION_END_COLUMN) {
         editor.getSelectionModel().removeSelection();
       }
       else {
-        int startOffset = Math.min(state.CARETS[0].SELECTION_START, document.getTextLength());
-        int endOffset = Math.min(state.CARETS[0].SELECTION_END, document.getTextLength());
+        int startOffset = editor.logicalPositionToOffset(new LogicalPosition(state.CARETS[0].SELECTION_START_LINE, state.CARETS[0].SELECTION_START_COLUMN));
+        int endOffset = editor.logicalPositionToOffset(new LogicalPosition(state.CARETS[0].SELECTION_END_LINE, state.CARETS[0].SELECTION_END_COLUMN));
         editor.getSelectionModel().setSelection(startOffset, endOffset);
       }
     }
index 3319d511e1a1c54de4cf2df1204c9fe6048b20f2..d4aca486c041b062cc6e3576dea53ff734c74e9e 100644 (file)
@@ -122,8 +122,10 @@ public final class TextEditorState implements FileEditorState {
   public static class CaretState {
     public int   LINE;
     public int   COLUMN;
-    public int   SELECTION_START;
-    public int   SELECTION_END;
+    public int   SELECTION_START_LINE;
+    public int   SELECTION_START_COLUMN;
+    public int   SELECTION_END_LINE;
+    public int   SELECTION_END_COLUMN;
 
     public boolean equals(Object o) {
       if (!(o instanceof CaretState)) {
@@ -134,8 +136,10 @@ public final class TextEditorState implements FileEditorState {
 
       if (COLUMN != caretState.COLUMN) return false;
       if (LINE != caretState.LINE) return false;
-      if (SELECTION_START != caretState.SELECTION_START) return false;
-      if (SELECTION_END != caretState.SELECTION_END) return false;
+      if (SELECTION_START_LINE != caretState.SELECTION_START_LINE) return false;
+      if (SELECTION_START_COLUMN != caretState.SELECTION_START_COLUMN) return false;
+      if (SELECTION_END_LINE != caretState.SELECTION_END_LINE) return false;
+      if (SELECTION_END_COLUMN != caretState.SELECTION_END_COLUMN) return false;
 
       return true;
     }
index 9b94c44ab05991880cdf285c22bbf1afabd9461b..c4433cd15ca9f1d26331188d932c39823f9704a5 100644 (file)
 package com.intellij.openapi.editor;
 
 import com.intellij.openapi.editor.ex.EditorEx;
+import com.intellij.openapi.editor.impl.AbstractEditorTest;
 import com.intellij.testFramework.EditorTestUtil;
-import com.intellij.testFramework.LightPlatformCodeInsightTestCase;
 
 import java.io.IOException;
 
-public class EditorMultiCaretColumnModeTest extends LightPlatformCodeInsightTestCase {
+public class EditorMultiCaretColumnModeTest extends AbstractEditorTest {
   public void setUp() throws Exception {
     super.setUp();
     EditorTestUtil.enableMultipleCarets();
@@ -192,8 +192,99 @@ public class EditorMultiCaretColumnModeTest extends LightPlatformCodeInsightTest
                       "bbbb bb<selection>bb<caret></selection>");
   }
 
+  public void testMoveToSelectionStart() throws Exception {
+    init("a");
+    mouse().clickAt(0, 2).dragTo(0, 4).release();
+    verifyCaretsAndSelections(0, 4, 0, 2, 0, 4);
+
+    executeAction("EditorLeft");
+    verifyCaretsAndSelections(0, 2, 0, 2, 0, 2);
+  }
+
+  public void testMoveToSelectionEnd() throws Exception {
+    init("a");
+    mouse().clickAt(0, 4).dragTo(0, 2).release();
+    verifyCaretsAndSelections(0, 2, 0, 2, 0, 4);
+
+    executeAction("EditorRight");
+    verifyCaretsAndSelections(0, 4, 0, 4, 0, 4);
+  }
+
+  public void testReverseBlockSelection() throws Exception {
+    init("a");
+    mouse().clickAt(0, 4).dragTo(0, 3).release();
+    verifyCaretsAndSelections(0, 3, 0, 3, 0, 4);
+
+    executeAction("EditorRightWithSelection");
+    verifyCaretsAndSelections(0, 4, 0, 4, 0, 4);
+  }
+
+  public void testSelectionWithKeyboardInEmptySpace() throws Exception {
+    init("\n\n");
+    mouse().clickAt(1, 1);
+    verifyCaretsAndSelections(1, 1, 1, 1, 1, 1);
+
+    executeAction("EditorRightWithSelection");
+    verifyCaretsAndSelections(1, 2, 1, 1, 1, 2);
+
+    executeAction("EditorDownWithSelection");
+    verifyCaretsAndSelections(1, 2, 1, 1, 1, 2,
+                              2, 2, 2, 1, 2, 2);
+
+    executeAction("EditorLeftWithSelection");
+    verifyCaretsAndSelections(1, 1, 1, 1, 1, 1,
+                              2, 1, 2, 1, 2, 1);
+
+    executeAction("EditorLeftWithSelection");
+    verifyCaretsAndSelections(1, 0, 1, 0, 1, 1,
+                              2, 0, 2, 0, 2, 1);
+
+    executeAction("EditorUpWithSelection");
+    verifyCaretsAndSelections(1, 0, 1, 0, 1, 1);
+
+    executeAction("EditorUpWithSelection");
+    verifyCaretsAndSelections(0, 0, 0, 0, 0, 1,
+                              1, 0, 1, 0, 1, 1);
+
+    executeAction("EditorRightWithSelection");
+    verifyCaretsAndSelections(0, 1, 0, 1, 0, 1,
+                              1, 1, 1, 1, 1, 1);
+
+    executeAction("EditorRightWithSelection");
+    verifyCaretsAndSelections(0, 2, 0, 1, 0, 2,
+                              1, 2, 1, 1, 1, 2);
+
+    executeAction("EditorDownWithSelection");
+    verifyCaretsAndSelections(1, 2, 1, 1, 1, 2);
+
+    executeAction("EditorLeftWithSelection");
+    verifyCaretsAndSelections(1, 1, 1, 1, 1, 1);
+  }
+
+  public void testBlockSelection() throws Exception {
+    init("a\n" +
+         "bbb\n" +
+         "ccccc");
+    mouse().clickAt(2, 4).dragTo(0, 1).release();
+    verifyCaretsAndSelections(0, 1, 0, 1, 0, 4,
+                              1, 1, 1, 1, 1, 4,
+                              2, 1, 2, 1, 2, 4);
+  }
+
+  public void testTyping() throws Exception {
+    init("a\n" +
+         "bbb\n" +
+         "ccccc");
+    mouse().clickAt(0, 2).dragTo(2, 3).release();
+    type('S');
+    checkResultByText("a S<caret>\n" +
+                      "bbS<caret>\n" +
+                      "ccS<caret>cc");
+  }
+
   private void init(String text) throws IOException {
     configureFromFileText(getTestName(false) + ".txt", text);
+    EditorTestUtil.setEditorVisibleSize(myEditor, 1000, 1000);
     ((EditorEx)myEditor).setColumnMode(true);
   }
 }
index 0b168c4cf813b34f13bde6cc9f31a99e92cce313..6f13af47dee5ed651923971479d9784b67958f8a 100644 (file)
@@ -21,6 +21,7 @@ import com.intellij.openapi.command.CommandProcessor;
 import com.intellij.openapi.command.impl.CurrentEditorProvider;
 import com.intellij.openapi.command.impl.UndoManagerImpl;
 import com.intellij.openapi.command.undo.UndoManager;
+import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.editor.impl.AbstractEditorTest;
 import com.intellij.openapi.fileEditor.FileEditor;
 import com.intellij.openapi.fileEditor.TextEditor;
@@ -29,6 +30,8 @@ import com.intellij.testFramework.EditorTestUtil;
 import com.intellij.testFramework.TestFileType;
 import org.jetbrains.annotations.NotNull;
 
+import java.io.IOException;
+
 public class EditorMultiCaretUndoRedoTest extends AbstractEditorTest {
   private CurrentEditorProvider mySavedCurrentEditorProvider;
 
@@ -58,9 +61,7 @@ public class EditorMultiCaretUndoRedoTest extends AbstractEditorTest {
   public void testUndoRedo() throws Exception {
     init("some<caret> text<caret>\n" +
          "some <selection><caret>other</selection> <selection>text<caret></selection>\n" +
-         "<selection>ano<caret>ther</selection> line",
-         TestFileType.TEXT);
-    setupEditorProvider();
+         "<selection>ano<caret>ther</selection> line");
     type('A');
     executeAction("EditorDelete");
     mouse().clickAt(0, 1);
@@ -80,6 +81,25 @@ public class EditorMultiCaretUndoRedoTest extends AbstractEditorTest {
                       "A<caret> line");
   }
 
+  public void testBlockSelectionStateAfterUndo() throws Exception {
+    init("a");
+    ((EditorEx)myEditor).setColumnMode(true);
+    mouse().clickAt(0, 2);
+    type('b');
+    undo();
+    executeAction("EditorRightWithSelection");
+    verifyCaretsAndSelections(0, 3, 0, 2, 0, 3);
+  }
+
+  public void testBlockSelectionStateAfterUndo2() throws Exception {
+    init("a");
+    ((EditorEx)myEditor).setColumnMode(true);
+    mouse().clickAt(0, 0).dragTo(0, 2).release();
+    type('b');
+    undo();
+    verifyCaretsAndSelections(0, 2, 0, 0, 0, 2);
+  }
+
   private void checkResult(final String text) {
     CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
       @Override
@@ -105,7 +125,9 @@ public class EditorMultiCaretUndoRedoTest extends AbstractEditorTest {
     return TextEditorProvider.getInstance().getTextEditor(myEditor);
   }
 
-  private static void setupEditorProvider() {
+  private void init(String text) throws IOException {
+    init(text, TestFileType.TEXT);
+    EditorTestUtil.setEditorVisibleSize(myEditor, 1000, 1000);
     getUndoManager().setEditorProvider(new CurrentEditorProvider() {
       @Override
       public FileEditor getCurrentEditor() {
index bbb8de3200ccac49b5752d077518bfad087113d0..2c460a5ad6b021c0b7b5030cb8dc0b5717ea1529 100644 (file)
@@ -15,9 +15,7 @@
  */
 package com.intellij.openapi.editor.impl;
 
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.FoldRegion;
-import com.intellij.openapi.editor.FoldingModel;
+import com.intellij.openapi.editor.*;
 import com.intellij.openapi.editor.ex.FoldingModelEx;
 import com.intellij.openapi.editor.impl.softwrap.mapping.CachingSoftWrapDataMapper;
 import com.intellij.openapi.util.Pair;
@@ -220,4 +218,18 @@ public abstract class AbstractEditorTest extends LightPlatformCodeInsightTestCas
   public EditorMouseFixture mouse() {
     return new EditorMouseFixture((EditorImpl)myEditor);
   }
+
+  // for each caret its visual position and visual positions of selection start an and should be provided in the following order:
+  // caretLine, caretColumn, selectionStartLine, selectionStartColumn, selectionEndLine, selectionEndColumn
+  public static void verifyCaretsAndSelections(int... coordinates) {
+    int caretCount = coordinates.length / 6;
+    List<Caret> carets = myEditor.getCaretModel().getAllCarets();
+    assertEquals("Unexpected caret count", caretCount, carets.size());
+    for (int i = 0; i < caretCount; i++) {
+      Caret caret = carets.get(i);
+      assertEquals("Unexpected position for caret " + (i + 1), new VisualPosition(coordinates[i * 6], coordinates[i * 6 + 1]), caret.getVisualPosition());
+      assertEquals("Unexpected selection start for caret " + (i + 1), new VisualPosition(coordinates[i * 6 + 2], coordinates[i * 6 + 3]), caret.getSelectionStartPosition());
+      assertEquals("Unexpected selection end for caret " + (i + 1), new VisualPosition(coordinates[i * 6 + 4], coordinates[i * 6 + 5]), caret.getSelectionEndPosition());
+    }
+  }
 }
index 2b8f99e2693a48471680ae288d6c79d3e287d9ac..2502e767b331257812752dbd3467324b6cf0ab5f 100644 (file)
  */
 package com.intellij.openapi.editor.impl;
 
+import com.intellij.openapi.editor.colors.EditorColors;
+import com.intellij.openapi.editor.colors.EditorColorsManager;
+import com.intellij.openapi.editor.colors.EditorColorsScheme;
 import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.fileTypes.PlainTextFileType;
 import com.intellij.testFramework.EditorTestUtil;
+import com.intellij.testFramework.fixtures.EditorMouseFixture;
 import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase;
-import org.jetbrains.annotations.NotNull;
 import org.junit.Assert;
 
 import java.awt.*;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
 import java.util.List;
 
 public class IterationStateTest extends LightPlatformCodeInsightFixtureTestCase {
 
+  private Color DEFAULT_BACKGROUND;
+  private Color CARET_ROW_BACKGROUND;
+  private Color SELECTION_BACKGROUND;
+
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    EditorColorsScheme colorsScheme = EditorColorsManager.getInstance().getGlobalScheme();
+    DEFAULT_BACKGROUND = colorsScheme.getDefaultBackground();
+    CARET_ROW_BACKGROUND = colorsScheme.getColor(EditorColors.CARET_ROW_COLOR);
+    SELECTION_BACKGROUND = colorsScheme.getColor(EditorColors.SELECTION_BACKGROUND_COLOR);
+    assertEquals(3, new HashSet<Color>(Arrays.asList(DEFAULT_BACKGROUND, CARET_ROW_BACKGROUND, SELECTION_BACKGROUND)).size());
+  }
+
   public void testBlockSelection() {
-    verifySplitting("aa,<block>bb\ncc,d</block>d",
+    init("aa,<block>bb\n" +
+         "cc,d</block>d");
+    verifySplitting(true,
                     new Segment(0, 3, Color.BLACK),
                     new Segment(3, 4, Color.WHITE),
                     new Segment(4, 5, Color.BLACK),
@@ -49,20 +70,106 @@ public class IterationStateTest extends LightPlatformCodeInsightFixtureTestCase
     }
   }
 
-  private void verifySplitting(String text, Segment... expectedSegments) {
-    myFixture.configureByText(PlainTextFileType.INSTANCE, text);
+  public void testColumnModeBlockSelection() {
+    EditorTestUtil.enableMultipleCarets();
+    try {
+      init("a\n" +
+           "bbb\n" +
+           "ccccc");
+      setColumnModeOn();
+      mouse().clickAt(0, 2).dragTo(2, 4).release();
+      verifySplitting(false,
+                      new Segment(0, 1, DEFAULT_BACKGROUND),
+                      new Segment(1, 2, DEFAULT_BACKGROUND).plus(1, DEFAULT_BACKGROUND).plus(2, SELECTION_BACKGROUND),
+                      new Segment(2, 4, DEFAULT_BACKGROUND),
+                      new Segment(4, 5, SELECTION_BACKGROUND),
+                      new Segment(5, 6, DEFAULT_BACKGROUND).plus(1, SELECTION_BACKGROUND),
+                      new Segment(6, 8, CARET_ROW_BACKGROUND),
+                      new Segment(8, 10, SELECTION_BACKGROUND),
+                      new Segment(10, 11, CARET_ROW_BACKGROUND));
+    }
+    finally {
+      EditorTestUtil.disableMultipleCarets();
+    }
+  }
+
+  public void testColumnModeBlockSelectionAtLastNonEmptyLine() {
+    EditorTestUtil.enableMultipleCarets();
+    try {
+      init("a\n" +
+           "bbb\n" +
+           "ccccc");
+      setColumnModeOn();
+      mouse().clickAt(0, 2).dragTo(2, 6).release();
+      verifySplitting(false,
+                      new Segment(0, 1, DEFAULT_BACKGROUND),
+                      new Segment(1, 2, DEFAULT_BACKGROUND).plus(1, DEFAULT_BACKGROUND).plus(4, SELECTION_BACKGROUND),
+                      new Segment(2, 4, DEFAULT_BACKGROUND),
+                      new Segment(4, 5, SELECTION_BACKGROUND),
+                      new Segment(5, 6, DEFAULT_BACKGROUND).plus(3, SELECTION_BACKGROUND),
+                      new Segment(6, 8, CARET_ROW_BACKGROUND),
+                      new Segment(8, 11, SELECTION_BACKGROUND),
+                      new Segment(11, 11, null).plus(1, SELECTION_BACKGROUND));
+    }
+    finally {
+      EditorTestUtil.disableMultipleCarets();
+    }
+  }
+
+  public void testColumnModeBlockSelectionAtLastEmptyLine() {
+    EditorTestUtil.enableMultipleCarets();
+    try {
+      init("a\n" +
+           "");
+      setColumnModeOn();
+      mouse().clickAt(1, 1).dragTo(1, 2).release();
+      verifySplitting(false,
+                      new Segment(0, 1, DEFAULT_BACKGROUND),
+                      new Segment(1, 2, DEFAULT_BACKGROUND),
+                      new Segment(2, 2, null).plus(1, CARET_ROW_BACKGROUND).plus(1, SELECTION_BACKGROUND));
+    }
+    finally {
+      EditorTestUtil.disableMultipleCarets();
+    }
+  }
+
+  public void testColumnModeBlockSelectionAtEmptyLines() {
+    EditorTestUtil.enableMultipleCarets();
+    try {
+      init("\n");
+      setColumnModeOn();
+      mouse().clickAt(0, 1).dragTo(1, 2).release();
+      verifySplitting(false,
+                      new Segment(0, 1, DEFAULT_BACKGROUND).plus(1, DEFAULT_BACKGROUND).plus(1, SELECTION_BACKGROUND),
+                      new Segment(1, 1, null).plus(1, CARET_ROW_BACKGROUND).plus(1, SELECTION_BACKGROUND));
+    }
+    finally {
+      EditorTestUtil.disableMultipleCarets();
+    }
+  }
+
+  private void verifySplitting(boolean checkForegroundColor, Segment... expectedSegments) {
     EditorEx editor = (EditorEx)myFixture.getEditor();
     IterationState iterationState = new IterationState(editor, 0, editor.getDocument().getTextLength(), true);
     try {
       List<Segment> actualSegments = new ArrayList<Segment>();
       do {
-        actualSegments.add(new Segment(iterationState.getStartOffset(),
-                                       iterationState.getEndOffset(),
-                                       iterationState.getMergedAttributes().getForegroundColor()));
+        Segment segment = new Segment(iterationState.getStartOffset(),
+                                      iterationState.getEndOffset(),
+                                      checkForegroundColor ? iterationState.getMergedAttributes().getForegroundColor()
+                                                           : iterationState.getMergedAttributes().getBackgroundColor());
+        readPastLineState(iterationState, segment);
+        actualSegments.add(segment);
         iterationState.advance();
       }
       while (!iterationState.atEnd());
 
+      if (iterationState.hasPastFileEndBackgroundSegments()) {
+        Segment segment = new Segment(iterationState.getEndOffset(), iterationState.getEndOffset(), null);
+        readPastLineState(iterationState, segment);
+        actualSegments.add(segment);
+      }
+
       Assert.assertArrayEquals(expectedSegments, actualSegments.toArray());
     }
     finally {
@@ -70,15 +177,46 @@ public class IterationStateTest extends LightPlatformCodeInsightFixtureTestCase
     }
   }
 
+  private static void readPastLineState(IterationState iterationState, Segment segment) {
+    while(iterationState.hasPastLineEndBackgroundSegment()) {
+      segment.plus(iterationState.getPastLineEndBackgroundSegmentWidth(), iterationState.getPastLineEndBackgroundAttributes().getBackgroundColor());
+      iterationState.advanceToNextPastLineEndBackgroundSegment();
+    }
+  }
+
+  private void init(String text) {
+    myFixture.configureByText(PlainTextFileType.INSTANCE, text);
+    EditorTestUtil.setEditorVisibleSize(myFixture.getEditor(), 1000, 1000);
+  }
+
+  private void setColumnModeOn() {
+    ((EditorEx)myFixture.getEditor()).setColumnMode(true);
+  }
+
+  private EditorMouseFixture mouse() {
+    return new EditorMouseFixture((EditorImpl)myFixture.getEditor());
+  }
+
   private static class Segment {
     private final int start;
     private final int end;
-    private final Color fgColor;
+    private final Color color;
+    private final List<Integer> pastLineEndSegmentWidths = new ArrayList<Integer>();
+    private final List<Color> pastLineEndSegmentColors = new ArrayList<Color>();
 
-    private Segment(int start, int end, @NotNull Color fgColor) {
+    private Segment(int start, int end, Color color) {
       this.start = start;
       this.end = end;
-      this.fgColor = fgColor;
+      this.color = color;
+    }
+
+    /**
+     * Adds a past-line-end background segment
+     */
+    private Segment plus(int width, Color color) {
+      pastLineEndSegmentWidths.add(width);
+      pastLineEndSegmentColors.add(color);
+      return this;
     }
 
     @Override
@@ -90,7 +228,9 @@ public class IterationStateTest extends LightPlatformCodeInsightFixtureTestCase
 
       if (end != segment.end) return false;
       if (start != segment.start) return false;
-      if (!fgColor.equals(segment.fgColor)) return false;
+      if (color != null ? !color.equals(segment.color) : segment.color != null) return false;
+      if (!pastLineEndSegmentColors.equals(segment.pastLineEndSegmentColors)) return false;
+      if (!pastLineEndSegmentWidths.equals(segment.pastLineEndSegmentWidths)) return false;
 
       return true;
     }
@@ -99,7 +239,9 @@ public class IterationStateTest extends LightPlatformCodeInsightFixtureTestCase
     public int hashCode() {
       int result = start;
       result = 31 * result + end;
-      result = 31 * result + fgColor.hashCode();
+      result = 31 * result + (color != null ? color.hashCode() : 0);
+      result = 31 * result + pastLineEndSegmentWidths.hashCode();
+      result = 31 * result + pastLineEndSegmentColors.hashCode();
       return result;
     }
 
@@ -108,7 +250,9 @@ public class IterationStateTest extends LightPlatformCodeInsightFixtureTestCase
       return "Segment{" +
              "start=" + start +
              ", end=" + end +
-             ", color=" + fgColor +
+             ", color=" + color +
+             (pastLineEndSegmentWidths.isEmpty() ? "" : ", pastLineEndSegmentWidths=" + pastLineEndSegmentWidths) +
+             (pastLineEndSegmentColors.isEmpty() ? "" : ", pastLineEndSegmentColors=" + pastLineEndSegmentColors) +
              '}';
     }
   }
index 96f5bd812c52a1434d867600c9c5d11f3cf4cf28..3ae9b8d1c69baa8840296b8016084e5b056d00f5 100644 (file)
@@ -30,7 +30,6 @@ import com.intellij.openapi.editor.highlighter.HighlighterIterator;
 import com.intellij.openapi.editor.impl.DefaultEditorTextRepresentationHelper;
 import com.intellij.openapi.editor.impl.SoftWrapModelImpl;
 import com.intellij.openapi.editor.impl.softwrap.mapping.SoftWrapApplianceManager;
-import com.intellij.openapi.util.Segment;
 import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.util.registry.Registry;
 import com.intellij.openapi.util.text.StringUtil;
@@ -309,13 +308,13 @@ public class EditorTestUtil {
   public static void setCaretsAndSelection(Editor editor, CaretAndSelectionState caretsState) {
     CaretModel caretModel = editor.getCaretModel();
     if (caretModel.supportsMultipleCarets()) {
-      List<LogicalPosition> caretPositions = new ArrayList<LogicalPosition>();
-      List<Segment> selections = new ArrayList<Segment>();
+      List<CaretState> states = new ArrayList<CaretState>(caretsState.carets.size());
       for (CaretInfo caret : caretsState.carets) {
-        caretPositions.add(caret.position == null ? null : editor.offsetToLogicalPosition(caret.getCaretOffset(editor.getDocument())));
-        selections.add(caret.selection == null ? null : caret.selection);
+        states.add(new CaretState(caret.position == null ? null : editor.offsetToLogicalPosition(caret.getCaretOffset(editor.getDocument())),
+                                  caret.selection == null ? null : editor.offsetToLogicalPosition(caret.selection.getStartOffset()),
+                                  caret.selection == null ? null : editor.offsetToLogicalPosition(caret.selection.getEndOffset())));
       }
-      caretModel.setCaretsAndSelections(caretPositions, selections);
+      caretModel.setCaretsAndSelections(states);
     }
     else {
       assertEquals("Multiple carets are not supported by the model", 1, caretsState.carets.size());