mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-24 06:34:10 +02:00
Rewrite inner motion to match outer motion
The implementations are so similar and can now be refactored/simplified
This commit is contained in:
parent
1a8789b50c
commit
0428c2aeff
@ -360,22 +360,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
return pos.coerceAtLeast(0)
|
return pos.coerceAtLeast(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("SameParameterValue")
|
|
||||||
private fun findCharBeforeNextWord(editor: VimEditor, pos: Int, isBig: Boolean, stopAtEndOfLine: Boolean): Int {
|
|
||||||
val chars = editor.text()
|
|
||||||
|
|
||||||
// Find the next word, and take the character before it. If there is no next word, we're at the end of the file, and
|
|
||||||
// offset will be chars.length. Subtracting one gives us an in-bounds index again
|
|
||||||
var offset = findNextWordOne(chars, editor, pos, isBig, stopAtEndOfLine) - 1
|
|
||||||
|
|
||||||
// Don't back up to the end of a non-empty line
|
|
||||||
if (chars[offset] == '\n' && !isEmptyLine(chars, offset)) {
|
|
||||||
offset--
|
|
||||||
}
|
|
||||||
|
|
||||||
return offset
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Remove this once findWordUnderCursor has been properly rewritten
|
// TODO: Remove this once findWordUnderCursor has been properly rewritten
|
||||||
private fun oldFindNextWordOne(
|
private fun oldFindNextWordOne(
|
||||||
chars: CharSequence,
|
chars: CharSequence,
|
||||||
@ -583,6 +567,14 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
return offset
|
return offset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun skipOneCharacterBack(offset: Int): Int {
|
||||||
|
return (offset - 1).coerceAtLeast(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun skipOneCharacterBackOnCurrentLine(chars: CharSequence, offset: Int): Int {
|
||||||
|
return if (chars[offset - 1] != '\n') offset - 1 else offset
|
||||||
|
}
|
||||||
|
|
||||||
override fun findNextCamelStart(chars: CharSequence, startIndex: Int, count: Int): Int? {
|
override fun findNextCamelStart(chars: CharSequence, startIndex: Int, count: Int): Int? {
|
||||||
return findCamelStart(chars, startIndex, count, Direction.FORWARDS)
|
return findCamelStart(chars, startIndex, count, Direction.FORWARDS)
|
||||||
}
|
}
|
||||||
@ -1593,24 +1585,42 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
// Note that `onSpace` is the character type of the original position, but this is also the character type of
|
// Note that `onSpace` is the character type of the original position, but this is also the character type of
|
||||||
// the current start position
|
// the current start position
|
||||||
end = when {
|
end = when {
|
||||||
// We're on preceding whitespace. Include it, and move to the end of the next word/WORD
|
// We're on preceding whitespace. Include it, and move to the end of the next word/WORD. Newlines are
|
||||||
|
// considered whitespace and this can wrap to the next line. An empty line will be considered a word and
|
||||||
|
// included.
|
||||||
isOuter && onSpace -> // "${s} word${se}"
|
isOuter && onSpace -> // "${s} word${se}"
|
||||||
findNextWordEnd(editor, start, 1, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
findNextWordEnd(editor, start, 1, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
||||||
|
|
||||||
// We're on a word, move to the end, and include following whitespace by moving to the character before the
|
// We're on a word, move to the end, and include following whitespace by moving to the character before the
|
||||||
// next word
|
// next word. Newlines are not considered part of whitespace, not included, and this does not wrap.
|
||||||
isOuter && !onSpace -> { // "${s}word ${se}word"
|
isOuter && !onSpace -> { // "${s}word ${se}word"
|
||||||
shouldEndOnWhitespace = true
|
shouldEndOnWhitespace = true
|
||||||
findCharBeforeNextWord(editor, start, isBig, stopAtEndOfLine = true)
|
|
||||||
|
// Outer object should include following whitespace. Skip forward over the current word and following
|
||||||
|
// whitespace. We know this isn't an empty line, and that we'll stop at the end of line, so it's always safe
|
||||||
|
// to move back one character.
|
||||||
|
val offset = findNextWordOne(chars, editor, start, isBig, stopAtEndOfLine = true)
|
||||||
|
skipOneCharacterBack(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
// We're on a word, move to the end, not including trailing whitespace
|
// We're on a word, move to the end, not including trailing whitespace. This never includes whitespace, and so
|
||||||
|
// never wraps
|
||||||
!isOuter && !onSpace -> // "${s}word${se} word"
|
!isOuter && !onSpace -> // "${s}word${se} word"
|
||||||
findNextWordEnd(editor, start, 1, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
findNextWordEnd(editor, start, 1, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
||||||
|
|
||||||
// We're on preceding whitespace, move to the character before the next word
|
// We're on preceding whitespace, move to the character before the next word. Newlines are not considered
|
||||||
else /* !isOuter && onSpace */ -> // "${s} ${se}word"
|
// whitespace and this does not wrap. Empty lines also do not wrap.
|
||||||
findCharBeforeNextWord(editor, start, isBig, stopAtEndOfLine = true)
|
else /* !isOuter && onSpace */ -> { // "${s} ${se}word"
|
||||||
|
|
||||||
|
// Inner object does not include whitespace, but does count it. Skip forward over the current whitespace
|
||||||
|
// until we find a new word or the end of line. The implementation of `findNextWordOne` will always move at
|
||||||
|
// least one character forward, so it's always safe to move one character back. If we are on an empty line,
|
||||||
|
// `findNextWordOne` will still move one character forward, taking us to the next line. Moving one back will
|
||||||
|
// return us to the original offset. You can see this with `viw` on an empty line - it only selects the
|
||||||
|
// current line.
|
||||||
|
val offset = findNextWordOne(chars, editor, start, isBig, stopAtEndOfLine = true)
|
||||||
|
skipOneCharacterBack(offset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
count--
|
count--
|
||||||
@ -1677,8 +1687,11 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
findNextWordEnd(editor, end, 1, isBig, stopOnEmptyLine = true)
|
findNextWordEnd(editor, end, 1, isBig, stopOnEmptyLine = true)
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Move to one before start of next word (skips following whitespace)
|
// Outer object includes whitespace. Starting on a word character, skip to the end of the current word and
|
||||||
findCharBeforeNextWord(editor, end, isBig, stopAtEndOfLine = true)
|
// then move one character back. Since we're on a word character, we know this isn't an empty line, and we
|
||||||
|
// will therefore always move forward, and so it is always safe to move one character back.
|
||||||
|
val offset = findNextWordOne(chars, editor, end, isBig, stopAtEndOfLine = true)
|
||||||
|
skipOneCharacterBack(offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -1714,15 +1727,6 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
//
|
//
|
||||||
// This can be generalised to move forward on character, skip again if it's a newline, then move to end of
|
// This can be generalised to move forward on character, skip again if it's a newline, then move to end of
|
||||||
// the now current character type.
|
// the now current character type.
|
||||||
// NEW: I think we can generalise to the same algorithm as outer motions, but flipped
|
|
||||||
// Move forward one char, and again if we land on newline
|
|
||||||
// If on whitespace, move to one char before start of next word (skipping following whitespace)
|
|
||||||
// If on word, move to end of current word (no preceding whitespace to skip)
|
|
||||||
// The trick is that we're deciding based on the moved offset, not the original offset. That's why the
|
|
||||||
// examples in the table above have some that don't work.
|
|
||||||
// Unfortunately, this doesn't work, possibly because findNextWord needs to be rewritten. E.g. we don't have a
|
|
||||||
// way of telling it to stop at the end of line
|
|
||||||
// TODO: Rewrite findNextWord and then try to rewrite this object to work the same for inner+outer motions
|
|
||||||
|
|
||||||
// Increment, and skip the newline char, unless we've just landed on an empty line
|
// Increment, and skip the newline char, unless we've just landed on an empty line
|
||||||
end++
|
end++
|
||||||
@ -1735,18 +1739,31 @@ abstract class VimSearchHelperBase : VimSearchHelper {
|
|||||||
return@repeat
|
return@repeat
|
||||||
}
|
}
|
||||||
|
|
||||||
val characterType = charType(editor, chars[end], isBig)
|
end = if (isWhitespace(editor, chars[end], isBig)) {
|
||||||
while (end < chars.length && charType(editor, chars[end], isBig) == characterType) {
|
// Inner object does not include whitespace, but does count it. Skip to the end of the whitespace by moving
|
||||||
end++
|
// to one character before the next word or end of the current line.
|
||||||
|
// For a non-empty line, it is always possible to move forward and so it is always safe to move one
|
||||||
// Break at end of line. We only wrap when we start at the end of the line, and that's handled above
|
// character back.
|
||||||
if (end < chars.length && chars[end] == '\n') break
|
// Things get weird with empty lines. When handling empty lines above (when there is no initial selection),
|
||||||
|
// we try to get to the character before the next word. We advance, wrap to the next line, and stop because
|
||||||
|
// we're on an empty line (normal before for e.g. `w`). We then come back one character and that puts us
|
||||||
|
// back at the initial offset, and the caret doesn't move.
|
||||||
|
// Vim does things differently if there's an existing selection, and we're moving on to an empty line. The
|
||||||
|
// algorithm needs to see what the next character is, so we move one char forward. This skips us past a
|
||||||
|
// newline char and onto an empty line. We then try to find the next word which automatically advances one,
|
||||||
|
// onto the start of the next line. And now Vim does NOT go back one character, because that would put us
|
||||||
|
// at the newline of the previous line.
|
||||||
|
// Interestingly, because we're not at the start of another line, this one might not be empty. But Vim still
|
||||||
|
// does not move back one, leading to an odd scenario where `iw` can select the *first* character of a word
|
||||||
|
// after whitespace/empty lines. See vim/vim#16514
|
||||||
|
// By refusing to move back even if the current line isn't empty, we're matching Vim's quirky behaviour!
|
||||||
|
val offset = findNextWordOne(chars, editor, end, isBig, stopAtEndOfLine = true)
|
||||||
|
skipOneCharacterBackOnCurrentLine(chars, offset)
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
// We've gone past the current character type, or hit a newline. Go back, unless that would put us onto a
|
// Skip to the end of the current word. This would skip preceding whitespace, but we know we're on a word
|
||||||
// newline char
|
// character.
|
||||||
if (chars[end - 1] != '\n' || end >= chars.length) {
|
findNextWordEnd(editor, end, 1, isBig, stopOnEmptyLine = true, allowMoveFromWordEnd = false)
|
||||||
end--
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user