From 6be6e7f173368a931574a01063a60e80b0f82e09 Mon Sep 17 00:00:00 2001 From: Iain Ballard Date: Mon, 22 Jun 2020 14:56:22 +0100 Subject: [PATCH] Partial implementation of virtualedit config This does not support all config settings, but does add the 'onemore' option. This partly addresses https://youtrack.jetbrains.com/issue/VIM-844 --- .../leftright/MotionArrowRightAction.kt | 5 +- .../motion/updown/MotionArrowDownAction.kt | 3 +- .../motion/updown/MotionArrowUpAction.kt | 3 +- .../action/motion/updown/MotionDownActions.kt | 5 +- .../motion/updown/MotionShiftDownAction.kt | 5 +- .../motion/updown/MotionShiftUpAction.kt | 5 +- .../action/motion/updown/MotionUpActions.kt | 5 +- .../idea/vim/command/CommandState.kt | 12 ++- .../maddyhome/idea/vim/group/ChangeGroup.java | 27 +++-- .../maddyhome/idea/vim/group/MotionGroup.java | 24 ++++- .../vim/handler/EditorActionHandlerBase.kt | 10 +- .../idea/vim/handler/MotionActionHandler.kt | 12 +-- .../idea/vim/helper/CommandStateExtensions.kt | 36 ++++++- .../idea/vim/option/OptionsManager.kt | 1 + .../jetbrains/plugins/ideavim/VimTestCase.kt | 5 +- .../delete/DeleteVisualLinesEndActionTest.kt | 40 ++++++++ .../motion/leftright/MotionRightActionTest.kt | 14 +++ .../updown/MotionArrowDownActionTest.kt | 98 +++++++++++++++++++ .../VisualToggleCharacterModeActionTest.kt | 27 +++++ .../ideavim/extension/CommonExtensionTests.kt | 4 +- 20 files changed, 293 insertions(+), 48 deletions(-) diff --git a/src/com/maddyhome/idea/vim/action/motion/leftright/MotionArrowRightAction.kt b/src/com/maddyhome/idea/vim/action/motion/leftright/MotionArrowRightAction.kt index 3ed12d8149..99513853c0 100644 --- a/src/com/maddyhome/idea/vim/action/motion/leftright/MotionArrowRightAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/leftright/MotionArrowRightAction.kt @@ -27,6 +27,8 @@ import com.maddyhome.idea.vim.command.Argument import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.helper.commandState +import com.maddyhome.idea.vim.helper.isEndAllowed import java.awt.event.KeyEvent import javax.swing.KeyStroke @@ -39,7 +41,8 @@ class MotionArrowRightAction : NonShiftedSpecialKeyHandler(), ComplicatedKeysAct ) override fun offset(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getMotion().moveCaretHorizontal(editor, caret, count, false) + val allowPastEnd = editor.commandState.isEndAllowed + return VimPlugin.getMotion().moveCaretHorizontal(editor, caret, count, allowPastEnd) } } diff --git a/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowDownAction.kt b/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowDownAction.kt index e4128eb2c8..4a99782d38 100644 --- a/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowDownAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowDownAction.kt @@ -29,6 +29,7 @@ import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.helper.inNormalMode import java.awt.event.KeyEvent import javax.swing.KeyStroke @@ -40,7 +41,7 @@ class MotionArrowDownAction : NonShiftedSpecialKeyHandler(), ComplicatedKeysActi private var col: Int = 0 override fun offset(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getMotion().moveCaretVertical(editor, caret, count) + return VimPlugin.getMotion().moveCaretVertical(editor, caret, count, editor.inNormalMode); } override fun preOffsetComputation(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean { diff --git a/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowUpAction.kt b/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowUpAction.kt index 0d3de8dc30..aac7577284 100644 --- a/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowUpAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/updown/MotionArrowUpAction.kt @@ -29,6 +29,7 @@ import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.NonShiftedSpecialKeyHandler import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.helper.inNormalMode import java.awt.event.KeyEvent import javax.swing.KeyStroke @@ -40,7 +41,7 @@ class MotionArrowUpAction : NonShiftedSpecialKeyHandler(), ComplicatedKeysAction private var col: Int = 0 override fun offset(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getMotion().moveCaretVertical(editor, caret, -count) + return VimPlugin.getMotion().moveCaretVertical(editor, caret, -count, editor.inNormalMode); } override fun preOffsetComputation(editor: Editor, caret: Caret, context: DataContext, cmd: Command): Boolean { diff --git a/src/com/maddyhome/idea/vim/action/motion/updown/MotionDownActions.kt b/src/com/maddyhome/idea/vim/action/motion/updown/MotionDownActions.kt index 21e5b423b8..d04eb2fd87 100644 --- a/src/com/maddyhome/idea/vim/action/motion/updown/MotionDownActions.kt +++ b/src/com/maddyhome/idea/vim/action/motion/updown/MotionDownActions.kt @@ -31,6 +31,7 @@ import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.inNormalMode sealed class MotionDownBase : MotionActionHandler.ForEachCaret() { private var col: Int = 0 @@ -50,7 +51,7 @@ open class MotionDownAction : MotionDownBase() { override val motionType: MotionType = MotionType.LINE_WISE override fun getOffset(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getMotion().moveCaretVertical(editor, caret, count) + return VimPlugin.getMotion().moveCaretVertical(editor, caret, count, editor.inNormalMode) } } @@ -75,6 +76,6 @@ class MotionDownNotLineWiseAction : MotionDownBase() { override val motionType: MotionType = MotionType.EXCLUSIVE override fun getOffset(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getMotion().moveCaretVertical(editor, caret, count) + return VimPlugin.getMotion().moveCaretVertical(editor, caret, count, editor.inNormalMode) } } diff --git a/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftDownAction.kt b/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftDownAction.kt index 58d6308b80..39211d989b 100644 --- a/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftDownAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftDownAction.kt @@ -24,8 +24,7 @@ import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler -import com.maddyhome.idea.vim.helper.EditorHelper -import com.maddyhome.idea.vim.helper.vimForEachCaret +import com.maddyhome.idea.vim.helper.* /** * @author Alex Plate @@ -37,7 +36,7 @@ class MotionShiftDownAction : ShiftedArrowKeyHandler() { override fun motionWithKeyModel(editor: Editor, context: DataContext, cmd: Command) { editor.vimForEachCaret { caret -> - val vertical = VimPlugin.getMotion().moveCaretVertical(editor, caret, cmd.count) + val vertical = VimPlugin.getMotion().moveCaretVertical(editor, caret, cmd.count, editor.inNormalMode) val col = EditorHelper.prepareLastColumn(editor, caret) MotionGroup.moveCaret(editor, caret, vertical) diff --git a/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftUpAction.kt b/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftUpAction.kt index 5130991405..f1cf9db135 100644 --- a/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftUpAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/updown/MotionShiftUpAction.kt @@ -24,8 +24,7 @@ import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.handler.ShiftedArrowKeyHandler -import com.maddyhome.idea.vim.helper.EditorHelper -import com.maddyhome.idea.vim.helper.vimForEachCaret +import com.maddyhome.idea.vim.helper.* /** * @author Alex Plate @@ -37,7 +36,7 @@ class MotionShiftUpAction : ShiftedArrowKeyHandler() { override fun motionWithKeyModel(editor: Editor, context: DataContext, cmd: Command) { editor.vimForEachCaret { caret -> - val vertical = VimPlugin.getMotion().moveCaretVertical(editor, caret, -cmd.count) + val vertical = VimPlugin.getMotion().moveCaretVertical(editor, caret, -cmd.count, editor.inNormalMode) val col = EditorHelper.prepareLastColumn(editor, caret) MotionGroup.moveCaret(editor, caret, vertical) diff --git a/src/com/maddyhome/idea/vim/action/motion/updown/MotionUpActions.kt b/src/com/maddyhome/idea/vim/action/motion/updown/MotionUpActions.kt index 67079bf6ea..054d0deedd 100644 --- a/src/com/maddyhome/idea/vim/action/motion/updown/MotionUpActions.kt +++ b/src/com/maddyhome/idea/vim/action/motion/updown/MotionUpActions.kt @@ -31,6 +31,7 @@ import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.MotionType import com.maddyhome.idea.vim.handler.MotionActionHandler import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.inNormalMode sealed class MotionUpBase : MotionActionHandler.ForEachCaret() { private var col: Int = 0 @@ -49,7 +50,7 @@ open class MotionUpAction : MotionUpBase() { override val motionType: MotionType = MotionType.LINE_WISE override fun getOffset(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getMotion().moveCaretVertical(editor, caret, -count) + return VimPlugin.getMotion().moveCaretVertical(editor, caret, -count, editor.inNormalMode) } } @@ -73,6 +74,6 @@ class MotionUpNotLineWiseAction : MotionUpBase() { override val motionType: MotionType = MotionType.EXCLUSIVE override fun getOffset(editor: Editor, caret: Caret, context: DataContext, count: Int, rawCount: Int, argument: Argument?): Int { - return VimPlugin.getMotion().moveCaretVertical(editor, caret, -count) + return VimPlugin.getMotion().moveCaretVertical(editor, caret, -count, editor.inNormalMode) } } diff --git a/src/com/maddyhome/idea/vim/command/CommandState.kt b/src/com/maddyhome/idea/vim/command/CommandState.kt index 9c855b2958..aac4bcf29d 100644 --- a/src/com/maddyhome/idea/vim/command/CommandState.kt +++ b/src/com/maddyhome/idea/vim/command/CommandState.kt @@ -21,11 +21,8 @@ import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.diagnostic.debug import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin -import com.maddyhome.idea.vim.command.CommandState -import com.maddyhome.idea.vim.helper.DigraphResult -import com.maddyhome.idea.vim.helper.DigraphSequence -import com.maddyhome.idea.vim.helper.noneOfEnum -import com.maddyhome.idea.vim.helper.vimCommandState +import com.maddyhome.idea.vim.action.motion.updown.* +import com.maddyhome.idea.vim.helper.* import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.option.OptionsManager.showmode import org.jetbrains.annotations.ApiStatus @@ -61,6 +58,8 @@ class CommandState private constructor() { var executingCommand: Command? = null private set + var previousCommand: Command? = null + // Keep the compatibility with the IdeaVim-EasyMotion plugin before the stable release @get:Deprecated("") @get:ApiStatus.ScheduledForRemoval(inVersion = "0.58") @@ -75,6 +74,9 @@ class CommandState private constructor() { } fun setExecutingCommand(cmd: Command) { + if (previousCommand != null && !cmd.isUpDownMotion){ + previousCommand = executingCommand // track the previous motion command for some edge cases around '$' + } executingCommand = cmd } diff --git a/src/com/maddyhome/idea/vim/group/ChangeGroup.java b/src/com/maddyhome/idea/vim/group/ChangeGroup.java index bde1c84ecc..e52183d65a 100644 --- a/src/com/maddyhome/idea/vim/group/ChangeGroup.java +++ b/src/com/maddyhome/idea/vim/group/ChangeGroup.java @@ -173,7 +173,7 @@ public void insertNewLineAbove(final @NotNull Editor editor, @NotNull DataContex firstLiners.add(caret); } else { - MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1)); + MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1, false)); MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLineEnd(editor, caret)); } } @@ -183,7 +183,7 @@ public void insertNewLineAbove(final @NotNull Editor editor, @NotNull DataContex for (Caret caret : editor.getCaretModel().getAllCarets()) { if (firstLiners.contains(caret)) { - MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1)); + MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1, false)); } } } @@ -204,7 +204,7 @@ private void insertNewLineAbove(@NotNull Editor editor, @NotNull Caret caret, in firstLiner = true; } else { - MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1)); + MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1, false)); MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretToLineEnd(editor, caret)); } @@ -212,7 +212,7 @@ private void insertNewLineAbove(@NotNull Editor editor, @NotNull Caret caret, in insertText(editor, caret, "\n" + IndentConfig.create(editor).createIndentBySize(col)); if (firstLiner) { - MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1)); + MotionGroup.moveCaret(editor, caret, VimPlugin.getMotion().moveCaretVertical(editor, caret, -1, false)); } } @@ -766,15 +766,26 @@ public void beforeProcessKey(final @NotNull Editor editor, * @return true if able to delete the text, false if not */ public boolean deleteEndOfLine(@NotNull Editor editor, @NotNull Caret caret, int count) { + int initialOffset = caret.getOffset(); int offset = VimPlugin.getMotion().moveCaretToLineEndOffset(editor, caret, count - 1, true); + + int startOffset = initialOffset; + if (offset == initialOffset) startOffset--; // handle delete from virtual space + if (offset != -1) { - final TextRange rangeToDelete = new TextRange(caret.getOffset(), offset); + final TextRange rangeToDelete = new TextRange(startOffset, offset); editor.getCaretModel().getAllCarets().stream().filter(c -> c != caret && rangeToDelete.contains(c.getOffset())) .forEach(c -> editor.getCaretModel().removeCaret(c)); boolean res = deleteText(editor, rangeToDelete, SelectionType.CHARACTER_WISE); - int pos = VimPlugin.getMotion().moveCaretHorizontal(editor, caret, -1, false); - if (pos != -1) { - MotionGroup.moveCaret(editor, caret, pos); + + if (CommandStateHelper.getUsesVirtualSpace()) { + MotionGroup.moveCaret(editor, caret, startOffset); + } + else { + int pos = VimPlugin.getMotion().moveCaretHorizontal(editor, caret, -1, false); + if (pos != -1) { + MotionGroup.moveCaret(editor, caret, pos); + } } return res; diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index dad5dbf150..ce8c317d7c 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -31,6 +31,7 @@ import com.intellij.openapi.vfs.VirtualFileSystem; import com.maddyhome.idea.vim.KeyHandler; import com.maddyhome.idea.vim.VimPlugin; +import com.maddyhome.idea.vim.action.motion.leftright.MotionLastColumnAction; import com.maddyhome.idea.vim.command.*; import com.maddyhome.idea.vim.common.Jump; import com.maddyhome.idea.vim.common.Mark; @@ -38,6 +39,7 @@ import com.maddyhome.idea.vim.ex.ExOutputModel; import com.maddyhome.idea.vim.group.visual.VimSelection; import com.maddyhome.idea.vim.group.visual.VisualGroupKt; +import com.maddyhome.idea.vim.handler.EditorActionHandlerBase; import com.maddyhome.idea.vim.handler.MotionActionHandler; import com.maddyhome.idea.vim.handler.TextObjectActionHandler; import com.maddyhome.idea.vim.helper.*; @@ -1150,7 +1152,7 @@ private void scrollLineToScreenLocation(@NotNull Editor editor, else { offset = moveCaretVertical(editor, editor.getCaretModel().getPrimaryCaret(), EditorHelper.visualLineToLogicalLine(editor, visualLine) - - editor.getCaretModel().getLogicalPosition().line); + editor.getCaretModel().getLogicalPosition().line, false); } moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), offset); @@ -1184,7 +1186,7 @@ public int moveCaretGotoNextTab(@NotNull Editor editor, @NotNull DataContext con return editor.getCaretModel().getOffset(); } - public int moveCaretVertical(@NotNull Editor editor, @NotNull Caret caret, int count) { + public int moveCaretVertical(@NotNull Editor editor, @NotNull Caret caret, int count, boolean useEndOfLineTracking) { VisualPosition pos = caret.getVisualPosition(); final LogicalPosition logicalPosition = caret.getLogicalPosition(); if ((pos.line == 0 && count < 0) || (pos.line >= EditorHelper.getVisualLineCount(editor) - 1 && count > 0)) { @@ -1217,12 +1219,28 @@ public int moveCaretVertical(@NotNull Editor editor, @NotNull Caret caret, int c col = EditorHelper .normalizeVisualColumn(editor, line, col, CommandStateHelper.isEndAllowed(CommandStateHelper.getMode(editor))); col += newInlineElements; - VisualPosition newPos = new VisualPosition(line, col); + if (useEndOfLineTracking && InLastColumnMode(editor) && !caret.hasSelection()) { + // we're in 'last column' mode (after pressing '$'); Run to last column + int newLastColumn = EditorHelper.lastColumnForLine(editor, line, CommandStateHelper.isEndAllowed(mode)); + col = newLastColumn; + } + + VisualPosition newPos = new VisualPosition(line, col); return EditorHelper.visualPositionToOffset(editor, newPos); } } + private boolean InLastColumnMode(@NotNull Editor editor) { + Command state = CommandStateHelper.getPreviousCommand(editor); + if (state == null) return false; + + EditorActionHandlerBase action = state.getAction(); + if (action == null) return false; + + return action instanceof MotionLastColumnAction; + } + public int moveCaretToLinePercent(@NotNull Editor editor, int count) { if (count > 100) count = 100; diff --git a/src/com/maddyhome/idea/vim/handler/EditorActionHandlerBase.kt b/src/com/maddyhome/idea/vim/handler/EditorActionHandlerBase.kt index 071dda6674..59704dfb2d 100644 --- a/src/com/maddyhome/idea/vim/handler/EditorActionHandlerBase.kt +++ b/src/com/maddyhome/idea/vim/handler/EditorActionHandlerBase.kt @@ -25,13 +25,11 @@ import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.actionSystem.CaretSpecificDataContext import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.action.motion.updown.* 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.helper.StringHelper -import com.maddyhome.idea.vim.helper.commandState -import com.maddyhome.idea.vim.helper.getTopLevelEditor -import com.maddyhome.idea.vim.helper.noneOfEnum +import com.maddyhome.idea.vim.helper.* import java.util.* import javax.swing.KeyStroke @@ -91,6 +89,10 @@ abstract class EditorActionHandlerBase(private val myRunForEachCaret: Boolean) { } if (!baseExecute(editor, caret, CaretSpecificDataContext(context, caret), cmd)) VimPlugin.indicateError() + + if (!cmd.isUpDownMotion){ + editor.previousCommand = cmd // track the previous motion command for some edge cases + } } open fun process(cmd: Command) { diff --git a/src/com/maddyhome/idea/vim/handler/MotionActionHandler.kt b/src/com/maddyhome/idea/vim/handler/MotionActionHandler.kt index b45ce30899..31fc521c76 100644 --- a/src/com/maddyhome/idea/vim/handler/MotionActionHandler.kt +++ b/src/com/maddyhome/idea/vim/handler/MotionActionHandler.kt @@ -24,17 +24,13 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.event.CaretEvent import com.intellij.openapi.editor.event.CaretListener import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.action.motion.updown.* 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.command.MotionType import com.maddyhome.idea.vim.group.MotionGroup -import com.maddyhome.idea.vim.helper.EditorHelper -import com.maddyhome.idea.vim.helper.inBlockSubMode -import com.maddyhome.idea.vim.helper.inVisualMode -import com.maddyhome.idea.vim.helper.isEndAllowed -import com.maddyhome.idea.vim.helper.mode -import com.maddyhome.idea.vim.helper.vimSelectionStart +import com.maddyhome.idea.vim.helper.* /** * @author Alex Plate @@ -140,7 +136,7 @@ sealed class MotionActionHandler : EditorActionHandlerBase(false) { if (CommandFlags.FLAG_SAVE_JUMP in cmd.flags) { VimPlugin.getMark().saveJumpLocation(editor) } - if (!editor.mode.isEndAllowed) { + if (!editor.commandState.isEndAllowed) { offset = EditorHelper.normalizeOffset(editor, offset, false) } preMove(editor, context, cmd) @@ -179,7 +175,7 @@ sealed class MotionActionHandler : EditorActionHandlerBase(false) { if (CommandFlags.FLAG_SAVE_JUMP in cmd.flags) { VimPlugin.getMark().saveJumpLocation(editor) } - if (!editor.mode.isEndAllowed) { + if (!editor.commandState.isEndAllowed) { offset = EditorHelper.normalizeOffset(editor, offset, false) } preMove(editor, caret, context, cmd) diff --git a/src/com/maddyhome/idea/vim/helper/CommandStateExtensions.kt b/src/com/maddyhome/idea/vim/helper/CommandStateExtensions.kt index 0d1be22998..71f6e84fb1 100644 --- a/src/com/maddyhome/idea/vim/helper/CommandStateExtensions.kt +++ b/src/com/maddyhome/idea/vim/helper/CommandStateExtensions.kt @@ -21,12 +21,27 @@ package com.maddyhome.idea.vim.helper import com.intellij.openapi.editor.Editor +import com.maddyhome.idea.vim.action.motion.leftright.MotionLastColumnAction +import com.maddyhome.idea.vim.action.motion.updown.* +import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.CommandState +import com.maddyhome.idea.vim.option.OptionsManager -val CommandState.Mode.isEndAllowed +val usesVirtualSpace + get() = OptionsManager.virtualedit.value == "onemore" + +val CommandState.Mode.isEndAllowed : Boolean get() = when (this) { CommandState.Mode.INSERT, CommandState.Mode.VISUAL, CommandState.Mode.SELECT -> true - CommandState.Mode.COMMAND, CommandState.Mode.CMD_LINE, CommandState.Mode.REPLACE, CommandState.Mode.OP_PENDING -> false + CommandState.Mode.COMMAND, CommandState.Mode.CMD_LINE, CommandState.Mode.REPLACE, CommandState.Mode.OP_PENDING -> usesVirtualSpace + } + +val CommandState.isEndAllowed:Boolean + get() { + val isAllowedByMode = this.mode.isEndAllowed + val trimCase = (this.mode == CommandState.Mode.COMMAND) && (this.previousCommand?.action is MotionLastColumnAction) + + return isAllowedByMode && !trimCase } val CommandState.Mode.isBlockCaret @@ -50,6 +65,22 @@ var Editor.subMode this.commandState.subMode = value } +var Editor.previousCommand: Command? + get() = this.commandState.previousCommand + set(value) { + this.commandState.previousCommand = value + } + +val Command.isUpDownMotion: Boolean + get() = ( + this.action is MotionDownBase + || this.action is MotionUpBase + || this.action is MotionUpAction + || this.action is MotionDownAction + || this.action is MotionArrowUpAction + || this.action is MotionArrowDownAction) + + @get:JvmName("inNormalMode") val Editor.inNormalMode get() = this.mode == CommandState.Mode.COMMAND @@ -78,5 +109,6 @@ val Editor.inBlockSubMode val Editor.inSingleCommandMode get() = this.subMode == CommandState.SubMode.SINGLE_COMMAND && this.inNormalMode +@get:JvmName("commandState") val Editor?.commandState get() = CommandState.getInstance(this) diff --git a/src/com/maddyhome/idea/vim/option/OptionsManager.kt b/src/com/maddyhome/idea/vim/option/OptionsManager.kt index 6d14eea79f..47a07d0c37 100644 --- a/src/com/maddyhome/idea/vim/option/OptionsManager.kt +++ b/src/com/maddyhome/idea/vim/option/OptionsManager.kt @@ -86,6 +86,7 @@ object OptionsManager { val idearefactormode = addOption(BoundStringOption(IdeaRefactorMode.name, IdeaRefactorMode.name, IdeaRefactorMode.select, IdeaRefactorMode.availableValues)) val ideastatusicon = addOption(BoundStringOption(IdeaStatusIcon.name, IdeaStatusIcon.name, IdeaStatusIcon.enabled, IdeaStatusIcon.allValues)) val ideastrictmode = addOption(ToggleOption("ideastrictmode", "ideastrictmode", false)) + val virtualedit = addOption(BoundStringOption("virtualedit","ve","", arrayOf("block", "insert", "all", "onemore") )) // Dev only experimental options val dialogescape = addOption(BoundStringOption("dialogescape", "de", "legacy", arrayOf("legacy", "on", "off"))) diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index 109936aa08..3c0a7d2e1c 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -43,12 +43,9 @@ import com.maddyhome.idea.vim.command.CommandState.SubMode import com.maddyhome.idea.vim.ex.ExOutputModel.Companion.getInstance import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment import com.maddyhome.idea.vim.group.visual.VimVisualTimer.swingTimer -import com.maddyhome.idea.vim.helper.EditorDataContext +import com.maddyhome.idea.vim.helper.* import com.maddyhome.idea.vim.helper.RunnableHelper.runWriteCommand -import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.helper.StringHelper.stringToKeys -import com.maddyhome.idea.vim.helper.TestInputModel -import com.maddyhome.idea.vim.helper.inBlockSubMode import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.option.OptionsManager.getOption import com.maddyhome.idea.vim.option.OptionsManager.ideastrictmode diff --git a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteVisualLinesEndActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteVisualLinesEndActionTest.kt index ef092b776b..bed024c607 100644 --- a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteVisualLinesEndActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteVisualLinesEndActionTest.kt @@ -23,6 +23,7 @@ package org.jetbrains.plugins.ideavim.action.change.delete import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers +import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase class DeleteVisualLinesEndActionTest : VimTestCase() { @@ -46,6 +47,45 @@ class DeleteVisualLinesEndActionTest : VimTestCase() { doTest(keys, before, after, CommandState.Mode.COMMAND, CommandState.SubMode.NONE) } + fun `test virtual edit delete middle to end`() { + OptionsManager.virtualedit.set("onemore") + doTest("D", """ + Yesterday it w${c}orked + Today it is not working + The test is like that. + """.trimIndent(), """ + Yesterday it w${c} + Today it is not working + The test is like that. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + } + + fun `test virtual edit delete end to end`() { + OptionsManager.virtualedit.set("onemore") + doTest("D", """ + Yesterday it worke${c}d + Today it is not working + The test is like that. + """.trimIndent(), """ + Yesterday it worke${c} + Today it is not working + The test is like that. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + } + + fun `test virtual edit delete to end from virtual space`() { + OptionsManager.virtualedit.set("onemore") + doTest("D", """ + Yesterday it worked${c} + Today it is not working + The test is like that. + """.trimIndent(), """ + Yesterday it worke${c} + Today it is not working + The test is like that. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + } + @VimBehaviorDiffers(originalVimAfter = """ A Discovery diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt index 8514928b4a..f3af34e0a3 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionRightActionTest.kt @@ -23,6 +23,7 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright import com.maddyhome.idea.vim.command.CommandState import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.TestWithoutNeovim +import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase class MotionRightActionTest : VimTestCase() { @@ -80,6 +81,19 @@ class MotionRightActionTest : VimTestCase() { """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) } + fun `test virtual edit motion to the end`() { + OptionsManager.virtualedit.set("onemore") + doTest("3l", """ + Yesterday it worke${c}d + Today it is not working + The test is like that. + """.trimIndent(), """ + Yesterday it worked${c} + Today it is not working + The test is like that. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + } + @TestWithoutNeovim(SkipNeovimReason.NON_ASCII) fun `test simple motion non-ascii`() { doTest("l", """ diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/updown/MotionArrowDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/updown/MotionArrowDownActionTest.kt index 32dc8e48b2..1ee54889d6 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/updown/MotionArrowDownActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/updown/MotionArrowDownActionTest.kt @@ -22,6 +22,7 @@ package org.jetbrains.plugins.ideavim.action.motion.updown import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.option.KeyModelOptionData +import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.VimOptionDefaultAll @@ -170,6 +171,103 @@ class MotionArrowDownActionTest : VimOptionTestCase(KeyModelOptionData.name) { CommandState.SubMode.VISUAL_CHARACTER) } + @VimOptionTestConfiguration(VimTestOption(KeyModelOptionData.name, VimTestOptionType.LIST, [])) + fun `test virtual edit down to shorter line`() { + OptionsManager.virtualedit.set("onemore") + doTest(listOf(""), """ + class MyClass ${c}{ + } + """.trimIndent(), """ + class MyClass { + }${c} + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + } + + @VimOptionTestConfiguration(VimTestOption(KeyModelOptionData.name, VimTestOptionType.LIST, [])) + fun `test virtual edit down to shorter line after dollar`() { + OptionsManager.virtualedit.set("onemore") + doTest(listOf("$", ""), """ + class ${c}MyClass { + } + """.trimIndent(), """ + class MyClass { + ${c}} + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + } + + @VimOptionTestConfiguration(VimTestOption(KeyModelOptionData.name, VimTestOptionType.LIST, [])) + fun `test up and down after dollar`() { + OptionsManager.virtualedit.set("onemore") + // Once you press '$', then any up or down actions stay on the end of the current line. + // Any non up/down action breaks this. + var start =""" + what ${c}a long line I am + yet I am short + Lo and behold, I am the longest yet + nope. + """.trimIndent() + + // Arrow keys + + doTest(listOf("$", ""), start, """ + what a long line I am + yet I am shor${c}t + Lo and behold, I am the longest yet + nope. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + + doTest(listOf("$", "", ""), start, """ + what a long line I am + yet I am short + Lo and behold, I am the longest ye${c}t + nope. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + + doTest(listOf("$", "", "", ""), start, """ + what a long line I am + yet I am short + Lo and behold, I am the longest yet + nope${c}. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + + doTest(listOf("$", "", "", "", ""), start, """ + what a long line I am + yet I am short + Lo and behold, I am the longest ye${c}t + nope. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + + // j k keys + + doTest(listOf("$", "j"), start, """ + what a long line I am + yet I am shor${c}t + Lo and behold, I am the longest yet + nope. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + + doTest(listOf("$", "j", "j"), start, """ + what a long line I am + yet I am short + Lo and behold, I am the longest ye${c}t + nope. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + + doTest(listOf("$", "j", "j", "j"), start, """ + what a long line I am + yet I am short + Lo and behold, I am the longest yet + nope${c}. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + + doTest(listOf("$", "j", "j", "j", "k"), start, """ + what a long line I am + yet I am short + Lo and behold, I am the longest ye${c}t + nope. + """.trimIndent(), CommandState.Mode.COMMAND, CommandState.SubMode.NONE) + } + @TestWithoutNeovim(SkipNeovimReason.OPTION) @VimOptionTestConfiguration(VimTestOption(KeyModelOptionData.name, VimTestOptionType.LIST, [KeyModelOptionData.stopselect])) fun `test char select simple move`() { diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/visual/VisualToggleCharacterModeActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/visual/VisualToggleCharacterModeActionTest.kt index bc4d671643..d6df00d6e5 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/visual/VisualToggleCharacterModeActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/visual/VisualToggleCharacterModeActionTest.kt @@ -381,11 +381,20 @@ class VisualToggleCharacterModeActionTest : VimTestCase() { where it was settled on some sodden sand hard by the torrent of a mountain pass. """.trimIndent(), + // Correct vim behaviour: + /*""" + A Discovery + + Iall rocks and lavender and tufted grass, + w${s}here it was settled on some sodden sand${c}${se} + hard by the torrent of a mountain pass. + """.trimIndent(),*/ CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER) } @VimBehaviorDiffers(description = "Different caret position") fun `test enter visual with count with dollar motion and down movement`() { + // expect to see switches v, $, d, v. doTest(listOf("v\$dj", "1v", "j"), """ A Discovery @@ -403,6 +412,15 @@ class VisualToggleCharacterModeActionTest : VimTestCase() { where it was settled on some sodden sand[long line]${c}${se} hard by the torrent of a mountain pass. """.trimIndent(), + // Correct vim behaviour: + /* """ + A Discovery + + I + all rocks and lavender and tufted grass, + w${s}here it was settled on some sodden sand[long line] + hard by the torrent of a mountain pass.${c}${se} + """.trimIndent(),*/ CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_CHARACTER) } @@ -634,6 +652,15 @@ class VisualToggleCharacterModeActionTest : VimTestCase() { ${s}where it was settled on some sodden sand[long line${c}]${se} ${s}hard by the torrent of a mountain pass.${c}${se} """.trimIndent(), + // correct vim behaviour + /*""" + A Discovery + + I + a + w${s}here it was settled on some sodden sand[long line${c}]${se} + h${s}ard by the torrent of a mountain pass.${c}${se} + """.trimIndent(),*/ CommandState.Mode.VISUAL, CommandState.SubMode.VISUAL_BLOCK) } diff --git a/test/org/jetbrains/plugins/ideavim/extension/CommonExtensionTests.kt b/test/org/jetbrains/plugins/ideavim/extension/CommonExtensionTests.kt index 7a609f74ee..223dba5f55 100644 --- a/test/org/jetbrains/plugins/ideavim/extension/CommonExtensionTests.kt +++ b/test/org/jetbrains/plugins/ideavim/extension/CommonExtensionTests.kt @@ -29,6 +29,8 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping import com.maddyhome.idea.vim.extension.VimExtensionHandler import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.helper.inNormalMode +import com.maddyhome.idea.vim.helper.inSingleCommandMode import com.maddyhome.idea.vim.helper.isEndAllowed import com.maddyhome.idea.vim.helper.mode import org.jetbrains.plugins.ideavim.SkipNeovimReason @@ -192,7 +194,7 @@ private class TestExtension : VimExtension { override fun execute(editor: Editor, context: DataContext) { VimPlugin.getVisualMotion().enterVisualMode(editor, CommandState.SubMode.VISUAL_LINE) val caret = editor.caretModel.currentCaret - val newOffset = VimPlugin.getMotion().moveCaretVertical(editor, caret, 1) + val newOffset = VimPlugin.getMotion().moveCaretVertical(editor, caret, 1, editor.inNormalMode) MotionGroup.moveCaret(editor, caret, newOffset) } }