1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-13 06:16:58 +02:00

Add tests and fixes for print command

Handles validation of count and correctly moves caret to end of range after execution. Also fix issue where the results of :print are accumulated and not cleared.

Fixes VIM-2570
This commit is contained in:
Matt Ellis
2024-04-16 23:42:55 +01:00
committed by Alex Pláte
parent 9ca9530061
commit 8ecb1f7296
7 changed files with 194 additions and 50 deletions
src
main
java
com
resources
dictionaries
test
java
org
jetbrains
plugins
ideavim
vim-engine/src/main/kotlin/com/maddyhome/idea/vim
api
vimscript
model

@@ -13,24 +13,46 @@ import com.maddyhome.idea.vim.api.VimExOutputPanel
import com.maddyhome.idea.vim.helper.vimExOutput
import com.maddyhome.idea.vim.ui.ExOutputPanel
/**
* @author vlan
*/
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
private var isActiveInTestMode = false
override val isActive: Boolean
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.isPanelActive(myEditor)
} else {
isActiveInTestMode
}
override var text: String? = null
private set
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).text
} else {
field
}
set(value) {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(value ?: "")
} else {
field = value
isActiveInTestMode = !value.isNullOrEmpty()
}
}
override fun output(text: String) {
this.text = text
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).setText(text)
}
}
override fun clear() {
text = null
}
override fun close() {
if (!ApplicationManager.getApplication().isUnitTestMode) {
ExOutputPanel.getInstance(myEditor).deactivate(false)
ExOutputPanel.getInstance(myEditor).close()
}
else {
isActiveInTestMode = false
}
}

@@ -151,6 +151,10 @@ public class ExOutputPanel extends JPanel {
}
}
public String getText() {
return myText.getText();
}
@SuppressWarnings("ConstantConditions")
@Override
public Color getForeground() {
@@ -288,7 +292,7 @@ public class ExOutputPanel extends JPanel {
}
}
private void close() {
public void close() {
close(null);
}

@@ -4,6 +4,7 @@ maddyhome
setglobal
setlocal
vglobal
breakindent
colorcolumn

@@ -17,8 +17,19 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class GlobalCommandTest : VimTestCase() {
companion object {
private val initialText = """
A Discovery
I found it in a legendary land
all rocks and lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent()
}
@Test
fun `test default range`() {
fun `test delete search term in default range of whole file`() {
doTest(
"g/found/d",
initialText,
@@ -33,7 +44,7 @@ class GlobalCommandTest : VimTestCase() {
}
@Test
fun `test default range first line`() {
fun `test delete first line in default range`() {
doTest(
"g/Discovery/d",
initialText,
@@ -48,7 +59,7 @@ class GlobalCommandTest : VimTestCase() {
}
@Test
fun `test default range last line`() {
fun `test delete last line in default range`() {
doTest(
"g/torrent/d",
initialText,
@@ -63,7 +74,7 @@ class GlobalCommandTest : VimTestCase() {
}
@Test
fun `test two lines`() {
fun `test delete multiple matching lines`() {
doTest(
"g/it/d",
initialText,
@@ -77,7 +88,7 @@ class GlobalCommandTest : VimTestCase() {
}
@Test
fun `test two lines force`() {
fun `test delete multiple non-matching lines with global-bang`() {
doTest(
"g!/it/d",
initialText,
@@ -89,7 +100,7 @@ class GlobalCommandTest : VimTestCase() {
}
@Test
fun `test vglobal`() {
fun `test delete multiple non-matching lines with vglobal`() {
doTest(
"v/it/d",
initialText,
@@ -101,7 +112,7 @@ class GlobalCommandTest : VimTestCase() {
}
@Test
fun `test current line`() {
fun `test delete nothing if not found in current line`() {
doTest(
".g/found/d",
initialText,
@@ -110,7 +121,7 @@ class GlobalCommandTest : VimTestCase() {
}
@Test
fun `test current line right place`() {
fun `test delete current line if matching`() {
doTest(
".g/found/d",
"""
@@ -237,7 +248,7 @@ class GlobalCommandTest : VimTestCase() {
@TestWithoutNeovim(SkipNeovimReason.DIFFERENT)
@Test
fun `test g with one separator and pattern`() {
fun `test print matching line if no command`() {
doTest(
"g/found",
initialText,
@@ -248,7 +259,21 @@ class GlobalCommandTest : VimTestCase() {
@TestWithoutNeovim(SkipNeovimReason.DIFFERENT)
@Test
fun `test g with one separator and pattern and separator`() {
fun `test print multiple matching line if no command`() {
doTest(
"g/it",
initialText,
initialText,
)
assertExOutput("""
|I found it in a legendary land
|where it was settled on some sodden sand
|""".trimMargin())
}
@TestWithoutNeovim(SkipNeovimReason.DIFFERENT)
@Test
fun `test print matching lines if no command and no trailing separator`() {
doTest(
"g/found/",
initialText,
@@ -281,15 +306,4 @@ class GlobalCommandTest : VimTestCase() {
private fun doTest(command: String, before: String, after: String) {
doTest(listOf(exCommand(command)), before, after, Mode.NORMAL())
}
companion object {
private val initialText = """
A Discovery
I found it in a legendary land
all rocks and lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent()
}
}

@@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.maddyhome.idea.vim.ex.ExOutputModel
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
@@ -16,14 +17,28 @@ class PrintCommandTest : VimTestCase() {
fun `test default range`() {
configureByText(initialText)
typeText(commandToKeys("p"))
assertExOutput("Lorem Ipsum\n")
assertExOutput(" Lorem Ipsum\n")
}
@Test
fun `test clears output between execution`() {
configureByText(initialText)
typeText(commandToKeys("p"))
assertExOutput(" Lorem Ipsum\n")
// TODO: We need a better way to handle output
// We should be waiting for a keypress now, such as <Enter> or <Esc> to close the output panel. But that's handled
// by a separate key event loop which doesn't operate in tests.
// Simulate closing the output panel in the same way as if we'd entered the right key
ExOutputModel.getInstance(fixture.editor).close()
typeText(commandToKeys("p"))
assertExOutput(" Lorem Ipsum\n")
}
@Test
fun `test default range with P`() {
configureByText(initialText)
typeText(commandToKeys("P"))
assertExOutput("Lorem Ipsum\n")
assertExOutput(" Lorem Ipsum\n")
}
@Test
@@ -34,27 +49,100 @@ class PrintCommandTest : VimTestCase() {
}
@Test
fun `test with count`() {
configureByText(initialText)
typeText(commandToKeys("p 3"))
assertExOutput(
fun `test moves caret to start of last line of range skipping whitespace`() {
doTest(
exCommand("2,5p"),
initialText,
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,
""".trimIndent(),
| Lorem Ipsum
|
| Lorem ipsum dolor sit amet,
| consectetur adipiscing elit
| ${c}Sed in orci mauris.
| Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
assertExOutput("""
|
| Lorem ipsum dolor sit amet,
| consectetur adipiscing elit
| Sed in orci mauris.
|""".trimMargin(),
)
}
@Test
fun `test with count`() {
doTest(
exCommand("p3"),
initialText,
"""
| Lorem Ipsum
|
| ${c}Lorem ipsum dolor sit amet,
| consectetur adipiscing elit
| Sed in orci mauris.
| Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
assertExOutput("""
| Lorem Ipsum
|
| Lorem ipsum dolor sit amet,
|""".trimMargin(),
)
}
@Test
fun `test with invalid count`() {
// Note that caret isn't moved
doTest(
exCommand("p3,4"),
initialText,
"""
|${c} Lorem Ipsum
|
| Lorem ipsum dolor sit amet,
| consectetur adipiscing elit
| Sed in orci mauris.
| Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
assertPluginError(true)
assertPluginErrorMessageContains("E488: Trailing characters: ,4")
}
@Test
fun `test with range and count`() {
doTest(
exCommand("2,3p4"),
initialText,
"""
| Lorem Ipsum
|
| Lorem ipsum dolor sit amet,
| consectetur adipiscing elit
| Sed in orci mauris.
| ${c}Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
assertExOutput("""
| Lorem ipsum dolor sit amet,
| consectetur adipiscing elit
| Sed in orci mauris.
| Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
)
}
companion object {
private val initialText = """
Lorem Ipsum
Lorem ipsum dolor sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
| Lorem Ipsum
|
| Lorem ipsum dolor sit amet,
| consectetur adipiscing elit
| Sed in orci mauris.
| Cras id tellus in ex imperdiet egestas.
""".trimMargin()
}
}

@@ -13,8 +13,11 @@ public interface VimExOutputPanelService {
}
public interface VimExOutputPanel {
public val isActive: Boolean
public val text: String?
public fun output(text: String)
public fun clear()
public fun close()
}

@@ -15,6 +15,7 @@ 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
import com.maddyhome.idea.vim.ex.ranges.toTextRange
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
/**
@@ -27,11 +28,22 @@ public data class PrintCommand(val range: Range, val argument: String) : Command
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
editor.removeSecondaryCarets()
val caret = editor.currentCaret()
val textRange = getTextRange(editor, caret, checkCount = true)
val lineRange = getLineRangeWithCount(editor, caret)
val textRange = lineRange.toTextRange(editor)
val text = editor.getText(textRange)
// Move the caret to the start of the last line of the range
val offset = injector.motion.moveCaretToLineStartSkipLeading(editor, lineRange.endLine)
caret.moveToOffset(offset)
// Note that we append to the existing text because we can be called multiple times by the :global command
// TODO: We need a better way to handle output. This is not very efficient, especially if we have a lot of output
val exOutputModel = injector.exOutputPanel.getPanel(editor)
if (!exOutputModel.isActive) {
// When we add text, we make the panel active. So if we're appending, it should already be active. If it's not,
// make sure it's clear
exOutputModel.clear()
}
exOutputModel.output((exOutputModel.text ?: "") + text)
return ExecutionResult.Success
}