mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-06-06 07:34:04 +02:00
Rewrite backwards search for word under cursor
This commit is contained in:
parent
d2b85cbb10
commit
4787a5e22a
src/test/java/org/jetbrains/plugins/ideavim/action/motion/object
MotionInnerBigWordActionTest.ktMotionInnerWordActionTest.ktMotionOuterBigWordActionTest.ktMotionOuterWordActionTest.kt
vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api
@ -215,7 +215,7 @@ class MotionInnerBigWordActionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test select WORD from whitespace at end of line with multiple lines and existing left-to-right selection`() {
|
fun `test select WORD from whitespace at end of line with multiple lines and existing left-to-right selection`() {
|
||||||
doTest(
|
doTest(
|
||||||
listOf("v", "h", "iW"),
|
listOf("v", "l", "iW"),
|
||||||
"""
|
"""
|
||||||
|Lorem Ipsum...${c}.....
|
|Lorem Ipsum...${c}.....
|
||||||
|
|
|
|
||||||
@ -225,7 +225,7 @@ class MotionInnerBigWordActionTest : VimTestCase() {
|
|||||||
|Cras id tellus in ex imperdiet egestas.
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
""".trimMargin().dotToSpace(),
|
""".trimMargin().dotToSpace(),
|
||||||
"""
|
"""
|
||||||
|Lorem Ipsum${s}${c}....${se}....
|
|Lorem Ipsum...${s}....${c}.${se}
|
||||||
|
|
|
|
||||||
|Lorem ipsum dolor sit amet
|
|Lorem ipsum dolor sit amet
|
||||||
|consectetur adipiscing elit
|
|consectetur adipiscing elit
|
||||||
@ -239,7 +239,7 @@ class MotionInnerBigWordActionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test select WORD from whitespace at end of line with multiple lines and existing left-to-right selection 2`() {
|
fun `test select WORD from whitespace at end of line with multiple lines and existing left-to-right selection 2`() {
|
||||||
doTest(
|
doTest(
|
||||||
listOf("v", "h", "iW"),
|
listOf("v", "l", "iW"),
|
||||||
"""
|
"""
|
||||||
|Lorem Ipsum
|
|Lorem Ipsum
|
||||||
|
|
|
|
||||||
@ -251,7 +251,7 @@ class MotionInnerBigWordActionTest : VimTestCase() {
|
|||||||
"""
|
"""
|
||||||
|Lorem Ipsum
|
|Lorem Ipsum
|
||||||
|
|
|
|
||||||
|Lorem ipsum dolor sit amet${s}${c}....${se}....
|
|Lorem ipsum dolor sit amet...${s}....${c}.${se}
|
||||||
| consectetur adipiscing elit
|
| consectetur adipiscing elit
|
||||||
|Sed in orci mauris.
|
|Sed in orci mauris.
|
||||||
|Cras id tellus in ex imperdiet egestas.
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
@ -338,6 +338,54 @@ class MotionInnerBigWordActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select WORD from word at start of line with existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "iW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
|c${c}onsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit ${s}${c}amet,
|
||||||
|
|co${se}nsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select WORD from whitespace at start of line with existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "iW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
| ${c} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit ${s}${c}amet,
|
||||||
|
| ${se} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@VimBehaviorDiffers(originalVimAfter =
|
@VimBehaviorDiffers(originalVimAfter =
|
||||||
"""
|
"""
|
||||||
|Lorem ipsum dolor sit amet,
|
|Lorem ipsum dolor sit amet,
|
||||||
@ -434,7 +482,6 @@ class MotionInnerBigWordActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@VimBehaviorDiffers(
|
@VimBehaviorDiffers(
|
||||||
originalVimAfter = "${s}Lorem${c}${se} ipsum dolor sit amet, consectetur adipiscing elit",
|
originalVimAfter = "${s}Lorem${c}${se} ipsum dolor sit amet, consectetur adipiscing elit",
|
||||||
description = "Text objects are implicitly inclusive, because they set the selection." +
|
description = "Text objects are implicitly inclusive, because they set the selection." +
|
||||||
@ -699,8 +746,9 @@ class MotionInnerBigWordActionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test repeated text object expands to empty line`() {
|
fun `test repeated text object expands to empty line`() {
|
||||||
// Well. This behaviour is weird, and looks like a bug, but it matches Vim's behaviour.
|
// Well. This behaviour is weird, and looks like a bug, but it matches Vim's behaviour.
|
||||||
// I'm not entirely sure why this happens, but it's a vote of confidence in IdeaVim's implementation that we're
|
// There's an explanation of the behaviour in VimSearchHelperBase.findWordUnderCursor. Basically, Vim moves forward
|
||||||
// matching bugs! 😁
|
// over an empty line, but when moving back, stops at the start of a line. This puts us off-by-one, but in the same
|
||||||
|
// way that Vim is off-by-one!
|
||||||
// See https://github.com/vim/vim/issues/16514
|
// See https://github.com/vim/vim/issues/16514
|
||||||
doTest(
|
doTest(
|
||||||
listOf("viW", "iW"),
|
listOf("viW", "iW"),
|
||||||
|
@ -225,7 +225,7 @@ class MotionInnerWordActionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test select word from whitespace at end of line with multiple lines and existing left-to-right selection`() {
|
fun `test select word from whitespace at end of line with multiple lines and existing left-to-right selection`() {
|
||||||
doTest(
|
doTest(
|
||||||
listOf("v", "h", "iw"),
|
listOf("v", "l", "iw"),
|
||||||
"""
|
"""
|
||||||
|Lorem Ipsum...${c}.....
|
|Lorem Ipsum...${c}.....
|
||||||
|
|
|
|
||||||
@ -235,7 +235,7 @@ class MotionInnerWordActionTest : VimTestCase() {
|
|||||||
|Cras id tellus in ex imperdiet egestas.
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
""".trimMargin().dotToSpace(),
|
""".trimMargin().dotToSpace(),
|
||||||
"""
|
"""
|
||||||
|Lorem Ipsum${s}${c}....${se}....
|
|Lorem Ipsum...${s}....${c}.${se}
|
||||||
|
|
|
|
||||||
|Lorem ipsum dolor sit amet
|
|Lorem ipsum dolor sit amet
|
||||||
|consectetur adipiscing elit
|
|consectetur adipiscing elit
|
||||||
@ -249,7 +249,7 @@ class MotionInnerWordActionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test select word from whitespace at end of line with multiple lines and existing left-to-right selection 2`() {
|
fun `test select word from whitespace at end of line with multiple lines and existing left-to-right selection 2`() {
|
||||||
doTest(
|
doTest(
|
||||||
listOf("v", "h", "iw"),
|
listOf("v", "l", "iw"),
|
||||||
"""
|
"""
|
||||||
|Lorem Ipsum
|
|Lorem Ipsum
|
||||||
|
|
|
|
||||||
@ -261,7 +261,7 @@ class MotionInnerWordActionTest : VimTestCase() {
|
|||||||
"""
|
"""
|
||||||
|Lorem Ipsum
|
|Lorem Ipsum
|
||||||
|
|
|
|
||||||
|Lorem ipsum dolor sit amet${s}${c}....${se}....
|
|Lorem ipsum dolor sit amet...${s}....${c}.${se}
|
||||||
| consectetur adipiscing elit
|
| consectetur adipiscing elit
|
||||||
|Sed in orci mauris.
|
|Sed in orci mauris.
|
||||||
|Cras id tellus in ex imperdiet egestas.
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
@ -348,6 +348,54 @@ class MotionInnerWordActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select word from start of line with existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "iw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
|c${c}onsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet${s}${c},
|
||||||
|
|co${se}nsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select word from whitespace at start of line with existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "iw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
| ${c} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet${s}${c},
|
||||||
|
| ${se} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@VimBehaviorDiffers(originalVimAfter =
|
@VimBehaviorDiffers(originalVimAfter =
|
||||||
"""
|
"""
|
||||||
|Lorem ipsum dolor sit amet,
|
|Lorem ipsum dolor sit amet,
|
||||||
@ -708,8 +756,10 @@ class MotionInnerWordActionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test repeated text object expands to empty line`() {
|
fun `test repeated text object expands to empty line`() {
|
||||||
// Well. This behaviour is weird, and looks like a bug, but it matches Vim's behaviour.
|
// Well. This behaviour is weird, and looks like a bug, but it matches Vim's behaviour.
|
||||||
// I'm not entirely sure why this happens, but it's a vote of confidence in IdeaVim's implementation that we're
|
// There's an explanation of the behaviour in VimSearchHelperBase.findWordUnderCursor. Basically, Vim moves forward
|
||||||
// matching bugs! 😁
|
// over an empty line, but when moving back, stops at the start of a line. This puts us off-by-one, but in the same
|
||||||
|
// way that Vim is off-by-one!
|
||||||
|
// See https://github.com/vim/vim/issues/16514
|
||||||
doTest(
|
doTest(
|
||||||
listOf("viw", "iw"),
|
listOf("viw", "iw"),
|
||||||
"""
|
"""
|
||||||
|
@ -708,6 +708,66 @@ class MotionOuterBigWordActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VimBehaviorDiffers(
|
||||||
|
originalVimAfter =
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum...${s}.....
|
||||||
|
|${c}
|
||||||
|
|${se}Lorem ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""",
|
||||||
|
description = "Off by one because IdeaVim does not currently support selecting newline char"
|
||||||
|
)
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from whitespace at end of line with multiple lines and existing left-to-right selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "l", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum...${c}.....
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum...${s}.....
|
||||||
|
|${c}${se}
|
||||||
|
|Lorem ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from whitespace at end of line with multiple lines and existing left-to-right selection 2`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "l", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet...${c}.....
|
||||||
|
| consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet...${s}.....
|
||||||
|
| consectetu${c}r${se} adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test select outer WORD with existing right-to-left selection selects rest of word and preceding whitespace`() {
|
fun `test select outer WORD with existing right-to-left selection selects rest of word and preceding whitespace`() {
|
||||||
doTest(
|
doTest(
|
||||||
@ -758,6 +818,150 @@ class MotionOuterBigWordActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from whitespace at start of line with multiple lines and existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
| ${c} Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|${s}${c}
|
||||||
|
| ${se} Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from whitespace at start of line with multiple lines and existing right-to-left-selection 2`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
| ${c} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit ${s}${c}amet,
|
||||||
|
| ${se} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from whitespace at start of line with multiple lines and existing right-to-left-selection 3`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,.......
|
||||||
|
| ${c} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit ${s}${c}amet,.......
|
||||||
|
| ${se} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from start of line with existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
|c${c}onsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit${s}${c} amet,
|
||||||
|
|co${se}nsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from word at start of line with existing right-to-left selection and preceding whitespace on previous line`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
|cons${c}ectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
|${s}${c}conse${se}ctetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer WORD from word at start of line with existing right-to-left selection and preceding whitespace on previous line 2`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aW"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
| cons${c}ectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
|${s}${c} conse${se}ctetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test select multiple outer WORDs selects following whitespace`() {
|
fun `test select multiple outer WORDs selects following whitespace`() {
|
||||||
doTest(
|
doTest(
|
||||||
|
@ -708,6 +708,66 @@ class MotionOuterWordActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@VimBehaviorDiffers(
|
||||||
|
originalVimAfter =
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum...${s}.....
|
||||||
|
|${c}
|
||||||
|
|${se}Lorem ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""",
|
||||||
|
description = "Off by one because IdeaVim does not currently support selecting newline char"
|
||||||
|
)
|
||||||
|
@Test
|
||||||
|
fun `test select outer word from whitespace at end of line with multiple lines and existing left-to-right selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "l", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum...${c}.....
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum...${s}.....
|
||||||
|
|${c}${se}
|
||||||
|
|Lorem ipsum dolor sit amet
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select word from whitespace at end of line with multiple lines and existing left-to-right selection 2`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "l", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet...${c}.....
|
||||||
|
| consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet...${s}.....
|
||||||
|
| consectetu${c}r${se} adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test select outer word with existing right-to-left selection selects rest of word and preceding whitespace`() {
|
fun `test select outer word with existing right-to-left selection selects rest of word and preceding whitespace`() {
|
||||||
doTest(
|
doTest(
|
||||||
@ -758,6 +818,150 @@ class MotionOuterWordActionTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer word from whitespace at start of line with multiple lines and existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
| ${c} Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|${s}${c}
|
||||||
|
| ${se} Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer word from whitespace at start of line with multiple lines and existing right-to-left-selection 2`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
| ${c} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet${s}${c},
|
||||||
|
| ${se} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer word from whitespace at start of line with multiple lines and existing right-to-left-selection 3`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,.......
|
||||||
|
| ${c} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet${s}${c},.......
|
||||||
|
| ${se} consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer word from start of line with existing right-to-left selection`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,
|
||||||
|
|c${c}onsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet${s}${c},
|
||||||
|
|co${se}nsectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer word from word at start of line with existing right-to-left selection and preceding whitespace on previous line`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
|cons${c}ectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
|${s}${c}conse${se}ctetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test select outer word from word at start of line with existing right-to-left selection and preceding whitespace on previous line 2`() {
|
||||||
|
doTest(
|
||||||
|
listOf("v", "h", "aw"),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
| cons${c}ectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
"""
|
||||||
|
|Lorem Ipsum
|
||||||
|
|
|
||||||
|
|Lorem ipsum dolor sit amet,........
|
||||||
|
|${s}${c} conse${se}ctetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin().dotToSpace(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test select multiple outer words selects following whitespace`() {
|
fun `test select multiple outer words selects following whitespace`() {
|
||||||
doTest(
|
doTest(
|
||||||
|
@ -135,7 +135,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
pos = if (count > 0) {
|
pos = if (count > 0) {
|
||||||
findNextWordEndOne(text, editor, pos, bigWord, stopOnEmptyLine, allowMoveFromWordEnd = true)
|
findNextWordEndOne(text, editor, pos, bigWord, stopOnEmptyLine, allowMoveFromWordEnd = true)
|
||||||
} else {
|
} else {
|
||||||
findPreviousWordEndOne(text, editor, pos, bigWord)
|
findPreviousWordEndOne(text, editor, pos, bigWord).coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return pos
|
return pos
|
||||||
@ -337,12 +337,18 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
start: Int,
|
start: Int,
|
||||||
bigWord: Boolean,
|
bigWord: Boolean,
|
||||||
|
allowMoveFromWordStart: Boolean = true,
|
||||||
): Int {
|
): Int {
|
||||||
var pos = start
|
var pos = start
|
||||||
|
val startingCharType = charType(editor, chars[pos.coerceAtMost(chars.length - 1)], bigWord)
|
||||||
|
|
||||||
// Always move back one to make sure that we don't get stuck on the start of a word
|
// Always move back one to make sure that we don't get stuck on the start of a word
|
||||||
pos--
|
pos--
|
||||||
|
|
||||||
|
if (allowMoveFromWordStart
|
||||||
|
|| startingCharType == charType(editor, chars[pos], bigWord)
|
||||||
|
|| isWhitespace(editor, chars[pos], bigWord)
|
||||||
|
) {
|
||||||
// Skip any intermediate whitespace, stopping at an empty line (offset is the newline char of the empty line).
|
// Skip any intermediate whitespace, stopping at an empty line (offset is the newline char of the empty line).
|
||||||
// This will leave us on the last character of the previous word.
|
// This will leave us on the last character of the previous word.
|
||||||
while (pos >= 0 && isWhitespace(editor, chars[pos], bigWord)) {
|
while (pos >= 0 && isWhitespace(editor, chars[pos], bigWord)) {
|
||||||
@ -350,85 +356,14 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
pos--
|
pos--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (pos <= 0) return 0
|
||||||
|
|
||||||
// We're now on a word character, or at the start of the file. Move back until we're past the start of the word,
|
// We're now on a word character, or at the start of the file. Move back until we're past the start of the word,
|
||||||
// then move forward to the start of the word
|
// then move forward to the start of the word
|
||||||
if (pos >= 0) {
|
pos = skipWhileCharacterType(editor, chars, pos, -1, charType(editor, chars[pos], bigWord), bigWord)
|
||||||
pos = skipWhileCharacterType(editor, chars, pos, -1, charType(editor, chars[pos], bigWord), bigWord) + 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return pos.coerceAtLeast(0)
|
return (pos + 1).coerceAtLeast(0)
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove this once findWordUnderCursor has been properly rewritten
|
|
||||||
private fun oldFindNextWordOne(
|
|
||||||
chars: CharSequence,
|
|
||||||
editor: VimEditor,
|
|
||||||
pos: Int,
|
|
||||||
size: Int,
|
|
||||||
step: Int,
|
|
||||||
bigWord: Boolean,
|
|
||||||
spaceWords: Boolean,
|
|
||||||
): Int {
|
|
||||||
var found = false
|
|
||||||
var _pos = pos // CAREFUL! This might be at the end of the file, but we need this for calculations below
|
|
||||||
|
|
||||||
// For back searches, skip any current whitespace so we start at the end of a word
|
|
||||||
if (step < 0 && _pos > 0) {
|
|
||||||
if (charType(editor, chars[_pos - 1], bigWord) === CharacterHelper.CharacterType.WHITESPACE && !spaceWords) {
|
|
||||||
_pos = skipSpace(editor, chars, pos - 1, step, size, true) + 1
|
|
||||||
}
|
|
||||||
// _pos might be at the end of file. Handle this so we don't try to walk backwards based on incorrect char type
|
|
||||||
if (_pos == size || (_pos > 0 && charType(editor, chars[_pos], bigWord) !== charType(editor, chars[_pos - 1], bigWord))) {
|
|
||||||
_pos += step
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var res = _pos.coerceAtMost(size - 1)
|
|
||||||
if (_pos < 0 || _pos >= size) {
|
|
||||||
return _pos
|
|
||||||
}
|
|
||||||
var char = chars[_pos]
|
|
||||||
var lineLength = 0
|
|
||||||
var type = charType(editor, char, bigWord)
|
|
||||||
if (type === CharacterHelper.CharacterType.WHITESPACE && step < 0 && _pos > 0 && !spaceWords) {
|
|
||||||
type = charType(editor, chars[_pos - 1], bigWord)
|
|
||||||
}
|
|
||||||
_pos += step
|
|
||||||
while (_pos in 0 until size && !found) {
|
|
||||||
val newChar = chars[_pos]
|
|
||||||
val newType = charType(editor, chars[_pos], bigWord)
|
|
||||||
if (newType !== type) {
|
|
||||||
if (newType === CharacterHelper.CharacterType.WHITESPACE && step >= 0 && !spaceWords) {
|
|
||||||
_pos = skipSpace(editor, chars, _pos, step, size, true)
|
|
||||||
res = _pos
|
|
||||||
} else if (step < 0) {
|
|
||||||
res = _pos + 1
|
|
||||||
} else {
|
|
||||||
res = _pos
|
|
||||||
}
|
|
||||||
type = charType(editor, chars[res], bigWord)
|
|
||||||
found = true
|
|
||||||
} else if (newChar == '\n' && (spaceWords || lineLength == 0)) {
|
|
||||||
// An empty line is considered a word/WORD, and if we're matching spaces as words, new line is a terminator
|
|
||||||
res = if (step < 0) _pos + 1 else _pos
|
|
||||||
found = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if (newChar == '\n') lineLength = 0 else lineLength++
|
|
||||||
|
|
||||||
_pos += step
|
|
||||||
}
|
|
||||||
if (found) {
|
|
||||||
if (res < 0) { // (pos <= 0)
|
|
||||||
res = 0
|
|
||||||
} else if (res >= size) { // (pos >= size)
|
|
||||||
res = size - 1
|
|
||||||
}
|
|
||||||
} else if (_pos <= 0) {
|
|
||||||
res = 0
|
|
||||||
} else if (_pos >= size) {
|
|
||||||
res = size
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("GrazieInspection")
|
@Suppress("GrazieInspection")
|
||||||
@ -488,11 +423,18 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
return pos.coerceIn(0, chars.length - 1)
|
return pos.coerceIn(0, chars.length - 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the end of the previous word, skipping the current word and any intermediate whitespace
|
||||||
|
*
|
||||||
|
* Note that this will return `-1` if there is no previous word! This is necessary to distinguish between no previous
|
||||||
|
* word and the previous word being on the last character of the file.
|
||||||
|
*/
|
||||||
private fun findPreviousWordEndOne(
|
private fun findPreviousWordEndOne(
|
||||||
chars: CharSequence,
|
chars: CharSequence,
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
start: Int,
|
start: Int,
|
||||||
bigWord: Boolean,
|
bigWord: Boolean,
|
||||||
|
stopAtEndOfPreviousLine: Boolean = false,
|
||||||
): Int {
|
): Int {
|
||||||
var pos = start
|
var pos = start
|
||||||
val startingCharType = charType(editor, chars[pos], bigWord)
|
val startingCharType = charType(editor, chars[pos], bigWord)
|
||||||
@ -510,32 +452,17 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
// If we ended up on whitespace, skip backwards until we find either the last character of the previous word/WORD,
|
// If we ended up on whitespace, skip backwards until we find either the last character of the previous word/WORD,
|
||||||
// or we have to stop at an empty line, which is considered a WORD
|
// or we have to stop at an empty line, which is considered a WORD
|
||||||
while (pos in 0 until chars.length && isWhitespace(editor, chars[pos], bigWord)) {
|
while (pos in 0 until chars.length && isWhitespace(editor, chars[pos], bigWord)) {
|
||||||
// Always check empty line when moving backwards
|
// Unlike moving forwards, we always check for empty lines.
|
||||||
if (isEmptyLine(chars, pos)) return pos
|
// If requested, we stop when we wrap at the start of the current line. Ideally, we would stop when we hit the
|
||||||
|
// start of the current line (e.g. return `pos+1`), but this doesn't work with how the function is called. It's
|
||||||
|
// used when finding the word under the cursor - we move to the character after the end of the previous word. It's
|
||||||
|
// easier to return the newline at the end of the previous line so that adding one will move us to the start of
|
||||||
|
// the current line.
|
||||||
|
if (isEmptyLine(chars, pos) || (chars[pos] == '\n' && stopAtEndOfPreviousLine)) return pos
|
||||||
pos--
|
pos--
|
||||||
}
|
}
|
||||||
|
|
||||||
return pos.coerceAtLeast(0)
|
return pos
|
||||||
}
|
|
||||||
|
|
||||||
private fun skipSpace(
|
|
||||||
editor: VimEditor,
|
|
||||||
chars: CharSequence,
|
|
||||||
offset: Int,
|
|
||||||
step: Int,
|
|
||||||
size: Int,
|
|
||||||
matchEmptyLine: Boolean,
|
|
||||||
): Int {
|
|
||||||
var _offset = offset
|
|
||||||
var prev = 0.toChar()
|
|
||||||
while (_offset in 0 until size) {
|
|
||||||
val c = chars[_offset]
|
|
||||||
if (c == '\n' && c == prev && matchEmptyLine) break
|
|
||||||
if (charType(editor, c, false) !== CharacterHelper.CharacterType.WHITESPACE) break
|
|
||||||
prev = c
|
|
||||||
_offset += step
|
|
||||||
}
|
|
||||||
return if (_offset < size) _offset else size - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isEmptyLine(chars: CharSequence, offset: Int): Boolean {
|
private fun isEmptyLine(chars: CharSequence, offset: Int): Boolean {
|
||||||
@ -1494,59 +1421,17 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
isBig: Boolean,
|
isBig: Boolean,
|
||||||
hasSelection: Boolean,
|
hasSelection: Boolean,
|
||||||
): TextRange {
|
): TextRange {
|
||||||
logger.debug("count=$count")
|
// Note: for more detailed comments with examples, check git history!
|
||||||
logger.debug("dir=$dir")
|
val pos = caret.offset
|
||||||
logger.debug("isOuter=$isOuter")
|
val chars = editor.text()
|
||||||
logger.debug("isBig=$isBig")
|
if (chars.isEmpty()) return TextRange(0, 0)
|
||||||
logger.debug("hasSelection=$hasSelection")
|
|
||||||
|
|
||||||
val chars: CharSequence = editor.text()
|
|
||||||
val max: Int = editor.fileSize().toInt()
|
|
||||||
if (max == 0) return TextRange(0, 0)
|
|
||||||
|
|
||||||
logger.debug("max=$max")
|
|
||||||
|
|
||||||
val pos: Int = caret.offset
|
|
||||||
if (chars.length <= pos) return TextRange(chars.length - 1, chars.length - 1)
|
if (chars.length <= pos) return TextRange(chars.length - 1, chars.length - 1)
|
||||||
|
|
||||||
val onSpace = charType(editor, chars[pos], isBig) === CharacterHelper.CharacterType.WHITESPACE
|
|
||||||
|
|
||||||
// Find word start. Note that the caret might be on the word start, but the selection start might not be!
|
|
||||||
val onWordStart = pos == 0 || charType(editor, chars[pos - 1], isBig) !== charType(editor, chars[pos], isBig)
|
|
||||||
var start = pos
|
var start = pos
|
||||||
|
var end = start
|
||||||
logger.debug("pos=$pos")
|
|
||||||
logger.debug("onWordStart=$onWordStart")
|
|
||||||
|
|
||||||
// TODO: This could be simplified to move backwards until char type changes
|
|
||||||
if ((!onWordStart && !(onSpace && isOuter)) || hasSelection || (count > 1 && dir == -1)) {
|
|
||||||
start = if (dir == 1) {
|
|
||||||
oldFindNextWordOne(editor.text(), editor, pos, editor.text().length, -1, isBig, spaceWords = !isOuter)
|
|
||||||
} else {
|
|
||||||
val c = -(count - if (onWordStart && !hasSelection) 1 else 0)
|
|
||||||
oldFindNextWordOne(editor.text(), editor, pos, editor.text().length, c, isBig, spaceWords = !isOuter)
|
|
||||||
}
|
|
||||||
start = editor.normalizeOffset(start, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("start=$start")
|
|
||||||
|
|
||||||
// Find word end
|
|
||||||
val onWordEnd = pos >= max - 1 || charType(editor, chars[pos + 1], isBig) !== charType(editor, chars[pos], isBig)
|
|
||||||
|
|
||||||
logger.debug("onWordEnd=$onWordEnd")
|
|
||||||
|
|
||||||
var end = pos
|
|
||||||
|
|
||||||
// TODO: Figure out the logic of this going backwards
|
|
||||||
if (dir == 1) {
|
|
||||||
var count = count
|
var count = count
|
||||||
var shouldEndOnWhitespace = false
|
var shouldEndOnWhitespace = false
|
||||||
|
|
||||||
// Note: for more detailed comments with examples, check git history!
|
|
||||||
|
|
||||||
end = pos
|
|
||||||
|
|
||||||
// If there's no selection, calculate the initial range by moving back to the start of the current character type
|
// If there's no selection, calculate the initial range by moving back to the start of the current character type
|
||||||
// on the current line (word/WORD or whitespace). Then move forward:
|
// on the current line (word/WORD or whitespace). Then move forward:
|
||||||
// * For inner objects, move to the end of the current word or whitespace block (or line).
|
// * For inner objects, move to the end of the current word or whitespace block (or line).
|
||||||
@ -1565,8 +1450,8 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
end = if ((!isOuter && isWhitespace(editor, chars[start], isBig))
|
end = if ((!isOuter && isWhitespace(editor, chars[start], isBig))
|
||||||
|| (isOuter && !isWhitespace(editor, chars[start], isBig))) {
|
|| (isOuter && !isWhitespace(editor, chars[start], isBig))
|
||||||
|
) {
|
||||||
// * Inner object, on whitespace. Skip forward to the end of the current whitespace, just before the next
|
// * Inner object, on whitespace. Skip forward to the end of the current whitespace, just before the next
|
||||||
// word or end of line (no wrapping). This will always move us forward one character, so it's always safe to
|
// word or end of line (no wrapping). This will always move us forward one character, so it's always safe to
|
||||||
// move one character back. If we're moving on to an empty line (newline is whitespace!) this will move one
|
// move one character back. If we're moving on to an empty line (newline is whitespace!) this will move one
|
||||||
@ -1581,8 +1466,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
|
|
||||||
val offset = findNextWordOne(chars, editor, start, isBig, stopAtEndOfLine = true)
|
val offset = findNextWordOne(chars, editor, start, isBig, stopAtEndOfLine = true)
|
||||||
skipOneCharacterBack(offset)
|
skipOneCharacterBack(offset)
|
||||||
}
|
} else {
|
||||||
else {
|
|
||||||
// * Inner object, on word. Move to the end of the current word, do not bother with whitespace.
|
// * Inner object, on word. Move to the end of the current word, do not bother with whitespace.
|
||||||
// * Outer object, on whitespace. Include whitespace and the following word by moving to the end of the next
|
// * Outer object, on whitespace. Include whitespace and the following word by moving to the end of the next
|
||||||
// word/WORD. Newlines are considered whitespace and so can wrap. Make sure that if we are currently at the
|
// word/WORD. Newlines are considered whitespace and so can wrap. Make sure that if we are currently at the
|
||||||
@ -1593,6 +1477,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
count--
|
count--
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dir == 1) {
|
||||||
// Once we have an initial selection, loop over what's left of count.
|
// Once we have an initial selection, loop over what's left of count.
|
||||||
// * For inner objects, move to the end of the current or next character type block, or the end of line.
|
// * For inner objects, move to the end of the current or next character type block, or the end of line.
|
||||||
// If we're on the last character, it's the next block, otherwise it's the current block. Whitespace is not
|
// If we're on the last character, it's the next block, otherwise it's the current block. Whitespace is not
|
||||||
@ -1633,7 +1518,7 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
// newline char of the previous line.
|
// newline char of the previous line.
|
||||||
// You can see this behaviour with `v2iw` on empty lines. Vim selects the first line while initialising the
|
// You can see this behaviour with `v2iw` on empty lines. Vim selects the first line while initialising the
|
||||||
// range, and then advances 2 lines while handling the second iteration. Similarly, `v3iw` selects 5 lines.
|
// range, and then advances 2 lines while handling the second iteration. Similarly, `v3iw` selects 5 lines.
|
||||||
// Interestingly, because we're not at the start of another line, the now-current line might not be empty.
|
// Interestingly, because we're now at the start of another line, the now-current line might not be empty.
|
||||||
// That means Vim now has a "word" text object that selects just the first character in a line!
|
// That means Vim now has a "word" text object that selects just the first character in a line!
|
||||||
// And because we've figured out this difference in handling empty lines, we match Vim's quirky behaviour!
|
// And because we've figured out this difference in handling empty lines, we match Vim's quirky behaviour!
|
||||||
// See vim/vim#16514
|
// See vim/vim#16514
|
||||||
@ -1654,6 +1539,39 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
findNextWordEndOne(chars, editor, end, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
findNextWordEndOne(chars, editor, end, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// If direction is backwards, then end is already correctly positioned, and we need to move start.
|
||||||
|
repeat(count) {
|
||||||
|
// As above, move back early so we handle word boundaries correctly
|
||||||
|
start--
|
||||||
|
if (start > 0 && chars[start] == '\n' && !isEmptyLine(chars, start)) {
|
||||||
|
start--
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start < 0) {
|
||||||
|
start++
|
||||||
|
return@repeat
|
||||||
|
}
|
||||||
|
|
||||||
|
start = if ((!isOuter && isWhitespace(editor, chars[start], isBig))
|
||||||
|
|| (isOuter && !isWhitespace(editor, chars[start], isBig))
|
||||||
|
) {
|
||||||
|
// * Inner object, on whitespace. Move to start of whitespace, by moving to the end of the previous word and
|
||||||
|
// then moving forward. Newlines are whitespace, but we stop at the start of the line.
|
||||||
|
// * Outer object, on word. Move to start of current word, then include preceding whitespace, but stop at
|
||||||
|
// start of line. This is the same as one past the end of the previous word.
|
||||||
|
val offset = findPreviousWordEndOne(chars, editor, start, isBig, stopAtEndOfPreviousLine = true) + 1
|
||||||
|
if (chars[offset] == '\n') offset + 1 else offset
|
||||||
|
} else {
|
||||||
|
// * Inner object, on word. Move back to the start of the current word. Ignore whitespace.
|
||||||
|
// * Outer object, on whitespace. Skip the current whitespace and move to the start of the previous word.
|
||||||
|
// Newlines are whitespace, so this will wrap at the start of the line and move to the start of the last
|
||||||
|
// word on the previous line, skipping trailing whitespace.
|
||||||
|
findPreviousWordOne(chars, editor, start, isBig, allowMoveFromWordStart = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (isOuter && shouldEndOnWhitespace && start > 0
|
if (isOuter && shouldEndOnWhitespace && start > 0
|
||||||
&& !isWhitespace(editor, chars[end], isBig)
|
&& !isWhitespace(editor, chars[end], isBig)
|
||||||
@ -1676,102 +1594,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
// start==endExclusive). This little hack makes sure that `viw` will (mostly) work on a single empty line
|
// start==endExclusive). This little hack makes sure that `viw` will (mostly) work on a single empty line
|
||||||
if (start == end && chars[start] == '\n') end++
|
if (start == end && chars[start] == '\n') end++
|
||||||
|
|
||||||
return TextRange(start, end + 1)
|
|
||||||
}
|
|
||||||
else if (!onWordEnd || hasSelection || (count > 1 && dir == 1) || (onSpace && isOuter)) {
|
|
||||||
end = if (dir == 1) {
|
|
||||||
val c = count - if (onWordEnd && !hasSelection && (!(onSpace && isOuter) || (onSpace && !isOuter))) 1 else 0
|
|
||||||
var c2 = 0
|
|
||||||
repeat(c) {
|
|
||||||
c2 += findNextWordEndOne(chars, editor, end, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
|
||||||
}
|
|
||||||
c2
|
|
||||||
} else {
|
|
||||||
findNextWordEnd(editor, pos, 1, isBig, !isOuter)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug("end=$end")
|
|
||||||
|
|
||||||
val hasForwardHeadingSelection = dir == 1 && hasSelection
|
|
||||||
val hasBackwardHeadingSelection = dir == -1 && hasSelection
|
|
||||||
val hasFollowingWhitespace = if (end < max - 1) {
|
|
||||||
val c = chars[end + 1]
|
|
||||||
charType(editor, c, false) === CharacterHelper.CharacterType.WHITESPACE && c != '\n'
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
val includePrecedingWhitespace = if (isOuter) {
|
|
||||||
// Outer word motion. Include preceding whitespace:
|
|
||||||
// ✗ NEVER: Has forward-facing selection
|
|
||||||
// ✓ Started on space, and there's no (forward-facing) selection
|
|
||||||
// ✓ Started on word and has backward-facing selection
|
|
||||||
// ✓ No whitespace after word under cursor (see `:help v_a'`), but only if there's a preceding word on the line
|
|
||||||
!hasForwardHeadingSelection
|
|
||||||
&& ((onSpace && !hasSelection)
|
|
||||||
|| (hasBackwardHeadingSelection && !onSpace)
|
|
||||||
|| (!hasFollowingWhitespace && editor.anyNonWhitespace(start, -1)))
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Inner word motion. Include preceding whitespace:
|
|
||||||
// ✓ Start on space with backwards-facing selection
|
|
||||||
// ✓ Start on space with no (forwards-facing) selection
|
|
||||||
onSpace && (hasBackwardHeadingSelection || !hasForwardHeadingSelection)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Include following whitespace:
|
|
||||||
// * ALWAYS: outer word motions with forward direction, has following whitespace to select, and we're not already
|
|
||||||
// about to extend the range with preceding whitespace (Vim usually only expands in one direction)
|
|
||||||
// * AND:
|
|
||||||
// ✓ Does not have a selection
|
|
||||||
// ✓ Has a selection that does not start on (preceding) whitespace
|
|
||||||
// ✓ The range between caret offset (exclusive) and end of word does not contain whitespace
|
|
||||||
// This last one is subtle, and means we can expand in both directions (perhaps only through repeated motions,
|
|
||||||
// such as `vawaw`). Examples:
|
|
||||||
// * Wrapping across newlines. On the last word, there is no following whitespace, so we select preceding
|
|
||||||
// whitespace. Repeating the motion expands to the end of the next word on a subsequent line. But if that word
|
|
||||||
// has preceding whitespace, even on a prior line, then we don't expand the range to following whitespace
|
|
||||||
// * If the next word is not space, but a non-word character, then we expand to include following whitespace
|
|
||||||
val selectionStartOnSpace = hasSelection && charType(editor, chars[caret.vimSelectionStart], isBig) === CharacterHelper.CharacterType.WHITESPACE
|
|
||||||
val hasIntermediateWhitespace =
|
|
||||||
(pos + 1 < max && chars[pos + 1] != '\n' && charType(editor, chars[pos + 1], isBig) === CharacterHelper.CharacterType.WHITESPACE)
|
|
||||||
|| (pos + 2 < max && chars[pos + 1] == '\n' && charType(editor, chars[pos + 2], isBig) === CharacterHelper.CharacterType.WHITESPACE)
|
|
||||||
val includeFollowingWhitespace = isOuter && dir == 1
|
|
||||||
&& !includePrecedingWhitespace && hasFollowingWhitespace
|
|
||||||
&& (!hasSelection || (!selectionStartOnSpace && !hasIntermediateWhitespace) || !hasIntermediateWhitespace)
|
|
||||||
|
|
||||||
logger.debug("goBack=$includePrecedingWhitespace")
|
|
||||||
logger.debug("goForward=$includeFollowingWhitespace")
|
|
||||||
|
|
||||||
if (includeFollowingWhitespace) {
|
|
||||||
while (end + 1 < max
|
|
||||||
&& chars[end + 1] != '\n'
|
|
||||||
&& charType(editor, chars[end + 1], false) === CharacterHelper.CharacterType.WHITESPACE
|
|
||||||
) {
|
|
||||||
end++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (includePrecedingWhitespace) {
|
|
||||||
while (start > 0
|
|
||||||
&& chars[start - 1] != '\n'
|
|
||||||
&& charType(editor, chars[start - 1], false) === CharacterHelper.CharacterType.WHITESPACE
|
|
||||||
) {
|
|
||||||
start--
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
// Text range's end offset is exclusive
|
||||||
return TextRange(start, end + 1)
|
return TextRange(start, end + 1)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user