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:
src
main
test
java
org
jetbrains
plugins
ideavim
ex
implementation
vim-engine/src/main/kotlin/com/maddyhome/idea/vim
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user