diff --git a/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt b/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt
index 61887413d..29e411e29 100644
--- a/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt
+++ b/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt
@@ -282,4 +282,4 @@ fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFu
 
 fun interface ScriptFunction {
   fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/maddyhome/idea/vim/extension/highlightedyank/VimHighlightedYank.kt b/src/main/java/com/maddyhome/idea/vim/extension/highlightedyank/VimHighlightedYank.kt
index b4bf7e524..e30df1a28 100644
--- a/src/main/java/com/maddyhome/idea/vim/extension/highlightedyank/VimHighlightedYank.kt
+++ b/src/main/java/com/maddyhome/idea/vim/extension/highlightedyank/VimHighlightedYank.kt
@@ -21,9 +21,7 @@ import com.intellij.openapi.editor.markup.TextAttributes
 import com.intellij.openapi.util.Disposer
 import com.intellij.util.Alarm
 import com.intellij.util.Alarm.ThreadToUse
-import com.jetbrains.rd.util.first
 import com.maddyhome.idea.vim.VimPlugin
-import com.maddyhome.idea.vim.api.ImmutableVimCaret
 import com.maddyhome.idea.vim.api.VimEditor
 import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.common.ModeChangeListener
@@ -123,9 +121,9 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
     initialised = false
   }
 
-  override fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) {
+  override fun yankPerformed(editor: VimEditor, range: TextRange) {
     ensureInitialised()
-    highlightHandler.highlightYankRange(caretToRange)
+    highlightHandler.highlightYankRange(editor.ij, range)
   }
 
   override fun modeChanged(editor: VimEditor, oldMode: Mode) {
@@ -146,25 +144,22 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
     private var lastEditor: Editor? = null
     private val highlighters = mutableSetOf<RangeHighlighter>()
 
-    fun highlightYankRange(caretToRange: Map<ImmutableVimCaret, TextRange>) {
+    fun highlightYankRange(editor: Editor, range: TextRange) {
       // from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
       clearYankHighlighters()
 
-      val editor = caretToRange.first().key.editor.ij
       lastEditor = editor
 
       val attributes = getHighlightTextAttributes(editor)
-      for (range in caretToRange.values) {
-        for (i in 0 until range.size()) {
-          val highlighter = editor.markupModel.addRangeHighlighter(
-            range.startOffsets[i],
-            range.endOffsets[i],
-            HighlighterLayer.SELECTION,
-            attributes,
-            HighlighterTargetArea.EXACT_RANGE,
-          )
-          highlighters.add(highlighter)
-        }
+      for (i in 0 until range.size()) {
+        val highlighter = editor.markupModel.addRangeHighlighter(
+          range.startOffsets[i],
+          range.endOffsets[i],
+          HighlighterLayer.SELECTION,
+          attributes,
+          HighlighterTargetArea.EXACT_RANGE,
+        )
+        highlighters.add(highlighter)
       }
 
       // from vim-highlightedyank docs: A negative number makes the highlight persistent.
@@ -282,4 +277,4 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
       return default
     }
   }
-}
\ No newline at end of file
+}
diff --git a/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt b/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt
index 0a7c7ff24..9a6603858 100644
--- a/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt
+++ b/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt
@@ -29,7 +29,6 @@ import com.maddyhome.idea.vim.extension.exportOperatorFunction
 import com.maddyhome.idea.vim.group.visual.VimSelection
 import com.maddyhome.idea.vim.helper.exitVisualMode
 import com.maddyhome.idea.vim.key.OperatorFunction
-import com.maddyhome.idea.vim.newapi.IjVimCopiedText
 import com.maddyhome.idea.vim.newapi.IjVimEditor
 import com.maddyhome.idea.vim.newapi.ij
 import com.maddyhome.idea.vim.newapi.vim
@@ -154,8 +153,7 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
     usedType = SelectionType.CHARACTER_WISE
   }
 
-  val copiedText = IjVimCopiedText(usedText, (savedRegister.copiedText as IjVimCopiedText).transferableData)
-  val textData = PutData.TextData(savedRegister.name, copiedText, usedType)
+  val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData, savedRegister.name)
 
   val putData = PutData(
     textData,
diff --git a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
index 177bac911..30776253d 100644
--- a/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
+++ b/src/main/java/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt
@@ -207,7 +207,7 @@ internal class VimSurroundExtension : VimExtension {
               val trimmedValue = if (newSurround.shouldTrim) innerValue.trim() else innerValue
               it.first + trimmedValue + it.second
             } ?: innerValue
-            val textData = PutData.TextData(null, injector.clipboardManager.dumbCopiedText(text), SelectionType.CHARACTER_WISE)
+            val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), null)
             val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false)
 
             surrounding.caret to putData
diff --git a/src/main/java/com/maddyhome/idea/vim/group/RegisterGroup.java b/src/main/java/com/maddyhome/idea/vim/group/RegisterGroup.java
index bba5e33c7..66c87b10f 100644
--- a/src/main/java/com/maddyhome/idea/vim/group/RegisterGroup.java
+++ b/src/main/java/com/maddyhome/idea/vim/group/RegisterGroup.java
@@ -25,10 +25,9 @@ import org.jetbrains.annotations.Nullable;
 import javax.swing.*;
 import java.awt.event.KeyEvent;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
-import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
-
 /**
  * This group works with command associated with copying and pasting text
  */
@@ -128,7 +127,7 @@ public class RegisterGroup extends VimRegisterGroupBase implements PersistentSta
           final String text = VimPlugin.getXML().getSafeXmlText(textElement);
           if (text != null) {
             logger.trace("Register data parsed");
-            register = new Register(key, injector.getClipboardManager().dumbCopiedText(text), type);
+            register = new Register(key, type, text, Collections.emptyList());
           }
           else {
             logger.trace("Cannot parse register data");
diff --git a/src/main/java/com/maddyhome/idea/vim/group/copy/PutGroup.kt b/src/main/java/com/maddyhome/idea/vim/group/copy/PutGroup.kt
index aedbe5780..61d9ee767 100644
--- a/src/main/java/com/maddyhome/idea/vim/group/copy/PutGroup.kt
+++ b/src/main/java/com/maddyhome/idea/vim/group/copy/PutGroup.kt
@@ -36,7 +36,6 @@ import com.maddyhome.idea.vim.ide.isClionNova
 import com.maddyhome.idea.vim.ide.isRider
 import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
 import com.maddyhome.idea.vim.newapi.IjVimCaret
-import com.maddyhome.idea.vim.newapi.IjVimCopiedText
 import com.maddyhome.idea.vim.newapi.IjVimEditor
 import com.maddyhome.idea.vim.newapi.ij
 import com.maddyhome.idea.vim.newapi.vim
@@ -127,7 +126,7 @@ internal class PutGroup : VimPutBase() {
       point.dispose()
       if (!caret.isValid) return@forEach
 
-      val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.copiedText.text.length)
+      val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.text.length)
       val endOffset = if (data.indent) {
         doIndent(
           vimEditor,
@@ -179,10 +178,12 @@ internal class PutGroup : VimPutBase() {
     val allContentsBefore = CopyPasteManager.getInstance().allContents
     val sizeBeforeInsert = allContentsBefore.size
     val firstItemBefore = allContentsBefore.firstOrNull()
-    logger.debug { "Copied text: ${text.copiedText}" }
-    val (textContent, transferableData) = text.copiedText as IjVimCopiedText
+    logger.debug { "Transferable classes: ${text.transferableData.joinToString { it.javaClass.name }}" }
     val origContent: TextBlockTransferable =
-      injector.clipboardManager.setClipboardText(textContent, textContent, transferableData) as TextBlockTransferable
+      injector.clipboardManager.setClipboardText(
+      text.text,
+      transferableData = text.transferableData,
+    ) as TextBlockTransferable
     val allContentsAfter = CopyPasteManager.getInstance().allContents
     val sizeAfterInsert = allContentsAfter.size
     try {
@@ -190,7 +191,7 @@ internal class PutGroup : VimPutBase() {
     } finally {
       val textInClipboard = (firstItemBefore as? TextBlockTransferable)
         ?.getTransferData(DataFlavor.stringFlavor) as? String
-      val textOnTop = textInClipboard != null && textInClipboard != text.copiedText.text
+      val textOnTop = textInClipboard != null && textInClipboard != text.text
       if (sizeBeforeInsert != sizeAfterInsert || textOnTop) {
         // Sometimes an inserted text replaces an existing one. E.g. on insert with + or * register
         (CopyPasteManager.getInstance() as? CopyPasteManagerEx)?.run { removeContent(origContent) }
diff --git a/src/main/java/com/maddyhome/idea/vim/helper/UserDataManager.kt b/src/main/java/com/maddyhome/idea/vim/helper/UserDataManager.kt
index 4412ce7ac..d8b08ef11 100644
--- a/src/main/java/com/maddyhome/idea/vim/helper/UserDataManager.kt
+++ b/src/main/java/com/maddyhome/idea/vim/helper/UserDataManager.kt
@@ -18,7 +18,6 @@ import com.intellij.openapi.editor.VisualPosition
 import com.intellij.openapi.editor.markup.RangeHighlighter
 import com.intellij.openapi.util.Key
 import com.intellij.openapi.util.UserDataHolder
-import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
 import com.maddyhome.idea.vim.api.LocalMarkStorage
 import com.maddyhome.idea.vim.api.SelectionInfo
 import com.maddyhome.idea.vim.common.InsertSequence
@@ -98,7 +97,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
 }
 
 // TODO: Data could be lost during visual block motion
-internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor()
 internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
 internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()
 
diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimCaret.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimCaret.kt
index 236618e56..f990c4466 100644
--- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimCaret.kt
+++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimCaret.kt
@@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret
 import com.intellij.openapi.editor.LogicalPosition
 import com.intellij.openapi.editor.VisualPosition
 import com.maddyhome.idea.vim.api.BufferPosition
-import com.maddyhome.idea.vim.api.CaretRegisterStorage
-import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
 import com.maddyhome.idea.vim.api.ImmutableVimCaret
 import com.maddyhome.idea.vim.api.LocalMarkStorage
 import com.maddyhome.idea.vim.api.SelectionInfo
@@ -21,6 +19,7 @@ import com.maddyhome.idea.vim.api.VimCaret
 import com.maddyhome.idea.vim.api.VimCaretBase
 import com.maddyhome.idea.vim.api.VimEditor
 import com.maddyhome.idea.vim.api.VimVisualPosition
+import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.common.InsertSequence
 import com.maddyhome.idea.vim.common.LiveRange
 import com.maddyhome.idea.vim.group.visual.VisualChange
@@ -29,7 +28,6 @@ import com.maddyhome.idea.vim.helper.insertHistory
 import com.maddyhome.idea.vim.helper.lastSelectionInfo
 import com.maddyhome.idea.vim.helper.markStorage
 import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
-import com.maddyhome.idea.vim.helper.registerStorage
 import com.maddyhome.idea.vim.helper.resetVimLastColumn
 import com.maddyhome.idea.vim.helper.vimInsertStart
 import com.maddyhome.idea.vim.helper.vimLastColumn
@@ -37,22 +35,14 @@ import com.maddyhome.idea.vim.helper.vimLastVisualOperatorRange
 import com.maddyhome.idea.vim.helper.vimLine
 import com.maddyhome.idea.vim.helper.vimSelectionStart
 import com.maddyhome.idea.vim.helper.vimSelectionStartClear
+import com.maddyhome.idea.vim.register.VimRegisterGroup
 import com.maddyhome.idea.vim.state.mode.SelectionType
 
 internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
 
-  override val registerStorage: CaretRegisterStorage
-    get() {
-      var storage = this.caret.registerStorage
-      if (storage == null) {
-        initInjector() // To initialize injector used in CaretRegisterStorageBase
-        storage = CaretRegisterStorageBase(this)
-        this.caret.registerStorage = storage
-      } else if (storage.caret != this) {
-        storage.caret = this
-      }
-      return storage
-    }
+  override val registerStorage: VimRegisterGroup
+    get() = injector.registerGroup
+ 
   override val markStorage: LocalMarkStorage
     get() {
       var storage = this.caret.markStorage
diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt
index 72df0b94d..cfb58f22a 100644
--- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt
+++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt
@@ -179,21 +179,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
     return editor.caretModel.allCarets.map { IjVimCaret(it) }
   }
 
+  override var isFirstCaret = true
+  override var isReversingCarets = false
+  
   @Suppress("ideavimRunForEachCaret")
   override fun forEachCaret(action: (VimCaret) -> Unit) {
     if (editor.vim.inBlockSelection) {
       action(IjVimCaret(editor.caretModel.primaryCaret))
     } else {
-      editor.caretModel.runForEachCaret({
-        if (it.isValid) {
-          action(IjVimCaret(it))
-        }
-      }, false)
+      try {
+        editor.caretModel.runForEachCaret({
+          if (it.isValid) {
+            action(IjVimCaret(it))
+            isFirstCaret = false
+          }
+        }, false)
+      } finally {
+        isFirstCaret = true
+      }
     }
   }
 
   override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
-    editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse)
+    isReversingCarets = reverse
+    try {
+      editor.caretModel.runForEachCaret({
+        action(IjVimCaret(it))
+        isFirstCaret = false
+      }, reverse)
+    } finally {
+      isFirstCaret = true
+      isReversingCarets = false
+    }
   }
 
   override fun isInForEachCaretScope(): Boolean {
diff --git a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/variables/RegisterVariableTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/variables/RegisterVariableTest.kt
index 8fccc9bb2..d0571584c 100644
--- a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/variables/RegisterVariableTest.kt
+++ b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/variables/RegisterVariableTest.kt
@@ -9,6 +9,7 @@
 package org.jetbrains.plugins.ideavim.ex.implementation.variables
 
 import com.maddyhome.idea.vim.api.injector
+import com.maddyhome.idea.vim.newapi.vim
 import org.jetbrains.plugins.ideavim.VimTestCase
 import org.junit.jupiter.api.Test
 import kotlin.test.assertEquals
@@ -43,10 +44,12 @@ class RegisterVariableTest : VimTestCase() {
     configureByText("abcd")
     enterCommand("""vnoremap <expr> y '"' . v:register . 'y'""")
     typeText("vl\"zy")
-    val register = injector.registerGroup.getRegisters()
+    val vimEditor = fixture.editor.vim
+    val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
+    val register = injector.registerGroup.getRegisters(vimEditor, context)
       .filter { reg -> reg.name == 'z' }
       .first()
     assertEquals("ab", register.text)
   }
 
-}
\ No newline at end of file
+}
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertRegisterAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertRegisterAction.kt
index 752fad568..814b91910 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertRegisterAction.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertRegisterAction.kt
@@ -69,15 +69,10 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
  */
 @RWLockLabel.SelfSynchronized
 private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean {
-  val register: Register? = injector.registerGroup.getRegister(editor, context, key)
+  val register: Register? = injector.registerGroup.getRegister(key)
   if (register != null) {
-    val textData = PutData.TextData(
-      register.name,
-      injector.clipboardManager.dumbCopiedText(register.text),
-      SelectionType.CHARACTER_WISE
-    )
-    val putData =
-      PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
+    val textData = PutData.TextData(register.text, SelectionType.CHARACTER_WISE, emptyList(), register.name)
+    val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
     injector.put.putText(editor, context, putData)
     return true
   }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutTextAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutTextAction.kt
index 5d8031d64..61a93633a 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutTextAction.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutTextAction.kt
@@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.action.copy
 import com.intellij.vim.annotations.CommandOrMotion
 import com.intellij.vim.annotations.Mode
 import com.maddyhome.idea.vim.api.ExecutionContext
-import com.maddyhome.idea.vim.api.ImmutableVimCaret
 import com.maddyhome.idea.vim.api.VimEditor
 import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.command.Argument
@@ -36,33 +35,40 @@ sealed class PutTextBaseAction(
     val count = operatorArguments.count1
     val sortedCarets = editor.sortedCarets()
     return if (sortedCarets.size > 1) {
-      val caretToPutData = sortedCarets.associateWith { getPutDataForCaret(editor, context, it, count) }
+      val putData = getPutData(count)
+
+      val splitText = putData.textData?.rawText?.split('\n')?.dropLastWhile(String::isEmpty)
+      val caretToPutData = if (splitText != null && splitText.size == sortedCarets.size) {
+        sortedCarets.mapIndexed { index, caret -> caret to putData.copy(textData = putData.textData.copy(rawText = splitText[splitText.lastIndex - index])) }.toMap()
+      } else {
+        sortedCarets.associateWith { putData }
+      }
+      
       var result = true
       caretToPutData.forEach {
         result = injector.put.putTextForCaret(editor, it.key, context, it.value) && result
       }
       result
     } else {
-      val putData = getPutDataForCaret(editor, context, sortedCarets.single(), count)
-      injector.put.putText(editor, context, putData)
+      injector.put.putText(editor, context, getPutData(count))
     }
   }
 
-  private fun getPutDataForCaret(
-    editor: VimEditor,
-    context: ExecutionContext,
-    caret: ImmutableVimCaret,
-    count: Int,
+  private fun getPutData(count: Int,
   ): PutData {
-    val registerService = injector.registerGroup
-    val registerChar = if (caret.editor.carets().size == 1) {
-      registerService.currentRegister
-    } else {
-      registerService.getCurrentRegisterForMulticaret()
-    }
-    val register = caret.registerStorage.getRegister(editor, context, registerChar)
-    val textData = register?.let { TextData(register) }
-    return PutData(textData, null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
+    return PutData(getRegisterTextData(), null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
+  }
+}
+
+fun getRegisterTextData(): TextData? {
+  val register = injector.registerGroup.getRegister(injector.registerGroup.currentRegister)
+  return register?.let {
+    TextData(
+      register.text ?: injector.parser.toPrintableString(register.keys),
+      register.type,
+      register.transferableData,
+      register.name,
+    )
   }
 }
 
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutVisualTextAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutVisualTextAction.kt
index 321c99d69..55e65c860 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutVisualTextAction.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/copy/PutVisualTextAction.kt
@@ -41,8 +41,22 @@ sealed class PutVisualTextBaseAction(
   ): Boolean {
     if (caretsAndSelections.isEmpty()) return false
     val count = cmd.count
-    val caretToPutData =
-      editor.sortedCarets().associateWith { getPutDataForCaret(editor, context, it, caretsAndSelections[it], count) }
+    val sortedCarets =
+      editor.sortedCarets()
+
+    val textData = getRegisterTextData()
+    val splitText = textData?.rawText?.split('\n')?.dropLastWhile(String::isEmpty)
+
+    val caretToTextData = if (splitText != null && splitText.size == sortedCarets.size) {
+      sortedCarets.mapIndexed { index, caret -> caret to textData.copy(rawText = splitText[splitText.lastIndex - index]) }.toMap()
+    } else {
+      sortedCarets.associateWith { textData }
+    }
+    
+    val caretToPutData = caretToTextData.mapValues { (caret, textData) ->
+      getPutDataForCaret(textData, caret, caretsAndSelections[caret], count)
+    }
+    
     injector.registerGroup.resetRegister()
     var result = true
     caretToPutData.forEach {
@@ -50,17 +64,11 @@ sealed class PutVisualTextBaseAction(
     }
     return result
   }
-
-  private fun getPutDataForCaret(
-    editor: VimEditor,
-    context: ExecutionContext,
+  
+  private fun getPutDataForCaret(textData: PutData.TextData?,
     caret: VimCaret,
     selection: VimSelection?,
-    count: Int,
-  ): PutData {
-    val lastRegisterChar = injector.registerGroup.lastRegisterChar
-    val register = caret.registerStorage.getRegister(editor, context, lastRegisterChar)
-    val textData = register?.let { PutData.TextData(register) }
+    count: Int,): PutData {
     val visualSelection = selection?.let { PutData.VisualSelection(mapOf(caret to it), it.type) }
     return PutData(textData, visualSelection, count, insertTextBeforeCaret, indent, caretAfterInsertedText)
   }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaret.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaret.kt
index 835d32fa1..cb14523b4 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaret.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaret.kt
@@ -9,7 +9,6 @@
 package com.maddyhome.idea.vim.api
 
 import com.maddyhome.idea.vim.common.LiveRange
-import com.maddyhome.idea.vim.common.TextRange
 import com.maddyhome.idea.vim.group.visual.VisualChange
 import com.maddyhome.idea.vim.group.visual.vimMoveBlockSelectionToOffset
 import com.maddyhome.idea.vim.group.visual.vimMoveSelectionToCaret
@@ -17,13 +16,11 @@ import com.maddyhome.idea.vim.handler.Motion
 import com.maddyhome.idea.vim.helper.RWLockLabel
 import com.maddyhome.idea.vim.helper.StrictMode
 import com.maddyhome.idea.vim.helper.exitVisualMode
-import com.maddyhome.idea.vim.register.Register
-import com.maddyhome.idea.vim.state.mode.SelectionType
+import com.maddyhome.idea.vim.register.VimRegisterGroup
 import com.maddyhome.idea.vim.state.mode.inBlockSelection
 import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual
 import com.maddyhome.idea.vim.state.mode.inSelectMode
 import com.maddyhome.idea.vim.state.mode.inVisualMode
-import javax.swing.KeyStroke
 
 /**
  * Immutable interface of the caret. Immutable caret is an important concept of Fleet.
@@ -65,7 +62,7 @@ interface ImmutableVimCaret {
   fun hasSelection(): Boolean
 
   var lastSelectionInfo: SelectionInfo
-  val registerStorage: CaretRegisterStorage
+  val registerStorage: VimRegisterGroup
   val markStorage: LocalMarkStorage
 }
 
@@ -149,36 +146,3 @@ fun VimCaret.moveToMotion(motion: Motion): VimCaret {
     this
   }
 }
-
-interface CaretRegisterStorage {
-  val caret: ImmutableVimCaret
-
-  /**
-   * Stores text to caret's recordable (named/numbered/unnamed) register
-   */
-  @Deprecated("Please use the same method, but with ExecutionContext")
-  fun storeText(editor: VimEditor, range: TextRange, type: SelectionType, isDelete: Boolean): Boolean
-  fun storeText(
-    editor: VimEditor,
-    context: ExecutionContext,
-    range: TextRange,
-    type: SelectionType,
-    isDelete: Boolean,
-  ): Boolean
-
-  /**
-   * Gets text from caret's recordable register
-   * If the register is not recordable - global text state will be returned
-   */
-  @Deprecated("Please use com.maddyhome.idea.vim.api.CaretRegisterStorage#getRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)")
-  fun getRegister(r: Char): Register?
-  fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register?
-
-  @Deprecated("Please use com.maddyhome.idea.vim.api.CaretRegisterStorage#setKeys(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.util.List<? extends javax.swing.KeyStroke>)")
-  fun setKeys(register: Char, keys: List<KeyStroke>)
-  fun setKeys(editor: VimEditor, context: ExecutionContext, register: Char, keys: List<KeyStroke>)
-
-  @Deprecated("Please use com.maddyhome.idea.vim.api.CaretRegisterStorage#saveRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, com.maddyhome.idea.vim.register.Register)")
-  fun saveRegister(r: Char, register: Register)
-  fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register)
-}
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaretBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaretBase.kt
index 85685864e..32d88b282 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaretBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimCaretBase.kt
@@ -8,106 +8,4 @@
 
 package com.maddyhome.idea.vim.api
 
-import com.maddyhome.idea.vim.common.TextRange
-import com.maddyhome.idea.vim.register.Register
-import com.maddyhome.idea.vim.register.RegisterConstants
-import com.maddyhome.idea.vim.register.VimRegisterGroupBase
-import com.maddyhome.idea.vim.state.mode.SelectionType
-import javax.swing.KeyStroke
-
 abstract class VimCaretBase : VimCaret
-
-open class CaretRegisterStorageBase(override var caret: ImmutableVimCaret) : CaretRegisterStorage,
-  VimRegisterGroupBase() {
-  companion object {
-    private const val ALLOWED_TO_STORE_REGISTERS = RegisterConstants.RECORDABLE_REGISTERS +
-      RegisterConstants.SMALL_DELETION_REGISTER +
-      RegisterConstants.BLACK_HOLE_REGISTER +
-      RegisterConstants.LAST_INSERTED_TEXT_REGISTER +
-      RegisterConstants.LAST_SEARCH_REGISTER
-  }
-
-  override var lastRegisterChar: Char
-    get() {
-      return injector.registerGroup.lastRegisterChar
-    }
-    set(_) {}
-
-  override var isRegisterSpecifiedExplicitly: Boolean
-    get() {
-      return injector.registerGroup.isRegisterSpecifiedExplicitly
-    }
-    set(_) {}
-
-  @Deprecated("Please use the same method, but with ExecutionContext")
-  override fun storeText(editor: VimEditor, range: TextRange, type: SelectionType, isDelete: Boolean): Boolean {
-    val context = injector.executionContextManager.getEditorExecutionContext(editor)
-    return storeText(editor, context, caret, range, type, isDelete)
-  }
-
-  override fun storeText(
-    editor: VimEditor,
-    context: ExecutionContext,
-    range: TextRange,
-    type: SelectionType,
-    isDelete: Boolean,
-  ): Boolean {
-    val registerChar = if (caret.editor.carets().size == 1) currentRegister else getCurrentRegisterForMulticaret()
-    if (caret.isPrimary) {
-      val registerService = injector.registerGroup
-      registerService.lastRegisterChar = registerChar
-      return registerService.storeText(editor, context, caret, range, type, isDelete)
-    } else {
-      if (!ALLOWED_TO_STORE_REGISTERS.contains(registerChar)) {
-        return false
-      }
-      val text = preprocessTextBeforeStoring(editor.getText(range), type)
-      return storeTextInternal(editor, context, range, text, type, registerChar, isDelete)
-    }
-  }
-
-  override fun getRegister(r: Char): Register? {
-    val editorStub = injector.fallbackWindow
-    val contextStub = injector.executionContextManager.getEditorExecutionContext(editorStub)
-    return getRegister(editorStub, contextStub, r)
-  }
-
-  override fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? {
-    if (caret.isPrimary || !RegisterConstants.RECORDABLE_REGISTERS.contains(r)) {
-      return injector.registerGroup.getRegister(editor, context, r)
-    }
-    return super.getRegister(editor, context, r) ?: injector.registerGroup.getRegister(editor, context, r)
-  }
-
-  override fun setKeys(register: Char, keys: List<KeyStroke>) {
-    val editorStub = injector.fallbackWindow
-    val contextStub = injector.executionContextManager.getEditorExecutionContext(editorStub)
-    setKeys(editorStub, contextStub, register, keys)
-  }
-
-  override fun setKeys(editor: VimEditor, context: ExecutionContext, register: Char, keys: List<KeyStroke>) {
-    if (caret.isPrimary) {
-      injector.registerGroup.setKeys(register, keys)
-    }
-    if (!RegisterConstants.RECORDABLE_REGISTERS.contains(register)) {
-      return
-    }
-    return super.setKeys(register, keys)
-  }
-
-  override fun saveRegister(r: Char, register: Register) {
-    val editorStub = injector.fallbackWindow
-    val contextStub = injector.executionContextManager.getEditorExecutionContext(editorStub)
-    saveRegister(editorStub, contextStub, r, register)
-  }
-
-  override fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) {
-    if (caret.isPrimary) {
-      injector.registerGroup.saveRegister(editor, context, r, register)
-    }
-    if (!RegisterConstants.RECORDABLE_REGISTERS.contains(r)) {
-      return
-    }
-    return super.saveRegister(editor, context, r, register)
-  }
-}
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt
index 21d2a7711..1191280dc 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt
@@ -182,31 +182,41 @@ abstract class VimChangeGroupBase : VimChangeGroup {
         return false
       }
     }
-
-    val isInsertMode = editor.mode == Mode.INSERT || editor.mode == Mode.REPLACE
-    val shouldYank = type != null && !isInsertMode && saveToRegister
-    if (shouldYank && !caret.registerStorage.storeText(editor, context, updatedRange, type, isDelete = true)) {
-      return false
-    }
-
-    val startOffsets = updatedRange.startOffsets
-    val endOffsets = updatedRange.endOffsets
-    for (i in updatedRange.size() - 1 downTo 0) {
-      val (newRange, _) = editor.search(
-        startOffsets[i] to endOffsets[i],
+    val mode = editor.mode
+    if (type == null ||
+      (mode == Mode.INSERT || mode == Mode.REPLACE) ||
+      !saveToRegister ||
+      injector.registerGroup.storeText(
         editor,
-        LineDeleteShift.NL_ON_END
-      ) ?: continue
-      injector.application.runWriteAction {
+        context,
+        caret,
+        updatedRange,
+        type,
+        true,
+        !editor.isFirstCaret,
+        editor.isReversingCarets
+      )
+    ) {
+      val startOffsets = updatedRange.startOffsets
+      val endOffsets = updatedRange.endOffsets
+      for (i in updatedRange.size() - 1 downTo 0) {
+        val (newRange, _) = editor.search(
+          startOffsets[i] to endOffsets[i],
+          editor,
+          LineDeleteShift.NL_ON_END
+        ) ?: continue
+        injector.application.runWriteAction {
         editor.deleteString(TextRange(newRange.first, newRange.second))
       }
+      }
+      if (type != null) {
+        val start = updatedRange.startOffset
+        injector.markService.setMark(caret, MARK_CHANGE_POS, start)
+        injector.markService.setChangeMarks(caret, TextRange(start, start + 1))
+      }
+      return true
     }
-    if (type != null) {
-      val start = updatedRange.startOffset
-      injector.markService.setMark(caret, MARK_CHANGE_POS, start)
-      injector.markService.setChangeMarks(caret, TextRange(start, start + 1))
-    }
-    return true
+    return false
   }
 
   /**
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt
index a0918f44d..8fb821005 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt
@@ -111,7 +111,8 @@ interface VimEditor {
    * This method should perform caret merging after the operations. This is similar to IJ runForEachCaret
    * TODO review
    */
-
+  val isFirstCaret: Boolean
+  val isReversingCarets: Boolean
   fun forEachCaret(action: (VimCaret) -> Unit)
   fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean = false)
   fun isInForEachCaretScope(): Boolean
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimInjectorBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimInjectorBase.kt
index 7cb6e7fcf..bffaf478a 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimInjectorBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimInjectorBase.kt
@@ -17,8 +17,6 @@ import com.maddyhome.idea.vim.common.VimListenersNotifier
 import com.maddyhome.idea.vim.diagnostic.VimLogger
 import com.maddyhome.idea.vim.diagnostic.vimLogger
 import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
-import com.maddyhome.idea.vim.register.VimRegisterGroup
-import com.maddyhome.idea.vim.register.VimRegisterGroupBase
 import com.maddyhome.idea.vim.state.VimStateMachine
 import com.maddyhome.idea.vim.vimscript.services.VariableService
 import com.maddyhome.idea.vim.vimscript.services.VimVariableServiceBase
@@ -28,7 +26,6 @@ import com.maddyhome.idea.vim.yank.YankGroupBase
 abstract class VimInjectorBase : VimInjector {
   companion object {
     val logger: VimLogger by lazy { vimLogger<VimInjectorBase>() }
-    val registerGroupStub: VimRegisterGroupBase by lazy { object : VimRegisterGroupBase() {} }
   }
 
   override val vimState: VimStateMachine = VimStateMachineImpl()
@@ -38,8 +35,6 @@ abstract class VimInjectorBase : VimInjector {
 
   override val variableService: VariableService by lazy { object : VimVariableServiceBase() {} }
 
-  override val registerGroup: VimRegisterGroup by lazy { registerGroupStub }
-  override val registerGroupIfCreated: VimRegisterGroup? by lazy { registerGroupStub }
   override val messages: VimMessages by lazy { VimMessagesStub() }
   override val processGroup: VimProcessGroup by lazy { VimProcessGroupStub() }
   override val application: VimApplication by lazy { VimApplicationStub() }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt
index 327796f4f..4de781dc9 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt
@@ -8,7 +8,6 @@
 
 package com.maddyhome.idea.vim.common
 
-import com.maddyhome.idea.vim.api.ImmutableVimCaret
 import com.maddyhome.idea.vim.api.VimEditor
 import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.state.mode.Mode
@@ -72,9 +71,9 @@ class VimListenersNotifier {
     isReplaceCharListeners.forEach { it.isReplaceCharChanged(editor) }
   }
 
-  fun notifyYankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) {
+  fun notifyYankPerformed(editor: VimEditor, range: TextRange) {
     if (!injector.enabler.isEnabled()) return // we remove all the listeners when turning the plugin off, but let's do it just in case
-    yankListeners.forEach { it.yankPerformed(caretToRange) }
+    yankListeners.forEach { it.yankPerformed(editor, range) }
   }
 
   fun reset() {
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimYankListener.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimYankListener.kt
index 52709aa86..30da65814 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimYankListener.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimYankListener.kt
@@ -8,8 +8,8 @@
 
 package com.maddyhome.idea.vim.common
 
-import com.maddyhome.idea.vim.api.ImmutableVimCaret
+import com.maddyhome.idea.vim.api.VimEditor
 
 interface VimYankListener {
-  fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>)
-}
\ No newline at end of file
+  fun yankPerformed(editor: VimEditor, range: TextRange)
+}
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/ProcessedTextData.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/ProcessedTextData.kt
index a18fa08e7..e5249f061 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/ProcessedTextData.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/ProcessedTextData.kt
@@ -8,11 +8,11 @@
 
 package com.maddyhome.idea.vim.put
 
-import com.maddyhome.idea.vim.common.VimCopiedText
 import com.maddyhome.idea.vim.state.mode.SelectionType
 
 data class ProcessedTextData(
-  val registerChar: Char?,
-  val copiedText: VimCopiedText,
+  val text: String,
   val typeInRegister: SelectionType,
+  val transferableData: List<Any>,
+  val registerChar: Char?,
 )
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/PutData.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/PutData.kt
index a4053a5ab..120cf7dc1 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/PutData.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/PutData.kt
@@ -9,9 +9,7 @@
 package com.maddyhome.idea.vim.put
 
 import com.maddyhome.idea.vim.api.VimCaret
-import com.maddyhome.idea.vim.common.VimCopiedText
 import com.maddyhome.idea.vim.group.visual.VimSelection
-import com.maddyhome.idea.vim.register.Register
 import com.maddyhome.idea.vim.state.mode.SelectionType
 
 /**
@@ -35,12 +33,9 @@ data class PutData(
   )
 
   data class TextData(
-    val registerChar: Char?,
-    val copiedText: VimCopiedText,
+    val rawText: String?,
     val typeInRegister: SelectionType,
-  ) {
-    constructor(register: Register) : this(register.name, register.copiedText, register.type)
-
-    val rawText = copiedText.text // TODO do not call it raw text...
-  }
+    val transferableData: List<Any>,
+    val registerChar: Char?,
+  )
 }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/VimPutBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/VimPutBase.kt
index 4aab09ee1..b6414f780 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/VimPutBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/put/VimPutBase.kt
@@ -141,7 +141,6 @@ abstract class VimPutBase : VimPut {
 
     if (data.visualSelection?.typeInEditor?.isLine == true && data.textData.typeInRegister.isChar) text += "\n"
 
-    // TODO: shouldn't it be adjusted when we are storing the text?
     if (data.textData.typeInRegister.isLine && text.isNotEmpty() && text.last() != '\n') text += '\n'
 
     if (data.textData.typeInRegister.isChar && text.lastOrNull() == '\n' && data.visualSelection?.typeInEditor?.isLine == false) {
@@ -150,9 +149,10 @@ abstract class VimPutBase : VimPut {
     }
 
     return ProcessedTextData(
-      data.textData.registerChar,
-      data.textData.copiedText.updateText(text),
+      text,
       data.textData.typeInRegister,
+      data.textData.transferableData,
+      data.textData.registerChar,
     )
   }
 
@@ -512,7 +512,7 @@ abstract class VimPutBase : VimPut {
     startOffsets.forEach { startOffset ->
       val selectionType = data.visualSelection?.typeInEditor ?: SelectionType.CHARACTER_WISE
       val (endOffset, updatedCaret) = putTextInternal(
-        editor, updated, context, text.copiedText.text, text.typeInRegister, selectionType,
+        editor, updated, context, text.text, text.typeInRegister, selectionType,
         startOffset, data.count, data.indent, data.caretAfterInsertedText,
       )
       updated = updatedCaret
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt
index cf699ab6b..fdecc15ae 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/regexp/VimRegex.kt
@@ -633,7 +633,13 @@ class VimRegex(pattern: String) {
     override fun carets(): List<VimCaret> = emptyList()
 
     override fun nativeCarets(): List<VimCaret> = emptyList()
+    
+    override val isFirstCaret: Boolean
+      get() = false
 
+    override val isReversingCarets: Boolean
+      get() = false
+    
     override fun forEachCaret(action: (VimCaret) -> Unit) {}
 
     override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {}
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/Register.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/Register.kt
index f775c89eb..9f25f346d 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/Register.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/Register.kt
@@ -8,32 +8,86 @@
 package com.maddyhome.idea.vim.register
 
 import com.maddyhome.idea.vim.api.injector
-import com.maddyhome.idea.vim.common.VimCopiedText
 import com.maddyhome.idea.vim.helper.EngineStringHelper
 import com.maddyhome.idea.vim.state.mode.SelectionType
 import org.jetbrains.annotations.NonNls
+import java.awt.event.KeyEvent
 import javax.swing.KeyStroke
 
-// TODO should we prefer keys over text, as they are more informative?
-// TODO e.g.  could be both <Esc> and <C-[> after trying to restore original keys
-data class Register(
-  val name: Char,
-  val copiedText: VimCopiedText,
-  val type: SelectionType,
-) {
-  val text = copiedText.text
-  val keys get() = injector.parser.stringToKeys(copiedText.text)
-  val printableString: String =
-    EngineStringHelper.toPrintableCharacters(keys) // should be the same as [text], but we can't render control notation properly
+class Register {
+  var name: Char
+  val type: SelectionType
+  val keys: MutableList<KeyStroke>
+  val transferableData: MutableList<out Any>
+  val rawText: String?
 
-  constructor(name: Char, type: SelectionType, keys: MutableList<KeyStroke>) : this(
-    name,
-    injector.clipboardManager.dumbCopiedText(injector.parser.toPrintableString(keys)),
-    type
-  )
-//  constructor(name: Char, type: SelectionType, text: String, transferableData: MutableList<out Any>) : this(name, text, type, transferableData)
+  constructor(name: Char, type: SelectionType, keys: MutableList<KeyStroke>) {
+    this.name = name
+    this.type = type
+    this.keys = keys
+    this.transferableData = mutableListOf()
+    this.rawText = text
+  }
 
-  override fun toString(): String = "@$name = $printableString"
+  constructor(
+    name: Char,
+    type: SelectionType,
+    text: String,
+    transferableData: MutableList<out Any>,
+  ) {
+    this.name = name
+    this.type = type
+    this.keys = injector.parser.stringToKeys(text).toMutableList()
+    this.transferableData = transferableData
+    this.rawText = text
+  }
+
+  constructor(
+    name: Char,
+    type: SelectionType,
+    text: String,
+    transferableData: MutableList<out Any>,
+    rawText: String,
+  ) {
+    this.name = name
+    this.type = type
+    this.keys = injector.parser.stringToKeys(text).toMutableList()
+    this.transferableData = transferableData
+    this.rawText = rawText
+  }
+
+  val text: String?
+    get() {
+      val builder = StringBuilder()
+      for (key in keys) {
+        val c = key.keyChar
+        if (c == KeyEvent.CHAR_UNDEFINED) {
+          return null
+        }
+        builder.append(c)
+      }
+      return builder.toString()
+    }
+
+  val printableString: String
+    get() = EngineStringHelper.toPrintableCharacters(keys) // should be the same as [text], but we can't render control notation properly
+  
+  /**
+   * Append the supplied text to any existing text.
+   */
+  fun addTextAndResetTransferableData(text: String) {
+    addKeys(injector.parser.stringToKeys(text))
+    transferableData.clear()
+  }
+  
+  fun prependTextAndResetTransferableData(text: String) {
+    this.keys.addAll(0, injector.parser.stringToKeys(text))
+    transferableData.clear()
+  }
+
+  fun addKeys(keys: List<KeyStroke>) {
+    this.keys.addAll(keys)
+  }
 
   object KeySorter : Comparator<Register> {
     @NonNls
@@ -44,27 +98,3 @@ data class Register(
     }
   }
 }
-
-/**
- * Imagine you yanked two lines and have the following content in your register a - foo\nbar\n (register type is line-wise)
- * Now, there are three different ways to append content, each with a different outcome:
- * - If you append a macro qAbazq, you'll get foo\nbarbaz\n in register `a` and it stays line-wise
- * - If you use Vim script and execute let @A = "baz", the result will be foo\nbar\nbaz and the register becomes character-wise
- * - If you copy "baz" to register A, it becomes foo\nbar\nbaz\n and stays line-wise
- *
- * At the moment, we will stick to the third option to not overcomplicate the plugin
- * (until there is a user who notices the difference)
- */
-fun Register.addText(text: String): Register {
-  return when (this.type) {
-    SelectionType.CHARACTER_WISE -> {
-      Register(this.name, injector.clipboardManager.dumbCopiedText(this.text + text), SelectionType.CHARACTER_WISE) // todo it's empty for historical reasons, but should we really clear transferable data?
-    }
-    SelectionType.LINE_WISE -> {
-      Register(this.name, injector.clipboardManager.dumbCopiedText(this.text + text + (if (text.endsWith('\n')) "" else "\n")), SelectionType.LINE_WISE) // todo it's empty for historical reasons, but should we really clear transferable data?
-    }
-    SelectionType.BLOCK_WISE -> {
-      Register(this.name, injector.clipboardManager.dumbCopiedText(this.text + "\n" + text), SelectionType.BLOCK_WISE) // todo it's empty for historical reasons, but should we really clear transferable data?
-    }
-  }
-}
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt
index f487a9936..82a4825a6 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt
@@ -17,6 +17,13 @@ import javax.swing.KeyStroke
 
 interface VimRegisterGroup {
 
+  /**
+   * Get the last register selected by the user
+   *
+   * @return The register, null if no such register
+   */
+  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getLastRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)")
+  val lastRegister: Register?
   var lastRegisterChar: Char
   val currentRegister: Char
 
@@ -32,7 +39,6 @@ interface VimRegisterGroup {
   val isRegisterSpecifiedExplicitly: Boolean
   val defaultRegister: Char
 
-  fun getLastRegister(editor: VimEditor, context: ExecutionContext): Register?
   fun isValid(reg: Char): Boolean
   fun selectRegister(reg: Char): Boolean
   fun resetRegister()
@@ -42,15 +48,6 @@ interface VimRegisterGroup {
   fun isRegisterWritable(reg: Char): Boolean
 
   /** Store text into the last register. */
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, com.maddyhome.idea.vim.api.ImmutableVimCaret, com.maddyhome.idea.vim.common.TextRange, com.maddyhome.idea.vim.state.mode.SelectionType, boolean)")
-  fun storeText(
-    editor: VimEditor,
-    caret: ImmutableVimCaret,
-    range: TextRange,
-    type: SelectionType,
-    isDelete: Boolean,
-  ): Boolean
-
   fun storeText(
     editor: VimEditor,
     context: ExecutionContext,
@@ -58,31 +55,20 @@ interface VimRegisterGroup {
     range: TextRange,
     type: SelectionType,
     isDelete: Boolean,
+    forceAppend: Boolean = false,
+    prependInsteadOfAppend: Boolean = false
   ): Boolean
 
   /**
    * Stores text to any writable register (used for the let command)
    */
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String)")
-  fun storeText(register: Char, text: String): Boolean
   fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String): Boolean
 
   /**
    * Stores text to any writable register (used for multicaret tests)
    */
   @TestOnly
-  // todo better tests
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String, com.maddyhome.idea.vim.state.mode.SelectionType)")
-  fun storeText(register: Char, text: String, selectionType: SelectionType): Boolean
-
-  @TestOnly
-  fun storeText(
-    editor: VimEditor,
-    context: ExecutionContext,
-    register: Char,
-    text: String,
-    selectionType: SelectionType,
-  ): Boolean
+  fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String, selectionType: SelectionType): Boolean
 
   /**
    * Stores text, character wise, in the given special register
@@ -98,29 +84,17 @@ interface VimRegisterGroup {
    * preferable to yank from the fixture editor.
    */
   fun storeTextSpecial(register: Char, text: String): Boolean
-
   @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)")
   fun getRegister(r: Char): Register?
   fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register?
-
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegisters(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)")
-  fun getRegisters(): List<Register>
   fun getRegisters(editor: VimEditor, context: ExecutionContext): List<Register>
-
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#saveRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, com.maddyhome.idea.vim.register.Register)")
-  fun saveRegister(r: Char, register: Register)
   fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register)
   fun startRecording(register: Char): Boolean
 
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getPlaybackRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)")
-  fun getPlaybackRegister(r: Char): Register?
   fun getPlaybackRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register?
   fun recordText(text: String)
   fun setKeys(register: Char, keys: List<KeyStroke>)
   fun setKeys(register: Char, keys: List<KeyStroke>, type: SelectionType)
-
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#finishRecording(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)")
-  fun finishRecording()
   fun finishRecording(editor: VimEditor, context: ExecutionContext)
   fun getCurrentRegisterForMulticaret(): Char // `set clipbaard+=unnamedplus` should not make system register the default one when working with multiple carets VIM-2804
   fun isSystemClipboard(register: Char): Boolean
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt
index 908a41b25..8a56a0e10 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt
@@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.api.globalOptions
 import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.command.Argument
 import com.maddyhome.idea.vim.common.TextRange
-import com.maddyhome.idea.vim.common.VimCopiedText
 import com.maddyhome.idea.vim.diagnostic.VimLogger
 import com.maddyhome.idea.vim.diagnostic.debug
 import com.maddyhome.idea.vim.diagnostic.vimLogger
@@ -75,9 +74,13 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
   override val defaultRegister: Char
     get() = defaultRegisterChar
 
-  override fun getLastRegister(editor: VimEditor, context: ExecutionContext): Register? {
-    return getRegister(editor, context, lastRegisterChar)
-  }
+  /**
+   * Get the last register selected by the user
+   *
+   * @return The register, null if no such register
+   */
+  override val lastRegister: Register?
+    get() = getRegister(lastRegisterChar)
 
   private val onClipboardChanged: () -> Unit = {
     val clipboardOptionValue = injector.globalOptions().clipboard
@@ -113,13 +116,20 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
     return if (isValid(reg)) {
       isRegisterSpecifiedExplicitly = true
       lastRegisterChar = reg
-      logger.debug { "register selected: $lastRegisterChar" }
+      logger.debug { "register selected: $lastRegister" }
+
       true
     } else {
       false
     }
   }
 
+  override fun getRegister(r: Char): Register? {
+    val dummyEditor = injector.fallbackWindow
+    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
+    return getRegister(dummyEditor, dummyContext, r)
+  }
+
   /**
    * Reset the selected register back to the default register.
    */
@@ -175,6 +185,8 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
     type: SelectionType,
     register: Char,
     isDelete: Boolean,
+    forceAppend: Boolean,
+    prependInsteadOfAppend: Boolean,
   ): Boolean {
     // Null register doesn't get saved, but acts like it was
     if (lastRegisterChar == BLACK_HOLE_REGISTER) return true
@@ -193,50 +205,62 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
       end = t
     }
 
-    val copiedText =
-      if (start != -1) { // FIXME: so, we had invalid ranges all the time?.. I've never handled such cases
-        injector.clipboardManager.collectCopiedText(editor, context, range, text)
-      } else {
-        injector.clipboardManager.dumbCopiedText(text)
-      }
-    logger.debug { "Copy to '$lastRegisterChar' with copied text: $copiedText" }
     // If this is an uppercase register, we need to append the text to the corresponding lowercase register
-    if (Character.isUpperCase(register)) {
+    val transferableData: List<Any> =
+      if (start != -1) injector.clipboardManager.getTransferableData(editor, range) else ArrayList()
+    var processedText =
+      if (start != -1) injector.clipboardManager.preprocessText(editor, range, text, transferableData) else text
+    logger.debug {
+      val transferableClasses = transferableData.joinToString(",") { it.javaClass.name }
+      "Copy to '$lastRegister' with transferable data: $transferableClasses"
+    }
+    if (Character.isUpperCase(register) || forceAppend) {
+      if (forceAppend && type == SelectionType.CHARACTER_WISE) {
+        processedText = if (prependInsteadOfAppend)
+          processedText + '\n'
+        else
+          '\n' + processedText
+      }
       val lreg = Character.toLowerCase(register)
       val r = myRegisters[lreg]
       // Append the text if the lowercase register existed
       if (r != null) {
-        myRegisters[lreg] = r.addText(copiedText.text)
+        if (prependInsteadOfAppend) {
+          r.prependTextAndResetTransferableData(processedText)
+        }
+        else {
+          r.addTextAndResetTransferableData(processedText)
+        }
       } else {
-        myRegisters[lreg] = Register(lreg, copiedText, type)
-        logger.debug { "register '$register' contains: \"$copiedText\"" }
+        myRegisters[lreg] = Register(lreg, type, processedText, ArrayList(transferableData))
+        logger.debug { "register '$register' contains: \"$processedText\"" }
       } // Set the text if the lowercase register didn't exist yet
     } else {
-      myRegisters[register] = Register(register, copiedText, type)
-      logger.debug { "register '$register' contains: \"$copiedText\"" }
+      myRegisters[register] = Register(register, type, processedText, ArrayList(transferableData))
+      logger.debug { "register '$register' contains: \"$processedText\"" }
     } // Put the text in the specified register
 
     if (register == CLIPBOARD_REGISTER) {
-      injector.clipboardManager.setClipboardContent(editor, context, copiedText)
+      injector.clipboardManager.setClipboardText(processedText, text, ArrayList(transferableData))
       if (!isRegisterSpecifiedExplicitly && !isDelete && isPrimaryRegisterSupported() && OptionConstants.clipboard_unnamedplus in injector.globalOptions().clipboard) {
-        injector.clipboardManager.setPrimaryContent(editor, context, copiedText)
+        injector.clipboardManager.setPrimaryText(processedText, text, ArrayList(transferableData))
       }
     }
     if (register == PRIMARY_REGISTER) {
       if (isPrimaryRegisterSupported()) {
-        injector.clipboardManager.setPrimaryContent(editor, context, copiedText)
+        injector.clipboardManager.setPrimaryText(processedText, text, ArrayList(transferableData))
         if (!isRegisterSpecifiedExplicitly && !isDelete && OptionConstants.clipboard_unnamed in injector.globalOptions().clipboard) {
-          injector.clipboardManager.setClipboardContent(editor, context, copiedText)
+          injector.clipboardManager.setClipboardText(processedText, text, ArrayList(transferableData))
         }
       } else {
-        injector.clipboardManager.setClipboardContent(editor, context, copiedText)
+        injector.clipboardManager.setClipboardText(processedText, text, ArrayList(transferableData))
       }
     }
 
     // Also add it to the unnamed register if the default wasn't specified
     if (register != UNNAMED_REGISTER && ".:/".indexOf(register) == -1) {
-      myRegisters[UNNAMED_REGISTER] = Register(UNNAMED_REGISTER, copiedText, type)
-      logger.debug { "register '$UNNAMED_REGISTER' contains: \"$copiedText\"" }
+      myRegisters[UNNAMED_REGISTER] = Register(UNNAMED_REGISTER, type, processedText, ArrayList(transferableData))
+      logger.debug { "register '$UNNAMED_REGISTER' contains: \"$processedText\"" }
     }
 
     if (isDelete) {
@@ -256,44 +280,26 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
         while (d >= '1') {
           val t = myRegisters[d]
           if (t != null) {
-            val incName = (d.code + 1).toChar()
-            myRegisters[incName] = Register(incName, t.copiedText, t.type)
+            t.name = (d.code + 1).toChar()
+            myRegisters[(d.code + 1).toChar()] = t
           }
           d--
         }
-        myRegisters['1'] = Register('1', copiedText, type)
+        myRegisters['1'] = Register('1', type, processedText, ArrayList(transferableData))
       }
 
       // Deletes smaller than one line and without specified register go the the "-" register
       if (smallInlineDeletion && register == defaultRegister) {
         myRegisters[SMALL_DELETION_REGISTER] =
-          Register(SMALL_DELETION_REGISTER, copiedText, type)
+          Register(SMALL_DELETION_REGISTER, type, processedText, ArrayList(transferableData))
       }
     } else if (register == defaultRegister) {
-      myRegisters['0'] = Register('0', copiedText, type)
-      logger.debug { "register '0' contains: \"$copiedText\"" }
+      myRegisters['0'] = Register('0', type, processedText, ArrayList(transferableData))
+      logger.debug { "register '0' contains: \"$processedText\"" }
     } // Yanks also go to register 0 if the default register was used
     return true
   }
-
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, com.maddyhome.idea.vim.api.ImmutableVimCaret, com.maddyhome.idea.vim.common.TextRange, com.maddyhome.idea.vim.state.mode.SelectionType, boolean)")
-  override fun storeText(
-    editor: VimEditor,
-    caret: ImmutableVimCaret,
-    range: TextRange,
-    type: SelectionType,
-    isDelete: Boolean,
-  ): Boolean {
-    return storeText(
-      editor,
-      injector.executionContextManager.getEditorExecutionContext(editor),
-      caret,
-      range,
-      type,
-      isDelete
-    )
-  }
-
+  
   /**
    * Store text into the last register.
    *
@@ -310,10 +316,12 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
     range: TextRange,
     type: SelectionType,
     isDelete: Boolean,
+    forceAppend: Boolean,
+    prependInsteadOfAppend: Boolean
   ): Boolean {
     if (isRegisterWritable()) {
       val text = preprocessTextBeforeStoring(editor.getText(range), type)
-      return storeTextInternal(editor, context, range, text, type, lastRegisterChar, isDelete)
+      return storeTextInternal(editor, context, range, text, type, lastRegisterChar, isDelete, forceAppend, prependInsteadOfAppend)
     }
 
     return false
@@ -348,68 +356,36 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
     if (READONLY_REGISTERS.indexOf(register) == -1 && register != LAST_SEARCH_REGISTER && register != UNNAMED_REGISTER) {
       return false
     }
-    myRegisters[register] = Register(
-      register,
-      injector.clipboardManager.dumbCopiedText(text),
+    myRegisters[register] = Register(register,
       SelectionType.CHARACTER_WISE
-    ) // TODO why transferable data is not collected?
+    , text, ArrayList())
     logger.debug { "register '$register' contains: \"$text\"" }
     return true
   }
 
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)")
-  override fun getRegister(r: Char): Register? {
-    val dummyEditor = injector.fallbackWindow
-    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
-    return getRegister(dummyEditor, dummyContext, r)
+  override fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String): Boolean {
+    return storeText(editor, context, register, text, SelectionType.CHARACTER_WISE)
   }
-
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String, com.maddyhome.idea.vim.state.mode.SelectionType)")
-  override fun storeText(register: Char, text: String, selectionType: SelectionType): Boolean {
-    val dummyEditor = injector.fallbackWindow
-    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
-    return storeText(dummyEditor, dummyContext, register, text, selectionType)
-  }
-
-  override fun storeText(
-    editor: VimEditor,
-    context: ExecutionContext,
-    register: Char,
-    text: String,
-    selectionType: SelectionType,
+  
+  override fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String, selectionType: SelectionType,
   ): Boolean {
     if (!WRITABLE_REGISTERS.contains(register)) {
       return false
     }
     logger.debug { "register '$register' contains: \"$text\"" }
-    val oldRegister = getRegister(editor, context, register.lowercaseChar())
-    val newRegister = if (register.isUpperCase() && oldRegister != null) {
-      oldRegister.addText(text)
+    val textToStore = if (register.isUpperCase()) {
+      (getRegister(register.lowercaseChar())?.rawText ?: "") + text
     } else {
-      Register(
-        register,
-        injector.clipboardManager.dumbCopiedText(text),
-        selectionType
-      ) // FIXME why don't we collect transferable data?
+      text
     }
-    saveRegister(editor, context, register, newRegister)
+    val reg = Register(register, selectionType, textToStore, ArrayList())
+    saveRegister(editor, context, register, reg)
     if (register == '/') {
       injector.searchGroup.lastSearchPattern = text // todo we should not have this field if we have the "/" register
     }
     return true
   }
 
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#storeText(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, java.lang.String)")
-  override fun storeText(register: Char, text: String): Boolean {
-    val dummyEditor = injector.fallbackWindow
-    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
-    return storeText(dummyEditor, dummyContext, register, text)
-  }
-
-  override fun storeText(editor: VimEditor, context: ExecutionContext, register: Char, text: String): Boolean {
-    return storeText(editor, context, register, text, SelectionType.CHARACTER_WISE)
-  }
-
   private fun guessSelectionType(text: String): SelectionType {
     return if (text.endsWith("\n")) SelectionType.LINE_WISE else SelectionType.CHARACTER_WISE
   }
@@ -421,10 +397,10 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
    * @param r - the register character corresponding to either the primary selection (*) or clipboard selection (+)
    * @return the content of the selection, if available, otherwise null
    */
-  private fun refreshClipboardRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? {
+  private fun refreshClipboardRegister(r: Char): Register? {
     return when (r) {
-      PRIMARY_REGISTER -> refreshPrimaryRegister(editor, context)
-      CLIPBOARD_REGISTER -> refreshClipboardRegister(editor, context)
+      PRIMARY_REGISTER -> refreshPrimaryRegister()
+      CLIPBOARD_REGISTER -> refreshClipboardRegister()
       else -> throw RuntimeException("Clipboard register expected, got $r")
     }
   }
@@ -433,56 +409,60 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
     return System.getenv("DISPLAY") != null && injector.systemInfoService.isXWindow
   }
 
-  private fun setSystemPrimaryRegisterText(editor: VimEditor, context: ExecutionContext, copiedText: VimCopiedText) {
-    logger.trace("Setting text: $copiedText to primary selection...")
+  private fun setSystemPrimaryRegisterText(text: String, rawText: String, transferableData: List<Any>) {
+    logger.trace("Setting text: $text to primary selection...")
     if (isPrimaryRegisterSupported()) {
       try {
-        injector.clipboardManager.setPrimaryContent(editor, context, copiedText)
+        injector.clipboardManager.setPrimaryText(text, rawText, transferableData)
       } catch (e: Exception) {
         logger.warn("False positive X11 primary selection support")
         logger.trace("Setting text to primary selection failed. Setting it to clipboard selection instead")
-        setSystemClipboardRegisterText(editor, context, copiedText)
+        setSystemClipboardRegisterText(text, rawText, transferableData)
       }
     } else {
       logger.trace("X11 primary selection is not supporting. Setting clipboard selection instead")
-      setSystemClipboardRegisterText(editor, context, copiedText)
+      setSystemClipboardRegisterText(text, rawText, transferableData)
     }
   }
 
-  private fun setSystemClipboardRegisterText(editor: VimEditor, context: ExecutionContext, copiedText: VimCopiedText) {
-    injector.clipboardManager.setClipboardContent(editor, context, copiedText)
+  private fun setSystemClipboardRegisterText(text: String, rawText: String, transferableData: List<Any>) {
+    injector.clipboardManager.setClipboardText(text, rawText, transferableData)
   }
 
-  private fun refreshPrimaryRegister(editor: VimEditor, context: ExecutionContext): Register? {
+  private fun refreshPrimaryRegister(): Register? {
     logger.trace("Syncing cached primary selection value..")
     if (!isPrimaryRegisterSupported()) {
       logger.trace("X11 primary selection is not supported. Syncing clipboard selection..")
-      return refreshClipboardRegister(editor, context)
+      return refreshClipboardRegister()
     }
     try {
-      val clipboardData = injector.clipboardManager.getPrimaryContent(editor, context) ?: return null
+      val clipboardData = injector.clipboardManager.getPrimaryTextAndTransferableData() ?: return null
       val currentRegister = myRegisters[PRIMARY_REGISTER]
-      if (currentRegister != null && clipboardData.text == currentRegister.text) {
+      val text = clipboardData.first
+      val transferableData = clipboardData.second?.toMutableList()
+      if (currentRegister != null && text == currentRegister.text) {
         return currentRegister
       }
-      return Register(PRIMARY_REGISTER, clipboardData, guessSelectionType(clipboardData.text))
+      return transferableData?.let { Register(PRIMARY_REGISTER, guessSelectionType(text), text, it) }
     } catch (e: Exception) {
       logger.warn("False positive X11 primary selection support")
       logger.trace("Syncing primary selection failed. Syncing clipboard selection instead")
-      return refreshClipboardRegister(editor, context)
+      return refreshClipboardRegister()
     }
   }
 
-  private fun refreshClipboardRegister(editor: VimEditor, context: ExecutionContext): Register? {
+  private fun refreshClipboardRegister(): Register? {
     // for some reason non-X systems use PRIMARY_REGISTER as a clipboard storage
     val systemAwareClipboardRegister = if (isPrimaryRegisterSupported()) CLIPBOARD_REGISTER else PRIMARY_REGISTER
 
-    val clipboardData = injector.clipboardManager.getClipboardContent(editor, context) ?: return null
+    val clipboardData = injector.clipboardManager.getClipboardTextAndTransferableData() ?: return null
     val currentRegister = myRegisters[systemAwareClipboardRegister]
-    if (currentRegister != null && clipboardData.text == currentRegister.text) {
+    val text = clipboardData.first
+    val transferableData = clipboardData.second?.toMutableList()
+    if (currentRegister != null && text == currentRegister.text) {
       return currentRegister
     }
-    return Register(systemAwareClipboardRegister, clipboardData, guessSelectionType(clipboardData.text))
+    return transferableData?.let { Register(systemAwareClipboardRegister, guessSelectionType(text), text, it) }
   }
 
   override fun getRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? {
@@ -492,50 +472,35 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
       myR = Character.toLowerCase(myR)
     }
     return if (CLIPBOARD_REGISTERS.indexOf(myR) >= 0) refreshClipboardRegister(
-      editor,
-      context,
-      myR
-    ) else myRegisters[myR]
-  }
-
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getRegisters(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)")
-  override fun getRegisters(): List<Register> {
-    val dummyEditor = injector.fallbackWindow
-    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
-    return getRegisters(dummyEditor, dummyContext)
+      myR) else myRegisters[myR]
   }
 
   override fun getRegisters(editor: VimEditor, context: ExecutionContext): List<Register> {
     val filteredRegisters = myRegisters.values.filterNot { CLIPBOARD_REGISTERS.contains(it.name) }.toMutableList()
     val clipboardRegisters = CLIPBOARD_REGISTERS
       .filterNot { it == CLIPBOARD_REGISTER && !isPrimaryRegisterSupported() } // for some reason non-X systems use PRIMARY_REGISTER as a clipboard storage
-      .mapNotNull { refreshClipboardRegister(editor, context, it) }
+      .mapNotNull { refreshClipboardRegister(it) }
 
     return (filteredRegisters + clipboardRegisters).sortedWith(Register.KeySorter)
   }
 
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#saveRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char, com.maddyhome.idea.vim.register.Register)")
-  override fun saveRegister(r: Char, register: Register) {
-    val dummyEditor = injector.fallbackWindow
-    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
-    saveRegister(dummyEditor, dummyContext, r, register)
-  }
-
   override fun saveRegister(editor: VimEditor, context: ExecutionContext, r: Char, register: Register) {
     var myR = if (Character.isUpperCase(r)) Character.toLowerCase(r) else r
+    val text = register.text
+    val rawText = register.rawText
 
-    if (CLIPBOARD_REGISTERS.indexOf(myR) >= 0) {
+    if (CLIPBOARD_REGISTERS.indexOf(myR) >= 0 && text != null && rawText != null) {
       when (myR) {
         CLIPBOARD_REGISTER -> {
           if (!isPrimaryRegisterSupported()) {
             // it looks wrong, but for some reason non-X systems use the * register to store the clipboard content
             myR = PRIMARY_REGISTER
           }
-          setSystemClipboardRegisterText(editor, context, register.copiedText)
+          setSystemClipboardRegisterText(text, rawText, ArrayList(register.transferableData))
         }
 
         PRIMARY_REGISTER -> {
-          setSystemPrimaryRegisterText(editor, context, register.copiedText)
+          setSystemPrimaryRegisterText(text, rawText, ArrayList(register.transferableData))
         }
       }
     }
@@ -552,15 +517,8 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
     }
   }
 
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#getPlaybackRegister(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext, char)")
-  override fun getPlaybackRegister(r: Char): Register? {
-    val dummyEditor = injector.fallbackWindow
-    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
-    return getPlaybackRegister(dummyEditor, dummyContext, r)
-  }
-
   override fun getPlaybackRegister(editor: VimEditor, context: ExecutionContext, r: Char): Register? {
-    return if (PLAYBACK_REGISTERS.indexOf(r) != 0) getRegister(editor, context, r) else null
+    return if (PLAYBACK_REGISTERS.indexOf(r) != 0) getRegister(r) else null
   }
 
   override fun recordText(text: String) {
@@ -578,19 +536,12 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
     myRegisters[register] = Register(register, type, keys.toMutableList())
   }
 
-  @Deprecated("Please use com.maddyhome.idea.vim.register.VimRegisterGroup#finishRecording(com.maddyhome.idea.vim.api.VimEditor, com.maddyhome.idea.vim.api.ExecutionContext)")
-  override fun finishRecording() {
-    val dummyEditor = injector.fallbackWindow
-    val dummyContext = injector.executionContextManager.getEditorExecutionContext(dummyEditor)
-    finishRecording(dummyEditor, dummyContext)
-  }
-
   override fun finishRecording(editor: VimEditor, context: ExecutionContext) {
     val register = recordRegister
     if (register != null) {
       var reg: Register? = null
       if (Character.isUpperCase(register)) {
-        reg = getRegister(editor, context, register)
+        reg = getRegister(register)
       }
 
       val myRecordList = recordList
@@ -599,7 +550,7 @@ abstract class VimRegisterGroupBase : VimRegisterGroup {
           reg = Register(Character.toLowerCase(register), SelectionType.CHARACTER_WISE, myRecordList)
           myRegisters[Character.toLowerCase(register)] = reg
         } else {
-          myRegisters[reg.name.lowercaseChar()] = reg.addText(injector.parser.toPrintableString(myRecordList))
+          reg.addKeys(myRecordList)
         }
       }
     }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/CopyTextCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/CopyTextCommand.kt
index 0eb57c71f..77dd38345 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/CopyTextCommand.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/CopyTextCommand.kt
@@ -11,6 +11,7 @@ package com.maddyhome.idea.vim.vimscript.model.commands
 import com.intellij.vim.annotations.ExCommand
 import com.maddyhome.idea.vim.api.ExecutionContext
 import com.maddyhome.idea.vim.api.VimEditor
+import com.maddyhome.idea.vim.api.getText
 import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.command.OperatorArguments
 import com.maddyhome.idea.vim.ex.ranges.Range
@@ -37,7 +38,7 @@ data class CopyTextCommand(val range: Range, val modifier: CommandModifier, val
     val carets = editor.sortedCarets()
     for (caret in carets) {
       val range = getLineRange(editor, caret).toTextRange(editor)
-      val copiedText = injector.clipboardManager.collectCopiedText(editor, context, range)
+      val text = editor.getText(range)
 
       // Copy is defined as:
       // :[range]co[py] {address}
@@ -46,7 +47,8 @@ data class CopyTextCommand(val range: Range, val modifier: CommandModifier, val
       // the line _before_ the first line (i.e., copy to above the first line).
       val address1 = getAddressFromArgument(editor)
 
-      val textData = PutData.TextData(null, copiedText, SelectionType.LINE_WISE)
+      val transferableData = injector.clipboardManager.getTransferableData(editor, range)
+      val textData = PutData.TextData(text, SelectionType.LINE_WISE, transferableData, null)
       var mutableCaret = caret
       val putData = if (address1 == 0) {
         // TODO: This should maintain current column location
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt
index d5777b219..d0b1120a5 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/MoveTextCommand.kt
@@ -88,7 +88,7 @@ data class MoveTextCommand(val range: Range, val modifier: CommandModifier, val
     val selectionEndOffset = lastSelectionInfo.end?.let { editor.bufferPositionToOffset(it) }
 
     val text = editor.getText(range)
-    val textData = PutData.TextData(null, injector.clipboardManager.dumbCopiedText(text), SelectionType.LINE_WISE)
+    val textData = PutData.TextData(text, SelectionType.LINE_WISE, emptyList(), null)
 
     val dropNewLineInEnd = (line + linesMoved == editor.lineCount() - 1 && text.last() == '\n') ||
       (lineRange.endLine == editor.lineCount() - 1)
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/PutLinesCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/PutLinesCommand.kt
index f7abf135f..cbd5828e6 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/PutLinesCommand.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/PutLinesCommand.kt
@@ -47,7 +47,12 @@ data class PutLinesCommand(val range: Range, val modifier: CommandModifier, val
 
     val line = if (range.size() == 0) -1 else getLine(editor)
     val textData = registerGroup.getRegister(editor, context, registerGroup.lastRegisterChar)?.let {
-      PutData.TextData(null, it.copiedText, SelectionType.LINE_WISE)
+      PutData.TextData(
+        it.text ?: injector.parser.toKeyNotation(it.keys),
+        SelectionType.LINE_WISE,
+        it.transferableData,
+        null,
+      )
     }
     val putData = PutData(
       textData,
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/RegistersCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/RegistersCommand.kt
index 89f5295e1..c3dd53e41 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/RegistersCommand.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/RegistersCommand.kt
@@ -14,6 +14,7 @@ import com.maddyhome.idea.vim.api.VimEditor
 import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.command.OperatorArguments
 import com.maddyhome.idea.vim.ex.ranges.Range
+import com.maddyhome.idea.vim.helper.EngineStringHelper
 import com.maddyhome.idea.vim.state.mode.SelectionType
 import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
 
@@ -41,7 +42,8 @@ data class RegistersCommand(val range: Range, val modifier: CommandModifier, val
           SelectionType.CHARACTER_WISE -> "c"
           SelectionType.BLOCK_WISE -> "b"
         }
-        "  $type  \"${reg.name}   ${reg.printableString.take(200)}"
+        val text = reg.rawText?.let { injector.parser.parseKeys(it) } ?: reg.keys
+        "  $type  \"${reg.name}   ${EngineStringHelper.toPrintableCharacters(text).take(200)}"
       }
 
     injector.outputPanel.output(editor, context, regs)
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/VimYankGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/VimYankGroup.kt
index 6a38b52f2..8c5e5eee8 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/VimYankGroup.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/VimYankGroup.kt
@@ -38,8 +38,6 @@ interface VimYankGroup {
    * @param count  The number of lines to yank
    * @return true if able to yank the lines, false if not
    */
-  @Deprecated("Please use the same method, but with ExecutionContext")
-  fun yankLine(editor: VimEditor, count: Int): Boolean
   fun yankLine(editor: VimEditor, context: ExecutionContext, count: Int): Boolean
 
   /**
@@ -50,8 +48,6 @@ interface VimYankGroup {
    * @param type   The type of yank
    * @return true if able to yank the range, false if not
    */
-  @Deprecated("Please use the same method, but with ExecutionContext")
-  fun yankRange(editor: VimEditor, range: TextRange?, type: SelectionType, moveCursor: Boolean): Boolean
   fun yankRange(
     editor: VimEditor,
     context: ExecutionContext,
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/YankGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/YankGroupBase.kt
index c0696703c..6ac70bce7 100644
--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/YankGroupBase.kt
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/yank/YankGroupBase.kt
@@ -10,18 +10,14 @@ package com.maddyhome.idea.vim.yank
 
 import com.maddyhome.idea.vim.action.motion.updown.MotionDownLess1FirstNonSpaceAction
 import com.maddyhome.idea.vim.api.ExecutionContext
-import com.maddyhome.idea.vim.api.ImmutableVimCaret
 import com.maddyhome.idea.vim.api.VimCaret
 import com.maddyhome.idea.vim.api.VimEditor
-import com.maddyhome.idea.vim.api.anyNonWhitespace
 import com.maddyhome.idea.vim.api.getLineEndForOffset
 import com.maddyhome.idea.vim.api.getLineStartForOffset
 import com.maddyhome.idea.vim.api.injector
 import com.maddyhome.idea.vim.command.Argument
-import com.maddyhome.idea.vim.command.MotionType
 import com.maddyhome.idea.vim.command.OperatorArguments
 import com.maddyhome.idea.vim.common.TextRange
-import com.maddyhome.idea.vim.handler.MotionActionHandler
 import com.maddyhome.idea.vim.state.mode.SelectionType
 import org.jetbrains.annotations.Contract
 import kotlin.math.min
@@ -30,20 +26,16 @@ open class YankGroupBase : VimYankGroup {
   private fun yankRange(
     editor: VimEditor,
     context: ExecutionContext,
-    caretToRange: Map<ImmutableVimCaret, Pair<TextRange, SelectionType>>,
+    range: TextRange,
+    type: SelectionType,
     startOffsets: Map<VimCaret, Int>?,
   ): Boolean {
     startOffsets?.forEach { (caret, offset) ->
       caret.moveToOffset(offset)
     }
 
-    injector.listenersNotifier.notifyYankPerformed(caretToRange.mapValues { it.value.first })
-
-    var result = true
-    for ((caret, myRange) in caretToRange) {
-      result = caret.registerStorage.storeText(editor, context, myRange.first, myRange.second, false) && result
-    }
-    return result
+    injector.listenersNotifier.notifyYankPerformed(editor, range)
+    return injector.registerGroup.storeText(editor, context, editor.primaryCaret(), range, type, false)
   }
 
   @Contract("_, _ -> new")
@@ -83,11 +75,12 @@ open class YankGroupBase : VimYankGroup {
     operatorArguments: OperatorArguments,
   ): Boolean {
     val motion = argument as? Argument.Motion ?: return false
+    val motionType = motion.getMotionType()
 
     val nativeCaretCount = editor.nativeCarets().size
     if (nativeCaretCount <= 0) return false
 
-    val caretToRange = HashMap<ImmutableVimCaret, Pair<TextRange, SelectionType>>(nativeCaretCount)
+    val ranges = ArrayList<Pair<Int, Int>>(nativeCaretCount)
 
     // This logic is from original vim
     val startOffsets =
@@ -97,49 +90,28 @@ open class YankGroupBase : VimYankGroup {
         HashMap<VimCaret, Int>(nativeCaretCount)
       }
 
-
     for (caret in editor.nativeCarets()) {
-      var motionType = motion.getMotionType()
       val motionRange = injector.motion.getMotionRange(editor, caret, context, argument, operatorArguments)
         ?: continue
 
       assert(motionRange.size() == 1)
+      ranges.add(motionRange.startOffset to motionRange.endOffset)
       startOffsets?.put(caret, motionRange.normalize().startOffset)
-
-      // Yank motion commands that are not linewise become linewise if all the following are true:
-      // 1) The range is across multiple lines
-      // 2) There is only whitespace before the start of the range
-      // 3) There is only whitespace after the end of the range
-      if (argument.motion is MotionActionHandler && argument.motion.motionType == MotionType.EXCLUSIVE) {
-        val start = editor.offsetToBufferPosition(motionRange.startOffset)
-        val end = editor.offsetToBufferPosition(motionRange.endOffset)
-        if (start.line != end.line
-          && !editor.anyNonWhitespace(motionRange.startOffset, -1)
-          && !editor.anyNonWhitespace(motionRange.endOffset, 1)
-        ) {
-          motionType = SelectionType.LINE_WISE
-        }
-      }
-
-      caretToRange[caret] = TextRange(motionRange.startOffset, motionRange.endOffset) to motionType
     }
 
-    if (caretToRange.isEmpty()) return false
+    val range = getTextRange(ranges, motionType) ?: return false
+
+    if (range.size() == 0) return false
 
     return yankRange(
       editor,
       context,
-      caretToRange,
+      range,
+      motionType,
       startOffsets,
     )
   }
 
-  @Deprecated("Please use the same method, but with ExecutionContext")
-  override fun yankLine(editor: VimEditor, count: Int): Boolean {
-    val context = injector.executionContextManager.getEditorExecutionContext(editor)
-    return yankLine(editor, context, count)
-  }
-
   /**
    * This yanks count lines of text
    *
@@ -149,24 +121,18 @@ open class YankGroupBase : VimYankGroup {
    */
   override fun yankLine(editor: VimEditor, context: ExecutionContext, count: Int): Boolean {
     val caretCount = editor.nativeCarets().size
-    val caretToRange = HashMap<ImmutableVimCaret, Pair<TextRange, SelectionType>>(caretCount)
+    val ranges = ArrayList<Pair<Int, Int>>(caretCount)
     for (caret in editor.nativeCarets()) {
       val start = injector.motion.moveCaretToCurrentLineStart(editor, caret)
-      val end =
-        min(injector.motion.moveCaretToRelativeLineEnd(editor, caret, count - 1, true) + 1, editor.fileSize().toInt())
+      val end = min(injector.motion.moveCaretToRelativeLineEnd(editor, caret, count - 1, true) + 1, editor.fileSize().toInt())
 
       if (end == -1) continue
 
-      caretToRange[caret] = TextRange(start, end) to SelectionType.LINE_WISE
+      ranges.add(start to end)
     }
 
-    return yankRange(editor, context, caretToRange, null)
-  }
-
-  @Deprecated("Please use the same method, but with ExecutionContext")
-  override fun yankRange(editor: VimEditor, range: TextRange?, type: SelectionType, moveCursor: Boolean): Boolean {
-    val context = injector.executionContextManager.getEditorExecutionContext(editor)
-    return yankRange(editor, context, range, type, moveCursor)
+    val range = getTextRange(ranges, SelectionType.LINE_WISE) ?: return false
+    return yankRange(editor, context, range, SelectionType.LINE_WISE, null)
   }
 
   /**
@@ -177,15 +143,8 @@ open class YankGroupBase : VimYankGroup {
    * @param type   The type of yank
    * @return true if able to yank the range, false if not
    */
-  override fun yankRange(
-    editor: VimEditor,
-    context: ExecutionContext,
-    range: TextRange?,
-    type: SelectionType,
-    moveCursor: Boolean,
-  ): Boolean {
+  override fun yankRange(editor: VimEditor, context: ExecutionContext, range: TextRange?, type: SelectionType, moveCursor: Boolean): Boolean {
     range ?: return false
-    val caretToRange = HashMap<ImmutableVimCaret, Pair<TextRange, SelectionType>>()
 
     if (type == SelectionType.LINE_WISE) {
       for (i in 0 until range.size()) {
@@ -205,19 +164,17 @@ open class YankGroupBase : VimYankGroup {
     val startOffsets = HashMap<VimCaret, Int>(editor.nativeCarets().size)
     if (type == SelectionType.BLOCK_WISE) {
       startOffsets[editor.primaryCaret()] = range.normalize().startOffset
-      caretToRange[editor.primaryCaret()] = range to type
     } else {
       for ((i, caret) in editor.nativeCarets().withIndex()) {
         val textRange = TextRange(rangeStartOffsets[i], rangeEndOffsets[i])
         startOffsets[caret] = textRange.normalize().startOffset
-        caretToRange[caret] = textRange to type
       }
     }
 
     return if (moveCursor) {
-      yankRange(editor, context, caretToRange, startOffsets)
+      yankRange(editor, context, range, type, startOffsets)
     } else {
-      yankRange(editor, context, caretToRange, null)
+      yankRange(editor, context, range, type, null)
     }
   }
 }