mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-05-28 08:34:02 +02:00
Use KeyProcessResultBuilder
It will help us to build the KeyProcessResult that we need for asynchronous key processing
This commit is contained in:
parent
275c5d28e1
commit
19fa00837c
src/main/java/com/maddyhome/idea/vim/group
vim-engine/src/main/kotlin/com/maddyhome/idea/vim
@ -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) {
|
||||
requestFocus(panel.entry)
|
||||
panel.handleKey(stroke)
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -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()
|
||||
LOG.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth")
|
||||
return
|
||||
processBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||
LOG.warn("Key handling, maximum recursion of the key received. maxdepth=$maxMapDepth")
|
||||
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) {
|
||||
return false
|
||||
} else {
|
||||
injector.exEntryPanel.clearCurrentAction()
|
||||
}
|
||||
}
|
||||
}
|
||||
when (res.result) {
|
||||
DigraphResult.RES_HANDLED -> {
|
||||
commandBuilder.addKey(key)
|
||||
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 (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
|
||||
commandBuilder.fallbackToCharacterArgument()
|
||||
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, _, _ ->
|
||||
if (lambdaKeyState.commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) {
|
||||
lambdaKeyState.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 -> {
|
||||
// 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 (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 (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,58 +545,63 @@ 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 {
|
||||
LOG.trace("Set waiting for the argument")
|
||||
val argumentType = action.argumentType
|
||||
startWaitingForArgument(editor, context, key.keyChar, action, argumentType!!, keyState, editorState)
|
||||
partialReset(keyState, editorState.mode)
|
||||
processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext ->
|
||||
LOG.trace("Set waiting for the argument")
|
||||
val argumentType = action.argumentType
|
||||
val editorState = lambdaEditor.vimStateMachine
|
||||
startWaitingForArgument(lambdaEditor, lambdaContext, key.keyChar, action, argumentType!!, lambdaKeyState, editorState)
|
||||
lambdaKeyState.partialReset(editorState.mode)
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
* When pressing ':', ExEntryAction is chosen as the command. Since it expects no arguments, it is invoked and
|
||||
calls ProcessGroup#startExCommand, pushes CMD_LINE mode, and the action is popped. The ex handler will push
|
||||
the final <CR> through handleKey, which chooses ProcessExEntryAction. Because we're not expecting EX_STRING,
|
||||
this branch does NOT fire, and ProcessExEntryAction handles the ex cmd line entry.
|
||||
* When pressing '/' or '?', SearchEntry(Fwd|Rev)Action is chosen as the command. This expects an argument of
|
||||
EX_STRING, so startWaitingForArgument calls ProcessGroup#startSearchCommand. The ex handler pushes the final
|
||||
<CR> through handleKey, which chooses ProcessExEntryAction, and we hit this branch. We don't invoke
|
||||
ProcessExEntryAction, but pop it, set the search text as an argument on SearchEntry(Fwd|Rev)Action and invoke
|
||||
that instead.
|
||||
* When using '/' or '?' as part of a motion (e.g. "d/foo"), the above happens again, and all is good. Because
|
||||
the text has been applied as an argument on the last command, '.' will correctly repeat it.
|
||||
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.
|
||||
* When pressing ':', ExEntryAction is chosen as the command. Since it expects no arguments, it is invoked and
|
||||
calls ProcessGroup#startExCommand, pushes CMD_LINE mode, and the action is popped. The ex handler will push
|
||||
the final <CR> through handleKey, which chooses ProcessExEntryAction. Because we're not expecting EX_STRING,
|
||||
this branch does NOT fire, and ProcessExEntryAction handles the ex cmd line entry.
|
||||
* When pressing '/' or '?', SearchEntry(Fwd|Rev)Action is chosen as the command. This expects an argument of
|
||||
EX_STRING, so startWaitingForArgument calls ProcessGroup#startSearchCommand. The ex handler pushes the final
|
||||
<CR> through handleKey, which chooses ProcessExEntryAction, and we hit this branch. We don't invoke
|
||||
ProcessExEntryAction, but pop it, set the search text as an argument on SearchEntry(Fwd|Rev)Action and invoke
|
||||
that instead.
|
||||
* When using '/' or '?' as part of a motion (e.g. "d/foo"), the above happens again, and all is good. Because
|
||||
the text has been applied as an argument on the last command, '.' will correctly repeat it.
|
||||
|
||||
It's hard to see how to improve this. Removing EX_STRING means starting ex input has to happen in ExEntryAction
|
||||
and SearchEntry(Fwd|Rev)Action, and the ex command invoked in ProcessExEntryAction, but that breaks any initial
|
||||
operator, which would be invoked first (e.g. 'd' in "d/foo").
|
||||
*/
|
||||
LOG.trace("Processing ex_string")
|
||||
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()
|
||||
It's hard to see how to improve this. Removing EX_STRING means starting ex input has to happen in ExEntryAction
|
||||
and SearchEntry(Fwd|Rev)Action, and the ex command invoked in ProcessExEntryAction, but that breaks any initial
|
||||
operator, which would be invoked first (e.g. 'd' in "d/foo").
|
||||
*/
|
||||
LOG.trace("Processing ex_string")
|
||||
val text = injector.processGroup.endSearchCommand()
|
||||
commandBuilder.popCommandPart() // Pop ProcessExEntryAction
|
||||
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
|
||||
lambdaEditor.mode = lambdaEditor.mode.returnTo()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user