From ab42788071c16050649f03d435bacc290b8810c5 Mon Sep 17 00:00:00 2001
From: Dmitry Batrak <Dmitry.Batrak@jetbrains.com>
Date: Fri, 11 Jul 2014 13:57:39 +0400
Subject: [PATCH] IDEA-125021 Improve multi-caret copy-paste logic

---
 .../editorActions/CopyHandler.java            |   7 +-
 .../editorActions/TextBlockTransferable.java  |   2 +-
 .../TextBlockTransferableData.java            |   2 +-
 .../editor/CaretStateTransferableData.java    |  56 +++++
 .../editor/ClipboardTextPerCaretSplitter.java |  18 +-
 .../openapi/editor/CopyPasteSupport.java      | 212 ++++++++++++++++++
 .../editor/EditorModificationUtil.java        | 155 +------------
 .../editor/impl/SelectionModelImpl.java       |  11 +-
 .../openapi/editor/EditorMultiCaretTest.java  |  32 +++
 9 files changed, 333 insertions(+), 162 deletions(-)
 rename platform/{lang-impl => platform-api}/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java (98%)
 rename platform/{lang-impl => platform-api}/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java (95%)
 create mode 100644 platform/platform-api/src/com/intellij/openapi/editor/CaretStateTransferableData.java
 create mode 100644 platform/platform-api/src/com/intellij/openapi/editor/CopyPasteSupport.java

diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java
index a68a1fdeee5a..2a46c08fc2dd 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java
+++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/CopyHandler.java
@@ -37,8 +37,6 @@ import java.util.ArrayList;
 import java.util.List;
 
 public class CopyHandler extends EditorActionHandler {
-  private static final Logger LOG = Logger.getInstance(CopyHandler.class);
-
   private final EditorActionHandler myOriginalAction;
 
   public CopyHandler(final EditorActionHandler originalHandler) {
@@ -93,7 +91,10 @@ public class CopyHandler extends EditorActionHandler {
       transferableDatas.addAll(processor.collectTransferableData(file, editor, startOffsets, endOffsets));
     }
 
-    String rawText = TextBlockTransferable.convertLineSeparators(selectionModel.getSelectedText(true), "\n", transferableDatas);
+    String text = editor.getCaretModel().supportsMultipleCarets()
+                  ? CopyPasteSupport.getSelectedTextForClipboard(editor, transferableDatas)
+                  : selectionModel.getSelectedText();
+    String rawText = TextBlockTransferable.convertLineSeparators(text, "\n", transferableDatas);
     String escapedText = null;
     for (CopyPastePreProcessor processor : Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
       escapedText = processor.preprocessOnCopy(file, startOffsets, endOffsets, rawText);
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java b/platform/platform-api/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java
similarity index 98%
rename from platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java
rename to platform/platform-api/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java
index 075f7618eddd..7d972816769c 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java
+++ b/platform/platform-api/src/com/intellij/codeInsight/editorActions/TextBlockTransferable.java
@@ -30,7 +30,7 @@ import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 
-class TextBlockTransferable implements Transferable {
+public class TextBlockTransferable implements Transferable {
   private final Collection<TextBlockTransferableData> myExtraData;
   private final RawText myRawText;
   private final String myText;
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java b/platform/platform-api/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java
similarity index 95%
rename from platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java
rename to platform/platform-api/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java
index c15a50e3019a..bdaf6f2ea546 100644
--- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java
+++ b/platform/platform-api/src/com/intellij/codeInsight/editorActions/TextBlockTransferableData.java
@@ -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.
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/CaretStateTransferableData.java b/platform/platform-api/src/com/intellij/openapi/editor/CaretStateTransferableData.java
new file mode 100644
index 000000000000..f0d2085cbb6b
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/editor/CaretStateTransferableData.java
@@ -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 com.intellij.codeInsight.editorActions.TextBlockTransferableData;
+
+import java.awt.datatransfer.DataFlavor;
+
+public class CaretStateTransferableData implements TextBlockTransferableData {
+  public static final DataFlavor FLAVOR = new DataFlavor(CaretStateTransferableData.class, "Caret state");
+
+  public final int[] startOffsets;
+  public final int[] endOffsets;
+
+  public CaretStateTransferableData(int[] startOffsets, int[] endOffsets) {
+    this.startOffsets = startOffsets;
+    this.endOffsets = endOffsets;
+  }
+
+  @Override
+  public DataFlavor getFlavor() {
+    return FLAVOR;
+  }
+
+  @Override
+  public int getOffsetCount() {
+    return startOffsets.length + endOffsets.length;
+  }
+
+  @Override
+  public int getOffsets(int[] offsets, int index) {
+    System.arraycopy(startOffsets, 0, offsets, index, startOffsets.length);
+    System.arraycopy(endOffsets, 0, offsets, index + startOffsets.length, endOffsets.length);
+    return index + getOffsetCount();
+  }
+
+  @Override
+  public int setOffsets(int[] offsets, int index) {
+    System.arraycopy(offsets, index, startOffsets, 0, startOffsets.length);
+    System.arraycopy(offsets, index + startOffsets.length, endOffsets, 0, endOffsets.length);
+    return index + getOffsetCount();
+  }
+}
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/ClipboardTextPerCaretSplitter.java b/platform/platform-api/src/com/intellij/openapi/editor/ClipboardTextPerCaretSplitter.java
index afc58b818bce..a3e62412f4a4 100644
--- a/platform/platform-api/src/com/intellij/openapi/editor/ClipboardTextPerCaretSplitter.java
+++ b/platform/platform-api/src/com/intellij/openapi/editor/ClipboardTextPerCaretSplitter.java
@@ -15,12 +15,16 @@
  */
 package com.intellij.openapi.editor;
 
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
 public class ClipboardTextPerCaretSplitter {
-  public List<String> split(String input, int caretCount) {
+  @NotNull
+  public List<String> split(@NotNull String input, @Nullable CaretStateTransferableData caretData, int caretCount) {
     if (caretCount <= 0) {
       throw new IllegalArgumentException("Caret count must be positive");
     }
@@ -28,9 +32,17 @@ public class ClipboardTextPerCaretSplitter {
       return Collections.singletonList(input);
     }
     List<String> result = new ArrayList<String>(caretCount);
-    String[] lines = input.split("\n", -1);
+    int sourceCaretCount = caretData == null ? -1 : caretData.startOffsets.length;
+    String[] lines = sourceCaretCount == 1 || sourceCaretCount == caretCount ? null : input.split("\n", -1);
     for (int i = 0; i < caretCount; i++) {
-      if (lines.length == 0) {
+      if (sourceCaretCount == 1) {
+        result.add(input);
+      }
+      else if (sourceCaretCount == caretCount) {
+        //noinspection ConstantConditions
+        result.add(new String(input.substring(caretData.startOffsets[i], caretData.endOffsets[i])));
+      }
+      else if (lines.length == 0) {
         result.add("");
       }
       else if (lines.length == 1) {
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/CopyPasteSupport.java b/platform/platform-api/src/com/intellij/openapi/editor/CopyPasteSupport.java
new file mode 100644
index 000000000000..7095080aa13d
--- /dev/null
+++ b/platform/platform-api/src/com/intellij/openapi/editor/CopyPasteSupport.java
@@ -0,0 +1,212 @@
+/*
+ * 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.codeInsight.editorActions.TextBlockTransferable;
+import com.intellij.codeInsight.editorActions.TextBlockTransferableData;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.openapi.ide.CopyPasteManager;
+import com.intellij.openapi.util.TextRange;
+import com.intellij.openapi.util.text.LineTokenizer;
+import com.intellij.util.Producer;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.datatransfer.UnsupportedFlavorException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+
+public class CopyPasteSupport {
+  private static final Logger LOG = Logger.getInstance(CopyPasteSupport.class);
+
+  private CopyPasteSupport() { }
+
+  public static void copySelectionToClipboard(@NotNull Editor editor) {
+    ApplicationManager.getApplication().assertIsDispatchThread();
+    List<TextBlockTransferableData> extraData = new ArrayList<TextBlockTransferableData>();
+    String s = editor.getCaretModel().supportsMultipleCarets() ? getSelectedTextForClipboard(editor, extraData)
+                                                               : editor.getSelectionModel().getSelectedText();
+    if (s == null) return;
+
+    s = TextBlockTransferable.convertLineSeparators(s, "\n", extraData);
+    Transferable contents = editor.getCaretModel().supportsMultipleCarets() ? new TextBlockTransferable(s, extraData, null) : new StringSelection(s);
+    CopyPasteManager.getInstance().setContents(contents);
+  }
+
+  public static String getSelectedTextForClipboard(@NotNull Editor editor, @NotNull Collection<TextBlockTransferableData> extraDataCollector) {
+    final StringBuilder buf = new StringBuilder();
+    String separator = "";
+    List<Caret> carets = editor.getCaretModel().getAllCarets();
+    int[] startOffsets = new int[carets.size()];
+    int[] endOffsets = new int[carets.size()];
+    for (int i = 0; i < carets.size(); i++) {
+      buf.append(separator);
+      String caretSelectedText = carets.get(i).getSelectedText();
+      startOffsets[i] = buf.length();
+      if (caretSelectedText != null) {
+        buf.append(caretSelectedText);
+      }
+      endOffsets[i] = buf.length();
+      separator = "\n";
+    }
+    extraDataCollector.add(new CaretStateTransferableData(startOffsets, endOffsets));
+    return buf.toString();
+  }
+
+
+  public static TextRange pasteFromClipboard(Editor editor) {
+    return pasteTransferable(editor, (Producer<Transferable>)null);
+  }
+
+  public static TextRange pasteTransferable(Editor editor, final Transferable content) {
+    return pasteTransferable(editor, new Producer<Transferable>() {
+      @Nullable
+      @Override
+      public Transferable produce() {
+        return content;
+      }
+    });
+  }
+
+  @Nullable
+  public static TextRange pasteTransferable(final Editor editor, @Nullable Producer<Transferable> producer) {
+    Transferable content = getTransferable(producer);
+    if (content == null) return null;
+    String text = getStringContent(content);
+    if (text == null) return null;
+
+    if (editor.getCaretModel().supportsMultipleCarets()) {
+      int caretCount = editor.getCaretModel().getCaretCount();
+      if (caretCount == 1 && editor.isColumnMode()) {
+        int pastedLineCount = LineTokenizer.calcLineCount(text, true);
+        EditorModificationUtil.deleteSelectedText(editor);
+        Caret caret = editor.getCaretModel().getPrimaryCaret();
+        for (int i = 0; i < pastedLineCount - 1; i++) {
+          caret = caret.clone(false);
+          if (caret == null) {
+            break;
+          }
+        }
+        caretCount = editor.getCaretModel().getCaretCount();
+      }
+      CaretStateTransferableData caretData = null;
+      try {
+        caretData = content.isDataFlavorSupported(CaretStateTransferableData.FLAVOR)
+                    ? (CaretStateTransferableData)content.getTransferData(CaretStateTransferableData.FLAVOR) : null;
+      }
+      catch (Exception e) {
+        LOG.error(e);
+      }
+      final Iterator<String> segments = new ClipboardTextPerCaretSplitter().split(text, caretData, caretCount).iterator();
+      editor.getCaretModel().runForEachCaret(new CaretAction() {
+        @Override
+        public void perform(Caret caret) {
+          EditorModificationUtil.insertStringAtCaret(editor, segments.next(), false, true);
+        }
+      });
+      return null;
+    }
+    else {
+      int caretOffset = editor.getCaretModel().getOffset();
+      EditorModificationUtil.insertStringAtCaret(editor, text, false, true);
+      return new TextRange(caretOffset, caretOffset + text.length());
+    }
+  }
+
+  public static void pasteTransferableAsBlock(Editor editor, @Nullable Producer<Transferable> producer) {
+    Transferable content = getTransferable(producer);
+    if (content == null) return;
+    String text = getStringContent(content);
+    if (text == null) return;
+
+    int caretLine = editor.getCaretModel().getLogicalPosition().line;
+    int originalCaretLine = caretLine;
+    int selectedLinesCount = 0;
+
+    final SelectionModel selectionModel = editor.getSelectionModel();
+    if (selectionModel.hasBlockSelection()) {
+      final LogicalPosition start = selectionModel.getBlockStart();
+      final LogicalPosition end = selectionModel.getBlockEnd();
+      assert start != null;
+      assert end != null;
+      LogicalPosition caret = new LogicalPosition(Math.min(start.line, end.line), Math.min(start.column, end.column));
+      selectedLinesCount = Math.abs(end.line - start.line);
+      caretLine = caret.line;
+
+      EditorModificationUtil.deleteSelectedText(editor);
+      editor.getCaretModel().moveToLogicalPosition(caret);
+    }
+
+    LogicalPosition caretToRestore = editor.getCaretModel().getLogicalPosition();
+
+    String[] lines = LineTokenizer.tokenize(text.toCharArray(), false);
+    if (lines.length > 1 || selectedLinesCount == 0) {
+      int longestLineLength = 0;
+      for (int i = 0; i < lines.length; i++) {
+        String line = lines[i];
+        longestLineLength = Math.max(longestLineLength, line.length());
+        editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(caretLine + i, caretToRestore.column));
+        EditorModificationUtil.insertStringAtCaret(editor, line, false, true);
+      }
+      caretToRestore = new LogicalPosition(originalCaretLine, caretToRestore.column + longestLineLength);
+    }
+    else {
+      for (int i = 0; i <= selectedLinesCount; i++) {
+        editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(caretLine + i, caretToRestore.column));
+        EditorModificationUtil.insertStringAtCaret(editor, text, false, true);
+      }
+      caretToRestore = new LogicalPosition(originalCaretLine, caretToRestore.column + text.length());
+    }
+
+    editor.getCaretModel().moveToLogicalPosition(caretToRestore);
+    EditorModificationUtil.zeroWidthBlockSelectionAtCaretColumn(editor, caretLine, caretLine + selectedLinesCount);
+  }
+
+  @Nullable
+  private static String getStringContent(@NotNull Transferable content) {
+    RawText raw = RawText.fromTransferable(content);
+    if (raw != null) return raw.rawText;
+
+    try {
+      return (String)content.getTransferData(DataFlavor.stringFlavor);
+    }
+    catch (UnsupportedFlavorException ignore) { }
+    catch (IOException ignore) { }
+
+    return null;
+  }
+
+  private static Transferable getTransferable(Producer<Transferable> producer) {
+    Transferable content = null;
+    if (producer != null) {
+      content = producer.produce();
+    }
+    else {
+      CopyPasteManager manager = CopyPasteManager.getInstance();
+      if (manager.areDataFlavorsAvailable(DataFlavor.stringFlavor)) {
+        content = manager.getContents();
+      }
+    }
+    return content;
+  }
+}
diff --git a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
index 3fddbc344585..ee5527967440 100644
--- a/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
+++ b/platform/platform-api/src/com/intellij/openapi/editor/EditorModificationUtil.java
@@ -19,20 +19,14 @@ import com.intellij.codeStyle.CodeStyleFacade;
 import com.intellij.openapi.editor.actionSystem.EditorActionManager;
 import com.intellij.openapi.editor.event.DocumentEvent;
 import com.intellij.openapi.editor.event.MockDocumentEvent;
-import com.intellij.openapi.ide.CopyPasteManager;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.TextRange;
-import com.intellij.openapi.util.text.LineTokenizer;
 import com.intellij.psi.PsiDocumentManager;
 import com.intellij.util.Producer;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-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 {
@@ -99,7 +93,7 @@ public class EditorModificationUtil {
     zeroWidthBlockSelectionAtCaretColumn(editor, startLine, endLine);
   }
 
-  private static void zeroWidthBlockSelectionAtCaretColumn(final Editor editor, final int startLine, final int endLine) {
+  public static void zeroWidthBlockSelectionAtCaretColumn(final Editor editor, final int startLine, final int endLine) {
     int caretColumn = editor.getCaretModel().getLogicalPosition().column;
     editor.getSelectionModel().setBlockSelection(new LogicalPosition(startLine, caretColumn), new LogicalPosition(endLine, caretColumn));
   }
@@ -181,112 +175,21 @@ public class EditorModificationUtil {
     return offset;
   }
 
+  /**
+   * @deprecated Use {@link com.intellij.openapi.editor.CopyPasteSupport#pasteTransferable(Editor, com.intellij.util.Producer)} instead.
+   * (to remove in IDEA 15)
+   */
   @Nullable
   public static TextRange pasteTransferable(final Editor editor, @Nullable Producer<Transferable> producer) {
-    String text = getStringContent(producer);
-    if (text == null) return null;
-
-    if (editor.getCaretModel().supportsMultipleCarets()) {
-      int caretCount = editor.getCaretModel().getCaretCount();
-      if (caretCount == 1 && editor.isColumnMode()) {
-        int pastedLineCount = LineTokenizer.calcLineCount(text, true);
-        deleteSelectedText(editor);
-        Caret caret = editor.getCaretModel().getPrimaryCaret();
-        for (int i = 0; i < pastedLineCount - 1; i++) {
-          caret = caret.clone(false);
-          if (caret == null) {
-            break;
-          }
-        }
-        caretCount = editor.getCaretModel().getCaretCount();
-      }
-      final Iterator<String> segments = new ClipboardTextPerCaretSplitter().split(text, caretCount).iterator();
-      editor.getCaretModel().runForEachCaret(new CaretAction() {
-        @Override
-        public void perform(Caret caret) {
-          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());
-    }
+    return CopyPasteSupport.pasteTransferable(editor, producer);
   }
 
+  /**
+   * @deprecated Use {@link com.intellij.openapi.editor.CopyPasteSupport#pasteTransferableAsBlock(Editor, com.intellij.util.Producer)} instead.
+   * (to remove in IDEA 15)
+   */
   public static void pasteTransferableAsBlock(Editor editor, @Nullable Producer<Transferable> producer) {
-    String text = getStringContent(producer);
-    if (text == null) return;
-
-    int caretLine = editor.getCaretModel().getLogicalPosition().line;
-    int originalCaretLine = caretLine;
-    int selectedLinesCount = 0;
-
-    final SelectionModel selectionModel = editor.getSelectionModel();
-    if (selectionModel.hasBlockSelection()) {
-      final LogicalPosition start = selectionModel.getBlockStart();
-      final LogicalPosition end = selectionModel.getBlockEnd();
-      assert start != null;
-      assert end != null;
-      LogicalPosition caret = new LogicalPosition(Math.min(start.line, end.line), Math.min(start.column, end.column));
-      selectedLinesCount = Math.abs(end.line - start.line);
-      caretLine = caret.line;
-
-      deleteSelectedText(editor);
-      editor.getCaretModel().moveToLogicalPosition(caret);
-    }
-
-    LogicalPosition caretToRestore = editor.getCaretModel().getLogicalPosition();
-
-    String[] lines = LineTokenizer.tokenize(text.toCharArray(), false);
-    if (lines.length > 1 || selectedLinesCount == 0) {
-      int longestLineLength = 0;
-      for (int i = 0; i < lines.length; i++) {
-        String line = lines[i];
-        longestLineLength = Math.max(longestLineLength, line.length());
-        editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(caretLine + i, caretToRestore.column));
-        insertStringAtCaret(editor, line, false, true);
-      }
-      caretToRestore = new LogicalPosition(originalCaretLine, caretToRestore.column + longestLineLength);
-    }
-    else {
-      for (int i = 0; i <= selectedLinesCount; i++) {
-        editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(caretLine + i, caretToRestore.column));
-        insertStringAtCaret(editor, text, false, true);
-      }
-      caretToRestore = new LogicalPosition(originalCaretLine, caretToRestore.column + text.length());
-    }
-
-    editor.getCaretModel().moveToLogicalPosition(caretToRestore);
-    zeroWidthBlockSelectionAtCaretColumn(editor, caretLine, caretLine + selectedLinesCount);
-  }
-
-  @Nullable
-  private static String getStringContent(@Nullable Producer<Transferable> producer) {
-    Transferable content = null;
-    if (producer != null) {
-      content = producer.produce();
-    }
-    else {
-      CopyPasteManager manager = CopyPasteManager.getInstance();
-      if (manager.areDataFlavorsAvailable(DataFlavor.stringFlavor)) {
-        content = manager.getContents();
-      }
-    }
-    if (content == null) return null;
-
-    RawText raw = RawText.fromTransferable(content);
-    if (raw != null) return raw.rawText;
-
-    try {
-      return (String)content.getTransferData(DataFlavor.stringFlavor);
-    }
-    catch (UnsupportedFlavorException ignore) { }
-    catch (IOException ignore) { }
-
-    return null;
+    CopyPasteSupport.pasteTransferableAsBlock(editor, producer);
   }
 
   /**
@@ -494,40 +397,4 @@ public class EditorModificationUtil {
     CaretModel caretModel = editor.getCaretModel();
     caretModel.moveToOffset(caretModel.getOffset() + caretShift);
   }
-
-  /** @deprecated use {@link #pasteTransferable(Editor, Producer)} (to remove in IDEA 14) */
-  @SuppressWarnings("UnusedDeclaration")
-  public static TextRange pasteFromClipboard(Editor editor) {
-    return pasteTransferable(editor, null);
-  }
-
-  /** @deprecated use {@link #pasteTransferable(Editor, Producer)} (to remove in IDEA 14) */
-  @SuppressWarnings("SpellCheckingInspection,UnusedDeclaration")
-  public static TextRange pasteFromTransferrable(final Transferable content, Editor editor) {
-    return pasteTransferable(editor, new Producer<Transferable>() {
-      @Nullable
-      @Override
-      public Transferable produce() {
-        return content;
-      }
-    });
-  }
-
-  @SuppressWarnings("UnusedDeclaration")
-  /** @deprecated use {@link #pasteTransferableAsBlock(Editor, Producer)} (to remove in IDEA 14) */
-  public static void pasteFromClipboardAsBlock(Editor editor) {
-    pasteTransferableAsBlock(editor, (Producer<Transferable>)null);
-  }
-
-  @SuppressWarnings("UnusedDeclaration")
-  /** @deprecated use {@link #pasteTransferableAsBlock(Editor, Producer)} (to remove in IDEA 14) */
-  public static void pasteTransferableAsBlock(Editor editor, @Nullable final Transferable content) {
-    pasteTransferableAsBlock(editor, new Producer<Transferable>() {
-      @Nullable
-      @Override
-      public Transferable produce() {
-        return content;
-      }
-    });
-  }
 }
diff --git a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java
index 263ea77c286f..2f5a81153579 100644
--- a/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java
+++ b/platform/platform-impl/src/com/intellij/openapi/editor/impl/SelectionModelImpl.java
@@ -36,16 +36,13 @@ import com.intellij.openapi.editor.ex.DocumentEx;
 import com.intellij.openapi.editor.ex.PrioritizedDocumentListener;
 import com.intellij.openapi.editor.ex.util.EditorUtil;
 import com.intellij.openapi.editor.markup.TextAttributes;
-import com.intellij.openapi.ide.CopyPasteManager;
 import com.intellij.openapi.util.Pair;
-import com.intellij.openapi.util.text.StringUtil;
 import com.intellij.util.ArrayUtil;
 import com.intellij.util.containers.ContainerUtil;
 import gnu.trove.TIntArrayList;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
-import java.awt.datatransfer.StringSelection;
 import java.util.Collection;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -570,13 +567,7 @@ public class SelectionModelImpl implements SelectionModel, PrioritizedDocumentLi
 
   @Override
   public void copySelectionToClipboard() {
-    validateContext(true);
-    String s = getSelectedText(true);
-    if (s == null) return;
-
-    s = StringUtil.convertLineSeparators(s);
-    StringSelection contents = new StringSelection(s);
-    CopyPasteManager.getInstance().setContents(contents);
+    CopyPasteSupport.copySelectionToClipboard(myEditor);
   }
 
   @Override
diff --git a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java
index 26be81d2f341..fdaf7c6ed5b0 100644
--- a/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java
+++ b/platform/platform-tests/testSrc/com/intellij/openapi/editor/EditorMultiCaretTest.java
@@ -245,6 +245,38 @@ public class EditorMultiCaretTest extends AbstractEditorTest {
                       "seven<caret>");
   }
 
+  public void testCopyMultilineFromOneCaretPasteIntoTwo() throws Exception {
+    init("<selection>one\n" +
+         "two<caret></selection>\n" +
+         "three\n" +
+         "four",
+         TestFileType.TEXT);
+    executeAction("EditorCopy");
+    executeAction("EditorTextStart");
+    executeAction("EditorCloneCaretBelow");
+    executeAction("EditorPaste");
+    checkResultByText("one\n" +
+                      "two<caret>one\n" +
+                      "one\n" +
+                      "two<caret>two\n" +
+                      "three\n" +
+                      "four");
+  }
+
+  public void testCopyPasteDoesNothingWithUnevenSelection() throws Exception {
+    init("<selection>one\n" +
+         "two<caret></selection>\n" +
+         "<selection>three<caret></selection>\n" +
+         "four",
+         TestFileType.TEXT);
+    executeAction("EditorCopy");
+    executeAction("EditorPaste");
+    checkResultByText("one\n" +
+                      "two<caret>\n" +
+                      "three<caret>\n" +
+                      "four");
+  }
+
   public void testEscapeAfterDragDown() throws Exception {
     init("line1\n" +
          "line2",
-- 
GitLab