1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-07-27 19:59:03 +02:00

Use KeyProcessResultBuilder

It will help us to build the KeyProcessResult that we need for asynchronous key processing
This commit is contained in:
filipp 2024-02-23 13:02:24 +02:00
parent 275c5d28e1
commit 19fa00837c
7 changed files with 233 additions and 124 deletions
src/main/java/com/maddyhome/idea/vim/group
vim-engine/src/main/kotlin/com/maddyhome/idea/vim

View File

@ -20,6 +20,7 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.util.execution.ParametersListUtil import com.intellij.util.execution.ParametersListUtil
import com.intellij.util.text.CharSequenceReader import com.intellij.util.text.CharSequenceReader
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -90,19 +91,22 @@ public class ProcessGroup : VimProcessGroupBase() {
panel.activate(editor.ij, context.ij, ":", initText, 1) panel.activate(editor.ij, context.ij, ":", initText, 1)
} }
public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean { public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
// This will only get called if somehow the key focus ended up in the editor while the ex entry window // This will only get called if somehow the key focus ended up in the editor while the ex entry window
// is open. So I'll put focus back in the editor and process the key. // is open. So I'll put focus back in the editor and process the key.
val panel = ExEntryPanel.getInstance() val panel = ExEntryPanel.getInstance()
if (panel.isActive) { if (panel.isActive) {
processResultBuilder.addExecutionStep { _, _, _ ->
requestFocus(panel.entry) requestFocus(panel.entry)
panel.handleKey(stroke) panel.handleKey(stroke)
}
return true return true
} else { } else {
editor.mode = NORMAL() processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
getInstance().reset(editor) lambdaEditor.mode = NORMAL()
getInstance().reset(lambdaEditor)
}
return false return false
} }
} }

View File

@ -86,6 +86,19 @@ public class KeyHandler {
mappingCompleted: Boolean, mappingCompleted: Boolean,
keyState: KeyHandlerState, keyState: KeyHandlerState,
) { ) {
val result = processKey(key, editor, allowKeyMappings, mappingCompleted, KeyProcessResult.SynchronousKeyProcessBuilder(keyState))
if (result is KeyProcessResult.Executable) {
result.execute(editor, context)
}
}
private fun processKey(
key: KeyStroke,
editor: VimEditor,
allowKeyMappings: Boolean,
mappingCompleted: Boolean,
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
): KeyProcessResult {
LOG.trace { LOG.trace {
""" """
------- Key Handler ------- ------- Key Handler -------
@ -95,59 +108,69 @@ public class KeyHandler {
} }
val maxMapDepth = injector.globalOptions().maxmapdepth val maxMapDepth = injector.globalOptions().maxmapdepth
if (handleKeyRecursionCount >= maxMapDepth) { if (handleKeyRecursionCount >= maxMapDepth) {
injector.messages.showStatusBarMessage(editor, injector.messages.message("E223")) processBuilder.addExecutionStep { _, lambdaEditor, _ ->
injector.messages.indicateError()
LOG.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth") LOG.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth")
return injector.messages.showStatusBarMessage(lambdaEditor, injector.messages.message("E223"))
injector.messages.indicateError()
}
return processBuilder.build()
} }
val newState = keyState ?: this.keyHandlerState
injector.messages.clearError() injector.messages.clearError()
val editorState = editor.vimStateMachine val editorState = editor.vimStateMachine
val commandBuilder = newState.commandBuilder val commandBuilder = processBuilder.state.commandBuilder
// If this is a "regular" character keystroke, get the character // If this is a "regular" character keystroke, get the character
val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar
// We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything.
var shouldRecord = handleKeyRecursionCount == 0 && injector.registerGroup.isRecording var shouldRecord = handleKeyRecursionCount == 0 && injector.registerGroup.isRecording
var isProcessed = false
handleKeyRecursionCount++ handleKeyRecursionCount++
try { try {
LOG.trace("Start key processing...") LOG.trace("Start key processing...")
if (!allowKeyMappings || !MappingProcessor.handleKeyMapping(editor, key, newState, context, mappingCompleted)) { if (!MappingProcessor.handleKeyMapping(key, editor, allowKeyMappings, mappingCompleted, processBuilder)) {
LOG.trace("Mappings processed, continue processing key.") LOG.trace("Mappings processed, continue processing key.")
if (isCommandCountKey(chKey, newState, editorState)) { if (isCommandCountKey(chKey, processBuilder.state, editorState)) {
commandBuilder.addCountCharacter(key) commandBuilder.addCountCharacter(key)
} else if (isDeleteCommandCountKey(key, newState, editorState.mode)) { isProcessed = true
} else if (isDeleteCommandCountKey(key, processBuilder.state, editorState.mode)) {
commandBuilder.deleteCountCharacter() commandBuilder.deleteCountCharacter()
isProcessed = true
} else if (isEditorReset(key, editorState)) { } else if (isEditorReset(key, editorState)) {
handleEditorReset(editor, key, newState, context) processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> handleEditorReset(lambdaEditor, key, lambdaKeyState, lambdaContext) }
isProcessed = true
} else if (isExpectingCharArgument(commandBuilder)) { } else if (isExpectingCharArgument(commandBuilder)) {
handleCharArgument(key, chKey, newState, editor) handleCharArgument(key, chKey, processBuilder.state, editor)
isProcessed = true
} else if (editorState.isRegisterPending) { } else if (editorState.isRegisterPending) {
LOG.trace("Pending mode.") LOG.trace("Pending mode.")
commandBuilder.addKey(key) commandBuilder.addKey(key)
handleSelectRegister(editorState, chKey, newState) handleSelectRegister(editorState, chKey, processBuilder.state)
} else if (!handleDigraph(editor, key, newState, context)) { isProcessed = true
} else if (!handleDigraph(editor, key, processBuilder)) {
LOG.debug("Digraph is NOT processed") LOG.debug("Digraph is NOT processed")
// Ask the key/action tree if this is an appropriate key at this point in the command and if so, // Ask the key/action tree if this is an appropriate key at this point in the command and if so,
// return the node matching this keystroke // return the node matching this keystroke
val node: Node<LazyVimCommand>? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, newState) val node: Node<LazyVimCommand>? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, processBuilder.state)
LOG.trace("Get the node for the current mode") LOG.trace("Get the node for the current mode")
if (node is CommandNode<LazyVimCommand>) { if (node is CommandNode<LazyVimCommand>) {
LOG.trace("Node is a command node") LOG.trace("Node is a command node")
handleCommandNode(editor, context, key, node, newState, editorState) handleCommandNode(key, node, processBuilder)
commandBuilder.addKey(key) commandBuilder.addKey(key)
isProcessed = true
} else if (node is CommandPartNode<LazyVimCommand>) { } else if (node is CommandPartNode<LazyVimCommand>) {
LOG.trace("Node is a command part node") LOG.trace("Node is a command part node")
commandBuilder.setCurrentCommandPartNode(node) commandBuilder.setCurrentCommandPartNode(node)
commandBuilder.addKey(key) commandBuilder.addKey(key)
} else if (isSelectRegister(key, newState, editorState)) { isProcessed = true
} else if (isSelectRegister(key, processBuilder.state, editorState)) {
LOG.trace("Select register") LOG.trace("Select register")
editorState.isRegisterPending = true editorState.isRegisterPending = true
commandBuilder.addKey(key) commandBuilder.addKey(key)
isProcessed = true
} else { } else {
// node == null // node == null
LOG.trace("We are not able to find a node for this key") LOG.trace("We are not able to find a node for this key")
@ -155,37 +178,55 @@ public class KeyHandler {
// If we are in insert/replace mode send this key in for processing // If we are in insert/replace mode send this key in for processing
if (editorState.mode == Mode.INSERT || editorState.mode == Mode.REPLACE) { if (editorState.mode == Mode.INSERT || editorState.mode == Mode.REPLACE) {
LOG.trace("Process insert or replace") LOG.trace("Process insert or replace")
shouldRecord = injector.changeGroup.processKey(editor, context, key) && shouldRecord shouldRecord = injector.changeGroup.processKey(editor, key, processBuilder) && shouldRecord
isProcessed = true
} else if (editorState.mode is Mode.SELECT) { } else if (editorState.mode is Mode.SELECT) {
LOG.trace("Process select") LOG.trace("Process select")
shouldRecord = injector.changeGroup.processKeyInSelectMode(editor, context, key) && shouldRecord shouldRecord = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord
isProcessed = true
} else if (editor.mode is Mode.CMD_LINE) { } else if (editor.mode is Mode.CMD_LINE) {
LOG.trace("Process cmd line") LOG.trace("Process cmd line")
shouldRecord = injector.processGroup.processExKey(editor, key) && shouldRecord shouldRecord = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord
isProcessed = true
} else { } else {
LOG.trace("Set command state to bad_command") LOG.trace("Set command state to bad_command")
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
} }
partialReset(newState, editorState.mode) partialReset(processBuilder.state, editorState.mode)
} }
} else {
isProcessed = true
}
} else {
isProcessed = true
}
if (isProcessed) {
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
finishedCommandPreparation(lambdaEditor, lambdaContext, key, shouldRecord, lambdaKeyState)
}
} else {
// Key wasn't processed by any of the consumers, so we reset our key state
// and tell IDE that the key is Unknown (handle key for us)
onUnknownKey(editor, processBuilder.state)
return KeyProcessResult.Unknown.apply {
handleKeyRecursionCount-- // because onFinish will now be executed for unknown
} }
} }
} finally { } finally {
handleKeyRecursionCount-- processBuilder.onFinish = { handleKeyRecursionCount-- }
} }
finishedCommandPreparation(editor, context, editorState, key, shouldRecord, newState) return processBuilder.build()
updateState(newState)
} }
internal fun finishedCommandPreparation( internal fun finishedCommandPreparation(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext, context: ExecutionContext,
editorState: VimStateMachine,
key: KeyStroke?, key: KeyStroke?,
shouldRecord: Boolean, shouldRecord: Boolean,
keyState: KeyHandlerState, keyState: KeyHandlerState,
) { ) {
// Do we have a fully entered command at this point? If so, let's execute it. // Do we have a fully entered command at this point? If so, let's execute it.
val editorState = editor.vimStateMachine
val commandBuilder = keyState.commandBuilder val commandBuilder = keyState.commandBuilder
if (commandBuilder.isReady) { if (commandBuilder.isReady) {
LOG.trace("Ready command builder. Execute command.") LOG.trace("Ready command builder. Execute command.")
@ -211,6 +252,21 @@ public class KeyHandler {
LOG.trace("----------- Key Handler Finished -----------") LOG.trace("----------- Key Handler Finished -----------")
} }
private fun onUnknownKey(editor: VimEditor, keyState: KeyHandlerState) {
keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
LOG.trace("Command builder is set to BAD")
editor.resetOpPending()
editor.vimStateMachine.resetRegisterPending()
editor.isReplaceCharacter = false
reset(keyState, editor.mode)
}
public fun setBadCommand(editor: VimEditor, keyState: KeyHandlerState) {
onUnknownKey(editor, keyState)
injector.messages.indicateError()
}
/** /**
* See the description for [com.maddyhome.idea.vim.command.DuplicableOperatorAction] * See the description for [com.maddyhome.idea.vim.command.DuplicableOperatorAction]
*/ */
@ -357,18 +413,16 @@ public class KeyHandler {
private fun handleDigraph( private fun handleDigraph(
editor: VimEditor, editor: VimEditor,
key: KeyStroke, key: KeyStroke,
keyState: KeyHandlerState, keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
context: ExecutionContext,
): Boolean { ): Boolean {
LOG.debug("Handling digraph")
// Support starting a digraph/literal sequence if the operator accepts one as an argument, e.g. 'r' or 'f'. // Support starting a digraph/literal sequence if the operator accepts one as an argument, e.g. 'r' or 'f'.
// Normally, we start the sequence (in Insert or CmdLine mode) through a VimAction that can be mapped. Our // Normally, we start the sequence (in Insert or CmdLine mode) through a VimAction that can be mapped. Our
// VimActions don't work as arguments for operators, so we have to special case here. Helpfully, Vim appears to // VimActions don't work as arguments for operators, so we have to special case here. Helpfully, Vim appears to
// hardcode the shortcuts, and doesn't support mapping, so everything works nicely. // hardcode the shortcuts, and doesn't support mapping, so everything works nicely.
val keyState = keyProcessResultBuilder.state
val commandBuilder = keyState.commandBuilder val commandBuilder = keyState.commandBuilder
val digraphSequence = keyState.digraphSequence val digraphSequence = keyState.digraphSequence
if (commandBuilder.expectedArgumentType == Argument.Type.DIGRAPH) { if (commandBuilder.expectedArgumentType == Argument.Type.DIGRAPH) {
LOG.trace("Expected argument is digraph")
if (digraphSequence.isDigraphStart(key)) { if (digraphSequence.isDigraphStart(key)) {
digraphSequence.startDigraphSequence() digraphSequence.startDigraphSequence()
commandBuilder.addKey(key) commandBuilder.addKey(key)
@ -381,34 +435,54 @@ public class KeyHandler {
} }
} }
val res = digraphSequence.processKey(key, editor) val res = digraphSequence.processKey(key, editor)
if (injector.exEntryPanel.isActive()) {
when (res.result) { when (res.result) {
DigraphResult.RES_HANDLED -> setPromptCharacterEx(if (commandBuilder.isPuttingLiteral()) '^' else key.keyChar) DigraphResult.RES_HANDLED -> {
DigraphResult.RES_DONE, DigraphResult.RES_BAD -> if (key.keyCode == KeyEvent.VK_C && key.modifiers and InputEvent.CTRL_DOWN_MASK != 0) { keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ ->
if (injector.exEntryPanel.isActive()) {
setPromptCharacterEx(if (lambdaKeyState.commandBuilder.isPuttingLiteral()) '^' else key.keyChar)
}
lambdaKeyState.commandBuilder.addKey(key)
}
return true
}
DigraphResult.RES_DONE -> {
if (injector.exEntryPanel.isActive()) {
if (key.keyCode == KeyEvent.VK_C && key.modifiers and InputEvent.CTRL_DOWN_MASK != 0) {
return false return false
} else { } else {
keyProcessResultBuilder.addExecutionStep { _, _, _ ->
injector.exEntryPanel.clearCurrentAction() injector.exEntryPanel.clearCurrentAction()
} }
} }
} }
when (res.result) {
DigraphResult.RES_HANDLED -> { keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ ->
commandBuilder.addKey(key) if (lambdaKeyState.commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
return true lambdaKeyState.commandBuilder.fallbackToCharacterArgument()
} }
DigraphResult.RES_DONE -> {
if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
commandBuilder.fallbackToCharacterArgument()
} }
val stroke = res.stroke ?: return false val stroke = res.stroke ?: return false
commandBuilder.addKey(key) keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditorState, lambdaContext ->
handleKey(editor, stroke, context, keyState) lambdaKeyState.commandBuilder.addKey(key)
handleKey(lambdaEditorState, stroke, lambdaContext, lambdaKeyState)
}
return true return true
} }
DigraphResult.RES_BAD -> { DigraphResult.RES_BAD -> {
if (injector.exEntryPanel.isActive()) {
if (key.keyCode == KeyEvent.VK_C && key.modifiers and InputEvent.CTRL_DOWN_MASK != 0) {
return false
} else {
keyProcessResultBuilder.addExecutionStep { _, _, _ ->
injector.exEntryPanel.clearCurrentAction()
}
}
}
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ ->
// BAD is an error. We were expecting a valid character, and we didn't get it. // BAD is an error. We were expecting a valid character, and we didn't get it.
if (commandBuilder.expectedArgumentType != null) { if (lambdaKeyState.commandBuilder.expectedArgumentType != null) {
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND setBadCommand(lambdaEditor, lambdaKeyState)
}
} }
return true return true
} }
@ -417,7 +491,9 @@ public class KeyHandler {
// state. E.g. waiting for {char} <BS> {char}. Let the key handler have a go at it. // state. E.g. waiting for {char} <BS> {char}. Let the key handler have a go at it.
if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) { if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
commandBuilder.fallbackToCharacterArgument() commandBuilder.fallbackToCharacterArgument()
handleKey(editor, key, context, keyState) keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
handleKey(lambdaEditor, key, lambdaContext, lambdaKeyState)
}
return true return true
} }
return false return false
@ -469,34 +545,38 @@ public class KeyHandler {
} }
private fun handleCommandNode( private fun handleCommandNode(
editor: VimEditor,
context: ExecutionContext,
key: KeyStroke, key: KeyStroke,
node: CommandNode<LazyVimCommand>, node: CommandNode<LazyVimCommand>,
keyState: KeyHandlerState, processBuilder: KeyProcessResult.KeyProcessResultBuilder,
editorState: VimStateMachine,
) { ) {
LOG.trace("Handle command node") LOG.trace("Handle command node")
// The user entered a valid command. Create the command and add it to the stack. // The user entered a valid command. Create the command and add it to the stack.
val action = node.actionHolder.instance val action = node.actionHolder.instance
val keyState = processBuilder.state
val commandBuilder = keyState.commandBuilder val commandBuilder = keyState.commandBuilder
val expectedArgumentType = commandBuilder.expectedArgumentType val expectedArgumentType = commandBuilder.expectedArgumentType
commandBuilder.pushCommandPart(action) commandBuilder.pushCommandPart(action)
if (!checkArgumentCompatibility(expectedArgumentType, action)) { if (!checkArgumentCompatibility(expectedArgumentType, action)) {
LOG.trace("Return from command node handling") LOG.trace("Return from command node handling")
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND processBuilder.addExecutionStep { lamdaKeyState, lambdaEditor, _ ->
setBadCommand(lambdaEditor, lamdaKeyState)
}
return return
} }
if (action.argumentType == null || stopMacroRecord(node)) { if (action.argumentType == null || stopMacroRecord(node)) {
LOG.trace("Set command state to READY") LOG.trace("Set command state to READY")
commandBuilder.commandState = CurrentCommandState.READY commandBuilder.commandState = CurrentCommandState.READY
} else { } else {
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
LOG.trace("Set waiting for the argument") LOG.trace("Set waiting for the argument")
val argumentType = action.argumentType val argumentType = action.argumentType
startWaitingForArgument(editor, context, key.keyChar, action, argumentType!!, keyState, editorState) val editorState = lambdaEditor.vimStateMachine
partialReset(keyState, editorState.mode) startWaitingForArgument(lambdaEditor, lambdaContext, key.keyChar, action, argumentType!!, lambdaKeyState, editorState)
lambdaKeyState.partialReset(editorState.mode)
}
} }
processBuilder.addExecutionStep { _, lambdaEditor, _ ->
// TODO In the name of God, get rid of EX_STRING, FLAG_COMPLETE_EX and all the related staff // TODO In the name of God, get rid of EX_STRING, FLAG_COMPLETE_EX and all the related staff
if (expectedArgumentType === Argument.Type.EX_STRING && action.flags.contains(CommandFlags.FLAG_COMPLETE_EX)) { if (expectedArgumentType === Argument.Type.EX_STRING && action.flags.contains(CommandFlags.FLAG_COMPLETE_EX)) {
/* The only action that implements FLAG_COMPLETE_EX is ProcessExEntryAction. /* The only action that implements FLAG_COMPLETE_EX is ProcessExEntryAction.
@ -520,7 +600,8 @@ public class KeyHandler {
val text = injector.processGroup.endSearchCommand() val text = injector.processGroup.endSearchCommand()
commandBuilder.popCommandPart() // Pop ProcessExEntryAction commandBuilder.popCommandPart() // Pop ProcessExEntryAction
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
editor.mode = editorState.mode.returnTo() lambdaEditor.mode = lambdaEditor.mode.returnTo()
}
} }
} }

View File

@ -7,6 +7,7 @@
*/ */
package com.maddyhome.idea.vim.api package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
@ -65,9 +66,9 @@ public interface VimChangeGroup {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
): Boolean ): Boolean
public fun processKey(editor: VimEditor, context: ExecutionContext, key: KeyStroke): Boolean public fun processKey(editor: VimEditor, key: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
public fun processKeyInSelectMode(editor: VimEditor, context: ExecutionContext, key: KeyStroke): Boolean public fun processKeyInSelectMode(editor: VimEditor, key: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
public fun deleteLine(editor: VimEditor, caret: VimCaret, count: Int, operatorArguments: OperatorArguments): Boolean public fun deleteLine(editor: VimEditor, caret: VimCaret, count: Int, operatorArguments: OperatorArguments): Boolean

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.api package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
@ -715,18 +716,18 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
*/ */
override fun processKey( override fun processKey(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext,
key: KeyStroke, key: KeyStroke,
processResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
): Boolean { ): Boolean {
logger.debug { "processKey($key)" } logger.debug { "processKey($key)" }
if (key.keyChar != KeyEvent.CHAR_UNDEFINED) { if (key.keyChar != KeyEvent.CHAR_UNDEFINED) {
type(editor, context, key.keyChar) processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, key.keyChar) }
return true return true
} }
// Shift-space // Shift-space
if (key.keyCode == 32 && key.modifiers and KeyEvent.SHIFT_DOWN_MASK != 0) { if (key.keyCode == 32 && key.modifiers and KeyEvent.SHIFT_DOWN_MASK != 0) {
type(editor, context, ' ') processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext -> type(lambdaEditor, lambdaContext, ' ') }
return true return true
} }
return false return false
@ -734,16 +735,18 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
override fun processKeyInSelectMode( override fun processKeyInSelectMode(
editor: VimEditor, editor: VimEditor,
context: ExecutionContext,
key: KeyStroke, key: KeyStroke,
processResultBuilder: KeyProcessResult.KeyProcessResultBuilder
): Boolean { ): Boolean {
var res: Boolean var res: Boolean
SelectionVimListenerSuppressor.lock().use { SelectionVimListenerSuppressor.lock().use {
res = processKey(editor, context, key) res = processKey(editor, key, processResultBuilder)
editor.exitSelectModeNative(false) processResultBuilder.addExecutionStep { _, lambdaEditor, lambdaContext ->
KeyHandler.getInstance().reset(editor) lambdaEditor.exitSelectModeNative(false)
if (isPrintableChar(key.keyChar) || activeTemplateWithLeftRightMotion(editor, key)) { KeyHandler.getInstance().reset(lambdaEditor)
injector.changeGroup.insertBeforeCursor(editor, context) if (isPrintableChar(key.keyChar) || activeTemplateWithLeftRightMotion(lambdaEditor, key)) {
injector.changeGroup.insertBeforeCursor(lambdaEditor, lambdaContext)
}
} }
} }
return res return res

View File

@ -7,6 +7,7 @@
*/ */
package com.maddyhome.idea.vim.api package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import javax.swing.KeyStroke import javax.swing.KeyStroke
@ -18,7 +19,7 @@ public interface VimProcessGroup {
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) public fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char)
public fun endSearchCommand(): String public fun endSearchCommand(): String
public fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean public fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean
public fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) public fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
public fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) public fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.api.stubs package com.maddyhome.idea.vim.api.stubs
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimProcessGroupBase import com.maddyhome.idea.vim.api.VimProcessGroupBase
@ -36,7 +37,7 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean { override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.command package com.maddyhome.idea.vim.command
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
@ -19,8 +20,8 @@ import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.impl.state.toMappingMode import com.maddyhome.idea.vim.impl.state.toMappingMode
import com.maddyhome.idea.vim.key.KeyMappingLayer import com.maddyhome.idea.vim.key.KeyMappingLayer
import com.maddyhome.idea.vim.key.MappingInfoLayer
import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.KeyHandlerState
import com.maddyhome.idea.vim.state.VimStateMachine
import javax.swing.KeyStroke import javax.swing.KeyStroke
public object MappingProcessor { public object MappingProcessor {
@ -28,13 +29,16 @@ public object MappingProcessor {
private val log = vimLogger<MappingProcessor>() private val log = vimLogger<MappingProcessor>()
internal fun handleKeyMapping( internal fun handleKeyMapping(
editor: VimEditor,
key: KeyStroke, key: KeyStroke,
keyState: KeyHandlerState, editor: VimEditor,
context: ExecutionContext, allowKeyMappings: Boolean,
mappingCompleted: Boolean, mappingCompleted: Boolean,
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
): Boolean { ): Boolean {
if (!allowKeyMappings) return false
log.debug("Start processing key mappings.") log.debug("Start processing key mappings.")
val keyState = keyProcessResultBuilder.state
val commandState = editor.vimStateMachine val commandState = editor.vimStateMachine
val mappingState = keyState.mappingState val mappingState = keyState.mappingState
val commandBuilder = keyState.commandBuilder val commandBuilder = keyState.commandBuilder
@ -58,9 +62,9 @@ public object MappingProcessor {
// Returns true if any of these methods handle the key. False means that the key is unrelated to mapping and should // Returns true if any of these methods handle the key. False means that the key is unrelated to mapping and should
// be processed as normal. // be processed as normal.
val mappingProcessed = val mappingProcessed =
handleUnfinishedMappingSequence(editor, keyState, mappingState, mapping, mappingCompleted) || handleUnfinishedMappingSequence(keyProcessResultBuilder, mapping, mappingCompleted) ||
handleCompleteMappingSequence(editor, keyState, context, mappingState, mapping, key) || handleCompleteMappingSequence(keyProcessResultBuilder, mapping, key) ||
handleAbandonedMappingSequence(editor, keyState, mappingState, context) handleAbandonedMappingSequence(keyProcessResultBuilder)
log.debug { "Finish mapping processing. Return $mappingProcessed" } log.debug { "Finish mapping processing. Return $mappingProcessed" }
return mappingProcessed return mappingProcessed
@ -76,9 +80,7 @@ public object MappingProcessor {
} }
private fun handleUnfinishedMappingSequence( private fun handleUnfinishedMappingSequence(
editor: VimEditor, processBuilder: KeyProcessResult.KeyProcessResultBuilder,
keyState: KeyHandlerState,
mappingState: MappingState,
mapping: KeyMappingLayer, mapping: KeyMappingLayer,
mappingCompleted: Boolean, mappingCompleted: Boolean,
): Boolean { ): Boolean {
@ -93,7 +95,7 @@ public object MappingProcessor {
// mapping is a prefix, it will get evaluated when the next character is entered. // mapping is a prefix, it will get evaluated when the next character is entered.
// Note that currentlyUnhandledKeySequence is the same as the state after commandState.getMappingKeys().add(key). It // Note that currentlyUnhandledKeySequence is the same as the state after commandState.getMappingKeys().add(key). It
// would be nice to tidy ths up // would be nice to tidy ths up
if (!mapping.isPrefix(mappingState.keys)) { if (!mapping.isPrefix(processBuilder.state.mappingState.keys)) {
log.debug("There are no mappings that start with the current sequence. Returning false.") log.debug("There are no mappings that start with the current sequence. Returning false.")
return false return false
} }
@ -102,6 +104,11 @@ public object MappingProcessor {
// Every time a key is pressed and handled, the timer is stopped. E.g. if there is a mapping for "dweri", and the // Every time a key is pressed and handled, the timer is stopped. E.g. if there is a mapping for "dweri", and the
// user has typed "dw" wait for the timeout, and then replay "d" and "w" without any mapping (which will of course // user has typed "dw" wait for the timeout, and then replay "d" and "w" without any mapping (which will of course
// delete a word) // delete a word)
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ -> processUnfinishedMappingSequence(lambdaEditor, lambdaKeyState) }
return true
}
private fun processUnfinishedMappingSequence(editor: VimEditor, keyState: KeyHandlerState) {
if (injector.options(editor).timeout) { if (injector.options(editor).timeout) {
log.trace("timeout is set. schedule a mapping timer") log.trace("timeout is set. schedule a mapping timer")
// XXX There is a strange issue that reports that mapping state is empty at the moment of the function call. // XXX There is a strange issue that reports that mapping state is empty at the moment of the function call.
@ -109,6 +116,7 @@ public object MappingProcessor {
// but before invoke later is handled. This is a rare case, so I'll just add a check to isPluginMapping. // but before invoke later is handled. This is a rare case, so I'll just add a check to isPluginMapping.
// But this "unexpected behaviour" exists, and it would be better not to relay on mutable state with delays. // But this "unexpected behaviour" exists, and it would be better not to relay on mutable state with delays.
// https://youtrack.jetbrains.com/issue/VIM-2392 // https://youtrack.jetbrains.com/issue/VIM-2392
val mappingState = keyState.mappingState
mappingState.startMappingTimer { mappingState.startMappingTimer {
injector.application.invokeLater( injector.application.invokeLater(
{ {
@ -127,7 +135,8 @@ public object MappingProcessor {
// of waiting for `abc` mapping. // of waiting for `abc` mapping.
val lastKeyInSequence = index == unhandledKeys.lastIndex val lastKeyInSequence = index == unhandledKeys.lastIndex
KeyHandler.getInstance().handleKey( val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(
editor, editor,
keyStroke, keyStroke,
injector.executionContextManager.onEditor(editor), injector.executionContextManager.onEditor(editor),
@ -142,19 +151,16 @@ public object MappingProcessor {
} }
} }
log.trace("Unfinished mapping processing finished") log.trace("Unfinished mapping processing finished")
return true
} }
private fun handleCompleteMappingSequence( private fun handleCompleteMappingSequence(
editor: VimEditor, processBuilder: KeyProcessResult.KeyProcessResultBuilder,
keyState: KeyHandlerState,
context: ExecutionContext,
mappingState: MappingState,
mapping: KeyMappingLayer, mapping: KeyMappingLayer,
key: KeyStroke, key: KeyStroke,
): Boolean { ): Boolean {
log.trace("Processing complete mapping sequence...") log.trace("Processing complete mapping sequence...")
// The current sequence isn't a prefix, check to see if it's a completed sequence. // The current sequence isn't a prefix, check to see if it's a completed sequence.
val mappingState = processBuilder.state.mappingState
val currentMappingInfo = mapping.getLayer(mappingState.keys) val currentMappingInfo = mapping.getLayer(mappingState.keys)
var mappingInfo = currentMappingInfo var mappingInfo = currentMappingInfo
if (mappingInfo == null) { if (mappingInfo == null) {
@ -180,6 +186,19 @@ public object MappingProcessor {
log.trace("Cannot find any mapping info for the sequence. Return false.") log.trace("Cannot find any mapping info for the sequence. Return false.")
return false return false
} }
processBuilder.addExecutionStep { b, c, d -> processCompleteMappingSequence(key, b, c, d, mappingInfo, currentMappingInfo) }
return true
}
private fun processCompleteMappingSequence(
key: KeyStroke,
keyState: KeyHandlerState,
editor: VimEditor,
context: ExecutionContext,
mappingInfo: MappingInfoLayer,
currentMappingInfo: MappingInfoLayer?,
) {
val mappingState = keyState.mappingState
mappingState.resetMappingSequence() mappingState.resetMappingSequence()
val currentContext = context.updateEditor(editor) val currentContext = context.updateEditor(editor)
log.trace("Executing mapping info") log.trace("Executing mapping info")
@ -216,20 +235,14 @@ public object MappingProcessor {
KeyHandler.getInstance().handleKey(editor, key, currentContext, allowKeyMappings = true, false, keyState) KeyHandler.getInstance().handleKey(editor, key, currentContext, allowKeyMappings = true, false, keyState)
} }
log.trace("Success processing of mapping") log.trace("Success processing of mapping")
return true
} }
private fun handleAbandonedMappingSequence( private fun handleAbandonedMappingSequence(processBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
editor: VimEditor,
keyState: KeyHandlerState,
mappingState: MappingState,
context: ExecutionContext,
): Boolean {
log.debug("Processing abandoned mapping sequence") log.debug("Processing abandoned mapping sequence")
// The user has terminated a mapping sequence with an unexpected key // The user has terminated a mapping sequence with an unexpected key
// E.g. if there is a mapping for "hello" and user enters command "help" the processing of "h", "e" and "l" will be // E.g. if there is a mapping for "hello" and user enters command "help" the processing of "h", "e" and "l" will be
// prevented by this handler. Make sure the currently unhandled keys are processed as normal. // prevented by this handler. Make sure the currently unhandled keys are processed as normal.
val unhandledKeyStrokes = mappingState.detachKeys() val unhandledKeyStrokes = processBuilder.state.mappingState.detachKeys()
// If there is only the current key to handle, do nothing // If there is only the current key to handle, do nothing
if (unhandledKeyStrokes.size == 1) { if (unhandledKeyStrokes.size == 1) {
@ -244,6 +257,12 @@ public object MappingProcessor {
// If user enters `dI`, the first `d` will be caught be this handler because it's a prefix for `ds` command. // If user enters `dI`, the first `d` will be caught be this handler because it's a prefix for `ds` command.
// After the user enters `I`, the caught `d` should be processed without mapping, and the rest of keys // After the user enters `I`, the caught `d` should be processed without mapping, and the rest of keys
// should be processed with mappings (to make I work) // should be processed with mappings (to make I work)
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
processAbondonedMappingSequence(unhandledKeyStrokes, lambdaEditor, lambdaContext, lambdaKeyState) }
return true
}
private fun processAbondonedMappingSequence(unhandledKeyStrokes: List<KeyStroke>, editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) {
if (isPluginMapping(unhandledKeyStrokes)) { if (isPluginMapping(unhandledKeyStrokes)) {
log.trace("This is a plugin mapping, process it") log.trace("This is a plugin mapping, process it")
KeyHandler.getInstance().handleKey( KeyHandler.getInstance().handleKey(
@ -264,7 +283,6 @@ public object MappingProcessor {
} }
} }
log.trace("Return true from abandoned keys processing.") log.trace("Return true from abandoned keys processing.")
return true
} }
// The <Plug>mappings are not executed if they fail to map to something. // The <Plug>mappings are not executed if they fail to map to something.