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

Fix incsearch not updating empty selection

As a by-product, this also fixes an off-by-one error where incsearch would effectively treat all Visual searches as exclusive
This commit is contained in:
Matt Ellis 2025-01-03 17:25:43 +00:00 committed by Alex Pláte
parent a969b93ba6
commit e9e86b07fb
6 changed files with 128 additions and 15 deletions
src
main/java/com/maddyhome/idea/vim/ui/ex
test/java/org/jetbrains/plugins/ideavim/group/search
vim-engine/src/main/kotlin/com/maddyhome/idea/vim

View File

@ -308,6 +308,9 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
protected void textChanged(@NotNull DocumentEvent e) {
try {
final Editor editor = entry.getEditor();
if (editor == null) {
return;
}
final String labelText = label.getText(); // Either '/', '?' or ':'boolean searchCommand = false;
@ -351,8 +354,8 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
final String pattern = searchText.substring(0, pattenEnd);
int patternEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
final String pattern = searchText.substring(0, patternEnd);
VimPlugin.getEditor().closeEditorSearchSession(editor);
final int matchOffset =

View File

@ -563,7 +563,67 @@ class IncsearchTests : VimTestCase() {
}
@Test
fun `test incsearch updates selection when started in Visual mode`() {
fun `test incsearch updates Visual selection`() {
doTest(
listOf("ve", "/dolor"),
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""
|${s}Lorem ipsum ${c}d${se}olor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.CMD_LINE(Mode.VISUAL(SelectionType.CHARACTER_WISE))
) {
enterCommand("set hlsearch incsearch")
}
assertSearchHighlights("dolor",
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
}
@Test
fun `test incsearch updates empty Visual selection`() {
doTest(
"v/ipsum",
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""
|${s}Lorem ${c}i${se}psum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.CMD_LINE(Mode.VISUAL(SelectionType.CHARACTER_WISE))
) {
enterCommand("set hlsearch incsearch")
}
assertSearchHighlights("ipsum",
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
}
@Test
fun `test incsearch updates exclusive Visual selection`() {
doTest(
listOf("ve", "/dolor"),
"""
@ -580,8 +640,48 @@ class IncsearchTests : VimTestCase() {
""".trimMargin(),
Mode.CMD_LINE(Mode.VISUAL(SelectionType.CHARACTER_WISE))
) {
enterCommand("set selection=exclusive")
enterCommand("set hlsearch incsearch")
}
assertSearchHighlights("dolor",
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
}
@Test
fun `test incsearch updates empty exclusive Visual selection`() {
doTest(
"v/ipsum",
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""
|${s}Lorem ${c}${se}ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.CMD_LINE(Mode.VISUAL(SelectionType.CHARACTER_WISE))
) {
enterCommand("set selection=exclusive")
enterCommand("set hlsearch incsearch")
}
assertSearchHighlights("ipsum",
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin()
)
}
@Test

View File

@ -20,7 +20,7 @@ import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.register.Register
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import com.maddyhome.idea.vim.state.mode.inCommandLineMode
import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import javax.swing.KeyStroke
@ -98,27 +98,26 @@ per-caret marks.
// Make sure to always reposition the caret, even if the offset hasn't changed. We might need to reposition due to
// changes in surrounding text, especially with inline inlays.
val oldOffset = this.offset
var caretAfterMove = moveToInlayAwareOffset(offset)
var updatedCaret = moveToInlayAwareOffset(offset)
// Similarly, always make sure the caret is positioned within the view. Adding or removing text could move the caret
// position relative to the view, without changing offset.
if (this == editor.primaryCaret()) {
injector.scroll.scrollCaretIntoView(editor)
}
caretAfterMove = if (editor.inVisualMode || editor.inSelectMode) {
// If we're in Visual or Select mode, update the selection. We also need to handle Command-line mode, with Visual
// pending, e.g. `v/foo` or `v:<C-U>normal 3j`
updatedCaret = if (editor.inVisualMode || editor.inSelectMode || editor.inCommandLineModeWithVisual) {
// Another inconsistency with immutable caret. This method should be called on the new caret instance.
caretAfterMove.vimMoveSelectionToCaret(this.vimSelectionStart)
editor.findLastVersionOfCaret(caretAfterMove) ?: caretAfterMove
} else if (editor.inCommandLineMode && caretAfterMove.hasSelection()) {
// If we're updating the caret in Command-line mode, it's most likely due to incsearch
caretAfterMove.setSelection(caretAfterMove.selectionStart, offset)
editor.findLastVersionOfCaret(caretAfterMove) ?: caretAfterMove
updatedCaret.vimMoveSelectionToCaret(this.vimSelectionStart)
editor.findLastVersionOfCaret(updatedCaret) ?: updatedCaret
} else {
editor.exitVisualMode()
caretAfterMove
updatedCaret
}
injector.motion.onAppCodeMovement(editor, this, offset, oldOffset)
return caretAfterMove
return updatedCaret
}
fun moveToOffsetNative(offset: Int)

View File

@ -20,6 +20,7 @@ import com.maddyhome.idea.vim.helper.RWLockLabel
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import com.maddyhome.idea.vim.state.mode.inCommandLineModeWithVisual
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.state.mode.selectionType
@ -122,7 +123,7 @@ fun vimMoveBlockSelectionToOffset(editor: VimEditor, offset: Int) {
* @see vimMoveBlockSelectionToOffset for blockwise selection
*/
fun VimCaret.vimMoveSelectionToCaret(vimSelectionStart: Int = this.vimSelectionStart) {
if (!editor.inVisualMode && !editor.inSelectMode) error("Attempt to extent selection in non-visual mode")
if (!editor.inVisualMode && !editor.inSelectMode && !editor.inCommandLineModeWithVisual) error("Attempt to extent selection in non-visual mode")
if (editor.inBlockSelection) error("Move caret with [vimMoveBlockSelectionToOffset]")
val startOffsetMark = vimSelectionStart

View File

@ -156,6 +156,13 @@ sealed interface Mode {
"CMD_LINE mode can be active only in NORMAL, OP_PENDING, VISUAL or INSERT modes, not ${returnTo.javaClass.simpleName}"
}
}
/**
* Returns true if Visual mode is pending, by starting Command-line with a Visual selection
*
* For example, `v/foo` or `v:<C-U>normal 3j`
*/
val isVisualPending = returnTo is VISUAL
}
}

View File

@ -26,6 +26,9 @@ val VimEditor.inSelectMode: Boolean
val VimEditor.inCommandLineMode: Boolean
get() = this.mode is Mode.CMD_LINE
val VimEditor.inCommandLineModeWithVisual: Boolean
get() = (this.mode as? Mode.CMD_LINE)?.isVisualPending == true
val VimEditor.singleModeActive: Boolean
get() = this.mode.isSingleModeActive