From 355cfe035dab4022d38cef0e6508d058520184fb Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 2 Feb 2024 11:16:39 +0200 Subject: [PATCH 01/44] Remove Editor from VimStateMachine Rationale: 1. A much more experienced developer, whom I highly respect, suggested to empty VimStateMachineImpl constructor in his TODO comment. 2. I aim for VimStateMachine to be a data class rather than being a container for both data and complex logic. 3. From an architectural perspective, it is more correct. Editors do have state (or they may possess a single global state if the corresponding option is set), but a state does not own an editor. --- .../maddyhome/idea/vim/group/ProcessGroup.kt | 10 +- .../vim/group/visual/IdeaSelectionControl.kt | 2 +- .../idea/vim/helper/ModeExtensions.kt | 12 +- .../idea/vim/listener/IdeaSpecifics.kt | 2 +- .../maddyhome/idea/vim/newapi/IjVimEditor.kt | 128 +++++++++++++++++ .../idea/vim/newapi/IjVimInjector.kt | 2 +- .../ui/widgets/macro/MacroWidgetFactory.kt | 5 +- .../options/helpers/IdeaRefactorModeHelper.kt | 2 +- .../ideavim/command/VimStateMachineTest.kt | 32 ++--- .../VimMultipleCursorsExtensionTest.kt | 6 +- .../propertybased/VimPropertyTestBase.kt | 1 - .../com/maddyhome/idea/vim/KeyHandler.kt | 28 ++-- .../motion/select/SelectToggleVisualMode.kt | 4 +- .../visual/VisualSelectPreviousAction.kt | 2 +- .../visual/VisualSwapSelectionsAction.kt | 2 +- .../idea/vim/api/VimChangeGroupBase.kt | 12 +- .../com/maddyhome/idea/vim/api/VimEditor.kt | 12 ++ .../idea/vim/api/VimVisualMotionGroupBase.kt | 10 +- .../idea/vim/common/MacroRecordingListener.kt | 4 +- .../idea/vim/common/VimListenersNotifier.kt | 8 +- .../maddyhome/idea/vim/helper/EngineHelper.kt | 4 +- .../idea/vim/helper/EngineModeExtensions.kt | 7 +- .../vim/impl/state/VimStateMachineImpl.kt | 133 +----------------- .../com/maddyhome/idea/vim/key/MappingInfo.kt | 2 +- .../idea/vim/register/VimRegisterGroup.kt | 2 + .../idea/vim/register/VimRegisterGroupBase.kt | 11 +- .../idea/vim/state/VimStateMachine.kt | 17 +-- 27 files changed, 229 insertions(+), 231 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt b/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt index 3c7e211dff..07e3788ca8 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt +++ b/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt @@ -85,7 +85,7 @@ public class ProcessGroup : VimProcessGroupBase() { modeBeforeCommandProcessing = currentMode val initText = getRange(editor, cmd) injector.markService.setVisualSelectionMarks(editor) - editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode) + editor.mode = Mode.CMD_LINE(currentMode) val panel = ExEntryPanel.getInstance() panel.activate(editor.ij, context.ij, ":", initText, 1) } @@ -101,7 +101,7 @@ public class ProcessGroup : VimProcessGroupBase() { return true } else { - getInstance(editor).mode = NORMAL() + editor.mode = NORMAL() getInstance().reset(editor) return false } @@ -112,7 +112,7 @@ public class ProcessGroup : VimProcessGroupBase() { panel.deactivate(true) var res = true try { - getInstance(editor).mode = NORMAL() + editor.mode = NORMAL() logger.debug("processing command") @@ -152,7 +152,7 @@ public class ProcessGroup : VimProcessGroupBase() { } public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) { - editor.vimStateMachine.mode = NORMAL() + editor.mode = NORMAL() getInstance().reset(editor) val panel = ExEntryPanel.getInstance() panel.deactivate(true, resetCaret) @@ -162,7 +162,7 @@ public class ProcessGroup : VimProcessGroupBase() { val initText = getRange(editor, cmd) + "!" val currentMode = editor.mode check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" } - editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode) + editor.mode = Mode.CMD_LINE(currentMode) val panel = ExEntryPanel.getInstance() panel.activate(editor.ij, context.ij, ":", initText, 1) } diff --git a/src/main/java/com/maddyhome/idea/vim/group/visual/IdeaSelectionControl.kt b/src/main/java/com/maddyhome/idea/vim/group/visual/IdeaSelectionControl.kt index 3b20287793..b378aa9ebf 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/visual/IdeaSelectionControl.kt +++ b/src/main/java/com/maddyhome/idea/vim/group/visual/IdeaSelectionControl.kt @@ -79,7 +79,7 @@ internal object IdeaSelectionControl { logger.debug("Some carets have selection. State before adjustment: ${editor.vim.mode}") - editor.vim.vimStateMachine.mode = Mode.NORMAL() + editor.vim.mode = Mode.NORMAL() activateMode(editor, chooseSelectionMode(editor, selectionSource, true)) } else { diff --git a/src/main/java/com/maddyhome/idea/vim/helper/ModeExtensions.kt b/src/main/java/com/maddyhome/idea/vim/helper/ModeExtensions.kt index 730111c7b0..34db3bcc3d 100644 --- a/src/main/java/com/maddyhome/idea/vim/helper/ModeExtensions.kt +++ b/src/main/java/com/maddyhome/idea/vim/helper/ModeExtensions.kt @@ -34,15 +34,15 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) { val returnTo = this.vim.vimStateMachine.mode.returnTo when (returnTo) { ReturnTo.INSERT -> { - this.vim.vimStateMachine.mode = Mode.INSERT + this.vim.mode = Mode.INSERT } ReturnTo.REPLACE -> { - this.vim.vimStateMachine.mode = Mode.REPLACE + this.vim.mode = Mode.REPLACE } null -> { - this.vim.vimStateMachine.mode = Mode.NORMAL() + this.vim.mode = Mode.NORMAL() } } SelectionVimListenerSuppressor.lock().use { @@ -67,15 +67,15 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) { val returnTo = this.vimStateMachine.mode.returnTo when (returnTo) { ReturnTo.INSERT -> { - this.vimStateMachine.mode = Mode.INSERT + this.mode = Mode.INSERT } ReturnTo.REPLACE -> { - this.vimStateMachine.mode = Mode.REPLACE + this.mode = Mode.REPLACE } null -> { - this.vimStateMachine.mode = Mode.NORMAL() + this.mode = Mode.NORMAL() } } SelectionVimListenerSuppressor.lock().use { diff --git a/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt b/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt index 2551a09d64..02d4b181be 100644 --- a/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt +++ b/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt @@ -124,7 +124,7 @@ internal object IdeaSpecifics { ) { editor?.let { val commandState = it.vim.vimStateMachine - commandState.mode = Mode.NORMAL() + it.vim.mode = Mode.NORMAL() VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim) KeyHandler.getInstance().reset(it.vim) } diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt index e3a9ee2d77..7020b92ce4 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt @@ -35,6 +35,8 @@ import com.maddyhome.idea.vim.api.VimScrollingModel import com.maddyhome.idea.vim.api.VimSelectionModel import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VirtualFile +import com.maddyhome.idea.vim.api.globalOptions +import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.common.EditorLine import com.maddyhome.idea.vim.common.IndentConfig @@ -55,9 +57,13 @@ import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode import com.maddyhome.idea.vim.helper.vimLastSelectionType +import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl import com.maddyhome.idea.vim.state.mode.Mode +import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.inBlockSelection +import com.maddyhome.idea.vim.state.mode.returnTo import org.jetbrains.annotations.ApiStatus import java.lang.System.identityHashCode @@ -129,6 +135,75 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { editor.document.replaceString(start, end, newString) } + override var mode: Mode + get() = vimStateMachine.mode + set(value) { + if (vimStateMachine.mode == value) return + + val oldValue = vimStateMachine.mode + (vimStateMachine as VimStateMachineImpl).mode = value + injector.listenersNotifier.notifyModeChanged(this, oldValue) + + // TODO maybe it would be better to utilize modeListeners for this purpose? + updateCaretsVisual() + doShowMode() + } + + override fun resetOpPending() { + if (this.mode is Mode.OP_PENDING) { + val returnTo = this.mode.returnTo + mode = when (returnTo) { + ReturnTo.INSERT -> Mode.INSERT + ReturnTo.REPLACE -> Mode.INSERT + null -> Mode.NORMAL() + } + } + } + + override var isReplaceCharacter: Boolean + get() = vimStateMachine.isReplaceCharacter + set(value) { + if (value != vimStateMachine.isReplaceCharacter) { + (vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value + + // TODO maybe it would be better to utilize listeners for this purpose? + updateCaretsVisual() + } + } + + override var isRecording: Boolean + get() = vimStateMachine.isRecording + set(value) { + (vimStateMachine as VimStateMachineImpl).isRecording = value + if (value) { + injector.listenersNotifier.notifyMacroRecordingStarted(this) + } else { + injector.listenersNotifier.notifyMacroRecordingFinished(this) + } + doShowMode() + } + + private fun updateCaretsVisual() { + if (injector.globalOptions().ideaglobalmode) { + injector.application.localEditors().forEach { editor -> + editor.updateCaretsVisualAttributes() + editor.updateCaretsVisualPosition() + } + } else { + editor.updateCaretsVisualAttributes() + editor.updateCaretsVisualPosition() + } + } + + override fun resetState() { + mode = Mode.NORMAL() + vimStateMachine.executingCommand = null + vimStateMachine.digraphSequence.reset() + vimStateMachine.commandBuilder.resetInProgressCommandPart( + injector.keyGroup.getKeyRoot(vimStateMachine.mappingState.mappingMode) + ) + } + // TODO: 30.12.2021 Is end offset inclusive? override fun getLineRange(line: EditorLine.Pointer): Pair { // TODO: 30.12.2021 getLineEndOffset returns the same value for "xyz" and "xyz\n" @@ -504,6 +579,59 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { // We can't use Object.toString() as this includes hashcode, which produces an error return "IjVimEditor[$editor@${identityHashCode(editor).toString(16)}]" } + + @Deprecated("It will be replaced by Vim Mode Widget") + override fun getStatusString(): String { + val modeState = this.mode + return buildString { + when (modeState) { + is Mode.NORMAL -> { + if (modeState.returnTo != null) append("-- (insert) --") + } + + Mode.INSERT -> append("-- INSERT --") + Mode.REPLACE -> append("-- REPLACE --") + is Mode.VISUAL -> { + val inInsert = if (modeState.returnTo != null) "(insert) " else "" + append("-- ${inInsert}VISUAL") + when (modeState.selectionType) { + SelectionType.LINE_WISE -> append(" LINE") + SelectionType.BLOCK_WISE -> append(" BLOCK") + else -> Unit + } + append(" --") + } + + is Mode.SELECT -> { + val inInsert = if (modeState.returnTo != null) "(insert) " else "" + append("-- ${inInsert}SELECT") + when (modeState.selectionType) { + SelectionType.LINE_WISE -> append(" LINE") + SelectionType.BLOCK_WISE -> append(" BLOCK") + else -> Unit + } + append(" --") + } + + else -> Unit + } + } + } + + @Deprecated("It will be replaced by Vim Mode Widget") + private fun doShowMode() { + val msg = StringBuilder() + if (injector.globalOptions().showmode) { + msg.append(getStatusString()) + } + if (vimStateMachine.isRecording) { + if (msg.isNotEmpty()) { + msg.append(" - ") + } + msg.append(injector.messages.message("show.mode.recording")) + } + injector.messages.showMode(this, msg.toString()) + } } public val Editor.vim: VimEditor diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt index ce6be131be..a05443934d 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimInjector.kt @@ -207,7 +207,7 @@ internal class IjVimInjector : VimInjectorBase() { override fun commandStateFor(editor: VimEditor): VimStateMachine { var res = editor.ij.vimStateMachine if (res == null) { - res = VimStateMachineImpl(editor) + res = VimStateMachineImpl() editor.ij.vimStateMachine = res } return res diff --git a/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt b/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt index c7ebc9e14b..95130479a0 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt @@ -30,12 +30,13 @@ internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget { private var content: String = "" private val macroRecordingListener = object : MacroRecordingListener { - override fun recordingStarted(editor: VimEditor, register: Char) { + override fun recordingStarted(editor: VimEditor) { + val register = injector.registerGroup.recordRegister content = "recording @$register" updateWidgetInStatusBar(ID, editor.ij.project) } - override fun recordingFinished(editor: VimEditor, register: Char) { + override fun recordingFinished(editor: VimEditor) { content = "" updateWidgetInStatusBar(ID, editor.ij.project) } diff --git a/src/main/java/com/maddyhome/idea/vim/vimscript/model/options/helpers/IdeaRefactorModeHelper.kt b/src/main/java/com/maddyhome/idea/vim/vimscript/model/options/helpers/IdeaRefactorModeHelper.kt index 28178bb577..65041ba8c8 100644 --- a/src/main/java/com/maddyhome/idea/vim/vimscript/model/options/helpers/IdeaRefactorModeHelper.kt +++ b/src/main/java/com/maddyhome/idea/vim/vimscript/model/options/helpers/IdeaRefactorModeHelper.kt @@ -57,7 +57,7 @@ internal object IdeaRefactorModeHelper { } is Action.SetMode -> { - editor.vim.vimStateMachine.mode = correction.newMode + editor.vim.mode = correction.newMode } } } diff --git a/src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt index bb07580388..d866479dfd 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt @@ -21,7 +21,7 @@ class VimStateMachineTest : VimTestCase() { @Test fun `test status string in normal`() { configureByText("123") - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("", statusString) } @@ -30,7 +30,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in insert`() { configureByText("123") typeText(injector.parser.parseKeys("i")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- INSERT --", statusString) } @@ -39,7 +39,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in replace`() { configureByText("123") typeText(injector.parser.parseKeys("R")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- REPLACE --", statusString) } @@ -48,7 +48,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in visual`() { configureByText("123") typeText(injector.parser.parseKeys("v")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- VISUAL --", statusString) } @@ -57,7 +57,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in visual line`() { configureByText("123") typeText(injector.parser.parseKeys("V")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- VISUAL LINE --", statusString) } @@ -66,7 +66,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in visual block`() { configureByText("123") typeText(injector.parser.parseKeys("")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- VISUAL BLOCK --", statusString) } @@ -75,7 +75,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in select`() { configureByText("123") typeText(injector.parser.parseKeys("gh")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- SELECT --", statusString) } @@ -84,7 +84,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in select line`() { configureByText("123") typeText(injector.parser.parseKeys("gH")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- SELECT LINE --", statusString) } @@ -93,7 +93,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in select block`() { configureByText("123") typeText(injector.parser.parseKeys("g")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- SELECT BLOCK --", statusString) } @@ -102,7 +102,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in one command`() { configureByText("123") typeText(injector.parser.parseKeys("i")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- (insert) --", statusString) } @@ -111,7 +111,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in one command visual`() { configureByText("123") typeText(injector.parser.parseKeys("iv")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- (insert) VISUAL --", statusString) } @@ -120,7 +120,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in one command visual block`() { configureByText("123") typeText(injector.parser.parseKeys("i")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- (insert) VISUAL BLOCK --", statusString) } @@ -129,7 +129,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in one command visual line`() { configureByText("123") typeText(injector.parser.parseKeys("iV")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- (insert) VISUAL LINE --", statusString) } @@ -138,7 +138,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in one command select`() { configureByText("123") typeText(injector.parser.parseKeys("igh")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- (insert) SELECT --", statusString) } @@ -147,7 +147,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in one command select block`() { configureByText("123") typeText(injector.parser.parseKeys("ig")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- (insert) SELECT BLOCK --", statusString) } @@ -156,7 +156,7 @@ class VimStateMachineTest : VimTestCase() { fun `test status string in one command select line`() { configureByText("123") typeText(injector.parser.parseKeys("igH")) - val statusString = fixture.editor.vim.vimStateMachine.getStatusString() + val statusString = fixture.editor.vim.getStatusString() kotlin.test.assertEquals("-- (insert) SELECT LINE --", statusString) } } diff --git a/src/test/java/org/jetbrains/plugins/ideavim/extension/multiplecursors/VimMultipleCursorsExtensionTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/extension/multiplecursors/VimMultipleCursorsExtensionTest.kt index 1c25bb010f..448451275c 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/extension/multiplecursors/VimMultipleCursorsExtensionTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/extension/multiplecursors/VimMultipleCursorsExtensionTest.kt @@ -190,7 +190,7 @@ class VimMultipleCursorsExtensionTest : VimTestCase() { |dfkjsg """.trimMargin() val editor = configureByText(before) - editor.vim.vimStateMachine.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE) + editor.vim.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE) typeText(injector.parser.parseKeys("")) @@ -273,7 +273,7 @@ class VimMultipleCursorsExtensionTest : VimTestCase() { |dfkjsg """.trimMargin() val editor = configureByText(before) - editor.vim.vimStateMachine.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE) + editor.vim.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE) typeText(injector.parser.parseKeys("")) assertMode(Mode.VISUAL(SelectionType.CHARACTER_WISE)) @@ -363,7 +363,7 @@ fun getCellType(${s}pos$se: VisualPosition): CellType { fun `test ignores regex in search pattern`() { val before = "test ${s}t.*st${c}$se toast tallest t.*st" val editor = configureByText(before) - editor.vim.vimStateMachine.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE) + editor.vim.mode = Mode.VISUAL(SelectionType.CHARACTER_WISE) typeText(injector.parser.parseKeys("")) val after = "test ${s}t.*st$se toast tallest ${s}t.*st$se" diff --git a/tests/property-tests/src/test/kotlin/org/jetbrains/plugins/ideavim/propertybased/VimPropertyTestBase.kt b/tests/property-tests/src/test/kotlin/org/jetbrains/plugins/ideavim/propertybased/VimPropertyTestBase.kt index 5de9c3f362..b5755150ee 100644 --- a/tests/property-tests/src/test/kotlin/org/jetbrains/plugins/ideavim/propertybased/VimPropertyTestBase.kt +++ b/tests/property-tests/src/test/kotlin/org/jetbrains/plugins/ideavim/propertybased/VimPropertyTestBase.kt @@ -32,7 +32,6 @@ abstract class VimPropertyTestBase : VimTestCase() { VimPlugin.getRegister().resetRegisters() editor.caretModel.runForEachCaret { it.moveToOffset(0) } - editor.vim.vimStateMachine.resetDigraph() VimPlugin.getSearch().resetState() VimPlugin.getChange().reset() } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index dab80e2cbc..857df958ba 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -116,7 +116,7 @@ public class KeyHandler { } else if (isEditorReset(key, editorState)) { handleEditorReset(editor, key, context, editorState) } else if (isExpectingCharArgument(commandBuilder)) { - handleCharArgument(key, chKey, editorState) + handleCharArgument(key, chKey, editorState, editor) } else if (editorState.isRegisterPending) { LOG.trace("Pending mode.") commandBuilder.addKey(key) @@ -183,9 +183,9 @@ public class KeyHandler { executeCommand(editor, context, editorState) } else if (commandBuilder.isBad) { LOG.trace("Command builder is set to BAD") - editorState.resetOpPending() + editor.resetOpPending() editorState.resetRegisterPending() - editorState.resetReplaceCharacter() + editor.isReplaceCharacter = false injector.messages.indicateError() reset(editor) } @@ -225,7 +225,7 @@ public class KeyHandler { ) { val commandBuilder = editorState.commandBuilder if (commandBuilder.isAwaitingCharOrDigraphArgument()) { - editorState.resetReplaceCharacter() + editor.isReplaceCharacter = false } if (commandBuilder.isAtDefaultState) { val register = injector.registerGroup @@ -312,7 +312,7 @@ public class KeyHandler { return expectingCharArgument } - private fun handleCharArgument(key: KeyStroke, chKey: Char, vimStateMachine: VimStateMachine) { + private fun handleCharArgument(key: KeyStroke, chKey: Char, vimStateMachine: VimStateMachine, editor: VimEditor) { var mutableChKey = chKey LOG.trace("Handling char argument") // We are expecting a character argument - is this a regular character the user typed? @@ -333,7 +333,7 @@ public class KeyHandler { // Oops - this isn't a valid character argument commandBuilder.commandState = CurrentCommandState.BAD_COMMAND } - vimStateMachine.resetReplaceCharacter() + editor.isReplaceCharacter = false } private fun handleDigraph( @@ -421,7 +421,7 @@ public class KeyHandler { ) // If we were in "operator pending" mode, reset back to normal mode. - editorState.resetOpPending() + editor.resetOpPending() // Save off the command we are about to execute editorState.executingCommand = command @@ -499,7 +499,7 @@ public class KeyHandler { val text = injector.processGroup.endSearchCommand() commandBuilder.popCommandPart() // Pop ProcessExEntryAction commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action - editorState.mode = editorState.mode.returnTo() + editor.mode = editorState.mode.returnTo() } } @@ -523,7 +523,7 @@ public class KeyHandler { if (editorState.isDotRepeatInProgress && argumentCaptured != null) { commandBuilder.completeCommandPart(argumentCaptured!!) } - editorState.mode = Mode.OP_PENDING(editorState.mode.returnTo) + editor.mode = Mode.OP_PENDING(editorState.mode.returnTo) } Argument.Type.DIGRAPH -> // Command actions represent the completion of a command. Showcmd relies on this - if the action represents a @@ -548,7 +548,7 @@ public class KeyHandler { commandBuilder.commandState = CurrentCommandState.NEW_COMMAND val currentMode = editorState.mode check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" } - editorState.mode = Mode.CMD_LINE(currentMode) + editor.mode = Mode.CMD_LINE(currentMode) } else -> Unit @@ -558,7 +558,7 @@ public class KeyHandler { // This was a typed solution // if (action is ChangeCharacterAction || action is ChangeVisualCharacterAction) if (action.id == "VimChangeCharacterAction" || action.id == "VimChangeVisualCharacterAction") { - editorState.isReplaceCharacter = true + editor.isReplaceCharacter = true } } @@ -604,7 +604,7 @@ public class KeyHandler { */ public fun fullReset(editor: VimEditor) { injector.messages.clearError() - VimStateMachine.getInstance(editor).reset() + editor.resetState() reset(editor) injector.registerGroupIfCreated?.resetRegister() editor.removeSelection() @@ -653,11 +653,11 @@ public class KeyHandler { if (myMode is Mode.NORMAL && returnTo != null && !cmd.flags.contains(CommandFlags.FLAG_EXPECT_MORE)) { when (returnTo) { ReturnTo.INSERT -> { - editorState.mode = Mode.INSERT + editor.mode = Mode.INSERT } ReturnTo.REPLACE -> { - editorState.mode = Mode.REPLACE + editor.mode = Mode.REPLACE } } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt index 1fa68a8e70..1c3bcf5cb3 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt @@ -45,7 +45,7 @@ public class SelectToggleVisualMode : VimActionHandler.SingleExecution() { val commandState = editor.vimStateMachine val myMode = commandState.mode if (myMode is com.maddyhome.idea.vim.state.mode.Mode.VISUAL) { - commandState.setSelectMode(myMode.selectionType) + editor.setSelectMode(myMode.selectionType) if (myMode.selectionType != SelectionType.LINE_WISE) { editor.nativeCarets().forEach { if (it.offset.point + injector.visualMotionGroup.selectionAdj == it.selectionEnd) { @@ -54,7 +54,7 @@ public class SelectToggleVisualMode : VimActionHandler.SingleExecution() { } } } else if (myMode is com.maddyhome.idea.vim.state.mode.Mode.SELECT) { - commandState.pushVisualMode(myMode.selectionType) + editor.pushVisualMode(myMode.selectionType) if (myMode.selectionType != SelectionType.LINE_WISE) { editor.nativeCarets().forEach { if (it.offset.point == it.selectionEnd && it.visualLineStart <= it.offset.point - injector.visualMotionGroup.selectionAdj) { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSelectPreviousAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSelectPreviousAction.kt index 31075c0308..c4f2d7643d 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSelectPreviousAction.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSelectPreviousAction.kt @@ -37,7 +37,7 @@ public class VisualSelectPreviousAction : VimActionHandler.SingleExecution() { if (caretToSelectionInfo.any { it.second.start == null || it.second.end == null }) return false - editor.vimStateMachine.mode = com.maddyhome.idea.vim.state.mode.Mode.VISUAL(selectionType) + editor.mode = com.maddyhome.idea.vim.state.mode.Mode.VISUAL(selectionType) for ((caret, selectionInfo) in caretToSelectionInfo) { val startOffset = editor.bufferPositionToOffset(selectionInfo.start!!) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSwapSelectionsAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSwapSelectionsAction.kt index 54cd42c34a..654727cdbc 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSwapSelectionsAction.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/visual/VisualSwapSelectionsAction.kt @@ -53,7 +53,7 @@ private fun swapVisualSelections(editor: VimEditor): Boolean { editor.vimLastSelectionType = mode.selectionType injector.markService.setVisualSelectionMarks(primaryCaret, TextRange(vimSelectionStart, primaryCaret.offset.point)) - editor.vimStateMachine.mode = mode.copy(selectionType = lastSelectionType) + editor.mode = mode.copy(selectionType = lastSelectionType) primaryCaret.vimSetSelection(lastVisualRange.startOffset, lastVisualRange.endOffset, true) injector.scroll.scrollCaretIntoView(editor) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt index 73e43fe5c0..20c960802b 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt @@ -36,7 +36,6 @@ import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGI import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.SelectionType -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.state.mode.toReturnTo import org.jetbrains.annotations.NonNls import java.awt.event.KeyEvent @@ -439,7 +438,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup { } val cmd = state.executingCommand if (cmd != null && state.isDotRepeatInProgress) { - state.mode = mode + editor.mode = mode if (mode == Mode.REPLACE) { editor.insertMode = false } @@ -465,7 +464,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup { if (mode == Mode.REPLACE) { editor.insertMode = true } - state.mode = Mode.NORMAL() + editor.mode = Mode.NORMAL() } else { lastInsert = cmd strokes.clear() @@ -480,7 +479,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup { vimDocument!!.addChangeListener(myChangeListener) oldOffset = editor.currentCaret().offset.point editor.insertMode = mode == Mode.INSERT - state.mode = mode + editor.mode = mode } notifyListeners(editor) } @@ -560,8 +559,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup { // The change pos '.' mark is the offset AFTER processing escape, and after switching to overtype markGroup.setMark(editor, MARK_CHANGE_POS) - getInstance(editor).mode = Mode.NORMAL() - editor.vimStateMachine.mode = Mode.NORMAL() + editor.mode = Mode.NORMAL() } private fun updateLastInsertedTextRegister() { @@ -640,7 +638,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup { * @param editor The editor to put into NORMAL mode for one command */ override fun processSingleCommand(editor: VimEditor) { - getInstance(editor).mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo) + editor.mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo) clearStrokes(editor) } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt index 4038bb4d39..20fb818ee9 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt @@ -124,6 +124,9 @@ import com.maddyhome.idea.vim.state.mode.SelectionType * ([VimVisualPosition] should be phased out if possible, as it is an IntelliJ concept, not a Vim concept.) */ public interface VimEditor { + public var mode: Mode + public var isReplaceCharacter: Boolean + public var isRecording: Boolean public val lfMakesNewLine: Boolean public var vimChangeActionSwitchMode: Mode? @@ -187,6 +190,9 @@ public interface VimEditor { public fun isDocumentWritable(): Boolean public fun isOneLineMode(): Boolean + @Deprecated("It will be replaced by Vim Mode Widget") + public fun getStatusString(): String + /** * public function for refactoring, get rid of it */ @@ -276,6 +282,12 @@ public interface VimEditor { * instance and need to search for a new version. */ public fun findLastVersionOfCaret(caret: T): T? + + /** + * Resets the command, mode, visual mode, and mapping mode to initial values. + */ + public fun resetState() + public fun resetOpPending() } public interface MutableVimEditor : VimEditor { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt index 0b2598caa7..d47e7b5c3e 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimVisualMotionGroupBase.kt @@ -32,7 +32,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup { get() = if (exclusiveSelection) 0 else 1 override fun enterSelectMode(editor: VimEditor, subMode: SelectionType): Boolean { - editor.vimStateMachine.setSelectMode(subMode) + editor.setSelectMode(subMode) editor.forEachCaret { it.vimSelectionStart = it.vimLeadSelectionOffset } return true } @@ -55,7 +55,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup { // Enable visual subMode if (rawCount > 0) { val primarySubMode = editor.primaryCaret().vimLastVisualOperatorRange?.type ?: selectionType - editor.vimStateMachine.pushVisualMode(primarySubMode) + editor.pushVisualMode(primarySubMode) editor.forEachCaret { val range = it.vimLastVisualOperatorRange ?: VisualChange.default(selectionType) @@ -71,7 +71,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup { it.vimLastColumn = intendedColumn } } else { - editor.vimStateMachine.mode = Mode.VISUAL( + editor.mode = Mode.VISUAL( selectionType, returnTo ?: editor.vimStateMachine.mode.returnTo ) @@ -90,7 +90,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup { check(mode is Mode.VISUAL) // Update visual subMode with new sub subMode - editor.vimStateMachine.mode = mode.copy(selectionType = selectionType) + editor.mode = mode.copy(selectionType = selectionType) for (caret in editor.carets()) { if (!caret.isValid) continue caret.vimUpdateEditorSelection() @@ -162,7 +162,7 @@ public abstract class VimVisualMotionGroupBase : VimVisualMotionGroup { */ override fun enterVisualMode(editor: VimEditor, subMode: SelectionType?): Boolean { val autodetectedSubMode = subMode ?: autodetectVisualSubmode(editor) - editor.vimStateMachine.mode = Mode.VISUAL(autodetectedSubMode) + editor.mode = Mode.VISUAL(autodetectedSubMode) // editor.vimStateMachine.setMode(VimStateMachine.Mode.VISUAL, autodetectedSubMode) if (autodetectedSubMode == SelectionType.BLOCK_WISE) { editor.primaryCaret().run { vimSelectionStart = vimLeadSelectionOffset } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt index 878fc3e3f4..d78f1c8043 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt @@ -11,6 +11,6 @@ package com.maddyhome.idea.vim.common import com.maddyhome.idea.vim.api.VimEditor public interface MacroRecordingListener { - public fun recordingStarted(editor: VimEditor, register: Char) - public fun recordingFinished(editor: VimEditor, register: Char) + public fun recordingStarted(editor: VimEditor) + public fun recordingFinished(editor: VimEditor) } \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt index 95834021ff..56d3c675ea 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt @@ -40,12 +40,12 @@ public class VimListenersNotifier { myEditorListeners.forEach { it.focusLost(editor) } } - public fun notifyMacroRecordingStarted(editor: VimEditor, register: Char) { - macroRecordingListeners.forEach { it.recordingStarted(editor, register) } + public fun notifyMacroRecordingStarted(editor: VimEditor) { + macroRecordingListeners.forEach { it.recordingStarted(editor) } } - public fun notifyMacroRecordingFinished(editor: VimEditor, register: Char) { - macroRecordingListeners.forEach { it.recordingFinished(editor, register) } + public fun notifyMacroRecordingFinished(editor: VimEditor) { + macroRecordingListeners.forEach { it.recordingFinished(editor) } } public fun notifyPluginTurnedOn() { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineHelper.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineHelper.kt index 0dea513b43..85af8ae555 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineHelper.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineHelper.kt @@ -54,10 +54,10 @@ public inline fun > enumSetOf(vararg value: T): EnumSet = else -> EnumSet.of(value[0], *value.slice(1..value.lastIndex).toTypedArray()) } -public fun VimStateMachine.setSelectMode(submode: SelectionType) { +public fun VimEditor.setSelectMode(submode: SelectionType) { mode = Mode.SELECT(submode, this.mode.returnTo) } -public fun VimStateMachine.pushVisualMode(submode: SelectionType) { +public fun VimEditor.pushVisualMode(submode: SelectionType) { mode = Mode.VISUAL(submode, this.mode.returnTo) } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineModeExtensions.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineModeExtensions.kt index 8b0f609cbd..213b9e8e55 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineModeExtensions.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/helper/EngineModeExtensions.kt @@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE import com.maddyhome.idea.vim.state.mode.inBlockSelection import com.maddyhome.idea.vim.state.mode.inVisualMode -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.state.mode.returnTo import com.maddyhome.idea.vim.state.mode.selectionType @@ -37,15 +36,15 @@ public fun VimEditor.exitVisualMode() { val returnTo = this.vimStateMachine.mode.returnTo when (returnTo) { ReturnTo.INSERT -> { - this.vimStateMachine.mode = Mode.INSERT + this.mode = Mode.INSERT } ReturnTo.REPLACE -> { - this.vimStateMachine.mode = Mode.REPLACE + this.mode = Mode.REPLACE } null -> { - this.vimStateMachine.mode = Mode.NORMAL() + this.mode = Mode.NORMAL() } } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt index 1aed5202fd..72492191bb 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt @@ -7,9 +7,7 @@ */ package com.maddyhome.idea.vim.impl.state -import com.maddyhome.idea.vim.action.change.LazyVimCommand import com.maddyhome.idea.vim.api.VimEditor -import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.CommandBuilder @@ -18,13 +16,10 @@ import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingState import com.maddyhome.idea.vim.common.DigraphResult import com.maddyhome.idea.vim.common.DigraphSequence -import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.helper.noneOfEnum -import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.ReturnTo -import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.returnTo import org.jetbrains.annotations.Contract import java.util.* @@ -32,37 +27,20 @@ import javax.swing.KeyStroke /** * Used to maintain state before and while entering a Vim command (operator, motion, text object, etc.) - * - * // TODO: 21.02.2022 This constructor should be empty */ -public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachine { - override val commandBuilder: CommandBuilder = CommandBuilder(getKeyRootNode(MappingMode.NORMAL)) +public class VimStateMachineImpl : VimStateMachine { + override val commandBuilder: CommandBuilder = CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL)) override var mode: Mode = Mode.NORMAL() set(value) { - if (field == value) return - - val oldValue = field field = value setMappingMode() - if (editor != null) { - injector.listenersNotifier.notifyModeChanged(editor, oldValue) - } - onModeChanged() } override val mappingState: MappingState = MappingState() override val digraphSequence: DigraphSequence = DigraphSequence() override var isRecording: Boolean = false - set(value) { - field = value - doShowMode() - } override var isDotRepeatInProgress: Boolean = false override var isRegisterPending: Boolean = false override var isReplaceCharacter: Boolean = false - set(value) { - field = value - onModeChanged() - } /** * The currently executing command @@ -86,22 +64,6 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi override val executingCommandFlags: EnumSet get() = executingCommand?.flags ?: noneOfEnum() - override fun resetOpPending() { - if (this.mode is Mode.OP_PENDING) { - val returnTo = this.mode.returnTo - mode = when (returnTo) { - ReturnTo.INSERT -> Mode.INSERT - ReturnTo.REPLACE -> Mode.INSERT - null -> Mode.NORMAL() - } - } - } - - override fun resetReplaceCharacter() { - if (isReplaceCharacter) { - isReplaceCharacter = false - } - } override fun resetRegisterPending() { if (isRegisterPending) { @@ -109,26 +71,6 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi } } - private fun resetModes() { -// modeStates.clear() - mode = Mode.NORMAL() - onModeChanged() - setMappingMode() - } - - private fun onModeChanged() { - if (editor != null) { - editor.updateCaretsVisualAttributes() - editor.updateCaretsVisualPosition() - } else { - injector.application.localEditors().forEach { editor -> - editor.updateCaretsVisualAttributes() - editor.updateCaretsVisualPosition() - } - } - doShowMode() - } - private fun setMappingMode() { mappingState.mappingMode = modeToMappingMode(this.mode) } @@ -145,10 +87,6 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi return digraphSequence.processKey(key, editor) } - override fun resetDigraph() { - digraphSequence.reset() - } - /** * Toggles the insert/overwrite state. If currently insert, goto replace mode. If currently replace, goto insert * mode. @@ -166,74 +104,7 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi } } - /** - * Resets the command, mode, visual mode, and mapping mode to initial values. - */ - override fun reset() { - executingCommand = null - resetModes() - commandBuilder.resetInProgressCommandPart(getKeyRootNode(mappingState.mappingMode)) - digraphSequence.reset() - } - - private fun doShowMode() { - val msg = StringBuilder() - if (injector.globalOptions().showmode) { - msg.append(getStatusString()) - } - if (isRecording) { - if (msg.isNotEmpty()) { - msg.append(" - ") - } - msg.append(injector.messages.message("show.mode.recording")) - } - injector.messages.showMode(editor, msg.toString()) - } - - override fun getStatusString(): String { - val modeState = this.mode - return buildString { - when (modeState) { - is Mode.NORMAL -> { - if (modeState.returnTo != null) append("-- (insert) --") - } - - Mode.INSERT -> append("-- INSERT --") - Mode.REPLACE -> append("-- REPLACE --") - is Mode.VISUAL -> { - val inInsert = if (modeState.returnTo != null) "(insert) " else "" - append("-- ${inInsert}VISUAL") - when (modeState.selectionType) { - SelectionType.LINE_WISE -> append(" LINE") - SelectionType.BLOCK_WISE -> append(" BLOCK") - else -> Unit - } - append(" --") - } - - is Mode.SELECT -> { - val inInsert = if (modeState.returnTo != null) "(insert) " else "" - append("-- ${inInsert}SELECT") - when (modeState.selectionType) { - SelectionType.LINE_WISE -> append(" LINE") - SelectionType.BLOCK_WISE -> append(" BLOCK") - else -> Unit - } - append(" --") - } - - else -> Unit - } - } - } - public companion object { - private val logger = vimLogger() - - private fun getKeyRootNode(mappingMode: MappingMode): CommandPartNode { - return injector.keyGroup.getKeyRoot(mappingMode) - } - @Contract(pure = true) public fun modeToMappingMode(mode: Mode): MappingMode { return when (mode) { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt index 3a3be90b94..851e1b8724 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt @@ -222,7 +222,7 @@ public class ToHandlerMappingInfo( val vimSelection = create(caret.vimSelectionStart, caret.offset.point, editor.mode.selectionType ?: CHARACTER_WISE, editor) offsets[caret] = vimSelection - commandState.mode = Mode.NORMAL() + editor.mode = Mode.NORMAL() } else if (startOffset != null && startOffset.point != caret.offset.point) { // Command line motions are always characterwise exclusive var endOffset = caret.offset diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt index dabbc7d471..39d09cc1f8 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt @@ -25,6 +25,8 @@ public interface VimRegisterGroup { public var lastRegisterChar: Char public val currentRegister: Char + public val recordRegister: Char + /** * When we access last register, it can be e.g. " because of two reasons: * 1. Because the default register value was used diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt index 4cc6119dfd..1c4df29e2f 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt @@ -36,9 +36,7 @@ import com.maddyhome.idea.vim.register.RegisterConstants.WRITABLE_REGISTERS import javax.swing.KeyStroke public abstract class VimRegisterGroupBase : VimRegisterGroup { - - @JvmField - protected var recordRegister: Char = 0.toChar() + public override var recordRegister: Char = 0.toChar() @JvmField protected var recordList: MutableList? = null @@ -477,10 +475,9 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup { override fun startRecording(editor: VimEditor, register: Char): Boolean { return if (RECORDABLE_REGISTERS.indexOf(register) != -1) { - VimStateMachine.getInstance(editor).isRecording = true recordRegister = register + editor.isRecording = true recordList = ArrayList() - injector.listenersNotifier.notifyMacroRecordingStarted(editor, register) true } else { false @@ -522,11 +519,9 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup { reg.addKeys(myRecordList) } } - VimStateMachine.getInstance(editor).isRecording = false - injector.listenersNotifier.notifyMacroRecordingFinished(editor, recordRegister) } - recordRegister = 0.toChar() + editor.isRecording = false } public companion object { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt index aa6a6b39a8..737c710dbf 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt @@ -27,13 +27,13 @@ import javax.swing.KeyStroke */ public interface VimStateMachine { public val commandBuilder: CommandBuilder - public var mode: Mode + public val mode: Mode public val mappingState: MappingState public val digraphSequence: DigraphSequence - public var isRecording: Boolean + public val isRecording: Boolean public var isDotRepeatInProgress: Boolean public var isRegisterPending: Boolean - public var isReplaceCharacter: Boolean + public val isReplaceCharacter: Boolean /** * The currently executing command @@ -51,12 +51,9 @@ public interface VimStateMachine { public fun isDuplicateOperatorKeyStroke(key: KeyStroke?): Boolean - public fun resetOpPending() - public fun resetReplaceCharacter() public fun resetRegisterPending() public fun startLiteralSequence() public fun processDigraphKey(key: KeyStroke, editor: VimEditor): DigraphResult - public fun resetDigraph() /** * Toggles the insert/overwrite state. If currently insert, goto replace mode. If currently replace, goto insert @@ -64,16 +61,12 @@ public interface VimStateMachine { */ public fun toggleInsertOverwrite() - /** - * Resets the command, mode, visual mode, and mapping mode to initial values. - */ - public fun reset() - public fun getStatusString(): String public fun startDigraphSequence() public companion object { - private val globalState = VimStateMachineImpl(null) + private val globalState = VimStateMachineImpl() + // TODO do we really need this method? Can't we use editor.vimStateMachine? public fun getInstance(editor: Any?): VimStateMachine { return if (editor == null || injector.globalOptions().ideaglobalmode) { globalState From 3738012dd6e861d52fd6fc75e1efe3c6a0032310 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 2 Feb 2024 13:03:29 +0200 Subject: [PATCH 02/44] Listeners refactoring 1. Listeners now disposed after turning plugin off 2. Change widget listeners to be recreated on plugin toggle 3. Add CaretVisualAttributesListener --- .../com/maddyhome/idea/vim/VimPlugin.java | 16 ++--- .../maddyhome/idea/vim/group/EditorGroup.java | 14 ++++- .../vim/helper/CaretVisualAttributesHelper.kt | 33 ++++++++++- .../idea/vim/listener/VimListenerManager.kt | 20 ++++++- .../maddyhome/idea/vim/newapi/IjVimEditor.kt | 29 +-------- .../idea/vim/ui/widgets/VimWidgetListener.kt | 7 +-- .../ui/widgets/macro/MacroWidgetFactory.kt | 59 +++++++++++-------- .../idea/vim/ui/widgets/mode/VimModeWidget.kt | 10 ---- ...FocusListener.kt => ModeWidgetListener.kt} | 34 +++++++++-- .../mode/listeners/ModeWidgetModeListener.kt | 25 -------- .../com/maddyhome/idea/vim/api/VimEditor.kt | 3 - .../maddyhome/idea/vim/api/VimEditorGroup.kt | 4 ++ .../idea/vim/common/IsReplaceCharListener.kt | 15 +++++ .../idea/vim/common/VimListenersNotifier.kt | 13 ++++ .../vim/group/visual/EngineVisualGroup.kt | 2 +- 15 files changed, 171 insertions(+), 113 deletions(-) rename src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/{ModeWidgetFocusListener.kt => ModeWidgetListener.kt} (55%) delete mode 100644 src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetModeListener.kt create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/IsReplaceCharListener.kt diff --git a/src/main/java/com/maddyhome/idea/vim/VimPlugin.java b/src/main/java/com/maddyhome/idea/vim/VimPlugin.java index 4f7e6aaba3..8260296149 100644 --- a/src/main/java/com/maddyhome/idea/vim/VimPlugin.java +++ b/src/main/java/com/maddyhome/idea/vim/VimPlugin.java @@ -211,22 +211,22 @@ public static boolean isNotEnabled() { public static void setEnabled(final boolean enabled) { if (isEnabled() == enabled) return; - if (!enabled) { - getInstance().turnOffPlugin(true); - } - getInstance().enabled = enabled; - if (enabled) { - getInstance().turnOnPlugin(); - } - if (enabled) { VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn(); } else { VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff(); } + if (!enabled) { + getInstance().turnOffPlugin(true); + } + + if (enabled) { + getInstance().turnOnPlugin(); + } + StatusBarIconFactory.Util.INSTANCE.updateIcon(); } diff --git a/src/main/java/com/maddyhome/idea/vim/group/EditorGroup.java b/src/main/java/com/maddyhome/idea/vim/group/EditorGroup.java index 31acd1cb1c..103f57a040 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/EditorGroup.java +++ b/src/main/java/com/maddyhome/idea/vim/group/EditorGroup.java @@ -246,7 +246,7 @@ public void editorCreated(@NotNull Editor editor) { switchToInsertMode.run(); } }); - updateCaretsVisualAttributes(editor); + updateCaretsVisualAttributes(new IjVimEditor(editor)); } public void editorDeinit(@NotNull Editor editor, boolean isReleased) { @@ -284,6 +284,18 @@ public void notifyIdeaJoin(@NotNull VimEditor editor) { notifyIdeaJoin(((IjVimEditor) editor).getEditor().getProject(), editor); } + @Override + public void updateCaretsVisualAttributes(@NotNull VimEditor editor) { + Editor ijEditor = ((IjVimEditor) editor).getEditor(); + CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor); + } + + @Override + public void updateCaretsVisualPosition(@NotNull VimEditor editor) { + Editor ijEditor = ((IjVimEditor) editor).getEditor(); + CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor); + } + public static class NumberChangeListener implements EffectiveOptionValueChangeListener { public static NumberChangeListener INSTANCE = new NumberChangeListener(); diff --git a/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt b/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt index 67df7c3b6d..ac7621edbc 100644 --- a/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt +++ b/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt @@ -18,14 +18,17 @@ import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.common.IsReplaceCharListener +import com.maddyhome.idea.vim.common.ModeChangeListener +import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener import com.maddyhome.idea.vim.options.helpers.GuiCursorMode import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper import com.maddyhome.idea.vim.options.helpers.GuiCursorType +import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.inBlockSelection -import com.maddyhome.idea.vim.state.mode.mode import org.jetbrains.annotations.TestOnly import java.awt.Color @@ -134,3 +137,31 @@ private object AttributesCache { @TestOnly internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode() + +public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener { + override fun isReplaceCharChanged(editor: VimEditor) { + updateCaretsVisual(editor) + } + + override fun modeChanged(editor: VimEditor, oldMode: Mode) { + updateCaretsVisual(editor) + } + + private fun updateCaretsVisual(editor: VimEditor) { + if (injector.globalOptions().ideaglobalmode) { + updateAllEditorsCaretsVisual() + } else { + val ijEditor = (editor as IjVimEditor).editor + ijEditor.updateCaretsVisualAttributes() + ijEditor.updateCaretsVisualPosition() + } + } + + public fun updateAllEditorsCaretsVisual() { + injector.application.localEditors().forEach { editor -> + val ijEditor = (editor as IjVimEditor).editor + ijEditor.updateCaretsVisualAttributes() + ijEditor.updateCaretsVisualPosition() + } + } +} \ No newline at end of file diff --git a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt index 46db59eaf3..9ea518fc93 100644 --- a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt +++ b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt @@ -71,6 +71,7 @@ import com.maddyhome.idea.vim.group.visual.VimVisualTimer import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd import com.maddyhome.idea.vim.handler.correctorRequester import com.maddyhome.idea.vim.handler.keyCheckRequests +import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener import com.maddyhome.idea.vim.helper.GuicursorChangeListener import com.maddyhome.idea.vim.helper.StrictMode import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker @@ -91,11 +92,12 @@ import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.state.mode.inSelectMode -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener import com.maddyhome.idea.vim.ui.ex.ExEntryPanel +import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener +import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener import com.maddyhome.idea.vim.vimDisposable import java.awt.event.MouseAdapter @@ -134,11 +136,27 @@ internal object VimListenerManager { EditorListeners.addAll() check(correctorRequester.tryEmit(Unit)) check(keyCheckRequests.tryEmit(Unit)) + + val caretVisualAttributesListener = CaretVisualAttributesListener() + injector.listenersNotifier.modeChangeListeners.add(caretVisualAttributesListener) + injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener) + caretVisualAttributesListener.updateAllEditorsCaretsVisual() + + val modeWidgetListener = ModeWidgetListener() + injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener) + injector.listenersNotifier.myEditorListeners.add(modeWidgetListener) + injector.listenersNotifier.vimPluginListeners.add(modeWidgetListener) + + val macroWidgetListener = MacroWidgetListener() + injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener) + injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener) } fun turnOff() { GlobalListeners.disable() EditorListeners.removeAll() + injector.listenersNotifier.reset() + check(correctorRequester.tryEmit(Unit)) } diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt index 7020b92ce4..93fca8e3dd 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt @@ -53,8 +53,6 @@ import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.helper.getTopLevelEditor import com.maddyhome.idea.vim.helper.inExMode import com.maddyhome.idea.vim.helper.isTemplateActive -import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes -import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode import com.maddyhome.idea.vim.helper.vimLastSelectionType import com.maddyhome.idea.vim.helper.vimStateMachine @@ -143,9 +141,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { val oldValue = vimStateMachine.mode (vimStateMachine as VimStateMachineImpl).mode = value injector.listenersNotifier.notifyModeChanged(this, oldValue) - - // TODO maybe it would be better to utilize modeListeners for this purpose? - updateCaretsVisual() doShowMode() } @@ -165,9 +160,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { set(value) { if (value != vimStateMachine.isReplaceCharacter) { (vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value - - // TODO maybe it would be better to utilize listeners for this purpose? - updateCaretsVisual() + injector.listenersNotifier.notifyIsReplaceCharChanged(this) } } @@ -183,18 +176,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { doShowMode() } - private fun updateCaretsVisual() { - if (injector.globalOptions().ideaglobalmode) { - injector.application.localEditors().forEach { editor -> - editor.updateCaretsVisualAttributes() - editor.updateCaretsVisualPosition() - } - } else { - editor.updateCaretsVisualAttributes() - editor.updateCaretsVisualPosition() - } - } - override fun resetState() { mode = Mode.NORMAL() vimStateMachine.executingCommand = null @@ -318,14 +299,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { } } - override fun updateCaretsVisualAttributes() { - editor.updateCaretsVisualAttributes() - } - - override fun updateCaretsVisualPosition() { - editor.updateCaretsVisualPosition() - } - override fun offsetToVisualPosition(offset: Int): VimVisualPosition { return editor.offsetToVisualPosition(offset).let { VimVisualPosition(it.line, it.column, it.leansRight) } } diff --git a/src/main/java/com/maddyhome/idea/vim/ui/widgets/VimWidgetListener.kt b/src/main/java/com/maddyhome/idea/vim/ui/widgets/VimWidgetListener.kt index b40942749c..90c1d651c7 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/widgets/VimWidgetListener.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/widgets/VimWidgetListener.kt @@ -8,15 +8,10 @@ package com.maddyhome.idea.vim.ui.widgets -import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.common.VimPluginListener import com.maddyhome.idea.vim.options.GlobalOptionChangeListener -public class VimWidgetListener(private val updateWidget: Runnable) : GlobalOptionChangeListener, VimPluginListener { - init { - injector.listenersNotifier.vimPluginListeners.add(this) - } - +public open class VimWidgetListener(private val updateWidget: Runnable) : GlobalOptionChangeListener, VimPluginListener { override fun onGlobalOptionChanged() { updateWidget.run() } diff --git a/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt b/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt index 95130479a0..145bb742b8 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt @@ -13,11 +13,13 @@ import com.intellij.openapi.project.Project import com.intellij.openapi.project.ProjectManager import com.intellij.openapi.wm.StatusBarWidget import com.intellij.openapi.wm.StatusBarWidgetFactory +import com.intellij.openapi.wm.WindowManager import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.common.MacroRecordingListener +import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener @@ -26,22 +28,7 @@ import java.awt.Component private const val ID = "IdeaVimMacro" -internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget { - private var content: String = "" - - private val macroRecordingListener = object : MacroRecordingListener { - override fun recordingStarted(editor: VimEditor) { - val register = injector.registerGroup.recordRegister - content = "recording @$register" - updateWidgetInStatusBar(ID, editor.ij.project) - } - - override fun recordingFinished(editor: VimEditor) { - content = "" - updateWidgetInStatusBar(ID, editor.ij.project) - } - } - +internal class MacroWidgetFactory : StatusBarWidgetFactory { override fun getId(): String { return ID } @@ -51,22 +38,23 @@ internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget { } override fun createWidget(project: Project): StatusBarWidget { - injector.listenersNotifier.macroRecordingListeners.add(macroRecordingListener) return VimMacroWidget() } override fun isAvailable(project: Project): Boolean { return VimPlugin.isEnabled() && injector.globalIjOptions().showmodewidget } +} - private inner class VimMacroWidget : StatusBarWidget { - override fun ID(): String { - return ID - } +public class VimMacroWidget : StatusBarWidget, VimStatusBarWidget { + public var content: String = "" - override fun getPresentation(): StatusBarWidget.WidgetPresentation { - return VimModeWidgetPresentation() - } + override fun ID(): String { + return ID + } + + override fun getPresentation(): StatusBarWidget.WidgetPresentation { + return VimModeWidgetPresentation() } private inner class VimModeWidgetPresentation : StatusBarWidget.TextPresentation { @@ -92,4 +80,25 @@ public fun updateMacroWidget() { } } -public val macroWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateMacroWidget() } \ No newline at end of file +public class MacroWidgetListener : MacroRecordingListener, VimWidgetListener({ updateMacroWidget() }) { + override fun recordingStarted(editor: VimEditor) { + val macroWidget = getWidget(editor) ?: return + val register = injector.registerGroup.recordRegister + macroWidget.content = "recording @$register" + macroWidget.updateWidgetInStatusBar(ID, editor.ij.project) + } + + override fun recordingFinished(editor: VimEditor) { + val macroWidget = getWidget(editor) ?: return + macroWidget.content = "" + macroWidget.updateWidgetInStatusBar(ID, editor.ij.project) + } + + private fun getWidget(editor: VimEditor): VimMacroWidget? { + val project = (editor as IjVimEditor).editor.project ?: return null + val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null + return statusBar.getWidget(ID) as? VimMacroWidget + } +} + +public val macroWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateMacroWidget() } diff --git a/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/VimModeWidget.kt b/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/VimModeWidget.kt index 796636572c..eacf375e6c 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/VimModeWidget.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/VimModeWidget.kt @@ -20,14 +20,9 @@ import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager import com.intellij.ui.awt.RelativePoint import com.intellij.ui.components.JBLabel import com.intellij.ui.util.width -import com.maddyhome.idea.vim.api.injector -import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.SelectionType -import com.maddyhome.idea.vim.state.mode.mode -import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetFocusListener -import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetModeListener import java.awt.Dimension import java.awt.Point import java.awt.event.MouseAdapter @@ -54,11 +49,6 @@ public class VimModeWidget(public val project: Project) : CustomStatusBarWidget, val mode = getFocusedEditor(project)?.vim?.mode updateLabel(mode) - injector.listenersNotifier.apply { - modeChangeListeners.add(ModeWidgetModeListener(this@VimModeWidget)) - myEditorListeners.add(ModeWidgetFocusListener(this@VimModeWidget)) - } - label.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { val popup = ModeWidgetPopup.createPopup() ?: return diff --git a/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetFocusListener.kt b/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetListener.kt similarity index 55% rename from src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetFocusListener.kt rename to src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetListener.kt index 457c034cf9..65d89ee97d 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetFocusListener.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetListener.kt @@ -11,23 +11,48 @@ package com.maddyhome.idea.vim.ui.widgets.mode.listeners import com.intellij.openapi.editor.Editor import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.project.Project +import com.intellij.openapi.wm.WindowManager import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.common.EditorListener +import com.maddyhome.idea.vim.common.ModeChangeListener +import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.vim -import com.maddyhome.idea.vim.state.mode.mode +import com.maddyhome.idea.vim.state.mode.Mode +import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener +import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget import com.maddyhome.idea.vim.ui.widgets.mode.updateModeWidget -internal class ModeWidgetFocusListener(private val modeWidget: VimModeWidget): EditorListener { +internal class ModeWidgetListener: ModeChangeListener, EditorListener, VimWidgetListener({ updateModeWidget()}) { + override fun modeChanged(editor: VimEditor, oldMode: Mode) { + val modeWidget = getWidget(editor) ?: return + val editorMode = editor.mode + if (editorMode !is Mode.OP_PENDING) { + modeWidget.updateWidget(editorMode) + } + } + + private fun getWidget(editor: VimEditor): VimModeWidget? { + val project = (editor as IjVimEditor).editor.project ?: return null + return getWidget(project) + } + + private fun getWidget(project: Project): VimModeWidget? { + val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null + return statusBar.getWidget(ModeWidgetFactory.ID) as? VimModeWidget + } + override fun created(editor: VimEditor) { updateModeWidget() + val modeWidget = getWidget(editor) ?: return val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode modeWidget.updateWidget(mode) } override fun released(editor: VimEditor) { updateModeWidget() + val modeWidget = getWidget(editor) ?: return val focusedEditor = getFocusedEditorForProject(editor.ij.project) if (focusedEditor == null || focusedEditor == editor.ij) { modeWidget.updateWidget(null) @@ -35,18 +60,19 @@ internal class ModeWidgetFocusListener(private val modeWidget: VimModeWidget): E } override fun focusGained(editor: VimEditor) { - if (editor.ij.project != modeWidget.project) return + val modeWidget = getWidget(editor) ?: return val mode = editor.mode modeWidget.updateWidget(mode) } override fun focusLost(editor: VimEditor) { + val modeWidget = getWidget(editor) ?: return val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode modeWidget.updateWidget(mode) } private fun getFocusedEditorForProject(editorProject: Project?): Editor? { - if (editorProject != modeWidget.project) return null + if (editorProject == null) return null val fileEditorManager = FileEditorManager.getInstance(editorProject) return fileEditorManager.selectedTextEditor } diff --git a/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetModeListener.kt b/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetModeListener.kt deleted file mode 100644 index dd5ac3b0ae..0000000000 --- a/src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/listeners/ModeWidgetModeListener.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2003-2023 The IdeaVim authors - * - * Use of this source code is governed by an MIT-style - * license that can be found in the LICENSE.txt file or at - * https://opensource.org/licenses/MIT. - */ - -package com.maddyhome.idea.vim.ui.widgets.mode.listeners - -import com.maddyhome.idea.vim.api.VimEditor -import com.maddyhome.idea.vim.common.ModeChangeListener -import com.maddyhome.idea.vim.newapi.ij -import com.maddyhome.idea.vim.state.mode.Mode -import com.maddyhome.idea.vim.state.mode.mode -import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget - -internal class ModeWidgetModeListener(private val modeWidget: VimModeWidget): ModeChangeListener { - override fun modeChanged(editor: VimEditor, oldMode: Mode) { - val editorMode = editor.mode - if (editorMode !is Mode.OP_PENDING && editor.ij.project == modeWidget.project) { - modeWidget.updateWidget(editorMode) - } - } -} \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt index 20fb818ee9..63358528ca 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt @@ -202,9 +202,6 @@ public interface VimEditor { shiftType: LineDeleteShift, ): Pair, LineDeleteShift>? - public fun updateCaretsVisualAttributes() - public fun updateCaretsVisualPosition() - public fun offsetToBufferPosition(offset: Int): BufferPosition public fun bufferPositionToOffset(position: BufferPosition): Int diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditorGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditorGroup.kt index 943dfdbc40..08bdd25179 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditorGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditorGroup.kt @@ -12,4 +12,8 @@ public interface VimEditorGroup { public fun notifyIdeaJoin(editor: VimEditor) public fun localEditors(): Collection public fun localEditors(buffer: VimDocument): Collection + + // TODO find a better place for methods below. Maybe make CaretVisualAttributesHelper abstract? + public fun updateCaretsVisualAttributes(editor: VimEditor) + public fun updateCaretsVisualPosition(editor: VimEditor) } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/IsReplaceCharListener.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/IsReplaceCharListener.kt new file mode 100644 index 0000000000..724a1d550e --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/IsReplaceCharListener.kt @@ -0,0 +1,15 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.common + +import com.maddyhome.idea.vim.api.VimEditor + +public interface IsReplaceCharListener { + public fun isReplaceCharChanged(editor: VimEditor) +} \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt index 56d3c675ea..2574d4cc67 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt @@ -19,6 +19,7 @@ public class VimListenersNotifier { public val myEditorListeners: MutableCollection = ConcurrentLinkedDeque() public val macroRecordingListeners: MutableCollection = ConcurrentLinkedDeque() public val vimPluginListeners: MutableCollection = ConcurrentLinkedDeque() + public val isReplaceCharListeners: MutableCollection = ConcurrentLinkedDeque() public fun notifyModeChanged(editor: VimEditor, oldMode: Mode) { modeChangeListeners.forEach { it.modeChanged(editor, oldMode) } @@ -55,4 +56,16 @@ public class VimListenersNotifier { public fun notifyPluginTurnedOff() { vimPluginListeners.forEach { it.turnedOff() } } + + public fun notifyIsReplaceCharChanged(editor: VimEditor) { + isReplaceCharListeners.forEach { it.isReplaceCharChanged(editor) } + } + + public fun reset() { + modeChangeListeners.clear() + myEditorListeners.clear() + macroRecordingListeners.clear() + vimPluginListeners.clear() + isReplaceCharListeners.clear() + } } \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/group/visual/EngineVisualGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/group/visual/EngineVisualGroup.kt index c10a1ffc09..b1cad19a3e 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/group/visual/EngineVisualGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/group/visual/EngineVisualGroup.kt @@ -51,7 +51,7 @@ public fun setVisualSelection(selectionStart: Int, selectionEnd: Int, caret: Vim editor.vimSetSystemBlockSelectionSilently(blockStart, blockEnd) // We've just added secondary carets again, hide them to better emulate block selection - editor.updateCaretsVisualAttributes() + injector.editorGroup.updateCaretsVisualAttributes(editor) for (aCaret in editor.nativeCarets()) { if (!aCaret.isValid) continue From 363db05db7d4308f249dd4eef74b4de85d013220 Mon Sep 17 00:00:00 2001 From: filipp Date: Sat, 3 Feb 2024 09:45:08 +0200 Subject: [PATCH 03/44] Macro recording state is no longer per editor It will not only simplify VimStateMachine, but also help us to support multi-editor macros in future --- .../idea/vim/extension/VimExtensionFacade.kt | 2 +- .../idea/vim/listener/IdeaSpecifics.kt | 4 +-- .../maddyhome/idea/vim/newapi/IjVimEditor.kt | 14 +------- .../com/maddyhome/idea/vim/ui/ModalEntry.kt | 3 +- .../ui/widgets/macro/MacroWidgetFactory.kt | 27 ++++++++------ .../plugins/ideavim/action/MacroActionTest.kt | 9 ++--- .../com/maddyhome/idea/vim/KeyHandler.kt | 6 ++-- .../vim/action/macro/ToggleRecordingAction.kt | 6 ++-- .../com/maddyhome/idea/vim/api/VimEditor.kt | 1 - .../idea/vim/common/MacroRecordingListener.kt | 6 ++-- .../idea/vim/common/VimListenersNotifier.kt | 8 ++--- .../vim/impl/state/VimStateMachineImpl.kt | 3 -- .../idea/vim/register/VimRegisterGroup.kt | 7 ++-- .../idea/vim/register/VimRegisterGroupBase.kt | 36 ++++++++++++------- .../idea/vim/state/VimStateMachine.kt | 1 - 15 files changed, 64 insertions(+), 69 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt b/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt index da7ac99813..aacb3b6a06 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt +++ b/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt @@ -152,7 +152,7 @@ public object VimExtensionFacade { LOG.trace("Unit test mode is active") val mappingStack = KeyHandler.getInstance().keyStack mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also { - if (editor.vim.vimStateMachine.isRecording) { + if (injector.registerGroup.isRecording) { KeyHandler.getInstance().modalEntryKeys += it } } diff --git a/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt b/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt index 02d4b181be..15cf0e3fab 100644 --- a/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt +++ b/src/main/java/com/maddyhome/idea/vim/listener/IdeaSpecifics.kt @@ -74,7 +74,7 @@ internal object IdeaSpecifics { } } - if (hostEditor != null && action is ChooseItemAction && hostEditor.vimStateMachine?.isRecording == true) { + if (hostEditor != null && action is ChooseItemAction && injector.registerGroup.isRecording) { val lookup = LookupManager.getActiveLookup(hostEditor) if (lookup != null) { val charsToRemove = hostEditor.caretModel.primaryCaret.offset - lookup.lookupStart @@ -95,7 +95,7 @@ internal object IdeaSpecifics { if (VimPlugin.isNotEnabled()) return val editor = editor - if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) { + if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) { val prevDocumentLength = completionPrevDocumentLength val prevDocumentOffset = completionPrevDocumentOffset diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt index 93fca8e3dd..d4d5c81b09 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt @@ -164,18 +164,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { } } - override var isRecording: Boolean - get() = vimStateMachine.isRecording - set(value) { - (vimStateMachine as VimStateMachineImpl).isRecording = value - if (value) { - injector.listenersNotifier.notifyMacroRecordingStarted(this) - } else { - injector.listenersNotifier.notifyMacroRecordingFinished(this) - } - doShowMode() - } - override fun resetState() { mode = Mode.NORMAL() vimStateMachine.executingCommand = null @@ -597,7 +585,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { if (injector.globalOptions().showmode) { msg.append(getStatusString()) } - if (vimStateMachine.isRecording) { + if (injector.registerGroup.isRecording) { if (msg.isNotEmpty()) { msg.append(" - ") } diff --git a/src/main/java/com/maddyhome/idea/vim/ui/ModalEntry.kt b/src/main/java/com/maddyhome/idea/vim/ui/ModalEntry.kt index 5dc45dcc81..ea8197245c 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/ModalEntry.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/ModalEntry.kt @@ -13,6 +13,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.trace import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.helper.isCloseKeyStroke import com.maddyhome.idea.vim.helper.vimStateMachine import java.awt.KeyEventDispatcher @@ -60,7 +61,7 @@ public object ModalEntry { } else { return true } - if (editor.vimStateMachine.isRecording) { + if (injector.registerGroup.isRecording) { KeyHandler.getInstance().modalEntryKeys += stroke } if (!processor(stroke)) { diff --git a/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt b/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt index 145bb742b8..b3a2f0bd06 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/widgets/macro/MacroWidgetFactory.kt @@ -80,22 +80,27 @@ public fun updateMacroWidget() { } } +// TODO: At the moment recording macro & RegisterGroup is bound to application, so macro will be recorded even if we +// move between projects. BUT it's not a good idea. Maybe RegisterGroup should have it's own project scope instances public class MacroWidgetListener : MacroRecordingListener, VimWidgetListener({ updateMacroWidget() }) { - override fun recordingStarted(editor: VimEditor) { - val macroWidget = getWidget(editor) ?: return - val register = injector.registerGroup.recordRegister - macroWidget.content = "recording @$register" - macroWidget.updateWidgetInStatusBar(ID, editor.ij.project) + override fun recordingStarted() { + for (project in ProjectManager.getInstance().openProjects) { + val macroWidget = getWidget(project) ?: continue + val register = injector.registerGroup.recordRegister + macroWidget.content = "recording @$register" + macroWidget.updateWidgetInStatusBar(ID, project) + } } - override fun recordingFinished(editor: VimEditor) { - val macroWidget = getWidget(editor) ?: return - macroWidget.content = "" - macroWidget.updateWidgetInStatusBar(ID, editor.ij.project) + override fun recordingFinished() { + for (project in ProjectManager.getInstance().openProjects) { + val macroWidget = getWidget(project) ?: continue + macroWidget.content = "" + macroWidget.updateWidgetInStatusBar(ID, project) + } } - private fun getWidget(editor: VimEditor): VimMacroWidget? { - val project = (editor as IjVimEditor).editor.project ?: return null + private fun getWidget(project: Project): VimMacroWidget? { val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null return statusBar.getWidget(ID) as? VimMacroWidget } diff --git a/src/test/java/org/jetbrains/plugins/ideavim/action/MacroActionTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/action/MacroActionTest.kt index 1e24248c9f..35254e555a 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/action/MacroActionTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/action/MacroActionTest.kt @@ -14,8 +14,6 @@ import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.keys import com.maddyhome.idea.vim.command.MappingMode -import com.maddyhome.idea.vim.helper.vimStateMachine -import com.maddyhome.idea.vim.newapi.vim import org.jetbrains.plugins.ideavim.ExceptionHandler import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor import org.jetbrains.plugins.ideavim.SkipNeovimReason @@ -44,9 +42,8 @@ class MacroActionTest : VimTestCase() { // |q| @Test fun testRecordMacro() { - val editor = typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "one two three\n") - val commandState = editor.vim.vimStateMachine - kotlin.test.assertFalse(commandState.isRecording) + typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "one two three\n") + kotlin.test.assertFalse(injector.registerGroup.isRecording) assertRegister('a', "3l") } @@ -101,7 +98,7 @@ class MacroActionTest : VimTestCase() { val register = VimPlugin.getRegister().getRegister('a') val registerSize = register!!.keys.size - kotlin.test.assertEquals(9, registerSize) + assertEquals(9, registerSize) } @Test diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 857df958ba..d111ea9947 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -103,7 +103,7 @@ public class KeyHandler { 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 && editorState.isRecording + var shouldRecord = handleKeyRecursionCount == 0 && injector.registerGroup.isRecording handleKeyRecursionCount++ try { LOG.trace("Start key processing...") @@ -191,7 +191,7 @@ public class KeyHandler { } // Don't record the keystroke that stops the recording (unmapped this is `q`) - if (shouldRecord && editorState.isRecording && key != null) { + if (shouldRecord && injector.registerGroup.isRecording && key != null) { injector.registerGroup.recordKeyStroke(key) modalEntryKeys.forEach { injector.registerGroup.recordKeyStroke(it) } modalEntryKeys.clear() @@ -506,7 +506,7 @@ public class KeyHandler { private fun stopMacroRecord(node: CommandNode, editorState: VimStateMachine): Boolean { // TODO // return editorState.isRecording && node.actionHolder.getInstance() is ToggleRecordingAction - return editorState.isRecording && node.actionHolder.instance.id == "VimToggleRecordingAction" + return injector.registerGroup.isRecording && node.actionHolder.instance.id == "VimToggleRecordingAction" } private fun startWaitingForArgument( diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/macro/ToggleRecordingAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/macro/ToggleRecordingAction.kt index e5986b3856..c082ba591a 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/macro/ToggleRecordingAction.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/macro/ToggleRecordingAction.kt @@ -25,12 +25,12 @@ public class ToggleRecordingAction : VimActionHandler.SingleExecution() { override val argumentType: Argument.Type = Argument.Type.CHARACTER override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { - return if (!editor.vimStateMachine.isRecording) { + return if (!injector.registerGroup.isRecording) { val argument = cmd.argument ?: return false val reg = argument.character - injector.registerGroup.startRecording(editor, reg) + injector.registerGroup.startRecording(reg) } else { - injector.registerGroup.finishRecording(editor) + injector.registerGroup.finishRecording() true } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt index 63358528ca..7903ad7e3f 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt @@ -126,7 +126,6 @@ import com.maddyhome.idea.vim.state.mode.SelectionType public interface VimEditor { public var mode: Mode public var isReplaceCharacter: Boolean - public var isRecording: Boolean public val lfMakesNewLine: Boolean public var vimChangeActionSwitchMode: Mode? diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt index d78f1c8043..cb12cd42ed 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/MacroRecordingListener.kt @@ -8,9 +8,7 @@ package com.maddyhome.idea.vim.common -import com.maddyhome.idea.vim.api.VimEditor - public interface MacroRecordingListener { - public fun recordingStarted(editor: VimEditor) - public fun recordingFinished(editor: VimEditor) + public fun recordingStarted() + public fun recordingFinished() } \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt index 2574d4cc67..c61f87bbb9 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/VimListenersNotifier.kt @@ -41,12 +41,12 @@ public class VimListenersNotifier { myEditorListeners.forEach { it.focusLost(editor) } } - public fun notifyMacroRecordingStarted(editor: VimEditor) { - macroRecordingListeners.forEach { it.recordingStarted(editor) } + public fun notifyMacroRecordingStarted() { + macroRecordingListeners.forEach { it.recordingStarted() } } - public fun notifyMacroRecordingFinished(editor: VimEditor) { - macroRecordingListeners.forEach { it.recordingFinished(editor) } + public fun notifyMacroRecordingFinished() { + macroRecordingListeners.forEach { it.recordingFinished() } } public fun notifyPluginTurnedOn() { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt index 72492191bb..43c19104da 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt @@ -19,8 +19,6 @@ import com.maddyhome.idea.vim.common.DigraphSequence import com.maddyhome.idea.vim.helper.noneOfEnum import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode -import com.maddyhome.idea.vim.state.mode.ReturnTo -import com.maddyhome.idea.vim.state.mode.returnTo import org.jetbrains.annotations.Contract import java.util.* import javax.swing.KeyStroke @@ -37,7 +35,6 @@ public class VimStateMachineImpl : VimStateMachine { } override val mappingState: MappingState = MappingState() override val digraphSequence: DigraphSequence = DigraphSequence() - override var isRecording: Boolean = false override var isDotRepeatInProgress: Boolean = false override var isRegisterPending: Boolean = false override var isReplaceCharacter: Boolean = false diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt index 39d09cc1f8..f654bcfc83 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroup.kt @@ -25,7 +25,8 @@ public interface VimRegisterGroup { public var lastRegisterChar: Char public val currentRegister: Char - public val recordRegister: Char + public val isRecording: Boolean + public val recordRegister: Char? /** * When we access last register, it can be e.g. " because of two reasons: @@ -81,13 +82,13 @@ public interface VimRegisterGroup { public fun getRegister(r: Char): Register? public fun getRegisters(): List public fun saveRegister(r: Char, register: Register) - public fun startRecording(editor: VimEditor, register: Char): Boolean + public fun startRecording(register: Char): Boolean public fun getPlaybackRegister(r: Char): Register? public fun recordText(text: String) public fun setKeys(register: Char, keys: List) public fun setKeys(register: Char, keys: List, type: SelectionType) - public fun finishRecording(editor: VimEditor) + public fun finishRecording() public fun getCurrentRegisterForMulticaret(): Char // `set clipbaard+=unnamedplus` should not make system register the default one when working with multiple carets VIM-2804 public fun isSystemClipboard(register: Char): Boolean public fun isPrimaryRegisterSupported(): Boolean diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt index 1c4df29e2f..5fb696c43d 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/register/VimRegisterGroupBase.kt @@ -36,7 +36,18 @@ import com.maddyhome.idea.vim.register.RegisterConstants.WRITABLE_REGISTERS import javax.swing.KeyStroke public abstract class VimRegisterGroupBase : VimRegisterGroup { - public override var recordRegister: Char = 0.toChar() + override val isRecording: Boolean + get() = recordRegister != null + + public override var recordRegister: Char? = null + set(value) { + field = value + if (value != null) { + injector.listenersNotifier.notifyMacroRecordingStarted() + } else { + injector.listenersNotifier.notifyMacroRecordingFinished() + } + } @JvmField protected var recordList: MutableList? = null @@ -125,7 +136,7 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup { override fun recordKeyStroke(key: KeyStroke) { val myRecordList = recordList - if (recordRegister != 0.toChar() && myRecordList != null) { + if (isRecording && myRecordList != null) { myRecordList.add(key) } } @@ -473,10 +484,9 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup { myRegisters[myR] = register } - override fun startRecording(editor: VimEditor, register: Char): Boolean { + override fun startRecording(register: Char): Boolean { return if (RECORDABLE_REGISTERS.indexOf(register) != -1) { recordRegister = register - editor.isRecording = true recordList = ArrayList() true } else { @@ -490,7 +500,7 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup { override fun recordText(text: String) { val myRecordList = recordList - if (recordRegister != 0.toChar() && myRecordList != null) { + if (isRecording && myRecordList != null) { myRecordList.addAll(injector.parser.stringToKeys(text)) } } @@ -503,25 +513,25 @@ public abstract class VimRegisterGroupBase : VimRegisterGroup { myRegisters[register] = Register(register, type, keys.toMutableList()) } - override fun finishRecording(editor: VimEditor) { - if (recordRegister != 0.toChar()) { + override fun finishRecording() { + val register = recordRegister + if (register != null) { var reg: Register? = null - if (Character.isUpperCase(recordRegister)) { - reg = getRegister(recordRegister) + if (Character.isUpperCase(register)) { + reg = getRegister(register) } val myRecordList = recordList if (myRecordList != null) { if (reg == null) { - reg = Register(Character.toLowerCase(recordRegister), SelectionType.CHARACTER_WISE, myRecordList) - myRegisters[Character.toLowerCase(recordRegister)] = reg + reg = Register(Character.toLowerCase(register), SelectionType.CHARACTER_WISE, myRecordList) + myRegisters[Character.toLowerCase(register)] = reg } else { reg.addKeys(myRecordList) } } } - recordRegister = 0.toChar() - editor.isRecording = false + recordRegister = null } public companion object { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt index 737c710dbf..4c3414ca33 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt @@ -30,7 +30,6 @@ public interface VimStateMachine { public val mode: Mode public val mappingState: MappingState public val digraphSequence: DigraphSequence - public val isRecording: Boolean public var isDotRepeatInProgress: Boolean public var isRegisterPending: Boolean public val isReplaceCharacter: Boolean From 6edfd8ed2262ff122b9f0681e653e8144ed29f2c Mon Sep 17 00:00:00 2001 From: filipp Date: Sat, 3 Feb 2024 10:33:46 +0200 Subject: [PATCH 04/44] Remove deprecated showmode status bar text update that does not work with the new UI and will be replaced with widget --- .../maddyhome/idea/vim/newapi/IjVimEditor.kt | 55 ------ .../ideavim/command/VimShowModeTest.kt | 169 ++++++++++++++++++ .../ideavim/command/VimStateMachineTest.kt | 162 ----------------- .../highlightedyank/VimHighlightedYankTest.kt | 8 +- .../com/maddyhome/idea/vim/api/VimEditor.kt | 3 - .../com/maddyhome/idea/vim/api/VimMessages.kt | 4 - 6 files changed, 176 insertions(+), 225 deletions(-) create mode 100644 src/test/java/org/jetbrains/plugins/ideavim/command/VimShowModeTest.kt delete mode 100644 src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt index d4d5c81b09..a68f37fb78 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt @@ -35,7 +35,6 @@ import com.maddyhome.idea.vim.api.VimScrollingModel import com.maddyhome.idea.vim.api.VimSelectionModel import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VirtualFile -import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.common.EditorLine @@ -141,7 +140,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { val oldValue = vimStateMachine.mode (vimStateMachine as VimStateMachineImpl).mode = value injector.listenersNotifier.notifyModeChanged(this, oldValue) - doShowMode() } override fun resetOpPending() { @@ -540,59 +538,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { // We can't use Object.toString() as this includes hashcode, which produces an error return "IjVimEditor[$editor@${identityHashCode(editor).toString(16)}]" } - - @Deprecated("It will be replaced by Vim Mode Widget") - override fun getStatusString(): String { - val modeState = this.mode - return buildString { - when (modeState) { - is Mode.NORMAL -> { - if (modeState.returnTo != null) append("-- (insert) --") - } - - Mode.INSERT -> append("-- INSERT --") - Mode.REPLACE -> append("-- REPLACE --") - is Mode.VISUAL -> { - val inInsert = if (modeState.returnTo != null) "(insert) " else "" - append("-- ${inInsert}VISUAL") - when (modeState.selectionType) { - SelectionType.LINE_WISE -> append(" LINE") - SelectionType.BLOCK_WISE -> append(" BLOCK") - else -> Unit - } - append(" --") - } - - is Mode.SELECT -> { - val inInsert = if (modeState.returnTo != null) "(insert) " else "" - append("-- ${inInsert}SELECT") - when (modeState.selectionType) { - SelectionType.LINE_WISE -> append(" LINE") - SelectionType.BLOCK_WISE -> append(" BLOCK") - else -> Unit - } - append(" --") - } - - else -> Unit - } - } - } - - @Deprecated("It will be replaced by Vim Mode Widget") - private fun doShowMode() { - val msg = StringBuilder() - if (injector.globalOptions().showmode) { - msg.append(getStatusString()) - } - if (injector.registerGroup.isRecording) { - if (msg.isNotEmpty()) { - msg.append(" - ") - } - msg.append(injector.messages.message("show.mode.recording")) - } - injector.messages.showMode(this, msg.toString()) - } } public val Editor.vim: VimEditor diff --git a/src/test/java/org/jetbrains/plugins/ideavim/command/VimShowModeTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/command/VimShowModeTest.kt new file mode 100644 index 0000000000..5852739e26 --- /dev/null +++ b/src/test/java/org/jetbrains/plugins/ideavim/command/VimShowModeTest.kt @@ -0,0 +1,169 @@ +/* + * Copyright 2003-2023 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package org.jetbrains.plugins.ideavim.command + +import com.intellij.openapi.wm.WindowManager +import com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory +import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget +import org.jetbrains.plugins.ideavim.VimTestCase + +// TODO it would be cool to test widget, but status bar is not initialized +class VimShowModeTest : VimTestCase() { +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in normal`() { +// configureByText("123") +// val widget = getModeWidget() +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in insert`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("i")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- INSERT --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in replace`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("R")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- REPLACE --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in visual`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("v")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- VISUAL --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in visual line`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("V")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- VISUAL LINE --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in visual block`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- VISUAL BLOCK --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in select`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("gh")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- SELECT --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in select line`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("gH")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- SELECT LINE --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in select block`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("g")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- SELECT BLOCK --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in one command`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("i")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- (insert) --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in one command visual`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("iv")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- (insert) VISUAL --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in one command visual block`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("i")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- (insert) VISUAL BLOCK --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in one command visual line`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("iV")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- (insert) VISUAL LINE --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in one command select`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("igh")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- (insert) SELECT --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in one command select block`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("ig")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- (insert) SELECT BLOCK --", statusString) +// } +// +// @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) +// @Test +// fun `test status string in one command select line`() { +// configureByText("123") +// typeText(injector.parser.parseKeys("igH")) +// val statusString = fixture.editor.vim.getStatusString() +// kotlin.test.assertEquals("-- (insert) SELECT LINE --", statusString) +// } +// + + // Always return null + private fun getModeWidget(): VimModeWidget? { + val project = fixture.editor?.project ?: return null + val statusBar = WindowManager.getInstance()?.getStatusBar(project) ?: return null + return statusBar.getWidget(ModeWidgetFactory.ID) as? VimModeWidget + } +} diff --git a/src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt deleted file mode 100644 index d866479dfd..0000000000 --- a/src/test/java/org/jetbrains/plugins/ideavim/command/VimStateMachineTest.kt +++ /dev/null @@ -1,162 +0,0 @@ -/* - * Copyright 2003-2023 The IdeaVim authors - * - * Use of this source code is governed by an MIT-style - * license that can be found in the LICENSE.txt file or at - * https://opensource.org/licenses/MIT. - */ - -package org.jetbrains.plugins.ideavim.command - -import com.maddyhome.idea.vim.api.injector -import com.maddyhome.idea.vim.helper.vimStateMachine -import com.maddyhome.idea.vim.newapi.vim -import org.jetbrains.plugins.ideavim.SkipNeovimReason -import org.jetbrains.plugins.ideavim.TestWithoutNeovim -import org.jetbrains.plugins.ideavim.VimTestCase -import org.junit.jupiter.api.Test - -class VimStateMachineTest : VimTestCase() { - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in normal`() { - configureByText("123") - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in insert`() { - configureByText("123") - typeText(injector.parser.parseKeys("i")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- INSERT --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in replace`() { - configureByText("123") - typeText(injector.parser.parseKeys("R")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- REPLACE --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in visual`() { - configureByText("123") - typeText(injector.parser.parseKeys("v")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- VISUAL --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in visual line`() { - configureByText("123") - typeText(injector.parser.parseKeys("V")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- VISUAL LINE --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in visual block`() { - configureByText("123") - typeText(injector.parser.parseKeys("")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- VISUAL BLOCK --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in select`() { - configureByText("123") - typeText(injector.parser.parseKeys("gh")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- SELECT --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in select line`() { - configureByText("123") - typeText(injector.parser.parseKeys("gH")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- SELECT LINE --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in select block`() { - configureByText("123") - typeText(injector.parser.parseKeys("g")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- SELECT BLOCK --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in one command`() { - configureByText("123") - typeText(injector.parser.parseKeys("i")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- (insert) --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in one command visual`() { - configureByText("123") - typeText(injector.parser.parseKeys("iv")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- (insert) VISUAL --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in one command visual block`() { - configureByText("123") - typeText(injector.parser.parseKeys("i")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- (insert) VISUAL BLOCK --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in one command visual line`() { - configureByText("123") - typeText(injector.parser.parseKeys("iV")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- (insert) VISUAL LINE --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in one command select`() { - configureByText("123") - typeText(injector.parser.parseKeys("igh")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- (insert) SELECT --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in one command select block`() { - configureByText("123") - typeText(injector.parser.parseKeys("ig")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- (insert) SELECT BLOCK --", statusString) - } - - @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING) - @Test - fun `test status string in one command select line`() { - configureByText("123") - typeText(injector.parser.parseKeys("igH")) - val statusString = fixture.editor.vim.getStatusString() - kotlin.test.assertEquals("-- (insert) SELECT LINE --", statusString) - } -} diff --git a/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt index e73453a8b3..6340498e45 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt @@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.extension.highlightedyank import com.intellij.openapi.editor.markup.RangeHighlighter import com.maddyhome.idea.vim.extension.highlightedyank.DEFAULT_HIGHLIGHT_DURATION +import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.state.mode.Mode import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.assertHappened @@ -30,6 +31,7 @@ class VimHighlightedYankTest : VimTestCase() { assertAllHighlightersCount(1) assertHighlighterRange(1, 40, getFirstHighlighter()) + clearMessage() } @Test @@ -38,6 +40,7 @@ class VimHighlightedYankTest : VimTestCase() { assertAllHighlightersCount(1) assertHighlighterRange(5, 8, getFirstHighlighter()) + clearMessage() } @Test @@ -45,6 +48,7 @@ class VimHighlightedYankTest : VimTestCase() { doTest("yyi", code, code, Mode.INSERT) assertAllHighlightersCount(0) + clearMessage() } @Test @@ -61,6 +65,7 @@ class VimHighlightedYankTest : VimTestCase() { assertHighlighterRange(12, 15, highlighters[1]) assertHighlighterRange(20, 23, highlighters[0]) assertHighlighterRange(28, 31, highlighters[2]) + clearMessage() } @Test @@ -73,6 +78,7 @@ Mode.INSERT, ) assertAllHighlightersCount(0) + clearMessage() } @Test @@ -82,9 +88,9 @@ Mode.INSERT, assertHappened(DEFAULT_HIGHLIGHT_DURATION.toInt(), 200) { getAllHighlightersCount() == 0 } + clearMessage() } - private val code = """ fun ${c}sum(x: Int, y: Int, z: Int): Int { return x + y + z diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt index 7903ad7e3f..9adcf74856 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt @@ -189,9 +189,6 @@ public interface VimEditor { public fun isDocumentWritable(): Boolean public fun isOneLineMode(): Boolean - @Deprecated("It will be replaced by Vim Mode Widget") - public fun getStatusString(): String - /** * public function for refactoring, get rid of it */ diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimMessages.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimMessages.kt index 1bc7b4b949..ebbcb793d6 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimMessages.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimMessages.kt @@ -17,8 +17,4 @@ public interface VimMessages { public fun message(key: String, vararg params: Any): String public fun updateStatusBar(editor: VimEditor) - - public fun showMode(editor: VimEditor?, msg: String) { - showStatusBarMessage(editor, msg) - } } From 5fc2f042243cdfbf3c2af31bdeab20a710360bc7 Mon Sep 17 00:00:00 2001 From: filipp Date: Sat, 3 Feb 2024 23:03:54 +0200 Subject: [PATCH 05/44] Remove mappingMode from MappingState It unnecessarily binds mappingState to mode and thus to editor. And we want to simplify things and have a single MappingState instead of multiple of them --- .../idea/vim/command/CommandState.kt | 2 +- .../argtextobj/VimArgTextObjExtension.java | 2 +- .../idea/vim/extension/matchit/Matchit.kt | 2 +- .../ReplaceWithRegister.kt | 6 ++-- .../VimTextObjEntireExtension.java | 2 +- .../textobjindent/VimIndentObject.java | 2 +- .../maddyhome/idea/vim/newapi/IjVimEditor.kt | 3 +- .../com/maddyhome/idea/vim/KeyHandler.kt | 13 +++++---- .../idea/vim/command/MappingProcessor.kt | 6 ++-- .../idea/vim/command/MappingState.kt | 5 ---- .../vim/impl/state/VimStateMachineImpl.kt | 28 +++++++++++-------- .../com/maddyhome/idea/vim/key/MappingInfo.kt | 5 ++-- .../idea/vim/state/VimStateMachine.kt | 4 +-- .../vim/vimscript/model/commands/Command.kt | 3 +- 14 files changed, 42 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/command/CommandState.kt b/src/main/java/com/maddyhome/idea/vim/command/CommandState.kt index eb428f4d95..f98e21b201 100644 --- a/src/main/java/com/maddyhome/idea/vim/command/CommandState.kt +++ b/src/main/java/com/maddyhome/idea/vim/command/CommandState.kt @@ -21,7 +21,7 @@ import com.maddyhome.idea.vim.state.mode.SelectionType public class CommandState(private val machine: VimStateMachine) { public val isOperatorPending: Boolean - get() = machine.isOperatorPending + get() = machine.isOperatorPending(machine.mode) public val mode: CommandState.Mode get() { diff --git a/src/main/java/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java b/src/main/java/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java index 81ff92e75f..15a2b5661d 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java +++ b/src/main/java/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java @@ -251,7 +251,7 @@ public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner); //noinspection DuplicatedCode - if (!vimStateMachine.isOperatorPending()) { + if (!vimStateMachine.isOperatorPending(editor.getMode())) { editor.nativeCarets().forEach((VimCaret caret) -> { final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0); if (range != null) { diff --git a/src/main/java/com/maddyhome/idea/vim/extension/matchit/Matchit.kt b/src/main/java/com/maddyhome/idea/vim/extension/matchit/Matchit.kt index 663f1f34fc..6b01c683b7 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/matchit/Matchit.kt +++ b/src/main/java/com/maddyhome/idea/vim/extension/matchit/Matchit.kt @@ -99,7 +99,7 @@ internal class Matchit : VimExtension { // Normally we want to jump to the start of the matching pair. But when moving forward in operator // pending mode, we want to include the entire match. isInOpPending makes that distinction. - val isInOpPending = commandState.isOperatorPending + val isInOpPending = commandState.isOperatorPending(editor.mode) if (isInOpPending) { val matchitAction = MatchitAction() diff --git a/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt b/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt index b73bb6f10f..bd143c4b87 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt +++ b/src/main/java/com/maddyhome/idea/vim/extension/replacewithregister/ReplaceWithRegister.kt @@ -31,7 +31,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.helper.exitVisualMode -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.key.OperatorFunction import com.maddyhome.idea.vim.newapi.IjVimEditor @@ -165,13 +164,14 @@ internal class ReplaceWithRegister : VimExtension { caretAfterInsertedText = false, putToLine = -1, ) + val vimEditor = editor.vim ClipboardOptionHelper.IdeaputDisabler().use { VimPlugin.getPut().putText( - IjVimEditor(editor), + vimEditor, injector.executionContextManager.onEditor(editor.vim), putData, operatorArguments = OperatorArguments( - editor.vimStateMachine?.isOperatorPending ?: false, + editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false, 0, editor.vim.mode, ), diff --git a/src/main/java/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java b/src/main/java/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java index 2910b42201..5d00af8934 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java +++ b/src/main/java/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java @@ -138,7 +138,7 @@ public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing); //noinspection DuplicatedCode - if (!vimStateMachine.isOperatorPending()) { + if (!vimStateMachine.isOperatorPending(editor.getMode())) { ((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0); if (range != null) { diff --git a/src/main/java/com/maddyhome/idea/vim/extension/textobjindent/VimIndentObject.java b/src/main/java/com/maddyhome/idea/vim/extension/textobjindent/VimIndentObject.java index 647fa311f4..f051465f9a 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/textobjindent/VimIndentObject.java +++ b/src/main/java/com/maddyhome/idea/vim/extension/textobjindent/VimIndentObject.java @@ -267,7 +267,7 @@ public void execute(@NotNull VimEditor editor, @NotNull ExecutionContext context final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow); - if (!vimStateMachine.isOperatorPending()) { + if (!vimStateMachine.isOperatorPending(editor.getMode())) { ((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> { final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0); if (range != null) { diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt index a68f37fb78..ce3bd22a08 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt @@ -56,6 +56,7 @@ import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode import com.maddyhome.idea.vim.helper.vimLastSelectionType import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl +import com.maddyhome.idea.vim.impl.state.toMappingMode import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.SelectionType @@ -167,7 +168,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { vimStateMachine.executingCommand = null vimStateMachine.digraphSequence.reset() vimStateMachine.commandBuilder.resetInProgressCommandPart( - injector.keyGroup.getKeyRoot(vimStateMachine.mappingState.mappingMode) + injector.keyGroup.getKeyRoot(mode.toMappingMode()) ) } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index d111ea9947..be54dc46cc 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -29,6 +29,7 @@ import com.maddyhome.idea.vim.diagnostic.vimLogger import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.helper.isCloseKeyStroke import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.impl.state.toMappingMode import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.KeyStack @@ -152,7 +153,7 @@ public class KeyHandler { } else if (editorState.mode is Mode.SELECT) { LOG.trace("Process select") shouldRecord = injector.changeGroup.processKeyInSelectMode(editor, context, key) && shouldRecord - } else if (editorState.mappingState.mappingMode == MappingMode.CMD_LINE) { + } else if (editor.mode is Mode.CMD_LINE) { LOG.trace("Process cmd line") shouldRecord = injector.processGroup.processExKey(editor, key) && shouldRecord } else { @@ -210,7 +211,7 @@ public class KeyHandler { node: Node?, editorState: VimStateMachine, ): Node? { - return if (editorState.isDuplicateOperatorKeyStroke(key)) { + return if (editorState.isDuplicateOperatorKeyStroke(key, editorState.mode)) { editorState.commandBuilder.getChildNode(KeyStroke.getKeyStroke('_')) } else { node @@ -290,7 +291,7 @@ public class KeyHandler { return if (editorState.isRegisterPending) { true } else { - key.keyChar == '"' && !editorState.isOperatorPending && editorState.commandBuilder.expectedArgumentType == null + key.keyChar == '"' && !editorState.isOperatorPending(editorState.mode) && editorState.commandBuilder.expectedArgumentType == null } } @@ -415,7 +416,7 @@ public class KeyHandler { LOG.trace("Command execution") val command = editorState.commandBuilder.buildCommand() val operatorArguments = OperatorArguments( - editorState.mappingState.mappingMode == MappingMode.OP_PENDING, + editor.mode is Mode.OP_PENDING, command.rawCount, editorState.mode, ) @@ -578,7 +579,7 @@ public class KeyHandler { public fun partialReset(editor: VimEditor) { val editorState = VimStateMachine.getInstance(editor) editorState.mappingState.resetMappingSequence() - editorState.commandBuilder.resetInProgressCommandPart(getKeyRoot(editorState.mappingState.mappingMode)) + editorState.commandBuilder.resetInProgressCommandPart(getKeyRoot(editor.mode.toMappingMode())) } /** @@ -589,7 +590,7 @@ public class KeyHandler { public fun reset(editor: VimEditor) { partialReset(editor) val editorState = VimStateMachine.getInstance(editor) - editorState.commandBuilder.resetAll(getKeyRoot(editorState.mappingState.mappingMode)) + editorState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode())) } private fun getKeyRoot(mappingMode: MappingMode): CommandPartNode { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt index 73125f2583..516e2dc93f 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt @@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.diagnostic.debug import com.maddyhome.idea.vim.diagnostic.trace 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.state.VimStateMachine import javax.swing.KeyStroke @@ -48,8 +49,9 @@ public object MappingProcessor { // Save the unhandled keystrokes until we either complete or abandon the sequence. log.trace("Add key to mapping state") mappingState.addKey(key) - val mapping = injector.keyGroup.getKeyMappingLayer(mappingState.mappingMode) - log.trace { "Get keys for mapping mode. mode = " + mappingState.mappingMode } + val mappingMode = editor.mode.toMappingMode() + val mapping = injector.keyGroup.getKeyMappingLayer(mappingMode) + log.trace { "Get keys for mapping mode. mode = $mappingMode" } // 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. diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt index e7caab7fce..5c6488fbd0 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt @@ -35,11 +35,6 @@ public class MappingState { public val keys: Iterable get() = keyList - public var mappingMode: MappingMode = MappingMode.NORMAL - set(value) { - field = value - } - private val timer = Timer(injector.globalOptions().timeoutlen, null) private var keyList = mutableListOf() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt index 43c19104da..07ced49216 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt @@ -29,10 +29,6 @@ import javax.swing.KeyStroke public class VimStateMachineImpl : VimStateMachine { override val commandBuilder: CommandBuilder = CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL)) override var mode: Mode = Mode.NORMAL() - set(value) { - field = value - setMappingMode() - } override val mappingState: MappingState = MappingState() override val digraphSequence: DigraphSequence = DigraphSequence() override var isDotRepeatInProgress: Boolean = false @@ -51,11 +47,12 @@ public class VimStateMachineImpl : VimStateMachine { */ override var executingCommand: Command? = null - override val isOperatorPending: Boolean - get() = mappingState.mappingMode == MappingMode.OP_PENDING && !commandBuilder.isEmpty + override fun isOperatorPending(mode: Mode): Boolean { + return mode is Mode.OP_PENDING && !commandBuilder.isEmpty + } - override fun isDuplicateOperatorKeyStroke(key: KeyStroke?): Boolean { - return isOperatorPending && commandBuilder.isDuplicateOperatorKeyStroke(key!!) + override fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode): Boolean { + return isOperatorPending(mode) && commandBuilder.isDuplicateOperatorKeyStroke(key) } override val executingCommandFlags: EnumSet @@ -68,10 +65,6 @@ public class VimStateMachineImpl : VimStateMachine { } } - private fun setMappingMode() { - mappingState.mappingMode = modeToMappingMode(this.mode) - } - override fun startDigraphSequence() { digraphSequence.startDigraphSequence() } @@ -115,3 +108,14 @@ public class VimStateMachineImpl : VimStateMachine { } } } + +public fun Mode.toMappingMode(): MappingMode { + return when (this) { + is Mode.NORMAL -> MappingMode.NORMAL + Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT + is Mode.VISUAL -> MappingMode.VISUAL + is Mode.SELECT -> MappingMode.SELECT + is Mode.CMD_LINE -> MappingMode.CMD_LINE + is Mode.OP_PENDING -> MappingMode.OP_PENDING + } +} diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt index 851e1b8724..5ed39a6ca3 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt @@ -30,7 +30,6 @@ import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import com.maddyhome.idea.vim.vimscript.model.expressions.Expression @@ -158,7 +157,7 @@ public class ToHandlerMappingInfo( // Cache isOperatorPending in case the extension changes the mode while moving the caret // See CommonExtensionTest // TODO: Is this legal? Should we assert in this case? - val shouldCalculateOffsets: Boolean = vimStateMachine.isOperatorPending + val shouldCalculateOffsets: Boolean = vimStateMachine.isOperatorPending(editor.mode) val startOffsets: Map = editor.carets().associateWith { it.offset } @@ -186,7 +185,7 @@ public class ToHandlerMappingInfo( } } - val operatorArguments = OperatorArguments(vimStateMachine.isOperatorPending, vimStateMachine.commandBuilder.count, vimStateMachine.mode) + val operatorArguments = OperatorArguments(vimStateMachine.isOperatorPending(editor.mode), vimStateMachine.commandBuilder.count, vimStateMachine.mode) injector.actionExecutor.executeCommand( editor, { extensionHandler.execute(editor, context, operatorArguments) }, diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt index 4c3414ca33..c39692ebb9 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt @@ -45,10 +45,10 @@ public interface VimStateMachine { * This field is reset after the command has been executed. */ public var executingCommand: Command? - public val isOperatorPending: Boolean + public fun isOperatorPending(mode: Mode): Boolean public val executingCommandFlags: EnumSet - public fun isDuplicateOperatorKeyStroke(key: KeyStroke?): Boolean + public fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode): Boolean public fun resetRegisterPending() public fun startLiteralSequence() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/Command.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/Command.kt index e690ffac32..bbdb38d5e1 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/Command.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/Command.kt @@ -24,7 +24,6 @@ import com.maddyhome.idea.vim.ex.NoRangeAllowedException import com.maddyhome.idea.vim.ex.ranges.LineRange import com.maddyhome.idea.vim.ex.ranges.Ranges import com.maddyhome.idea.vim.helper.Msg -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.helper.noneOfEnum import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.vimscript.model.Executable @@ -71,7 +70,7 @@ public sealed class Command(public var commandRanges: Ranges, public val command } val operatorArguments = OperatorArguments( - editor.vimStateMachine.isOperatorPending, + editor.vimStateMachine.isOperatorPending(editor.mode), 0, editor.mode, ) From 7966a6dc914aa5520729ad91b337cd35901db10e Mon Sep 17 00:00:00 2001 From: filipp Date: Sat, 3 Feb 2024 23:22:54 +0200 Subject: [PATCH 06/44] Create KeyHandlerState We do not need multiple commandBuilder, digraphSequence or mappingState and this class will be a singleton containing them --- .../jetbrains/plugins/ideavim/VimTestCase.kt | 2 +- .../com/maddyhome/idea/vim/KeyHandler.kt | 2 ++ .../vim/impl/state/VimStateMachineImpl.kt | 8 ++--- .../idea/vim/state/KeyHandlerState.kt | 35 +++++++++++++++++++ 4 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt diff --git a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt index 2005333598..f3e0b9ae82 100644 --- a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -73,7 +73,6 @@ import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper import com.maddyhome.idea.vim.options.helpers.GuiCursorType import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.inBlockSelection -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref @@ -119,6 +118,7 @@ abstract class VimTestCase { if (editor != null) { KeyHandler.getInstance().fullReset(editor.vim) } + KeyHandler.getInstance().keyState.reset(Mode.NORMAL()) VimPlugin.getOptionGroup().resetAllOptionsForTesting() VimPlugin.getKey().resetKeyMappings() VimPlugin.getSearch().resetState() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index be54dc46cc..e25133ddc2 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -34,6 +34,7 @@ import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.Node +import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.ReturnTo @@ -48,6 +49,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { + public val keyState: KeyHandlerState = KeyHandlerState() private var handleKeyRecursionCount = 0 diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt index 07ced49216..9f0bc19da7 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt @@ -7,8 +7,8 @@ */ package com.maddyhome.idea.vim.impl.state +import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.api.VimEditor -import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.CommandBuilder import com.maddyhome.idea.vim.command.CommandFlags @@ -27,10 +27,10 @@ import javax.swing.KeyStroke * Used to maintain state before and while entering a Vim command (operator, motion, text object, etc.) */ public class VimStateMachineImpl : VimStateMachine { - override val commandBuilder: CommandBuilder = CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL)) + override val commandBuilder: CommandBuilder = KeyHandler.getInstance().keyState.commandBuilder override var mode: Mode = Mode.NORMAL() - override val mappingState: MappingState = MappingState() - override val digraphSequence: DigraphSequence = DigraphSequence() + override val mappingState: MappingState = KeyHandler.getInstance().keyState.mappingState + override val digraphSequence: DigraphSequence = KeyHandler.getInstance().keyState.digraphSequence override var isDotRepeatInProgress: Boolean = false override var isRegisterPending: Boolean = false override var isReplaceCharacter: Boolean = false diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt new file mode 100644 index 0000000000..ab72f86ec6 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.state + +import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.command.CommandBuilder +import com.maddyhome.idea.vim.command.MappingMode +import com.maddyhome.idea.vim.command.MappingState +import com.maddyhome.idea.vim.common.DigraphSequence +import com.maddyhome.idea.vim.impl.state.toMappingMode +import com.maddyhome.idea.vim.state.mode.Mode + +public class KeyHandlerState { + public val mappingState: MappingState = MappingState() + public val digraphSequence: DigraphSequence = DigraphSequence() + public val commandBuilder: CommandBuilder = CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL)) + + public fun partialReset(mode: Mode) { + digraphSequence.reset() + mappingState.resetMappingSequence() + commandBuilder.resetInProgressCommandPart(injector.keyGroup.getKeyRoot(mode.toMappingMode())) + } + + public fun reset(mode: Mode) { + digraphSequence.reset() + mappingState.resetMappingSequence() + commandBuilder.resetAll(injector.keyGroup.getKeyRoot(mode.toMappingMode())) + } +} \ No newline at end of file From 31e7c49608e5cc4aa32f2e2de1e4719ba4f70a51 Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Sun, 4 Feb 2024 13:53:48 +0200 Subject: [PATCH 07/44] Add equals & hashCode --- .../idea/vim/action/change/LazyVimCommand.kt | 20 +++++++++ .../idea/vim/command/CommandBuilder.kt | 27 ++++++++++++ .../idea/vim/command/MappingState.kt | 43 ++++++++++++++++++- .../idea/vim/common/DigraphSequence.kt | 26 +++++++++++ .../com/maddyhome/idea/vim/key/Nodes.kt | 15 ++++++- .../idea/vim/state/KeyHandlerState.kt | 10 +++-- 6 files changed, 133 insertions(+), 8 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt index a61d976f62..f3119d5f56 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt @@ -20,4 +20,24 @@ public class LazyVimCommand( classLoader: ClassLoader, ) : LazyInstance(className, classLoader) { public val actionId: String = EditorActionHandlerBase.getActionId(className) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as LazyVimCommand + + if (keys != other.keys) return false + if (modes != other.modes) return false + if (actionId != other.actionId) return false + + return true + } + + override fun hashCode(): Int { + var result = keys.hashCode() + result = 31 * result + modes.hashCode() + result = 31 * result + actionId.hashCode() + return result + } } \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt index 317452d404..6a79f2b421 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt @@ -191,6 +191,33 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode< @TestOnly public fun getCurrentTrie(): CommandPartNode = currentCommandPartNode + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as CommandBuilder + + if (currentCommandPartNode != other.currentCommandPartNode) return false + if (commandParts != other.commandParts) return false + if (keyList != other.keyList) return false + if (commandState != other.commandState) return false + if (count != other.count) return false + if (expectedArgumentType != other.expectedArgumentType) return false + if (prevExpectedArgumentType != other.prevExpectedArgumentType) return false + + return true + } + + override fun hashCode(): Int { + var result = currentCommandPartNode.hashCode() + result = 31 * result + commandParts.hashCode() + result = 31 * result + keyList.hashCode() + result = 31 * result + commandState.hashCode() + result = 31 * result + count + result = 31 * result + (expectedArgumentType?.hashCode() ?: 0) + result = 31 * result + (prevExpectedArgumentType?.hashCode() ?: 0) + return result + } public companion object { private val LOG = vimLogger() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt index 5c6488fbd0..7707789708 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt @@ -35,7 +35,7 @@ public class MappingState { public val keys: Iterable get() = keyList - private val timer = Timer(injector.globalOptions().timeoutlen, null) + private val timer = VimTimer(injector.globalOptions().timeoutlen) private var keyList = mutableListOf() init { @@ -72,7 +72,46 @@ public class MappingState { // NOTE: We intentionally don't reset mapping mode here } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as MappingState + + if (mapDepth != other.mapDepth) return false + if (timer != other.timer) return false + if (keyList != other.keyList) return false + + return true + } + + override fun hashCode(): Int { + var result = mapDepth + result = 31 * result + timer.hashCode() + result = 31 * result + keyList.hashCode() + return result + } + public companion object { private val LOG = vimLogger() } -} + + public class VimTimer(delay: Int) : Timer(delay, null) { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as VimTimer + + if (delay != other.delay) return false + if (initialDelay != other.initialDelay) return false + if (isRunning != other.isRunning) return false + + return true + } + + override fun hashCode(): Int { + return javaClass.hashCode() + } + } +} \ No newline at end of file diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt index ca2e49e03d..4fe4b95600 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt @@ -222,6 +222,32 @@ public class DigraphSequence { codeChars = CharArray(8) } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as DigraphSequence + + if (digraphState != other.digraphState) return false + if (digraphChar != other.digraphChar) return false + if (!codeChars.contentEquals(other.codeChars)) return false + if (codeCnt != other.codeCnt) return false + if (codeType != other.codeType) return false + if (codeMax != other.codeMax) return false + + return true + } + + override fun hashCode(): Int { + var result = digraphState + result = 31 * result + digraphChar.hashCode() + result = 31 * result + codeChars.contentHashCode() + result = 31 * result + codeCnt + result = 31 * result + codeType + result = 31 * result + codeMax + return result + } + public companion object { private const val DIG_STATE_PENDING = 1 private const val DIG_STATE_DIG_ONE = 2 diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/Nodes.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/Nodes.kt index 649fe3b615..8db5bfd105 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/Nodes.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/Nodes.kt @@ -42,10 +42,21 @@ import javax.swing.KeyStroke public interface Node /** Represents a complete command */ -public class CommandNode(public val actionHolder: T) : Node +public data class CommandNode(public val actionHolder: T) : Node /** Represents a part of the command */ -public open class CommandPartNode : Node, HashMap>() +public open class CommandPartNode : Node, HashMap>() { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + if (!super.equals(other)) return false + return true + } + + override fun hashCode(): Int { + return super.hashCode() + } +} /** Represents a root node for the mode */ public class RootNode : CommandPartNode() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt index ab72f86ec6..9f58e406dc 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt @@ -16,10 +16,12 @@ import com.maddyhome.idea.vim.common.DigraphSequence import com.maddyhome.idea.vim.impl.state.toMappingMode import com.maddyhome.idea.vim.state.mode.Mode -public class KeyHandlerState { - public val mappingState: MappingState = MappingState() - public val digraphSequence: DigraphSequence = DigraphSequence() - public val commandBuilder: CommandBuilder = CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL)) +public data class KeyHandlerState( + public val mappingState: MappingState, + public val digraphSequence: DigraphSequence, + public val commandBuilder: CommandBuilder, +) { + public constructor() : this(MappingState(), DigraphSequence(), CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL))) public fun partialReset(mode: Mode) { digraphSequence.reset() From 282e581bdb39f37822ae6a1c5be9ddb12942d43e Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Sun, 4 Feb 2024 17:39:55 +0200 Subject: [PATCH 08/44] Make state cloneable --- .../com/maddyhome/idea/vim/KeyHandler.kt | 2 +- .../idea/vim/command/CommandBuilder.kt | 31 +++++++++++++------ .../idea/vim/command/MappingState.kt | 12 +++++-- .../idea/vim/common/DigraphSequence.kt | 14 ++++++++- .../idea/vim/state/KeyHandlerState.kt | 10 +++++- 5 files changed, 54 insertions(+), 15 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index e25133ddc2..f57cb485b7 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -49,7 +49,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - public val keyState: KeyHandlerState = KeyHandlerState() + public var keyState: KeyHandlerState = KeyHandlerState() private var handleKeyRecursionCount = 0 diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt index 6a79f2b421..90d34569b2 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/CommandBuilder.kt @@ -17,11 +17,10 @@ import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.Node import com.maddyhome.idea.vim.key.RootNode import org.jetbrains.annotations.TestOnly -import java.util.* import javax.swing.KeyStroke -public class CommandBuilder(private var currentCommandPartNode: CommandPartNode) { - private val commandParts = ArrayDeque() +public class CommandBuilder(private var currentCommandPartNode: CommandPartNode): Cloneable { + private var commandParts = ArrayDeque() private var keyList = mutableListOf() public var commandState: CurrentCommandState = CurrentCommandState.NEW_COMMAND @@ -66,7 +65,7 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode< public fun popCommandPart(): Command { val command = commandParts.removeLast() - expectedArgumentType = if (commandParts.size > 0) commandParts.peekLast().action.argumentType else null + expectedArgumentType = if (commandParts.size > 0) commandParts.last().action.argumentType else null return command } @@ -107,7 +106,7 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode< public fun isAwaitingCharOrDigraphArgument(): Boolean { if (commandParts.size == 0) return false - val argumentType = commandParts.peekLast().action.argumentType + val argumentType = commandParts.last().action.argumentType val awaiting = argumentType == Argument.Type.CHARACTER || argumentType == Argument.Type.DIGRAPH LOG.debug { "Awaiting char of digraph: $awaiting" } return awaiting @@ -125,7 +124,7 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode< } public fun isPuttingLiteral(): Boolean { - return !commandParts.isEmpty() && commandParts.last.action.id == "VimInsertCompletedLiteralAction" + return !commandParts.isEmpty() && commandParts.last().action.id == "VimInsertCompletedLiteralAction" } public fun isDone(): Boolean { @@ -133,21 +132,21 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode< } public fun completeCommandPart(argument: Argument) { - commandParts.peekLast().argument = argument + commandParts.last().argument = argument commandState = CurrentCommandState.READY } public fun isDuplicateOperatorKeyStroke(key: KeyStroke): Boolean { - val action = commandParts.peekLast().action as? DuplicableOperatorAction + val action = commandParts.last().action as? DuplicableOperatorAction return action?.duplicateWith == key.keyChar } public fun hasCurrentCommandPartArgument(): Boolean { - return commandParts.peek()?.argument != null + return commandParts.firstOrNull()?.argument != null } public fun buildCommand(): Command { - if (commandParts.last.action.id == "VimInsertCompletedDigraphAction" || commandParts.last.action.id == "VimResetModeAction") { + if (commandParts.last().action.id == "VimInsertCompletedDigraphAction" || commandParts.last().action.id == "VimResetModeAction") { expectedArgumentType = prevExpectedArgumentType prevExpectedArgumentType = null return commandParts.removeLast() @@ -219,6 +218,18 @@ public class CommandBuilder(private var currentCommandPartNode: CommandPartNode< return result } + public override fun clone(): CommandBuilder { + val result = CommandBuilder(currentCommandPartNode) + result.commandParts = ArrayDeque(commandParts) + result.keyList = keyList.toMutableList() + result.commandState = commandState + result.count = count + result.expectedArgumentType = expectedArgumentType + result.prevExpectedArgumentType = prevExpectedArgumentType + + return result + } + public companion object { private val LOG = vimLogger() } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt index 7707789708..373a1c29ee 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingState.kt @@ -16,7 +16,7 @@ import java.awt.event.ActionListener import javax.swing.KeyStroke import javax.swing.Timer -public class MappingState { +public class MappingState: Cloneable { // Map command depth. 0 - if it is not a map command. 1 - regular map command. 2+ - nested map commands private var mapDepth = 0 @@ -35,7 +35,7 @@ public class MappingState { public val keys: Iterable get() = keyList - private val timer = VimTimer(injector.globalOptions().timeoutlen) + private var timer = VimTimer(injector.globalOptions().timeoutlen) private var keyList = mutableListOf() init { @@ -92,6 +92,14 @@ public class MappingState { return result } + public override fun clone(): MappingState { + val result = MappingState() + result.timer = timer + result.mapDepth = mapDepth + result.keyList = keyList.toMutableList() + return result + } + public companion object { private val LOG = vimLogger() } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt index 4fe4b95600..c7c0922f0e 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt @@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.diagnostic.vimLogger import java.awt.event.KeyEvent import javax.swing.KeyStroke -public class DigraphSequence { +public class DigraphSequence: Cloneable { private var digraphState = DIG_STATE_PENDING private var digraphChar = 0.toChar() private lateinit var codeChars: CharArray @@ -248,6 +248,18 @@ public class DigraphSequence { return result } + public override fun clone(): DigraphSequence { + val result = DigraphSequence() + result.digraphState = digraphState + result.digraphChar = digraphChar + result.codeChars = codeChars.copyOf() + result.codeCnt = codeCnt + result.codeType = codeType + result.codeMax = codeMax + + return result + } + public companion object { private const val DIG_STATE_PENDING = 1 private const val DIG_STATE_DIG_ONE = 2 diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt index 9f58e406dc..f956eaf1e1 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt @@ -20,7 +20,7 @@ public data class KeyHandlerState( public val mappingState: MappingState, public val digraphSequence: DigraphSequence, public val commandBuilder: CommandBuilder, -) { +): Cloneable { public constructor() : this(MappingState(), DigraphSequence(), CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL))) public fun partialReset(mode: Mode) { @@ -34,4 +34,12 @@ public data class KeyHandlerState( mappingState.resetMappingSequence() commandBuilder.resetAll(injector.keyGroup.getKeyRoot(mode.toMappingMode())) } + + public override fun clone(): KeyHandlerState { + return KeyHandlerState( + mappingState.clone(), + digraphSequence.clone(), + commandBuilder.clone() + ) + } } \ No newline at end of file From 02540eb303ef80e6b789753c67f3078a2e6601dd Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Mon, 5 Feb 2024 00:27:32 +0200 Subject: [PATCH 09/44] Pass KeyHandlerState as a method argument --- .../jetbrains/plugins/ideavim/VimTestCase.kt | 2 +- .../com/maddyhome/idea/vim/KeyHandler.kt | 180 +++++++++++------- .../idea/vim/command/MappingProcessor.kt | 31 +-- .../vim/impl/state/VimStateMachineImpl.kt | 26 ++- .../com/maddyhome/idea/vim/key/MappingInfo.kt | 29 +-- .../idea/vim/key/MappingInfoLayer.kt | 3 +- .../idea/vim/state/VimStateMachine.kt | 6 +- 7 files changed, 169 insertions(+), 108 deletions(-) diff --git a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt index f3e0b9ae82..ebc9a93019 100644 --- a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -118,7 +118,7 @@ abstract class VimTestCase { if (editor != null) { KeyHandler.getInstance().fullReset(editor.vim) } - KeyHandler.getInstance().keyState.reset(Mode.NORMAL()) + KeyHandler.getInstance().keyHandlerState.reset(Mode.NORMAL()) VimPlugin.getOptionGroup().resetAllOptionsForTesting() VimPlugin.getKey().resetKeyMappings() VimPlugin.getSearch().resetState() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index f57cb485b7..e3b92f4817 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -49,7 +49,8 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - public var keyState: KeyHandlerState = KeyHandlerState() + public var keyHandlerState: KeyHandlerState = KeyHandlerState() + private set private var handleKeyRecursionCount = 0 @@ -64,8 +65,12 @@ public class KeyHandler { * @param key The keystroke typed by the user * @param context The data context */ - public fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext) { - handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false) + public fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, keyState: KeyHandlerState? = null) { + if (keyState == null) { + handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false) + } else { + handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, keyState) + } } /** @@ -75,6 +80,7 @@ public class KeyHandler { * @param mappingCompleted - if true, we don't check if the mapping is incomplete * * TODO mappingCompleted and recursionCounter - we should find a more beautiful way to use them + * TODO it should not receive editor at all and use the focused one. It will help to execute macro between multiple editors */ public fun handleKey( editor: VimEditor, @@ -82,6 +88,7 @@ public class KeyHandler { context: ExecutionContext, allowKeyMappings: Boolean, mappingCompleted: Boolean, + keyState: KeyHandlerState? = null, ) { LOG.trace { """ @@ -98,9 +105,10 @@ public class KeyHandler { return } + val newState = keyState ?: this.keyHandlerState injector.messages.clearError() val editorState = editor.vimStateMachine - val commandBuilder = editorState.commandBuilder + val commandBuilder = newState.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 @@ -110,37 +118,37 @@ public class KeyHandler { handleKeyRecursionCount++ try { LOG.trace("Start key processing...") - if (!allowKeyMappings || !MappingProcessor.handleKeyMapping(editor, key, context, mappingCompleted)) { + if (!allowKeyMappings || !MappingProcessor.handleKeyMapping(editor, key, newState, context, mappingCompleted)) { LOG.trace("Mappings processed, continue processing key.") - if (isCommandCountKey(chKey, editorState)) { + if (isCommandCountKey(chKey, newState, editorState)) { commandBuilder.addCountCharacter(key) - } else if (isDeleteCommandCountKey(key, editorState)) { + } else if (isDeleteCommandCountKey(key, newState, editorState.mode)) { commandBuilder.deleteCountCharacter() } else if (isEditorReset(key, editorState)) { - handleEditorReset(editor, key, context, editorState) + handleEditorReset(editor, key, newState, context) } else if (isExpectingCharArgument(commandBuilder)) { - handleCharArgument(key, chKey, editorState, editor) + handleCharArgument(key, chKey, newState, editor) } else if (editorState.isRegisterPending) { LOG.trace("Pending mode.") commandBuilder.addKey(key) - handleSelectRegister(editorState, chKey) - } else if (!handleDigraph(editor, key, context, editorState)) { + handleSelectRegister(editorState, chKey, newState) + } else if (!handleDigraph(editor, key, newState, context)) { 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? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState) + val node: Node? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, newState) LOG.trace("Get the node for the current mode") if (node is CommandNode) { LOG.trace("Node is a command node") - handleCommandNode(editor, context, key, node, editorState) + handleCommandNode(editor, context, key, node, newState, editorState) commandBuilder.addKey(key) } else if (node is CommandPartNode) { LOG.trace("Node is a command part node") commandBuilder.setCurrentCommandPartNode(node) commandBuilder.addKey(key) - } else if (isSelectRegister(key, editorState)) { + } else if (isSelectRegister(key, newState, editorState)) { LOG.trace("Select register") editorState.isRegisterPending = true commandBuilder.addKey(key) @@ -162,35 +170,37 @@ public class KeyHandler { LOG.trace("Set command state to bad_command") commandBuilder.commandState = CurrentCommandState.BAD_COMMAND } - partialReset(editor) + partialReset(newState, editorState.mode) } } } - finishedCommandPreparation(editor, context, editorState, commandBuilder, key, shouldRecord) } finally { handleKeyRecursionCount-- } + finishedCommandPreparation(editor, context, editorState, key, shouldRecord, newState) + updateState(newState) } internal fun finishedCommandPreparation( editor: VimEditor, context: ExecutionContext, editorState: VimStateMachine, - commandBuilder: CommandBuilder, key: KeyStroke?, shouldRecord: Boolean, + keyState: KeyHandlerState, ) { // Do we have a fully entered command at this point? If so, let's execute it. + val commandBuilder = keyState.commandBuilder if (commandBuilder.isReady) { LOG.trace("Ready command builder. Execute command.") - executeCommand(editor, context, editorState) + executeCommand(editor, context, editorState, keyState) } else if (commandBuilder.isBad) { LOG.trace("Command builder is set to BAD") editor.resetOpPending() editorState.resetRegisterPending() editor.isReplaceCharacter = false injector.messages.indicateError() - reset(editor) + reset(keyState, editorState.mode) } // Don't record the keystroke that stops the recording (unmapped this is `q`) @@ -211,22 +221,31 @@ public class KeyHandler { private fun mapOpCommand( key: KeyStroke, node: Node?, - editorState: VimStateMachine, + mode: Mode, + keyState: KeyHandlerState, ): Node? { - return if (editorState.isDuplicateOperatorKeyStroke(key, editorState.mode)) { - editorState.commandBuilder.getChildNode(KeyStroke.getKeyStroke('_')) + return if (isDuplicateOperatorKeyStroke(key, mode, keyState)) { + keyState.commandBuilder.getChildNode(KeyStroke.getKeyStroke('_')) } else { node } } + public fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode, keyState: KeyHandlerState): Boolean { + return isOperatorPending(mode, keyState) && keyState.commandBuilder.isDuplicateOperatorKeyStroke(key) + } + + public fun isOperatorPending(mode: Mode, keyState: KeyHandlerState): Boolean { + return mode is Mode.OP_PENDING && !keyState.commandBuilder.isEmpty + } + private fun handleEditorReset( editor: VimEditor, key: KeyStroke, + keyState: KeyHandlerState, context: ExecutionContext, - editorState: VimStateMachine, ) { - val commandBuilder = editorState.commandBuilder + val commandBuilder = keyState.commandBuilder if (commandBuilder.isAwaitingCharOrDigraphArgument()) { editor.isReplaceCharacter = false } @@ -249,12 +268,12 @@ public class KeyHandler { } } } - reset(editor) + reset(keyState, editor.mode) } - private fun isCommandCountKey(chKey: Char, editorState: VimStateMachine): Boolean { + private fun isCommandCountKey(chKey: Char, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean { // Make sure to avoid handling '0' as the start of a count. - val commandBuilder = editorState.commandBuilder + val commandBuilder = keyState.commandBuilder val notRegisterPendingCommand = editorState.mode is Mode.NORMAL && !editorState.isRegisterPending val visualMode = editorState.mode is Mode.VISUAL && !editorState.isRegisterPending val opPendingMode = editorState.mode is Mode.OP_PENDING @@ -269,11 +288,11 @@ public class KeyHandler { return false } - private fun isDeleteCommandCountKey(key: KeyStroke, editorState: VimStateMachine): Boolean { + private fun isDeleteCommandCountKey(key: KeyStroke, keyState: KeyHandlerState, mode: Mode): Boolean { // See `:help N` - val commandBuilder = editorState.commandBuilder + val commandBuilder = keyState.commandBuilder val isDeleteCommandKeyCount = - (editorState.mode is Mode.NORMAL || editorState.mode is Mode.VISUAL || editorState.mode is Mode.OP_PENDING) && + (mode is Mode.NORMAL || mode is Mode.VISUAL || mode is Mode.OP_PENDING) && commandBuilder.isExpectingCount && commandBuilder.count > 0 && key.keyCode == KeyEvent.VK_DELETE LOG.debug { "This is a delete command key count: $isDeleteCommandKeyCount" } @@ -286,26 +305,26 @@ public class KeyHandler { return editorReset } - private fun isSelectRegister(key: KeyStroke, editorState: VimStateMachine): Boolean { + private fun isSelectRegister(key: KeyStroke, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean { if (editorState.mode !is Mode.NORMAL && editorState.mode !is Mode.VISUAL) { return false } return if (editorState.isRegisterPending) { true } else { - key.keyChar == '"' && !editorState.isOperatorPending(editorState.mode) && editorState.commandBuilder.expectedArgumentType == null + key.keyChar == '"' && !isOperatorPending(editorState.mode, keyState) && keyState.commandBuilder.expectedArgumentType == null } } - private fun handleSelectRegister(vimStateMachine: VimStateMachine, chKey: Char) { + private fun handleSelectRegister(vimStateMachine: VimStateMachine, chKey: Char, keyState: KeyHandlerState) { LOG.trace("Handle select register") vimStateMachine.resetRegisterPending() if (injector.registerGroup.isValid(chKey)) { LOG.trace("Valid register") - vimStateMachine.commandBuilder.pushCommandPart(chKey) + keyState.commandBuilder.pushCommandPart(chKey) } else { LOG.trace("Invalid register, set command state to BAD_COMMAND") - vimStateMachine.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND + keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND } } @@ -315,7 +334,7 @@ public class KeyHandler { return expectingCharArgument } - private fun handleCharArgument(key: KeyStroke, chKey: Char, vimStateMachine: VimStateMachine, editor: VimEditor) { + private fun handleCharArgument(key: KeyStroke, chKey: Char, keyState: KeyHandlerState, editor: VimEditor) { var mutableChKey = chKey LOG.trace("Handling char argument") // We are expecting a character argument - is this a regular character the user typed? @@ -326,7 +345,7 @@ public class KeyHandler { KeyEvent.VK_ENTER -> mutableChKey = '\n' } } - val commandBuilder = vimStateMachine.commandBuilder + val commandBuilder = keyState.commandBuilder if (mutableChKey.code != 0) { LOG.trace("Add character argument to the current command") // Create the character argument, add it to the current command, and signal we are ready to process the command @@ -342,29 +361,30 @@ public class KeyHandler { private fun handleDigraph( editor: VimEditor, key: KeyStroke, + keyState: KeyHandlerState, context: ExecutionContext, - editorState: VimStateMachine, ): 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 commandBuilder = editorState.commandBuilder + val commandBuilder = keyState.commandBuilder + val digraphSequence = keyState.digraphSequence if (commandBuilder.expectedArgumentType == Argument.Type.DIGRAPH) { LOG.trace("Expected argument is digraph") - if (editorState.digraphSequence.isDigraphStart(key)) { - editorState.startDigraphSequence() - editorState.commandBuilder.addKey(key) + if (digraphSequence.isDigraphStart(key)) { + digraphSequence.startDigraphSequence() + commandBuilder.addKey(key) return true } - if (editorState.digraphSequence.isLiteralStart(key)) { - editorState.startLiteralSequence() - editorState.commandBuilder.addKey(key) + if (digraphSequence.isLiteralStart(key)) { + digraphSequence.startLiteralSequence() + commandBuilder.addKey(key) return true } } - val res = editorState.processDigraphKey(key, editor) + val res = digraphSequence.processKey(key, editor) if (injector.exEntryPanel.isActive()) { when (res.result) { DigraphResult.RES_HANDLED -> setPromptCharacterEx(if (commandBuilder.isPuttingLiteral()) '^' else key.keyChar) @@ -377,7 +397,7 @@ public class KeyHandler { } when (res.result) { DigraphResult.RES_HANDLED -> { - editorState.commandBuilder.addKey(key) + commandBuilder.addKey(key) return true } DigraphResult.RES_DONE -> { @@ -385,8 +405,8 @@ public class KeyHandler { commandBuilder.fallbackToCharacterArgument() } val stroke = res.stroke ?: return false - editorState.commandBuilder.addKey(key) - handleKey(editor, stroke, context) + commandBuilder.addKey(key) + handleKey(editor, stroke, context, keyState) return true } DigraphResult.RES_BAD -> { @@ -401,7 +421,7 @@ public class KeyHandler { // state. E.g. waiting for {char} {char}. Let the key handler have a go at it. if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) { commandBuilder.fallbackToCharacterArgument() - handleKey(editor, key, context) + handleKey(editor, key, context, keyState) return true } return false @@ -414,9 +434,10 @@ public class KeyHandler { editor: VimEditor, context: ExecutionContext, editorState: VimStateMachine, + keyState: KeyHandlerState, ) { LOG.trace("Command execution") - val command = editorState.commandBuilder.buildCommand() + val command = keyState.commandBuilder.buildCommand() val operatorArguments = OperatorArguments( editor.mode is Mode.OP_PENDING, command.rawCount, @@ -432,13 +453,13 @@ public class KeyHandler { if (type.isWrite) { if (!editor.isWritable()) { injector.messages.indicateError() - reset(editor) + reset(keyState, editorState.mode) LOG.warn("File is not writable") return } } if (injector.application.isMainThread()) { - val action: Runnable = ActionRunner(editor, context, command, operatorArguments) + val action: Runnable = ActionRunner(editor, context, command, keyState, operatorArguments) val cmdAction = command.action val name = cmdAction.id if (type.isWrite) { @@ -456,12 +477,13 @@ public class KeyHandler { context: ExecutionContext, key: KeyStroke, node: CommandNode, + keyState: KeyHandlerState, editorState: VimStateMachine, ) { 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 commandBuilder = editorState.commandBuilder + val commandBuilder = keyState.commandBuilder val expectedArgumentType = commandBuilder.expectedArgumentType commandBuilder.pushCommandPart(action) if (!checkArgumentCompatibility(expectedArgumentType, action)) { @@ -469,14 +491,14 @@ public class KeyHandler { commandBuilder.commandState = CurrentCommandState.BAD_COMMAND return } - if (action.argumentType == null || stopMacroRecord(node, editorState)) { + 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!!, editorState) - partialReset(editor) + startWaitingForArgument(editor, context, key.keyChar, action, argumentType!!, keyState, editorState) + partialReset(keyState, editorState.mode) } // TODO In the name of God, get rid of EX_STRING, FLAG_COMPLETE_EX and all the related staff @@ -506,7 +528,7 @@ public class KeyHandler { } } - private fun stopMacroRecord(node: CommandNode, editorState: VimStateMachine): Boolean { + private fun stopMacroRecord(node: CommandNode): Boolean { // TODO // return editorState.isRecording && node.actionHolder.getInstance() is ToggleRecordingAction return injector.registerGroup.isRecording && node.actionHolder.instance.id == "VimToggleRecordingAction" @@ -518,9 +540,10 @@ public class KeyHandler { key: Char, action: EditorActionHandlerBase, argument: Argument.Type, + keyState: KeyHandlerState, editorState: VimStateMachine, ) { - val commandBuilder = editorState.commandBuilder + val commandBuilder = keyState.commandBuilder when (argument) { Argument.Type.MOTION -> { if (editorState.isDotRepeatInProgress && argumentCaptured != null) { @@ -537,10 +560,10 @@ public class KeyHandler { // TODO // if (action is InsertCompletedDigraphAction) { if (action.id == "VimInsertCompletedDigraphAction") { - editorState.startDigraphSequence() + keyState.digraphSequence.startDigraphSequence() setPromptCharacterEx('?') } else if (action.id == "VimInsertCompletedLiteralAction") { - editorState.startLiteralSequence() + keyState.digraphSequence.startLiteralSequence() setPromptCharacterEx('^') } @@ -579,9 +602,13 @@ public class KeyHandler { * @param editor The editor to reset. */ public fun partialReset(editor: VimEditor) { - val editorState = VimStateMachine.getInstance(editor) - editorState.mappingState.resetMappingSequence() - editorState.commandBuilder.resetInProgressCommandPart(getKeyRoot(editor.mode.toMappingMode())) + partialReset(keyHandlerState, editor.mode) + } + + // TODO replace with com.maddyhome.idea.vim.state.KeyHandlerState#partialReset + private fun partialReset(keyState: KeyHandlerState, mode: Mode) { + keyState.mappingState.resetMappingSequence() + keyState.commandBuilder.resetInProgressCommandPart(getKeyRoot(mode.toMappingMode())) } /** @@ -589,16 +616,26 @@ public class KeyHandler { * * @param editor The editor to reset. */ + // TODO replace with com.maddyhome.idea.vim.state.KeyHandlerState#reset public fun reset(editor: VimEditor) { - partialReset(editor) - val editorState = VimStateMachine.getInstance(editor) - editorState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode())) + partialReset(keyHandlerState, editor.mode) + keyHandlerState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode())) + } + + // TODO replace with com.maddyhome.idea.vim.state.KeyHandlerState#reset + public fun reset(keyState: KeyHandlerState, mode: Mode) { + partialReset(keyState, mode) + keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode())) } private fun getKeyRoot(mappingMode: MappingMode): CommandPartNode { return injector.keyGroup.getKeyRoot(mappingMode) } + private fun updateState(keyState: KeyHandlerState) { + this.keyHandlerState = keyState + } + /** * Completely resets the state of this handler. Resets the command mode to normal, resets, and clears the selected * register. @@ -608,7 +645,7 @@ public class KeyHandler { public fun fullReset(editor: VimEditor) { injector.messages.clearError() editor.resetState() - reset(editor) + reset(keyHandlerState, editor.mode) injector.registerGroupIfCreated?.resetRegister() editor.removeSelection() } @@ -627,11 +664,12 @@ public class KeyHandler { val editor: VimEditor, val context: ExecutionContext, val cmd: Command, + val keyState: KeyHandlerState, val operatorArguments: OperatorArguments, ) : Runnable { override fun run() { val editorState = VimStateMachine.getInstance(editor) - editorState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND + keyState.commandBuilder.commandState = CurrentCommandState.NEW_COMMAND val register = cmd.register if (register != null) { injector.registerGroup.selectRegister(register) @@ -664,8 +702,8 @@ public class KeyHandler { } } } - if (editorState.commandBuilder.isDone()) { - getInstance().reset(editor) + if (keyState.commandBuilder.isDone()) { + getInstance().reset(keyState, editorState.mode) } } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt index 516e2dc93f..722476ae45 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt @@ -19,6 +19,7 @@ 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.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import javax.swing.KeyStroke @@ -29,16 +30,17 @@ public object MappingProcessor { internal fun handleKeyMapping( editor: VimEditor, key: KeyStroke, + keyState: KeyHandlerState, context: ExecutionContext, mappingCompleted: Boolean, ): Boolean { log.debug("Start processing key mappings.") val commandState = editor.vimStateMachine - val mappingState = commandState.mappingState - val commandBuilder = commandState.commandBuilder + val mappingState = keyState.mappingState + val commandBuilder = keyState.commandBuilder if (commandBuilder.isAwaitingCharOrDigraphArgument() || commandBuilder.isBuildingMultiKeyCommand() || - isMappingDisabledForKey(key, commandState) || + isMappingDisabledForKey(key, keyState) || commandState.isRegisterPending ) { log.debug("Finish key processing, returning false") @@ -56,25 +58,26 @@ 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, mappingState, mapping, mappingCompleted) || - handleCompleteMappingSequence(editor, context, mappingState, mapping, key) || - handleAbandonedMappingSequence(editor, mappingState, context) + handleUnfinishedMappingSequence(editor, keyState, mappingState, mapping, mappingCompleted) || + handleCompleteMappingSequence(editor, keyState, context, mappingState, mapping, key) || + handleAbandonedMappingSequence(editor, keyState, mappingState, context) log.debug { "Finish mapping processing. Return $mappingProcessed" } return mappingProcessed } - private fun isMappingDisabledForKey(key: KeyStroke, vimStateMachine: VimStateMachine): Boolean { + private fun isMappingDisabledForKey(key: KeyStroke, keyState: KeyHandlerState): Boolean { // "0" can be mapped, but the mapping isn't applied when entering a count. Other digits are always mapped, even when // entering a count. // See `:help :map-modes` - val isMappingDisabled = key.keyChar == '0' && vimStateMachine.commandBuilder.count > 0 + val isMappingDisabled = key.keyChar == '0' && keyState.commandBuilder.count > 0 log.debug { "Mapping disabled for key: $isMappingDisabled" } return isMappingDisabled } private fun handleUnfinishedMappingSequence( editor: VimEditor, + keyState: KeyHandlerState, mappingState: MappingState, mapping: KeyMappingLayer, mappingCompleted: Boolean, @@ -130,6 +133,7 @@ public object MappingProcessor { injector.executionContextManager.onEditor(editor), allowKeyMappings = true, mappingCompleted = lastKeyInSequence, + keyState, ) } }, @@ -143,6 +147,7 @@ public object MappingProcessor { private fun handleCompleteMappingSequence( editor: VimEditor, + keyState: KeyHandlerState, context: ExecutionContext, mappingState: MappingState, mapping: KeyMappingLayer, @@ -180,7 +185,7 @@ public object MappingProcessor { log.trace("Executing mapping info") try { mappingState.startMapExecution() - mappingInfo.execute(editor, context) + mappingInfo.execute(editor, context, keyState) } catch (e: Exception) { injector.messages.showStatusBarMessage(editor, e.message) injector.messages.indicateError() @@ -208,7 +213,7 @@ public object MappingProcessor { // If we've just evaluated the previous key sequence, make sure to also handle the current key if (mappingInfo !== currentMappingInfo) { log.trace("Evaluating the current key") - KeyHandler.getInstance().handleKey(editor, key, currentContext, allowKeyMappings = true, false) + KeyHandler.getInstance().handleKey(editor, key, currentContext, allowKeyMappings = true, false, keyState) } log.trace("Success processing of mapping") return true @@ -216,6 +221,7 @@ public object MappingProcessor { private fun handleAbandonedMappingSequence( editor: VimEditor, + keyState: KeyHandlerState, mappingState: MappingState, context: ExecutionContext, ): Boolean { @@ -246,14 +252,15 @@ public object MappingProcessor { context, allowKeyMappings = true, mappingCompleted = false, + keyState, ) } else { log.trace("Process abandoned keys.") KeyHandler.getInstance() - .handleKey(editor, unhandledKeyStrokes[0], context, allowKeyMappings = false, mappingCompleted = false) + .handleKey(editor, unhandledKeyStrokes[0], context, allowKeyMappings = false, mappingCompleted = false, keyState) for (keyStroke in unhandledKeyStrokes.subList(1, unhandledKeyStrokes.size)) { KeyHandler.getInstance() - .handleKey(editor, keyStroke, context, allowKeyMappings = true, mappingCompleted = false) + .handleKey(editor, keyStroke, context, allowKeyMappings = true, mappingCompleted = false, keyState) } } log.trace("Return true from abandoned keys processing.") diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt index 9f0bc19da7..4579909bfd 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/impl/state/VimStateMachineImpl.kt @@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.command.MappingState import com.maddyhome.idea.vim.common.DigraphResult import com.maddyhome.idea.vim.common.DigraphSequence import com.maddyhome.idea.vim.helper.noneOfEnum +import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode import org.jetbrains.annotations.Contract @@ -27,10 +28,14 @@ import javax.swing.KeyStroke * Used to maintain state before and while entering a Vim command (operator, motion, text object, etc.) */ public class VimStateMachineImpl : VimStateMachine { - override val commandBuilder: CommandBuilder = KeyHandler.getInstance().keyState.commandBuilder + @Deprecated("Please use KeyHandlerState instead") + override val commandBuilder: CommandBuilder = KeyHandler.getInstance().keyHandlerState.commandBuilder + @Deprecated("Please use KeyHandlerState instead") + override val mappingState: MappingState = KeyHandler.getInstance().keyHandlerState.mappingState + @Deprecated("Please use KeyHandlerState instead") + override val digraphSequence: DigraphSequence = KeyHandler.getInstance().keyHandlerState.digraphSequence + override var mode: Mode = Mode.NORMAL() - override val mappingState: MappingState = KeyHandler.getInstance().keyState.mappingState - override val digraphSequence: DigraphSequence = KeyHandler.getInstance().keyState.digraphSequence override var isDotRepeatInProgress: Boolean = false override var isRegisterPending: Boolean = false override var isReplaceCharacter: Boolean = false @@ -48,11 +53,13 @@ public class VimStateMachineImpl : VimStateMachine { override var executingCommand: Command? = null override fun isOperatorPending(mode: Mode): Boolean { - return mode is Mode.OP_PENDING && !commandBuilder.isEmpty + val keyHandler = KeyHandler.getInstance() + return keyHandler.isOperatorPending(mode, keyHandler.keyHandlerState) } override fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode): Boolean { - return isOperatorPending(mode) && commandBuilder.isDuplicateOperatorKeyStroke(key) + val keyHandler = KeyHandler.getInstance() + return keyHandler.isDuplicateOperatorKeyStroke(key, mode, keyHandler.keyHandlerState) } override val executingCommandFlags: EnumSet @@ -66,15 +73,18 @@ public class VimStateMachineImpl : VimStateMachine { } override fun startDigraphSequence() { - digraphSequence.startDigraphSequence() + val keyHandler = KeyHandler.getInstance() + keyHandler.keyHandlerState.digraphSequence.startDigraphSequence() } override fun startLiteralSequence() { - digraphSequence.startLiteralSequence() + val keyHandler = KeyHandler.getInstance() + keyHandler.keyHandlerState.digraphSequence.startLiteralSequence() } override fun processDigraphKey(key: KeyStroke, editor: VimEditor): DigraphResult { - return digraphSequence.processKey(key, editor) + val keyHandler = KeyHandler.getInstance() + return keyHandler.keyHandlerState.digraphSequence.processKey(key, editor) } /** diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt index 5ed39a6ca3..b206c42fb2 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt @@ -26,7 +26,7 @@ import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection.Companion.create import com.maddyhome.idea.vim.helper.VimNlsSafe -import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE @@ -49,7 +49,7 @@ public sealed class MappingInfo( @VimNlsSafe abstract override fun getPresentableString(): String - abstract override fun execute(editor: VimEditor, context: ExecutionContext) + abstract override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) override fun compareTo(other: MappingInfo): Int { val size = fromKeys.size @@ -85,7 +85,7 @@ public class ToKeysMappingInfo( ) : MappingInfo(fromKeys, isRecursive, owner) { override fun getPresentableString(): String = injector.parser.toKeyNotation(toKeys) - override fun execute(editor: VimEditor, context: ExecutionContext) { + override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { LOG.debug("Executing 'ToKeys' mapping info...") val editorDataContext = injector.executionContextManager.onEditor(editor, context) val fromIsPrefix = KeyHandler.isPrefix(fromKeys, toKeys) @@ -123,7 +123,7 @@ public class ToExpressionMappingInfo( ) : MappingInfo(fromKeys, isRecursive, owner) { override fun getPresentableString(): String = originalString - override fun execute(editor: VimEditor, context: ExecutionContext) { + override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { LOG.debug("Executing 'ToExpression' mapping info...") val editorDataContext = injector.executionContextManager.onEditor(editor, context) val toKeys = injector.parser.parseKeys(toExpression.evaluate(editor, context, CommandLineVimLContext).toString()) @@ -150,7 +150,7 @@ public class ToHandlerMappingInfo( ) : MappingInfo(fromKeys, isRecursive, owner) { override fun getPresentableString(): String = "call ${extensionHandler.javaClass.canonicalName}" - override fun execute(editor: VimEditor, context: ExecutionContext) { + override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { LOG.debug("Executing 'ToHandler' mapping info...") val vimStateMachine = VimStateMachine.getInstance(editor) @@ -168,24 +168,25 @@ public class ToHandlerMappingInfo( val handler = extensionHandler if (handler is ExtensionHandler.WithCallback) { handler._backingFunction = Runnable { - myFun(shouldCalculateOffsets, editor, startOffsets) + myFun(shouldCalculateOffsets, editor, startOffsets, keyState) if (shouldCalculateOffsets) { injector.application.invokeLater { - KeyHandler.getInstance().finishedCommandPreparation( + val keyHandler = KeyHandler.getInstance() + keyHandler.finishedCommandPreparation( editor, context, VimStateMachine.getInstance(editor), - VimStateMachine.getInstance(editor).commandBuilder, null, false, + keyState ) } } } } - val operatorArguments = OperatorArguments(vimStateMachine.isOperatorPending(editor.mode), vimStateMachine.commandBuilder.count, vimStateMachine.mode) + val operatorArguments = OperatorArguments(vimStateMachine.isOperatorPending(editor.mode), keyState.commandBuilder.count, vimStateMachine.mode) injector.actionExecutor.executeCommand( editor, { extensionHandler.execute(editor, context, operatorArguments) }, @@ -200,7 +201,7 @@ public class ToHandlerMappingInfo( } if (handler !is ExtensionHandler.WithCallback) { - myFun(shouldCalculateOffsets, editor, startOffsets) + myFun(shouldCalculateOffsets, editor, startOffsets, keyState) } } @@ -211,9 +212,9 @@ public class ToHandlerMappingInfo( shouldCalculateOffsets: Boolean, editor: VimEditor, startOffsets: Map, + keyState: KeyHandlerState, ) { - val commandState = editor.vimStateMachine - if (shouldCalculateOffsets && !commandState.commandBuilder.hasCurrentCommandPartArgument()) { + if (shouldCalculateOffsets && !keyState.commandBuilder.hasCurrentCommandPartArgument()) { val offsets: MutableMap = HashMap() for (caret in editor.carets()) { var startOffset = startOffsets[caret] @@ -239,7 +240,7 @@ public class ToHandlerMappingInfo( } } if (offsets.isNotEmpty()) { - commandState.commandBuilder.completeCommandPart(Argument(offsets)) + keyState.commandBuilder.completeCommandPart(Argument(offsets)) } } } @@ -254,7 +255,7 @@ public class ToActionMappingInfo( ) : MappingInfo(fromKeys, isRecursive, owner) { override fun getPresentableString(): String = "action $action" - override fun execute(editor: VimEditor, context: ExecutionContext) { + override fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) { LOG.debug("Executing 'ToAction' mapping...") val editorDataContext = injector.executionContextManager.onEditor(editor, context) val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfoLayer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfoLayer.kt index 8df2c0855b..551d75d1ac 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfoLayer.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfoLayer.kt @@ -10,8 +10,9 @@ package com.maddyhome.idea.vim.key import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.state.KeyHandlerState public interface MappingInfoLayer { public fun getPresentableString(): String - public fun execute(editor: VimEditor, context: ExecutionContext) + public fun execute(editor: VimEditor, context: ExecutionContext, keyState: KeyHandlerState) } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt index c39692ebb9..0e857bb25b 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/VimStateMachine.kt @@ -26,10 +26,14 @@ import javax.swing.KeyStroke * Used to maintain state before and while entering a Vim command (operator, motion, text object, etc.) */ public interface VimStateMachine { + @Deprecated("Please use KeyHandlerState instead") public val commandBuilder: CommandBuilder - public val mode: Mode + @Deprecated("Please use KeyHandlerState instead") public val mappingState: MappingState + @Deprecated("Please use KeyHandlerState instead") public val digraphSequence: DigraphSequence + + public val mode: Mode public var isDotRepeatInProgress: Boolean public var isRegisterPending: Boolean public val isReplaceCharacter: Boolean From 00f5541dc6455b055070aeb36d0a9b8a07284ae6 Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Mon, 5 Feb 2024 12:40:20 +0200 Subject: [PATCH 10/44] Add KeyProcessResult interface --- .../com/maddyhome/idea/vim/KeyHandler.kt | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index e3b92f4817..29ea65feaa 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -632,7 +632,7 @@ public class KeyHandler { return injector.keyGroup.getKeyRoot(mappingMode) } - private fun updateState(keyState: KeyHandlerState) { + public fun updateState(keyState: KeyHandlerState) { this.keyHandlerState = keyState } @@ -729,3 +729,54 @@ public class KeyHandler { public fun getInstance(): KeyHandler = instance } } + +/** + * This class was created to manage Fleet input processing. + * Fleet needs to synchronously determine if the key will be handled by the plugin or should be passed elsewhere. + * The key processing itself will be executed asynchronously at a later time. + */ +public sealed interface KeyProcessResult { + /** + * Key input that is not recognized by IdeaVim and should be passed to IDE. + */ + public object Unknown: KeyProcessResult + + /** + * Key input that is recognized by IdeaVim and can be processed. + * Key handling is a two-step process: + * 1. Determine if the key should be processed and how (is it a command, mapping, or something else). + * 2. Execute the recognized command. + * This class should be returned after the first step is complete. + * It will continue the key handling and finish the process. + */ + public class Processable( + private val originalState: KeyHandlerState, + private val preProcessState: KeyHandlerState, + private val processing: ( + key: KeyStroke, + keyState: KeyHandlerState, + editor: VimEditor, + context: ExecutionContext, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + ) -> KeyHandlerState + ): KeyProcessResult { + + public companion object { + private val logger = vimLogger() + private val lock = Object() + } + + // TODO add concurrency to other places + public fun processKey(key: KeyStroke, editor: VimEditor, context: ExecutionContext, allowKeyMappings: Boolean, mappingCompleted: Boolean) { + synchronized(lock) { + val keyHandler = KeyHandler.getInstance() + if (keyHandler.keyHandlerState != originalState) { + logger.warn("Unexpected editor state. Aborting command execution.") + } + val newState = processing(key, preProcessState, editor, context, allowKeyMappings, mappingCompleted) + keyHandler.updateState(newState) + } + } + } +} From 15ae069f6f61e74a0cf842fa1f0861a197b5c623 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 10:36:35 +0200 Subject: [PATCH 11/44] Make keyHandlerState argument not null Applying default values may lead to unexpected results, especially if we sometimes want to use the global state (IJ), and at other times, its clone for asynchronous processing (Fleet). --- .../com/maddyhome/idea/vim/VimTypedActionHandler.kt | 2 +- .../maddyhome/idea/vim/action/VimShortcutKeyAction.kt | 4 +++- .../maddyhome/idea/vim/extension/VimExtensionFacade.kt | 3 ++- .../java/com/maddyhome/idea/vim/group/MacroGroup.kt | 4 +++- .../com/maddyhome/idea/vim/handler/VimEnterHandler.kt | 3 ++- .../java/com/maddyhome/idea/vim/ui/ex/ExActions.kt | 3 ++- .../java/com/maddyhome/idea/vim/ui/ex/ExEditorKit.kt | 4 +++- .../maddyhome/idea/vim/ui/ex/ExShortcutKeyAction.kt | 4 +++- .../org/jetbrains/plugins/ideavim/VimTestCase.kt | 2 +- .../main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 10 +++------- .../change/insert/InsertCompletedDigraphAction.kt | 3 ++- .../change/insert/InsertCompletedLiteralAction.kt | 3 ++- .../kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt | 4 ++-- .../idea/vim/vimscript/model/commands/NormalCommand.kt | 2 +- 14 files changed, 30 insertions(+), 21 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/VimTypedActionHandler.kt b/src/main/java/com/maddyhome/idea/vim/VimTypedActionHandler.kt index d030f5e857..38b306f702 100644 --- a/src/main/java/com/maddyhome/idea/vim/VimTypedActionHandler.kt +++ b/src/main/java/com/maddyhome/idea/vim/VimTypedActionHandler.kt @@ -77,7 +77,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0 val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers) val startTime = if (traceTime) System.currentTimeMillis() else null - handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim)) + handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim), handler.keyHandlerState) if (startTime != null) { val duration = System.currentTimeMillis() - startTime LOG.info("VimTypedAction '$charTyped': $duration ms") diff --git a/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt b/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt index 1ff5af5f1c..779d8a2df3 100644 --- a/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt +++ b/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt @@ -79,10 +79,12 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible // Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler? try { val start = if (traceTime) System.currentTimeMillis() else null - KeyHandler.getInstance().handleKey( + val keyHandler = KeyHandler.getInstance() + keyHandler.handleKey( editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim), + keyHandler.keyHandlerState, ) if (start != null) { val duration = System.currentTimeMillis() - start diff --git a/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt b/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt index aacb3b6a06..fae03900ba 100644 --- a/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt +++ b/src/main/java/com/maddyhome/idea/vim/extension/VimExtensionFacade.kt @@ -136,7 +136,8 @@ public object VimExtensionFacade { @JvmStatic public fun executeNormalWithoutMapping(keys: List, editor: Editor) { val context = injector.executionContextManager.onEditor(editor.vim) - keys.forEach { KeyHandler.getInstance().handleKey(editor.vim, it, context, false, false) } + val keyHandler = KeyHandler.getInstance() + keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) } } /** Returns a single key stroke from the user input similar to 'getchar()'. */ diff --git a/src/main/java/com/maddyhome/idea/vim/group/MacroGroup.kt b/src/main/java/com/maddyhome/idea/vim/group/MacroGroup.kt index abc9ca52da..fff1d7cca1 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/MacroGroup.kt +++ b/src/main/java/com/maddyhome/idea/vim/group/MacroGroup.kt @@ -14,6 +14,7 @@ import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressManager import com.intellij.openapi.progress.util.PotemkinProgress +import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.VimEditor @@ -75,11 +76,12 @@ internal class MacroGroup : VimMacroBase() { } catch (e: ProcessCanceledException) { return@runnable } + val keyHandler = KeyHandler.getInstance() ProgressManager.getInstance().executeNonCancelableSection { // Prevent autocompletion during macros. // See https://github.com/JetBrains/ideavim/pull/772 for details CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion) - getInstance().handleKey(editor, key, context) + keyHandler.handleKey(editor, key, context, keyHandler.keyHandlerState) } if (injector.messages.isError()) return@runnable } diff --git a/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt b/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt index ac76396d01..96c7a051c7 100644 --- a/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt +++ b/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt @@ -340,7 +340,8 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) { val enterKey = key(key) val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim) - KeyHandler.getInstance().handleKey(editor.vim, enterKey, context) + val keyHandler = KeyHandler.getInstance() + keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState) } override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean { diff --git a/src/main/java/com/maddyhome/idea/vim/ui/ex/ExActions.kt b/src/main/java/com/maddyhome/idea/vim/ui/ex/ExActions.kt index b9717fddc4..b8f879df83 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/ex/ExActions.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/ex/ExActions.kt @@ -114,7 +114,8 @@ internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) { // write action // * The key handler routines get the chance to clean up and reset state val entry = ExEntryPanel.getInstance().entry - KeyHandler.getInstance().handleKey(entry.editor.vim, stroke, entry.context.vim) + val keyHandler = KeyHandler.getInstance() + keyHandler.handleKey(entry.editor.vim, stroke, entry.context.vim, keyHandler.keyHandlerState) } companion object { diff --git a/src/main/java/com/maddyhome/idea/vim/ui/ex/ExEditorKit.kt b/src/main/java/com/maddyhome/idea/vim/ui/ex/ExEditorKit.kt index 9ec29fdea3..de683e6d33 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/ex/ExEditorKit.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/ex/ExEditorKit.kt @@ -125,10 +125,12 @@ internal object ExEditorKit : DefaultEditorKit() { if (target.useHandleKeyFromEx) { val entry = ExEntryPanel.getInstance().entry val editor = entry.editor - KeyHandler.getInstance().handleKey( + val keyHandler = KeyHandler.getInstance() + keyHandler.handleKey( editor.vim, key, injector.executionContextManager.onEditor(editor.vim, entry.context.vim), + keyHandler.keyHandlerState, ) } else { val event = ActionEvent(e.source, e.id, c.toString(), e.getWhen(), e.modifiers) diff --git a/src/main/java/com/maddyhome/idea/vim/ui/ex/ExShortcutKeyAction.kt b/src/main/java/com/maddyhome/idea/vim/ui/ex/ExShortcutKeyAction.kt index 2b3a630bb5..280fac7710 100644 --- a/src/main/java/com/maddyhome/idea/vim/ui/ex/ExShortcutKeyAction.kt +++ b/src/main/java/com/maddyhome/idea/vim/ui/ex/ExShortcutKeyAction.kt @@ -36,10 +36,12 @@ internal class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : Dum val keyStroke = getKeyStroke(e) if (keyStroke != null) { val editor = exEntryPanel.entry.editor - KeyHandler.getInstance().handleKey( + val keyHandler = KeyHandler.getInstance() + keyHandler.handleKey( editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim), + keyHandler.keyHandlerState ) } } diff --git a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt index ebc9a93019..f7446e9ad8 100644 --- a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -817,7 +817,7 @@ abstract class VimTestCase { val inputModel = TestInputModel.getInstance(editor) var key = inputModel.nextKeyStroke() while (key != null) { - keyHandler.handleKey(editor.vim, key, dataContext) + keyHandler.handleKey(editor.vim, key, dataContext, keyHandler.keyHandlerState) key = inputModel.nextKeyStroke() } }, diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 29ea65feaa..0760d62f06 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -65,12 +65,8 @@ public class KeyHandler { * @param key The keystroke typed by the user * @param context The data context */ - public fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, keyState: KeyHandlerState? = null) { - if (keyState == null) { - handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false) - } else { - handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, keyState) - } + public fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, keyState: KeyHandlerState) { + handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, keyState) } /** @@ -88,7 +84,7 @@ public class KeyHandler { context: ExecutionContext, allowKeyMappings: Boolean, mappingCompleted: Boolean, - keyState: KeyHandlerState? = null, + keyState: KeyHandlerState, ) { LOG.trace { """ diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedDigraphAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedDigraphAction.kt index 00164cee85..4fdf18db1e 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedDigraphAction.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedDigraphAction.kt @@ -27,7 +27,8 @@ public class InsertCompletedDigraphAction : VimActionHandler.SingleExecution() { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { // The converted digraph character has been captured as an argument, push it back through key handler val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character) - KeyHandler.getInstance().handleKey(editor, keyStroke, context) + val keyHandler = KeyHandler.getInstance() + keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState) return true } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedLiteralAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedLiteralAction.kt index dd74d80be7..a26f081ac0 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedLiteralAction.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertCompletedLiteralAction.kt @@ -27,7 +27,8 @@ public class InsertCompletedLiteralAction : VimActionHandler.SingleExecution() { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { // The converted literal character has been captured as an argument, push it back through key handler val keyStroke = KeyStroke.getKeyStroke(cmd.argument!!.character) - KeyHandler.getInstance().handleKey(editor, keyStroke, context) + val keyHandler = KeyHandler.getInstance() + keyHandler.handleKey(editor, keyStroke, context, keyHandler.keyHandlerState) return true } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt index b206c42fb2..6ea0480bac 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt @@ -97,7 +97,7 @@ public class ToKeysMappingInfo( while (keyHandler.keyStack.hasStroke()) { val keyStroke = keyHandler.keyStack.feedStroke() val recursive = isRecursive && !(first && fromIsPrefix) - keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false) + keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false, keyState) first = false } } finally { @@ -132,7 +132,7 @@ public class ToExpressionMappingInfo( for (keyStroke in toKeys) { val recursive = isRecursive && !(first && fromIsPrefix) val keyHandler = KeyHandler.getInstance() - keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false) + keyHandler.handleKey(editor, keyStroke, editorDataContext, recursive, false, keyState) first = false } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/NormalCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/NormalCommand.kt index 499a493c32..59bbd711b2 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/NormalCommand.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/vimscript/model/commands/NormalCommand.kt @@ -73,7 +73,7 @@ public data class NormalCommand(val ranges: Ranges, val argument: String) : Comm val keyHandler = KeyHandler.getInstance() keyHandler.reset(editor) for (key in keys) { - keyHandler.handleKey(editor, key, context, useMappings, true) + keyHandler.handleKey(editor, key, context, useMappings, true, keyHandler.keyHandlerState) } // Exit if state leaves as insert or cmd_line From 275c5d28e18a9ceb290545dafe328459bbd4be28 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 10:54:10 +0200 Subject: [PATCH 12/44] Add KeyProcessResultBuilder --- .../com/maddyhome/idea/vim/KeyHandler.kt | 78 ++++++++++++++----- 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 0760d62f06..7e6448c0ed 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -738,41 +738,81 @@ public sealed interface KeyProcessResult { public object Unknown: KeyProcessResult /** - * Key input that is recognized by IdeaVim and can be processed. + * Key input that is recognized by IdeaVim and can be executed. * Key handling is a two-step process: * 1. Determine if the key should be processed and how (is it a command, mapping, or something else). * 2. Execute the recognized command. * This class should be returned after the first step is complete. * It will continue the key handling and finish the process. */ - public class Processable( + public class Executable( private val originalState: KeyHandlerState, private val preProcessState: KeyHandlerState, - private val processing: ( - key: KeyStroke, - keyState: KeyHandlerState, - editor: VimEditor, - context: ExecutionContext, - allowKeyMappings: Boolean, - mappingCompleted: Boolean, - ) -> KeyHandlerState + private val processing: KeyProcessing, ): KeyProcessResult { public companion object { private val logger = vimLogger() - private val lock = Object() } - // TODO add concurrency to other places - public fun processKey(key: KeyStroke, editor: VimEditor, context: ExecutionContext, allowKeyMappings: Boolean, mappingCompleted: Boolean) { - synchronized(lock) { - val keyHandler = KeyHandler.getInstance() - if (keyHandler.keyHandlerState != originalState) { - logger.warn("Unexpected editor state. Aborting command execution.") + public fun execute(editor: VimEditor, context: ExecutionContext) { + val keyHandler = KeyHandler.getInstance() + if (keyHandler.keyHandlerState != originalState) { + logger.warn("Unexpected editor state. Aborting command execution.") + } + processing(preProcessState, editor, context) + keyHandler.updateState(preProcessState) + } + } + + public abstract class KeyProcessResultBuilder { + public abstract val state: KeyHandlerState + protected val processings: MutableList = mutableListOf() + public var onFinish: (() -> Unit)? = null // FIXME I'm a dirty hack to support recursion counter + + public fun addExecutionStep(keyProcessing: KeyProcessing) { + processings.add(keyProcessing) + } + + public abstract fun build(): KeyProcessResult + } + + // Works with existing state and modifies it during execution + // It's the way IdeaVim worked for the long time and for this class we do not create + // unnecessary objects and assume that the code will be executed immediately + public class SynchronousKeyProcessBuilder(public override val state: KeyHandlerState): KeyProcessResultBuilder() { + public override fun build(): KeyProcessResult { + return Executable(state, state) { keyHandlerState, vimEditor, executionContext -> + try { + for (processing in processings) { + processing(keyHandlerState, vimEditor, executionContext) + } + } finally { + onFinish?.let { it() } + } + } + } + } + + // Created new state, nothing is modified during the builder work (key processing) + // The new state will be applied later, when we run KeyProcess (it may not be run at all) + public class AsyncKeyProcessBuilder(originalState: KeyHandlerState): KeyProcessResultBuilder() { + private val originalState: KeyHandlerState = KeyHandler.getInstance().keyHandlerState + public override val state: KeyHandlerState = originalState.clone() + + public override fun build(): KeyProcessResult { + return Executable(originalState, state) { keyHandlerState, vimEditor, executionContext -> + try { + for (processing in processings) { + processing(keyHandlerState, vimEditor, executionContext) + } + } finally { + onFinish?.let { it() } + KeyHandler.getInstance().updateState(state) } - val newState = processing(key, preProcessState, editor, context, allowKeyMappings, mappingCompleted) - keyHandler.updateState(newState) } } } } + +public typealias KeyProcessing = (KeyHandlerState, VimEditor, ExecutionContext) -> Unit From 19fa00837c68f33c36a525f10dd8ef082f04156d Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 13:02:24 +0200 Subject: [PATCH 13/44] Use KeyProcessResultBuilder It will help us to build the KeyProcessResult that we need for asynchronous key processing --- .../maddyhome/idea/vim/group/ProcessGroup.kt | 16 +- .../com/maddyhome/idea/vim/KeyHandler.kt | 245 ++++++++++++------ .../maddyhome/idea/vim/api/VimChangeGroup.kt | 5 +- .../idea/vim/api/VimChangeGroupBase.kt | 21 +- .../maddyhome/idea/vim/api/VimProcessGroup.kt | 3 +- .../idea/vim/api/stubs/VimProcessGroupStub.kt | 3 +- .../idea/vim/command/MappingProcessor.kt | 70 +++-- 7 files changed, 236 insertions(+), 127 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt b/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt index 07e3788ca8..e45bb0adc4 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt +++ b/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt @@ -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 } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 7e6448c0ed..9cf87a6386 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -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? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, newState) + val node: Node? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, processBuilder.state) LOG.trace("Get the node for the current mode") if (node is CommandNode) { 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) { 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} {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, - 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) - } - - // 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 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 - 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() + 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) + } + } + + 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 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 + 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 + lambdaEditor.mode = lambdaEditor.mode.returnTo() + } } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt index 6e097661cd..1e102d28f5 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt @@ -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 diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt index 20c960802b..7ea3e8a24a 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt @@ -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 diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimProcessGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimProcessGroup.kt index 4f02e8c982..954619b3c5 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimProcessGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimProcessGroup.kt @@ -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 diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimProcessGroupStub.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimProcessGroupStub.kt index baebd1ac3d..9af96ff866 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimProcessGroupStub.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/stubs/VimProcessGroupStub.kt @@ -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") } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt index 722476ae45..2095c05fba 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt @@ -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() 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, 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 mappings are not executed if they fail to map to something. From f454d60234dddd158f61110f45dff9801a420d0c Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 13:36:47 +0200 Subject: [PATCH 14/44] Add MutableBoolean to be able to pass and modify shouldRecord in methods --- .../kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 9cf87a6386..d2f8c40de9 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -124,7 +124,7 @@ public class KeyHandler { 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 + val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording) var isProcessed = false handleKeyRecursionCount++ try { @@ -178,15 +178,15 @@ 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, key, processBuilder) && shouldRecord + shouldRecord.value = injector.changeGroup.processKey(editor, key, processBuilder) && shouldRecord.value isProcessed = true } else if (editorState.mode is Mode.SELECT) { LOG.trace("Process select") - shouldRecord = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord + shouldRecord.value = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord.value isProcessed = true } else if (editor.mode is Mode.CMD_LINE) { LOG.trace("Process cmd line") - shouldRecord = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord + shouldRecord.value = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord.value isProcessed = true } else { LOG.trace("Set command state to bad_command") @@ -222,7 +222,7 @@ public class KeyHandler { editor: VimEditor, context: ExecutionContext, key: KeyStroke?, - shouldRecord: Boolean, + shouldRecord: MutableBoolean, keyState: KeyHandlerState, ) { // Do we have a fully entered command at this point? If so, let's execute it. @@ -241,7 +241,7 @@ public class KeyHandler { } // Don't record the keystroke that stops the recording (unmapped this is `q`) - if (shouldRecord && injector.registerGroup.isRecording && key != null) { + if (shouldRecord.value && injector.registerGroup.isRecording && key != null) { injector.registerGroup.recordKeyStroke(key) modalEntryKeys.forEach { injector.registerGroup.recordKeyStroke(it) } modalEntryKeys.clear() @@ -805,6 +805,8 @@ public class KeyHandler { @JvmStatic public fun getInstance(): KeyHandler = instance } + + public data class MutableBoolean(public var value: Boolean) } /** From e3ec9c614b50ce6c2f9797c4c87bd505b7207cb8 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 13:37:35 +0200 Subject: [PATCH 15/44] Add KeyConsumer It will help us to have a more modular KeyHandler in future (chain of different consumers) --- .../com/maddyhome/idea/vim/key/KeyConsumer.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt new file mode 100644 index 0000000000..200a458686 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt @@ -0,0 +1,25 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import javax.swing.KeyStroke + +public interface KeyConsumer { + public fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean +} \ No newline at end of file From 0ab32cac3439d946b182c602ca4ad835ea310259 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 13:39:17 +0200 Subject: [PATCH 16/44] Make MappingProcessor a KeyConsumer --- .../src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 2 +- .../com/maddyhome/idea/vim/command/MappingProcessor.kt | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index d2f8c40de9..376c940a35 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -129,7 +129,7 @@ public class KeyHandler { handleKeyRecursionCount++ try { LOG.trace("Start key processing...") - if (!MappingProcessor.handleKeyMapping(key, editor, allowKeyMappings, mappingCompleted, processBuilder)) { + if (!MappingProcessor.consumeKey(key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord)) { LOG.trace("Mappings processed, continue processing key.") if (isCommandCountKey(chKey, processBuilder.state, editorState)) { commandBuilder.addCountCharacter(key) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt index 2095c05fba..86cabd21cb 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/command/MappingProcessor.kt @@ -19,21 +19,23 @@ import com.maddyhome.idea.vim.diagnostic.trace 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.KeyConsumer import com.maddyhome.idea.vim.key.KeyMappingLayer import com.maddyhome.idea.vim.key.MappingInfoLayer import com.maddyhome.idea.vim.state.KeyHandlerState import javax.swing.KeyStroke -public object MappingProcessor { +public object MappingProcessor: KeyConsumer { private val log = vimLogger() - internal fun handleKeyMapping( + public override fun consumeKey( key: KeyStroke, editor: VimEditor, allowKeyMappings: Boolean, mappingCompleted: Boolean, keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, ): Boolean { if (!allowKeyMappings) return false From 43175061e01bdeb7d498e10fe46220958f1f0913 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 13:40:30 +0200 Subject: [PATCH 17/44] Fix broken digraphSequence It shouldn't be retested on partial reset --- .../main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt index f956eaf1e1..2b9b4d136c 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/state/KeyHandlerState.kt @@ -24,7 +24,6 @@ public data class KeyHandlerState( public constructor() : this(MappingState(), DigraphSequence(), CommandBuilder(injector.keyGroup.getKeyRoot(MappingMode.NORMAL))) public fun partialReset(mode: Mode) { - digraphSequence.reset() mappingState.resetMappingSequence() commandBuilder.resetInProgressCommandPart(injector.keyGroup.getKeyRoot(mode.toMappingMode())) } From 9826f0a7f0a99e6f24247e5cdb7bdc6d05602c43 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 13:45:29 +0200 Subject: [PATCH 18/44] Move some logic to CommandCountConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 33 +++-------- .../vim/key/consumers/CommandCountConsumer.kt | 59 +++++++++++++++++++ 2 files changed, 67 insertions(+), 25 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandCountConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 376c940a35..6720121794 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -32,8 +32,10 @@ import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.impl.state.toMappingMode import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandPartNode +import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.Node +import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode @@ -49,6 +51,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -125,16 +128,15 @@ public class KeyHandler { // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording) - var isProcessed = false handleKeyRecursionCount++ try { LOG.trace("Start key processing...") - if (!MappingProcessor.consumeKey(key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord)) { + var isProcessed = keyConsumers.any { + it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) + } + if (!isProcessed) { LOG.trace("Mappings processed, continue processing key.") - if (isCommandCountKey(chKey, processBuilder.state, editorState)) { - commandBuilder.addCountCharacter(key) - isProcessed = true - } else if (isDeleteCommandCountKey(key, processBuilder.state, editorState.mode)) { + if (isDeleteCommandCountKey(key, processBuilder.state, editorState.mode)) { commandBuilder.deleteCountCharacter() isProcessed = true } else if (isEditorReset(key, editorState)) { @@ -197,8 +199,6 @@ public class KeyHandler { } else { isProcessed = true } - } else { - isProcessed = true } if (isProcessed) { processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> @@ -323,23 +323,6 @@ public class KeyHandler { reset(keyState, editor.mode) } - private fun isCommandCountKey(chKey: Char, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean { - // Make sure to avoid handling '0' as the start of a count. - val commandBuilder = keyState.commandBuilder - val notRegisterPendingCommand = editorState.mode is Mode.NORMAL && !editorState.isRegisterPending - val visualMode = editorState.mode is Mode.VISUAL && !editorState.isRegisterPending - val opPendingMode = editorState.mode is Mode.OP_PENDING - - if (notRegisterPendingCommand || visualMode || opPendingMode) { - if (commandBuilder.isExpectingCount && Character.isDigit(chKey) && (commandBuilder.count > 0 || chKey != '0')) { - LOG.debug("This is a command key count") - return true - } - } - LOG.debug("This is NOT a command key count") - return false - } - private fun isDeleteCommandCountKey(key: KeyStroke, keyState: KeyHandlerState, mode: Mode): Boolean { // See `:help N` val commandBuilder = keyState.commandBuilder diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandCountConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandCountConsumer.kt new file mode 100644 index 0000000000..0b7d798987 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandCountConsumer.kt @@ -0,0 +1,59 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.key.KeyConsumer +import com.maddyhome.idea.vim.state.KeyHandlerState +import com.maddyhome.idea.vim.state.mode.Mode +import java.awt.event.KeyEvent +import javax.swing.KeyStroke + +public class CommandCountConsumer : KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar + if (!isCommandCountKey(chKey, keyProcessResultBuilder.state, editor)) return false + + keyProcessResultBuilder.state.commandBuilder.addCountCharacter(key) + return true + } + + private fun isCommandCountKey(chKey: Char, keyState: KeyHandlerState, editor: VimEditor): Boolean { + // Make sure to avoid handling '0' as the start of a count. + val editorState = editor.vimStateMachine + val commandBuilder = keyState.commandBuilder + val notRegisterPendingCommand = editorState.mode is Mode.NORMAL && !editorState.isRegisterPending + val visualMode = editorState.mode is Mode.VISUAL && !editorState.isRegisterPending + val opPendingMode = editorState.mode is Mode.OP_PENDING + + if (notRegisterPendingCommand || visualMode || opPendingMode) { + if (commandBuilder.isExpectingCount && Character.isDigit(chKey) && (commandBuilder.count > 0 || chKey != '0')) { + logger.debug("This is a command key count") + return true + } + } + logger.debug("This is NOT a command key count") + return false + } +} \ No newline at end of file From 46425a24c3d6b868b3aa6151ec1f7056060559fe Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:11:46 +0200 Subject: [PATCH 19/44] Move some logic to DeleteCommandConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 19 ++----- .../key/consumers/DeleteCommandConsumer.kt | 50 +++++++++++++++++++ 2 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DeleteCommandConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 6720121794..e2665d8966 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.Node import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer +import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode @@ -51,7 +52,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -136,10 +137,7 @@ public class KeyHandler { } if (!isProcessed) { LOG.trace("Mappings processed, continue processing key.") - if (isDeleteCommandCountKey(key, processBuilder.state, editorState.mode)) { - commandBuilder.deleteCountCharacter() - isProcessed = true - } else if (isEditorReset(key, editorState)) { + if (isEditorReset(key, editorState)) { processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> handleEditorReset(lambdaEditor, key, lambdaKeyState, lambdaContext) } isProcessed = true } else if (isExpectingCharArgument(commandBuilder)) { @@ -323,17 +321,6 @@ public class KeyHandler { reset(keyState, editor.mode) } - private fun isDeleteCommandCountKey(key: KeyStroke, keyState: KeyHandlerState, mode: Mode): Boolean { - // See `:help N` - val commandBuilder = keyState.commandBuilder - val isDeleteCommandKeyCount = - (mode is Mode.NORMAL || mode is Mode.VISUAL || mode is Mode.OP_PENDING) && - commandBuilder.isExpectingCount && commandBuilder.count > 0 && key.keyCode == KeyEvent.VK_DELETE - - LOG.debug { "This is a delete command key count: $isDeleteCommandKeyCount" } - return isDeleteCommandKeyCount - } - private fun isEditorReset(key: KeyStroke, editorState: VimStateMachine): Boolean { val editorReset = editorState.mode is Mode.NORMAL && key.isCloseKeyStroke() LOG.debug { "This is editor reset: $editorReset" } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DeleteCommandConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DeleteCommandConsumer.kt new file mode 100644 index 0000000000..9da29475c2 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DeleteCommandConsumer.kt @@ -0,0 +1,50 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.diagnostic.debug +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.key.KeyConsumer +import com.maddyhome.idea.vim.state.KeyHandlerState +import com.maddyhome.idea.vim.state.mode.Mode +import java.awt.event.KeyEvent +import javax.swing.KeyStroke + +public class DeleteCommandConsumer : KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + if (!isDeleteCommandCountKey(key, keyProcessResultBuilder.state, editor.mode)) return false + keyProcessResultBuilder.state.commandBuilder.deleteCountCharacter() + return true + } + + private fun isDeleteCommandCountKey(key: KeyStroke, keyState: KeyHandlerState, mode: Mode): Boolean { + // See `:help N` + val commandBuilder = keyState.commandBuilder + val isDeleteCommandKeyCount = + (mode is Mode.NORMAL || mode is Mode.VISUAL || mode is Mode.OP_PENDING) && + commandBuilder.isExpectingCount && commandBuilder.count > 0 && key.keyCode == KeyEvent.VK_DELETE + + logger.debug { "This is a delete command key count: $isDeleteCommandKeyCount" } + return isDeleteCommandKeyCount + } +} \ No newline at end of file From c501457322dc7a645021ba6acede0bc97b8d89d5 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:13:38 +0200 Subject: [PATCH 20/44] Move some logic to EditorResetConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 46 +---------- .../vim/key/consumers/EditorResetConsumer.kt | 80 +++++++++++++++++++ 2 files changed, 83 insertions(+), 43 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/EditorResetConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index e2665d8966..758dfde639 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -37,6 +37,7 @@ import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.Node import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer +import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode @@ -52,7 +53,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -137,10 +138,7 @@ public class KeyHandler { } if (!isProcessed) { LOG.trace("Mappings processed, continue processing key.") - if (isEditorReset(key, editorState)) { - processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> handleEditorReset(lambdaEditor, key, lambdaKeyState, lambdaContext) } - isProcessed = true - } else if (isExpectingCharArgument(commandBuilder)) { + if (isExpectingCharArgument(commandBuilder)) { handleCharArgument(key, chKey, processBuilder.state, editor) isProcessed = true } else if (editorState.isRegisterPending) { @@ -289,44 +287,6 @@ public class KeyHandler { return mode is Mode.OP_PENDING && !keyState.commandBuilder.isEmpty } - private fun handleEditorReset( - editor: VimEditor, - key: KeyStroke, - keyState: KeyHandlerState, - context: ExecutionContext, - ) { - val commandBuilder = keyState.commandBuilder - if (commandBuilder.isAwaitingCharOrDigraphArgument()) { - editor.isReplaceCharacter = false - } - if (commandBuilder.isAtDefaultState) { - val register = injector.registerGroup - if (register.currentRegister == register.defaultRegister) { - var indicateError = true - if (key.keyCode == KeyEvent.VK_ESCAPE) { - val executed = arrayOf(null) - injector.actionExecutor.executeCommand( - editor, - { executed[0] = injector.actionExecutor.executeEsc(context) }, - "", - null, - ) - indicateError = !executed[0]!! - } - if (indicateError) { - injector.messages.indicateError() - } - } - } - reset(keyState, editor.mode) - } - - private fun isEditorReset(key: KeyStroke, editorState: VimStateMachine): Boolean { - val editorReset = editorState.mode is Mode.NORMAL && key.isCloseKeyStroke() - LOG.debug { "This is editor reset: $editorReset" } - return editorReset - } - private fun isSelectRegister(key: KeyStroke, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean { if (editorState.mode !is Mode.NORMAL && editorState.mode !is Mode.VISUAL) { return false diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/EditorResetConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/EditorResetConsumer.kt new file mode 100644 index 0000000000..028f81018d --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/EditorResetConsumer.kt @@ -0,0 +1,80 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +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 +import com.maddyhome.idea.vim.diagnostic.debug +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.helper.isCloseKeyStroke +import com.maddyhome.idea.vim.key.KeyConsumer +import com.maddyhome.idea.vim.state.KeyHandlerState +import com.maddyhome.idea.vim.state.mode.Mode +import java.awt.event.KeyEvent +import javax.swing.KeyStroke + +public class EditorResetConsumer : KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + if (!isEditorReset(key, editor)) return false + keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> handleEditorReset(lambdaEditor, key, lambdaKeyState, lambdaContext) } + return true + } + + private fun isEditorReset(key: KeyStroke, editor: VimEditor): Boolean { + val editorReset = editor.mode is Mode.NORMAL && key.isCloseKeyStroke() + logger.debug { "This is editor reset: $editorReset" } + return editorReset + } + + private fun handleEditorReset( + editor: VimEditor, + key: KeyStroke, + keyState: KeyHandlerState, + context: ExecutionContext, + ) { + val commandBuilder = keyState.commandBuilder + if (commandBuilder.isAwaitingCharOrDigraphArgument()) { + editor.isReplaceCharacter = false + } + if (commandBuilder.isAtDefaultState) { + val register = injector.registerGroup + if (register.currentRegister == register.defaultRegister) { + var indicateError = true + if (key.keyCode == KeyEvent.VK_ESCAPE) { + val executed = arrayOf(null) + injector.actionExecutor.executeCommand( + editor, + { executed[0] = injector.actionExecutor.executeEsc(context) }, + "", + null, + ) + indicateError = !executed[0]!! + } + if (indicateError) { + injector.messages.indicateError() + } + } + } + KeyHandler.getInstance().reset(keyState, editor.mode) + } +} \ No newline at end of file From 6741120f1923e7d668016b0c8ad0b7e9620ede0a Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:14:49 +0200 Subject: [PATCH 21/44] Move some logic to CharArgumentConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 38 +--------- .../vim/key/consumers/CharArgumentConsumer.kt | 75 +++++++++++++++++++ 2 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CharArgumentConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 758dfde639..88ebb523cd 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -35,6 +35,7 @@ import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.Node +import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer @@ -53,7 +54,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -138,10 +139,7 @@ public class KeyHandler { } if (!isProcessed) { LOG.trace("Mappings processed, continue processing key.") - if (isExpectingCharArgument(commandBuilder)) { - handleCharArgument(key, chKey, processBuilder.state, editor) - isProcessed = true - } else if (editorState.isRegisterPending) { + if (editorState.isRegisterPending) { LOG.trace("Pending mode.") commandBuilder.addKey(key) handleSelectRegister(editorState, chKey, processBuilder.state) @@ -310,36 +308,6 @@ public class KeyHandler { } } - private fun isExpectingCharArgument(commandBuilder: CommandBuilder): Boolean { - val expectingCharArgument = commandBuilder.expectedArgumentType === Argument.Type.CHARACTER - LOG.debug { "Expecting char argument: $expectingCharArgument" } - return expectingCharArgument - } - - private fun handleCharArgument(key: KeyStroke, chKey: Char, keyState: KeyHandlerState, editor: VimEditor) { - var mutableChKey = chKey - LOG.trace("Handling char argument") - // We are expecting a character argument - is this a regular character the user typed? - // Some special keys can be handled as character arguments - let's check for them here. - if (mutableChKey.code == 0) { - when (key.keyCode) { - KeyEvent.VK_TAB -> mutableChKey = '\t' - KeyEvent.VK_ENTER -> mutableChKey = '\n' - } - } - val commandBuilder = keyState.commandBuilder - if (mutableChKey.code != 0) { - LOG.trace("Add character argument to the current command") - // Create the character argument, add it to the current command, and signal we are ready to process the command - commandBuilder.completeCommandPart(Argument(mutableChKey)) - } else { - LOG.trace("This is not a valid character argument. Set command state to BAD_COMMAND") - // Oops - this isn't a valid character argument - commandBuilder.commandState = CurrentCommandState.BAD_COMMAND - } - editor.isReplaceCharacter = false - } - private fun handleDigraph( editor: VimEditor, key: KeyStroke, diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CharArgumentConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CharArgumentConsumer.kt new file mode 100644 index 0000000000..0de656bc88 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CharArgumentConsumer.kt @@ -0,0 +1,75 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.command.Argument +import com.maddyhome.idea.vim.command.CommandBuilder +import com.maddyhome.idea.vim.diagnostic.debug +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.key.KeyConsumer +import java.awt.event.KeyEvent +import javax.swing.KeyStroke + +public class CharArgumentConsumer: KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + if (!isExpectingCharArgument(keyProcessResultBuilder.state.commandBuilder)) return false + + val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar + handleCharArgument(key, chKey, keyProcessResultBuilder) + return true + } + + private fun isExpectingCharArgument(commandBuilder: CommandBuilder): Boolean { + val expectingCharArgument = commandBuilder.expectedArgumentType === Argument.Type.CHARACTER + logger.debug { "Expecting char argument: $expectingCharArgument" } + return expectingCharArgument + } + + private fun handleCharArgument(key: KeyStroke, chKey: Char, processBuilder: KeyProcessResult.KeyProcessResultBuilder) { + var mutableChKey = chKey + logger.trace("Handling char argument") + // We are expecting a character argument - is this a regular character the user typed? + // Some special keys can be handled as character arguments - let's check for them here. + if (mutableChKey.code == 0) { + when (key.keyCode) { + KeyEvent.VK_TAB -> mutableChKey = '\t' + KeyEvent.VK_ENTER -> mutableChKey = '\n' + } + } + val commandBuilder = processBuilder.state.commandBuilder + if (mutableChKey.code != 0) { + processBuilder.addExecutionStep { _, lambdaEditor, _ -> + // Create the character argument, add it to the current command, and signal we are ready to process the command + logger.trace("Add character argument to the current command") + commandBuilder.completeCommandPart(Argument(mutableChKey)) + lambdaEditor.isReplaceCharacter = false + } + } else { + logger.trace("This is not a valid character argument. Set command state to BAD_COMMAND") + // Oops - this isn't a valid character argument + processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ -> + KeyHandler.getInstance().setBadCommand(lambdaEditor, lambdaKeyState) + } + } + } +} \ No newline at end of file From 1d9514a20503b64671c1453bd7d4a70fb9da718e Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:16:38 +0200 Subject: [PATCH 22/44] Move some logic to RegisterConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 22 +------ .../vim/key/consumers/RegisterConsumer.kt | 57 +++++++++++++++++++ 2 files changed, 60 insertions(+), 19 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/RegisterConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 88ebb523cd..ca591d423a 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -39,6 +39,7 @@ import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer +import com.maddyhome.idea.vim.key.consumers.RegisterConsumer import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode @@ -54,7 +55,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -139,12 +140,7 @@ public class KeyHandler { } if (!isProcessed) { LOG.trace("Mappings processed, continue processing key.") - if (editorState.isRegisterPending) { - LOG.trace("Pending mode.") - commandBuilder.addKey(key) - handleSelectRegister(editorState, chKey, processBuilder.state) - isProcessed = true - } else if (!handleDigraph(editor, key, processBuilder)) { + 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, @@ -296,18 +292,6 @@ public class KeyHandler { } } - private fun handleSelectRegister(vimStateMachine: VimStateMachine, chKey: Char, keyState: KeyHandlerState) { - LOG.trace("Handle select register") - vimStateMachine.resetRegisterPending() - if (injector.registerGroup.isValid(chKey)) { - LOG.trace("Valid register") - keyState.commandBuilder.pushCommandPart(chKey) - } else { - LOG.trace("Invalid register, set command state to BAD_COMMAND") - keyState.commandBuilder.commandState = CurrentCommandState.BAD_COMMAND - } - } - private fun handleDigraph( editor: VimEditor, key: KeyStroke, diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/RegisterConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/RegisterConsumer.kt new file mode 100644 index 0000000000..8789674061 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/RegisterConsumer.kt @@ -0,0 +1,57 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.key.KeyConsumer +import java.awt.event.KeyEvent +import javax.swing.KeyStroke + +public class RegisterConsumer : KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + if (!editor.vimStateMachine.isRegisterPending) return false + + logger.trace("Pending mode.") + keyProcessResultBuilder.state.commandBuilder.addKey(key) + + val chKey: Char = if (key.keyChar == KeyEvent.CHAR_UNDEFINED) 0.toChar() else key.keyChar + handleSelectRegister(editor, chKey, keyProcessResultBuilder) + return true + } + + private fun handleSelectRegister(editor: VimEditor, chKey: Char, processBuilder: KeyProcessResult.KeyProcessResultBuilder) { + logger.trace("Handle select register") + editor.vimStateMachine.resetRegisterPending() + if (injector.registerGroup.isValid(chKey)) { + logger.trace("Valid register") + processBuilder.state.commandBuilder.pushCommandPart(chKey) + } else { + processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ -> + logger.trace("Invalid register, set command state to BAD_COMMAND") + KeyHandler.getInstance().setBadCommand(lambdaEditor, lambdaKeyState) + } + } + } +} \ No newline at end of file From e033b08535a088bebf42153daab36b97b03619ed Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:19:36 +0200 Subject: [PATCH 23/44] Move some logic to DigraphConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 178 ++++-------------- .../idea/vim/key/consumers/DigraphConsumer.kt | 125 ++++++++++++ 2 files changed, 165 insertions(+), 138 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DigraphConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index ca591d423a..c36003a804 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -38,6 +38,7 @@ import com.maddyhome.idea.vim.key.Node import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer +import com.maddyhome.idea.vim.key.consumers.DigraphConsumer import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer import com.maddyhome.idea.vim.key.consumers.RegisterConsumer import com.maddyhome.idea.vim.state.KeyHandlerState @@ -55,7 +56,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -139,55 +140,48 @@ public class KeyHandler { it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) } if (!isProcessed) { - LOG.trace("Mappings processed, continue processing key.") - 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? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, processBuilder.state) - LOG.trace("Get the node for the current mode") - - if (node is CommandNode) { - LOG.trace("Node is a command node") - handleCommandNode(key, node, processBuilder) - commandBuilder.addKey(key) + // 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? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, processBuilder.state) + LOG.trace("Get the node for the current mode") + + if (node is CommandNode) { + LOG.trace("Node is a command node") + handleCommandNode(key, node, processBuilder) + commandBuilder.addKey(key) + isProcessed = true + } else if (node is CommandPartNode) { + LOG.trace("Node is a command part node") + commandBuilder.setCurrentCommandPartNode(node) + commandBuilder.addKey(key) + 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") + + // 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.value = injector.changeGroup.processKey(editor, key, processBuilder) && shouldRecord.value isProcessed = true - } else if (node is CommandPartNode) { - LOG.trace("Node is a command part node") - commandBuilder.setCurrentCommandPartNode(node) - commandBuilder.addKey(key) + } else if (editorState.mode is Mode.SELECT) { + LOG.trace("Process select") + shouldRecord.value = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord.value isProcessed = true - } else if (isSelectRegister(key, processBuilder.state, editorState)) { - LOG.trace("Select register") - editorState.isRegisterPending = true - commandBuilder.addKey(key) + } else if (editor.mode is Mode.CMD_LINE) { + LOG.trace("Process cmd line") + shouldRecord.value = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord.value isProcessed = true } else { - // node == null - LOG.trace("We are not able to find a node for this key") - - // 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.value = injector.changeGroup.processKey(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else if (editorState.mode is Mode.SELECT) { - LOG.trace("Process select") - shouldRecord.value = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else if (editor.mode is Mode.CMD_LINE) { - LOG.trace("Process cmd line") - shouldRecord.value = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else { - LOG.trace("Set command state to bad_command") - commandBuilder.commandState = CurrentCommandState.BAD_COMMAND - } - partialReset(processBuilder.state, editorState.mode) + LOG.trace("Set command state to bad_command") + commandBuilder.commandState = CurrentCommandState.BAD_COMMAND } - } else { - isProcessed = true + partialReset(processBuilder.state, editorState.mode) } } if (isProcessed) { @@ -292,98 +286,6 @@ public class KeyHandler { } } - private fun handleDigraph( - editor: VimEditor, - key: KeyStroke, - keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, - ): Boolean { - // 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) { - if (digraphSequence.isDigraphStart(key)) { - digraphSequence.startDigraphSequence() - commandBuilder.addKey(key) - return true - } - if (digraphSequence.isLiteralStart(key)) { - digraphSequence.startLiteralSequence() - commandBuilder.addKey(key) - return true - } - } - val res = digraphSequence.processKey(key, editor) - when (res.result) { - 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() - } - } - } - - keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ -> - if (lambdaKeyState.commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) { - lambdaKeyState.commandBuilder.fallbackToCharacterArgument() - } - } - val stroke = res.stroke ?: return false - 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 (lambdaKeyState.commandBuilder.expectedArgumentType != null) { - setBadCommand(lambdaEditor, lambdaKeyState) - } - } - return true - } - DigraphResult.RES_UNHANDLED -> { - // UNHANDLED means the keystroke made no sense in the context of a digraph, but isn't an error in the current - // state. E.g. waiting for {char} {char}. Let the key handler have a go at it. - if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) { - commandBuilder.fallbackToCharacterArgument() - keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> - handleKey(lambdaEditor, key, lambdaContext, lambdaKeyState) - } - return true - } - return false - } - } - return false - } - private fun executeCommand( editor: VimEditor, context: ExecutionContext, @@ -609,7 +511,7 @@ public class KeyHandler { editor.removeSelection() } - private fun setPromptCharacterEx(promptCharacter: Char) { + public fun setPromptCharacterEx(promptCharacter: Char) { val exEntryPanel = injector.exEntryPanel if (exEntryPanel.isActive()) { exEntryPanel.setCurrentActionPromptCharacter(promptCharacter) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DigraphConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DigraphConsumer.kt new file mode 100644 index 0000000000..8aa559df67 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/DigraphConsumer.kt @@ -0,0 +1,125 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.command.Argument +import com.maddyhome.idea.vim.common.DigraphResult +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.key.KeyConsumer +import java.awt.event.InputEvent +import java.awt.event.KeyEvent +import javax.swing.KeyStroke + +public class DigraphConsumer : KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + logger.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) { + logger.trace("Expected argument is digraph") + if (digraphSequence.isDigraphStart(key)) { + digraphSequence.startDigraphSequence() + commandBuilder.addKey(key) + return true + } + if (digraphSequence.isLiteralStart(key)) { + digraphSequence.startLiteralSequence() + commandBuilder.addKey(key) + return true + } + } + val res = digraphSequence.processKey(key, editor) + val keyHandler = KeyHandler.getInstance() + when (res.result) { + DigraphResult.RES_HANDLED -> { + keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ -> + if (injector.exEntryPanel.isActive()) { + keyHandler.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() + } + } + } + + keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ -> + if (lambdaKeyState.commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) { + lambdaKeyState.commandBuilder.fallbackToCharacterArgument() + } + } + val stroke = res.stroke ?: return false + keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditorState, lambdaContext -> + lambdaKeyState.commandBuilder.addKey(key) + keyHandler.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 (lambdaKeyState.commandBuilder.expectedArgumentType != null) { + KeyHandler.getInstance().setBadCommand(lambdaEditor, lambdaKeyState) + } + } + return true + } + DigraphResult.RES_UNHANDLED -> { + // UNHANDLED means the keystroke made no sense in the context of a digraph, but isn't an error in the current + // state. E.g. waiting for {char} {char}. Let the key handler have a go at it. + if (commandBuilder.expectedArgumentType === Argument.Type.DIGRAPH) { + commandBuilder.fallbackToCharacterArgument() + keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> + keyHandler.handleKey(lambdaEditor, key, lambdaContext, lambdaKeyState) + } + return true + } + return false + } + } + return false + } +} \ No newline at end of file From f8011457127572669b68d9ee7702cf2f1ce46d97 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:23:37 +0200 Subject: [PATCH 24/44] Update MappingInfo to match newer signature --- .../main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt index 6ea0480bac..7a0ef575ac 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/MappingInfo.kt @@ -176,10 +176,9 @@ public class ToHandlerMappingInfo( keyHandler.finishedCommandPreparation( editor, context, - VimStateMachine.getInstance(editor), null, - false, - keyState + KeyHandler.MutableBoolean(false), + keyState, ) } } From eea3336934a5c520ee35ae1a5bb277b27b99e4e3 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:27:04 +0200 Subject: [PATCH 25/44] Move some logic to CommandConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 162 +------------ .../idea/vim/key/consumers/CommandConsumer.kt | 217 ++++++++++++++++++ 2 files changed, 218 insertions(+), 161 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index c36003a804..50e0f274e7 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -140,22 +140,7 @@ public class KeyHandler { it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) } if (!isProcessed) { - // 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? = mapOpCommand(key, commandBuilder.getChildNode(key), editorState.mode, processBuilder.state) - LOG.trace("Get the node for the current mode") - - if (node is CommandNode) { - LOG.trace("Node is a command node") - handleCommandNode(key, node, processBuilder) - commandBuilder.addKey(key) - isProcessed = true - } else if (node is CommandPartNode) { - LOG.trace("Node is a command part node") - commandBuilder.setCurrentCommandPartNode(node) - commandBuilder.addKey(key) - isProcessed = true - } else if (isSelectRegister(key, processBuilder.state, editorState)) { + if (isSelectRegister(key, processBuilder.state, editorState)) { LOG.trace("Select register") editorState.isRegisterPending = true commandBuilder.addKey(key) @@ -250,23 +235,6 @@ public class KeyHandler { injector.messages.indicateError() } - - /** - * See the description for [com.maddyhome.idea.vim.command.DuplicableOperatorAction] - */ - private fun mapOpCommand( - key: KeyStroke, - node: Node?, - mode: Mode, - keyState: KeyHandlerState, - ): Node? { - return if (isDuplicateOperatorKeyStroke(key, mode, keyState)) { - keyState.commandBuilder.getChildNode(KeyStroke.getKeyStroke('_')) - } else { - node - } - } - public fun isDuplicateOperatorKeyStroke(key: KeyStroke, mode: Mode, keyState: KeyHandlerState): Boolean { return isOperatorPending(mode, keyState) && keyState.commandBuilder.isDuplicateOperatorKeyStroke(key) } @@ -328,134 +296,6 @@ public class KeyHandler { } } - private fun handleCommandNode( - key: KeyStroke, - node: CommandNode, - 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") - 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 - 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. - * 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 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 - 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 - lambdaEditor.mode = lambdaEditor.mode.returnTo() - } - } - } - - private fun stopMacroRecord(node: CommandNode): Boolean { - // TODO -// return editorState.isRecording && node.actionHolder.getInstance() is ToggleRecordingAction - return injector.registerGroup.isRecording && node.actionHolder.instance.id == "VimToggleRecordingAction" - } - - private fun startWaitingForArgument( - editor: VimEditor, - context: ExecutionContext, - key: Char, - action: EditorActionHandlerBase, - argument: Argument.Type, - keyState: KeyHandlerState, - editorState: VimStateMachine, - ) { - val commandBuilder = keyState.commandBuilder - when (argument) { - Argument.Type.MOTION -> { - if (editorState.isDotRepeatInProgress && argumentCaptured != null) { - commandBuilder.completeCommandPart(argumentCaptured!!) - } - editor.mode = Mode.OP_PENDING(editorState.mode.returnTo) - } - - Argument.Type.DIGRAPH -> // Command actions represent the completion of a command. Showcmd relies on this - if the action represents a - // part of a command, the showcmd output is reset part way through. This means we need to special case entering - // digraph/literal input mode. We have an action that takes a digraph as an argument, and pushes it back through - // the key handler when it's complete. - - // TODO -// if (action is InsertCompletedDigraphAction) { - if (action.id == "VimInsertCompletedDigraphAction") { - keyState.digraphSequence.startDigraphSequence() - setPromptCharacterEx('?') - } else if (action.id == "VimInsertCompletedLiteralAction") { - keyState.digraphSequence.startLiteralSequence() - setPromptCharacterEx('^') - } - - Argument.Type.EX_STRING -> { - // The current Command expects an EX_STRING argument. E.g. SearchEntry(Fwd|Rev)Action. This won't execute until - // state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument. - injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key) - commandBuilder.commandState = CurrentCommandState.NEW_COMMAND - val currentMode = editorState.mode - check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" } - editor.mode = Mode.CMD_LINE(currentMode) - } - - else -> Unit - } - - // Another special case. Force a mode change to update the caret shape - // This was a typed solution - // if (action is ChangeCharacterAction || action is ChangeVisualCharacterAction) - if (action.id == "VimChangeCharacterAction" || action.id == "VimChangeVisualCharacterAction") { - editor.isReplaceCharacter = true - } - } - - private fun checkArgumentCompatibility( - expectedArgumentType: Argument.Type?, - action: EditorActionHandlerBase, - ): Boolean { - return !(expectedArgumentType === Argument.Type.MOTION && action.type !== Command.Type.MOTION) - } - /** * Partially resets the state of this handler. Resets the command count, clears the key list, resets the key tree * node to the root for the current mode we are in. diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandConsumer.kt new file mode 100644 index 0000000000..de919c9b9a --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/CommandConsumer.kt @@ -0,0 +1,217 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.action.change.LazyVimCommand +import com.maddyhome.idea.vim.api.ExecutionContext +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.command.Argument +import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags +import com.maddyhome.idea.vim.common.CurrentCommandState +import com.maddyhome.idea.vim.common.argumentCaptured +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.handler.EditorActionHandlerBase +import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.key.CommandNode +import com.maddyhome.idea.vim.key.CommandPartNode +import com.maddyhome.idea.vim.key.KeyConsumer +import com.maddyhome.idea.vim.key.Node +import com.maddyhome.idea.vim.state.KeyHandlerState +import com.maddyhome.idea.vim.state.VimStateMachine +import com.maddyhome.idea.vim.state.mode.Mode +import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd +import com.maddyhome.idea.vim.state.mode.returnTo +import javax.swing.KeyStroke + +public class CommandConsumer : KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + val commandBuilder = keyProcessResultBuilder.state.commandBuilder + // 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? = mapOpCommand(key, commandBuilder.getChildNode(key), editor.mode, keyProcessResultBuilder.state) + logger.trace("Get the node for the current mode") + + when (node) { + is CommandNode -> { + logger.trace("Node is a command node") + handleCommandNode(key, node, keyProcessResultBuilder) + keyProcessResultBuilder.addExecutionStep { lambdaKeyState, _, _ -> lambdaKeyState.commandBuilder.addKey(key) } + return true + } + + is CommandPartNode -> { + logger.trace("Node is a command part node") + commandBuilder.setCurrentCommandPartNode(node) + commandBuilder.addKey(key) + return true + } + + else -> { + return false + } + } + } + + /** + * See the description for [com.maddyhome.idea.vim.command.DuplicableOperatorAction] + */ + private fun mapOpCommand( + key: KeyStroke, + node: Node?, + mode: Mode, + keyState: KeyHandlerState, + ): Node? { + return if (KeyHandler.getInstance().isDuplicateOperatorKeyStroke(key, mode, keyState)) { + keyState.commandBuilder.getChildNode(KeyStroke.getKeyStroke('_')) + } else { + node + } + } + + private fun handleCommandNode( + key: KeyStroke, + node: CommandNode, + processBuilder: KeyProcessResult.KeyProcessResultBuilder, + ) { + logger.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)) { + logger.trace("Return from command node handling") + processBuilder.addExecutionStep { lamdaKeyState, lambdaEditor, _ -> + KeyHandler.getInstance().setBadCommand(lambdaEditor, lamdaKeyState) + } + return + } + if (action.argumentType == null || stopMacroRecord(node)) { + logger.trace("Set command state to READY") + commandBuilder.commandState = CurrentCommandState.READY + } else { + processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> + logger.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) + } + } + + 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 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 + 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"). + */ + logger.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() + } + } + } + + private fun stopMacroRecord(node: CommandNode): Boolean { + return injector.registerGroup.isRecording && node.actionHolder.instance.id == "VimToggleRecordingAction" + } + + private fun startWaitingForArgument( + editor: VimEditor, + context: ExecutionContext, + key: Char, + action: EditorActionHandlerBase, + argument: Argument.Type, + keyState: KeyHandlerState, + editorState: VimStateMachine, + ) { + val commandBuilder = keyState.commandBuilder + when (argument) { + Argument.Type.MOTION -> { + if (editorState.isDotRepeatInProgress && argumentCaptured != null) { + commandBuilder.completeCommandPart(argumentCaptured!!) + } + editor.mode = Mode.OP_PENDING(editorState.mode.returnTo) + } + + Argument.Type.DIGRAPH -> // Command actions represent the completion of a command. Showcmd relies on this - if the action represents a + // part of a command, the showcmd output is reset part way through. This means we need to special case entering + // digraph/literal input mode. We have an action that takes a digraph as an argument, and pushes it back through + // the key handler when it's complete. + + // TODO +// if (action is InsertCompletedDigraphAction) { + if (action.id == "VimInsertCompletedDigraphAction") { + keyState.digraphSequence.startDigraphSequence() + KeyHandler.getInstance().setPromptCharacterEx('?') + } else if (action.id == "VimInsertCompletedLiteralAction") { + keyState.digraphSequence.startLiteralSequence() + KeyHandler.getInstance().setPromptCharacterEx('^') + } + + Argument.Type.EX_STRING -> { + // The current Command expects an EX_STRING argument. E.g. SearchEntry(Fwd|Rev)Action. This won't execute until + // state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument. + injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key) + commandBuilder.commandState = CurrentCommandState.NEW_COMMAND + val currentMode = editorState.mode + check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" } + editor.mode = Mode.CMD_LINE(currentMode) + } + + else -> Unit + } + + // Another special case. Force a mode change to update the caret shape + // This was a typed solution + // if (action is ChangeCharacterAction || action is ChangeVisualCharacterAction) + if (action.id == "VimChangeCharacterAction" || action.id == "VimChangeVisualCharacterAction") { + editor.isReplaceCharacter = true + } + } + + private fun checkArgumentCompatibility( + expectedArgumentType: Argument.Type?, + action: EditorActionHandlerBase, + ): Boolean { + return !(expectedArgumentType === Argument.Type.MOTION && action.type !== Command.Type.MOTION) + } +} \ No newline at end of file From ddb1b80463067b7d995b06d8ac7e980944ec99c1 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:27:45 +0200 Subject: [PATCH 26/44] Move some logic to CommandConsumer --- .../src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 50e0f274e7..813603f76c 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyStack import com.maddyhome.idea.vim.key.Node import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer +import com.maddyhome.idea.vim.key.consumers.CommandConsumer import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer import com.maddyhome.idea.vim.key.consumers.DigraphConsumer @@ -56,7 +57,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set From 74a8277e10fa019e91f4e584e6a758a9c4fe1a99 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:29:49 +0200 Subject: [PATCH 27/44] Move some logic to SelectRegisterConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 71 +++++-------------- .../key/consumers/SelectRegisterConsumer.kt | 56 +++++++++++++++ 2 files changed, 75 insertions(+), 52 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/SelectRegisterConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 813603f76c..31583f2806 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -12,29 +12,20 @@ import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector -import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.Command -import com.maddyhome.idea.vim.command.CommandBuilder import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingProcessor import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.common.CurrentCommandState -import com.maddyhome.idea.vim.common.DigraphResult -import com.maddyhome.idea.vim.common.argumentCaptured import com.maddyhome.idea.vim.diagnostic.VimLogger -import com.maddyhome.idea.vim.diagnostic.debug import com.maddyhome.idea.vim.diagnostic.trace import com.maddyhome.idea.vim.diagnostic.vimLogger -import com.maddyhome.idea.vim.handler.EditorActionHandlerBase -import com.maddyhome.idea.vim.helper.isCloseKeyStroke import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.impl.state.toMappingMode -import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.KeyConsumer import com.maddyhome.idea.vim.key.KeyStack -import com.maddyhome.idea.vim.key.Node import com.maddyhome.idea.vim.key.consumers.CharArgumentConsumer import com.maddyhome.idea.vim.key.consumers.CommandConsumer import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer @@ -42,14 +33,12 @@ import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer import com.maddyhome.idea.vim.key.consumers.DigraphConsumer import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer import com.maddyhome.idea.vim.key.consumers.RegisterConsumer +import com.maddyhome.idea.vim.key.consumers.SelectRegisterConsumer import com.maddyhome.idea.vim.state.KeyHandlerState import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.ReturnTo -import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd import com.maddyhome.idea.vim.state.mode.returnTo -import java.awt.event.InputEvent -import java.awt.event.KeyEvent import javax.swing.KeyStroke /** @@ -57,7 +46,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer(), SelectRegisterConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -129,9 +118,6 @@ public class KeyHandler { val editorState = editor.vimStateMachine 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. val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording) handleKeyRecursionCount++ @@ -141,34 +127,26 @@ public class KeyHandler { it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) } if (!isProcessed) { - if (isSelectRegister(key, processBuilder.state, editorState)) { - LOG.trace("Select register") - editorState.isRegisterPending = true - commandBuilder.addKey(key) + LOG.trace("We are not able to find a node for this key") + + // 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.value = injector.changeGroup.processKey(editor, key, processBuilder) && shouldRecord.value + isProcessed = true + } else if (editorState.mode is Mode.SELECT) { + LOG.trace("Process select") + shouldRecord.value = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord.value + isProcessed = true + } else if (editor.mode is Mode.CMD_LINE) { + LOG.trace("Process cmd line") + shouldRecord.value = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord.value isProcessed = true } else { - // node == null - LOG.trace("We are not able to find a node for this key") - - // 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.value = injector.changeGroup.processKey(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else if (editorState.mode is Mode.SELECT) { - LOG.trace("Process select") - shouldRecord.value = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else if (editor.mode is Mode.CMD_LINE) { - LOG.trace("Process cmd line") - shouldRecord.value = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else { - LOG.trace("Set command state to bad_command") - commandBuilder.commandState = CurrentCommandState.BAD_COMMAND - } - partialReset(processBuilder.state, editorState.mode) + LOG.trace("Set command state to bad_command") + commandBuilder.commandState = CurrentCommandState.BAD_COMMAND } + partialReset(processBuilder.state, editorState.mode) } if (isProcessed) { processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> @@ -244,17 +222,6 @@ public class KeyHandler { return mode is Mode.OP_PENDING && !keyState.commandBuilder.isEmpty } - private fun isSelectRegister(key: KeyStroke, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean { - if (editorState.mode !is Mode.NORMAL && editorState.mode !is Mode.VISUAL) { - return false - } - return if (editorState.isRegisterPending) { - true - } else { - key.keyChar == '"' && !isOperatorPending(editorState.mode, keyState) && keyState.commandBuilder.expectedArgumentType == null - } - } - private fun executeCommand( editor: VimEditor, context: ExecutionContext, diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/SelectRegisterConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/SelectRegisterConsumer.kt new file mode 100644 index 0000000000..bea21acd05 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/SelectRegisterConsumer.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.key.KeyConsumer +import com.maddyhome.idea.vim.state.KeyHandlerState +import com.maddyhome.idea.vim.state.VimStateMachine +import com.maddyhome.idea.vim.state.mode.Mode +import javax.swing.KeyStroke + +public class SelectRegisterConsumer : KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + val state = keyProcessResultBuilder.state + if (!isSelectRegister(key, state, editor.vimStateMachine)) return false + + logger.trace("Select register") + state.commandBuilder.addKey(key) + keyProcessResultBuilder.addExecutionStep { _, lambdaEditor, _ -> + lambdaEditor.vimStateMachine.isRegisterPending = true + } + return true + } + + private fun isSelectRegister(key: KeyStroke, keyState: KeyHandlerState, editorState: VimStateMachine): Boolean { + if (editorState.mode !is Mode.NORMAL && editorState.mode !is Mode.VISUAL) { + return false + } + return if (editorState.isRegisterPending) { + true + } else { + key.keyChar == '"' && !KeyHandler.getInstance().isOperatorPending(editorState.mode, keyState) && keyState.commandBuilder.expectedArgumentType == null + } + } +} \ No newline at end of file From 7842b155c18cf55d184f2f0fbfc78a7b03d11664 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:37:01 +0200 Subject: [PATCH 28/44] Move some logic to ModeInputConsumer --- .../com/maddyhome/idea/vim/KeyHandler.kt | 29 +-------- .../vim/key/consumers/ModeInputConsumer.kt | 63 +++++++++++++++++++ 2 files changed, 66 insertions(+), 26 deletions(-) create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/ModeInputConsumer.kt diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 31583f2806..4ea3d90dc2 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -32,6 +32,7 @@ import com.maddyhome.idea.vim.key.consumers.CommandCountConsumer import com.maddyhome.idea.vim.key.consumers.DeleteCommandConsumer import com.maddyhome.idea.vim.key.consumers.DigraphConsumer import com.maddyhome.idea.vim.key.consumers.EditorResetConsumer +import com.maddyhome.idea.vim.key.consumers.ModeInputConsumer import com.maddyhome.idea.vim.key.consumers.RegisterConsumer import com.maddyhome.idea.vim.key.consumers.SelectRegisterConsumer import com.maddyhome.idea.vim.state.KeyHandlerState @@ -46,7 +47,7 @@ import javax.swing.KeyStroke * actions. This is a singleton. */ public class KeyHandler { - private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer(), SelectRegisterConsumer()) + private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer(), SelectRegisterConsumer(), ModeInputConsumer()) public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set @@ -115,39 +116,15 @@ public class KeyHandler { } injector.messages.clearError() - val editorState = editor.vimStateMachine - val commandBuilder = processBuilder.state.commandBuilder // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording) handleKeyRecursionCount++ try { LOG.trace("Start key processing...") - var isProcessed = keyConsumers.any { + val isProcessed = keyConsumers.any { it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) } - if (!isProcessed) { - LOG.trace("We are not able to find a node for this key") - - // 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.value = injector.changeGroup.processKey(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else if (editorState.mode is Mode.SELECT) { - LOG.trace("Process select") - shouldRecord.value = injector.changeGroup.processKeyInSelectMode(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else if (editor.mode is Mode.CMD_LINE) { - LOG.trace("Process cmd line") - shouldRecord.value = injector.processGroup.processExKey(editor, key, processBuilder) && shouldRecord.value - isProcessed = true - } else { - LOG.trace("Set command state to bad_command") - commandBuilder.commandState = CurrentCommandState.BAD_COMMAND - } - partialReset(processBuilder.state, editorState.mode) - } if (isProcessed) { processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> finishedCommandPreparation(lambdaEditor, lambdaContext, key, shouldRecord, lambdaKeyState) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/ModeInputConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/ModeInputConsumer.kt new file mode 100644 index 0000000000..326bb7ec09 --- /dev/null +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/consumers/ModeInputConsumer.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2003-2024 The IdeaVim authors + * + * Use of this source code is governed by an MIT-style + * license that can be found in the LICENSE.txt file or at + * https://opensource.org/licenses/MIT. + */ + +package com.maddyhome.idea.vim.key.consumers + +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.KeyProcessResult +import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.diagnostic.vimLogger +import com.maddyhome.idea.vim.key.KeyConsumer +import com.maddyhome.idea.vim.state.mode.Mode +import javax.swing.KeyStroke + +public class ModeInputConsumer: KeyConsumer { + private companion object { + private val logger = vimLogger() + } + + override fun consumeKey( + key: KeyStroke, + editor: VimEditor, + allowKeyMappings: Boolean, + mappingCompleted: Boolean, + keyProcessResultBuilder: KeyProcessResult.KeyProcessResultBuilder, + shouldRecord: KeyHandler.MutableBoolean, + ): Boolean { + val isProcessed = when (editor.mode) { + Mode.INSERT, Mode.REPLACE -> { + logger.trace("Process insert or replace") + val keyProcessed = injector.changeGroup.processKey(editor, key, keyProcessResultBuilder) + shouldRecord.value = keyProcessed && shouldRecord.value + keyProcessed + } + is Mode.SELECT -> { + logger.trace("Process select") + val keyProcessed = injector.changeGroup.processKeyInSelectMode(editor, key, keyProcessResultBuilder) + shouldRecord.value = keyProcessed && shouldRecord.value + keyProcessed + } + is Mode.CMD_LINE -> { + logger.trace("Process cmd line") + val keyProcessed = injector.processGroup.processExKey(editor, key, keyProcessResultBuilder) + shouldRecord.value = keyProcessed && shouldRecord.value + keyProcessed + } + else -> { + false + } + } + if (isProcessed) { + keyProcessResultBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, _ -> + lambdaKeyState.partialReset(lambdaEditor.mode) + } + } + return isProcessed + } +} \ No newline at end of file From e9bf06686f88f4fd3e8dfe8c1c1665f7e12f6c76 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:41:17 +0200 Subject: [PATCH 29/44] Add synchronize blocks to minimize risk of concurrent key processing and changing of the KeyHandlerState --- .../com/maddyhome/idea/vim/KeyHandler.kt | 79 ++++++++++--------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 4ea3d90dc2..f7a3959f24 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -98,49 +98,51 @@ public class KeyHandler { mappingCompleted: Boolean, processBuilder: KeyProcessResult.KeyProcessResultBuilder, ): KeyProcessResult { - LOG.trace { - """ + synchronized(lock) { + LOG.trace { + """ ------- Key Handler ------- Start key processing. allowKeyMappings: $allowKeyMappings, mappingCompleted: $mappingCompleted Key: $key """.trimIndent() - } - val maxMapDepth = injector.globalOptions().maxmapdepth - if (handleKeyRecursionCount >= maxMapDepth) { - 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 maxMapDepth = injector.globalOptions().maxmapdepth + if (handleKeyRecursionCount >= maxMapDepth) { + 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() + } - injector.messages.clearError() + injector.messages.clearError() - // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. - val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording) - handleKeyRecursionCount++ - try { - LOG.trace("Start key processing...") - val isProcessed = keyConsumers.any { - it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) - } - if (isProcessed) { - processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> - finishedCommandPreparation(lambdaEditor, lambdaContext, key, shouldRecord, lambdaKeyState) + // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. + val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording) + handleKeyRecursionCount++ + try { + LOG.trace("Start key processing...") + val isProcessed = keyConsumers.any { + it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) } - } 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 + 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 { + processBuilder.onFinish = { handleKeyRecursionCount-- } } - } finally { - processBuilder.onFinish = { handleKeyRecursionCount-- } + return processBuilder.build() } - return processBuilder.build() } internal fun finishedCommandPreparation( @@ -355,6 +357,7 @@ public class KeyHandler { } public companion object { + public val lock: Any = Object() private val LOG: VimLogger = vimLogger() internal fun isPrefix(list1: List, list2: List): Boolean { @@ -408,12 +411,14 @@ public sealed interface KeyProcessResult { } public fun execute(editor: VimEditor, context: ExecutionContext) { - val keyHandler = KeyHandler.getInstance() - if (keyHandler.keyHandlerState != originalState) { - logger.warn("Unexpected editor state. Aborting command execution.") + synchronized(KeyHandler.lock) { + val keyHandler = KeyHandler.getInstance() + if (keyHandler.keyHandlerState != originalState) { + logger.warn("Unexpected editor state. Aborting command execution.") + } + processing(preProcessState, editor, context) + keyHandler.updateState(preProcessState) } - processing(preProcessState, editor, context) - keyHandler.updateState(preProcessState) } } From 23fdadc32e76d8633877e16fb7151a6178398086 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:43:25 +0200 Subject: [PATCH 30/44] Fix test Sometimes it's not a plugin error and may indicate that key is propagated for later handling by IDE But what we know for sure - that for both cases we should reset command builder --- .../org/jetbrains/plugins/ideavim/action/CopyActionTest.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/test/java/org/jetbrains/plugins/ideavim/action/CopyActionTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/action/CopyActionTest.kt index 33cd3fb94d..33cf28aa4c 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/action/CopyActionTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/action/CopyActionTest.kt @@ -10,6 +10,8 @@ package org.jetbrains.plugins.ideavim.action import com.intellij.idea.TestFor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.api.injector +import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.state.mode.Mode import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.TestWithoutNeovim @@ -17,6 +19,7 @@ import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.waitAndAssert import org.junit.jupiter.api.Test import kotlin.test.assertNotNull +import kotlin.test.assertTrue /** * @author vlan @@ -144,7 +147,7 @@ class CopyActionTest : VimTestCase() { """.trimIndent(), ) - assertPluginError(true) + assertTrue(fixture.editor.vim.vimStateMachine.commandBuilder.isEmpty) } @Test From ea62f227bfd0d62ec42db4805946e6c912596f4b Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:44:53 +0200 Subject: [PATCH 31/44] Remove piece of code for handling bad commands Bad commands are handled in consumers --- .../src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index f7a3959f24..424b0b3020 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -157,14 +157,7 @@ public class KeyHandler { val commandBuilder = keyState.commandBuilder if (commandBuilder.isReady) { LOG.trace("Ready command builder. Execute command.") - executeCommand(editor, context, editorState, keyState) - } else if (commandBuilder.isBad) { - LOG.trace("Command builder is set to BAD") - editor.resetOpPending() - editorState.resetRegisterPending() - editor.isReplaceCharacter = false - injector.messages.indicateError() - reset(keyState, editorState.mode) + executeCommand(editor, context, editor.vimStateMachine, keyState) } // Don't record the keystroke that stops the recording (unmapped this is `q`) From d3704d602f9d2ded29fdbc321e592aed1cfedfed Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 14:45:21 +0200 Subject: [PATCH 32/44] Cleanup after moving logic to other classes --- .../maddyhome/idea/vim/group/ProcessGroup.kt | 1 - .../com/maddyhome/idea/vim/KeyHandler.kt | 24 ++++++++++++------- .../idea/vim/api/VimChangeGroupBase.kt | 1 - 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt b/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt index e45bb0adc4..6a5ae6fdc5 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt +++ b/src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt @@ -38,7 +38,6 @@ import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode.NORMAL import com.maddyhome.idea.vim.state.mode.Mode.VISUAL import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd -import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.ui.ex.ExEntryPanel import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import java.io.BufferedWriter diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 424b0b3020..84f76ce2b7 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -46,13 +46,16 @@ import javax.swing.KeyStroke * This handles every keystroke that the user can argType except those that are still valid hotkeys for various Idea * actions. This is a singleton. */ +// TODO for future refactorings (PR are welcome) +// 1. avoid using handleKeyRecursionCount & shouldRecord +// 2. maybe we can live without allowKeyMappings: Boolean & mappingCompleted: Boolean public class KeyHandler { private val keyConsumers: List = listOf(MappingProcessor, CommandCountConsumer(), DeleteCommandConsumer(), EditorResetConsumer(), CharArgumentConsumer(), RegisterConsumer(), DigraphConsumer(), CommandConsumer(), SelectRegisterConsumer(), ModeInputConsumer()) + private var handleKeyRecursionCount = 0 + public var keyHandlerState: KeyHandlerState = KeyHandlerState() private set - private var handleKeyRecursionCount = 0 - public val keyStack: KeyStack = KeyStack() public val modalEntryKeys: MutableList = ArrayList() @@ -73,9 +76,6 @@ public class KeyHandler { * * @param allowKeyMappings - If we allow key mappings or not * @param mappingCompleted - if true, we don't check if the mapping is incomplete - * - * TODO mappingCompleted and recursionCounter - we should find a more beautiful way to use them - * TODO it should not receive editor at all and use the focused one. It will help to execute macro between multiple editors */ public fun handleKey( editor: VimEditor, @@ -117,14 +117,20 @@ public class KeyHandler { } injector.messages.clearError() - // We only record unmapped keystrokes. If we've recursed to handle mapping, don't record anything. val shouldRecord = MutableBoolean(handleKeyRecursionCount == 0 && injector.registerGroup.isRecording) + handleKeyRecursionCount++ try { - LOG.trace("Start key processing...") val isProcessed = keyConsumers.any { - it.consumeKey( key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord ) + it.consumeKey( + key, + editor, + allowKeyMappings, + mappingCompleted, + processBuilder, + shouldRecord + ) } if (isProcessed) { processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> @@ -153,8 +159,8 @@ public class KeyHandler { 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.") executeCommand(editor, context, editor.vimStateMachine, keyState) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt index 7ea3e8a24a..8d3cf4d624 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt @@ -710,7 +710,6 @@ public abstract class VimChangeGroupBase : VimChangeGroup { * This processes all "regular" keystrokes entered while in insert/replace mode * * @param editor The editor the character was typed into - * @param context The data context * @param key The user entered keystroke * @return true if this was a regular character, false if not */ From 2de933c723370f1ca220cf987704e95ec44df9f4 Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Thu, 8 Feb 2024 00:47:26 +0200 Subject: [PATCH 33/44] Make processKey public --- vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 84f76ce2b7..02311aed8d 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -91,7 +91,7 @@ public class KeyHandler { } } - private fun processKey( + public fun processKey( key: KeyStroke, editor: VimEditor, allowKeyMappings: Boolean, From db35c979b4a88fcb52931cd23c2b8aff3d7a40c8 Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Thu, 8 Feb 2024 00:50:45 +0200 Subject: [PATCH 34/44] Move some editor methods to the base class --- .../maddyhome/idea/vim/newapi/IjVimEditor.kt | 39 ---------------- .../com/maddyhome/idea/vim/api/VimEditor.kt | 44 +++++++++++++++++++ 2 files changed, 44 insertions(+), 39 deletions(-) diff --git a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt index ce3bd22a08..e93dba5f34 100644 --- a/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt +++ b/src/main/java/com/maddyhome/idea/vim/newapi/IjVimEditor.kt @@ -133,45 +133,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { editor.document.replaceString(start, end, newString) } - override var mode: Mode - get() = vimStateMachine.mode - set(value) { - if (vimStateMachine.mode == value) return - - val oldValue = vimStateMachine.mode - (vimStateMachine as VimStateMachineImpl).mode = value - injector.listenersNotifier.notifyModeChanged(this, oldValue) - } - - override fun resetOpPending() { - if (this.mode is Mode.OP_PENDING) { - val returnTo = this.mode.returnTo - mode = when (returnTo) { - ReturnTo.INSERT -> Mode.INSERT - ReturnTo.REPLACE -> Mode.INSERT - null -> Mode.NORMAL() - } - } - } - - override var isReplaceCharacter: Boolean - get() = vimStateMachine.isReplaceCharacter - set(value) { - if (value != vimStateMachine.isReplaceCharacter) { - (vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value - injector.listenersNotifier.notifyIsReplaceCharChanged(this) - } - } - - override fun resetState() { - mode = Mode.NORMAL() - vimStateMachine.executingCommand = null - vimStateMachine.digraphSequence.reset() - vimStateMachine.commandBuilder.resetInProgressCommandPart( - injector.keyGroup.getKeyRoot(mode.toMappingMode()) - ) - } - // TODO: 30.12.2021 Is end offset inclusive? override fun getLineRange(line: EditorLine.Pointer): Pair { // TODO: 30.12.2021 getLineEndOffset returns the same value for "xyz" and "xyz\n" diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt index 9adcf74856..4921d96a71 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt @@ -14,8 +14,13 @@ import com.maddyhome.idea.vim.common.LiveRange import com.maddyhome.idea.vim.common.Offset import com.maddyhome.idea.vim.common.Pointer import com.maddyhome.idea.vim.common.TextRange +import com.maddyhome.idea.vim.helper.vimStateMachine +import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl +import com.maddyhome.idea.vim.impl.state.toMappingMode import com.maddyhome.idea.vim.state.mode.Mode +import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.SelectionType +import com.maddyhome.idea.vim.state.mode.returnTo /** * Every line in [VimEditor] ends with a new line TODO <- this is probably not true already @@ -287,6 +292,45 @@ public interface MutableVimEditor : VimEditor { public fun addLine(atPosition: EditorLine.Offset): EditorLine.Pointer? public fun insertText(atPosition: Offset, text: CharSequence) public fun replaceString(start: Int, end: Int, newString: String) + + override var mode: Mode + get() = vimStateMachine.mode + set(value) { + if (vimStateMachine.mode == value) return + + val oldValue = vimStateMachine.mode + (vimStateMachine as VimStateMachineImpl).mode = value + injector.listenersNotifier.notifyModeChanged(this, oldValue) + } + + override var isReplaceCharacter: Boolean + get() = vimStateMachine.isReplaceCharacter + set(value) { + if (value != vimStateMachine.isReplaceCharacter) { + (vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value + injector.listenersNotifier.notifyIsReplaceCharChanged(this) + } + } + + public override fun resetOpPending() { + if (this.mode is Mode.OP_PENDING) { + val returnTo = this.mode.returnTo + mode = when (returnTo) { + ReturnTo.INSERT -> Mode.INSERT + ReturnTo.REPLACE -> Mode.INSERT + null -> Mode.NORMAL() + } + } + } + + override fun resetState() { + mode = Mode.NORMAL() + vimStateMachine.executingCommand = null + vimStateMachine.digraphSequence.reset() + vimStateMachine.commandBuilder.resetInProgressCommandPart( + injector.keyGroup.getKeyRoot(mode.toMappingMode()) + ) + } } public abstract class LinearEditor : VimEditor { From a7dfef61e93189cbf42d22dadba3942fca2aaff3 Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Thu, 8 Feb 2024 00:52:13 +0200 Subject: [PATCH 35/44] Make LazyVimCommand open --- .../com/maddyhome/idea/vim/action/change/LazyVimCommand.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt index f3119d5f56..c6b645eeda 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/LazyVimCommand.kt @@ -13,7 +13,7 @@ import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.vimscript.model.LazyInstance import javax.swing.KeyStroke -public class LazyVimCommand( +public open class LazyVimCommand( public val keys: Set>, public val modes: Set, className: String, From 924b7418e82336e6f84dbdb03ed187b25403180c Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Thu, 8 Feb 2024 17:26:48 +0200 Subject: [PATCH 36/44] Fix DigraphSequence cloning --- .../kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt index c7c0922f0e..96a28b3ef0 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/common/DigraphSequence.kt @@ -252,7 +252,9 @@ public class DigraphSequence: Cloneable { val result = DigraphSequence() result.digraphState = digraphState result.digraphChar = digraphChar - result.codeChars = codeChars.copyOf() + if (::codeChars.isInitialized) { + result.codeChars = codeChars.copyOf() + } result.codeCnt = codeCnt result.codeType = codeType result.codeMax = codeMax From c8113eea8320d88febd987b2e2cd0d4590248b2f Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Thu, 8 Feb 2024 22:51:26 +0200 Subject: [PATCH 37/44] Commit state after receiving unknown key --- vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 02311aed8d..655d56e924 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -140,6 +140,7 @@ public class KeyHandler { // 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) + updateState(processBuilder.state) return KeyProcessResult.Unknown.apply { handleKeyRecursionCount-- // because onFinish will now be executed for unknown } From 38bc914504d3bc403533da5af4028d6589d98739 Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Fri, 9 Feb 2024 14:26:24 +0200 Subject: [PATCH 38/44] Avoid using annotation-processors in vim-engine --- gradle.properties | 2 +- vim-engine/build.gradle.kts | 3 ++- .../kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt | 5 ++++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 297de38765..8b88bb18ab 100644 --- a/gradle.properties +++ b/gradle.properties @@ -27,7 +27,7 @@ publishChannels=eap # Kotlinx serialization also uses some version of kotlin stdlib under the hood. However, # we exclude this version from the dependency and use our own version of kotlin that is specified above -kotlinxSerializationVersion=1.5.1 +kotlinxSerializationVersion=1.6.2 slackUrl= youtrackToken= diff --git a/vim-engine/build.gradle.kts b/vim-engine/build.gradle.kts index a5cdcad3cb..daec42934b 100644 --- a/vim-engine/build.gradle.kts +++ b/vim-engine/build.gradle.kts @@ -11,6 +11,7 @@ plugins { kotlin("jvm") // id("org.jlleitschuh.gradle.ktlint") id("com.google.devtools.ksp") version "1.9.22-1.0.17" + kotlin("plugin.serialization") version "1.9.22" `maven-publish` antlr } @@ -51,7 +52,7 @@ dependencies { antlr("org.antlr:antlr4:4.10.1") ksp(project(":annotation-processors")) - implementation(project(":annotation-processors")) + compileOnly(project(":annotation-processors")) compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") testImplementation("org.mockito.kotlin:mockito-kotlin:5.0.0") diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt index e5d83d576d..ecef98386f 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/CommandProvider.kt @@ -8,11 +8,11 @@ package com.maddyhome.idea.vim.action -import com.intellij.vim.processors.CommandBean import com.maddyhome.idea.vim.action.change.LazyVimCommand import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.MappingMode import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import java.io.InputStream @@ -44,3 +44,6 @@ public interface CommandProvider { ?: throw RuntimeException("Failed to fetch ex commands from ${javaClass.name}") } } + +@Serializable +public data class CommandBean(val keys: String, val `class`: String, val modes: String) From 795abd77a70eb2b6a426a1ca13c4de2c78e0d0e0 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 16 Feb 2024 09:52:56 +0200 Subject: [PATCH 39/44] Add documentation --- .../com/maddyhome/idea/vim/KeyHandler.kt | 37 +++++++++++++------ .../com/maddyhome/idea/vim/key/KeyConsumer.kt | 3 ++ 2 files changed, 28 insertions(+), 12 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index 655d56e924..a19bb22715 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -46,7 +46,7 @@ import javax.swing.KeyStroke * This handles every keystroke that the user can argType except those that are still valid hotkeys for various Idea * actions. This is a singleton. */ -// TODO for future refactorings (PR are welcome) +// TODO for future refactorings (PRs are welcome) // 1. avoid using handleKeyRecursionCount & shouldRecord // 2. maybe we can live without allowKeyMappings: Boolean & mappingCompleted: Boolean public class KeyHandler { @@ -91,6 +91,13 @@ public class KeyHandler { } } + /** + * This method determines whether IdeaVim can handle the passed key or not. + * For instance, if there is no mapping for , we should return 'KeyProcessResult.Unknown' to inform the IDE that + * we did not process the keypress, and therefore need to propagate it further. + * Alternatively, if we understand the key, we return a 'KeyProcessResult.Executable', which contains a runnable that + * could execute the key if needed. + */ public fun processKey( key: KeyStroke, editor: VimEditor, @@ -123,14 +130,7 @@ public class KeyHandler { handleKeyRecursionCount++ try { val isProcessed = keyConsumers.any { - it.consumeKey( - key, - editor, - allowKeyMappings, - mappingCompleted, - processBuilder, - shouldRecord - ) + it.consumeKey(key, editor, allowKeyMappings, mappingCompleted, processBuilder, shouldRecord) } if (isProcessed) { processBuilder.addExecutionStep { lambdaKeyState, lambdaEditor, lambdaContext -> @@ -138,7 +138,6 @@ public class KeyHandler { } } 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) updateState(processBuilder.state) return KeyProcessResult.Unknown.apply { @@ -422,6 +421,20 @@ public sealed interface KeyProcessResult { } } + /** + * This class serves as a wrapper around the key handling algorithm and should be used with care: + * We process keys in two steps: + * 1. We first determine if IdeaVim can handle the key or not. At this stage, you should avoid modifying anything + * except state: KeyHandlerState. This is because it is not guaranteed that the key will be handled by IdeaVim at + * all, and we want to minimize possible side effects. + * 2. If it's confirmed that the key will be handled, add all the key handling processes as execution steps, + * slated for later execution. + * + * Please note that execution steps could depend on KeyHandlerState, and because of that we cannot change the state + * after adding an execution step. This is because an execution step does not anticipate changes to the state. + * If there's need to alter the state following any of the execution steps, wrap the state modification as an + * execution step. This will allow state modification to occur later rather than immediately. + */ public abstract class KeyProcessResultBuilder { public abstract val state: KeyHandlerState protected val processings: MutableList = mutableListOf() @@ -451,8 +464,8 @@ public sealed interface KeyProcessResult { } } - // Created new state, nothing is modified during the builder work (key processing) - // The new state will be applied later, when we run KeyProcess (it may not be run at all) + // Works with a clone of current state, nothing is modified during the builder work (key processing) + // The new state will be applied later, when we run Executable.execute() (it may not be run at all) public class AsyncKeyProcessBuilder(originalState: KeyHandlerState): KeyProcessResultBuilder() { private val originalState: KeyHandlerState = KeyHandler.getInstance().keyHandlerState public override val state: KeyHandlerState = originalState.clone() diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt index 200a458686..6a3c5b6dc0 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/key/KeyConsumer.kt @@ -14,6 +14,9 @@ import com.maddyhome.idea.vim.api.VimEditor import javax.swing.KeyStroke public interface KeyConsumer { + /** + * @return true if consumed key and could do something meaningful wit it + */ public fun consumeKey( key: KeyStroke, editor: VimEditor, From 4aac11352266469d41b3ce06b89113ffcb8b2511 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 16 Feb 2024 10:02:47 +0200 Subject: [PATCH 40/44] Remove duplicate method --- .../kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index a19bb22715..e229f4424b 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -249,13 +249,7 @@ public class KeyHandler { * @param editor The editor to reset. */ public fun partialReset(editor: VimEditor) { - partialReset(keyHandlerState, editor.mode) - } - - // TODO replace with com.maddyhome.idea.vim.state.KeyHandlerState#partialReset - private fun partialReset(keyState: KeyHandlerState, mode: Mode) { - keyState.mappingState.resetMappingSequence() - keyState.commandBuilder.resetInProgressCommandPart(getKeyRoot(mode.toMappingMode())) + keyHandlerState.partialReset(editor.mode) } /** @@ -263,15 +257,13 @@ public class KeyHandler { * * @param editor The editor to reset. */ - // TODO replace with com.maddyhome.idea.vim.state.KeyHandlerState#reset public fun reset(editor: VimEditor) { - partialReset(keyHandlerState, editor.mode) + keyHandlerState.partialReset(editor.mode) keyHandlerState.commandBuilder.resetAll(getKeyRoot(editor.mode.toMappingMode())) } - // TODO replace with com.maddyhome.idea.vim.state.KeyHandlerState#reset public fun reset(keyState: KeyHandlerState, mode: Mode) { - partialReset(keyState, mode) + keyHandlerState.partialReset(mode) keyState.commandBuilder.resetAll(getKeyRoot(mode.toMappingMode())) } From 7523db186f4dc1443684af44fbe4d89c31c1d47e Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Sun, 18 Feb 2024 03:07:10 +0200 Subject: [PATCH 41/44] Empty status bar message after each test --- .../extension/highlightedyank/VimHighlightedYankTest.kt | 7 ------- .../kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt | 1 + 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt index 6340498e45..91ce2e7081 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/extension/highlightedyank/VimHighlightedYankTest.kt @@ -10,7 +10,6 @@ package org.jetbrains.plugins.ideavim.extension.highlightedyank import com.intellij.openapi.editor.markup.RangeHighlighter import com.maddyhome.idea.vim.extension.highlightedyank.DEFAULT_HIGHLIGHT_DURATION -import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.state.mode.Mode import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.assertHappened @@ -31,7 +30,6 @@ class VimHighlightedYankTest : VimTestCase() { assertAllHighlightersCount(1) assertHighlighterRange(1, 40, getFirstHighlighter()) - clearMessage() } @Test @@ -40,7 +38,6 @@ class VimHighlightedYankTest : VimTestCase() { assertAllHighlightersCount(1) assertHighlighterRange(5, 8, getFirstHighlighter()) - clearMessage() } @Test @@ -48,7 +45,6 @@ class VimHighlightedYankTest : VimTestCase() { doTest("yyi", code, code, Mode.INSERT) assertAllHighlightersCount(0) - clearMessage() } @Test @@ -65,7 +61,6 @@ class VimHighlightedYankTest : VimTestCase() { assertHighlighterRange(12, 15, highlighters[1]) assertHighlighterRange(20, 23, highlighters[0]) assertHighlighterRange(28, 31, highlighters[2]) - clearMessage() } @Test @@ -78,7 +73,6 @@ Mode.INSERT, ) assertAllHighlightersCount(0) - clearMessage() } @Test @@ -88,7 +82,6 @@ Mode.INSERT, assertHappened(DEFAULT_HIGHLIGHT_DURATION.toInt(), 200) { getAllHighlightersCount() == 0 } - clearMessage() } private val code = """ diff --git a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt index f7446e9ad8..85ce4c0cad 100644 --- a/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/src/testFixtures/kotlin/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -157,6 +157,7 @@ abstract class VimTestCase { bookmarksManager?.bookmarks?.forEach { bookmark -> bookmarksManager.remove(bookmark) } + fixture.editor?.let { injector.messages.showStatusBarMessage(it.vim, "") } SelectionVimListenerSuppressor.lock().use { fixture.tearDown() } ExEntryPanel.getInstance().deactivate(false) VimPlugin.getVariableService().clear() From 8eaa6df318f83861a9ced1efa4877cdf11be24dc Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Sun, 18 Feb 2024 03:10:44 +0200 Subject: [PATCH 42/44] Throw error instead of warning on state conflict It may indicate some serious issues, and we would like to know if anything goes wrong --- vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt index e229f4424b..c04873150a 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/KeyHandler.kt @@ -405,7 +405,7 @@ public sealed interface KeyProcessResult { synchronized(KeyHandler.lock) { val keyHandler = KeyHandler.getInstance() if (keyHandler.keyHandlerState != originalState) { - logger.warn("Unexpected editor state. Aborting command execution.") + logger.error("Unexpected editor state. Aborting command execution.") } processing(preProcessState, editor, context) keyHandler.updateState(preProcessState) From 1a4333fa1b56504670b715a8814cf3fa0ca87260 Mon Sep 17 00:00:00 2001 From: Filipp Vakhitov Date: Tue, 20 Feb 2024 14:57:28 +0200 Subject: [PATCH 43/44] Move implementations to upper level It will simplify support of immutable editors in Fleet --- .../com/maddyhome/idea/vim/api/VimEditor.kt | 68 +++++++++---------- 1 file changed, 31 insertions(+), 37 deletions(-) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt index 4921d96a71..f6f7877013 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimEditor.kt @@ -130,7 +130,23 @@ import com.maddyhome.idea.vim.state.mode.returnTo */ public interface VimEditor { public var mode: Mode - public var isReplaceCharacter: Boolean + get() = vimStateMachine.mode + set(value) { + if (vimStateMachine.mode == value) return + + val oldValue = vimStateMachine.mode + (vimStateMachine as VimStateMachineImpl).mode = value + injector.listenersNotifier.notifyModeChanged(this, oldValue) + } + + public var isReplaceCharacter: Boolean + get() = vimStateMachine.isReplaceCharacter + set(value) { + if (value != vimStateMachine.isReplaceCharacter) { + (vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value + injector.listenersNotifier.notifyIsReplaceCharChanged(this) + } + } public val lfMakesNewLine: Boolean public var vimChangeActionSwitchMode: Mode? @@ -284,35 +300,16 @@ public interface VimEditor { /** * Resets the command, mode, visual mode, and mapping mode to initial values. */ - public fun resetState() - public fun resetOpPending() -} - -public interface MutableVimEditor : VimEditor { - public fun addLine(atPosition: EditorLine.Offset): EditorLine.Pointer? - public fun insertText(atPosition: Offset, text: CharSequence) - public fun replaceString(start: Int, end: Int, newString: String) - - override var mode: Mode - get() = vimStateMachine.mode - set(value) { - if (vimStateMachine.mode == value) return - - val oldValue = vimStateMachine.mode - (vimStateMachine as VimStateMachineImpl).mode = value - injector.listenersNotifier.notifyModeChanged(this, oldValue) - } - - override var isReplaceCharacter: Boolean - get() = vimStateMachine.isReplaceCharacter - set(value) { - if (value != vimStateMachine.isReplaceCharacter) { - (vimStateMachine as VimStateMachineImpl).isReplaceCharacter = value - injector.listenersNotifier.notifyIsReplaceCharChanged(this) - } - } + public fun resetState() { + mode = Mode.NORMAL() + vimStateMachine.executingCommand = null + vimStateMachine.digraphSequence.reset() + vimStateMachine.commandBuilder.resetInProgressCommandPart( + injector.keyGroup.getKeyRoot(mode.toMappingMode()) + ) + } - public override fun resetOpPending() { + public fun resetOpPending() { if (this.mode is Mode.OP_PENDING) { val returnTo = this.mode.returnTo mode = when (returnTo) { @@ -322,15 +319,12 @@ public interface MutableVimEditor : VimEditor { } } } +} - override fun resetState() { - mode = Mode.NORMAL() - vimStateMachine.executingCommand = null - vimStateMachine.digraphSequence.reset() - vimStateMachine.commandBuilder.resetInProgressCommandPart( - injector.keyGroup.getKeyRoot(mode.toMappingMode()) - ) - } +public interface MutableVimEditor : VimEditor { + public fun addLine(atPosition: EditorLine.Offset): EditorLine.Pointer? + public fun insertText(atPosition: Offset, text: CharSequence) + public fun replaceString(start: Int, end: Int, newString: String) } public abstract class LinearEditor : VimEditor { From b737362abafff4ea8318a6f6680120970548cbb1 Mon Sep 17 00:00:00 2001 From: filipp Date: Fri, 23 Feb 2024 17:21:18 +0200 Subject: [PATCH 44/44] Update CaretVisualAttributesListener to use new Editor API --- .../maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt b/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt index 2bd06ecb56..cb7be80fe5 100644 --- a/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt +++ b/src/main/java/com/maddyhome/idea/vim/helper/CaretVisualAttributesHelper.kt @@ -162,7 +162,7 @@ public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeLi } public fun updateAllEditorsCaretsVisual() { - injector.application.localEditors().forEach { editor -> + injector.editorGroup.getEditors().forEach { editor -> val ijEditor = (editor as IjVimEditor).editor ijEditor.updateCaretsVisualAttributes() ijEditor.updateCaretsVisualPosition()