From db9abd5b1a8f175b5352f808c6a80863762ef0ac Mon Sep 17 00:00:00 2001 From: sumoooru2 Date: Sat, 24 Oct 2020 13:21:01 +0300 Subject: [PATCH] Implement cmap --- src/com/maddyhome/idea/vim/KeyHandler.java | 31 ++++++++++++++++++- .../idea/vim/command/CommandBuilder.kt | 21 +++++++++++++ .../maddyhome/idea/vim/ui/ExEditorKit.java | 12 +++++-- .../idea/vim/ui/ExShortcutKeyAction.kt | 6 ++-- .../maddyhome/idea/vim/ui/ExTextField.java | 13 ++++++-- .../jetbrains/plugins/ideavim/VimTestCase.kt | 7 +---- .../ideavim/action/MotionActionTest.java | 5 +++ .../plugins/ideavim/ex/ExEntryTest.kt | 28 +++++++++++++++++ 8 files changed, 109 insertions(+), 14 deletions(-) diff --git a/src/com/maddyhome/idea/vim/KeyHandler.java b/src/com/maddyhome/idea/vim/KeyHandler.java index 87190b910d..968578ed81 100644 --- a/src/com/maddyhome/idea/vim/KeyHandler.java +++ b/src/com/maddyhome/idea/vim/KeyHandler.java @@ -45,6 +45,7 @@ import com.maddyhome.idea.vim.helper.*; import com.maddyhome.idea.vim.key.*; import com.maddyhome.idea.vim.option.OptionsManager; +import com.maddyhome.idea.vim.ui.ExEntryPanel; import com.maddyhome.idea.vim.ui.ShowCmd; import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NonNls; @@ -53,6 +54,7 @@ import javax.swing.*; import java.awt.*; +import java.awt.event.InputEvent; import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.HashMap; @@ -591,6 +593,22 @@ private boolean handleDigraph(@NotNull Editor editor, } DigraphResult res = editorState.processDigraphKey(key, editor); + if (ExEntryPanel.getInstance().isActive()) { + switch (res.getResult()) { + case DigraphResult.RES_HANDLED: + setPromptCharacterEx(commandBuilder.isPuttingLiteral() ? '^' : key.getKeyChar()); + break; + case DigraphResult.RES_DONE: + case DigraphResult.RES_BAD: + if (key.getKeyCode() == KeyEvent.VK_C && (key.getModifiers() & InputEvent.CTRL_DOWN_MASK) != 0) { + return false; + } else { + ExEntryPanel.getInstance().getEntry().clearCurrentAction(); + } + break; + } + } + switch (res.getResult()) { case DigraphResult.RES_HANDLED: editorState.getCommandBuilder().addKey(key); @@ -745,8 +763,10 @@ private void startWaitingForArgument(Editor editor, // the key handler when it's complete. if (action instanceof InsertCompletedDigraphAction) { editorState.startDigraphSequence(); + setPromptCharacterEx('?'); } else if (action instanceof InsertCompletedLiteralAction) { editorState.startLiteralSequence(); + setPromptCharacterEx('^'); } break; case EX_STRING: @@ -841,6 +861,13 @@ public void fullReset(@Nullable Editor editor) { } + private void setPromptCharacterEx(final char promptCharacter) { + final ExEntryPanel exEntryPanel = ExEntryPanel.getInstance(); + if (exEntryPanel.isActive()) { + exEntryPanel.getEntry().setCurrentActionPromptCharacter(promptCharacter); + } + } + // This class is copied from com.intellij.openapi.editor.actionSystem.DialogAwareDataContext.DialogAwareDataContext private static final class DialogAwareDataContext implements DataContext { @SuppressWarnings("rawtypes") @@ -909,7 +936,9 @@ public void run() { editorState.popModes(); } - KeyHandler.getInstance().reset(editor); + if (editorState.getCommandBuilder().isDone()) { + KeyHandler.getInstance().reset(editor); + } } private final Editor editor; diff --git a/src/com/maddyhome/idea/vim/command/CommandBuilder.kt b/src/com/maddyhome/idea/vim/command/CommandBuilder.kt index ee95fe3bc6..d672880067 100644 --- a/src/com/maddyhome/idea/vim/command/CommandBuilder.kt +++ b/src/com/maddyhome/idea/vim/command/CommandBuilder.kt @@ -1,6 +1,9 @@ package com.maddyhome.idea.vim.command import com.maddyhome.idea.vim.action.DuplicableOperatorAction +import com.maddyhome.idea.vim.action.ResetModeAction +import com.maddyhome.idea.vim.action.change.insert.InsertCompletedDigraphAction +import com.maddyhome.idea.vim.action.change.insert.InsertCompletedLiteralAction import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.Node @@ -24,6 +27,9 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) { var expectedArgumentType: Argument.Type? = null private set + var prevExpectedArgumentType: Argument.Type? = null + private set + val isReady get() = commandState == CurrentCommandState.READY val isBad get() = commandState == CurrentCommandState.BAD_COMMAND val isEmpty get() = commandParts.isEmpty() @@ -38,6 +44,7 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) { fun pushCommandPart(action: EditorActionHandlerBase) { commandParts.add(Command(count, action, action.type, action.flags)) + prevExpectedArgumentType = expectedArgumentType expectedArgumentType = action.argumentType count = 0 } @@ -106,6 +113,14 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) { return currentCommandPartNode !is RootNode } + fun isPuttingLiteral(): Boolean { + return !commandParts.isEmpty() && commandParts.last.action is InsertCompletedLiteralAction + } + + fun isDone(): Boolean { + return commandParts.isEmpty() + } + fun completeCommandPart(argument: Argument) { commandParts.peekLast().argument = argument commandState = CurrentCommandState.READY @@ -121,6 +136,11 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) { } fun buildCommand(): Command { + if (commandParts.last.action is InsertCompletedDigraphAction || commandParts.last.action is ResetModeAction) { + expectedArgumentType = prevExpectedArgumentType + prevExpectedArgumentType = null + return commandParts.removeLast() + } var command: Command = commandParts.removeFirst() while (commandParts.size > 0) { @@ -147,6 +167,7 @@ class CommandBuilder(private var currentCommandPartNode: CommandPartNode) { commandParts.clear() keyList.clear() expectedArgumentType = null + prevExpectedArgumentType = null } fun resetInProgressCommandPart(commandPartNode: CommandPartNode) { diff --git a/src/com/maddyhome/idea/vim/ui/ExEditorKit.java b/src/com/maddyhome/idea/vim/ui/ExEditorKit.java index ae0870ca87..8d0ebe482b 100644 --- a/src/com/maddyhome/idea/vim/ui/ExEditorKit.java +++ b/src/com/maddyhome/idea/vim/ui/ExEditorKit.java @@ -19,11 +19,13 @@ package com.maddyhome.idea.vim.ui; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.editor.Editor; import com.maddyhome.idea.vim.KeyHandler; import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.common.Register; import com.maddyhome.idea.vim.helper.DigraphResult; import com.maddyhome.idea.vim.helper.DigraphSequence; +import com.maddyhome.idea.vim.helper.EditorDataContext; import com.maddyhome.idea.vim.helper.SearchHelper; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; @@ -143,8 +145,14 @@ public void actionPerformed(@NotNull ActionEvent e) { if (key != null) { final char c = key.getKeyChar(); if (c > 0) { - ActionEvent event = new ActionEvent(e.getSource(), e.getID(), String.valueOf(c), e.getWhen(), e.getModifiers()); - super.actionPerformed(event); + if (target.useHandleKeyFromEx) { + final ExTextField entry = ExEntryPanel.getInstance().getEntry(); + final Editor editor = entry.getEditor(); + KeyHandler.getInstance().handleKey(editor, key, new EditorDataContext(editor, entry.getContext())); + } else { + ActionEvent event = new ActionEvent(e.getSource(), e.getID(), String.valueOf(c), e.getWhen(), e.getModifiers()); + super.actionPerformed(event); + } target.saveLastEntry(); } } diff --git a/src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt b/src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt index a55b7d4975..dceeffc0f1 100644 --- a/src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt +++ b/src/com/maddyhome/idea/vim/ui/ExShortcutKeyAction.kt @@ -21,7 +21,8 @@ package com.maddyhome.idea.vim.ui import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.KeyboardShortcut -import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.KeyHandler +import com.maddyhome.idea.vim.helper.EditorDataContext import java.awt.event.KeyEvent import javax.swing.KeyStroke @@ -42,7 +43,8 @@ class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : AnAction() { override fun actionPerformed(e: AnActionEvent) { val keyStroke = getKeyStroke(e) if (keyStroke != null) { - VimPlugin.getProcess().processExKey(exEntryPanel.entry.editor, keyStroke) + val editor = exEntryPanel.entry.editor + KeyHandler.getInstance().handleKey(editor, keyStroke, EditorDataContext(editor, e.dataContext)) } } diff --git a/src/com/maddyhome/idea/vim/ui/ExTextField.java b/src/com/maddyhome/idea/vim/ui/ExTextField.java index e44ac08212..215d34956e 100644 --- a/src/com/maddyhome/idea/vim/ui/ExTextField.java +++ b/src/com/maddyhome/idea/vim/ui/ExTextField.java @@ -52,6 +52,8 @@ public class ExTextField extends JTextField { public static final @NonNls String KEYMAP_NAME = "ex"; + public boolean useHandleKeyFromEx = true; + ExTextField() { // We need to store this in a field, because we can't trust getCaret(), as it will return an instance of // ComposedTextCaret when working with dead keys or input methods @@ -282,7 +284,12 @@ public void handleKey(@NotNull KeyStroke stroke) { (stroke.isOnKeyRelease() ? KeyEvent.KEY_RELEASED : KeyEvent.KEY_PRESSED), (new Date()).getTime(), modifiers, keyCode, c); - super.processKeyEvent(event); + useHandleKeyFromEx = false; + try { + super.processKeyEvent(event); + }finally { + useHandleKeyFromEx = true; + } } } @@ -329,7 +336,7 @@ void setCurrentAction(@NotNull ExEditorKit.MultiStepAction action, char pendingI setCurrentActionPromptCharacter(pendingIndicator); } - void clearCurrentAction() { + public void clearCurrentAction() { if (currentAction != null) { currentAction.reset(); } @@ -337,7 +344,7 @@ void clearCurrentAction() { clearCurrentActionPromptCharacter(); } - void setCurrentActionPromptCharacter(char promptCharacter) { + public void setCurrentActionPromptCharacter(char promptCharacter) { actualText = removePromptCharacter(); this.currentActionPromptCharacter = promptCharacter; currentActionPromptCharacterOffset = currentActionPromptCharacterOffset == -1 ? getCaretPosition() : currentActionPromptCharacterOffset; diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index 906c7a8520..c737753cbc 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -453,12 +453,7 @@ abstract class VimTestCase : UsefulTestCase() { val inputModel = TestInputModel.getInstance(editor) var key = inputModel.nextKeyStroke() while (key != null) { - val exEntryPanel = ExEntryPanel.getInstance() - if (exEntryPanel.isActive) { - exEntryPanel.handleKey(key) - } else { - keyHandler.handleKey(editor, key, dataContext) - } + keyHandler.handleKey(editor, key, dataContext) key = inputModel.nextKeyStroke() } }, null, null) diff --git a/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java b/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java index f968f23141..28da5dc621 100644 --- a/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java +++ b/test/org/jetbrains/plugins/ideavim/action/MotionActionTest.java @@ -448,6 +448,11 @@ public void testReplaceEmptyTagContent() { myFixture.checkResult(""); } + public void testDeleteToDigraph() { + typeTextInFile(parseKeys("d/O:"),"abcdÖef"); + myFixture.checkResult("abÖef"); + } + // |[(| public void testUnmatchedOpenParenthesis() { typeTextInFile(parseKeys("[("), diff --git a/test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt b/test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt index a4ecdfe9c4..dfe310df9a 100644 --- a/test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt +++ b/test/org/jetbrains/plugins/ideavim/ex/ExEntryTest.kt @@ -550,6 +550,34 @@ class ExEntryTest : VimTestCase() { assertExText("hello") } + fun `test cmap`() { + typeExInput(":cmap x z") + typeExInput(":cnoremap w z") + typeExInput(":cmap z y") + typeExInput(":z") + assertExText("y") + deactivateExEntry() + + typeExInput(":x") + assertExText("y") + deactivateExEntry() + + typeExInput(":w") + assertExText("z") + } + + fun `test cmap Ctrl`() { + typeExInput(":cmap \\ b") + typeExInput(":") + assertExText("b") + deactivateExEntry() + + VimPlugin.getRegister().setKeys('e', StringHelper.parseKeys("hello world")) + typeExInput(":cmap d \\") + typeExInput(":de") + assertExText("hello world") + } + private fun typeExInput(text: String) { assertTrue("Ex command must start with ':', '/' or '?'", text.startsWith(":") || text.startsWith('/') || text.startsWith('?'))