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

Ensure builder resets to a root command trie node

Also refactors command nodes a bit for better debug/trace output
This commit is contained in:
Matt Ellis 2024-08-28 16:46:13 +01:00 committed by Alex Pláte
parent 0936e0761f
commit def9ca479b
8 changed files with 87 additions and 50 deletions
src/main/java/com/maddyhome/idea/vim/extension/nerdtree
tests/property-tests/src/test/kotlin/org/jetbrains/plugins/ideavim/propertybased
vim-engine/src/main/kotlin/com/maddyhome/idea/vim

View File

@ -555,12 +555,13 @@ private fun registerCommand(default: String, action: NerdAction) {
} }
private val actionsRoot: RootNode<NerdAction> = RootNode() private val actionsRoot: RootNode<NerdAction> = RootNode("NERDTree")
private var currentNode: CommandPartNode<NerdAction> = actionsRoot private var currentNode: CommandPartNode<NerdAction> = actionsRoot
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> { private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
return if (node is CommandPartNode<NerdAction>) { return if (node is CommandPartNode<NerdAction>) {
val res = node.keys.toMutableSet() val res = node.children.keys.toMutableSet()
res += node.values.map { collectShortcuts(it) }.flatten() res += node.children.values.map { collectShortcuts(it) }.flatten()
res res
} else { } else {
emptySet() emptySet()

View File

@ -95,7 +95,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie() val currentNode = KeyHandler.getInstance().keyHandlerState.commandBuilder.getCurrentTrie()
// Note: esc is always an option // Note: esc is always an option
val possibleKeys = (currentNode.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) } val possibleKeys = (currentNode.children.keys.toList() + esc).sortedBy { injector.parser.toKeyNotation(it) }
println("Keys: ${possibleKeys.joinToString(", ")}") println("Keys: ${possibleKeys.joinToString(", ")}")
val keyGenerator = Generator.integers(0, possibleKeys.lastIndex) val keyGenerator = Generator.integers(0, possibleKeys.lastIndex)
.suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList } .suchThat { injector.parser.toKeyNotation(possibleKeys[it]) !in stinkyKeysList }

View File

@ -22,9 +22,9 @@ import com.maddyhome.idea.vim.diagnostic.VimLogger
import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.trace
import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.impl.state.toMappingMode import com.maddyhome.idea.vim.impl.state.toMappingMode
import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyConsumer
import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.KeyStack
import com.maddyhome.idea.vim.key.RootNode
import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer
import com.maddyhome.idea.vim.key.consumers.CommandConsumer import com.maddyhome.idea.vim.key.consumers.CommandConsumer
import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer
@ -282,7 +282,7 @@ class KeyHandler {
keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode())) keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode()))
} }
private fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> { private fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand> {
return injector.keyGroup.getKeyRoot(mappingMode) return injector.keyGroup.getKeyRoot(mappingMode)
} }

View File

@ -10,17 +10,17 @@ package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.action.change.LazyVimCommand import com.maddyhome.idea.vim.action.change.LazyVimCommand
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.KeyMapping import com.maddyhome.idea.vim.key.KeyMapping
import com.maddyhome.idea.vim.key.KeyMappingLayer import com.maddyhome.idea.vim.key.KeyMappingLayer
import com.maddyhome.idea.vim.key.MappingInfo import com.maddyhome.idea.vim.key.MappingInfo
import com.maddyhome.idea.vim.key.MappingOwner import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.key.RootNode
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import javax.swing.KeyStroke import javax.swing.KeyStroke
interface VimKeyGroup { interface VimKeyGroup {
fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand>
fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer
fun getActions(editor: VimEditor, keyStroke: KeyStroke): List<NativeAction> fun getActions(editor: VimEditor, keyStroke: KeyStroke): List<NativeAction>
fun getKeymapConflicts(keyStroke: KeyStroke): List<NativeAction> fun getKeymapConflicts(keyStroke: KeyStroke): List<NativeAction>

View File

@ -12,7 +12,6 @@ import com.maddyhome.idea.vim.action.change.LazyVimCommand
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.key.KeyMapping import com.maddyhome.idea.vim.key.KeyMapping
import com.maddyhome.idea.vim.key.KeyMappingLayer import com.maddyhome.idea.vim.key.KeyMappingLayer
import com.maddyhome.idea.vim.key.MappingInfo import com.maddyhome.idea.vim.key.MappingInfo
@ -30,7 +29,7 @@ abstract class VimKeyGroupBase : VimKeyGroup {
@JvmField @JvmField
val myShortcutConflicts: MutableMap<KeyStroke, ShortcutOwnerInfo> = LinkedHashMap() val myShortcutConflicts: MutableMap<KeyStroke, ShortcutOwnerInfo> = LinkedHashMap()
val requiredShortcutKeys: MutableSet<RequiredShortcut> = HashSet(300) val requiredShortcutKeys: MutableSet<RequiredShortcut> = HashSet(300)
val keyRoots: MutableMap<MappingMode, CommandPartNode<LazyVimCommand>> = EnumMap(MappingMode::class.java) val keyRoots: MutableMap<MappingMode, RootNode<LazyVimCommand>> = EnumMap(MappingMode::class.java)
val keyMappings: MutableMap<MappingMode, KeyMapping> = EnumMap(MappingMode::class.java) val keyMappings: MutableMap<MappingMode, KeyMapping> = EnumMap(MappingMode::class.java)
override fun removeKeyMapping(modes: Set<MappingMode>, keys: List<KeyStroke>) { override fun removeKeyMapping(modes: Set<MappingMode>, keys: List<KeyStroke>) {
@ -63,7 +62,7 @@ abstract class VimKeyGroupBase : VimKeyGroup {
* @param mappingMode The mapping mode * @param mappingMode The mapping mode
* @return The key mapping tree root * @return The key mapping tree root
*/ */
override fun getKeyRoot(mappingMode: MappingMode): CommandPartNode<LazyVimCommand> = keyRoots.getOrPut(mappingMode) { RootNode() } override fun getKeyRoot(mappingMode: MappingMode): RootNode<LazyVimCommand> = keyRoots.getOrPut(mappingMode) { RootNode(mappingMode.name.get(0).lowercase()) }
override fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer = getKeyMapping(mode) override fun getKeyMappingLayer(mode: MappingMode): KeyMappingLayer = getKeyMapping(mode)

View File

@ -32,14 +32,8 @@ class CommandBuilder private constructor(
private val keyList: MutableList<KeyStroke>, private val keyList: MutableList<KeyStroke>,
) : Cloneable { ) : Cloneable {
constructor( constructor(rootNode: RootNode<LazyVimCommand>, initialUncommittedRawCount: Int = 0)
currentCommandPartNode: CommandPartNode<LazyVimCommand>, : this(rootNode, mutableListOf(initialUncommittedRawCount), mutableListOf())
initialUncommittedRawCount: Int = 0
) : this(
currentCommandPartNode,
mutableListOf(initialUncommittedRawCount),
mutableListOf(),
)
private var selectedRegister: Char? = null private var selectedRegister: Char? = null
private var action: EditorActionHandlerBase? = null private var action: EditorActionHandlerBase? = null
@ -263,13 +257,13 @@ class CommandBuilder private constructor(
val node = currentCommandPartNode[key] val node = currentCommandPartNode[key]
when (node) { when (node) {
is CommandNode -> { is CommandNode -> {
logger.trace { "Found full command node - $node ($key)" } logger.trace { "Found full command node ($key) - ${node.debugString}" }
addKey(key) addKey(key)
processor(node.actionHolder.instance) processor(node.actionHolder.instance)
return true return true
} }
is CommandPartNode -> { is CommandPartNode -> {
logger.trace { "Found command part node - $node ($key)" } logger.trace { "Found command part node ($key) - ${node.debugString}" }
currentCommandPartNode = node currentCommandPartNode = node
addKey(key) addKey(key)
return true return true
@ -290,7 +284,7 @@ class CommandBuilder private constructor(
* @see DuplicableOperatorAction * @see DuplicableOperatorAction
*/ */
fun convertDuplicateOperatorKeyStrokeToMotion(key: KeyStroke): KeyStroke { fun convertDuplicateOperatorKeyStrokeToMotion(key: KeyStroke): KeyStroke {
logger.trace("convertDuplicateOperatorKeyStrokeToMotion is executed. key = $key") logger.trace { "convertDuplicateOperatorKeyStrokeToMotion is executed. key = $key" }
// Simple check to ensure that we're in OP_PENDING. If we don't have an action, we don't have an operator. If we // Simple check to ensure that we're in OP_PENDING. If we don't have an action, we don't have an operator. If we
// have an argument, we can't be in OP_PENDING // have an argument, we can't be in OP_PENDING
@ -319,13 +313,13 @@ class CommandBuilder private constructor(
fun buildCommand(): Command { fun buildCommand(): Command {
val rawCount = calculateCount0Snapshot() val rawCount = calculateCount0Snapshot()
val command = Command(selectedRegister, rawCount, action!!, argument, action!!.type, action?.flags ?: noneOfEnum()) val command = Command(selectedRegister, rawCount, action!!, argument, action!!.type, action?.flags ?: noneOfEnum())
resetAll(currentCommandPartNode) resetAll(currentCommandPartNode.root as RootNode<LazyVimCommand>)
return command return command
} }
fun resetAll(commandPartNode: CommandPartNode<LazyVimCommand>) { fun resetAll(rootNode: RootNode<LazyVimCommand>) {
logger.trace("resetAll is executed") logger.trace("resetAll is executed")
resetInProgressCommandPart(commandPartNode) currentCommandPartNode = rootNode
commandState = CurrentCommandState.NEW_COMMAND commandState = CurrentCommandState.NEW_COMMAND
counts.clear() counts.clear()
counts.add(0) counts.add(0)
@ -337,12 +331,16 @@ class CommandBuilder private constructor(
fallbackArgumentType = null fallbackArgumentType = null
} }
// TODO: Why do we need this to be public? /**
// Who needs to reset the current command part node without resetting the whole command builder? * Change the command trie root node used to find commands for the current mode
fun resetInProgressCommandPart(commandPartNode: CommandPartNode<LazyVimCommand>) { *
logger.trace("resetInProgressCommandPart is executed") * Typically, we reset the command trie root node after a command is executed, using the root node of the current
counts[counts.size - 1] = 0 * mode - this is handled by [resetAll]. This function allows us to change the root node without executing a command
currentCommandPartNode = commandPartNode * or fully resetting the command builder, such as when switching to Op-pending while entering an operator+motion.
*/
fun resetCommandTrieRootNode(rootNode: RootNode<LazyVimCommand>) {
logger.trace("resetCommandTrieRootNode is executed")
currentCommandPartNode = rootNode
} }
@TestOnly @TestOnly

View File

@ -40,17 +40,36 @@ import javax.swing.KeyStroke
* and the user should complete the sequence, it's [CommandPartNode] * and the user should complete the sequence, it's [CommandPartNode]
*/ */
@Suppress("GrazieInspection") @Suppress("GrazieInspection")
interface Node<T> interface Node<T> {
val debugString: String
val parent: Node<T>?
val root: Node<T>
get() = parent?.root ?: this
}
/** Represents a complete command */ /** Represents a complete command */
data class CommandNode<T>(val actionHolder: T) : Node<T> { data class CommandNode<T>(override val parent: Node<T>, val actionHolder: T, private val name: String) : Node<T> {
override fun toString(): String { override val debugString: String
return "COMMAND NODE (${ actionHolder.toString() })" get() = toString()
}
override fun toString() = "COMMAND NODE ($name - ${actionHolder.toString()})"
} }
/** Represents a part of the command */ /** Represents a part of the command */
open class CommandPartNode<T> : Node<T>, HashMap<KeyStroke, Node<T>>() { open class CommandPartNode<T>(
override val parent: Node<T>?,
internal val name: String,
internal val depth: Int) : Node<T> {
val children = mutableMapOf<KeyStroke, Node<T>>()
operator fun set(stroke: KeyStroke, node: Node<T>) {
children[stroke] = node
}
operator fun get(stroke: KeyStroke): Node<T>? = children[stroke]
override fun equals(other: Any?): Boolean { override fun equals(other: Any?): Boolean {
if (this === other) return true if (this === other) return true
if (javaClass != other?.javaClass) return false if (javaClass != other?.javaClass) return false
@ -58,21 +77,32 @@ open class CommandPartNode<T> : Node<T>, HashMap<KeyStroke, Node<T>>() {
return true return true
} }
override fun hashCode(): Int { override fun hashCode() = super.hashCode()
return super.hashCode()
}
override fun toString(): String { override fun toString() = "COMMAND PART NODE ($name - ${children.size} children)"
return """
COMMAND PART NODE( override val debugString
${entries.joinToString(separator = "\n") { " " + injector.parser.toKeyNotation(it.key) + " - " + it.value }} get() = buildString {
) append("COMMAND PART NODE(")
""".trimIndent() appendLine(name)
} children.entries.forEach {
repeat(depth + 1) { append(" ") }
append(injector.parser.toKeyNotation(it.key))
append(" - ")
appendLine(it.value.debugString)
}
repeat(depth) { append(" ") }
append(")")
}
} }
/** Represents a root node for the mode */ /** Represents a root node for the mode */
class RootNode<T> : CommandPartNode<T>() class RootNode<T>(name: String) : CommandPartNode<T>(null, name, 0) {
override val debugString: String
get() = "ROOT NODE ($name)\n" + super.debugString
override fun toString() = "ROOT NODE ($name - ${children.size} children)"
}
fun <T> Node<T>.addLeafs(keyStrokes: List<KeyStroke>, actionHolder: T) { fun <T> Node<T>.addLeafs(keyStrokes: List<KeyStroke>, actionHolder: T) {
var node: Node<T> = this var node: Node<T> = this
@ -90,7 +120,16 @@ private fun <T> addNode(base: CommandPartNode<T>, actionHolder: T, key: KeyStrok
val existing = base[key] val existing = base[key]
if (existing != null) return existing if (existing != null) return existing
val newNode: Node<T> = if (isLastInSequence) CommandNode(actionHolder) else CommandPartNode() val childName = injector.parser.toKeyNotation(key)
val name = when (base) {
is RootNode -> base.name + "_" + childName
else -> base.name + childName
}
val newNode: Node<T> = if (isLastInSequence) {
CommandNode(base, actionHolder, name)
} else {
CommandPartNode(base, name, base.depth + 1)
}
base[key] = newNode base[key] = newNode
return newNode return newNode
} }

View File

@ -68,7 +68,7 @@ data class KeyHandlerState(
fun partialReset(mode: Mode) { fun partialReset(mode: Mode) {
logger.trace("entered partialReset. mode: $mode") logger.trace("entered partialReset. mode: $mode")
mappingState.resetMappingSequence() mappingState.resetMappingSequence()
commandBuilder.resetInProgressCommandPart(injector.keyGroup.getKeyRoot(mode.toMappingMode())) commandBuilder.resetCommandTrieRootNode(injector.keyGroup.getKeyRoot(mode.toMappingMode()))
} }
fun reset(mode: Mode) { fun reset(mode: Mode) {