Skip to content

Commit

Permalink
Fix incsearch highlights with operator count
Browse files Browse the repository at this point in the history
E.g. `2"a3"b4"c5d6/foo` will now highlight the current match correctly
  • Loading branch information
citizenmatt committed May 30, 2024
1 parent 26d1e81 commit fa18d23
Show file tree
Hide file tree
Showing 8 changed files with 83 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -58,11 +58,11 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
)
} else if (!repeatHandler && lastCommand != null) {
if (cmd.rawCount > 0) {
lastCommand.count = cmd.count
lastCommand.rawCount = cmd.count
val arg = lastCommand.argument
if (arg != null) {
val mot = arg.motion
mot.count = 0
mot.rawCount = 0
}
}
state.executingCommand = lastCommand
Expand Down
10 changes: 6 additions & 4 deletions src/main/java/com/maddyhome/idea/vim/ui/ex/ExEntryPanel.java
Original file line number Diff line number Diff line change
Expand Up @@ -304,10 +304,12 @@ protected void textChanged(@NotNull DocumentEvent e) {
}
}

// If we're showing highlights for the search command `/`, then the command builder will have a count already
// coerced to 1. If we're showing highlights for an ex command such as `:s`, there won't be a command, and there
// obviously won't be a count.
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getCount());
// Get the current count from the command builder. This value is coerced to at least 1, so will always be valid.
// The aggregated value includes any counts for operator and register selections, and the uncommitted count for
// the search command (`/` or `?`). E.g., `2"a3"b4"c5d6/` would return 720.
// If we're showing highlights for an ex command like `:s`, there won't be a command, but the value is already
// coerced to at least 1.
int count1 = KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getAggregatedUncommittedCount();

if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,24 @@ class MotionActionTest : VimTestCase() {
}
}

@Test
fun testDeleteToSearchResultWithCount() {
doTest(
"d3/ipsum<CR>",
"lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum",
"ipsum lorem 4 ipsum lorem 5 ipsum"
)
}

@Test
fun testDeleteToSearchResultWithCountAndOperatorCount() {
doTest(
"2d3/ipsum<CR>",
"lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum lorem 6 ipsum lorem 7 ipsum",
"ipsum lorem 7 ipsum"
)
}

@Test
fun testDeleteToSearchResultWithLinewiseOffset() {
val before = """
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -727,6 +727,15 @@ class SearchGroupTest : VimTestCase() {
assertPosition(2, 0)
}

@Test
fun `test incsearch highlights with count and operator count`() {
configureByText("lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum lorem 6 ipsum lorem 7 ipsum")
enterCommand("set hlsearch incsearch")
typeText("2d", "3/ipsum") // No enter
assertSearchHighlights("ipsum",
"lorem 1 «ipsum» lorem 2 «ipsum» lorem 3 «ipsum» lorem 4 «ipsum» lorem 5 «ipsum» lorem 6 ‷ipsum‴ lorem 7 «ipsum»")
}

@Test
fun `test backwards search with count`() {
configureByText(
Expand Down Expand Up @@ -900,6 +909,15 @@ class SearchGroupTest : VimTestCase() {
assertPosition(8, 0)
}

@Test
fun `test backwards incsearch highlights with count and operator count`() {
configureByText("lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum lorem 6 ipsum lorem 7 ipsu${c}m")
enterCommand("set hlsearch incsearch")
typeText("2d", "3?ipsum") // No enter
assertSearchHighlights("ipsum",
"lorem 1 «ipsum» lorem 2 ‷ipsum‴ lorem 3 «ipsum» lorem 4 «ipsum» lorem 5 «ipsum» lorem 6 «ipsum» lorem 7 «ipsum»")
}

// |i_CTRL-K|
@Test
fun `test search digraph`() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1180,7 +1180,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
if (operatorArguments.count1 > 1) {
count0--
} else if (motion.count > 1) {
motion.count = motion.count - 1
motion.rawCount = motion.count - 1
} else {
motion.flags = EnumSet.noneOf(CommandFlags::class.java)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,8 @@ public data class Command(
action.process(this)
}

var count: Int
val count: Int
get() = rawCount.coerceAtLeast(1)
set(value) {
rawCount = value
}

var argument: Argument? = null
var register: Char? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,47 @@ import javax.swing.KeyStroke

public class CommandBuilder(
private var currentCommandPartNode: CommandPartNode<LazyVimCommand>,
initialUncommittedCount: Int = 0,
initialUncommittedRawCount: Int = 0,
) : Cloneable {
private var commandParts = ArrayDeque<Command>()
private var keyList = mutableListOf<KeyStroke>()

public var commandState: CurrentCommandState = CurrentCommandState.NEW_COMMAND
public var count: Int = initialUncommittedCount

/**
* The current uncommitted count for the currently in-progress command part
*
* TODO: Investigate usages. This value cannot be trusted
* TODO: Rename to uncommittedRawCount
*
* This value is not coerced, and can be 0.
*
* There are very few reasons for using this value. It is incomplete (the user could type another digit), and there
* can be other committed command parts, such as operator and multiple register selections, each of which will can a
* count (e.g., `2"a3"b4"c5d6` waiting for a motion). The count is only final after [buildCommand], and then only via
* [Command.count] or [Command.rawCount].
*
* The [aggregatedUncommittedCount] property can be used to get the current total count across all command parts,
* although this value is also not guaranteed to be final.
*/
public var count: Int = initialUncommittedRawCount
private set

/**
* The current aggregated, but uncommitted count for all command parts in the command builder, coerced to 1
*
* This value multiplies together the count for command parts currently committed, such as operator and multiple
* register selections, as well as the current uncommitted count for the next command part. E.g., `2"a3"b4"c5d6` will
* multiply each count together to get what would be the final count. All counts are coerced to at least 1 before
* multiplying, which means the result will also be at least 1.
*
* Note that there are very few uses for this value. The final value should be retrieved from [Command.count] or
* [Command.rawCount] after a call to [buildCommand]. This value is expected to be used for `'incsearch'`
* highlighting.
*/
public val aggregatedUncommittedCount: Int
get() = (commandParts.map { it.count }.reduceOrNull { acc, i -> acc * i } ?: 1) * count.coerceAtLeast(1)

public val keys: Iterable<KeyStroke> get() = keyList
public val register: Char?
get() = commandParts.lastOrNull()?.register
Expand Down Expand Up @@ -169,8 +202,8 @@ public class CommandBuilder(
var command: Command = commandParts.removeFirst()
while (commandParts.size > 0) {
val next = commandParts.removeFirst()
next.count = if (command.rawCount == 0 && next.rawCount == 0) 0 else command.count * next.count
command.count = 0
next.rawCount = if (command.rawCount == 0 && next.rawCount == 0) 0 else command.count * next.count
command.rawCount = 0
if (command.type == Command.Type.SELECT_REGISTER) {
next.register = command.register
command.register = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ public data class KeyHandlerState(
// We do not reset the uncommitted count in the editor command builder. The Ex actions ignore it, preferring the
// range in the text command. The search actions use it, and it will be combined with an operator count as expected.
// E.g., `2d3/foo` will delete up to the 6th occurrence of `foo`
// TODO: 2d3/foo doesn't handle incsearch properly!
commandLineCommandBuilder = CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.CMD_LINE), editorCommandBuilder.count)
}

Expand Down

0 comments on commit fa18d23

Please sign in to comment.