Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,28 @@ class InsertRegisterTest : VimTestCase() {
""".trimIndent()
doTest(keys, before, after, Mode.INSERT)
}

@Test
fun `test insert named register with CTRL-R`() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good - this test verifies basic <C-R> functionality. However, consider adding a test that demonstrates the auto-indent behavior of <C-R> vs the literal behavior of <C-R><C-R> with multi-line or indented content.

configureByText("\n")
enterCommand("let @a=\"hello world\"")
typeText("i<C-R>a")
assertState("hello world$c\n")
}

@Test
fun `test insert named register literally with CTRL-R CTRL-R`() {
configureByText("\n")
enterCommand("let @a=\"hello world\"")
typeText("i<C-R><C-R>a")
assertState("hello world$c\n")
}

@Test
fun `test insert named register literally with CTRL-R CTRL-O`() {
configureByText("\n")
enterCommand("let @a=\"hello world\"")
typeText("i<C-R><C-O>a")
assertState("hello world$c\n")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/

package org.jetbrains.plugins.ideavim.action.change.insert

import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test

class InsertRegisterLiterallyActionTest : VimTestCase() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing test coverage: Consider adding tests that verify the auto-indent behavior difference between <C-R> and <C-R><C-R>/<C-R><C-O>.

For example:

  • Test with multi-line register content that has indentation
  • Verify that <C-R> applies auto-indent in contexts where it would occur
  • Verify that <C-R><C-R> and <C-R><C-O> insert without auto-indent modification

The command-line mode tests in InsertRegisterLiterallyActionTest.kt (ex mode) include tests for special characters and literal insertion - similar comprehensive tests would strengthen this suite.

@Test
fun `test insert named register literally with CTRL-R CTRL-R`() {
configureByText("\n")
enterCommand("let @a=\"hello world\"")
typeText("i<C-R><C-R>a")
assertState("hello world${c}\n")
}

@Test
fun `test insert named register literally with CTRL-R CTRL-O`() {
configureByText("\n")
enterCommand("let @a=\"hello world\"")
typeText("i<C-R><C-O>a")
assertState("hello world${c}\n")
}

@Test
fun `test insert named register literally with CTRL-R CTRL-R after existing text`() {
configureByText("${c}\n")
enterCommand("let @a=\"world\"")
typeText("ihello <C-R><C-R>a")
assertState("hello world${c}\n")
}

@Test
fun `test insert named register literally with CTRL-R CTRL-O after existing text`() {
configureByText("${c}\n")
enterCommand("let @a=\"world\"")
typeText("ihello <C-R><C-O>a")
assertState("hello world${c}\n")
}

@Test
fun `test insert default register literally with CTRL-R CTRL-R`() {
configureByText("${c}hello\n")
typeText("ywo<C-R><C-R>\"")
assertState("hello\nhello${c}\n")
}

@Test
fun `test insert default register literally with CTRL-R CTRL-O`() {
configureByText("${c}hello\n")
typeText("ywo<C-R><C-O>\"")
assertState("hello\nhello${c}\n")
}

@Test
fun `test insert numbered register literally with CTRL-R CTRL-R`() {
configureByText("${c}line1\nline2\n")
typeText("yyjo<C-R><C-R>0")
assertState("line1\nline2\nline1\n${c}\n")
}

@Test
fun `test insert numbered register literally with CTRL-R CTRL-O`() {
configureByText("${c}line1\nline2\n")
typeText("yyjo<C-R><C-O>0")
assertState("line1\nline2\nline1\n${c}\n")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,42 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
}
}

@CommandOrMotion(keys = ["<C-R><C-R>", "<C-R><C-O>"], modes = [Mode.INSERT])
class InsertRegisterLiterallyAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

override val argumentType: Argument.Type = Argument.Type.CHARACTER

override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
val argument = cmd.argument as? Argument.Character ?: return false
if (argument.character == '=') {
injector.commandLine.readInputAndProcess(editor, context, "=", finishOn = null) { input ->
try {
if (input.isNotEmpty()) {
val expression =
injector.vimscriptParser.parseExpression(input)?.evaluate(editor, context, Script(listOf()))
?: throw exExceptionMessage("E15", input)
val textToStore = expression.toInsertableString()
injector.registerGroup.storeTextSpecial('=', textToStore)
}
insertRegisterLiterally(editor, context, '=')
} catch (e: ExException) {
injector.messages.indicateError()
injector.messages.showStatusBarMessage(editor, e.message)
}
}
return true
} else {
return insertRegisterLiterally(editor, context, argument.character)
}
}
}

/**
* Inserts the contents of the specified register
*
Expand All @@ -84,3 +120,28 @@ private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Ch
}
return false
}

/**
* Inserts the contents of the specified register literally without auto-indent
*
* @param editor The editor to insert the text into
* @param context The data context
* @param key The register name
* @return true if able to insert the register contents, false if not
*/
@VimLockLabel.SelfSynchronized
private fun insertRegisterLiterally(editor: VimEditor, context: ExecutionContext, key: Char): Boolean {
val register: Register? = injector.registerGroup.getRegister(editor, context, 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)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rawIndent = true is correct for the "literally" variant - it prevents auto-indent as expected for <C-R><C-R> and <C-R><C-O>.

injector.put.putText(editor, context, putData)
return true
}
return false
}
10 changes: 10 additions & 0 deletions vim-engine/src/main/resources/ksp-generated/engine_commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -444,6 +444,11 @@
"class": "com.maddyhome.idea.vim.action.ex.InsertCurrentLineAction",
"modes": "C"
},
{
"keys": "<C-R><C-O>",
"class": "com.maddyhome.idea.vim.action.change.insert.InsertRegisterLiterallyAction",
"modes": "I"
},
{
"keys": "<C-R><C-O>",
"class": "com.maddyhome.idea.vim.action.ex.InsertRegisterLiterallyAction",
Expand All @@ -469,6 +474,11 @@
"class": "com.maddyhome.idea.vim.action.ex.InsertWordUnderCaretAction",
"modes": "C"
},
{
"keys": "<C-R><C-R>",
"class": "com.maddyhome.idea.vim.action.change.insert.InsertRegisterLiterallyAction",
"modes": "I"
},
{
"keys": "<C-R><C-R>",
"class": "com.maddyhome.idea.vim.action.ex.InsertRegisterLiterallyAction",
Expand Down
Loading