1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-06-17 06:40:00 +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.text.CharSequenceReader
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.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
@ -90,19 +91,22 @@ public class ProcessGroup : VimProcessGroupBase() {
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
// is open. So I'll put focus back in the editor and process the key.
val panel = ExEntryPanel.getInstance()
if (panel.isActive) {
processResultBuilder.addExecutionStep { _, _, _ ->
requestFocus(panel.entry)
panel.handleKey(stroke)
}
return true
} else {
editor.mode = NORMAL()
getInstance().reset(editor)
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
lambdaEditor.mode = NORMAL()
getInstance().reset(lambdaEditor)
}
return false
}
}

View File

@ -86,6 +86,19 @@ public class KeyHandler {
mappingCompleted: Boolean,
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 {
"""
------- Key Handler -------
@ -95,59 +108,69 @@ public class KeyHandler {
}
val maxMapDepth = injector.globalOptions().maxmapdepth
if (handleKeyRecursionCount >= maxMapDepth) {
injector.messages.showStatusBarMessage(editor, injector.messages.message("E223"))
injector.messages.indicateError()
processBuilder.addExecutionStep { _, lambdaEditor, _ ->
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()
val editorState = editor.vimStateMachine
val commandBuilder = newState.commandBuilder
val commandBuilder = processBuilder.state.commandBuilder
// If this is a "regular" character keystroke, get the character
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.
var shouldRecord = handleKeyRecursionCount == 0 && injector.registerGroup.isRecording
var isProcessed = false
handleKeyRecursionCount++
try {
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.")
if (isCommandCountKey(chKey, newState, editorState)) {
if (isCommandCountKey(chKey, processBuilder.state, editorState)) {
commandBuilder.addCountCharacter(key)
} else if (isDeleteCommandCountKey(key, newState, editorState.mode)) {
isProcessed = true
} else if (isDeleteCommandCountKey(key, processBuilder.state, editorState.mode)) {
commandBuilder.deleteCountCharacter()
isProcessed = true
} 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)) {
handleCharArgument(key, chKey, newState, editor)
handleCharArgument(key, chKey, processBuilder.state, editor)
isProcessed = true
} else if (editorState.isRegisterPending) {
LOG.trace("Pending mode.")
commandBuilder.addKey(key)
handleSelectRegister(editorState, chKey, newState)
} else if (!handleDigraph(editor, key, newState, context)) {
handleSelectRegister(editorState, chKey, processBuilder.state)
isProcessed = true
} else if (!handleDigraph(editor, key, processBuilder)) {
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,
// 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")
if (node is CommandNode<LazyVimCommand>) {
LOG.trace("Node is a command node")
handleCommandNode(editor, context, key, node, newState, editorState)
handleCommandNode(key, node, processBuilder)
commandBuilder.addKey(key)
isProcessed = true
} else if (node is CommandPartNode<LazyVimCommand>) {
LOG.trace("Node is a command part node")
commandBuilder.setCurrentCommandPartNode(node)
commandBuilder.addKey(key)
} else if (isSelectRegister(key, newState, editorState)) {
isProcessed = true
} else if (isSelectRegister(key, processBuilder.state, editorState)) {
LOG.trace("Select register")
editorState.isRegisterPending = true
commandBuilder.addKey(key)
isProcessed = true
} else {
// node == null
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 (editorState.mode == Mode.INSERT || editorState.mode == Mode.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) {
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) {
LOG.trace("Process cmd line")
shouldRecord = injector.processGroup.processExKey(editor, key) && shouldRecord
shouldRecord = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord
isProcessed = true
} else {
LOG.trace("Set command state to 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 {
handleKeyRecursionCount--
processBuilder.onFinish = { handleKeyRecursionCount-- }
}
finishedCommandPreparation(editor, context, editorState, key, shouldRecord, newState)
updateState(newState)
return processBuilder.build()
}
internal fun finishedCommandPreparation(
editor: VimEditor,
context: ExecutionContext,
editorState: VimStateMachine,
key: KeyStroke?,
shouldRecord: Boolean,
keyState: KeyHandlerState,
) {
// Do we have a fully entered command at this point? If so, let's execute it.
val editorState = editor.vimStateMachine
val commandBuilder = keyState.commandBuilder
if (commandBuilder.isReady) {
LOG.trace("Ready command builder. Execute command.")
@ -211,6 +252,21 @@ public class KeyHandler {
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]
*/
@ -357,18 +413,16 @@ public class KeyHandler {
private fun handleDigraph(
editor: VimEditor,
key: KeyStroke,
keyState: KeyHandlerState,
context: ExecutionContext,
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
): Boolean {
LOG.debug("Handling digraph")
// 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
// 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.
val keyState = keyProcessResultBuilder.state
val commandBuilder = keyState.commandBuilder
val digraphSequence = keyState.digraphSequence
if (commandBuilder.expectedArgumentType == Argument.Type.DIGRAPH) {
LOG.trace("Expected argument is digraph")
if (digraphSequence.isDigraphStart(key)) {
digraphSequence.startDigraphSequence()
commandBuilder.addKey(key)
@ -381,34 +435,54 @@ public class KeyHandler {
}
}
val res = digraphSequence.processKey(key, editor)
if (injector.exEntryPanel.isActive()) {
when (res.result) {
DigraphResult.RES_HANDLED -> setPromptCharacterEx(if (commandBuilder.isPuttingLiteral()) '^' else key.keyChar)
DigraphResult.RES_DONE, DigraphResult.RES_BAD -> if (key.keyCode == KeyEvent.VK_C && key.modifiers and InputEvent.CTRL_DOWN_MASK != 0) {
DigraphResult.RES_HANDLED -> {
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
} else {
keyProcessResultBuilder.addExecutionStep { _, _, _ ->
injector.exEntryPanel.clearCurrentAction()
}
}
}
when (res.result) {
DigraphResult.RES_HANDLED -> {
commandBuilder.addKey(key)
return true
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ ->
if (lambdaKeyState.commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
lambdaKeyState.commandBuilder.fallbackToCharacterArgument()
}
DigraphResult.RES_DONE -> {
if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
commandBuilder.fallbackToCharacterArgument()
}
val stroke = res.stroke ?: return false
commandBuilder.addKey(key)
handleKey(editor, stroke, context, keyState)
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditorState, lambdaContext ->
lambdaKeyState.commandBuilder.addKey(key)
handleKey(lambdaEditorState, stroke, lambdaContext, lambdaKeyState)
}
return true
}
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.
if (commandBuilder.expectedArgumentType != null) {
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
if (lambdaKeyState.commandBuilder.expectedArgumentType != null) {
setBadCommand(lambdaEditor, lambdaKeyState)
}
}
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.
if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
commandBuilder.fallbackToCharacterArgument()
handleKey(editor, key, context, keyState)
keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
handleKey(lambdaEditor, key, lambdaContext, lambdaKeyState)
}
return true
}
return false
@ -469,34 +545,38 @@ public class KeyHandler {
}
private fun handleCommandNode(
editor: VimEditor,
context: ExecutionContext,
key: KeyStroke,
node: CommandNode<LazyVimCommand>,
keyState: KeyHandlerState,
editorState: VimStateMachine,
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
) {
LOG.trace("Handle command node")
// The user entered a valid command. Create the command and add it to the stack.
val action = node.actionHolder.instance
val keyState = processBuilder.state
val commandBuilder = keyState.commandBuilder
val expectedArgumentType = commandBuilder.expectedArgumentType
commandBuilder.pushCommandPart(action)
if (!checkArgumentCompatibility(expectedArgumentType, action)) {
LOG.trace("Return from command node handling")
commandBuilder.commandState = CurrentCommandState.BAD_COMMAND
processBuilder.addExecutionStep { lamdaKeyState, lambdaEditor, _ ->
setBadCommand(lambdaEditor, lamdaKeyState)
}
return
}
if (action.argumentType == null || stopMacroRecord(node)) {
LOG.trace("Set command state to READY")
commandBuilder.commandState = CurrentCommandState.READY
} else {
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
LOG.trace("Set waiting for the argument")
val argumentType = action.argumentType
startWaitingForArgument(editor, context, key.keyChar, action, argumentType!!, keyState, editorState)
partialReset(keyState, editorState.mode)
val editorState = lambdaEditor.vimStateMachine
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
if (expectedArgumentType === Argument.Type.EX_STRING && action.flags.contains(CommandFlags.FLAG_COMPLETE_EX)) {
/* The only action that implements FLAG_COMPLETE_EX is ProcessExEntryAction.
@ -520,7 +600,8 @@ public class KeyHandler {
val text = injector.processGroup.endSearchCommand()
commandBuilder.popCommandPart() // Pop ProcessExEntryAction
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
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
@ -65,9 +66,9 @@ public interface VimChangeGroup {
operatorArguments: OperatorArguments,
): 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

View File

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

View File

@ -7,6 +7,7 @@
*/
package com.maddyhome.idea.vim.api
import com.maddyhome.idea.vim.KeyProcessResult
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.state.mode.Mode
import javax.swing.KeyStroke
@ -18,7 +19,7 @@ public interface VimProcessGroup {
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char)
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 startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean

View File

@ -8,6 +8,7 @@
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.VimEditor
import com.maddyhome.idea.vim.api.VimProcessGroupBase
@ -36,7 +37,7 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
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")
}

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.command
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.VimEditor
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.impl.state.toMappingMode
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.VimStateMachine
import javax.swing.KeyStroke
public object MappingProcessor {
@ -28,13 +29,16 @@ public object MappingProcessor {
private val log = vimLogger<MappingProcessor>()
internal fun handleKeyMapping(
editor: VimEditor,
key: KeyStroke,
keyState: KeyHandlerState,
context: ExecutionContext,
editor: VimEditor,
allowKeyMappings: Boolean,
mappingCompleted: Boolean,
keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder,
): Boolean {
if (!allowKeyMappings) return false
log.debug("Start processing key mappings.")
val keyState = keyProcessResultBuilder.state
val commandState = editor.vimStateMachine
val mappingState = keyState.mappingState
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
// be processed as normal.
val mappingProcessed =
handleUnfinishedMappingSequence(editor, keyState, mappingState, mapping, mappingCompleted) ||
handleCompleteMappingSequence(editor, keyState, context, mappingState, mapping, key) ||
handleAbandonedMappingSequence(editor, keyState, mappingState, context)
handleUnfinishedMappingSequence(keyProcessResultBuilder, mapping, mappingCompleted) ||
handleCompleteMappingSequence(keyProcessResultBuilder, mapping, key) ||
handleAbandonedMappingSequence(keyProcessResultBuilder)
log.debug { "Finish mapping processing. Return $mappingProcessed" }
return mappingProcessed
@ -76,9 +80,7 @@ public object MappingProcessor {
}
private fun handleUnfinishedMappingSequence(
editor: VimEditor,
keyState: KeyHandlerState,
mappingState: MappingState,
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
mapping: KeyMappingLayer,
mappingCompleted: Boolean,
): Boolean {
@ -93,7 +95,7 @@ public object MappingProcessor {
// 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
// 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.")
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
// user has typed "dw" wait for the timeout, and then replay "d" and "w" without any mapping (which will of course
// delete a word)
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ -> processUnfinishedMappingSequence(lambdaEditor, lambdaKeyState) }
return true
}
private fun processUnfinishedMappingSequence(editor: VimEditor, keyState: KeyHandlerState) {
if (injector.options(editor).timeout) {
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.
@ -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 this "unexpected behaviour" exists, and it would be better not to relay on mutable state with delays.
// https://youtrack.jetbrains.com/issue/VIM-2392
val mappingState = keyState.mappingState
mappingState.startMappingTimer {
injector.application.invokeLater(
{
@ -127,7 +135,8 @@ public object MappingProcessor {
// of waiting for `abc` mapping.
val lastKeyInSequence = index == unhandledKeys.lastIndex
KeyHandler.getInstance().handleKey(
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(
editor,
keyStroke,
injector.executionContextManager.onEditor(editor),
@ -142,19 +151,16 @@ public object MappingProcessor {
}
}
log.trace("Unfinished mapping processing finished")
return true
}
private fun handleCompleteMappingSequence(
editor: VimEditor,
keyState: KeyHandlerState,
context: ExecutionContext,
mappingState: MappingState,
processBuilder: KeyProcessResult.KeyProcessResultBuilder,
mapping: KeyMappingLayer,
key: KeyStroke,
): Boolean {
log.trace("Processing complete mapping 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)
var mappingInfo = currentMappingInfo
if (mappingInfo == null) {
@ -180,6 +186,19 @@ public object MappingProcessor {
log.trace("Cannot find any mapping info for the sequence. 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()
val currentContext = context.updateEditor(editor)
log.trace("Executing mapping info")
@ -216,20 +235,14 @@ public object MappingProcessor {
KeyHandler.getInstance().handleKey(editor, key, currentContext, allowKeyMappings = true, false, keyState)
}
log.trace("Success processing of mapping")
return true
}
private fun handleAbandonedMappingSequence(
editor: VimEditor,
keyState: KeyHandlerState,
mappingState: MappingState,
context: ExecutionContext,
): Boolean {
private fun handleAbandonedMappingSequence(processBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
log.debug("Processing abandoned mapping sequence")
// 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
// 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 (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.
// 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)
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)) {
log.trace("This is a plugin mapping, process it")
KeyHandler.getInstance().handleKey(
@ -264,7 +283,6 @@ public object MappingProcessor {
}
}
log.trace("Return true from abandoned keys processing.")
return true
}
// The <Plug>mappings are not executed if they fail to map to something.