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) } } }