1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-05-06 12:34:03 +02:00

Handle blank and empty lines for text objects

Fixes VIM-1642
This commit is contained in:
Matt Ellis 2025-01-16 23:04:03 +00:00 committed by Alex Pláte
parent f25c56b545
commit 426c2fd006
4 changed files with 235 additions and 5 deletions
src/test/java/org/jetbrains/plugins/ideavim/action/motion/object
vim-engine/src/main/kotlin/com/maddyhome/idea/vim

View File

@ -338,6 +338,116 @@ class MotionInnerBigWordActionTest : VimTestCase() {
)
}
@VimBehaviorDiffers(originalVimAfter =
"""
|Lorem ipsum dolor sit amet,
|
|${s}${c}
|${se}
|
|consectetur adipiscing elit
""",
description = "The caret and selection should not be at the same offset. This indicates that IdeaVim is " +
"shortening the selection range to (incorrectly) avoid selecting the end of line char. Once IdeaVim allows " +
"this, both caret and selection end offset will be incorrect, indicating an off-by-ine error somewhere, " +
"which will need fixing." +
"Fix when IdeaVim supports selecting end of line char"
)
@Test
fun `test select empty line`() {
doTest(
"viW",
"""
|Lorem ipsum dolor sit amet,
|
|${c}
|
|
|consectetur adipiscing elit
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet,
|
|${s}
|${c}${se}
|
|consectetur adipiscing elit
""".trimMargin(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@VimBehaviorDiffers(originalVimAfter =
"""
|Lorem ipsum dolor sit amet,
|
|${s}
|
|
|
|${c}${se}
|
|consectetur adipiscing elit
""",
description = "I don't understand Vim's logic for selecting mulitple empty lines with 'iw'." +
"E.g. `v3iw` will select 5 lines, `v4iw` will select 7, `v5iw` selects 9. " +
"Fix only when/if Vim's behaviour is clarified"
)
@Test
fun `test select multiple empty lines`() {
doTest(
"v3iW",
"""
|Lorem ipsum dolor sit amet,
|
|${c}
|
|
|
|
|
|consectetur adipiscing elit
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet,
|
|${s}
|
|
|${c}${se}
|
|
|consectetur adipiscing elit
""".trimMargin(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@Test
fun `test select blank line`() {
doTest(
"viW",
"""
|Lorem ipsum dolor sit amet,
|
|..${c}..
|
|
|consectetur adipiscing elit
""".trimMargin().dotToSpace(),
"""
|Lorem ipsum dolor sit amet,
|
|${s}...${c}.${se}
|
|
|consectetur adipiscing elit
""".trimMargin().dotToSpace(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@VimBehaviorDiffers(
originalVimAfter = "${s}Lorem${c}${se} ipsum dolor sit amet, consectetur adipiscing elit",
description = "Text objects are implicitly inclusive, because they set the selection." +

View File

@ -338,6 +338,115 @@ class MotionInnerWordActionTest : VimTestCase() {
)
}
@VimBehaviorDiffers(originalVimAfter =
"""
|Lorem ipsum dolor sit amet,
|
|${s}${c}
|${se}
|
|consectetur adipiscing elit
""",
description = "The caret and selection should not be at the same offset. This indicates that IdeaVim is " +
"shortening the selection range to (incorrectly) avoid selecting the end of line char. Once IdeaVim allows " +
"this, both caret and selection end offset will be incorrect, indicating an off-by-ine error somewhere, " +
"which will need fixing." +
"Fix when IdeaVim supports selecting end of line char"
)
@Test
fun `test select empty line`() {
doTest(
"viw",
"""
|Lorem ipsum dolor sit amet,
|
|${c}
|
|
|consectetur adipiscing elit
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet,
|
|${s}
|${c}${se}
|
|consectetur adipiscing elit
""".trimMargin(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@VimBehaviorDiffers(originalVimAfter =
"""
|Lorem ipsum dolor sit amet,
|
|${s}
|
|
|
|${c}${se}
|
|consectetur adipiscing elit
""",
description = "I don't understand Vim's logic for selecting mulitple empty lines with 'iw'." +
"E.g. `v3iw` will select 5 lines, `v4iw` will select 7, `v5iw` selects 9. " +
"Fix only when/if Vim's behaviour is clarified"
)
@Test
fun `test select multiple empty lines`() {
doTest(
"v3iw",
"""
|Lorem ipsum dolor sit amet,
|
|${c}
|
|
|
|
|
|consectetur adipiscing elit
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet,
|
|${s}
|
|
|${c}${se}
|
|
|consectetur adipiscing elit
""".trimMargin(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@Test
fun `test select blank line`() {
doTest(
"viw",
"""
|Lorem ipsum dolor sit amet,
|
|..${c}..
|
|
|consectetur adipiscing elit
""".trimMargin().dotToSpace(),
"""
|Lorem ipsum dolor sit amet,
|
|${s}...${c}.${se}
|
|
|consectetur adipiscing elit
""".trimMargin().dotToSpace(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@VimBehaviorDiffers(
originalVimAfter = "${s}Lorem${c}${se} ipsum dolor sit amet, consectetur adipiscing elit",
description = "Text objects are implicitly inclusive, because they set the selection." +

View File

@ -379,13 +379,13 @@ abstract class VimSearchHelperBase : VimSearchHelper {
private fun findNextWordEndOne(
chars: CharSequence,
editor: VimEditor,
pos: Int,
start: Int,
size: Int,
step: Int,
bigWord: Boolean,
spaceWords: Boolean,
): Int {
var pos = pos
var pos = start
var found = false
// For forward searches, skip any current whitespace so we start at the start of a word
if (step > 0 && pos < size - 1) {
@ -430,7 +430,10 @@ abstract class VimSearchHelperBase : VimSearchHelper {
&& (newChar == lastChar
|| (spaceWords && charType(editor, lastChar, bigWord) === CharacterHelper.CharacterType.WHITESPACE))
) {
res = if (step < 0) pos + 1 else pos - 1
// Add/subtract one so we don't take the current new line char as our end of word char.
// However, if we're matching empty lines and we start on an empty line, then we'll get two consecutive new line
// chars. Subtracting will put us back at the start character
res = if (step < 0) pos + 1 else (pos - 1).coerceAtLeast(start + 1)
found = true
}
lastChar = newChar
@ -1515,6 +1518,13 @@ abstract class VimSearchHelperBase : VimSearchHelper {
logger.debug("start=$start")
logger.debug("end=$end")
// TODO: Remove this when IdeaVim supports selecting the new line character
// A selection with start == end is perfectly valid, and will select a single character. However, IdeaVim
// unnecessarily prevents selecting the new line character at the end of a line. If the selection is just that new
// line character, then nothing is selected (we end up with a selection with range start==endInclusive, rather than
// start==endExclusive). This little hack makes sure that `viw` will (mostly) work on a single empty line
if (start == end && chars[start] == '\n') end++
// Text range's end offset is exclusive
return TextRange(start, end + 1)
}

View File

@ -19,9 +19,10 @@ import com.maddyhome.idea.vim.state.mode.Mode
fun charToNativeSelection(editor: VimEditor, start: Int, end: Int, mode: Mode): Pair<Int, Int> {
val (nativeStart, nativeEnd) = sort(start, end)
// TODO: Remove this unnecessary restriction on not selecting the new line character
// When doing so, please remove the hack in VimSearchHelperBase.findWordUnderCursor
val lineEnd = editor.getLineEndForOffset(nativeEnd)
val adj =
if (isExclusiveSelection() || nativeEnd == lineEnd || mode is Mode.SELECT) 0 else 1
val adj = if (isExclusiveSelection() || nativeEnd == lineEnd || mode is Mode.SELECT) 0 else 1
val adjEnd = (nativeEnd + adj).coerceAtMost(editor.fileSize().toInt())
return nativeStart to adjEnd
}