IDEA-80056 Column selection mode improvement
authorDmitry Batrak <Dmitry.Batrak@jetbrains.com>
Tue, 4 Feb 2014 08:42:11 +0000 (12:42 +0400)
committerDmitry Batrak <Dmitry.Batrak@jetbrains.com>
Wed, 5 Feb 2014 13:01:27 +0000 (17:01 +0400)
initial version

121 files changed:
platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java [new file with mode: 0644]
platform/editor-ui-api/src/com/intellij/openapi/editor/CaretModel.java
platform/editor-ui-api/src/com/intellij/openapi/editor/SelectionModel.java
platform/editor-ui-api/src/com/intellij/openapi/editor/event/CaretEvent.java
platform/editor-ui-api/src/com/intellij/openapi/editor/event/MultipleCaretListener.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/codeInsight/daemon/impl/GotoNextErrorHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/BackspaceHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/BaseEnterHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeBlockEndAction.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeBlockEndWithSelectionAction.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeBlockStartAction.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/CodeBlockStartWithSelectionAction.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/CutHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/EndHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/JoinLinesHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/MatchBraceAction.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/PasteHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/SelectWordHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/UnSelectWordHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/moveUpDown/BaseMoveHandler.java
platform/lang-impl/src/com/intellij/codeInsight/editorActions/smartEnter/SmartEnterAction.java
platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/BackspaceHandler.java
platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/EndHandler.java
platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/HomeHandler.java
platform/lang-impl/src/com/intellij/codeInsight/lookup/impl/LookupActionHandler.java
platform/lang-impl/src/com/intellij/codeInsight/navigation/IncrementalSearchHandler.java
platform/lang-impl/src/com/intellij/codeInsight/navigation/MethodDownHandler.java
platform/lang-impl/src/com/intellij/codeInsight/navigation/MethodUpHandler.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/HomeEndHandler.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/editorActions/LineStartEndWithSelectionHandler.java
platform/lang-impl/src/com/intellij/ide/bookmarks/actions/GotoBookmarkActionBase.java
platform/lang-impl/src/com/intellij/injected/editor/CaretModelWindow.java
platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/injected/editor/SelectionModelWindow.java
platform/lang-impl/src/com/intellij/lang/customFolding/GotoCustomRegionAction.java
platform/lang-impl/src/com/intellij/openapi/editor/actions/NamedElementDuplicateHandler.java
platform/lang-impl/src/com/intellij/openapi/editor/actions/SelectWordAtCaretAction.java
platform/platform-api/src/com/intellij/openapi/editor/ClipboardTextPerCaretSplitter.java [new file with mode: 0644]
platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorAction.java
platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorActionHandler.java
platform/platform-api/src/com/intellij/openapi/editor/actionSystem/EditorWriteActionHandler.java
platform/platform-api/src/com/intellij/openapi/editor/actionSystem/TypedAction.java
platform/platform-api/src/com/intellij/openapi/fileEditor/OpenFileDescriptor.java
platform/platform-impl/src/com/intellij/ide/util/GotoLineNumberDialog.java
platform/platform-impl/src/com/intellij/openapi/diff/actions/DiffWalkerAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/BackspaceAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/CloneCaretAbove.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/openapi/editor/actions/CloneCaretBelow.java [new file with mode: 0644]
platform/platform-impl/src/com/intellij/openapi/editor/actions/CopyAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/CutAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteLineAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteToWordEndAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/DeleteToWordStartAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/DuplicateLinesAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/EnterAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/EscapeAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/HungryBackspaceAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/IndentSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/JoinLinesAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/LineEndAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/LineEndWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/LineStartAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/LineStartWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretDownAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretDownWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretLeftOrRightHandler.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretLeftWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretRightWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretUpAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/MoveCaretUpWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/NextWordAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/NextWordInDifferentHumpsModeAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/NextWordInDifferentHumpsModeWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/NextWordWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageBottomAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageBottomWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageDownAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageDownWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageTopAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageTopWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageUpAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PageUpWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PasteAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PreviousWordAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PreviousWordInDifferentHumpsModeAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PreviousWordInDifferentHumpsModeWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/PreviousWordWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/SelectLineAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/SimplePasteAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/SplitLineAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/StartNewLineAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/StartNewLineBeforeAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/SwapSelectionBoundariesAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/TabAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/TextEndAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/TextEndWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/TextStartAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/TextStartWithSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/ToggleCaseAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/ToggleColumnModeAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/UnindentSelectionAction.java
platform/platform-impl/src/com/intellij/openapi/editor/actions/UnselectWordAtCaretAction.java
platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java [new file with mode: 0644]
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/EditorMarkupModelImpl.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/editor/textarea/TextComponentSelectionModel.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-resources-en/src/messages/ActionsBundle.properties
platform/platform-resources-en/src/misc/registry.properties
platform/platform-resources/src/META-INF/LangExtensions.xml
platform/platform-resources/src/idea/PlatformActions.xml
platform/platform-tests/testSrc/com/intellij/openapi/editor/actions/KillToWordEndActionTest.java

diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/Caret.java
new file mode 100644 (file)
index 0000000..976dc1f
--- /dev/null
@@ -0,0 +1,256 @@
+/*
+ * 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 com.intellij.openapi.Disposable;
+import com.intellij.openapi.util.UserDataHolderEx;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * Represents a specific caret instance in the editor when it support multiple carets (see {@link CaretModel#supportsMultipleCarets()}.
+ * Provides methods to query and modify caret position and caret's associated selection.
+ */
+public interface Caret extends UserDataHolderEx, Disposable {
+  /**
+   * Returns an instance of CaretModel, current caret is associated with.
+   */
+  @NotNull
+  CaretModel getCaretModel();
+
+  /**
+   * Tells whether this caret is valid, i.e. recognized by the caret model currently. Caret is valid since its creation till its
+   * removal from caret model.
+   *
+   * @see com.intellij.openapi.editor.CaretModel#addCaret(VisualPosition)
+   * @see com.intellij.openapi.editor.CaretModel#removeCaret(Caret)
+   */
+  boolean isValid();
+
+  /**
+   * Moves the caret by the specified number of lines and/or columns.
+   *
+   * @param columnShift    the number of columns to move the caret by.
+   * @param lineShift      the number of lines to move the caret by.
+   * @param withSelection  if true, the caret move should extend the selection in the document.
+   * @param scrollToCaret  if true, the document should be scrolled so that the caret is visible after the move.
+   */
+  void moveCaretRelatively(int columnShift,
+                           int lineShift,
+                           boolean withSelection,
+                           boolean scrollToCaret);
+
+  /**
+   * Moves the caret to the specified logical position.
+   * If corresponding position is in the folded region currently, the region will be expanded.
+   *
+   * @param pos the position to move to.
+   */
+  void moveToLogicalPosition(@NotNull LogicalPosition pos);
+
+  /**
+   * Moves the caret to the specified visual position.
+   *
+   * @param pos the position to move to.
+   */
+  void moveToVisualPosition(@NotNull VisualPosition pos);
+
+  /**
+   * Short hand for calling {@link #moveToOffset(int, boolean)} with <code>'false'</code> as a second argument.
+   *
+   * @param offset      the offset to move to
+   */
+  void moveToOffset(int offset);
+
+  /**
+   * Moves the caret to the specified offset in the document.
+   * If corresponding position is in the folded region currently, the region will be expanded.
+   *
+   * @param offset                  the offset to move to.
+   * @param locateBeforeSoftWrap    there is a possible case that there is a soft wrap at the given offset, hence, the same offset
+   *                                corresponds to two different visual positions - just before soft wrap and just after soft wrap.
+   *                                We may want to clearly indicate where to put the caret then. Given parameter allows to do that.
+   *                                <b>Note:</b> it's ignored if there is no soft wrap at the given offset
+   */
+  void moveToOffset(int offset, boolean locateBeforeSoftWrap);
+
+  /**
+   * Caret position may be updated on document change (e.g. consider that user updates from VCS that causes addition of text
+   * before caret. Caret offset, visual and logical positions should be updated then). So, there is a possible case
+   * that caret model in in the process of caret position update now.
+   * <p/>
+   * Current method allows to check that.
+   *
+   * @return    <code>true</code> if caret position is up-to-date for now; <code>false</code> otherwise
+   */
+  boolean isUpToDate();
+
+  /**
+   * Returns the logical position of the caret.
+   *
+   * @return the caret position.
+   */
+  @NotNull
+  LogicalPosition getLogicalPosition();
+
+  /**
+   * Returns the visual position of the caret.
+   *
+   * @return the caret position.
+   */
+  @NotNull
+  VisualPosition getVisualPosition();
+
+  /**
+   * Returns the offset of the caret in the document.
+   *
+   * @return the caret offset.
+   */
+  int getOffset();
+
+  /**
+   * @return    document offset for the start of the logical line where caret is located
+   */
+  int getVisualLineStart();
+
+  /**
+   * @return    document offset that points to the first symbol shown at the next visual line after the one with caret on it
+   */
+  int getVisualLineEnd();
+
+  /**
+   * Returns the start offset in the document of the selected text range, or the caret
+   * position if there is currently no selection.
+   *
+   * @return the selection start offset.
+   */
+  int getSelectionStart();
+
+  /**
+   * @return    object that encapsulates information about visual position of selected text start if any
+   */
+  @NotNull
+  VisualPosition getSelectionStartPosition();
+
+  /**
+   * Returns the end offset in the document of the selected text range, or the caret
+   * position if there is currently no selection.
+   *
+   * @return the selection end offset.
+   */
+  int getSelectionEnd();
+
+  /**
+   * @return    object that encapsulates information about visual position of selected text end if any;
+   */
+  @NotNull
+  VisualPosition getSelectionEndPosition();
+
+  /**
+   * Returns the text selected in the editor.
+   *
+   * @return the selected text, or null if there is currently no selection.
+   */
+  @Nullable
+  String getSelectedText();
+
+  /**
+   * Returns the offset from which the user started to extend the selection (the selection start
+   * if the selection was extended in forward direction, or the selection end if it was
+   * extended backward).
+   *
+   * @return the offset from which the selection was started, or the caret offset if there is
+   *         currently no selection.
+   */
+  int getLeadSelectionOffset();
+
+  /**
+   * @return    object that encapsulates information about visual position from which the user started to extend the selection if any
+   */
+  @NotNull
+  VisualPosition getLeadSelectionPosition();
+
+  /**
+   * Checks if a range of text is currently selected.
+   *
+   * @return true if a range of text is selected, false otherwise.
+   */
+  boolean hasSelection();
+
+  /**
+   * Selects the specified range of text.
+   *
+   * @param startOffset the start offset of the text range to select.
+   * @param endOffset   the end offset of the text range to select.
+   */
+  void setSelection(int startOffset, int endOffset);
+
+  /**
+   * 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.
+   *
+   * @param startOffset     start selection offset
+   * @param endPosition     end visual position of the text range to select (<code>null</code> argument means that
+   *                        no specific visual position should be used)
+   * @param endOffset       end selection offset
+   */
+  void setSelection(int startOffset, @Nullable VisualPosition endPosition, int endOffset);
+
+  /**
+   * 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.
+   *
+   * @param startPosition   start visual position of the text range to select (<code>null</code> argument means that
+   *                        no specific visual position should be used)
+   * @param endPosition     end visual position of the text range to select (<code>null</code> argument means that
+   *                        no specific visual position should be used)
+   * @param startOffset     start selection offset
+   * @param endOffset       end selection offset
+   */
+  void setSelection(@Nullable VisualPosition startPosition, int startOffset, @Nullable VisualPosition endPosition, int endOffset);
+
+  /**
+   * Removes the selection in the editor.
+   */
+  void removeSelection();
+
+  /**
+   * Selects the entire line of text at the caret position.
+   */
+  void selectLineAtCaret();
+
+  /**
+   * Selects the entire word at the caret position, optionally using camel-case rules to
+   * determine word boundaries.
+   *
+   * @param honorCamelWordsSettings if true and "Use CamelHumps words" is enabled,
+   *                                upper-case letters within the word are considered as
+   *                                boundaries for the range of text to select.
+   */
+  void selectWordAtCaret(boolean honorCamelWordsSettings);
+
+  /**
+   * Clones the current caret and positions the new one right above or below the current one. If current caret has selection, corresponding
+   * 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
+   */
+  @Nullable
+  Caret clone(boolean above);
+}
index a83020bd6384600306b3e0109720bd0c0dd26587..2c9e3d6db26e1a7aa55963fea9b6baeab17fedf6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -17,11 +17,26 @@ 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;
+
+import java.util.Collection;
+import java.util.List;
 
 /**
  * Provides services for moving the caret and retrieving information about caret position.
  *
+ * May support several carets existing simultaneously in a document. {@link #supportsMultipleCarets()} method can be used to find out
+ * whether particular instance of CaretModel does it. If it does, query and update methods for caret position operate on a certain 'primary'
+ * caret. There exists a way to perform the same operation(s) on each caret - see {@link #runForEachCaret(Runnable)} method. Within its
+ * context, query and update methods operate on the current caret in that iteration.
+ * How 'primary' caret is determined by the model is not dictated.
+ * At all times at least one caret will exist in a document.
+ * <p>
+ * Update methods, and {@link #runForEachCaret(Runnable)} method should only be run from EDT. Query methods can be run from any thread, when
+ * called not from EDT, those methods are 'not aware' of 'runForEachCaret' scope - they will always return information about primary caret.
+ *
  * @see Editor#getCaretModel()
  */
 public interface CaretModel {
@@ -32,7 +47,7 @@ public interface CaretModel {
    * @param lineShift      the number of lines to move the caret by.
    * @param withSelection  if true, the caret move should extend the range or block selection in the document.
    * @param blockSelection if true and <code>withSelection</code> is true, the caret move should extend
-   *                       the block selection in the document.
+   *                       the block selection in the document. This parameter is ignored when multiple carets are supported by the model.
    * @param scrollToCaret  if true, the document should be scrolled so that the caret is visible after the move.
    */
   void moveCaretRelatively(int columnShift,
@@ -43,6 +58,7 @@ public interface CaretModel {
 
   /**
    * Moves the caret to the specified logical position.
+   * If corresponding position is in the folded region currently, the region will be expanded.
    *
    * @param pos the position to move to.
    */
@@ -64,6 +80,7 @@ public interface CaretModel {
 
   /**
    * Moves the caret to the specified offset in the document.
+   * If corresponding position is in the folded region currently, the region will be expanded.
    *
    * @param offset                  the offset to move to.
    * @param locateBeforeSoftWrap    there is a possible case that there is a soft wrap at the given offset, hence, the same offset
@@ -137,4 +154,104 @@ public interface CaretModel {
    * @return Caret attributes.
    */
   TextAttributes getTextAttributes();
+
+  /**
+   * Tells whether multiple coexisting carets are supported by this CaretModel instance.
+   */
+  boolean supportsMultipleCarets();
+
+  /**
+   * Returns current caret - the one, query and update methods in the model operate at the moment. This is either an iteration-current
+   * caret within the context of {@link #runForEachCaret(Runnable)} method, or the 'primary' caret without that context.
+   * <p>
+   * If multiple carets are not supported, the behaviour is unspecified.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  @NotNull
+  Caret getCurrentCaret();
+
+  /**
+   * Returns the 'primary' caret.
+   * <p>
+   * If multiple carets are not supported, the behaviour is unspecified.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  @NotNull
+  Caret getPrimaryCaret();
+
+  /**
+   * Returns all carets currently existing in the document, ordered by their position in the document.
+   * <p>
+   * If multiple carets are not supported, the behaviour is unspecified.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  @NotNull
+  Collection<Caret> getAllCarets();
+
+  /**
+   * Returns a caret at the given position in the document, or <code>null</code>, if there's no caret there.
+   * <p>
+   * If multiple carets are not supported, the behaviour is unspecified.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  @Nullable
+  Caret getCaretAt(@NotNull VisualPosition pos);
+
+  /**
+   * Adds a new caret at the given position, and returns corresponding Caret instance. Locations outside of possible values for the given
+   * document are trimmed automatically.
+   * Does nothing if a caret already exists at specified location or selection of existing caret includes the specified location,
+   * <code>null</code> is returned in this case.
+   * <p>
+   * If multiple carets are not supported, the behaviour is unspecified.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  @Nullable
+  Caret addCaret(@NotNull VisualPosition pos);
+
+  /**
+   * Removes a given caret if it's recognized by the model and is not the only existing caret in the document, returning <code>true</code>.
+   * <code>false</code> is returned if any of the above condition doesn't hold, and the removal cannot happen.
+   * <p>
+   * If multiple carets are not supported, the behaviour is unspecified.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  boolean removeCaret(@NotNull Caret caret);
+
+  /**
+   * Removes all carets except the 'primary' one from the document.
+   * <p>
+   * If multiple carets are not supported, does nothing.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  void removeSecondaryCarets();
+
+  /**
+   * Sets the number of carets, their positions and selection ranges according to the provided parameters. Null values in any of the lists
+   * 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 setCarets(@NotNull List<LogicalPosition> caretPositions, @NotNull List<? extends Segment> selections);
+
+  /**
+   * Executes the given task for each existing caret. Carets are iterated in their position order. Set of carets to iterate over is
+   * determined in the beginning and is not affected by the potential carets addition or removal by the task being executed.
+   * At the end, merging of carets and selections is performed, so that no two carets will occur at the same logical position and
+   * no two selection will overlap after this method is finished.
+   * <p>
+   * If multiple carets are not supported, the given task is just executed once.
+   *
+   * @see #supportsMultipleCarets()
+   */
+  void runForEachCaret(@NotNull Runnable runnable);
 }
index ad88d82a64c0663adf89362a99cc97770c503fc8..9d66612d16320501766a0dc778e82ca6a9496f9c 100644 (file)
@@ -28,7 +28,14 @@ import org.jetbrains.annotations.Nullable;
  * these two types of selection, but only one type of selection can exist in a document at
  * any given time.
  *
+ * When caret model supports multiple carets, block selection mode is not supported.
+ * Instead, each caret can have its own selected region. Most of the methods querying or
+ * updating selection operate on the current caret (see {@link com.intellij.openapi.editor.CaretModel#runForEachCaret(Runnable)},
+ * or 'primary' caret if current caret is not defined. The exception is {@link #copySelectionToClipboard()},
+ * which copies contents of all selected regions to clipboard as one piece of text.
+ *
  * @see Editor#getSelectionModel()
+ * @see com.intellij.openapi.editor.CaretModel
  */
 public interface SelectionModel {
   /**
@@ -69,6 +76,13 @@ public interface SelectionModel {
   String getSelectedText();
 
   /**
+   * If <code>allCarets</code> is <code>true</code>, returns the concatenation of selections for all carets, or <code>null</code> if there
+   * are no selections. If <code>allCarets</code> is <code>false</code>, works just like {@link #getSelectedText}.
+   */
+  @Nullable
+  String getSelectedText(boolean allCarets);
+
+  /**
    * Returns the offset from which the user started to extend the selection (the selection start
    * if the selection was extended in forward direction, or the selection end if it was
    * extended backward).
@@ -93,6 +107,15 @@ public interface SelectionModel {
   boolean hasSelection();
 
   /**
+   * Checks if a range of text is currently selected. If <code>anyCaret</code> is <code>true</code>, check all existing carets in
+   * the document, and returns <code>true</code> if any of them has selection, otherwise checks only the current caret.
+   *
+   * @return true if a range of text is selected, false otherwise.
+   * @see #hasBlockSelection()
+   */
+  boolean hasSelection(boolean anyCaret);
+
+  /**
    * Selects the specified range of text in regular (non-block) selection mode.
    *
    * @param startOffset the start offset of the text range to select.
@@ -133,6 +156,12 @@ public interface SelectionModel {
   void removeSelection();
 
   /**
+   * Removes the selection in the editor. If <code>allCarets</code> is <code>true</code>, removes selections from all carets in the
+   * document, otherwise, does this just for the current caret.
+   */
+  void removeSelection(boolean allCarets);
+
+  /**
    * Adds a listener for receiving information about selection changes.
    *
    * @param listener the listener instance.
@@ -163,11 +192,15 @@ public interface SelectionModel {
 
   /**
    * Copies the currently selected text to the clipboard.
+   *
+   * When multiple selections exist in the document, all of them are copied, as a single piece of text.
    */
   void copySelectionToClipboard();
 
   /**
-   * Selects the specified rectangle of text in block selection mode.
+   * Selects the specified rectangle of text in block selection mode. When multiple carets are supported by editor, this creates an
+   * equivalent multi-caret selection (note, that in this case {@link #hasBlockSelection()} will still return <code>false</code>
+   * afterwards!).
    *
    * @param blockStart the start of the rectangle to select.
    * @param blockEnd   the end of the rectangle to select.
@@ -176,14 +209,15 @@ public interface SelectionModel {
   void setBlockSelection(@NotNull LogicalPosition blockStart, @NotNull LogicalPosition blockEnd);
 
   /**
-   * Removes the block selection from the document.
+   * Removes the block selection from the document. Does nothing if multiple carets are supported by editor.
    *
    * @see #removeSelection()
+   * @see #removeSelection(boolean)
    */
   void removeBlockSelection();
 
   /**
-   * Checks if a rectangular block of text is currently selected in the document.
+   * Checks if a rectangular block of text is currently selected in the document. Does not apply to multiple-caret selections.
    *
    * @return true if a block selection currently exists, false otherwise.
    * @see #hasSelection()
@@ -191,9 +225,8 @@ public interface SelectionModel {
   boolean hasBlockSelection();
 
   /**
-   * Returns an array of start offsets in the document for line parts selected
-   * in block selection mode. The size of the returned array is equal to the height
-   * of the selected text block.
+   * Returns an array of start offsets in the document for line parts selected in the document currently. Works both in single-caret state,
+   * multiple-caret selection and block-mode selection (for carets not having a selection, caret position is returned).
    *
    * @return the array of start offsets, or an array of 1 element if a range selection
    * currently exists, or an empty array if no selection exists.
@@ -202,9 +235,8 @@ public interface SelectionModel {
   int[] getBlockSelectionStarts();
 
   /**
-   * Returns an array of end offsets in the document for line parts selected
-   * in block selection mode. The size of the returned array is equal to the height
-   * of the selected text block.
+   * Returns an array of end offsets in the document for line parts selected in the document currently. Works both in single-caret state,
+   * multiple-caret selection and block-mode selection (for carets not having a selection, caret position is returned).
    *
    * @return the array of end offsets, or an array of 1 element if a range selection
    * currently exists, or an empty array if no selection exists.
index 5d37f95faf1ca7d1b1267c00f33adc0dfde0d587..8fcc3e052cecd3ae7e31585a28b440498325402e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
  */
 package com.intellij.openapi.editor.event;
 
+import com.intellij.openapi.editor.Caret;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.LogicalPosition;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import java.util.EventObject;
 
 public class CaretEvent extends EventObject {
+  private final Caret myCaret;
   private final LogicalPosition myOldPosition;
   private final LogicalPosition myNewPosition;
 
   public CaretEvent(@NotNull Editor editor, @NotNull LogicalPosition oldPosition, @NotNull LogicalPosition newPosition) {
+    this(editor, null, oldPosition, newPosition);
+  }
+
+  public CaretEvent(@NotNull Editor editor, @Nullable Caret caret, @NotNull LogicalPosition oldPosition, @NotNull LogicalPosition newPosition) {
     super(editor);
+    myCaret = caret;
     myOldPosition = oldPosition;
     myNewPosition = newPosition;
   }
@@ -36,6 +44,11 @@ public class CaretEvent extends EventObject {
     return (Editor) getSource();
   }
 
+  @Nullable
+  public Caret getCaret() {
+    return myCaret;
+  }
+
   @NotNull
   public LogicalPosition getOldPosition() {
     return myOldPosition;
diff --git a/platform/editor-ui-api/src/com/intellij/openapi/editor/event/MultipleCaretListener.java b/platform/editor-ui-api/src/com/intellij/openapi/editor/event/MultipleCaretListener.java
new file mode 100644 (file)
index 0000000..0c225cf
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * 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.event;
+
+/**
+ * In addition to caret movement event received by CaretListener instances, these listeners will also get caret creation/destroying events.
+ *
+ * @see CaretListener
+ * @see com.intellij.openapi.editor.CaretModel#addCaretListener(CaretListener)
+ * @see EditorEventMulticaster#addCaretListener(CaretListener)
+ */
+public interface MultipleCaretListener extends CaretListener {
+  /**
+   * Called when a new caret was added to the document.
+   */
+  void caretAdded(CaretEvent e);
+
+  /**
+   * Called when a caret was removed from the document.
+   */
+  void caretRemoved(CaretEvent e);
+}
index c8d9e8f6090f29fcbfd3510cfeecd33b676ef4b1..8da21f68ccd3f6cf0690cd5f60f10fd46c31bcc4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -126,6 +126,7 @@ public class GotoNextErrorHandler implements CodeInsightActionHandler {
     if (offset != oldOffset) {
       ScrollType scrollType = offset > oldOffset ? ScrollType.CENTER_DOWN : ScrollType.CENTER_UP;
       editor.getSelectionModel().removeSelection();
+      editor.getCaretModel().removeSecondaryCarets();
       editor.getCaretModel().moveToOffset(offset);
       scrollingModel.scrollToCaret(scrollType);
     }
index 4a21b92f6fb7350ab384ce9fa5912b607bb5ed4c..c130b8a856bf5a0f20e12c526fbed5d125c62acd 100644 (file)
@@ -44,6 +44,7 @@ public class BackspaceHandler extends EditorWriteActionHandler {
   protected final EditorActionHandler myOriginalHandler;
 
   public BackspaceHandler(EditorActionHandler originalHandler) {
+    super(true);
     myOriginalHandler = originalHandler;
   }
 
index 91a5118fe9bdffbafbf28de162869c3cb7836a6f..2cd7c33dac97f2705c242f8b529d0d47e16f76e8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -23,6 +23,10 @@ import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
 public abstract class BaseEnterHandler extends EditorWriteActionHandler {
   private static final String GROUP_ID = "EnterHandler.GROUP_ID";
 
+  protected BaseEnterHandler() {
+    super(true);
+  }
+
   @Override
   public DocCommandGroupId getCommandGroupId(Editor editor) {
     return DocCommandGroupId.withGroupId(editor.getDocument(), GROUP_ID);
index 8ebd653bcb14fc65b062f6685f3b7e3b67e97d39..e7c723c17f8bf8839614dbdcc918031e4a8fa97e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -26,7 +26,6 @@ package com.intellij.codeInsight.editorActions;
 
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
@@ -39,6 +38,10 @@ public class CodeBlockEndAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       Project project = CommonDataKeys.PROJECT.getData(dataContext);
index 085e465588cc22f8aa6f0774fde111f8c9d0f95c..9431da1fc4d7cff7bffe8449a2ef1c943bc6ae06 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -38,6 +38,10 @@ public class CodeBlockEndWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       Project project = CommonDataKeys.PROJECT.getData(dataContext);
index 3ab23ae1b4c9352036000d52630c6609c0797b4f..690efff63d163e4e26f4b65b1d76143be93da67f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -26,7 +26,6 @@ package com.intellij.codeInsight.editorActions;
 
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
@@ -39,6 +38,10 @@ public class CodeBlockStartAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       Project project = CommonDataKeys.PROJECT.getData(dataContext);
index 352297b17dfa0b057fe2919a974af98c550fecc5..9819efcdc439db6639fbc7ef172ad3945e61a3eb 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -26,7 +26,6 @@ package com.intellij.codeInsight.editorActions;
 
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
@@ -39,6 +38,10 @@ public class CodeBlockStartWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       Project project = CommonDataKeys.PROJECT.getData(dataContext);
index a94cb6d1683aab00e28f6335c6e310009ab14930..5490ccc0899e6d61cccc934aa832c46c51fbadf9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -66,13 +66,23 @@ public class CopyHandler extends EditorActionHandler {
     }
 
     final SelectionModel selectionModel = editor.getSelectionModel();
-    if(!selectionModel.hasSelection() && !selectionModel.hasBlockSelection()) {
+    if(!selectionModel.hasSelection(true) && !selectionModel.hasBlockSelection()) {
       if (Registry.is(CopyAction.SKIP_COPY_AND_CUT_FOR_EMPTY_SELECTION_KEY)) {
         return;
       }
-      selectionModel.selectLineAtCaret();
-      if (!selectionModel.hasSelection()) return;
-      EditorActionUtil.moveCaretToLineStartIgnoringSoftWraps(editor);
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          selectionModel.selectLineAtCaret();
+        }
+      });
+      if (!selectionModel.hasSelection(true)) return;
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          EditorActionUtil.moveCaretToLineStartIgnoringSoftWraps(editor);
+        }
+      });
     }
 
     PsiDocumentManager.getInstance(project).commitAllDocuments();
@@ -88,7 +98,7 @@ public class CopyHandler extends EditorActionHandler {
       }
     }
 
-    String rawText = TextBlockTransferable.convertLineSeparators(selectionModel.getSelectedText(), "\n", transferableDatas);
+    String rawText = TextBlockTransferable.convertLineSeparators(selectionModel.getSelectedText(true), "\n", transferableDatas);
     String escapedText = null;
     for(CopyPastePreProcessor processor: Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
       escapedText = processor.preprocessOnCopy(file, startOffsets, endOffsets, rawText);
index 426d95d1fe3e42636eb2c5ae05e4653ea8a95254..05279e35ccfea54d317c27d1549f00af5168adcf 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -20,19 +20,22 @@ import com.intellij.ide.DataManager;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
 import com.intellij.openapi.actionSystem.IdeActions;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
-import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.EditorModificationUtil;
-import com.intellij.openapi.editor.SelectionModel;
+import com.intellij.openapi.editor.*;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
 import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
 import com.intellij.openapi.editor.actions.CopyAction;
 import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.TextRange;
 import com.intellij.openapi.util.registry.Registry;
 import com.intellij.psi.PsiDocumentManager;
 import com.intellij.psi.PsiFile;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
 public class CutHandler extends EditorWriteActionHandler {
   private final EditorActionHandler myOriginalHandler;
 
@@ -41,7 +44,7 @@ public class CutHandler extends EditorWriteActionHandler {
   }
 
   @Override
-  public void executeWriteAction(Editor editor, DataContext dataContext) {
+  public void executeWriteAction(final Editor editor, DataContext dataContext) {
     Project project = CommonDataKeys.PROJECT.getData(DataManager.getInstance().getDataContext(editor.getContentComponent()));
     if (project == null) {
       if (myOriginalHandler != null) {
@@ -59,27 +62,58 @@ public class CutHandler extends EditorWriteActionHandler {
       return;
     }
 
-    SelectionModel selectionModel = editor.getSelectionModel();
-    if (!selectionModel.hasSelection() && !selectionModel.hasBlockSelection()) {
+    final SelectionModel selectionModel = editor.getSelectionModel();
+    if (!selectionModel.hasSelection(true) && !selectionModel.hasBlockSelection()) {
       if (Registry.is(CopyAction.SKIP_COPY_AND_CUT_FOR_EMPTY_SELECTION_KEY)) {
         return;
       }
-      selectionModel.selectLineAtCaret();
-      if (!selectionModel.hasSelection()) return;
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          selectionModel.selectLineAtCaret();
+        }
+      });
+      if (!selectionModel.hasSelection(true)) return;
     }
 
     int start = selectionModel.getSelectionStart();
     int end = selectionModel.getSelectionEnd();
+    final List<TextRange> selections = new ArrayList<TextRange>();
+    if (editor.getCaretModel().supportsMultipleCarets()) {
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          selections.add(new TextRange(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()));
+        }
+      });
+    }
 
     EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_COPY).execute(editor, dataContext);
 
-    if (start != end) {
-      // There is a possible case that 'sticky selection' is active. It's automatically removed on copying then, so, we explicitly
-      // remove the text.
-      editor.getDocument().deleteString(start, end);
+    if (editor.getCaretModel().supportsMultipleCarets()) {
+
+      Collections.reverse(selections);
+      final Iterator<TextRange> it = selections.iterator();
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          TextRange range = it.next();
+          editor.getCaretModel().moveToOffset(range.getStartOffset());
+          selectionModel.removeSelection();
+          editor.getDocument().deleteString(range.getStartOffset(), range.getEndOffset());
+        }
+      });
+      editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
     }
     else {
-      EditorModificationUtil.deleteSelectedText(editor);
+      if (start != end) {
+        // There is a possible case that 'sticky selection' is active. It's automatically removed on copying then, so, we explicitly
+        // remove the text.
+        editor.getDocument().deleteString(start, end);
+      }
+      else {
+        EditorModificationUtil.deleteSelectedText(editor);
+      }
     }
   }
 }
index 7315311e653cebfbe747c97868f37f611770750c..dd00e2669fa0f1be0d8f7ff017f821a684da656f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -20,7 +20,6 @@ import com.intellij.codeInsight.CodeInsightSettings;
 import com.intellij.ide.DataManager;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.editor.*;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
@@ -37,6 +36,7 @@ public class EndHandler extends EditorActionHandler {
   private final EditorActionHandler myOriginalHandler;
 
   public EndHandler(EditorActionHandler originalHandler) {
+    super(true);
     myOriginalHandler = originalHandler;
   }
 
index 1d8330713483dfc5a3eac6650bfaab245b61e792..eebdb50ea2ee1a67ed9198af8ab7c605976e36f5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -28,7 +28,6 @@ import com.intellij.ide.DataManager;
 import com.intellij.lang.ASTNode;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.LogicalPosition;
@@ -58,6 +57,7 @@ public class JoinLinesHandler extends EditorWriteActionHandler {
   private final EditorActionHandler myOriginalHandler;
 
   public JoinLinesHandler(EditorActionHandler originalHandler) {
+    super(true);
     myOriginalHandler = originalHandler;
   }
 
index 11b70bb88790323f94dacf1b49848b16b635bb7a..0f19120e39f0c3e43e344072c5e8da3a86f22e3e 100644 (file)
@@ -1,8 +1,22 @@
+/*
+ * 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.codeInsight.editorActions;
 
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.editor.CaretModel;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
@@ -24,6 +38,10 @@ public class MatchBraceAction extends EditorAction {
   }
 
   private static class MyHandler extends EditorActionHandler {
+    public MyHandler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       Project project = CommonDataKeys.PROJECT.getData(dataContext);
index 93c53deac63fd4264c091fe7e90bf1110aa6c478..07360b07e5d32c24df8441d7ae9be4d14b5ef580 100644 (file)
@@ -44,6 +44,7 @@ import com.intellij.psi.SingleRootFileViewProvider;
 import com.intellij.psi.codeStyle.CodeStyleManager;
 import com.intellij.util.DocumentUtil;
 import com.intellij.util.IncorrectOperationException;
+import com.intellij.util.ParameterizedRunnable;
 import com.intellij.util.Producer;
 import com.intellij.util.containers.HashMap;
 import com.intellij.util.text.CharArrayUtil;
@@ -52,6 +53,7 @@ import org.jetbrains.annotations.Nullable;
 
 import java.awt.datatransfer.DataFlavor;
 import java.awt.datatransfer.Transferable;
+import java.util.Iterator;
 import java.util.Map;
 
 public class PasteHandler extends EditorActionHandler implements EditorTextInsertHandler {
@@ -89,7 +91,7 @@ public class PasteHandler extends EditorActionHandler implements EditorTextInser
     }
 
     final Project project = editor.getProject();
-    if (project == null || editor.isColumnMode() || editor.getSelectionModel().hasBlockSelection()) {
+    if (project == null || !editor.getCaretModel().supportsMultipleCarets() && (editor.isColumnMode() || editor.getSelectionModel().hasBlockSelection())) {
       if (myOriginalHandler != null) {
         myOriginalHandler.execute(editor, context);
       }
@@ -163,103 +165,116 @@ public class PasteHandler extends EditorActionHandler implements EditorTextInser
       }
 
       text = TextBlockTransferable.convertLineSeparators(text, "\n", extraData.values());
-
-      final CaretModel caretModel = editor.getCaretModel();
-      final SelectionModel selectionModel = editor.getSelectionModel();
-      final int col = caretModel.getLogicalPosition().column;
-
-      // There is a possible case that we want to perform paste while there is an active selection at the editor and caret is located
-      // inside it (e.g. Ctrl+A is pressed while caret is not at the zero column). We want to insert the text at selection start column
-      // then, hence, inserted block of text should be indented according to the selection start as well.
-      final int blockIndentAnchorColumn;
-      final int caretOffset = caretModel.getOffset();
-      if (selectionModel.hasSelection() && caretOffset >= selectionModel.getSelectionStart()) {
-        blockIndentAnchorColumn = editor.offsetToLogicalPosition(selectionModel.getSelectionStart()).column;
-      }
-      else {
-        blockIndentAnchorColumn = col;
-      }
-
-      // We assume that EditorModificationUtil.insertStringAtCaret() is smart enough to remove currently selected text (if any).
-
       RawText rawText = RawText.fromTransferable(content);
+
       String newText = text;
       for (CopyPastePreProcessor preProcessor : Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
         newText = preProcessor.preprocessOnPaste(project, file, editor, newText, rawText);
       }
-      int indentOptions = text.equals(newText) ? settings.REFORMAT_ON_PASTE : CodeInsightSettings.REFORMAT_BLOCK;
-      text = newText;
-
-      if (LanguageFormatting.INSTANCE.forContext(file) == null && indentOptions != CodeInsightSettings.NO_REFORMAT) {
-        indentOptions = CodeInsightSettings.INDENT_BLOCK;
-      }
-
-      final String _text = text;
-      ApplicationManager.getApplication().runWriteAction(
-        new Runnable() {
-          @Override
-          public void run() {
-            EditorModificationUtil.insertStringAtCaret(editor, _text, false, true);
+      final int indentOptions = LanguageFormatting.INSTANCE.forContext(file) == null ? CodeInsightSettings.INDENT_BLOCK
+                                                                                     : text.equals(newText) ? settings.REFORMAT_ON_PASTE
+                                                                                                            : CodeInsightSettings.REFORMAT_BLOCK;
+
+      final ParameterizedRunnable<String> runnable = new ParameterizedRunnable<String>() {
+        public void run(String text) {
+          final CaretModel caretModel = editor.getCaretModel();
+          final SelectionModel selectionModel = editor.getSelectionModel();
+          final int col = caretModel.getLogicalPosition().column;
+
+          // There is a possible case that we want to perform paste while there is an active selection at the editor and caret is located
+          // inside it (e.g. Ctrl+A is pressed while caret is not at the zero column). We want to insert the text at selection start column
+          // then, hence, inserted block of text should be indented according to the selection start as well.
+          final int blockIndentAnchorColumn;
+          final int caretOffset = caretModel.getOffset();
+          if (selectionModel.hasSelection() && caretOffset >= selectionModel.getSelectionStart()) {
+            blockIndentAnchorColumn = editor.offsetToLogicalPosition(selectionModel.getSelectionStart()).column;
+          }
+          else {
+            blockIndentAnchorColumn = col;
           }
-        }
-      );
 
-      int length = text.length();
-      int offset = caretModel.getOffset() - length;
-      if (offset < 0) {
-        length += offset;
-        offset = 0;
-      }
-      final RangeMarker bounds = document.createRangeMarker(offset, offset + length);
+          // We assume that EditorModificationUtil.insertStringAtCaret() is smart enough to remove currently selected text (if any).
 
-      caretModel.moveToOffset(bounds.getEndOffset());
-      editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
-      selectionModel.removeSelection();
+          final String _text = text;
+          ApplicationManager.getApplication().runWriteAction(
+            new Runnable() {
+              @Override
+              public void run() {
+                EditorModificationUtil.insertStringAtCaret(editor, _text, false, true);
+              }
+            }
+          );
 
-      final Ref<Boolean> indented = new Ref<Boolean>(Boolean.FALSE);
-      for (Map.Entry<CopyPastePostProcessor, TextBlockTransferableData> e : extraData.entrySet()) {
-        //noinspection unchecked
-        e.getKey().processTransferableData(project, editor, bounds, caretOffset, indented, e.getValue());
-      }
+          int length = text.length();
+          int offset = caretModel.getOffset() - length;
+          if (offset < 0) {
+            length += offset;
+            offset = 0;
+          }
+          final RangeMarker bounds = document.createRangeMarker(offset, offset + length);
 
-      boolean pastedTextContainsWhiteSpacesOnly =
-        CharArrayUtil.shiftForward(document.getCharsSequence(), bounds.getStartOffset(), " \n\t") >= bounds.getEndOffset();
-
-      VirtualFile virtualFile = file.getVirtualFile();
-      if (!pastedTextContainsWhiteSpacesOnly && (virtualFile == null || !SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile))) {
-        final int indentOptions1 = indentOptions;
-        ApplicationManager.getApplication().runWriteAction(
-          new Runnable() {
-            @Override
-            public void run() {
-              switch (indentOptions1) {
-                case CodeInsightSettings.INDENT_BLOCK:
-                  if (!indented.get()) {
-                    indentBlock(project, editor, bounds.getStartOffset(), bounds.getEndOffset(), blockIndentAnchorColumn);
-                  }
-                  break;
+          caretModel.moveToOffset(bounds.getEndOffset());
+          editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+          selectionModel.removeSelection();
 
-                case CodeInsightSettings.INDENT_EACH_LINE:
-                  if (!indented.get()) {
-                    indentEachLine(project, editor, bounds.getStartOffset(), bounds.getEndOffset());
-                  }
-                  break;
+          final Ref<Boolean> indented = new Ref<Boolean>(Boolean.FALSE);
+          for (Map.Entry<CopyPastePostProcessor, TextBlockTransferableData> e : extraData.entrySet()) {
+            //noinspection unchecked
+            e.getKey().processTransferableData(project, editor, bounds, caretOffset, indented, e.getValue());
+          }
 
-                case CodeInsightSettings.REFORMAT_BLOCK:
-                  indentEachLine(project, editor, bounds.getStartOffset(), bounds.getEndOffset()); // this is needed for example when inserting a comment before method
-                  reformatBlock(project, editor, bounds.getStartOffset(), bounds.getEndOffset());
-                  break;
+          boolean pastedTextContainsWhiteSpacesOnly =
+            CharArrayUtil.shiftForward(document.getCharsSequence(), bounds.getStartOffset(), " \n\t") >= bounds.getEndOffset();
+
+          VirtualFile virtualFile = file.getVirtualFile();
+          if (!pastedTextContainsWhiteSpacesOnly && (virtualFile == null || !SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile))) {
+            ApplicationManager.getApplication().runWriteAction(
+              new Runnable() {
+                @Override
+                public void run() {
+                  switch (indentOptions) {
+                    case CodeInsightSettings.INDENT_BLOCK:
+                      if (!indented.get()) {
+                        indentBlock(project, editor, bounds.getStartOffset(), bounds.getEndOffset(), blockIndentAnchorColumn);
+                      }
+                      break;
+
+                    case CodeInsightSettings.INDENT_EACH_LINE:
+                      if (!indented.get()) {
+                        indentEachLine(project, editor, bounds.getStartOffset(), bounds.getEndOffset());
+                      }
+                      break;
+
+                    case CodeInsightSettings.REFORMAT_BLOCK:
+                      indentEachLine(project, editor, bounds.getStartOffset(), bounds.getEndOffset()); // this is needed for example when inserting a comment before method
+                      reformatBlock(project, editor, bounds.getStartOffset(), bounds.getEndOffset());
+                      break;
+                  }
+                }
               }
-            }
+            );
           }
-        );
-      }
 
-      if (bounds.isValid()) {
-        caretModel.moveToOffset(bounds.getEndOffset());
-        editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
-        selectionModel.removeSelection();
-        editor.putUserData(EditorEx.LAST_PASTED_REGION, TextRange.create(bounds));
+          if (bounds.isValid()) {
+            caretModel.moveToOffset(bounds.getEndOffset());
+            editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+            selectionModel.removeSelection();
+            editor.putUserData(EditorEx.LAST_PASTED_REGION, TextRange.create(bounds));
+          }
+        }
+      };
+      if (editor.getCaretModel().supportsMultipleCarets()) {
+        int caretCount = editor.getCaretModel().getAllCarets().size();
+        final Iterator<String> segments = new ClipboardTextPerCaretSplitter().split(newText, caretCount).iterator();
+        editor.getCaretModel().runForEachCaret(new Runnable() {
+          @Override
+          public void run() {
+            runnable.run(segments.next());
+          }
+        });
+      }
+      else {
+        runnable.run(newText);
       }
     }
   }
index 34003d1ae6f66c88334c39cc6bad5d273ce28c65..7d0fa65e4ef4e7921ac2705df5014ed276f2abcd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -46,6 +46,7 @@ public class SelectWordHandler extends EditorActionHandler {
   private final EditorActionHandler myOriginalHandler;
 
   public SelectWordHandler(EditorActionHandler originalHandler) {
+    super(true);
     myOriginalHandler = originalHandler;
   }
 
index ab63582f883568be5428ad0e49a3110cf0917029..ca3f22fff9bb00a6ca237bb133e8ef92cfaa0253 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -19,7 +19,6 @@ package com.intellij.codeInsight.editorActions;
 import com.intellij.ide.DataManager;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
@@ -33,6 +32,7 @@ public class UnSelectWordHandler extends EditorActionHandler {
   private final EditorActionHandler myOriginalHandler;
 
   public UnSelectWordHandler(EditorActionHandler originalHandler) {
+    super(true);
     myOriginalHandler = originalHandler;
   }
 
index 29b49e5495a01819aeba16eeaba4885044ff3dc3..a5c2a59ee3c6724326bbfb98dd6f3e4d93b59f1c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -36,6 +36,7 @@ public abstract class BaseMoveHandler extends EditorWriteActionHandler {
   protected final boolean isDown;
 
   public BaseMoveHandler(boolean down) {
+    super(true);
     isDown = down;
   }
 
index dd974d735d8492ef9580f7283cd755a94792c0e4..bbb1e163729e3561bbb2c2225fe0979402c7c03a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -53,6 +53,10 @@ public class SmartEnterAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public boolean isEnabled(Editor editor, DataContext dataContext) {
       return getEnterHandler().isEnabled(editor, dataContext);
index 77ad6a2a1825e4a33225d74890dea48d919ab062..a89e068b67b0ef79343600a523bc06ec674fe2dc 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -34,7 +34,7 @@ public class BackspaceHandler extends EditorActionHandler {
   public void execute(final Editor editor, final DataContext dataContext){
     LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor);
     if (lookup == null){
-      myOriginalHandler.execute(editor, dataContext);
+      myOriginalHandler.executeForAllCarets(editor, dataContext);
       return;
     }
 
@@ -52,7 +52,7 @@ public class BackspaceHandler extends EditorActionHandler {
     if (!lookup.performGuardedChange(new Runnable() {
       @Override
       public void run() {
-        handler.execute(editor, dataContext);
+        handler.executeForAllCarets(editor, dataContext);
       }
     })) {
       return;
index 7c947eecde381f55370072d50366a3fbd76987f1..183ba343415ca9ff1f612d7462e1ed06f4cb4958 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -33,7 +33,7 @@ public class EndHandler extends EditorActionHandler {
   public void execute(Editor editor, DataContext dataContext){
     LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor);
     if (lookup == null || !lookup.isFocused()) {
-      myOriginalHandler.execute(editor, dataContext);
+      myOriginalHandler.executeForAllCarets(editor, dataContext);
       return;
     }
 
index 781d17d30a34ac9fdb0d41fba464e5cf289049df..d52f726f4e360408816e68c9295bdbb16b838953 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -33,7 +33,7 @@ public class HomeHandler extends EditorActionHandler {
   public void execute(Editor editor, DataContext dataContext){
     LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(editor);
     if (lookup == null || !lookup.isFocused()) {
-      myOriginalHandler.execute(editor, dataContext);
+      myOriginalHandler.executeForAllCarets(editor, dataContext);
       return;
     }
 
index 79838c5c2e898c27eef8a62a2d5a7e6307366ff0..29427e48f5335fd74d438beed1bcf531939c8c64 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -56,7 +56,7 @@ public abstract class LookupActionHandler extends EditorActionHandler {
       if (project != null) {
         LookupManager.getInstance(project).hideActiveLookup();
       }
-      myOriginalHandler.execute(editor, dataContext);
+      myOriginalHandler.executeForAllCarets(editor, dataContext);
       return;
     }
 
@@ -147,7 +147,7 @@ public abstract class LookupActionHandler extends EditorActionHandler {
     @Override
     protected void executeInLookup(final LookupImpl lookup, DataContext context) {
       if (!UISettings.getInstance().CYCLE_SCROLLING && !lookup.isFocused() && lookup.getList().getSelectedIndex() == 0) {
-        myOriginalHandler.execute(lookup.getEditor(), context);
+        myOriginalHandler.executeForAllCarets(lookup.getEditor(), context);
         return;
       }
       executeUpOrDown(lookup, true);
@@ -187,7 +187,7 @@ public abstract class LookupActionHandler extends EditorActionHandler {
     @Override
     protected void executeInLookup(final LookupImpl lookup, DataContext context) {
       if (!lookup.isCompletion()) {
-        myOriginalHandler.execute(lookup.getEditor(), context);
+        myOriginalHandler.executeForAllCarets(lookup.getEditor(), context);
         return;
       }
 
@@ -214,7 +214,7 @@ public abstract class LookupActionHandler extends EditorActionHandler {
       final int offset = editor.getCaretModel().getOffset();
       CharSequence seq = editor.getDocument().getCharsSequence();
       if (seq.length() <= offset || !lookup.isCompletion()) {
-        myOriginalHandler.execute(editor, context);
+        myOriginalHandler.executeForAllCarets(editor, context);
         return;
       }
 
@@ -222,7 +222,7 @@ public abstract class LookupActionHandler extends EditorActionHandler {
       CharFilter.Result lookupAction = LookupTypedHandler.getLookupAction(c, lookup);
 
       if (lookupAction != CharFilter.Result.ADD_TO_PREFIX || Character.isWhitespace(c)) {
-        myOriginalHandler.execute(editor, context);
+        myOriginalHandler.executeForAllCarets(editor, context);
         return;
       }
 
index ef185d826a88b7424b4b491e7f3651b87010e8ce..8a831d87911e0ec9f8841e2cd8468579eba92fd3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -390,7 +390,7 @@ public class IncrementalSearchHandler {
     public void execute(Editor editor, DataContext dataContext) {
       PerEditorSearchData data = editor.getUserData(SEARCH_DATA_IN_EDITOR_VIEW_KEY);
       if (data == null || data.hint == null){
-        myOriginalHandler.execute(editor, dataContext);
+        myOriginalHandler.executeForAllCarets(editor, dataContext);
       }
       else{
         LightweightHint hint = data.hint;
@@ -416,7 +416,7 @@ public class IncrementalSearchHandler {
     public void execute(Editor editor, DataContext dataContext) {
       PerEditorSearchData data = editor.getUserData(SEARCH_DATA_IN_EDITOR_VIEW_KEY);
       if (data == null || data.hint == null){
-        myOriginalHandler.execute(editor, dataContext);
+        myOriginalHandler.executeForAllCarets(editor, dataContext);
       }
       else{
         LightweightHint hint = data.hint;
@@ -449,7 +449,7 @@ public class IncrementalSearchHandler {
     public void execute(Editor editor, DataContext dataContext) {
       PerEditorSearchData data = editor.getUserData(SEARCH_DATA_IN_EDITOR_VIEW_KEY);
       if (data == null || data.hint == null){
-        myOriginalHandler.execute(editor, dataContext);
+        myOriginalHandler.executeForAllCarets(editor, dataContext);
       }
       else{
         LightweightHint hint = data.hint;
index 2083cdf93a43a120209358a026fb15e7cd0de1af..80d329f09e440dc34ce687a0925d1d34c8127465 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -40,6 +40,7 @@ public class MethodDownHandler implements CodeInsightActionHandler {
       if (offset > caretOffset) {
         int line = editor.offsetToLogicalPosition(offset).line;
         if (line > caretLine) {
+          editor.getCaretModel().removeSecondaryCarets();
           editor.getCaretModel().moveToOffset(offset);
           editor.getSelectionModel().removeSelection();
           editor.getScrollingModel().scrollToCaret(ScrollType.CENTER_DOWN);
index eb4d85b29a84365865d8cc49071339ee614ff502..cabc87c918989709246428eb90bc2c089074b6ef 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -41,6 +41,7 @@ public class MethodUpHandler implements CodeInsightActionHandler {
       if (offset < caretOffset){
         int line = editor.offsetToLogicalPosition(offset).line;
         if (line < caretLine){
+          editor.getCaretModel().removeSecondaryCarets();
           editor.getCaretModel().moveToOffset(offset);
           editor.getSelectionModel().removeSelection();
           editor.getScrollingModel().scrollToCaret(ScrollType.CENTER_UP);
index c5e19094ee2d572bf9a6fd20514e5b94520e99bc..c2003676fa18ff6dbdf3820df76a10957643da83 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -28,6 +28,7 @@ public abstract class HomeEndHandler extends EditorActionHandler {
   boolean myIsHomeHandler;
 
   public HomeEndHandler(final EditorActionHandler originalHandler, boolean isHomeHandler) {
+    super(true);
     myOriginalHandler = originalHandler;
     myIsHomeHandler = isHomeHandler;
   }
index 01a1d532c967f01bbddb21fa5c3d36b093f119f7..1e8dc1833b975c5118fd6b49c2ee70a08c0cac64 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -28,6 +28,7 @@ public abstract class LineStartEndWithSelectionHandler extends EditorActionHandl
   boolean myIsHomeHandler;
 
   public LineStartEndWithSelectionHandler(final EditorActionHandler originalHandler, boolean isHomeHandler) {
+    super(true);
     myOriginalHandler = originalHandler;
     myIsHomeHandler = isHomeHandler;
   }
index 5aeb35e84226400feeab60e497badb0670e59032..1f843acec171b8c61d8bc48b595ae74cae677dfd 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -53,6 +53,7 @@ abstract class GotoBookmarkActionBase extends EditorAction {
 
         LogicalPosition pos = new LogicalPosition(line, 0);
         editor.getSelectionModel().removeSelection();
+        editor.getCaretModel().removeSecondaryCarets();
         editor.getCaretModel().moveToLogicalPosition(pos);
         editor.getScrollingModel().scrollTo(new LogicalPosition(line, 0), ScrollType.CENTER);
       }
index 06188e4ba45bb39d4a8c4de2d59362b07f45259a..df41cc22f90afc475f259980515efbe2e1f5f64c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -16,6 +16,7 @@
 
 package com.intellij.injected.editor;
 
+import com.intellij.openapi.editor.Caret;
 import com.intellij.openapi.editor.CaretModel;
 import com.intellij.openapi.editor.LogicalPosition;
 import com.intellij.openapi.editor.VisualPosition;
@@ -23,7 +24,14 @@ 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.ArrayList;
+import java.util.Collection;
+import java.util.List;
 
 /**
  * @author Alexey
@@ -102,7 +110,8 @@ public class CaretModelWindow implements CaretModel {
       @Override
       public void caretPositionChanged(CaretEvent e) {
         if (!myEditorWindow.getDocument().isValid()) return; // injected document can be destroyed by now
-        CaretEvent event = new CaretEvent(myEditorWindow, myEditorWindow.hostToInjected(e.getOldPosition()),
+        CaretEvent event = new CaretEvent(myEditorWindow, createInjectedCaret(e.getCaret()),
+                                          myEditorWindow.hostToInjected(e.getOldPosition()),
                                           myEditorWindow.hostToInjected(e.getNewPosition()));
         listener.caretPositionChanged(event);
       }
@@ -140,4 +149,84 @@ public class CaretModelWindow implements CaretModel {
   public TextAttributes getTextAttributes() {
     return myDelegate.getTextAttributes();
   }
+
+  @Override
+  public boolean supportsMultipleCarets() {
+    return myDelegate.supportsMultipleCarets();
+  }
+
+  @NotNull
+  @Override
+  public Caret getCurrentCaret() {
+    return createInjectedCaret(myDelegate.getCurrentCaret());
+  }
+
+  @NotNull
+  @Override
+  public Caret getPrimaryCaret() {
+    return createInjectedCaret(myDelegate.getPrimaryCaret());
+  }
+
+  @NotNull
+  @Override
+  public Collection<Caret> getAllCarets() {
+    Collection<Caret> hostCarets = myDelegate.getAllCarets();
+    Collection<Caret> carets = new ArrayList<Caret>(hostCarets.size());
+    for (Caret hostCaret : hostCarets) {
+      carets.add(createInjectedCaret(hostCaret));
+    }
+    return carets;
+  }
+
+  @Nullable
+  @Override
+  public Caret getCaretAt(@NotNull VisualPosition pos) {
+    LogicalPosition hostPos = myEditorWindow.injectedToHost(myEditorWindow.visualToLogicalPosition(pos));
+    Caret caret = myDelegate.getCaretAt(myHostEditor.logicalToVisualPosition(hostPos));
+    return createInjectedCaret(caret);
+  }
+
+  @Nullable
+  @Override
+  public Caret addCaret(@NotNull VisualPosition pos) {
+    LogicalPosition hostPos = myEditorWindow.injectedToHost(myEditorWindow.visualToLogicalPosition(pos));
+    Caret caret = myDelegate.addCaret(myHostEditor.logicalToVisualPosition(hostPos));
+    return createInjectedCaret(caret);
+  }
+
+  @Override
+  public boolean removeCaret(@NotNull Caret caret) {
+    if (caret instanceof InjectedCaret) {
+      caret = ((InjectedCaret)caret).myDelegate;
+    }
+    return myDelegate.removeCaret(caret);
+  }
+
+  @Override
+  public void removeSecondaryCarets() {
+    myDelegate.removeSecondaryCarets();
+  }
+
+  @Override
+  public void setCarets(@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));
+    }
+    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.setCarets(convertedPositions, convertedSelections);
+  }
+
+  private InjectedCaret createInjectedCaret(Caret caret) {
+    return caret == null ? null : new InjectedCaret(myEditorWindow, caret);
+  }
+
+  @Override
+  public void runForEachCaret(@NotNull Runnable runnable) {
+    myDelegate.runForEachCaret(runnable);
+  }
 }
diff --git a/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java b/platform/lang-impl/src/com/intellij/injected/editor/InjectedCaret.java
new file mode 100644 (file)
index 0000000..19651a7
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+ * 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.injected.editor;
+
+import com.intellij.openapi.editor.Caret;
+import com.intellij.openapi.editor.CaretModel;
+import com.intellij.openapi.editor.LogicalPosition;
+import com.intellij.openapi.editor.VisualPosition;
+import com.intellij.openapi.util.Key;
+import com.intellij.openapi.util.ProperTextRange;
+import com.intellij.openapi.util.TextRange;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+class InjectedCaret implements Caret {
+  private final EditorWindow myEditorWindow;
+  final Caret myDelegate;
+
+  InjectedCaret(EditorWindow window, Caret delegate) {
+    myEditorWindow = window;
+    myDelegate = delegate;
+  }
+
+  @NotNull
+  @Override
+  public CaretModel getCaretModel() {
+    return myEditorWindow.getCaretModel();
+  }
+
+  @Override
+  public boolean isValid() {
+    return myDelegate.isValid();
+  }
+
+  @Override
+  public void moveCaretRelatively(int columnShift, int lineShift, boolean withSelection, boolean scrollToCaret) {
+    myDelegate.moveCaretRelatively(columnShift, lineShift, withSelection, scrollToCaret);
+  }
+
+  @Override
+  public void moveToLogicalPosition(@NotNull LogicalPosition pos) {
+    LogicalPosition hostPos = myEditorWindow.injectedToHost(pos);
+    myDelegate.moveToLogicalPosition(hostPos);
+  }
+
+  @Override
+  public void moveToVisualPosition(@NotNull VisualPosition pos) {
+    LogicalPosition hostPos = myEditorWindow.injectedToHost(myEditorWindow.visualToLogicalPosition(pos));
+    myDelegate.moveToLogicalPosition(hostPos);
+  }
+
+  @Override
+  public void moveToOffset(int offset) {
+    moveToOffset(offset, false);
+  }
+
+  @Override
+  public void moveToOffset(int offset, boolean locateBeforeSoftWrap) {
+    int hostOffset = myEditorWindow.getDocument().injectedToHost(offset);
+    myDelegate.moveToOffset(hostOffset, locateBeforeSoftWrap);
+  }
+
+  @Override
+  public boolean isUpToDate() {
+    return myDelegate.isUpToDate();
+  }
+
+  @NotNull
+  @Override
+  public LogicalPosition getLogicalPosition() {
+    LogicalPosition hostPos = myDelegate.getLogicalPosition();
+    return myEditorWindow.hostToInjected(hostPos);
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getVisualPosition() {
+    LogicalPosition logicalPosition = getLogicalPosition();
+    return myEditorWindow.logicalToVisualPosition(logicalPosition);
+  }
+
+  @Override
+  public int getOffset() {
+    return myEditorWindow.getDocument().hostToInjected(myDelegate.getOffset());
+  }
+
+  @Override
+  public int getVisualLineStart() {
+    return myEditorWindow.getDocument().hostToInjected(myDelegate.getVisualLineStart());
+  }
+
+  @Override
+  public int getVisualLineEnd() {
+    return myEditorWindow.getDocument().hostToInjected(myDelegate.getVisualLineEnd());
+  }
+
+  @Override
+  public int getSelectionStart() {
+    return myEditorWindow.getDocument().hostToInjected(myDelegate.getSelectionStart());
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getSelectionStartPosition() {
+    return myDelegate.getSelectionStartPosition();
+  }
+
+  @Override
+  public int getSelectionEnd() {
+    return myEditorWindow.getDocument().hostToInjected(myDelegate.getSelectionEnd());
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getSelectionEndPosition() {
+    return myDelegate.getSelectionEndPosition();
+  }
+
+  @Nullable
+  @Override
+  public String getSelectedText() {
+    return myDelegate.getSelectedText();
+  }
+
+  @Override
+  public int getLeadSelectionOffset() {
+    return myEditorWindow.getDocument().hostToInjected(myDelegate.getLeadSelectionOffset());
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getLeadSelectionPosition() {
+    return myDelegate.getLeadSelectionPosition();
+  }
+
+  @Override
+  public boolean hasSelection() {
+    return myDelegate.hasSelection();
+  }
+
+  @Override
+  public void setSelection(int startOffset, int endOffset) {
+    TextRange hostRange = myEditorWindow.getDocument().injectedToHost(new ProperTextRange(startOffset, endOffset));
+    myDelegate.setSelection(hostRange.getStartOffset(), hostRange.getEndOffset());
+  }
+
+  @Override
+  public void setSelection(int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
+    TextRange hostRange = myEditorWindow.getDocument().injectedToHost(new ProperTextRange(startOffset, endOffset));
+    myDelegate.setSelection(hostRange.getStartOffset(), endPosition, hostRange.getEndOffset());
+  }
+
+  @Override
+  public void setSelection(@Nullable VisualPosition startPosition, int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
+    TextRange hostRange = myEditorWindow.getDocument().injectedToHost(new ProperTextRange(startOffset, endOffset));
+    myDelegate.setSelection(startPosition, hostRange.getStartOffset(), endPosition, hostRange.getEndOffset());
+  }
+
+  @Override
+  public void removeSelection() {
+    myDelegate.removeSelection();
+  }
+
+  @Override
+  public void selectLineAtCaret() {
+    myDelegate.selectLineAtCaret();
+  }
+
+  @Override
+  public void selectWordAtCaret(boolean honorCamelWordsSettings) {
+    myDelegate.selectWordAtCaret(honorCamelWordsSettings);
+  }
+
+  @Nullable
+  @Override
+  public Caret clone(boolean above) {
+    Caret clone = myDelegate.clone(above);
+    return clone == null ? null : new InjectedCaret(myEditorWindow, clone);
+  }
+
+  @Override
+  public void dispose() {
+    //noinspection SSBasedInspection
+    myDelegate.dispose();
+  }
+
+  @NotNull
+  @Override
+  public <T> T putUserDataIfAbsent(@NotNull Key<T> key, @NotNull T value) {
+    return myDelegate.putUserDataIfAbsent(key, value);
+  }
+
+  @Override
+  public <T> boolean replace(@NotNull Key<T> key, @Nullable T oldValue, @Nullable T newValue) {
+    return myDelegate.replace(key, oldValue, newValue);
+  }
+
+  @Nullable
+  @Override
+  public <T> T getUserData(@NotNull Key<T> key) {
+    return myDelegate.getUserData(key);
+  }
+
+  @Override
+  public <T> void putUserData(@NotNull Key<T> key, @Nullable T value) {
+    myDelegate.putUserData(key, value);
+  }
+}
index d21afd816db953c8cd09468c6be610df425385b7..37d9dd7268b1a26a907930a0fa5013d1c1e39eb8 100644 (file)
@@ -69,6 +69,12 @@ public class SelectionModelWindow implements SelectionModel {
     return myHostModel.getSelectedText();
   }
 
+  @Nullable
+  @Override
+  public String getSelectedText(boolean allCarets) {
+    return myHostModel.getSelectedText(allCarets);
+  }
+
   @Override
   public int getLeadSelectionOffset() {
     return myDocument.hostToInjected(myHostModel.getLeadSelectionOffset());
@@ -86,6 +92,11 @@ public class SelectionModelWindow implements SelectionModel {
   }
 
   @Override
+  public boolean hasSelection(boolean anyCaret) {
+    return myHostModel.hasSelection(anyCaret);
+  }
+
+  @Override
   public void setSelection(final int startOffset, final int endOffset) {
     TextRange hostRange = myDocument.injectedToHost(new ProperTextRange(startOffset, endOffset));
     myHostModel.setSelection(hostRange.getStartOffset(), hostRange.getEndOffset());
@@ -109,6 +120,11 @@ public class SelectionModelWindow implements SelectionModel {
   }
 
   @Override
+  public void removeSelection(boolean allCarets) {
+    myHostModel.removeSelection(allCarets);
+  }
+
+  @Override
   public void addSelectionListener(final SelectionListener listener) {
     myHostModel.addSelectionListener(listener);
   }
index 3aae0f3b273df09b2b8a110b9e4ea226357fd971..016f7f14cc4daa8867a3063dcc04f506f4e76883 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -82,6 +82,7 @@ public class GotoCustomRegionAction extends AnAction implements DumbAware {
   private static void navigateTo(Editor editor, PsiElement element) {
     int offset = element.getTextRange().getStartOffset();
     if (offset >= 0 && offset < editor.getDocument().getTextLength()) {
+      editor.getCaretModel().removeSecondaryCarets();
       editor.getCaretModel().moveToOffset(offset);
       editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
       editor.getSelectionModel().removeSelection();
index faa46736977f748abd1ad0391baaffdc912318ea..ae12790519dcd91601115785cb60ccfd81907089 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -36,6 +36,7 @@ public class NamedElementDuplicateHandler extends EditorWriteActionHandler {
   private final EditorActionHandler myOriginal;
 
   public NamedElementDuplicateHandler(EditorActionHandler original) {
+    super(true);
     myOriginal = original;
   }
 
index cebf9978553f75171c9cac8006f89fa1aab2f350..b70189f5140a667d8fc9a18fa85aedd267cb9a1c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -51,6 +51,10 @@ public class SelectWordAtCaretAction extends TextComponentEditorAction implement
   }
 
   private static class DefaultHandler extends EditorActionHandler {
+    private DefaultHandler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       int lineNumber = editor.getCaretModel().getLogicalPosition().line;
@@ -94,6 +98,7 @@ public class SelectWordAtCaretAction extends TextComponentEditorAction implement
     private final EditorActionHandler myDefaultHandler;
 
     private Handler(EditorActionHandler defaultHandler) {
+      super(true);
       myDefaultHandler = defaultHandler;
     }
 
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/ClipboardTextPerCaretSplitter.java b/platform/platform-api/src/com/intellij/openapi/editor/ClipboardTextPerCaretSplitter.java
new file mode 100644 (file)
index 0000000..afc58b8
--- /dev/null
@@ -0,0 +1,56 @@
+/*
+ * 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 java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class ClipboardTextPerCaretSplitter {
+  public List<String> split(String input, int caretCount) {
+    if (caretCount <= 0) {
+      throw new IllegalArgumentException("Caret count must be positive");
+    }
+    if (caretCount == 1) {
+      return Collections.singletonList(input);
+    }
+    List<String> result = new ArrayList<String>(caretCount);
+    String[] lines = input.split("\n", -1);
+    for (int i = 0; i < caretCount; i++) {
+      if (lines.length == 0) {
+        result.add("");
+      }
+      else if (lines.length == 1) {
+        result.add(lines[0]);
+      }
+      else if (lines.length % caretCount == 0) {
+        StringBuilder b = new StringBuilder();
+        int linesPerSegment = lines.length / caretCount;
+        for (int j = 0; j < linesPerSegment; j++) {
+          if (j > 0) {
+            b.append('\n');
+          }
+          b.append(lines[i * linesPerSegment + j]);
+        }
+        result.add(b.toString());
+      }
+      else {
+        result.add(i < lines.length ? lines[i] : "");
+      }
+    }
+    return result;
+  }
+}
index 7507cf525ef6cc97c42db1e569b11667493c9d95..2535bf520a31c088919eb4421d4b772e9b0270fa 100644 (file)
@@ -31,6 +31,7 @@ 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.List;
 
 public class EditorModificationUtil {
@@ -133,13 +134,26 @@ public class EditorModificationUtil {
   }
 
   @Nullable
-  public static TextRange pasteTransferable(Editor editor, @Nullable Producer<Transferable> producer) {
+  public static TextRange pasteTransferable(final Editor editor, @Nullable Producer<Transferable> producer) {
     String text = getStringContent(producer);
     if (text == null) return null;
 
-    int caretOffset = editor.getCaretModel().getOffset();
-    insertStringAtCaret(editor, text, false, true);
-    return new TextRange(caretOffset, caretOffset + text.length());
+    if (editor.getCaretModel().supportsMultipleCarets()) {
+      int caretCount = editor.getCaretModel().getAllCarets().size();
+      final Iterator<String> segments = new ClipboardTextPerCaretSplitter().split(text, caretCount).iterator();
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          insertStringAtCaret(editor, segments.next(), false, true);
+        }
+      });
+      return null;
+    }
+    else {
+      int caretOffset = editor.getCaretModel().getOffset();
+      insertStringAtCaret(editor, text, false, true);
+      return new TextRange(caretOffset, caretOffset + text.length());
+    }
   }
 
   public static void pasteTransferableAsBlock(Editor editor, @Nullable Producer<Transferable> producer) {
index a1dddffbea32f7c7ab96abced9dd5236e1e2faac..edcf499d7e14fa1f803f9d011e4cbe21a79dd9fc 100644 (file)
@@ -80,7 +80,7 @@ public abstract class EditorAction extends AnAction implements DumbAware {
     Runnable command = new Runnable() {
       @Override
       public void run() {
-        handler.execute(editor, getProjectAwareDataContext(editor, dataContext));
+        handler.executeForAllCarets(editor, getProjectAwareDataContext(editor, dataContext));
       }
     };
 
index 8b61ce838cbcadfa8dde2128ba8dd7bb8cfc19bb..2ae76ae2b135da90e2bfcba6fb452d8a8be714e3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -24,6 +24,16 @@ import com.intellij.openapi.editor.Editor;
  * @see EditorActionManager#setActionHandler(String, EditorActionHandler)
  */
 public abstract class EditorActionHandler {
+  private final boolean myRunForEachCaret;
+
+  protected EditorActionHandler() {
+    this(false);
+  }
+
+  protected EditorActionHandler(boolean runForEachCaret) {
+    myRunForEachCaret = runForEachCaret;
+  }
+
   /**
    * Checks if the action handler is currently enabled.
    *
@@ -47,6 +57,24 @@ public abstract class EditorActionHandler {
     return true;
   }
 
+  public boolean runForAllCarets() {
+    return myRunForEachCaret;
+  }
+
+  public void executeForAllCarets(final Editor editor, final DataContext dataContext) {
+    if (editor.getCaretModel().supportsMultipleCarets() && runForAllCarets()) {
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          execute(editor, dataContext);
+        }
+      });
+    }
+    else {
+      execute(editor, dataContext);
+    }
+  }
+
   public DocCommandGroupId getCommandGroupId(Editor editor) {
     // by default avoid merging two consequential commands, and, in the same time, pass along the Document
     return DocCommandGroupId.noneGroupId(editor.getDocument());
index e66ce8248ff8caf30311bf1b655217c2e9308a62..082f1a68bb2630b1b49e8e72442ea751a7cc818f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -25,6 +25,13 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.project.Project;
 
 public abstract class EditorWriteActionHandler extends EditorActionHandler {
+  protected EditorWriteActionHandler() {
+  }
+
+  protected EditorWriteActionHandler(boolean runForEachCaret) {
+    super(runForEachCaret);
+  }
+
   @Override
   public final void execute(final Editor editor, final DataContext dataContext) {
     if (editor.isViewer()) return;
index 48ecbb10327c0cbbee920bc1f7d7249c971503a1..0ceeabb21b95d5cd1c7de653d6d426546808adfd 100644 (file)
@@ -127,17 +127,22 @@ public class TypedAction {
       ApplicationManager.getApplication().runWriteAction(new DocumentRunnable(myEditor.getDocument(), myEditor.getProject()) {
         @Override
         public void run() {
-          Document doc = myEditor.getDocument();
-          doc.startGuardedBlockChecking();
-          try {
-            getHandler().execute(myEditor, myCharTyped, myDataContext);
-          }
-          catch (ReadOnlyFragmentModificationException e) {
-            EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
-          }
-          finally {
-            doc.stopGuardedBlockChecking();
-          }
+          myEditor.getCaretModel().runForEachCaret(new Runnable() {
+            @Override
+            public void run() {
+              Document doc = myEditor.getDocument();
+              doc.startGuardedBlockChecking();
+              try {
+                getHandler().execute(myEditor, myCharTyped, myDataContext);
+              }
+              catch (ReadOnlyFragmentModificationException e) {
+                EditorActionManager.getInstance().getReadonlyFragmentModificationHandler(doc).handle(e);
+              }
+              finally {
+                doc.stopGuardedBlockChecking();
+              }
+            }
+          });
         }
       });
     }
index d8f6572042e916c9c88ff9eb2d99ae8c9abaee8d..9561581bc38b3d6ab41bacc0c40b7f9822e9e01e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -204,11 +204,13 @@ public class OpenFileDescriptor implements Navigatable {
     if (myLogicalLine >= 0) {
       LogicalPosition pos = new LogicalPosition(myLogicalLine, Math.max(myLogicalColumn, 0));
       if (offset < 0 || offset == e.logicalPositionToOffset(pos)) {
+        caretModel.removeSecondaryCarets();
         caretModel.moveToLogicalPosition(pos);
         caretMoved = true;
       }
     }
     if (!caretMoved && offset >= 0) {
+      caretModel.removeSecondaryCarets();
       caretModel.moveToOffset(Math.min(offset, e.getDocument().getTextLength()));
       caretMoved = true;
     }
index 8d0387af33cb209974a12e00eed13b30e771803d..10d360b2e36137207eb862744188977d1aaea285 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2011 JetBrains s.r.o.
+ * 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.
@@ -45,6 +45,7 @@ public class GotoLineNumberDialog extends DialogWrapper {
       try {
         final int offset = Integer.parseInt(myOffsetField.getText());
         if (offset < myEditor.getDocument().getTextLength()) {
+          myEditor.getCaretModel().removeSecondaryCarets();
           myEditor.getCaretModel().moveToOffset(offset);
           myEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
           myEditor.getSelectionModel().removeSelection();
@@ -60,6 +61,7 @@ public class GotoLineNumberDialog extends DialogWrapper {
     if (lineNumber < 0) return;
 
     int columnNumber = getColumnNumber(currentPosition.column);
+    myEditor.getCaretModel().removeSecondaryCarets();
     myEditor.getCaretModel().moveToLogicalPosition(new LogicalPosition(Math.max(0, lineNumber - 1), Math.max(0, columnNumber - 1)));
     myEditor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
     myEditor.getSelectionModel().removeSelection();
index bb02c941736b46335cb60eb614cb4a1b3a0bb14a..99766df8e456c6dc57f8c8354505278cecf7870e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -39,6 +39,7 @@ abstract class DiffWalkerAction extends AnAction implements DumbAware {
     Editor editor = side.getEditor();
     if (line >= 0 && editor != null) {
       LogicalPosition pos = new LogicalPosition(line, 0);
+      editor.getCaretModel().removeSecondaryCarets();
       editor.getCaretModel().moveToLogicalPosition(pos);
       editor.getScrollingModel().scrollToCaret(ScrollType.CENTER);
     }
index 3d10dcef61aeba507393dd0330259800eb1bd2f7..ad5d0c2fb9b284a9a6566457ab5833d7698b9fb9 100644 (file)
@@ -39,6 +39,10 @@ public class BackspaceAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    private Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       MacUIUtil.hideCursor();
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/CloneCaretAbove.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/CloneCaretAbove.java
new file mode 100644 (file)
index 0000000..4b27db7
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.DataContext;
+import com.intellij.openapi.editor.CaretModel;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+
+public class CloneCaretAbove extends EditorAction {
+  public CloneCaretAbove() {
+    super(new Handler());
+  }
+
+  private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
+    @Override
+    public void execute(Editor editor, DataContext dataContext) {
+      CaretModel caretModel = editor.getCaretModel();
+      if (caretModel.supportsMultipleCarets()) {
+        caretModel.getCurrentCaret().clone(true);
+      }
+    }
+
+    @Override
+    public boolean isEnabled(Editor editor, DataContext dataContext) {
+      return editor.getCaretModel().supportsMultipleCarets();
+    }
+  }
+}
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/actions/CloneCaretBelow.java b/platform/platform-impl/src/com/intellij/openapi/editor/actions/CloneCaretBelow.java
new file mode 100644 (file)
index 0000000..48f46bb
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * 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.DataContext;
+import com.intellij.openapi.editor.CaretModel;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.actionSystem.EditorAction;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+
+public class CloneCaretBelow extends EditorAction {
+  public CloneCaretBelow() {
+    super(new Handler());
+  }
+
+  private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
+    @Override
+    public void execute(Editor editor, DataContext dataContext) {
+      CaretModel caretModel = editor.getCaretModel();
+      if (caretModel.supportsMultipleCarets()) {
+        caretModel.getCurrentCaret().clone(false);
+      }
+    }
+
+    @Override
+    public boolean isEnabled(Editor editor, DataContext dataContext) {
+      return editor.getCaretModel().supportsMultipleCarets();
+    }
+  }
+}
index ff0833d35ed26d9df93fcc6a2d58913f04f6db21..302948b7468aaa9d72f4fc1b9fd0112ad0281731 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -40,13 +40,18 @@ public class CopyAction extends EditorAction {
 
   private static class Handler extends EditorActionHandler {
     @Override
-    public void execute(Editor editor, DataContext dataContext) {
-      if (!editor.getSelectionModel().hasSelection() && !editor.getSelectionModel().hasBlockSelection()) {
+    public void execute(final Editor editor, DataContext dataContext) {
+      if (!editor.getSelectionModel().hasSelection(true) && !editor.getSelectionModel().hasBlockSelection()) {
         if (Registry.is(SKIP_COPY_AND_CUT_FOR_EMPTY_SELECTION_KEY)) {
           return;
         }
-        editor.getSelectionModel().selectLineAtCaret();
-        EditorActionUtil.moveCaretToLineStartIgnoringSoftWraps(editor);
+        editor.getCaretModel().runForEachCaret(new Runnable() {
+          @Override
+          public void run() {
+            editor.getSelectionModel().selectLineAtCaret();
+            EditorActionUtil.moveCaretToLineStartIgnoringSoftWraps(editor);
+          }
+        });
       }
       editor.getSelectionModel().copySelectionToClipboard();
     }
index 0f6cec38ed43a6ffd73d372b52f45c477913c4a4..92e6b5b2f645d7a84a165ba55b7453c8789429d9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -38,15 +38,25 @@ public class CutAction extends EditorAction {
 
   public static class Handler extends EditorWriteActionHandler {
     @Override
-    public void executeWriteAction(Editor editor, DataContext dataContext) {
-      if(!editor.getSelectionModel().hasSelection()) {
+    public void executeWriteAction(final Editor editor, DataContext dataContext) {
+      if(!editor.getSelectionModel().hasSelection(true)) {
         if (Registry.is(CopyAction.SKIP_COPY_AND_CUT_FOR_EMPTY_SELECTION_KEY)) {
           return;
         }
-        editor.getSelectionModel().selectLineAtCaret();
+        editor.getCaretModel().runForEachCaret(new Runnable() {
+          @Override
+          public void run() {
+            editor.getSelectionModel().selectLineAtCaret();
+          }
+        });
       }
       editor.getSelectionModel().copySelectionToClipboard();
-      EditorModificationUtil.deleteSelectedText(editor);
+      editor.getCaretModel().runForEachCaret(new Runnable() {
+        @Override
+        public void run() {
+          EditorModificationUtil.deleteSelectedText(editor);
+        }
+      });
     }
   }
 }
\ No newline at end of file
index fda908f7dd166daed78847034f772c061b1d6c1b..cdd239abacfb5b59293a81615d8220738bf0a11b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -40,6 +40,10 @@ public class DeleteAction extends EditorAction {
   }
 
   public static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       MacUIUtil.hideCursor();
index 406025a46cc37878f2384febdcc190cb441b99ec..4b5201e2c9eacdf35f083aca3db182cfcb374d0c 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -36,6 +36,10 @@ public class DeleteLineAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       CommandProcessor.getInstance().setCurrentCommandGroupId(EditorActionUtil.DELETE_COMMAND_GROUP);
index 42e739f9cc4308e7e4859a8c1a909b1e98215d63..40ab1853a892720b33f2155c6dcb805e46fb122e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -41,6 +41,7 @@ public class DeleteToWordEndAction extends TextComponentEditorAction {
     private final boolean myNegateCamelMode;
 
     Handler(boolean negateCamelMode) {
+      super(true);
       myNegateCamelMode = negateCamelMode;
     }
 
index cc8399ac2e294aa90ed65047663c806f50da36eb..76e251631864f548134ca14c33cafd016e3cee82 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -74,6 +74,7 @@ public class DeleteToWordStartAction extends TextComponentEditorAction {
     private final boolean myNegateCamelMode;
 
     Handler(boolean negateCamelMode) {
+      super(true);
       myNegateCamelMode = negateCamelMode;
     }
 
index 5e73400b79acfb54caa1b6360c449b0adbef3526..0e92fcf0d7e061172a6ce9c43de43280962eaaf2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -39,6 +39,10 @@ public class DuplicateAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       duplicateLineOrSelectedBlockAtCaret(editor);
index 2560098f9902f7f501173abf59e8eaaf5c637e60..0a2db774cd80835c6b67422c2b7056f25ea99efa 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -31,6 +31,10 @@ public class DuplicateLinesAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       if (editor.getSelectionModel().hasSelection()) {
index 10f3b2e705b7fece7f0ba902c2593dc4cbaeed10..cc7b2177cd9103c67f16a987a32d5d8212a783f9 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -38,6 +38,10 @@ public class EnterAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       CommandProcessor.getInstance().setCurrentCommandName(EditorBundle.message("typing.command.name"));
index 5280eb30b43bb6561c2b32207f8807fb369d13f8..8a19c8c19c27d50bda27f165f2b3558ccf609f27 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -17,6 +17,7 @@
 package com.intellij.openapi.editor.actions;
 
 import com.intellij.openapi.actionSystem.DataContext;
+import com.intellij.openapi.editor.CaretModel;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.SelectionModel;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
@@ -41,15 +42,16 @@ public class EscapeAction extends EditorAction {
           editorEx.setStickySelection(false);
         }
       }
-      
+      editor.getCaretModel().removeSecondaryCarets();
       editor.getSelectionModel().removeSelection();
     }
 
     @Override
     public boolean isEnabled(Editor editor, DataContext dataContext) {
       SelectionModel selectionModel = editor.getSelectionModel();
-      return //PlatformDataKeys.IS_MODAL_CONTEXT.getData(dataContext) != Boolean.TRUE &&
-             (selectionModel.hasSelection() || selectionModel.hasBlockSelection());
+      CaretModel caretModel = editor.getCaretModel();
+      return selectionModel.hasSelection() || selectionModel.hasBlockSelection()
+             || caretModel.supportsMultipleCarets() && caretModel.getAllCarets().size() > 1;
     }
   }
 }
index 548b4ff95107464c23a31beaf12a5316333a2075..094c9523d8090d9c4dcaabebc184a317b5917ae5 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -41,6 +41,10 @@ public class HungryBackspaceAction extends TextComponentEditorAction {
   }
   
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(@NotNull Editor editor, DataContext dataContext) {
       final Document document = editor.getDocument();
index d031ef21e64652fa5dc88566781f23757ca57a54..214004dee38c8ae50817339b1d1f79b5df74889d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -52,6 +52,10 @@ public class IndentSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       Project project = CommonDataKeys.PROJECT.getData(dataContext);
index 626714c544f4acf0fdbf7d6f91d603362edeb393..306cdfa958d1fc72b02e0c6003957117bbeaa72a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -37,6 +37,10 @@ public class JoinLinesAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       final Document doc = editor.getDocument();
index f8ce304a4f9b06006fa641cd2edc75dd4358d323..9fcbbe8e4cf44db0fc998e93b2050dfca5daccab 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -34,6 +34,10 @@ public class LineEndAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToLineEnd(editor, false);
index 7db08952c7a99011f45ffb72348d4b0e69b83851..3b7ce3241102d52e77dacc4d0ebeb9a6781cacb8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -34,6 +34,10 @@ public class LineEndWithSelectionAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToLineEnd(editor, true);
index 6c41cdb691afa684b4074c668645efea1bca48cb..cff12d2da7f1f56964d34bd88399a6e7c6928246 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -34,6 +34,10 @@ public class LineStartAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToLineStart(editor, false);
index cb62d8ed702f17c51e112b7bddc9867e66df87f6..f39026f4428e65253b99fdf1bf4d9f41f2d10dc1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -34,6 +34,10 @@ public class LineStartWithSelectionAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToLineStart(editor, true);
index 0b35906a7de97947024d0c4078d48a9233e7c966..80f3a3480925cb215d2df79fbdd9c00c73714a80 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class MoveCaretDownAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       editor.getCaretModel().moveCaretRelatively(0, 1, false, false, true);
index 8499ced4b3599f6235c2c7e1e84c12d0804f968d..1e893e6d9ee6f1a93ce3ec660d61db8f82cabf0d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,9 +35,18 @@ public class MoveCaretDownWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
-      editor.getCaretModel().moveCaretRelatively(0, 1, true, editor.isColumnMode(), true);
+      if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets()) {
+        editor.getCaretModel().getCurrentCaret().clone(false);
+      }
+      else {
+        editor.getCaretModel().moveCaretRelatively(0, 1, true, editor.isColumnMode(), true);
+      }
     }
   }
 }
index 8ad43290894731dd0d908dfbfabaf71d9fe086a8..1883cb55387c882e21f4d255b470f20d1dd5446d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -31,6 +31,7 @@ class MoveCaretLeftOrRightHandler extends EditorActionHandler {
   private final Direction myDirection;
 
   MoveCaretLeftOrRightHandler(Direction direction) {
+    super(true);
     myDirection = direction;
   }
 
index a8f22a229c41fae29b74a077709447d71d649858..1bdeaf548d6d5e8719eab044d2c23e77014019d7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class MoveCaretLeftWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       int columnShift = -1;
index b84ce856d2c05df21b17f5a68739116ce58c93cf..19d729fa7fe51bf7df31b2d6f4429f205854aea2 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class MoveCaretRightWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       editor.getCaretModel().moveCaretRelatively(1, 0, true, editor.isColumnMode(), true);
index 4a31803013b85b98024a53faa86d798087bd76ad..45992ddb5f9e108adcf98bea6c9826230624dcba 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class MoveCaretUpAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       int lineShift = -1;
index d137d4bf2deb0ae7b347a294e9c57f74d240bea6..a6028fd5d5d2613ee3dd8c030e38f1da79c032f3 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,10 +35,19 @@ public class MoveCaretUpWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
-      int lineShift = -1;
-      editor.getCaretModel().moveCaretRelatively(0, lineShift, true, editor.isColumnMode(), true);
+      if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets()) {
+        editor.getCaretModel().getCurrentCaret().clone(true);
+
+      }
+      else {
+        editor.getCaretModel().moveCaretRelatively(0, -1, true, editor.isColumnMode(), true);
+      }
     }
   }
 }
index 4c356228c546082f42a8e269c89a1fbfbc621f94..9db48b46da7da05ea01d39ebe63869226b0c2c84 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class NextWordAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToNextWord(editor, false, editor.getSettings().isCamelWords());
index d0fb2bcc363d613d80980f986803169e63ec3220..4f1f30693d165c81d98ab800b292924a089d4c55 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -30,6 +30,10 @@ public class NextWordInDifferentHumpsModeAction extends TextComponentEditorActio
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToNextWord(editor, false, !editor.getSettings().isCamelWords());
index bf1c4da5ae617eeca401610268ca54a9dd87b7d9..0e5afda458e2e96516b9de7da6e806217cce83e7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -30,6 +30,10 @@ public class NextWordInDifferentHumpsModeWithSelectionAction extends TextCompone
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToNextWord(editor, true, !editor.getSettings().isCamelWords());
index 8967c0e321ecf6528df70717ce72fb17ffa87773..6de8960760e1e98eb851bd20b22831af6e1bf009 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class NextWordWithSelectionAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToNextWord(editor, true, editor.getSettings().isCamelWords());
index c82e8390c163ec41e9a0ab266f9a351c56958a23..1515baf7a0608f0d504e5bd65269efcdb9d90707 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class PageBottomAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretPageBottom(editor, false);
index 221ce42f11eca633b9853679547ec1170ccc4edf..be3a04c96bb012d7a628df15c1fc1722490d3118 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class PageBottomWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretPageBottom(editor, true);
index 4fc4c859d3a8dc2613bec538ae723decbdcec9d4..1da22ea744eac910f0a0a5120ce2991b7002475a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -31,6 +31,10 @@ import com.intellij.openapi.actionSystem.DataContext;
 
 public class PageDownAction extends EditorAction {
   public static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretPageDown(editor, false);
index 0e7828212084b715bab8b331008ea9d4f73c5df3..427a4d96e908e88b91dc62a719f7ece4e2754122 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -24,6 +24,7 @@
  */
 package com.intellij.openapi.editor.actions;
 
+import com.intellij.openapi.editor.Caret;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
@@ -31,9 +32,25 @@ import com.intellij.openapi.actionSystem.DataContext;
 
 public class PageDownWithSelectionAction extends EditorAction {
   public static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
-      EditorActionUtil.moveCaretPageDown(editor, true);
+      if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets()) {
+        int lines = editor.getScrollingModel().getVisibleArea().height / editor.getLineHeight();
+        Caret caret = editor.getCaretModel().getCurrentCaret();
+        for (int i = 0; i < lines; i++) {
+          caret = caret.clone(false);
+          if (caret == null) {
+            break;
+          }
+        }
+      }
+      else {
+        EditorActionUtil.moveCaretPageDown(editor, true);
+      }
     }
   }
 
index d86b68cc35170d4dc486b8d300a6266da0bdd180..58846c65b90aed94564cb154e89067f34622345f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class PageTopAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretPageTop(editor, false);
index 9b7cc77a3f2820a5f0b5629ea52305094f394289..d24d4a8b579544af88ed015ec3400f866b52394e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class PageTopWithSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretPageTop(editor, true);
index 3a69510ecef59a1b9bda70bed541bb27fb95609c..a22268159eee2df3544fa2e69089a56fb0f0484a 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -31,6 +31,10 @@ import com.intellij.openapi.actionSystem.DataContext;
 
 public class PageUpAction extends EditorAction {
   public static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretPageUp(editor, false);
index 5bb39c70556166b163130ebc9edb00931df81ca8..b392086dc1451ad99f24dd7d065f820b698a088e 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -24,6 +24,7 @@
  */
 package com.intellij.openapi.editor.actions;
 
+import com.intellij.openapi.editor.Caret;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
 import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
@@ -31,9 +32,25 @@ import com.intellij.openapi.actionSystem.DataContext;
 
 public class PageUpWithSelectionAction extends EditorAction {
   public static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
-      EditorActionUtil.moveCaretPageUp(editor, true);
+      if (editor.isColumnMode() && editor.getCaretModel().supportsMultipleCarets()) {
+        int lines = editor.getScrollingModel().getVisibleArea().height / editor.getLineHeight();
+        Caret caret = editor.getCaretModel().getCurrentCaret();
+        for (int i = 0; i < lines; i++) {
+          caret = caret.clone(true);
+          if (caret == null) {
+            break;
+          }
+        }
+      }
+      else {
+        EditorActionUtil.moveCaretPageUp(editor, true);
+      }
     }
   }
 
index 7851070f9d9508f6cd718a92072416fe93e34fcd..76a2ae6f24e094ab7992f2f8841f73f3aedaec4a 100644 (file)
@@ -42,7 +42,7 @@ public class PasteAction extends EditorAction {
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       Producer<Transferable> producer = TRANSFERABLE_PROVIDER.getData(dataContext);
-      if (editor.isColumnMode() || editor.getSelectionModel().hasBlockSelection()) {
+      if (!editor.getCaretModel().supportsMultipleCarets() && (editor.isColumnMode() || editor.getSelectionModel().hasBlockSelection())) {
         EditorModificationUtil.pasteTransferableAsBlock(editor, producer);
       }
       else {
index cf423d969ff4118dbf27cdd3827bee6571df1883..9c9e9806783481a34b5dae1e2079d15ea8eaa820 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -35,6 +35,10 @@ public class PreviousWordAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToPreviousWord(editor, false, editor.getSettings().isCamelWords());
index d5debd14cf2ff30688cd82ac85db053b396c441e..4b6960d1f5a8f7cfcdf603cda857544fa078be54 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -30,6 +30,10 @@ public class PreviousWordInDifferentHumpsModeAction extends TextComponentEditorA
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToPreviousWord(editor, false, !editor.getSettings().isCamelWords());
index c6b4f2eea2a85ab6cb831c6b16b1694a86c4686e..fdfdc054f92e116564824ddb4c2dfb4348eca07b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -30,6 +30,10 @@ public class PreviousWordInDifferentHumpsModeWithSelectionAction  extends TextCo
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToPreviousWord(editor, true, !editor.getSettings().isCamelWords());
index c1e38172a8026a02008e9c704f57690538db1172..c1c5b3940a99407f65afb314f04f2ae4080c494d 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -36,6 +36,10 @@ public class PreviousWordWithSelectionAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       EditorActionUtil.moveCaretToPreviousWord(editor, true, editor.getSettings().isCamelWords());
index 37fe922fb6d634135492e3d8faa6d4b567ae4660..2398866714e34a3b4c38dff00818e96317479500 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -34,6 +34,10 @@ public class SelectLineAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       editor.getSelectionModel().selectLineAtCaret();
index a19a9a85a7e53c2c74823775f6f03c7e7e18c860..59a6cbca29a6396eb6d9edde03d75a60ab18579b 100644 (file)
@@ -51,7 +51,7 @@ public class SimplePasteAction extends EditorAction {
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       Producer<Transferable> producer = PasteAction.TRANSFERABLE_PROVIDER.getData(dataContext);
-      if (editor.isColumnMode()) {
+      if (!editor.getCaretModel().supportsMultipleCarets() && editor.isColumnMode()) {
         EditorModificationUtil.pasteTransferableAsBlock(editor, producer);
       }
       else {
index 2f94e39b4ed3fd0b28e1fbc892da8025d941d411..7716a9001b7e8a7fde49688dc8248dabc03a051b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -43,6 +43,10 @@ public class SplitLineAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public boolean isEnabled(Editor editor, DataContext dataContext) {
       return getEnterHandler().isEnabled(editor, dataContext) &&
index 81e484073dd1f64e7a6f8672a3a566bbf3131ee3..0302e484d00ddddb9b0e65e4a3472f6b5044157f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -34,6 +34,10 @@ public class StartNewLineAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public boolean isEnabled(Editor editor, DataContext dataContext) {
       return getEnterHandler().isEnabled(editor, dataContext);
index 115c8c949ea95eec2b43ee965de2b5240df6ee84..9510e97c90ffdeb97222d4414adcf9f7b388cfbc 100644 (file)
@@ -1,3 +1,18 @@
+/*
+ * 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.DataContext;
@@ -21,6 +36,10 @@ public class StartNewLineBeforeAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public boolean isEnabled(Editor editor, DataContext dataContext) {
       return getHandler(IdeActions.ACTION_EDITOR_ENTER).isEnabled(editor, dataContext);
index cde980eef598240afde72b3ba83d5cef34cae180..d6941786f4010820808015a819d6216d69168eac 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
@@ -37,6 +37,10 @@ public class SwapSelectionBoundariesAction extends EditorAction {
   }
   
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
       if (!(editor instanceof EditorEx)) {
index e0c9a526e7e35bd016f603f7b3797bb17e2d5e13..9054e1248ed39cd0c414137ebdbe51f6125899d8 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -48,6 +48,10 @@ public class TabAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       CommandProcessor.getInstance().setCurrentCommandGroupId(EditorActionUtil.EDIT_COMMAND_GROUP);
index 4531af8d6dd6705950e5dd0b6c38c98079e958df..ae96512c651140c818597ded1a06cf4a8f26a2e1 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -36,6 +36,7 @@ public class TextEndAction extends TextComponentEditorAction {
   private static class Handler extends EditorActionHandler {
     @Override
     public void execute(Editor editor, DataContext dataContext) {
+      editor.getCaretModel().removeSecondaryCarets();
       int offset = editor.getDocument().getTextLength();
       editor.getCaretModel().moveToOffset(offset);
       editor.getSelectionModel().removeSelection();
index 4b23744a6c2b54a8ad2da52c8877495acc7f8e2e..34ede5544495cb6c55e4171acf63d9c8019cba44 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -39,6 +39,7 @@ public class TextEndWithSelectionAction extends TextComponentEditorAction {
   private static class Handler extends EditorActionHandler {
     @Override
     public void execute(Editor editor, DataContext dataContext) {
+      editor.getCaretModel().removeSecondaryCarets();
       int selectionStart = editor.getSelectionModel().getLeadSelectionOffset();
       int offset = editor.getDocument().getTextLength();
       editor.getCaretModel().moveToOffset(offset);
index d63ca3bc0b41201235f7b0d5d93df580c61f8637..2fe51b35a9c8d300d75a2bfd2d4365842be7ae4b 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2013 JetBrains s.r.o.
+ * 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.
@@ -36,6 +36,7 @@ public class TextStartAction extends TextComponentEditorAction {
   private static class Handler extends EditorActionHandler {
     @Override
     public void execute(Editor editor, DataContext dataContext) {
+      editor.getCaretModel().removeSecondaryCarets();
       editor.getCaretModel().moveToOffset(0);
       editor.getSelectionModel().removeSelection();
 
index 4c441322da50511db0280a3034922b3c55c64510..685c8d5655b5cbb835a1d02e9a73fe45506631a4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -39,6 +39,7 @@ public class TextStartWithSelectionAction extends TextComponentEditorAction {
   private static class Handler extends EditorActionHandler {
     @Override
     public void execute(Editor editor, DataContext dataContext) {
+      editor.getCaretModel().removeSecondaryCarets();
       int selectionStart = editor.getSelectionModel().getLeadSelectionOffset();
       editor.getCaretModel().moveToOffset(0);
       editor.getSelectionModel().setSelection(selectionStart, 0);
index d147ab432c864e95a2173e59dad6309e3d5738a0..4253c2c9859e0a1c6da438919686b21a604fdfa4 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -36,6 +36,10 @@ public class ToggleCaseAction extends TextComponentEditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       final SelectionModel selectionModel = editor.getSelectionModel();
index ff8818976738f2480cb59a8941efbe066efe6e20..64659ce3a5a0c4f7eeb7f708f730d58fbba4fcf7 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -38,6 +38,10 @@ public class ToggleColumnModeAction extends ToggleAction implements DumbAware {
   @Override
   public void setSelected(AnActionEvent e, boolean state) {
     final EditorEx editor = getEditor(e);
+    if (editor.getCaretModel().supportsMultipleCarets()) {
+      editor.setColumnMode(state);
+      return;
+    }
     final SelectionModel selectionModel = editor.getSelectionModel();
     if (state) {
       boolean hasSelection = selectionModel.hasSelection();
index 231c7e9f14bc7dd8a47a8e0697e5ac8631f3dcfc..7a0ca1a9946d5489a3e52ab661dc0cfe7f2db39f 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -27,7 +27,6 @@ package com.intellij.openapi.editor.actions;
 import com.intellij.codeStyle.CodeStyleFacade;
 import com.intellij.openapi.actionSystem.CommonDataKeys;
 import com.intellij.openapi.actionSystem.DataContext;
-import com.intellij.openapi.actionSystem.PlatformDataKeys;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
 import com.intellij.openapi.editor.actionSystem.EditorAction;
@@ -35,7 +34,6 @@ import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
 import com.intellij.openapi.editor.ex.EditorEx;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
 import com.intellij.openapi.fileTypes.FileType;
-import com.intellij.openapi.fileTypes.FileTypeManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.vfs.VirtualFile;
 
@@ -45,6 +43,10 @@ public class UnindentSelectionAction extends EditorAction {
   }
 
   private static class Handler extends EditorWriteActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void executeWriteAction(Editor editor, DataContext dataContext) {
       Project project = CommonDataKeys.PROJECT.getData(dataContext);
index ad9ce0846caeb8624e4812031412fb502c892e28..a1ae088a89e294bfc365ad718bb19307d3866010 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
+ * 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.
@@ -37,6 +37,10 @@ public class UnselectWordAtCaretAction extends EditorAction implements DumbAware
   }
 
   private static class Handler extends EditorActionHandler {
+    public Handler() {
+      super(true);
+    }
+
     @Override
     public void execute(Editor editor, DataContext dataContext) {
     }
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/CaretImpl.java
new file mode 100644 (file)
index 0000000..1b79d35
--- /dev/null
@@ -0,0 +1,1447 @@
+/*
+ * 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.impl;
+
+import com.intellij.diagnostic.LogMessageEx;
+import com.intellij.openapi.actionSystem.IdeActions;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.editor.*;
+import com.intellij.openapi.editor.actionSystem.EditorActionHandler;
+import com.intellij.openapi.editor.actionSystem.EditorActionManager;
+import com.intellij.openapi.editor.actions.EditorActionUtil;
+import com.intellij.openapi.editor.event.CaretEvent;
+import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.ex.DocumentEx;
+import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
+import com.intellij.openapi.editor.ex.FoldingModelEx;
+import com.intellij.openapi.editor.ex.util.EditorUtil;
+import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
+import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper;
+import com.intellij.openapi.ide.CopyPasteManager;
+import com.intellij.openapi.util.Disposer;
+import com.intellij.openapi.util.Pair;
+import com.intellij.openapi.util.UserDataHolderBase;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.util.diff.FilesTooBigForDiffException;
+import com.intellij.util.text.CharArrayUtil;
+import com.intellij.util.ui.EmptyClipboardOwner;
+import org.jetbrains.annotations.NonNls;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.*;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.StringSelection;
+
+public class CaretImpl extends UserDataHolderBase implements Caret {
+  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.CaretImpl");
+
+  private final EditorImpl myEditor;
+  private boolean isValid = true;
+
+  private LogicalPosition myLogicalCaret;
+  private VerticalInfo myCaretInfo;
+  private VisualPosition myVisibleCaret;
+  private int myOffset;
+  private int myVirtualSpaceOffset;
+  private int myVisualLineStart;
+  private int myVisualLineEnd;
+  private RangeMarker savedBeforeBulkCaretMarker;
+  private boolean mySkipChangeRequests;
+  private int myLastColumnNumber = 0;
+  /**
+   * We check that caret is located at the target offset at the end of {@link #moveToOffset(int, boolean)} method. However,
+   * it's possible that the following situation occurs:
+   * <p/>
+   * <pre>
+   * <ol>
+   *   <li>Some client subscribes to caret change events;</li>
+   *   <li>{@link #moveToLogicalPosition(LogicalPosition)} is called;</li>
+   *   <li>Caret position is changed during {@link #moveToLogicalPosition(LogicalPosition)} processing;</li>
+   *   <li>The client receives caret position change event and adjusts the position;</li>
+   *   <li>{@link #moveToLogicalPosition(LogicalPosition)} processing is finished;</li>
+   *   <li>{@link #moveToLogicalPosition(LogicalPosition)} reports an error because the caret is not located at the target offset;</li>
+   * </ol>
+   * </pre>
+   * <p/>
+   * This field serves as a flag that reports unexpected caret position change requests nested from {@link #moveToOffset(int, boolean)}.
+   */
+  private boolean myReportCaretMoves;
+  /**
+   * There is a possible case that user defined non-monospaced font for editor. That means that various symbols have different
+   * visual widths. That means that if we move caret vertically it may deviate to the left/right. However, we can try to preserve
+   * its initial visual position when possible.
+   * <p/>
+   * This field holds desired value for visual <code>'x'</code> caret coordinate (negative value if no coordinate should be preserved).
+   */
+  private int myDesiredX = -1;
+
+  private volatile MyRangeMarker mySelectionMarker;
+  private int startBefore;
+  private int endBefore;
+  boolean myUnknownDirection;
+
+  CaretImpl(EditorImpl editor) {
+    myEditor = editor;
+
+    myLogicalCaret = new LogicalPosition(0, 0);
+    myVisibleCaret = new VisualPosition(0, 0);
+    myCaretInfo = new VerticalInfo(0, 0);
+    myOffset = 0;
+    myVisualLineStart = 0;
+    Document doc = myEditor.getDocument();
+    myVisualLineEnd = doc.getLineCount() > 1 ? doc.getLineStartOffset(1) : doc.getLineCount() == 0 ? 0 : doc.getLineEndOffset(0);
+  }
+
+  void onBulkDocumentUpdateStarted(@NotNull Document doc) {
+    if (doc != myEditor.getDocument() || myOffset > doc.getTextLength() || savedBeforeBulkCaretMarker != null) return;
+    savedBeforeBulkCaretMarker = doc.createRangeMarker(myOffset, myOffset);
+  }
+
+  void onBulkDocumentUpdateFinished(@NotNull Document doc) {
+    if (doc != myEditor.getDocument() || myEditor.getCaretModel().myIsInUpdate) return;
+    LOG.assertTrue(!myReportCaretMoves);
+
+    if (savedBeforeBulkCaretMarker != null) {
+      if(savedBeforeBulkCaretMarker.isValid()) {
+        if(savedBeforeBulkCaretMarker.getStartOffset() != myOffset) {
+          moveToOffset(savedBeforeBulkCaretMarker.getStartOffset());
+        }
+      } else if (myOffset > doc.getTextLength()) {
+        moveToOffset(doc.getTextLength());
+      }
+      releaseBulkCaretMarker();
+    }
+  }
+
+  public void beforeDocumentChange() {
+    MyRangeMarker marker = mySelectionMarker;
+    if (marker != null && marker.isValid()) {
+      startBefore = marker.getStartOffset();
+      endBefore = marker.getEndOffset();
+    }
+  }
+
+  public void documentChanged() {
+    MyRangeMarker marker = mySelectionMarker;
+    if (marker != null) {
+      int endAfter;
+      int startAfter;
+      if (marker.isValid()) {
+        startAfter = marker.getStartOffset();
+        endAfter = marker.getEndOffset();
+      }
+      else {
+        startAfter = endAfter = getOffset();
+        marker.release();
+        mySelectionMarker = null;
+      }
+
+      if (startBefore != startAfter || endBefore != endAfter) {
+        myEditor.getSelectionModel().fireSelectionChanged(startBefore, endBefore, startAfter, endAfter);
+      }
+    }
+  }
+
+  @Override
+  public void moveToOffset(int offset) {
+    moveToOffset(offset, false);
+  }
+
+  @Override
+  public void moveToOffset(final int offset, final boolean locateBeforeSoftWrap) {
+    assertIsDispatchThread();
+    validateCallContext();
+    if (mySkipChangeRequests) {
+      return;
+    }
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        final LogicalPosition logicalPosition = myEditor.offsetToLogicalPosition(offset);
+        CaretEvent event = moveToLogicalPosition(logicalPosition, locateBeforeSoftWrap, null, false);
+        final LogicalPosition positionByOffsetAfterMove = myEditor.offsetToLogicalPosition(myOffset);
+        if (!myEditor.getCaretModel().myIgnoreWrongMoves && !positionByOffsetAfterMove.equals(logicalPosition)) {
+          StringBuilder debugBuffer = new StringBuilder();
+          moveToLogicalPosition(logicalPosition, locateBeforeSoftWrap, debugBuffer, true);
+          int textStart = Math.max(0, Math.min(offset, myOffset) - 1);
+          final DocumentEx document = myEditor.getDocument();
+          int textEnd = Math.min(document.getTextLength() - 1, Math.max(offset, myOffset) + 1);
+          CharSequence text = document.getCharsSequence().subSequence(textStart, textEnd);
+          StringBuilder positionToOffsetTrace = new StringBuilder();
+          int inverseOffset = myEditor.logicalPositionToOffset(logicalPosition, positionToOffsetTrace);
+          LogMessageEx.error(
+            LOG, "caret moved to wrong offset. Please submit a dedicated ticket and attach current editor's text to it.",
+            String.format(
+              "Requested: offset=%d, logical position='%s' but actual: offset=%d, logical position='%s' (%s). %s%n"
+              + "interested text [%d;%d): '%s'%n debug trace: %s%nLogical position -> offset ('%s'->'%d') trace: %s",
+              offset, logicalPosition, myOffset, myLogicalCaret, positionByOffsetAfterMove, myEditor.dumpState(),
+              textStart, textEnd, text, debugBuffer, logicalPosition, inverseOffset, positionToOffsetTrace
+            )
+          );
+        }
+        if (event != null) {
+          myEditor.getCaretModel().fireCaretPositionChanged(event);
+          EditorActionUtil.selectNonexpandableFold(myEditor);
+        }
+      }
+    });
+  }
+
+  @NotNull
+  @Override
+  public CaretModel getCaretModel() {
+    return myEditor.getCaretModel();
+  }
+
+  @Override
+  public boolean isValid() {
+    return isValid;
+  }
+
+  @Override
+  public void moveCaretRelatively(int columnShift, int lineShift, boolean withSelection, boolean scrollToCaret) {
+    moveCaretRelatively(columnShift, lineShift, withSelection, false, scrollToCaret);
+  }
+
+  void moveCaretRelatively(final int columnShift,
+                                  final int lineShift,
+                                  final boolean withSelection,
+                                  final boolean blockSelection,
+                                  final boolean scrollToCaret) {
+    assertIsDispatchThread();
+    if (mySkipChangeRequests) {
+      return;
+    }
+    if (myReportCaretMoves) {
+      LogMessageEx.error(LOG, "Unexpected caret move request");
+    }
+    if (!myEditor.isStickySelection() && !myEditor.getCaretModel().isDocumentChanged) {
+      CopyPasteManager.getInstance().stopKillRings();
+    }
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        SelectionModelImpl selectionModel = myEditor.getSelectionModel();
+        final int leadSelectionOffset = selectionModel.getLeadSelectionOffset();
+        LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection()
+                                              ? selectionModel.getBlockStart()
+                                              : getLogicalPosition();
+        EditorSettings editorSettings = myEditor.getSettings();
+        VisualPosition visualCaret = getVisualPosition();
+
+        int desiredX = myDesiredX;
+        if (columnShift == 0) {
+          if (myDesiredX < 0) {
+            desiredX = myEditor.visualPositionToXY(visualCaret).x;
+          }
+        }
+        else {
+          myDesiredX = desiredX = -1;
+        }
+
+        int newLineNumber = visualCaret.line + lineShift;
+        int newColumnNumber = visualCaret.column + columnShift;
+        if (desiredX >= 0 && !ApplicationManager.getApplication().isUnitTestMode()) {
+          newColumnNumber = myEditor.xyToVisualPosition(new Point(desiredX, Math.max(0, newLineNumber) * myEditor.getLineHeight())).column;
+        }
+
+        Document document = myEditor.getDocument();
+        if (!editorSettings.isVirtualSpace() && columnShift == 0 && getLogicalPosition().softWrapLinesOnCurrentLogicalLine <= 0) {
+          newColumnNumber = myEditor.getCaretModel().supportsMultipleCarets() ? myLastColumnNumber : myEditor.getLastColumnNumber();
+        }
+        else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == 1) {
+          int lastLine = document.getLineCount() - 1;
+          if (lastLine < 0) lastLine = 0;
+          if (EditorModificationUtil.calcAfterLineEnd(myEditor) >= 0 &&
+              newLineNumber < myEditor.logicalToVisualPosition(new LogicalPosition(lastLine, 0)).line) {
+            newColumnNumber = 0;
+            newLineNumber++;
+          }
+        }
+        else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == -1) {
+          if (newColumnNumber < 0 && newLineNumber > 0) {
+            newLineNumber--;
+            newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber);
+          }
+        }
+
+        if (newColumnNumber < 0) newColumnNumber = 0;
+
+        // There is a possible case that caret is located at the first line and user presses 'Shift+Up'. We want to select all text
+        // from the document start to the current caret position then. So, we have a dedicated flag for tracking that.
+        boolean selectToDocumentStart = false;
+        if (newLineNumber < 0) {
+          selectToDocumentStart = true;
+          newLineNumber = 0;
+
+          // We want to move caret to the first column if it's already located at the first line and 'Up' is pressed.
+          newColumnNumber = 0;
+          desiredX = -1;
+        }
+
+        VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber);
+        int lastColumnNumber = newColumnNumber;
+        if (!editorSettings.isCaretInsideTabs() && !myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
+          LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber));
+          int offset = myEditor.logicalPositionToOffset(log);
+          if (offset >= document.getTextLength()) {
+            int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength()).column;
+            // We want to move caret to the last column if if it's located at the last line and 'Down' is pressed.
+            newColumnNumber = lastColumnNumber = Math.max(lastOffsetColumn, newColumnNumber);
+            desiredX = -1;
+          }
+          CharSequence text = document.getCharsSequence();
+          if (offset >= 0 && offset < document.getTextLength()) {
+            if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == myOffset)) {
+              if (columnShift <= 0) {
+                newColumnNumber = myEditor.offsetToVisualPosition(offset).column;
+              }
+              else {
+                SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1);
+                // There is a possible case that tabulation symbol is the last document symbol represented on a visual line before
+                // soft wrap. We can't just use column from 'offset + 1' because it would point on a next visual line.
+                if (softWrap == null) {
+                  newColumnNumber = myEditor.offsetToVisualPosition(offset + 1).column;
+                }
+                else {
+                  newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber);
+                }
+              }
+            }
+          }
+        }
+
+        pos = new VisualPosition(newLineNumber, newColumnNumber);
+        if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
+          LogicalPosition logical = myEditor.visualToLogicalPosition(pos);
+          int softWrapOffset = myEditor.logicalPositionToOffset(logical);
+          if (columnShift >= 0) {
+            moveToOffset(softWrapOffset);
+          }
+          else {
+            int line = myEditor.offsetToVisualLine(softWrapOffset - 1);
+            moveToVisualPosition(new VisualPosition(line, EditorUtil.getLastVisualLineColumnNumber(myEditor, line)));
+          }
+        }
+        else {
+          moveToVisualPosition(pos);
+          if (!editorSettings.isVirtualSpace() && columnShift == 0) {
+            setLastColumnNumber(lastColumnNumber);
+          }
+        }
+
+        if (withSelection) {
+          if (blockSelection && !myEditor.getCaretModel().supportsMultipleCarets()) {
+            selectionModel.setBlockSelection(blockSelectionStart, getLogicalPosition());
+          }
+          else {
+            if (selectToDocumentStart) {
+              selectionModel.setSelection(leadSelectionOffset, 0);
+            }
+            else if (pos.line >= myEditor.getVisibleLineCount()) {
+              if (leadSelectionOffset < document.getTextLength()) {
+                selectionModel.setSelection(leadSelectionOffset, document.getTextLength());
+              }
+            }
+            else {
+              int selectionStartToUse = leadSelectionOffset;
+              if (selectionModel.isUnknownDirection()) {
+                if (getOffset() > leadSelectionOffset) {
+                  selectionStartToUse = Math.min(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
+                }
+                else {
+                  selectionStartToUse = Math.max(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
+                }
+              }
+              selectionModel.setSelection(selectionStartToUse, getVisualPosition(), getOffset());
+            }
+          }
+        }
+        else {
+          removeSelection();
+        }
+
+        if (scrollToCaret) {
+          myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+        }
+
+        if (desiredX >= 0) {
+          myDesiredX = desiredX;
+        }
+
+        EditorActionUtil.selectNonexpandableFold(myEditor);
+      }
+    });
+  }
+
+  @Override
+  public void moveToLogicalPosition(@NotNull final LogicalPosition pos) {
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        moveToLogicalPosition(pos, false, null, true);
+      }
+    });
+  }
+
+
+  private CaretEvent doMoveToLogicalPosition(@NotNull LogicalPosition pos,
+                                             boolean locateBeforeSoftWrap,
+                                             @NonNls @Nullable StringBuilder debugBuffer,
+                                             boolean fireListeners) {
+    assertIsDispatchThread();
+    if (debugBuffer != null) {
+      debugBuffer.append("Start moveToLogicalPosition(). Locate before soft wrap: " + locateBeforeSoftWrap + ", position: " + pos + "\n");
+    }
+    myDesiredX = -1;
+    validateCallContext();
+    int column = pos.column;
+    int line = pos.line;
+    int softWrapLinesBefore = pos.softWrapLinesBeforeCurrentLogicalLine;
+    int softWrapLinesCurrent = pos.softWrapLinesOnCurrentLogicalLine;
+    int softWrapColumns = pos.softWrapColumnDiff;
+
+    Document doc = myEditor.getDocument();
+
+    if (column < 0) {
+      if (debugBuffer != null) {
+        debugBuffer.append("Resetting target logical column to zero as it is negative (").append(column).append(")\n");
+      }
+      column = 0;
+      softWrapColumns = 0;
+    }
+    if (line < 0) {
+      if (debugBuffer != null) {
+        debugBuffer.append("Resetting target logical line to zero as it is negative (").append(line).append(")\n");
+      }
+      line = 0;
+      softWrapLinesBefore = 0;
+      softWrapLinesCurrent = 0;
+    }
+
+    int lineCount = doc.getLineCount();
+    if (lineCount == 0) {
+      if (debugBuffer != null) {
+        debugBuffer.append("Resetting target logical line to zero as the document is empty\n");
+      }
+      line = 0;
+    }
+    else if (line > lineCount - 1) {
+      if (debugBuffer != null) {
+        debugBuffer.append("Resetting target logical line (" + line + ") to " + (lineCount - 1) + " as it is greater than total document lines number\n");
+      }
+      line = lineCount - 1;
+      softWrapLinesBefore = 0;
+      softWrapLinesCurrent = 0;
+    }
+
+    EditorSettings editorSettings = myEditor.getSettings();
+
+    if (!editorSettings.isVirtualSpace() && line < lineCount && !myEditor.getSelectionModel().hasBlockSelection()) {
+      int lineEndOffset = doc.getLineEndOffset(line);
+      final LogicalPosition endLinePosition = myEditor.offsetToLogicalPosition(lineEndOffset);
+      int lineEndColumnNumber = endLinePosition.column;
+      if (column > lineEndColumnNumber) {
+        int oldColumn = column;
+        column = lineEndColumnNumber;
+        if (softWrapColumns != 0) {
+          softWrapColumns -= column - lineEndColumnNumber;
+        }
+        if (debugBuffer != null) {
+          debugBuffer.append(
+            "Resetting target logical column (" + oldColumn + ") to " + lineEndColumnNumber +
+            " because caret is not allowed to be located after line end (offset: " +lineEndOffset + ", "
+            + "logical position: " + endLinePosition+ "). Current soft wrap columns value: " + softWrapColumns+ "\n");
+        }
+      }
+    }
+
+    myEditor.getFoldingModel().flushCaretPosition();
+
+    VerticalInfo oldInfo = myCaretInfo;
+    LogicalPosition oldCaretPosition = myLogicalCaret;
+
+    LogicalPosition logicalPositionToUse;
+    if (pos.visualPositionAware) {
+      logicalPositionToUse = new LogicalPosition(
+        line, column, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff
+      );
+    }
+    else {
+      logicalPositionToUse = new LogicalPosition(line, column);
+    }
+    setCurrentLogicalCaret(logicalPositionToUse);
+    final int offset = myEditor.logicalPositionToOffset(myLogicalCaret);
+    if (debugBuffer != null) {
+      debugBuffer.append("Resulting logical position to use: " + myLogicalCaret+
+                         ". It's mapped to offset " + offset+ "\n");
+    }
+
+    FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(offset);
+
+    if (collapsedAt != null && offset > collapsedAt.getStartOffset()) {
+      if (debugBuffer != null) {
+        debugBuffer.append("Scheduling expansion of fold region ").append(collapsedAt).append("\n");
+      }
+      Runnable runnable = new Runnable() {
+        @Override
+        public void run() {
+          FoldRegion[] allCollapsedAt = myEditor.getFoldingModel().fetchCollapsedAt(offset);
+          for (FoldRegion foldRange : allCollapsedAt) {
+            foldRange.setExpanded(true);
+          }
+        }
+      };
+
+      mySkipChangeRequests = true;
+      try {
+        myEditor.getFoldingModel().runBatchFoldingOperation(runnable, false);
+      }
+      finally {
+        mySkipChangeRequests = false;
+      }
+    }
+
+    setLastColumnNumber(myLogicalCaret.column);
+    myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret);
+
+    updateOffsetsFromLogicalPosition();
+    if (debugBuffer != null) {
+      debugBuffer.append("Storing offset " + myOffset + " (mapped from logical position " + myLogicalCaret + ")\n");
+    }
+    LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength());
+
+    updateVisualLineInfo();
+
+    myEditor.updateCaretCursor();
+    requestRepaint(oldInfo);
+
+    if (locateBeforeSoftWrap && SoftWrapHelper.isCaretAfterSoftWrap(myEditor)) {
+      int lineToUse = myVisibleCaret.line - 1;
+      if (lineToUse >= 0) {
+        final VisualPosition visualPosition = new VisualPosition(lineToUse, EditorUtil.getLastVisualLineColumnNumber(myEditor, lineToUse));
+        if (debugBuffer != null) {
+          debugBuffer.append("Adjusting caret position by moving it before soft wrap. Moving to visual position "+ visualPosition+"\n");
+        }
+        final LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(visualPosition);
+        final int tmpOffset = myEditor.logicalPositionToOffset(logicalPosition);
+        if (tmpOffset == myOffset) {
+          boolean restore = myReportCaretMoves;
+          myReportCaretMoves = false;
+          try {
+            moveToVisualPosition(visualPosition);
+            return null;
+          }
+          finally {
+            myReportCaretMoves = restore;
+          }
+        }
+        else {
+          LogMessageEx.error(LOG, "Invalid editor dimension mapping", String.format(
+            "Expected to map visual position '%s' to offset %d but got the following: -> logical position '%s'; -> offset %d. "
+            + "State: %s", visualPosition, myOffset, logicalPosition, tmpOffset, myEditor.dumpState()
+          ));
+        }
+      }
+    }
+
+    if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) {
+      CaretEvent event = new CaretEvent(myEditor, myEditor.getCaretModel().supportsMultipleCarets() ? this : null, oldCaretPosition, myLogicalCaret);
+      if (fireListeners) {
+        myEditor.getCaretModel().fireCaretPositionChanged(event);
+      }
+      else {
+        return event;
+      }
+    }
+    return null;
+  }
+
+  private void updateOffsetsFromLogicalPosition() {
+    myOffset = myEditor.logicalPositionToOffset(myLogicalCaret);
+    myVirtualSpaceOffset = myLogicalCaret.column - myEditor.offsetToLogicalPosition(myOffset).column;
+  }
+
+  private void setLastColumnNumber(int lastColumnNumber) {
+    myLastColumnNumber = lastColumnNumber;
+    myEditor.setLastColumnNumber(lastColumnNumber);
+  }
+
+  private void requestRepaint(VerticalInfo oldCaretInfo) {
+    int lineHeight = myEditor.getLineHeight();
+    Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
+    final EditorGutterComponentEx gutter = myEditor.getGutterComponentEx();
+    final EditorComponentImpl content = myEditor.getContentComponent();
+
+    int updateWidth = myEditor.getScrollPane().getHorizontalScrollBar().getValue() + visibleArea.width;
+    if (Math.abs(myCaretInfo.y - oldCaretInfo.y) <= 2 * lineHeight) {
+      int minY = Math.min(oldCaretInfo.y, myCaretInfo.y);
+      int maxY = Math.max(oldCaretInfo.y + oldCaretInfo.height, myCaretInfo.y + myCaretInfo.height);
+      content.repaintEditorComponent(0, minY, updateWidth, maxY - minY);
+      gutter.repaint(0, minY, gutter.getWidth(), maxY - minY);
+    }
+    else {
+      content.repaintEditorComponent(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight);
+      gutter.repaint(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight);
+      content.repaintEditorComponent(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight);
+      gutter.repaint(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight);
+    }
+  }
+
+  @Override
+  public void moveToVisualPosition(@NotNull final VisualPosition pos) {
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        moveToVisualPosition(pos, true);
+      }
+    });
+  }
+
+  void moveToVisualPosition(@NotNull VisualPosition pos, boolean fireListeners) {
+    assertIsDispatchThread();
+    validateCallContext();
+    if (mySkipChangeRequests) {
+      return;
+    }
+    if (myReportCaretMoves) {
+      LogMessageEx.error(LOG, "Unexpected caret move request");
+    }
+    if (!myEditor.isStickySelection() && !myEditor.getCaretModel().isDocumentChanged && !pos.equals(myVisibleCaret)) {
+      CopyPasteManager.getInstance().stopKillRings();
+    }
+
+    myDesiredX = -1;
+    int column = pos.column;
+    int line = pos.line;
+
+    if (column < 0) column = 0;
+
+    if (line < 0) line = 0;
+
+    int lastLine = myEditor.getVisibleLineCount() - 1;
+    if (lastLine <= 0) {
+      lastLine = 0;
+    }
+
+    if (line > lastLine) {
+      line = lastLine;
+    }
+
+    EditorSettings editorSettings = myEditor.getSettings();
+
+    if (!editorSettings.isVirtualSpace() && line <= lastLine) {
+      int lineEndColumn = EditorUtil.getLastVisualLineColumnNumber(myEditor, line);
+      if (column > lineEndColumn) {
+        column = lineEndColumn;
+      }
+
+      if (column < 0 && line > 0) {
+        line--;
+        column = EditorUtil.getLastVisualLineColumnNumber(myEditor, line);
+      }
+    }
+
+    myVisibleCaret = new VisualPosition(line, column);
+
+    VerticalInfo oldInfo = myCaretInfo;
+    LogicalPosition oldPosition = myLogicalCaret;
+
+    setCurrentLogicalCaret(myEditor.visualToLogicalPosition(myVisibleCaret));
+    updateOffsetsFromLogicalPosition();
+    LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength());
+
+    updateVisualLineInfo();
+
+    myEditor.getFoldingModel().flushCaretPosition();
+
+    setLastColumnNumber(column);
+    myEditor.updateCaretCursor();
+    requestRepaint(oldInfo);
+
+    if (fireListeners && !oldPosition.equals(myLogicalCaret)) {
+      CaretEvent event = new CaretEvent(myEditor, myEditor.getCaretModel().supportsMultipleCarets() ? this : null, oldPosition, myLogicalCaret);
+      myEditor.getCaretModel().fireCaretPositionChanged(event);
+    }
+  }
+
+  @Nullable
+  CaretEvent moveToLogicalPosition(@NotNull LogicalPosition pos,
+                                           boolean locateBeforeSoftWrap,
+                                           @Nullable StringBuilder debugBuffer,
+                                           boolean fireListeners) {
+    if (mySkipChangeRequests) {
+      return null;
+    }
+    if (myReportCaretMoves) {
+      LogMessageEx.error(LOG, "Unexpected caret move request");
+    }
+    if (!myEditor.isStickySelection() && !myEditor.getCaretModel().isDocumentChanged && !pos.equals(myLogicalCaret)) {
+      CopyPasteManager.getInstance().stopKillRings();
+    }
+
+    myReportCaretMoves = true;
+    try {
+      return doMoveToLogicalPosition(pos, locateBeforeSoftWrap, debugBuffer, fireListeners);
+    }
+    finally {
+      myReportCaretMoves = false;
+    }
+  }
+
+  private void assertIsDispatchThread() {
+    myEditor.assertIsDispatchThread();
+  }
+
+  private void validateCallContext() {
+    LOG.assertTrue(!myEditor.getCaretModel().myIsInUpdate, "Caret model is in its update process. All requests are illegal at this point.");
+  }
+
+  private void releaseBulkCaretMarker() {
+    if (savedBeforeBulkCaretMarker != null) {
+      savedBeforeBulkCaretMarker.dispose();
+      savedBeforeBulkCaretMarker = null;
+    }
+  }
+
+  @Override
+  public void dispose() {
+    if (mySelectionMarker != null) {
+      mySelectionMarker.release();
+      mySelectionMarker = null;
+    }
+    releaseBulkCaretMarker();
+    isValid = false;
+  }
+
+  @Override
+  public boolean isUpToDate() {
+    return !myEditor.getCaretModel().myIsInUpdate && !myReportCaretMoves;
+  }
+
+  @NotNull
+  @Override
+  public LogicalPosition getLogicalPosition() {
+    validateCallContext();
+    return myLogicalCaret;
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getVisualPosition() {
+    validateCallContext();
+    return myVisibleCaret;
+  }
+
+  @Override
+  public int getOffset() {
+    validateCallContext();
+    return myOffset;
+  }
+
+  @Override
+  public int getVisualLineStart() {
+    return myVisualLineStart;
+  }
+
+  @Override
+  public int getVisualLineEnd() {
+    return myVisualLineEnd;
+  }
+
+  @NotNull
+  private VerticalInfo createVerticalInfo(LogicalPosition position) {
+    Document document = myEditor.getDocument();
+    int logicalLine = position.line;
+    if (logicalLine >= document.getLineCount()) {
+      logicalLine = Math.max(0, document.getLineCount() - 1);
+    }
+    int startOffset = document.getLineStartOffset(logicalLine);
+    int endOffset = document.getLineEndOffset(logicalLine);
+
+    // There is a possible case that active logical line is represented on multiple lines due to soft wraps processing.
+    // We want to highlight those visual lines as 'active' then, so, we calculate 'y' position for the logical line start
+    // and height in accordance with the number of occupied visual lines.
+    VisualPosition visualPosition = myEditor.offsetToVisualPosition(document.getLineStartOffset(logicalLine));
+    int y = myEditor.visualPositionToXY(visualPosition).y;
+    int lineHeight = myEditor.getLineHeight();
+    int height = lineHeight;
+    java.util.List<? extends SoftWrap> softWraps = myEditor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset);
+    for (SoftWrap softWrap : softWraps) {
+      height += StringUtil.countNewLines(softWrap.getText()) * lineHeight;
+    }
+
+    return new VerticalInfo(y, height);
+  }
+
+  /**
+   * Recalculates caret visual position without changing its logical position (called when soft wraps are changing)
+   */
+  public void updateVisualPosition() {
+    VerticalInfo oldInfo = myCaretInfo;
+    LogicalPosition visUnawarePos = new LogicalPosition(myLogicalCaret.line, myLogicalCaret.column);
+    setCurrentLogicalCaret(visUnawarePos);
+    myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret);
+    updateVisualLineInfo();
+
+    myEditor.updateCaretCursor();
+    requestRepaint(oldInfo);
+  }
+
+  private void updateVisualLineInfo() {
+    myVisualLineStart = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line, 0)));
+    myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0)));
+  }
+
+  void updateCaretPosition(@NotNull final DocumentEventImpl event) {
+    final DocumentEx document = myEditor.getDocument();
+    boolean performSoftWrapAdjustment = event.getNewLength() > 0 // We want to put caret just after the last added symbol
+                                        // There is a possible case that the user removes text just before the soft wrap. We want to keep caret
+                                        // on a visual line with soft wrap start then.
+                                        || myEditor.getSoftWrapModel().getSoftWrap(event.getOffset()) != null;
+
+    if (event.isWholeTextReplaced()) {
+      int newLength = document.getTextLength();
+      if (myOffset == newLength - event.getNewLength() + event.getOldLength() || newLength == 0) {
+        moveToOffset(newLength, performSoftWrapAdjustment);
+      }
+      else {
+        try {
+          final int line = event.translateLineViaDiff(myLogicalCaret.line);
+          moveToLogicalPosition(new LogicalPosition(line, myLogicalCaret.column), performSoftWrapAdjustment, null, true);
+        }
+        catch (FilesTooBigForDiffException e1) {
+          LOG.info(e1);
+          moveToOffset(0);
+        }
+      }
+    }
+    else {
+      if (document.isInBulkUpdate()) return;
+      int startOffset = event.getOffset();
+      int oldEndOffset = startOffset + event.getOldLength();
+
+      int newOffset = myOffset;
+
+      if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(event)) {
+        newOffset += event.getNewLength() - event.getOldLength();
+      }
+      else if (myOffset >= startOffset && myOffset <= oldEndOffset) {
+        newOffset = Math.min(newOffset, startOffset + event.getNewLength());
+      }
+
+      newOffset = Math.min(newOffset, document.getTextLength());
+
+      if (myEditor.getCaretModel().supportsMultipleCarets() && myOffset != startOffset) {
+        LogicalPosition pos = myEditor.offsetToLogicalPosition(newOffset);
+        moveToLogicalPosition(new LogicalPosition(pos.line, pos.column + myVirtualSpaceOffset), // retain caret in the virtual space
+                            performSoftWrapAdjustment, null, true);
+      }
+      else {
+        moveToOffset(newOffset, performSoftWrapAdjustment);
+      }
+    }
+
+    updateVisualLineInfo();
+  }
+
+  private boolean needToShiftWhiteSpaces(final DocumentEvent e) {
+    if (!CharArrayUtil.containsOnlyWhiteSpaces(e.getNewFragment()) || CharArrayUtil.containLineBreaks(e.getNewFragment()))
+      return e.getOldLength() > 0;
+    if (e.getOffset() == 0) return false;
+    final char charBefore = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() - 1);
+    //final char charAfter = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() + e.getNewLength());
+    return Character.isWhitespace(charBefore)/* || !Character.isWhitespace(charAfter)*/;
+  }
+
+  private void setCurrentLogicalCaret(@NotNull LogicalPosition position) {
+    myLogicalCaret = position;
+    myCaretInfo = createVerticalInfo(position);
+  }
+
+  int getWordAtCaretStart() {
+    Document document = myEditor.getDocument();
+    int offset = getOffset();
+    if (offset == 0) return 0;
+    int lineNumber = getLogicalPosition().line;
+    CharSequence text = document.getCharsSequence();
+    int newOffset = offset - 1;
+    int minOffset = lineNumber > 0 ? document.getLineEndOffset(lineNumber - 1) : 0;
+    boolean camel = myEditor.getSettings().isCamelWords();
+    for (; newOffset > minOffset; newOffset--) {
+      if (EditorActionUtil.isWordStart(text, newOffset, camel)) break;
+    }
+
+    return newOffset;
+  }
+
+  int getWordAtCaretEnd() {
+    Document document = myEditor.getDocument();
+    int offset = getOffset();
+
+    CharSequence text = document.getCharsSequence();
+    if (offset >= document.getTextLength() - 1 || document.getLineCount() == 0) return offset;
+
+    int newOffset = offset + 1;
+
+    int lineNumber = getLogicalPosition().line;
+    int maxOffset = document.getLineEndOffset(lineNumber);
+    if (newOffset > maxOffset) {
+      if (lineNumber + 1 >= document.getLineCount()) return offset;
+      maxOffset = document.getLineEndOffset(lineNumber + 1);
+    }
+    boolean camel = myEditor.getSettings().isCamelWords();
+    for (; newOffset < maxOffset; newOffset++) {
+      if (EditorActionUtil.isWordEnd(text, newOffset, camel)) break;
+    }
+
+    return newOffset;
+  }
+
+  CaretImpl cloneWithoutSelection() {
+    CaretImpl clone = new CaretImpl(myEditor);
+    clone.myLogicalCaret = this.myLogicalCaret;
+    clone.myCaretInfo = this.myCaretInfo;
+    clone.myVisibleCaret = this.myVisibleCaret;
+    clone.myOffset = this.myOffset;
+    clone.myVirtualSpaceOffset = this.myVirtualSpaceOffset;
+    clone.myVisualLineStart = this.myVisualLineStart;
+    clone.myVisualLineEnd = this.myVisualLineEnd;
+    clone.savedBeforeBulkCaretMarker = this.savedBeforeBulkCaretMarker;
+    clone.mySkipChangeRequests = this.mySkipChangeRequests;
+    clone.myLastColumnNumber = this.myLastColumnNumber;
+    clone.myReportCaretMoves = this.myReportCaretMoves;
+    clone.myDesiredX = this.myDesiredX;
+    return clone;
+  }
+
+  @Nullable
+  @Override
+  public Caret clone(boolean above) {
+    assertIsDispatchThread();
+    int lineShift = above ? -1 : 1;
+    final CaretImpl clone = cloneWithoutSelection();
+    final int newSelectionStartOffset, newSelectionEndOffset;
+    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;
+    }
+    else {
+      newSelectionStartOffset = 0;
+      newSelectionEndOffset = 0;
+      hasNewSelection = false;
+    }
+    LogicalPosition oldPosition = hasSelection() && !hasNewSelection ? myEditor.offsetToLogicalPosition(getSelectionStart()) : getLogicalPosition();
+    int newLine = oldPosition.line + lineShift;
+    if (newLine < 0 || newLine >= myEditor.getDocument().getLineCount()) {
+      Disposer.dispose(clone);
+      return null;
+    }
+    clone.moveToLogicalPosition(new LogicalPosition(newLine, oldPosition.column), false, null, false);
+    if (myEditor.getCaretModel().addCaret(clone)) {
+      if (hasSelection() && hasNewSelection) {
+        myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+          @Override
+          public void run() {
+            clone.setSelection(Math.min(newSelectionStartOffset, newSelectionEndOffset), Math.max(newSelectionStartOffset, newSelectionEndOffset));
+          }
+        });
+        if (!clone.isValid()) {
+          return null;
+        }
+      }
+      myEditor.getScrollingModel().scrollTo(clone.getLogicalPosition(), ScrollType.RELATIVE);
+      return clone;
+    }
+    else {
+      Disposer.dispose(clone);
+      return null;
+    }
+  }
+
+  private int getTruncatedOffset(LogicalPosition position) {
+    if (position.line < 0) {
+      return 0;
+    }
+    else if (position.line >= myEditor.getDocument().getLineCount()) {
+      return myEditor.getDocument().getTextLength();
+    }
+    else {
+      return myEditor.logicalPositionToOffset(position);
+    }
+  }
+
+  /**
+   * @return  information on whether current selection's direction in known
+   * @see #setUnknownDirection(boolean)
+   */
+  public boolean isUnknownDirection() {
+    return myUnknownDirection;
+  }
+
+  /**
+   * There is a possible case that we don't know selection's direction. For example, a user might triple-click editor (select the
+   * whole line). We can't say what selection end is a {@link #getLeadSelectionOffset() leading end} then. However, that matters
+   * in a situation when a user clicks before or after that line holding Shift key. It's expected that the selection is expanded
+   * up to that point than.
+   * <p/>
+   * That's why we allow to specify that the direction is unknown and {@link #isUnknownDirection() expose this information}
+   * later.
+   * <p/>
+   * <b>Note:</b> when this method is called with <code>'true'</code>, subsequent calls are guaranteed to return <code>'true'</code>
+   * until selection is changed. 'Unknown direction' flag is automatically reset then.
+   *
+   * @param unknownDirection
+   */
+  public void setUnknownDirection(boolean unknownDirection) {
+    myUnknownDirection = unknownDirection;
+  }
+
+  @Override
+  public int getSelectionStart() {
+    validateContext(false);
+    if (hasSelection()) {
+      MyRangeMarker marker = mySelectionMarker;
+      if (marker != null) {
+        return marker.getStartOffset();
+      }
+    }
+    return getOffset();
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getSelectionStartPosition() {
+    VisualPosition defaultPosition = myEditor.offsetToVisualPosition(getSelectionStart());
+    if (!hasSelection()) {
+      return defaultPosition;
+    }
+
+    MyRangeMarker marker = mySelectionMarker;
+    if (marker == null) {
+      return defaultPosition;
+    }
+
+    VisualPosition result = marker.getStartPosition();
+    return result == null ? defaultPosition : result;
+  }
+
+  @Override
+  public int getSelectionEnd() {
+    validateContext(false);
+    if (hasSelection()) {
+      MyRangeMarker marker = mySelectionMarker;
+      if (marker != null) {
+        return marker.getEndOffset();
+      }
+    }
+    return getOffset();
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getSelectionEndPosition() {
+    VisualPosition defaultPosition = myEditor.offsetToVisualPosition(getSelectionEnd());
+    if (!hasSelection()) {
+      return defaultPosition;
+    }
+
+    MyRangeMarker marker = mySelectionMarker;
+    if (marker == null) {
+      return defaultPosition;
+    }
+
+    VisualPosition result = marker.getEndPosition();
+    return result == null ? defaultPosition : result;
+  }
+
+  @Override
+  public boolean hasSelection() {
+    validateContext(false);
+    MyRangeMarker marker = mySelectionMarker;
+    return marker != null && marker.isValid() && marker.getEndOffset() > marker.getStartOffset();
+  }
+
+  @Override
+  public void setSelection(int startOffset, int endOffset) {
+    doSetSelection(myEditor.offsetToVisualPosition(startOffset), startOffset, myEditor.offsetToVisualPosition(endOffset), endOffset, false);
+  }
+
+  @Override
+  public void setSelection(int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
+    VisualPosition startPosition;
+    if (hasSelection()) {
+      startPosition = getLeadSelectionPosition();
+    }
+    else {
+      startPosition = myEditor.offsetToVisualPosition(startOffset);
+    }
+    setSelection(startPosition, startOffset, endPosition, endOffset);
+  }
+
+  @Override
+  public void setSelection(@Nullable VisualPosition startPosition, int startOffset, @Nullable VisualPosition endPosition, int endOffset) {
+    VisualPosition startPositionToUse = startPosition == null ? myEditor.offsetToVisualPosition(startOffset) : startPosition;
+    VisualPosition endPositionToUse = endPosition == null ? myEditor.offsetToVisualPosition(endOffset) : endPosition;
+    doSetSelection(startPositionToUse, startOffset, endPositionToUse, endOffset, true);
+  }
+
+  private void doSetSelection(@NotNull final VisualPosition _startPosition,
+                              final int _startOffset,
+                              @NotNull final VisualPosition _endPosition,
+                              final int _endOffset,
+                              final boolean visualPositionAware)
+  {
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        VisualPosition startPosition = _startPosition;
+        int startOffset = _startOffset;
+        VisualPosition endPosition = _endPosition;
+        int endOffset = _endOffset;
+        myUnknownDirection = false;
+        final Document doc = myEditor.getDocument();
+        final Pair<String, String> markers = myEditor.getUserData(EditorImpl.EDITABLE_AREA_MARKER);
+        if (markers != null) {
+          final String text = doc.getText();
+          final int start = text.indexOf(markers.first) + markers.first.length();
+          final int end = text.indexOf(markers.second);
+          if (startOffset < endOffset) {
+            if (startOffset < start) {
+              startOffset = start;
+              startPosition = myEditor.offsetToVisualPosition(startOffset);
+            }
+            if (endOffset > end) {
+              endOffset = end;
+              endPosition = myEditor.offsetToVisualPosition(endOffset);
+            }
+          }
+          else {
+            if (endOffset < start) {
+              endOffset = start;
+              endPosition = myEditor.offsetToVisualPosition(startOffset);
+            }
+            if (startOffset > end) {
+              startOffset = end;
+              startPosition = myEditor.offsetToVisualPosition(endOffset);
+            }
+          }
+        }
+
+        validateContext(true);
+
+        myEditor.getSelectionModel().removeBlockSelection();
+
+        int textLength = doc.getTextLength();
+        if (startOffset < 0 || startOffset > textLength) {
+          LOG.error("Wrong startOffset: " + startOffset + ", textLength=" + textLength);
+        }
+        if (endOffset < 0 || endOffset > textLength) {
+          LOG.error("Wrong endOffset: " + endOffset + ", textLength=" + textLength);
+        }
+
+        if (!visualPositionAware && startOffset == endOffset) {
+          removeSelection();
+          return;
+        }
+
+    /* Normalize selection */
+        if (startOffset > endOffset) {
+          int tmp = startOffset;
+          startOffset = endOffset;
+          endOffset = tmp;
+        }
+
+        FoldingModelEx foldingModel = myEditor.getFoldingModel();
+        FoldRegion startFold = foldingModel.getCollapsedRegionAtOffset(startOffset);
+        if (startFold != null && startFold.getStartOffset() < startOffset) {
+          startOffset = startFold.getStartOffset();
+        }
+
+        FoldRegion endFold = foldingModel.getCollapsedRegionAtOffset(endOffset);
+        if (endFold != null && endFold.getStartOffset() < endOffset) {
+          // All visual positions that lay at collapsed fold region placeholder are mapped to the same offset. Hence, there are
+          // at least two distinct situations - selection end is located inside collapsed fold region placeholder and just before it.
+          // We want to expand selection to the fold region end at the former case and keep selection as-is at the latest one.
+          endOffset = endFold.getEndOffset();
+        }
+
+        int oldSelectionStart;
+        int oldSelectionEnd;
+
+        if (hasSelection()) {
+          oldSelectionStart = getSelectionStart();
+          oldSelectionEnd = getSelectionEnd();
+          if (oldSelectionStart == startOffset && oldSelectionEnd == endOffset && !visualPositionAware) return;
+        }
+        else {
+          oldSelectionStart = oldSelectionEnd = getOffset();
+        }
+
+        MyRangeMarker marker = mySelectionMarker;
+        if (marker != null) {
+          marker.release();
+        }
+
+        marker = new MyRangeMarker((DocumentEx)doc, startOffset, endOffset);
+        if (visualPositionAware) {
+          if (endPosition.after(startPosition)) {
+            marker.setStartPosition(startPosition);
+            marker.setEndPosition(endPosition);
+            marker.setEndPositionIsLead(false);
+          }
+          else {
+            marker.setStartPosition(endPosition);
+            marker.setEndPosition(startPosition);
+            marker.setEndPositionIsLead(true);
+          }
+        }
+        mySelectionMarker = marker;
+
+        myEditor.getSelectionModel().fireSelectionChanged(oldSelectionStart, oldSelectionEnd, startOffset, endOffset);
+
+        updateSystemSelection();
+      }
+    });
+  }
+
+  private void updateSystemSelection() {
+    if (GraphicsEnvironment.isHeadless()) return;
+
+    final Clipboard clip = myEditor.getComponent().getToolkit().getSystemSelection();
+    if (clip != null) {
+      clip.setContents(new StringSelection(myEditor.getSelectionModel().getSelectedText(true)), EmptyClipboardOwner.INSTANCE);
+    }
+  }
+
+  @Override
+  public void removeSelection() {
+    if (myEditor.isStickySelection()) {
+      // Most of our 'change caret position' actions (like move caret to word start/end etc) remove active selection.
+      // However, we don't want to do that for 'sticky selection'.
+      return;
+    }
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        validateContext(true);
+        myEditor.getSelectionModel().removeBlockSelection();
+        int caretOffset = getOffset();
+        MyRangeMarker marker = mySelectionMarker;
+        if (marker != null) {
+          int startOffset = marker.getStartOffset();
+          int endOffset = marker.getEndOffset();
+          marker.release();
+          mySelectionMarker = null;
+          myEditor.getSelectionModel().fireSelectionChanged(startOffset, endOffset, caretOffset, caretOffset);
+        }
+      }
+    });
+  }
+
+  @Override
+  public int getLeadSelectionOffset() {
+    validateContext(false);
+    int caretOffset = getOffset();
+    if (hasSelection()) {
+      MyRangeMarker marker = mySelectionMarker;
+      if (marker != null) {
+        int startOffset = marker.getStartOffset();
+        int endOffset = marker.getEndOffset();
+        if (caretOffset != startOffset && caretOffset != endOffset) {
+          // Try to check if current selection is tweaked by fold region.
+          FoldingModelEx foldingModel = myEditor.getFoldingModel();
+          FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(caretOffset);
+          if (foldRegion != null) {
+            if (foldRegion.getStartOffset() == startOffset) {
+              return endOffset;
+            }
+            else if (foldRegion.getEndOffset() == endOffset) {
+              return startOffset;
+            }
+          }
+        }
+
+        if (caretOffset == endOffset) {
+          return startOffset;
+        }
+        else {
+          return endOffset;
+        }
+      }
+    }
+    return caretOffset;
+  }
+
+  @NotNull
+  @Override
+  public VisualPosition getLeadSelectionPosition() {
+    MyRangeMarker marker = mySelectionMarker;
+    VisualPosition caretPosition = getVisualPosition();
+    if (marker == null) {
+      return caretPosition;
+    }
+
+    if (marker.isEndPositionIsLead()) {
+      VisualPosition result = marker.getEndPosition();
+      return result == null ? getSelectionEndPosition() : result;
+    }
+    else {
+      VisualPosition result = marker.getStartPosition();
+      return result == null ? getSelectionStartPosition() : result;
+    }
+  }
+
+  @Override
+  public void selectLineAtCaret() {
+    validateContext(true);
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        SelectionModelImpl.doSelectLineAtCaret(myEditor);
+      }
+    });
+  }
+
+  @Override
+  public void selectWordAtCaret(final boolean honorCamelWordsSettings) {
+    validateContext(true);
+    myEditor.getCaretModel().doWithCaretMerging(new Runnable() {
+      public void run() {
+        removeSelection();
+        final EditorSettings settings = myEditor.getSettings();
+        boolean camelTemp = settings.isCamelWords();
+
+        final boolean needOverrideSetting = camelTemp && !honorCamelWordsSettings;
+        if (needOverrideSetting) {
+          settings.setCamelWords(false);
+        }
+
+        try {
+          EditorActionHandler handler = EditorActionManager.getInstance().getActionHandler(
+            IdeActions.ACTION_EDITOR_SELECT_WORD_AT_CARET);
+          handler.execute(myEditor, myEditor.getDataContext());
+        }
+        finally {
+          if (needOverrideSetting) {
+            settings.resetCamelWords();
+          }
+        }
+      }
+    });
+  }
+
+  @Nullable
+  @Override
+  public String getSelectedText() {
+    if (!hasSelection()) {
+      return null;
+    }
+    CharSequence text = myEditor.getDocument().getCharsSequence();
+    int selectionStart = getSelectionStart();
+    int selectionEnd = getSelectionEnd();
+    return text.subSequence(selectionStart, selectionEnd).toString();
+  }
+
+  private void validateContext(boolean isWrite) {
+    if (!myEditor.getComponent().isShowing()) return;
+    if (isWrite) {
+      ApplicationManager.getApplication().assertIsDispatchThread();
+    }
+    else {
+      ApplicationManager.getApplication().assertReadAccessAllowed();
+    }
+  }
+
+  /**
+   * Encapsulates information about target vertical range info - its <code>'y'</code> coordinate and height in pixels.
+   */
+  public static class VerticalInfo {
+    public final int y;
+    public final int height;
+
+    private VerticalInfo(int y, int height) {
+      this.y = y;
+      this.height = height;
+    }
+  }
+
+  private class MyRangeMarker extends RangeMarkerImpl {
+    private VisualPosition myStartPosition;
+    private VisualPosition myEndPosition;
+    private boolean myEndPositionIsLead;
+    private boolean myIsReleased;
+
+    MyRangeMarker(DocumentEx document, int start, int end) {
+      super(document, start, end, true);
+      myIsReleased = false;
+    }
+
+    public void release() {
+      myIsReleased = true;
+      dispose();
+    }
+
+    @Nullable
+    public VisualPosition getStartPosition() {
+      invalidateVisualPositions();
+      return myStartPosition;
+    }
+
+    public void setStartPosition(@NotNull VisualPosition startPosition) {
+      myStartPosition = startPosition;
+    }
+
+    @Nullable
+    public VisualPosition getEndPosition() {
+      invalidateVisualPositions();
+      return myEndPosition;
+    }
+
+    public void setEndPosition(@NotNull VisualPosition endPosition) {
+      myEndPosition = endPosition;
+    }
+
+    public boolean isEndPositionIsLead() {
+      return myEndPositionIsLead;
+    }
+
+    public void setEndPositionIsLead(boolean endPositionIsLead) {
+      myEndPositionIsLead = endPositionIsLead;
+    }
+
+    int startBefore;
+    int endBefore;
+
+    @Override
+    protected void changedUpdateImpl(DocumentEvent e) {
+      if (myIsReleased) return;
+      startBefore = getStartOffset();
+      endBefore = getEndOffset();
+      super.changedUpdateImpl(e);
+    }
+
+    private void invalidateVisualPositions() {
+      SoftWrapModelImpl model = myEditor.getSoftWrapModel();
+      if (!myEditor.offsetToVisualPosition(getStartOffset()).equals(myStartPosition) && model.getSoftWrap(getStartOffset()) == null
+          || !myEditor.offsetToVisualPosition(getEndOffset()).equals(myEndPosition) && model.getSoftWrap(getEndOffset()) == null) {
+        myStartPosition = null;
+        myEndPosition = null;
+      }
+    }
+  }
+}
index de09f5eb5f848e46ff275a20ffb905bdd6dbb167..c93d7d6f0341d561e3a6e323fa4301b77f13d058 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright 2000-2012 JetBrains s.r.o.
+ * 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.
  */
 package com.intellij.openapi.editor.impl;
 
-import com.intellij.diagnostic.LogMessageEx;
 import com.intellij.openapi.Disposable;
 import com.intellij.openapi.application.ApplicationManager;
-import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.*;
-import com.intellij.openapi.editor.actions.EditorActionUtil;
 import com.intellij.openapi.editor.colors.EditorColors;
 import com.intellij.openapi.editor.event.CaretEvent;
 import com.intellij.openapi.editor.event.CaretListener;
 import com.intellij.openapi.editor.event.DocumentEvent;
+import com.intellij.openapi.editor.event.MultipleCaretListener;
 import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener;
-import com.intellij.openapi.editor.ex.DocumentEx;
-import com.intellij.openapi.editor.ex.EditorGutterComponentEx;
 import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
-import com.intellij.openapi.editor.ex.util.EditorUtil;
 import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
-import com.intellij.openapi.editor.impl.softwrap.SoftWrapHelper;
 import com.intellij.openapi.editor.markup.TextAttributes;
-import com.intellij.openapi.ide.CopyPasteManager;
-import com.intellij.openapi.util.text.StringUtil;
+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 com.intellij.util.diff.FilesTooBigForDiffException;
-import com.intellij.util.text.CharArrayUtil;
-import org.jetbrains.annotations.NonNls;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.awt.*;
-import java.util.List;
+import java.util.*;
 
 public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, Disposable {
-  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.impl.CaretModelImpl");
-
   private final EditorImpl myEditor;
-  private final EventDispatcher<CaretListener> myCaretListeners = EventDispatcher.create(CaretListener.class);
-  private LogicalPosition myLogicalCaret;
-  private VerticalInfo myCaretInfo;
-  private VisualPosition myVisibleCaret;
-  private int myOffset;
-  private int myVisualLineStart;
-  private int myVisualLineEnd;
+  
+  private final EventDispatcher<MultipleCaretListener> myCaretListeners = EventDispatcher.create(MultipleCaretListener.class);
+  private final Map<CaretListener, MultipleCaretListener> myListenerMap = new HashMap<CaretListener, MultipleCaretListener>();
+  private final boolean mySupportsMultipleCarets = Registry.is("editor.allow.multiple.carets");
+
   private TextAttributes myTextAttributes;
-  private boolean myIsInUpdate;
-  private boolean isDocumentChanged;
-  private RangeMarker savedBeforeBulkCaretMarker;
-  private boolean myIgnoreWrongMoves = false;
-  private boolean mySkipChangeRequests;
-
-  /**
-   * We check that caret is located at the target offset at the end of {@link #moveToOffset(int, boolean)} method. However,
-   * it's possible that the following situation occurs:
-   * <p/>
-   * <pre>
-   * <ol>
-   *   <li>Some client subscribes to caret change events;</li>
-   *   <li>{@link #moveToLogicalPosition(LogicalPosition)} is called;</li>
-   *   <li>Caret position is changed during {@link #moveToLogicalPosition(LogicalPosition)} processing;</li>
-   *   <li>The client receives caret position change event and adjusts the position;</li>
-   *   <li>{@link #moveToLogicalPosition(LogicalPosition)} processing is finished;</li>
-   *   <li>{@link #moveToLogicalPosition(LogicalPosition)} reports an error because the caret is not located at the target offset;</li>
-   * </ol>
-   * </pre>
-   * <p/>
-   * This field serves as a flag that reports unexpected caret position change requests nested from {@link #moveToOffset(int, boolean)}.
-   */
-  private boolean myReportCaretMoves;
-
-  /**
-   * There is a possible case that user defined non-monospaced font for editor. That means that various symbols have different
-   * visual widths. That means that if we move caret vertically it may deviate to the left/right. However, we can try to preserve
-   * its initial visual position when possible.
-   * <p/>
-   * This field holds desired value for visual <code>'x'</code> caret coordinate (negative value if no coordinate should be preserved).
-   */
-  private int myDesiredX = -1;
+
+  boolean myIgnoreWrongMoves = false;
+  boolean myIsInUpdate;
+  boolean isDocumentChanged;
+
+  private final LinkedList<CaretImpl> myCarets = new LinkedList<CaretImpl>();
+  private CaretImpl myCurrentCaret; // active caret in the context of 'runForEachCaret' call
+  private boolean myPerformCaretMergingAfterCurrentOperation;
 
   public CaretModelImpl(EditorImpl editor) {
     myEditor = editor;
-    myLogicalCaret = new LogicalPosition(0, 0);
-    myVisibleCaret = new VisualPosition(0, 0);
-    myCaretInfo = new VerticalInfo(0, 0);
-    myOffset = 0;
-    myVisualLineStart = 0;
-    Document doc = editor.getDocument();
-    myVisualLineEnd = doc.getLineCount() > 1 ? doc.getLineStartOffset(1) : doc.getLineCount() == 0 ? 0 : doc.getLineEndOffset(0);
+    myCarets.add(new CaretImpl(myEditor));
+
     DocumentBulkUpdateListener bulkUpdateListener = new DocumentBulkUpdateListener() {
       @Override
       public void updateStarted(@NotNull Document doc) {
-        if (doc != myEditor.getDocument() || myOffset > doc.getTextLength() || savedBeforeBulkCaretMarker != null) return;
-        savedBeforeBulkCaretMarker = doc.createRangeMarker(myOffset, myOffset);
+        for (CaretImpl caret : myCarets) {
+          caret.onBulkDocumentUpdateStarted(doc);
+        }
       }
 
       @Override
-      public void updateFinished(@NotNull Document doc) {
-        if (doc != myEditor.getDocument() || myIsInUpdate) return;
-        LOG.assertTrue(!myReportCaretMoves);
-
-        if (savedBeforeBulkCaretMarker != null) {
-          if(savedBeforeBulkCaretMarker.isValid()) {
-            if(savedBeforeBulkCaretMarker.getStartOffset() != myOffset) {
-              moveToOffset(savedBeforeBulkCaretMarker.getStartOffset());
+      public void updateFinished(@NotNull final Document doc) {
+        doWithCaretMerging(new Runnable() {
+          @Override
+          public void run() {
+            for (CaretImpl caret : myCarets) {
+              caret.onBulkDocumentUpdateFinished(doc);
             }
-          } else if (myOffset > doc.getTextLength()) {
-            moveToOffset(doc.getTextLength());
           }
-          releaseBulkCaretMarker();
-        }
+        });
       }
     };
     ApplicationManager.getApplication().getMessageBus().connect(this).subscribe(DocumentBulkUpdateListener.TOPIC, bulkUpdateListener);
   }
 
-  private void releaseBulkCaretMarker() {
-    if (savedBeforeBulkCaretMarker != null) {
-      savedBeforeBulkCaretMarker.dispose();
-      savedBeforeBulkCaretMarker = null;
-    }
-  }
-
   @Override
-  public void moveToVisualPosition(@NotNull VisualPosition pos) {
-    assertIsDispatchThread();
-    validateCallContext();
-    if (mySkipChangeRequests) {
-      return;
-    }
-    if (myReportCaretMoves) {
-      LogMessageEx.error(LOG, "Unexpected caret move request");
+  public void documentChanged(final DocumentEvent e) {
+    isDocumentChanged = true;
+    try {
+      myIsInUpdate = false;
+      doWithCaretMerging(new Runnable() {
+        @Override
+        public void run() {
+          for (CaretImpl caret : myCarets) {
+            caret.updateCaretPosition((DocumentEventImpl)e);
+          }
+        }
+      });
     }
-    if (!myEditor.isStickySelection() && !isDocumentChanged && !pos.equals(myVisibleCaret)) {
-      CopyPasteManager.getInstance().stopKillRings();
+    finally {
+      isDocumentChanged = false;
     }
+  }
 
-    myDesiredX = -1;
-    int column = pos.column;
-    int line = pos.line;
-
-    if (column < 0) column = 0;
-
-    if (line < 0) line = 0;
+  @Override
+  public void beforeDocumentChange(DocumentEvent e) {
+    myIsInUpdate = true;
+  }
 
-    int lastLine = myEditor.getVisibleLineCount() - 1;
-    if (lastLine <= 0) {
-      lastLine = 0;
-    }
+  @Override
+  public int getPriority() {
+    return EditorDocumentPriorities.CARET_MODEL;
+  }
 
-    if (line > lastLine) {
-      line = lastLine;
+  @Override
+  public void dispose() {
+    for (CaretImpl caret : myCarets) {
+      Disposer.dispose(caret);
     }
+  }
 
-    EditorSettings editorSettings = myEditor.getSettings();
-
-    if (!editorSettings.isVirtualSpace() && line <= lastLine) {
-      int lineEndColumn = EditorUtil.getLastVisualLineColumnNumber(myEditor, line);
-      if (column > lineEndColumn) {
-        column = lineEndColumn;
-      }
+  public void setIgnoreWrongMoves(boolean ignoreWrongMoves) {
+    myIgnoreWrongMoves = ignoreWrongMoves;
+  }
 
-      if (column < 0 && line > 0) {
-        line--;
-        column = EditorUtil.getLastVisualLineColumnNumber(myEditor, line);
-      }
+  public void updateVisualPosition() {
+    for (CaretImpl caret : myCarets) {
+      caret.updateVisualPosition();
     }
+  }
 
-    myVisibleCaret = new VisualPosition(line, column);
-
-    VerticalInfo oldInfo = myCaretInfo;
-    LogicalPosition oldPosition = myLogicalCaret;
-
-    setCurrentLogicalCaret(myEditor.visualToLogicalPosition(myVisibleCaret));
-    myOffset = myEditor.logicalPositionToOffset(myLogicalCaret);
-    LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength());
-
-    updateVisualLineInfo();
-
-    myEditor.getFoldingModel().flushCaretPosition();
-
-    myEditor.setLastColumnNumber(myVisibleCaret.column);
-    myEditor.updateCaretCursor();
-    requestRepaint(oldInfo);
+  @Override
+  public void moveCaretRelatively(final int columnShift, final int lineShift, final boolean withSelection, final boolean blockSelection, final boolean scrollToCaret) {
+    getCurrentCaret().moveCaretRelatively(columnShift, lineShift, withSelection, blockSelection, scrollToCaret);
+  }
 
-    if (!oldPosition.equals(myLogicalCaret)) {
-      CaretEvent event = new CaretEvent(myEditor, oldPosition, myLogicalCaret);
-      myCaretListeners.getMulticaster().caretPositionChanged(event);
-    }
+  @Override
+  public void moveToLogicalPosition(@NotNull LogicalPosition pos) {
+    getCurrentCaret().moveToLogicalPosition(pos);
   }
 
-  private void assertIsDispatchThread() {
-    myEditor.assertIsDispatchThread();
+  @Override
+  public void moveToVisualPosition(@NotNull VisualPosition pos) {
+    getCurrentCaret().moveToVisualPosition(pos);
   }
 
   @Override
   public void moveToOffset(int offset) {
-    moveToOffset(offset, false);
+    getCurrentCaret().moveToOffset(offset);
   }
 
   @Override
   public void moveToOffset(int offset, boolean locateBeforeSoftWrap) {
-    assertIsDispatchThread();
-    validateCallContext();
-    if (mySkipChangeRequests) {
-      return;
-    }
-    final LogicalPosition logicalPosition = myEditor.offsetToLogicalPosition(offset);
-    CaretEvent event = moveToLogicalPosition(logicalPosition, locateBeforeSoftWrap, null, false);
-    final LogicalPosition positionByOffsetAfterMove = myEditor.offsetToLogicalPosition(myOffset);
-    if (!myIgnoreWrongMoves && !positionByOffsetAfterMove.equals(logicalPosition)) {
-      StringBuilder debugBuffer = new StringBuilder();
-      moveToLogicalPosition(logicalPosition, locateBeforeSoftWrap, debugBuffer, true);
-      int textStart = Math.max(0, Math.min(offset, myOffset) - 1);
-      final DocumentEx document = myEditor.getDocument();
-      int textEnd = Math.min(document.getTextLength() - 1, Math.max(offset, myOffset) + 1);
-      CharSequence text = document.getCharsSequence().subSequence(textStart, textEnd);
-      StringBuilder positionToOffsetTrace = new StringBuilder();
-      int inverseOffset = myEditor.logicalPositionToOffset(logicalPosition, positionToOffsetTrace);
-      LogMessageEx.error(
-        LOG, "caret moved to wrong offset. Please submit a dedicated ticket and attach current editor's text to it.",
-        String.format(
-          "Requested: offset=%d, logical position='%s' but actual: offset=%d, logical position='%s' (%s). %s%n"
-          + "interested text [%d;%d): '%s'%n debug trace: %s%nLogical position -> offset ('%s'->'%d') trace: %s",
-          offset, logicalPosition, myOffset, myLogicalCaret, positionByOffsetAfterMove, myEditor.dumpState(),
-          textStart, textEnd, text, debugBuffer, logicalPosition, inverseOffset, positionToOffsetTrace
-        ));
-    }
-    if (event != null) {
-      myCaretListeners.getMulticaster().caretPositionChanged(event);
-      EditorActionUtil.selectNonexpandableFold(myEditor);
-    }
+    getCurrentCaret().moveToOffset(offset, locateBeforeSoftWrap);
   }
 
-  public void setIgnoreWrongMoves(boolean ignoreWrongMoves) {
-    myIgnoreWrongMoves = ignoreWrongMoves;
+  @Override
+  public boolean isUpToDate() {
+    return getCurrentCaret().isUpToDate();
   }
 
+  @NotNull
   @Override
-  public void moveCaretRelatively(int columnShift,
-                                  int lineShift,
-                                  boolean withSelection,
-                                  boolean blockSelection,
-                                  boolean scrollToCaret) {
-    assertIsDispatchThread();
-    if (mySkipChangeRequests) {
-      return;
-    }
-    if (myReportCaretMoves) {
-      LogMessageEx.error(LOG, "Unexpected caret move request");
-    }
-    if (!myEditor.isStickySelection() && !isDocumentChanged) {
-      CopyPasteManager.getInstance().stopKillRings();
-    }
-    SelectionModelImpl selectionModel = myEditor.getSelectionModel();
-    final int leadSelectionOffset = selectionModel.getLeadSelectionOffset();
-    LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection()
-                                          ? selectionModel.getBlockStart()
-                                          : getLogicalPosition();
-    EditorSettings editorSettings = myEditor.getSettings();
-    VisualPosition visualCaret = getVisualPosition();
-
-    int desiredX = myDesiredX;
-    if (columnShift == 0) {
-      if (myDesiredX < 0) {
-        desiredX = myEditor.visualPositionToXY(visualCaret).x;
-      }
-    }
-    else {
-      myDesiredX = desiredX = -1;
-    }
-
-    int newLineNumber = visualCaret.line + lineShift;
-    int newColumnNumber = visualCaret.column + columnShift;
-    if (desiredX >= 0 && !ApplicationManager.getApplication().isUnitTestMode()) {
-      newColumnNumber = myEditor.xyToVisualPosition(new Point(desiredX, Math.max(0, newLineNumber) * myEditor.getLineHeight())).column;
-    }
-
-    Document document = myEditor.getDocument();
-    if (!editorSettings.isVirtualSpace() && columnShift == 0 && getLogicalPosition().softWrapLinesOnCurrentLogicalLine <= 0) {
-      newColumnNumber = myEditor.getLastColumnNumber();
-    }
-    else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == 1) {
-      int lastLine = document.getLineCount() - 1;
-      if (lastLine < 0) lastLine = 0;
-      if (EditorModificationUtil.calcAfterLineEnd(myEditor) >= 0 &&
-          newLineNumber < myEditor.logicalToVisualPosition(new LogicalPosition(lastLine, 0)).line) {
-        newColumnNumber = 0;
-        newLineNumber++;
-      }
-    }
-    else if (!editorSettings.isVirtualSpace() && lineShift == 0 && columnShift == -1) {
-      if (newColumnNumber < 0 && newLineNumber > 0) {
-        newLineNumber--;
-        newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber);
-      }
-    }
-
-    if (newColumnNumber < 0) newColumnNumber = 0;
-
-    // There is a possible case that caret is located at the first line and user presses 'Shift+Up'. We want to select all text
-    // from the document start to the current caret position then. So, we have a dedicated flag for tracking that.
-    boolean selectToDocumentStart = false;
-    if (newLineNumber < 0) {
-      selectToDocumentStart = true;
-      newLineNumber = 0;
-
-      // We want to move caret to the first column if it's already located at the first line and 'Up' is pressed.
-      newColumnNumber = 0;
-      desiredX = -1;
-    }
-
-    VisualPosition pos = new VisualPosition(newLineNumber, newColumnNumber);
-    int lastColumnNumber = newColumnNumber;
-    if (!editorSettings.isCaretInsideTabs() && !myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
-      LogicalPosition log = myEditor.visualToLogicalPosition(new VisualPosition(newLineNumber, newColumnNumber));
-      int offset = myEditor.logicalPositionToOffset(log);
-      if (offset >= document.getTextLength()) {
-        int lastOffsetColumn = myEditor.offsetToVisualPosition(document.getTextLength()).column;
-        // We want to move caret to the last column if if it's located at the last line and 'Down' is pressed.
-        newColumnNumber = lastColumnNumber = Math.max(lastOffsetColumn, newColumnNumber);
-        desiredX = -1;
-      }
-      CharSequence text = document.getCharsSequence();
-      if (offset >= 0 && offset < document.getTextLength()) {
-        if (text.charAt(offset) == '\t' && (columnShift <= 0 || offset == myOffset)) {
-          if (columnShift <= 0) {
-            newColumnNumber = myEditor.offsetToVisualPosition(offset).column;
-          }
-          else {
-            SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset + 1);
-            // There is a possible case that tabulation symbol is the last document symbol represented on a visual line before
-            // soft wrap. We can't just use column from 'offset + 1' because it would point on a next visual line.
-            if (softWrap == null) {
-              newColumnNumber = myEditor.offsetToVisualPosition(offset + 1).column;
-            }
-            else {
-              newColumnNumber = EditorUtil.getLastVisualLineColumnNumber(myEditor, newLineNumber);
-            }
-          }
-        }
-      }
-    }
-
-    pos = new VisualPosition(newLineNumber, newColumnNumber);
-    if (columnShift != 0 && lineShift == 0 && myEditor.getSoftWrapModel().isInsideSoftWrap(pos)) {
-      LogicalPosition logical = myEditor.visualToLogicalPosition(pos);
-      int softWrapOffset = myEditor.logicalPositionToOffset(logical);
-      if (columnShift >= 0) {
-        moveToOffset(softWrapOffset);
-      }
-      else {
-        int line = myEditor.offsetToVisualLine(softWrapOffset - 1);
-        moveToVisualPosition(new VisualPosition(line, EditorUtil.getLastVisualLineColumnNumber(myEditor, line)));
-      }
-    }
-    else {
-      moveToVisualPosition(pos);
-      if (!editorSettings.isVirtualSpace() && columnShift == 0) {
-        myEditor.setLastColumnNumber(lastColumnNumber);
-      }
-    }
-
-    if (withSelection) {
-      if (blockSelection) {
-        selectionModel.setBlockSelection(blockSelectionStart, getLogicalPosition());
-      }
-      else {
-        if (selectToDocumentStart) {
-          selectionModel.setSelection(leadSelectionOffset, 0);
-        }
-        else if (pos.line >= myEditor.getVisibleLineCount()) {
-          if (leadSelectionOffset < document.getTextLength()) {
-            selectionModel.setSelection(leadSelectionOffset, document.getTextLength());
-          }
-        }
-        else {
-          int selectionStartToUse = leadSelectionOffset;
-          if (selectionModel.isUnknownDirection()) {
-            if (getOffset() > leadSelectionOffset) {
-              selectionStartToUse = Math.min(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
-            }
-            else {
-              selectionStartToUse = Math.max(selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
-            }
-          }
-          selectionModel.setSelection(selectionStartToUse, getVisualPosition(), getOffset());
-        }
-      }
-    }
-    else {
-      selectionModel.removeSelection();
-    }
-
-    if (scrollToCaret) {
-      myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
-    }
+  public LogicalPosition getLogicalPosition() {
+    return getCurrentCaret().getLogicalPosition();
+  }
 
-    if (desiredX >= 0) {
-      myDesiredX = desiredX;
-    }
+  @NotNull
+  @Override
+  public VisualPosition getVisualPosition() {
+    return getCurrentCaret().getVisualPosition();
+  }
 
-    EditorActionUtil.selectNonexpandableFold(myEditor);
+  @Override
+  public int getOffset() {
+    return getCurrentCaret().getOffset();
   }
 
   @Override
-  public void moveToLogicalPosition(@NotNull LogicalPosition pos) {
-    moveToLogicalPosition(pos, false, null, true);
+  public int getVisualLineStart() {
+    return getCurrentCaret().getVisualLineStart();
   }
 
-  @Nullable
-  private CaretEvent moveToLogicalPosition(@NotNull LogicalPosition pos,
-                                           boolean locateBeforeSoftWrap,
-                                           @Nullable StringBuilder debugBuffer,
-                                           boolean fireListeners) {
-    if (mySkipChangeRequests) {
-      return null;
-    }
-    if (myReportCaretMoves) {
-      LogMessageEx.error(LOG, "Unexpected caret move request");
-    }
-    if (!myEditor.isStickySelection() && !isDocumentChanged && !pos.equals(myLogicalCaret)) {
-      CopyPasteManager.getInstance().stopKillRings();
-    }
+  @Override
+  public int getVisualLineEnd() {
+    return getCurrentCaret().getVisualLineEnd();
+  }
 
-    myReportCaretMoves = true;
-    try {
-      return doMoveToLogicalPosition(pos, locateBeforeSoftWrap, debugBuffer, fireListeners);
-    }
-    finally {
-      myReportCaretMoves = false;
-    }
+  int getWordAtCaretStart() {
+    return getCurrentCaret().getWordAtCaretStart();
   }
 
-  private CaretEvent doMoveToLogicalPosition(@NotNull LogicalPosition pos,
-                                             boolean locateBeforeSoftWrap,
-                                             @NonNls @Nullable StringBuilder debugBuffer,
-                                             boolean fireListeners) {
-    assertIsDispatchThread();
-    if (debugBuffer != null) {
-      debugBuffer.append("Start moveToLogicalPosition(). Locate before soft wrap: " + locateBeforeSoftWrap + ", position: " + pos + "\n");
-    }
-    myDesiredX = -1;
-    validateCallContext();
-    int column = pos.column;
-    int line = pos.line;
-    int softWrapLinesBefore = pos.softWrapLinesBeforeCurrentLogicalLine;
-    int softWrapLinesCurrent = pos.softWrapLinesOnCurrentLogicalLine;
-    int softWrapColumns = pos.softWrapColumnDiff;
-
-    Document doc = myEditor.getDocument();
-
-    if (column < 0) {
-      if (debugBuffer != null) {
-        debugBuffer.append("Resetting target logical column to zero as it is negative (").append(column).append(")\n");
-      }
-      column = 0;
-      softWrapColumns = 0;
-    }
-    if (line < 0) {
-      if (debugBuffer != null) {
-        debugBuffer.append("Resetting target logical line to zero as it is negative (").append(line).append(")\n");
-      }
-      line = 0;
-      softWrapLinesBefore = 0;
-      softWrapLinesCurrent = 0;
-    }
+  int getWordAtCaretEnd() {
+    return getCurrentCaret().getWordAtCaretEnd();
+  }
 
-    int lineCount = doc.getLineCount();
-    if (lineCount == 0) {
-      if (debugBuffer != null) {
-        debugBuffer.append("Resetting target logical line to zero as the document is empty\n");
-      }
-      line = 0;
-    }
-    else if (line > lineCount - 1) {
-      if (debugBuffer != null) {
-        debugBuffer.append("Resetting target logical line (" + line + ") to " + (lineCount - 1) + " as it is greater than total document lines number\n");
-      }
-      line = lineCount - 1;
-      softWrapLinesBefore = 0;
-      softWrapLinesCurrent = 0;
+  @Override
+  public void addCaretListener(@NotNull final CaretListener listener) {
+    MultipleCaretListener newListener;
+    if (listener instanceof MultipleCaretListener) {
+      newListener = (MultipleCaretListener)listener;
     }
+    else {
+      newListener = new MultipleCaretListener() {
+        @Override
+        public void caretAdded(CaretEvent e) {
 
-    EditorSettings editorSettings = myEditor.getSettings();
-
-    if (!editorSettings.isVirtualSpace() && line < lineCount && !myEditor.getSelectionModel().hasBlockSelection()) {
-      int lineEndOffset = doc.getLineEndOffset(line);
-      final LogicalPosition endLinePosition = myEditor.offsetToLogicalPosition(lineEndOffset);
-      int lineEndColumnNumber = endLinePosition.column;
-      if (column > lineEndColumnNumber) {
-        int oldColumn = column;
-        column = lineEndColumnNumber;
-        if (softWrapColumns != 0) {
-          softWrapColumns -= column - lineEndColumnNumber;
         }
-        if (debugBuffer != null) {
-          debugBuffer.append(
-            "Resetting target logical column (" + oldColumn + ") to " + lineEndColumnNumber +
-            " because caret is not allowed to be located after line end (offset: " +lineEndOffset + ", "
-            + "logical position: " + endLinePosition+ "). Current soft wrap columns value: " + softWrapColumns+ "\n");
-        }
-      }
-    }
-
-    myEditor.getFoldingModel().flushCaretPosition();
-
-    VerticalInfo oldInfo = myCaretInfo;
-    LogicalPosition oldCaretPosition = myLogicalCaret;
 
-    LogicalPosition logicalPositionToUse;
-    if (pos.visualPositionAware) {
-      logicalPositionToUse = new LogicalPosition(
-        line, column, softWrapLinesBefore, softWrapLinesCurrent, softWrapColumns, pos.foldedLines, pos.foldingColumnDiff
-      );
-    }
-    else {
-      logicalPositionToUse = new LogicalPosition(line, column);
-    }
-    setCurrentLogicalCaret(logicalPositionToUse);
-    final int offset = myEditor.logicalPositionToOffset(myLogicalCaret);
-    if (debugBuffer != null) {
-      debugBuffer.append("Resulting logical position to use: " + myLogicalCaret+
-                         ". It's mapped to offset " + offset+ "\n");
-    }
+        @Override
+        public void caretRemoved(CaretEvent e) {
 
-    FoldRegion collapsedAt = myEditor.getFoldingModel().getCollapsedRegionAtOffset(offset);
+        }
 
-    if (collapsedAt != null && offset > collapsedAt.getStartOffset()) {
-      if (debugBuffer != null) {
-        debugBuffer.append("Scheduling expansion of fold region ").append(collapsedAt).append("\n");
-      }
-      Runnable runnable = new Runnable() {
         @Override
-        public void run() {
-          FoldRegion[] allCollapsedAt = myEditor.getFoldingModel().fetchCollapsedAt(offset);
-          for (FoldRegion foldRange : allCollapsedAt) {
-            foldRange.setExpanded(true);
-          }
+        public void caretPositionChanged(CaretEvent e) {
+          listener.caretPositionChanged(e);
         }
       };
-
-      mySkipChangeRequests = true;
-      try {
-        myEditor.getFoldingModel().runBatchFoldingOperation(runnable, false);
-      }
-      finally {
-        mySkipChangeRequests = false;
-      }
     }
+    myListenerMap.put(listener, newListener);
+    myCaretListeners.addListener(newListener);
+  }
 
-    myEditor.setLastColumnNumber(myLogicalCaret.column);
-    myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret);
-
-    myOffset = myEditor.logicalPositionToOffset(myLogicalCaret);
-    if (debugBuffer != null) {
-      debugBuffer.append("Storing offset " + myOffset + " (mapped from logical position " + myLogicalCaret + ")\n");
+  @Override
+  public void removeCaretListener(@NotNull CaretListener listener) {
+    MultipleCaretListener newListener = myListenerMap.remove(listener);
+    if (newListener != null) {
+      myCaretListeners.removeListener(newListener);
     }
-    LOG.assertTrue(myOffset >= 0 && myOffset <= myEditor.getDocument().getTextLength());
-
-    updateVisualLineInfo();
-
-    myEditor.updateCaretCursor();
-    requestRepaint(oldInfo);
+  }
 
-    if (locateBeforeSoftWrap && SoftWrapHelper.isCaretAfterSoftWrap(myEditor)) {
-      int lineToUse = myVisibleCaret.line - 1;
-      if (lineToUse >= 0) {
-        final VisualPosition visualPosition = new VisualPosition(lineToUse, EditorUtil.getLastVisualLineColumnNumber(myEditor, lineToUse));
-        if (debugBuffer != null) {
-          debugBuffer.append("Adjusting caret position by moving it before soft wrap. Moving to visual position "+ visualPosition+"\n");
-        }
-        final LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(visualPosition);
-        final int tmpOffset = myEditor.logicalPositionToOffset(logicalPosition);
-        if (tmpOffset == myOffset) {
-          boolean restore = myReportCaretMoves;
-          myReportCaretMoves = false;
-          try {
-            moveToVisualPosition(visualPosition);
-            return null;
-          }
-          finally {
-            myReportCaretMoves = restore;
-          }
-        }
-        else {
-          LogMessageEx.error(LOG, "Invalid editor dimension mapping", String.format(
-            "Expected to map visual position '%s' to offset %d but got the following: -> logical position '%s'; -> offset %d. "
-            + "State: %s", visualPosition, myOffset, logicalPosition, tmpOffset, myEditor.dumpState()
-          ));
-        }
-      }
+  @Override
+  public TextAttributes getTextAttributes() {
+    if (myTextAttributes == null) {
+      myTextAttributes = new TextAttributes();
+      myTextAttributes.setBackgroundColor(myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR));
     }
 
-    if (!oldCaretPosition.toVisualPosition().equals(myLogicalCaret.toVisualPosition())) {
-      CaretEvent event = new CaretEvent(myEditor, oldCaretPosition, myLogicalCaret);
-      if (fireListeners) {
-        myCaretListeners.getMulticaster().caretPositionChanged(event);
-      }
-      else {
-        return event;
-      }
-    }
-    return null;
+    return myTextAttributes;
   }
 
-  private void requestRepaint(VerticalInfo oldCaretInfo) {
-    int lineHeight = myEditor.getLineHeight();
-    Rectangle visibleArea = myEditor.getScrollingModel().getVisibleArea();
-    final EditorGutterComponentEx gutter = myEditor.getGutterComponentEx();
-    final EditorComponentImpl content = myEditor.getContentComponent();
-
-    int updateWidth = myEditor.getScrollPane().getHorizontalScrollBar().getValue() + visibleArea.width;
-    if (Math.abs(myCaretInfo.y - oldCaretInfo.y) <= 2 * lineHeight) {
-      int minY = Math.min(oldCaretInfo.y, myCaretInfo.y);
-      int maxY = Math.max(oldCaretInfo.y + oldCaretInfo.height, myCaretInfo.y + myCaretInfo.height);
-      content.repaintEditorComponent(0, minY, updateWidth, maxY - minY);
-      gutter.repaint(0, minY, gutter.getWidth(), maxY - minY);
-    }
-    else {
-      content.repaintEditorComponent(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight);
-      gutter.repaint(0, oldCaretInfo.y, updateWidth, oldCaretInfo.height + lineHeight);
-      content.repaintEditorComponent(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight);
-      gutter.repaint(0, myCaretInfo.y, updateWidth, myCaretInfo.height + lineHeight);
-    }
+  public void reinitSettings() {
+    myTextAttributes = null;
   }
 
   @Override
-  public boolean isUpToDate() {
-    return !myIsInUpdate && !myReportCaretMoves;
+  public boolean supportsMultipleCarets() {
+    return mySupportsMultipleCarets;
   }
 
   @Override
   @NotNull
-  public LogicalPosition getLogicalPosition() {
-    validateCallContext();
-    return myLogicalCaret;
-  }
-
-  private void validateCallContext() {
-    LOG.assertTrue(!myIsInUpdate, "Caret model is in its update process. All requests are illegal at this point.");
+  public CaretImpl getCurrentCaret() {
+    return ApplicationManager.getApplication().isDispatchThread() && myCurrentCaret != null ? myCurrentCaret : getPrimaryCaret();
   }
 
   @Override
   @NotNull
-  public VisualPosition getVisualPosition() {
-    validateCallContext();
-    return myVisibleCaret;
+  public CaretImpl getPrimaryCaret() {
+    synchronized (myCarets) {
+      return myCarets.get(myCarets.size() - 1);
+    }
   }
 
   @Override
-  public int getOffset() {
-    validateCallContext();
-    return myOffset;
+  @NotNull
+  public Collection<Caret> getAllCarets() {
+    List<Caret> carets;
+    synchronized (myCarets) {
+      carets = new ArrayList<Caret>(myCarets);
+    }
+    Collections.sort(carets, CaretPositionComparator.INSTANCE);
+    return carets;
   }
 
+  @Nullable
   @Override
-  public int getVisualLineStart() {
-    return myVisualLineStart;
+  public Caret getCaretAt(@NotNull VisualPosition pos) {
+    synchronized (myCarets) {
+      for (CaretImpl caret : myCarets) {
+        if (caret.getVisualPosition().equals(pos)) {
+          return caret;
+        }
+      }
+      return null;
+    }
   }
 
+  @Nullable
   @Override
-  public int getVisualLineEnd() {
-    return myVisualLineEnd;
+  public Caret addCaret(@NotNull VisualPosition pos) {
+    myEditor.assertIsDispatchThread();
+    CaretImpl caret = new CaretImpl(myEditor);
+    caret.moveToVisualPosition(pos, false);
+    if (addCaret(caret)) {
+      return caret;
+    }
+    else {
+      Disposer.dispose(caret);
+      return null;
+    }
   }
 
-  @Override
-  public void addCaretListener(@NotNull CaretListener listener) {
-    myCaretListeners.addListener(listener);
+  boolean addCaret(CaretImpl caretToAdd) {
+    for (CaretImpl caret : myCarets) {
+      VisualPosition newVisualPosition = caretToAdd.getVisualPosition();
+      int newOffset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(newVisualPosition));
+      if (caret.getVisualPosition().equals(newVisualPosition) || newOffset >= caret.getSelectionStart() && newOffset <= caret.getSelectionEnd()) {
+        return false;
+      }
+    }
+    synchronized (myCarets) {
+      myCarets.add(caretToAdd);
+    }
+    fireCaretAdded(caretToAdd);
+    return true;
   }
 
   @Override
-  public void removeCaretListener(@NotNull CaretListener listener) {
-    myCaretListeners.removeListener(listener);
+  public boolean removeCaret(@NotNull Caret caret) {
+    myEditor.assertIsDispatchThread();
+    if (myCarets.size() <= 1 || !(caret instanceof CaretImpl)) {
+      return false;
+    }
+    synchronized (myCarets) {
+      if (!myCarets.remove(caret)) {
+        return false;
+      }
+    }
+    fireCaretRemoved(caret);
+    Disposer.dispose(caret);
+    return true;
   }
 
   @Override
-  public TextAttributes getTextAttributes() {
-    if (myTextAttributes == null) {
-      myTextAttributes = new TextAttributes();
-      myTextAttributes.setBackgroundColor(myEditor.getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR));
+  public void removeSecondaryCarets() {
+    myEditor.assertIsDispatchThread();
+    if (!supportsMultipleCarets()) {
+      return;
+    }
+    ListIterator<CaretImpl> caretIterator = myCarets.listIterator(myCarets.size() - 1);
+    while (caretIterator.hasPrevious()) {
+      CaretImpl caret = caretIterator.previous();
+      synchronized (myCarets) {
+        caretIterator.remove();
+      }
+      fireCaretRemoved(caret);
+      Disposer.dispose(caret);
     }
-
-    return myTextAttributes;
-  }
-
-  public void reinitSettings() {
-    myTextAttributes = null;
   }
 
   @Override
-  public void documentChanged(DocumentEvent e) {
-    isDocumentChanged = true;
-    try {
-      updateCaretPosition((DocumentEventImpl)e);
+  public void runForEachCaret(@NotNull final Runnable runnable) {
+    myEditor.assertIsDispatchThread();
+    if (!supportsMultipleCarets()) {
+      runnable.run();
+      return;
     }
-    finally {
-      isDocumentChanged = false;
+    if (myCurrentCaret != null) {
+      throw new IllegalStateException("Current caret is defined, cannot operate on other ones");
     }
+    doWithCaretMerging(new Runnable() {
+      public void run() {
+        try {
+          Collection<Caret> sortedCarets = getAllCarets();
+          for (Caret caret : sortedCarets) {
+            myCurrentCaret = (CaretImpl)caret;
+            runnable.run();
+          }
+        }
+        finally {
+          myCurrentCaret = null;
+        }
+      }
+    });
   }
 
-  private void updateCaretPosition(@NotNull final DocumentEventImpl event) {
-    finishUpdate();
-    final DocumentEx document = myEditor.getDocument();
-    boolean performSoftWrapAdjustment = event.getNewLength() > 0 // We want to put caret just after the last added symbol
-                                        // There is a possible case that the user removes text just before the soft wrap. We want to keep caret
-                                        // on a visual line with soft wrap start then.
-                                        || myEditor.getSoftWrapModel().getSoftWrap(event.getOffset()) != null;
-
-    if (event.isWholeTextReplaced()) {
-      int newLength = document.getTextLength();
-      if (myOffset == newLength - event.getNewLength() + event.getOldLength() || newLength == 0) {
-        moveToOffset(newLength, performSoftWrapAdjustment);
+  private void mergeOverlappingCaretsAndSelections() {
+    if (!supportsMultipleCarets() || myCarets.size() <= 1) {
+      return;
+    }
+    LinkedList<CaretImpl> carets = new LinkedList<CaretImpl>(myCarets);
+    Collections.sort(carets, CaretPositionComparator.INSTANCE);
+    ListIterator<CaretImpl> it = carets.listIterator();
+    while (it.hasNext()) {
+      CaretImpl prevCaret = null;
+      if (it.hasPrevious()) {
+        prevCaret = it.previous();
+        it.next();
       }
-      else {
-        try {
-          final int line = event.translateLineViaDiff(myLogicalCaret.line);
-          moveToLogicalPosition(new LogicalPosition(line, myLogicalCaret.column), performSoftWrapAdjustment, null, true);
+      CaretImpl currCaret = it.next();
+      if (prevCaret != null && (currCaret.getVisualPosition().equals(prevCaret.getVisualPosition())
+                                || Math.max(currCaret.getSelectionStart(), prevCaret.getSelectionStart())
+                                   < Math.min(currCaret.getSelectionEnd(), prevCaret.getSelectionEnd()))) {
+        int newSelectionStart = Math.min(currCaret.getSelectionStart(), prevCaret.getSelectionStart());
+        int newSelectionEnd = Math.max(currCaret.getSelectionEnd(), prevCaret.getSelectionEnd());
+        CaretImpl toRetain, toRemove;
+        if (currCaret.getOffset() >= prevCaret.getSelectionStart() && currCaret.getOffset() <= prevCaret.getSelectionEnd()) {
+          toRetain = prevCaret;
+          toRemove = currCaret;
+          it.remove();
+          it.previous();
         }
-        catch (FilesTooBigForDiffException e1) {
-          LOG.info(e1);
-          moveToOffset(0);
+        else {
+          toRetain = currCaret;
+          toRemove = prevCaret;
+          it.previous();
+          it.previous();
+          it.remove();
+        }
+        removeCaret(toRemove);
+        if (newSelectionStart < newSelectionEnd) {
+          toRetain.setSelection(newSelectionStart, newSelectionEnd);
         }
       }
     }
-    else {
-      if (document.isInBulkUpdate()) return;
-      int startOffset = event.getOffset();
-      int oldEndOffset = startOffset + event.getOldLength();
-
-      int newOffset = myOffset;
+  }
 
-      if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(event)) {
-        newOffset += event.getNewLength() - event.getOldLength();
+  void doWithCaretMerging(Runnable runnable) {
+    if (myPerformCaretMergingAfterCurrentOperation) {
+      runnable.run();
+    }
+    else {
+      myPerformCaretMergingAfterCurrentOperation = true;
+      try {
+        runnable.run();
       }
-      else if (myOffset >= startOffset && myOffset <= oldEndOffset) {
-        newOffset = Math.min(newOffset, startOffset + event.getNewLength());
+      finally {
+        myPerformCaretMergingAfterCurrentOperation = false;
       }
-
-      newOffset = Math.min(newOffset, document.getTextLength());
-
-      // if (newOffset != myOffset) {
-      moveToOffset(newOffset, performSoftWrapAdjustment);
-      //}
-      //else {
-      //  moveToVisualPosition(oldPosition);
-      //}
+      mergeOverlappingCaretsAndSelections();
     }
-
-    updateVisualLineInfo();
-  }
-
-  private void finishUpdate() {
-    myIsInUpdate = false;
-  }
-
-  private boolean needToShiftWhiteSpaces(final DocumentEvent e) {
-    if (!CharArrayUtil.containsOnlyWhiteSpaces(e.getNewFragment()) || CharArrayUtil.containLineBreaks(e.getNewFragment()))
-      return e.getOldLength() > 0;
-    if (e.getOffset() == 0) return false;
-    final char charBefore = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() - 1);
-    //final char charAfter = myEditor.getDocument().getCharsSequence().charAt(e.getOffset() + e.getNewLength());
-    return Character.isWhitespace(charBefore)/* || !Character.isWhitespace(charAfter)*/;
   }
 
   @Override
-  public void beforeDocumentChange(DocumentEvent e) {
-    myIsInUpdate = true;
-  }
-
-  @Override
-  public int getPriority() {
-    return EditorDocumentPriorities.CARET_MODEL;
+  public void setCarets(@NotNull final List<LogicalPosition> caretPositions, @NotNull final List<? extends Segment> selections) {
+    myEditor.assertIsDispatchThread();
+    if (caretPositions.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) {
+          CaretImpl caret;
+          boolean caretAdded;
+          if (index++ < oldCaretCount) {
+            caret = caretIterator.next();
+            caretAdded = false;
+          }
+          else {
+            caret = new CaretImpl(myEditor);
+            caret.moveToLogicalPosition(caretPosition, false, null, false);
+            synchronized (myCarets) {
+              myCarets.add(caret);
+            }
+            fireCaretAdded(caret);
+    &nb