From 589e43f82591a4f8aa9eaa3746a4341f096e5b1c Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 20 Aug 2020 11:45:39 +0100 Subject: [PATCH 01/31] [VIM-2104] Use side scroll offset for horizontal scrolling --- src/com/maddyhome/idea/vim/group/MotionGroup.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index dad5dbf150..db4210b279 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -671,7 +671,7 @@ public static void scrollPositionIntoView(@NotNull Editor editor, int width = EditorHelper.getScreenWidth(editor); final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP); - scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); + scrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); scrollJumpSize = 0; if (scrollJump) { scrollJumpSize = Math.max(0, OptionsManager.INSTANCE.getSidescroll().value() - 1); From 4cebaa865b977dc8decdead87a6fd55f1830d864 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 20 Aug 2020 14:21:53 +0100 Subject: [PATCH 02/31] Fix scrolljump --- .../motion/scroll/MotionScrollHalfPageDownAction.kt | 5 +++++ .../action/motion/scroll/MotionScrollLineDownAction.kt | 5 +++++ .../vim/action/motion/scroll/MotionScrollLineUpAction.kt | 5 +++++ src/com/maddyhome/idea/vim/command/CommandFlags.kt | 9 +++++++++ src/com/maddyhome/idea/vim/group/MotionGroup.java | 2 +- 5 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfPageDownAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfPageDownAction.kt index 7e87228644..7e8b93a60c 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfPageDownAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfPageDownAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollHalfPageDownAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollScreen(editor, cmd.rawCount, true) } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineDownAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineDownAction.kt index 97ab6183a7..6403301408 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineDownAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineDownAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollLineDownAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLine(editor, cmd.count) } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineUpAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineUpAction.kt index 95de6cdbc4..85c0561495 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineUpAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLineUpAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollLineUpAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLine(editor, -cmd.count) } diff --git a/src/com/maddyhome/idea/vim/command/CommandFlags.kt b/src/com/maddyhome/idea/vim/command/CommandFlags.kt index b157d4061c..9262947a0f 100644 --- a/src/com/maddyhome/idea/vim/command/CommandFlags.kt +++ b/src/com/maddyhome/idea/vim/command/CommandFlags.kt @@ -53,6 +53,15 @@ enum class CommandFlags { * This keystroke should be saved as part of the current insert */ FLAG_SAVE_STROKE, + + /** + * Don't include scrolljump when adjusting the scroll area to ensure the current cursor position is visible. + * Should be used for commands that adjust the scroll area (such as or ). + * Technically, the current implementation doesn't need these flags, as these commands adjust the scroll area + * according to their own rules and then move the cursor to fit (e.g. move cursor down a line with ). Moving the + * cursor always tries to adjust the scroll area to ensure it's visible, which in this case is always a no-op. + * This is an implementation detail, so keep the flags for both documentation and in case of refactoring. + */ FLAG_IGNORE_SCROLL_JUMP, FLAG_IGNORE_SIDE_SCROLL_JUMP, diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index db4210b279..51e7ace37e 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -603,7 +603,7 @@ public boolean scrollColumnToLastScreenColumn(@NotNull Editor editor) { public static void scrollCaretIntoView(@NotNull Editor editor) { final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); - final boolean scrollJump = flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP); + final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP); scrollPositionIntoView(editor, editor.getCaretModel().getVisualPosition(), scrollJump); } From d08da77b2f55712ded8febdcce5e8e1405a47e35 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 20 Aug 2020 14:38:57 +0100 Subject: [PATCH 03/31] Split scrollPositionIntoView method into two --- .../maddyhome/idea/vim/group/MotionGroup.java | 35 ++++++++++++------- 1 file changed, 23 insertions(+), 12 deletions(-) diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index 51e7ace37e..1852713b2b 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -139,7 +139,6 @@ else if (cmd.getAction() instanceof TextObjectActionHandler) { // If we are a linewise motion we need to normalize the start and stop then move the start to the beginning // of the line and move the end to the end of the line. - EnumSet flags = cmd.getFlags(); if (cmd.isLinewiseMotion()) { if (caret.getLogicalPosition().line != EditorHelper.getLineCount(editor) - 1) { start = EditorHelper.getLineStartForOffset(editor, start); @@ -602,18 +601,23 @@ public boolean scrollColumnToLastScreenColumn(@NotNull Editor editor) { } public static void scrollCaretIntoView(@NotNull Editor editor) { - final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); - final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP); - scrollPositionIntoView(editor, editor.getCaretModel().getVisualPosition(), scrollJump); + scrollPositionIntoView(editor, editor.getCaretModel().getVisualPosition()); } public static void scrollPositionIntoView(@NotNull Editor editor, - @NotNull VisualPosition position, - boolean scrollJump) { + @NotNull VisualPosition position) { + scrollPositionIntoViewVertically(editor, position); + scrollPositionIntoViewHorizontally(editor, position); + } + + private static void scrollPositionIntoViewVertically(@NotNull Editor editor, + @NotNull VisualPosition position) { final int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor); final int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); final int visualLine = position.line; - final int column = position.column; + + final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); + final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP); // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); @@ -666,13 +670,19 @@ public static void scrollPositionIntoView(@NotNull Editor editor, } } } + } + + private static void scrollPositionIntoViewHorizontally(@NotNull Editor editor, + @NotNull VisualPosition position) { + final int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor); + final int column = position.column; + final int width = EditorHelper.getScreenWidth(editor); - int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor); - int width = EditorHelper.getScreenWidth(editor); final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); - scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP); - scrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); - scrollJumpSize = 0; + final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP); + + int scrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); + int scrollJumpSize = 0; if (scrollJump) { scrollJumpSize = Math.max(0, OptionsManager.INSTANCE.getSidescroll().value() - 1); if (scrollJumpSize == 0) { @@ -693,6 +703,7 @@ public static void scrollPositionIntoView(@NotNull Editor editor, scrollJumpSize = Math.min(scrollJumpSize, width / 2 - scrollOffset); + int diff; if (column < visualLeft) { diff = column - visualLeft + 1; scrollJumpSize = -scrollJumpSize; From d878c3e05d74974713e3052829cf409a43e7e432 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Tue, 1 Sep 2020 17:43:04 +0100 Subject: [PATCH 04/31] Improve handling of scrolljump Now very closely follows Vim's somewhat unintuitive handling. Doesn't work properly with soft wraps (like a lot of other parts of IdeaVim) --- .../maddyhome/idea/vim/group/MotionGroup.java | 151 +++++++---- .../idea/vim/helper/EditorHelper.java | 5 +- .../jetbrains/plugins/ideavim/VimTestCase.kt | 50 +++- .../MotionGroup_ScrollCaretIntoView_Test.kt | 246 ++++++++++++++++++ 4 files changed, 386 insertions(+), 66 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index 1852713b2b..f6d07c6338 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -610,66 +610,119 @@ public static void scrollPositionIntoView(@NotNull Editor editor, scrollPositionIntoViewHorizontally(editor, position); } - private static void scrollPositionIntoViewVertically(@NotNull Editor editor, - @NotNull VisualPosition position) { - final int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor); - final int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); - final int visualLine = position.line; + // Vim's version of this method is move.c:update_topline, which will first scroll to fit the current line number at + // the top of the window and then ensure that the current line fits at the bottom of the window + private static void scrollPositionIntoViewVertically(@NotNull Editor editor, @NotNull VisualPosition position) { - final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); - final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP); - - // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred - int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); - - int scrollJumpSize = 0; - if (scrollJump) { - scrollJumpSize = Math.max(0, OptionsManager.INSTANCE.getScrolljump().value() - 1); - } + // TODO: Make this work with soft wraps + // Vim's algorithm works counts line heights for wrapped lines. We're using visual lines, which handles collapsed + // folds, but treats soft wrapped lines as individual lines. + // Ironically, after figuring out how Vim's algorithm works (although not *why*), it looks likely to be rewritten as + // a dumb line for line reimplementation. - int visualTop = topVisualLine + scrollOffset; - int visualBottom = bottomVisualLine - scrollOffset + 1; - if (visualTop == visualBottom) { - visualBottom++; - } + final int topLine = EditorHelper.getVisualLineAtTopOfScreen(editor); + final int bottomLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); + final int lineToMakeVisible = position.line; - int diff; - if (visualLine < visualTop) { - diff = visualLine - visualTop; - scrollJumpSize = -scrollJumpSize; + // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred + final int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); + final int topBound = topLine + scrollOffset; + final int bottomBound = Math.max(topBound + 1, bottomLine - scrollOffset); + + // If we need to scroll the current line more than half a screen worth of lines then we just centre the new + // current line. This mimics vim behavior of e.g. 100G in a 300 line file with a screen size of 25 centering line + // 100. It also handles so=999 keeping the current line centred. + // Note that block inlays means that the pixel height we are scrolling can be larger than half the screen, even if + // the number of lines is less. I'm not sure what impact this has. + final int height = bottomLine - topLine + 1; + final int halfHeight = Math.max(2, (height / 2) - 1); + + // Scrolljump isn't handled as you might expect. It is the minimal number of lines to scroll, but that doesn't mean + // newLine = lineToMakeVisible +/- MAX(sj, so) + // When scrolling up (`k` - scrolling window up in the buffer; more lines are visible at the top of the window), Vim + // will select the new top line and repeatedly move it up until it's scrolled at least scrolljump or scroll lines, + // whichever is larger. Unintuitively, the initial scroll to set the new top line counts as 1 against scrolljump, + // even if it's across several lines, while each subsequent line is a single scrolled line. + // This means scrolling up is essentially (lineToMakeVisible + max(so, sj-1)) + // (See move.c:scroll_cursor_top) + // When scrolling down (`j` - scrolling window down in the buffer; more lines are visible at the bottom), Vim does + // something different, selecting a new bottom line and repeatedly advancing lines above and below. The total + // number of lines expanded is at least scrolljump and there must be at least scrolloff lines below. + // Since the lines are advancing simultaneously, it is only possible to get scrolljump/2 above the new cursor line. + // If there are fewer than scrolljump/2 lines between the current bottom line and the new cursor line, the extra + // lines are pushed below the new cursor line. Due to the algorithm advancing the "above" line before the "below" + // line, we can end up with more than just scrolljump/2 lines on the top (hence the sj+1). + // Therefore, the new top line is (cln + max(so, sj - min(cln-bl, ceiling((sj + 1)/2)))) + // (where cln is lineToMakeVisible, bl is bottomLine, so is scrolloff and sj is scrolljump) + // (See move.c:scroll_cursor_bot) + // On top of that, if the scroll distance is "too large", the new cursor line is positioned in the centre of the + // screen. What "too large" means depends on scroll direction. + final int scrollJump = getScrollJumpSize(editor, height); + + if (lineToMakeVisible < topBound) { + // Scrolling up, put the cursor at the top of the window (minus scrolloff) + if (topLine + scrollOffset - lineToMakeVisible >= halfHeight) { + EditorHelper.scrollVisualLineToMiddleOfScreen(editor, lineToMakeVisible); + } + else { + final int newTopLine = Math.max(0, lineToMakeVisible - Math.max(scrollOffset, (scrollJump - 1))); + EditorHelper.scrollVisualLineToTopOfScreen(editor, newTopLine); + } } - else { - diff = Math.max(0, visualLine - visualBottom + 1); + else if (lineToMakeVisible > bottomBound) { + // Scrolling down, put the cursor at the bottom of the window (minus scrolloff) + // Vim does a quick approximation before going through the full algorithm. It checks the line below the bottom + // line in the window (bottomLine + 1). See move.c:update_topline + int lineCount = lineToMakeVisible - (bottomLine + 1) + 1 + scrollOffset; + if (lineCount > height) { + EditorHelper.scrollVisualLineToMiddleOfScreen(editor, lineToMakeVisible); + } else { + // Vim expands out from lineToMakeVisible at least scrolljump lines. It stops expanding above when it hits the + // current bottom line, or (because it's expanding above and below) when it's scrolled scrolljump/2. It expands + // above first, and the initial scroll count is 1, so we used (scrolljump+1)/2 + final int scrolledAbove = lineToMakeVisible - bottomLine; + final int extra = Math.max(scrollOffset, scrollJump - Math.min(scrolledAbove, Math.round((scrollJump + 1) / 2.0f))); + final int scrolled = scrolledAbove + extra; + + // "used" is the count of lines expanded above and below. We expand below until we hit EOF (or when we've + // expanded over a screen full) or until we've scrolled enough and we've expanded at least linesAbove + // We expand above until usedAbove + usedBelow >= height. Or until we've scrolled enough (scrolled > sj and extra > so) + // and we've expanded at least linesAbove (and at most, linesAbove - scrolled - scrolledAbove - 1) + // The minus one is for the current line + //noinspection UnnecessaryLocalVariable + final int usedAbove = scrolledAbove; + final int usedBelow = Math.min(EditorHelper.getVisualLineCount(editor) - lineToMakeVisible, usedAbove - 1); + final int used = Math.min(height + 1, usedAbove + usedBelow); + + // If we've expanded more than a screen full, redraw with the cursor in the middle of the screen. If we're going + // scroll more than a screen full or more than scrolloff, redraw with the cursor in the middle of the screen. + lineCount = used > height ? used : scrolled; + if (lineCount >= height && lineCount > scrollOffset) { + EditorHelper.scrollVisualLineToMiddleOfScreen(editor, lineToMakeVisible); + } + else { + EditorHelper.scrollVisualLineToBottomOfScreen(editor, lineToMakeVisible + extra); + } + } } + } - if (diff != 0) { + private static int getScrollJumpSize(@NotNull Editor editor, int height) { + final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); + final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP); - // If we need to scroll the current line more than half a screen worth of lines then we just centre the new - // current line. This mimics vim behavior of e.g. 100G in a 300 line file with a screen size of 25 centering line - // 100. It also handles so=999 keeping the current line centred. - // It doesn't handle keeping the line centred when scroll offset is less than a full page height, as the new line - // might be within e.g. top + scroll offset, so we test for that separately. - // Note that block inlays means that the pixel height we are scrolling can be larger than half the screen, even if - // the number of lines is less. I'm not sure what impact this has. - int height = bottomVisualLine - topVisualLine + 1; - if (Math.abs(diff) > height / 2 || scrollOffset > height / 2) { - EditorHelper.scrollVisualLineToMiddleOfScreen(editor, visualLine); + // Default value is 1. Zero is a valid value, but we normalise to 1 - we always want to scroll at least one line + // If the value is negative, it's a percentage of the height. + if (scrollJump) { + final int scrollJumpSize = OptionsManager.INSTANCE.getScrolljump().value(); + if (scrollJumpSize < 0) { + return height * (Math.min(100, -scrollJumpSize) / 100); } else { - // Put the new cursor line "scrolljump" lines from the top/bottom. Ensure that the line is fully visible, - // including block inlays above/below the line - if (diff > 0) { - int resLine = bottomVisualLine + diff + scrollJumpSize; - EditorHelper.scrollVisualLineToBottomOfScreen(editor, resLine); - } - else { - int resLine = topVisualLine + diff + scrollJumpSize; - resLine = Math.min(resLine, EditorHelper.getVisualLineCount(editor) - height); - resLine = Math.max(0, resLine); - EditorHelper.scrollVisualLineToTopOfScreen(editor, resLine); - } + return Math.max(1, scrollJumpSize); } } + return 1; } private static void scrollPositionIntoViewHorizontally(@NotNull Editor editor, diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index d112d62cd2..2ed4cc2885 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -670,15 +670,14 @@ public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, int public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, int visualLine) { int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, false); int exPanelHeight = 0; - int exPanelWithoutShortcutsHeight = 0; if (ExEntryPanel.getInstance().isActive()) { exPanelHeight = ExEntryPanel.getInstance().getHeight(); } if (ExEntryPanel.getInstanceWithoutShortcuts().isActive()) { - exPanelWithoutShortcutsHeight = ExEntryPanel.getInstanceWithoutShortcuts().getHeight(); + exPanelHeight += ExEntryPanel.getInstanceWithoutShortcuts().getHeight(); } int y = editor.visualLineToY(visualLine); - int height = inlayHeight + editor.getLineHeight() + exPanelHeight + exPanelWithoutShortcutsHeight; + int height = inlayHeight + editor.getLineHeight() + exPanelHeight; Rectangle visibleArea = getVisibleArea(editor); return scrollVertically(editor, y - visibleArea.height + height); } diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index 109936aa08..f108d26b40 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -28,6 +28,7 @@ import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx +import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.PlainTextFileType import com.intellij.openapi.project.Project import com.intellij.testFramework.EditorTestUtil @@ -43,12 +44,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 @@ -77,6 +75,7 @@ abstract class VimTestCase : UsefulTestCase() { LightTempDirTestFixtureImpl(true)) myFixture.setUp() myFixture.testDataPath = testDataPath + // Note that myFixture.editor is usually null here. It's only set once configureByText has been called KeyHandler.getInstance().fullReset(myFixture.editor) resetAllOptions() VimPlugin.getKey().resetKeyMappings() @@ -123,24 +122,36 @@ abstract class VimTestCase : UsefulTestCase() { return typeText(keys) } - protected fun configureByText(content: String): Editor { - myFixture.configureByText(PlainTextFileType.INSTANCE, content) - return myFixture.editor + protected val screenWidth: Int + get() = 80 + protected val screenHeight: Int + get() = 35 + + protected fun setEditorVisibleSize() { + EditorTestUtil.setEditorVisibleSize(myFixture.editor, screenWidth, screenHeight) } + protected fun configureByText(content: String) = configureByText(PlainTextFileType.INSTANCE, content) + protected fun configureByJavaText(content: String) = configureByText(JavaFileType.INSTANCE, content) + protected fun configureByXmlText(content: String) = configureByText(XmlFileType.INSTANCE, content) - protected fun configureByFileName(fileName: String): Editor { - myFixture.configureByText(fileName, "\n") + private fun configureByText(fileType: FileType, content: String): Editor { + myFixture.configureByText(fileType, content) + setEditorVisibleSize() return myFixture.editor } - protected fun configureByJavaText(content: String): Editor { - myFixture.configureByText(JavaFileType.INSTANCE, content) + protected fun configureByFileName(fileName: String): Editor { + myFixture.configureByText(fileName, "\n") + setEditorVisibleSize() return myFixture.editor } - protected fun configureByXmlText(content: String): Editor { - myFixture.configureByText(XmlFileType.INSTANCE, content) - return myFixture.editor + protected fun configureLargeText(lineCount: Int) { + val stringBuilder = StringBuilder() + repeat(lineCount) { + stringBuilder.appendln("I found it in a legendary land") + } + configureByText(stringBuilder.toString()) } protected fun typeText(keys: List): Editor { @@ -182,6 +193,17 @@ abstract class VimTestCase : UsefulTestCase() { } } + // Use logical rather than visual lines, so we can correctly test handling of collapsed folds and soft wraps + fun assertVisibleArea(topLogicalLine: Int, bottomLogicalLine: Int) { + val actualVisualTop = EditorHelper.getVisualLineAtTopOfScreen(myFixture.editor) + val actualLogicalTop = EditorHelper.visualLineToLogicalLine(myFixture.editor, actualVisualTop) + val actualVisualBottom = EditorHelper.getVisualLineAtBottomOfScreen(myFixture.editor) + val actualLogicalBottom = EditorHelper.visualLineToLogicalLine(myFixture.editor, actualVisualBottom) + + Assert.assertEquals("Top logical lines don't match", topLogicalLine, actualLogicalTop) + Assert.assertEquals("Bottom logical lines don't match", bottomLogicalLine, actualLogicalBottom) + } + fun assertMode(expectedMode: CommandState.Mode) { val mode = CommandState.getInstance(myFixture.editor).mode Assert.assertEquals(expectedMode, mode) diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt new file mode 100644 index 0000000000..224ac95f0d --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt @@ -0,0 +1,246 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.group.motion + +import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +@Suppress("ClassName") +class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { + fun `test moving up causes scrolling up`() { + configureLargeText(200) + setPositionAndScroll(19, 24) + + typeText(parseKeys("12k")) + assertPosition(12, 0) + assertVisibleArea(12, 46) + } + + fun `test scroll up with scrolljump`() { + OptionsManager.scrolljump.set(10) + configureLargeText(200) + setPositionAndScroll(19, 24) + + typeText(parseKeys("12k")) + assertPosition(12, 0) + assertVisibleArea(3, 37) + } + + fun `test scroll up with scrolloff`() { + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(19, 29) + + typeText(parseKeys("12k")) + assertPosition(17, 0) + assertVisibleArea(12, 46) + } + + fun `test scroll up with scrolljump and scrolloff 1`() { + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + + setPositionAndScroll(19, 29) + typeText(parseKeys("12k")) + assertPosition(17, 0) + assertVisibleArea(8, 42) + } + + fun `test scroll up with scrolljump and scrolloff 2`() { + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(29, 39) + + typeText(parseKeys("20k")) + assertPosition(19, 0) + assertVisibleArea(10, 44) + } + + fun `test scroll up with collapsed folds`() { + configureLargeText(200) + // TODO: Implement zf + typeText(parseKeys("40G", "Vjjjj", ":'<,'>action CollapseSelection", "V")) + setPositionAndScroll(29, 49) + + typeText(parseKeys("30k")) + assertPosition(15, 0) + assertVisibleArea(15, 53) + } + + // TODO: Handle soft wraps +// fun `test scroll up with soft wraps`() { +// } + + fun `test scroll up more than half height moves caret to middle 1`() { + configureLargeText(200) + setPositionAndScroll(114, 149) + + typeText(parseKeys("50k")) + assertPosition(99, 0) + assertVisualLineAtMiddleOfScreen(99) + } + + fun `test scroll up more than half height moves caret to middle with scrolloff`() { + configureLargeText(200) + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(5) + setPositionAndScroll(99, 109) + assertPosition(109, 0) + + typeText(parseKeys("21k")) + assertPosition(88, 0) + assertVisualLineAtMiddleOfScreen(88) + } + + fun `test scroll up with less than half height moves caret to top of screen`() { + configureLargeText(200) + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(5) + setPositionAndScroll(99, 109) + + typeText(parseKeys("20k")) + assertPosition(89, 0) + assertVisibleArea(80, 114) + } + + fun `test moving down causes scrolling down`() { + configureLargeText(200) + setPositionAndScroll(0, 29) + + typeText(parseKeys("12j")) + assertPosition(41, 0) + assertVisibleArea(7, 41) + } + + fun `test scroll down with scrolljump`() { + OptionsManager.scrolljump.set(10) + configureLargeText(200) + setPositionAndScroll(0, 29) + + typeText(parseKeys("12j")) + assertPosition(41, 0) + assertVisibleArea(11, 45) + } + + fun `test scroll down with scrolloff`() { + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(0, 24) + + typeText(parseKeys("12j")) + assertPosition(36, 0) + assertVisibleArea(7, 41) + } + + fun `test scroll down with scrolljump and scrolloff 1`() { + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(0, 24) + + typeText(parseKeys("12j")) + assertPosition(36, 0) + assertVisibleArea(10, 44) + } + + fun `test scroll down with scrolljump and scrolloff 2`() { + OptionsManager.scrolljump.set(15) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(0, 24) + + typeText(parseKeys("20j")) + assertPosition(44, 0) + assertVisibleArea(17, 51) + } + + fun `test scroll down with scrolljump and scrolloff 3`() { + OptionsManager.scrolljump.set(20) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(0, 24) + + typeText(parseKeys("25j")) + assertPosition(49, 0) + assertVisibleArea(24, 58) + } + + fun `test scroll down with scrolljump and scrolloff 4`() { + OptionsManager.scrolljump.set(11) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(0, 24) + + typeText(parseKeys("12j")) + assertPosition(36, 0) + assertVisibleArea(11, 45) + } + + fun `test scroll down with scrolljump and scrolloff 5`() { + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(0, 29) + + typeText(parseKeys("12j")) + assertPosition(41, 0) + assertVisibleArea(12, 46) + } + + fun `test scroll down with scrolljump and scrolloff 6`() { + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(5) + configureLargeText(200) + setPositionAndScroll(0, 24) + + typeText(parseKeys("20j")) + assertPosition(44, 0) + assertVisibleArea(15, 49) + } + + fun `test scroll down too large cursor is centred`() { + OptionsManager.scrolljump.set(10) + OptionsManager.scrolloff.set(10) + configureLargeText(200) + setPositionAndScroll(0, 19) + + typeText(parseKeys("35j")) + assertPosition(54, 0) + assertVisualLineAtMiddleOfScreen(54) + } + + // 0-based + private fun setPositionAndScroll(scrollToLogicalLine: Int, caretLogicalLine: Int) { + val scrolloff = OptionsManager.scrolloff.value + val scrolljump = OptionsManager.scrolljump.value + OptionsManager.scrolloff.set(0) + OptionsManager.scrolljump.set(1) + typeText(parseKeys("${scrollToLogicalLine+1}z", "${caretLogicalLine+1}G")) + OptionsManager.scrolloff.set(scrolloff) + OptionsManager.scrolljump.set(scrolljump) + } + + private fun assertVisualLineAtMiddleOfScreen(expected: Int) { + assertEquals(expected, EditorHelper.getVisualLineAtMiddleOfScreen(myFixture.editor)) + } +} \ No newline at end of file From 8a42bff6ad05e43ad7ba3501abc81349dce310f6 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 2 Sep 2020 13:30:27 +0100 Subject: [PATCH 05/31] Add tests for scrolloff and scrolljump Behaviour matches Vim, apart from soft wraps --- .../maddyhome/idea/vim/group/MotionGroup.java | 88 ++++---- .../idea/vim/option/OptionsManager.kt | 2 +- .../jetbrains/plugins/ideavim/VimTestCase.kt | 49 ++++- .../MotionGroup_ScrollCaretIntoView_Test.kt | 51 ++--- .../MotionGroup_scrolloff_scrolljump_Test.kt | 188 ++++++++++++++++++ 5 files changed, 300 insertions(+), 78 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index f6d07c6338..cfdd8dd1fe 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -601,18 +601,14 @@ public boolean scrollColumnToLastScreenColumn(@NotNull Editor editor) { } public static void scrollCaretIntoView(@NotNull Editor editor) { - scrollPositionIntoView(editor, editor.getCaretModel().getVisualPosition()); - } - - public static void scrollPositionIntoView(@NotNull Editor editor, - @NotNull VisualPosition position) { - scrollPositionIntoViewVertically(editor, position); - scrollPositionIntoViewHorizontally(editor, position); + final VisualPosition position = editor.getCaretModel().getVisualPosition(); + scrollCaretIntoViewVertically(editor, position.line); + scrollCaretIntoViewHorizontally(editor, position); } // Vim's version of this method is move.c:update_topline, which will first scroll to fit the current line number at // the top of the window and then ensure that the current line fits at the bottom of the window - private static void scrollPositionIntoViewVertically(@NotNull Editor editor, @NotNull VisualPosition position) { + private static void scrollCaretIntoViewVertically(@NotNull Editor editor, final int caretLine) { // TODO: Make this work with soft wraps // Vim's algorithm works counts line heights for wrapped lines. We're using visual lines, which handles collapsed @@ -622,7 +618,6 @@ private static void scrollPositionIntoViewVertically(@NotNull Editor editor, @No final int topLine = EditorHelper.getVisualLineAtTopOfScreen(editor); final int bottomLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); - final int lineToMakeVisible = position.line; // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred final int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); @@ -638,49 +633,68 @@ private static void scrollPositionIntoViewVertically(@NotNull Editor editor, @No final int halfHeight = Math.max(2, (height / 2) - 1); // Scrolljump isn't handled as you might expect. It is the minimal number of lines to scroll, but that doesn't mean - // newLine = lineToMakeVisible +/- MAX(sj, so) + // newLine = caretLine +/- MAX(sj, so) + // // When scrolling up (`k` - scrolling window up in the buffer; more lines are visible at the top of the window), Vim - // will select the new top line and repeatedly move it up until it's scrolled at least scrolljump or scroll lines, - // whichever is larger. Unintuitively, the initial scroll to set the new top line counts as 1 against scrolljump, - // even if it's across several lines, while each subsequent line is a single scrolled line. - // This means scrolling up is essentially (lineToMakeVisible + max(so, sj-1)) + // will start at the new cursor line and repeatedly advance lines above and below. The new top line must be at least + // scrolloff above caretLine. If this takes the new top line above the current top line, we must scroll at least + // scrolljump. If the new caret line was already above the current top line, this counts as one scroll, and we + // scroll from the caret line. Otherwise, we scroll from the current top line. // (See move.c:scroll_cursor_top) - // When scrolling down (`j` - scrolling window down in the buffer; more lines are visible at the bottom), Vim does - // something different, selecting a new bottom line and repeatedly advancing lines above and below. The total - // number of lines expanded is at least scrolljump and there must be at least scrolloff lines below. + // + // When scrolling down (`j` - scrolling window down in the buffer; more lines are visible at the bottom), Vim again + // expands lines above and below the new bottom line, but calcualtes things a little differently. The total number + // of lines expanded is at least scrolljump and there must be at least scrolloff lines below. // Since the lines are advancing simultaneously, it is only possible to get scrolljump/2 above the new cursor line. // If there are fewer than scrolljump/2 lines between the current bottom line and the new cursor line, the extra // lines are pushed below the new cursor line. Due to the algorithm advancing the "above" line before the "below" // line, we can end up with more than just scrolljump/2 lines on the top (hence the sj+1). // Therefore, the new top line is (cln + max(so, sj - min(cln-bl, ceiling((sj + 1)/2)))) - // (where cln is lineToMakeVisible, bl is bottomLine, so is scrolloff and sj is scrolljump) + // (where cln is caretLine, bl is bottomLine, so is scrolloff and sj is scrolljump) // (See move.c:scroll_cursor_bot) + // // On top of that, if the scroll distance is "too large", the new cursor line is positioned in the centre of the - // screen. What "too large" means depends on scroll direction. - final int scrollJump = getScrollJumpSize(editor, height); + // screen. What "too large" means depends on scroll direction. There is an initial approximate check before working + // out correct scroll locations + final int scrollJump = getScrollJump(editor, height); - if (lineToMakeVisible < topBound) { + if (caretLine < topBound) { // Scrolling up, put the cursor at the top of the window (minus scrolloff) - if (topLine + scrollOffset - lineToMakeVisible >= halfHeight) { - EditorHelper.scrollVisualLineToMiddleOfScreen(editor, lineToMakeVisible); + // Initial approximation in move.c:update_topline + if (topLine + scrollOffset - caretLine >= halfHeight) { + EditorHelper.scrollVisualLineToMiddleOfScreen(editor, caretLine); } else { - final int newTopLine = Math.max(0, lineToMakeVisible - Math.max(scrollOffset, (scrollJump - 1))); - EditorHelper.scrollVisualLineToTopOfScreen(editor, newTopLine); + // New top line must be at least scrolloff above caretLine. If this is above current top line, we must scroll + // at least scrolljump. If caretLine was already above topLine, this counts as one scroll, and we scroll from + // here. Otherwise, we scroll from topLine + final int scrollJumpTopLine = Math.max(0, (caretLine < topLine) ? caretLine - scrollJump + 1 : topLine - scrollJump); + final int scrollOffsetTopLine = Math.max(0, caretLine - scrollOffset); + final int newTopLine = Math.min(scrollOffsetTopLine, scrollJumpTopLine); + + // Used is set to the line height of caretLine, and then incremented by line height of the lines above and + // below caretLine (up to scrolloff or end of file) + final int used = 1 + (newTopLine - topLine) + Math.min(scrollOffset, EditorHelper.getVisualLineCount(editor) - topLine); + if (used > height) { + EditorHelper.scrollVisualLineToMiddleOfScreen(editor, caretLine); + } + else { + EditorHelper.scrollVisualLineToTopOfScreen(editor, newTopLine); + } } } - else if (lineToMakeVisible > bottomBound) { + else if (caretLine > bottomBound) { // Scrolling down, put the cursor at the bottom of the window (minus scrolloff) // Vim does a quick approximation before going through the full algorithm. It checks the line below the bottom // line in the window (bottomLine + 1). See move.c:update_topline - int lineCount = lineToMakeVisible - (bottomLine + 1) + 1 + scrollOffset; + int lineCount = caretLine - (bottomLine + 1) + 1 + scrollOffset; if (lineCount > height) { - EditorHelper.scrollVisualLineToMiddleOfScreen(editor, lineToMakeVisible); + EditorHelper.scrollVisualLineToMiddleOfScreen(editor, caretLine); } else { - // Vim expands out from lineToMakeVisible at least scrolljump lines. It stops expanding above when it hits the + // Vim expands out from caretLine at least scrolljump lines. It stops expanding above when it hits the // current bottom line, or (because it's expanding above and below) when it's scrolled scrolljump/2. It expands // above first, and the initial scroll count is 1, so we used (scrolljump+1)/2 - final int scrolledAbove = lineToMakeVisible - bottomLine; + final int scrolledAbove = caretLine - bottomLine; final int extra = Math.max(scrollOffset, scrollJump - Math.min(scrolledAbove, Math.round((scrollJump + 1) / 2.0f))); final int scrolled = scrolledAbove + extra; @@ -691,23 +705,23 @@ else if (lineToMakeVisible > bottomBound) { // The minus one is for the current line //noinspection UnnecessaryLocalVariable final int usedAbove = scrolledAbove; - final int usedBelow = Math.min(EditorHelper.getVisualLineCount(editor) - lineToMakeVisible, usedAbove - 1); + final int usedBelow = Math.min(EditorHelper.getVisualLineCount(editor) - caretLine, usedAbove - 1); final int used = Math.min(height + 1, usedAbove + usedBelow); // If we've expanded more than a screen full, redraw with the cursor in the middle of the screen. If we're going // scroll more than a screen full or more than scrolloff, redraw with the cursor in the middle of the screen. lineCount = used > height ? used : scrolled; if (lineCount >= height && lineCount > scrollOffset) { - EditorHelper.scrollVisualLineToMiddleOfScreen(editor, lineToMakeVisible); + EditorHelper.scrollVisualLineToMiddleOfScreen(editor, caretLine); } else { - EditorHelper.scrollVisualLineToBottomOfScreen(editor, lineToMakeVisible + extra); + EditorHelper.scrollVisualLineToBottomOfScreen(editor, caretLine + extra); } } } } - private static int getScrollJumpSize(@NotNull Editor editor, int height) { + private static int getScrollJump(@NotNull Editor editor, int height) { final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SCROLL_JUMP); @@ -716,7 +730,7 @@ private static int getScrollJumpSize(@NotNull Editor editor, int height) { if (scrollJump) { final int scrollJumpSize = OptionsManager.INSTANCE.getScrolljump().value(); if (scrollJumpSize < 0) { - return height * (Math.min(100, -scrollJumpSize) / 100); + return (int) (height * (Math.min(100, -scrollJumpSize) / 100.0)); } else { return Math.max(1, scrollJumpSize); @@ -725,8 +739,8 @@ private static int getScrollJumpSize(@NotNull Editor editor, int height) { return 1; } - private static void scrollPositionIntoViewHorizontally(@NotNull Editor editor, - @NotNull VisualPosition position) { + private static void scrollCaretIntoViewHorizontally(@NotNull Editor editor, + @NotNull VisualPosition position) { final int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor); final int column = position.column; final int width = EditorHelper.getScreenWidth(editor); diff --git a/src/com/maddyhome/idea/vim/option/OptionsManager.kt b/src/com/maddyhome/idea/vim/option/OptionsManager.kt index 6d14eea79f..f335f2b7f1 100644 --- a/src/com/maddyhome/idea/vim/option/OptionsManager.kt +++ b/src/com/maddyhome/idea/vim/option/OptionsManager.kt @@ -66,7 +66,7 @@ object OptionsManager { val number = addOption(ToggleOption("number", "nu", false)) val relativenumber = addOption(ToggleOption("relativenumber", "rnu", false)) val scroll = addOption(NumberOption("scroll", "scr", 0)) - val scrolljump = addOption(NumberOption("scrolljump", "sj", 1)) + val scrolljump = addOption(NumberOption("scrolljump", "sj", 1, -100, Integer.MAX_VALUE)) val scrolloff = addOption(NumberOption("scrolloff", "so", 0)) val selection = addOption(BoundStringOption("selection", "sel", "inclusive", arrayOf("old", "inclusive", "exclusive"))) val selectmode = addOption(SelectModeOptionData.option) diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index f108d26b40..dc55ccc0a6 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -44,16 +44,22 @@ 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.* +import com.maddyhome.idea.vim.helper.EditorDataContext +import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.RunnableHelper.runWriteCommand +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys 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 import com.maddyhome.idea.vim.option.OptionsManager.getOption import com.maddyhome.idea.vim.option.OptionsManager.ideastrictmode import com.maddyhome.idea.vim.option.OptionsManager.resetAllOptions import com.maddyhome.idea.vim.option.ToggleOption import com.maddyhome.idea.vim.ui.ExEntryPanel -import junit.framework.Assert +import org.junit.Assert +import java.lang.Integer.min import java.util.* import java.util.function.Consumer import javax.swing.KeyStroke @@ -146,14 +152,39 @@ abstract class VimTestCase : UsefulTestCase() { return myFixture.editor } - protected fun configureLargeText(lineCount: Int) { + protected fun configureByPages(pageCount: Int) { val stringBuilder = StringBuilder() - repeat(lineCount) { + repeat(pageCount * screenHeight) { stringBuilder.appendln("I found it in a legendary land") } configureByText(stringBuilder.toString()) } + protected fun setPositionAndScroll(scrollToLogicalLine: Int, caretLogicalLine: Int) { + val scrolloff = min(OptionsManager.scrolloff.value(), screenHeight / 2) + val scrolljump = OptionsManager.scrolljump.value() + OptionsManager.scrolljump.set(1) + + // Convert to visual lines to handle any collapsed folds + val scrollToVisualLine = EditorHelper.logicalLineToVisualLine(myFixture.editor, scrollToLogicalLine) + val bottomVisualLine = scrollToVisualLine + screenHeight - 1 + val bottomLogicalLine = EditorHelper.visualLineToLogicalLine(myFixture.editor, bottomVisualLine) + + // Make sure we're not trying to put caret in an invalid location + val boundsTop = EditorHelper.visualLineToLogicalLine(myFixture.editor, scrollToVisualLine + scrolloff) + val boundsBottom = EditorHelper.visualLineToLogicalLine(myFixture.editor, bottomVisualLine - scrolloff) + Assert.assertTrue("Caret line $caretLogicalLine not inside legal screen bounds (${boundsTop} - ${boundsBottom})", + caretLogicalLine in boundsTop..boundsBottom) + + typeText(parseKeys("${scrollToLogicalLine+scrolloff+1}z", "${caretLogicalLine+1}G")) + + OptionsManager.scrolljump.set(scrolljump) + + // Make sure we're where we want to be + assertVisibleArea(scrollToLogicalLine, bottomLogicalLine) + assertPosition(caretLogicalLine, 0) + } + protected fun typeText(keys: List): Editor { val editor = myFixture.editor val project = myFixture.project @@ -278,7 +309,7 @@ abstract class VimTestCase : UsefulTestCase() { } private fun performTest(keys: String, after: String, modeAfter: CommandState.Mode, subModeAfter: SubMode) { - typeText(StringHelper.parseKeys(keys)) + typeText(parseKeys(keys)) myFixture.checkResult(after) assertState(modeAfter, subModeAfter) } @@ -336,9 +367,9 @@ abstract class VimTestCase : UsefulTestCase() { @JvmStatic fun commandToKeys(command: String): List { val keys: MutableList = ArrayList() - keys.addAll(StringHelper.parseKeys(":")) + keys.addAll(parseKeys(":")) keys.addAll(stringToKeys(command)) - keys.addAll(StringHelper.parseKeys("")) + keys.addAll(parseKeys("")) return keys } @@ -346,9 +377,9 @@ abstract class VimTestCase : UsefulTestCase() { fun searchToKeys(pattern: String, forwards: Boolean): List { val keys: MutableList = ArrayList() - keys.addAll(StringHelper.parseKeys(if (forwards) "/" else "?")) + keys.addAll(parseKeys(if (forwards) "/" else "?")) keys.addAll(stringToKeys(pattern)) - keys.addAll(StringHelper.parseKeys("")) + keys.addAll(parseKeys("")) return keys } diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt index 224ac95f0d..a88e89b265 100644 --- a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt @@ -26,7 +26,7 @@ import org.jetbrains.plugins.ideavim.VimTestCase @Suppress("ClassName") class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test moving up causes scrolling up`() { - configureLargeText(200) + configureByPages(5) setPositionAndScroll(19, 24) typeText(parseKeys("12k")) @@ -36,7 +36,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll up with scrolljump`() { OptionsManager.scrolljump.set(10) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(19, 24) typeText(parseKeys("12k")) @@ -46,7 +46,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll up with scrolloff`() { OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(19, 29) typeText(parseKeys("12k")) @@ -57,7 +57,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll up with scrolljump and scrolloff 1`() { OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(19, 29) typeText(parseKeys("12k")) @@ -68,7 +68,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll up with scrolljump and scrolloff 2`() { OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(29, 39) typeText(parseKeys("20k")) @@ -77,7 +77,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { } fun `test scroll up with collapsed folds`() { - configureLargeText(200) + configureByPages(5) // TODO: Implement zf typeText(parseKeys("40G", "Vjjjj", ":'<,'>action CollapseSelection", "V")) setPositionAndScroll(29, 49) @@ -92,8 +92,8 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { // } fun `test scroll up more than half height moves caret to middle 1`() { - configureLargeText(200) - setPositionAndScroll(114, 149) + configureByPages(5) + setPositionAndScroll(115, 149) typeText(parseKeys("50k")) assertPosition(99, 0) @@ -101,7 +101,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { } fun `test scroll up more than half height moves caret to middle with scrolloff`() { - configureLargeText(200) + configureByPages(5) OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(5) setPositionAndScroll(99, 109) @@ -113,7 +113,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { } fun `test scroll up with less than half height moves caret to top of screen`() { - configureLargeText(200) + configureByPages(5) OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(5) setPositionAndScroll(99, 109) @@ -124,7 +124,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { } fun `test moving down causes scrolling down`() { - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 29) typeText(parseKeys("12j")) @@ -134,7 +134,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolljump`() { OptionsManager.scrolljump.set(10) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 29) typeText(parseKeys("12j")) @@ -144,7 +144,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolloff`() { OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 24) typeText(parseKeys("12j")) @@ -155,7 +155,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolljump and scrolloff 1`() { OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 24) typeText(parseKeys("12j")) @@ -166,7 +166,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolljump and scrolloff 2`() { OptionsManager.scrolljump.set(15) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 24) typeText(parseKeys("20j")) @@ -177,7 +177,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolljump and scrolloff 3`() { OptionsManager.scrolljump.set(20) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 24) typeText(parseKeys("25j")) @@ -188,7 +188,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolljump and scrolloff 4`() { OptionsManager.scrolljump.set(11) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 24) typeText(parseKeys("12j")) @@ -199,7 +199,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolljump and scrolloff 5`() { OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 29) typeText(parseKeys("12j")) @@ -210,7 +210,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down with scrolljump and scrolloff 6`() { OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(5) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 24) typeText(parseKeys("20j")) @@ -221,7 +221,7 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { fun `test scroll down too large cursor is centred`() { OptionsManager.scrolljump.set(10) OptionsManager.scrolloff.set(10) - configureLargeText(200) + configureByPages(5) setPositionAndScroll(0, 19) typeText(parseKeys("35j")) @@ -229,17 +229,6 @@ class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { assertVisualLineAtMiddleOfScreen(54) } - // 0-based - private fun setPositionAndScroll(scrollToLogicalLine: Int, caretLogicalLine: Int) { - val scrolloff = OptionsManager.scrolloff.value - val scrolljump = OptionsManager.scrolljump.value - OptionsManager.scrolloff.set(0) - OptionsManager.scrolljump.set(1) - typeText(parseKeys("${scrollToLogicalLine+1}z", "${caretLogicalLine+1}G")) - OptionsManager.scrolloff.set(scrolloff) - OptionsManager.scrolljump.set(scrolljump) - } - private fun assertVisualLineAtMiddleOfScreen(expected: Int) { assertEquals(expected, EditorHelper.getVisualLineAtMiddleOfScreen(myFixture.editor)) } diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt new file mode 100644 index 0000000000..06a5cca357 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt @@ -0,0 +1,188 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +@file:Suppress("ClassName") + +package org.jetbrains.plugins.ideavim.group.motion + +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// These tests are sanity tests for scrolloff and scrolljump, with actions that move the cursor. Other actions that are +// affected by scrolloff or scrolljump should include that in the action specific tests +class MotionGroup_scrolloff_Test : VimTestCase() { + fun `test move up shows no context with scrolloff=0`() { + OptionsManager.scrolloff.set(0) + configureByPages(5) + setPositionAndScroll(25, 25) + typeText(parseKeys("k")) + assertPosition(24, 0) + assertVisibleArea(24, 58) + } + + fun `test move up shows context line with scrolloff=1`() { + OptionsManager.scrolloff.set(1) + configureByPages(5) + setPositionAndScroll(25, 26) + typeText(parseKeys("k")) + assertPosition(25, 0) + assertVisibleArea(24, 58) + } + + fun `test move up shows context lines with scrolloff=10`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(25, 35) + typeText(parseKeys("k")) + assertPosition(34, 0) + assertVisibleArea(24, 58) + } + + fun `test move down shows no context with scrolloff=0`() { + OptionsManager.scrolloff.set(0) + configureByPages(5) + setPositionAndScroll(25, 59) + typeText(parseKeys("j")) + assertPosition(60, 0) + assertVisibleArea(26, 60) + } + + fun `test move down shows context line with scrolloff=1`() { + OptionsManager.scrolloff.set(1) + configureByPages(5) + setPositionAndScroll(25, 58) + typeText(parseKeys("j")) + assertPosition(59, 0) + assertVisibleArea(26, 60) + } + + fun `test move down shows context lines with scrolloff=10`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(25, 49) + typeText(parseKeys("j")) + assertPosition(50, 0) + assertVisibleArea(26, 60) + } + + fun `test scrolloff=999 keeps cursor in centre of screen`() { + OptionsManager.scrolloff.set(999) + configureByPages(5) + setPositionAndScroll(25, 42) + typeText(parseKeys("j")) + assertPosition(43, 0) + assertVisibleArea(26, 60) + } + + fun `test negative scrolljump treated as percentage 1`() { + OptionsManager.scrolljump.set(-50) + configureByPages(5) + setPositionAndScroll(39, 39) + typeText(parseKeys("k")) + assertPosition(38, 0) + assertVisibleArea(22, 56) + } + + fun `test negative scrolljump treated as percentage 2`() { + OptionsManager.scrolljump.set(-10) + configureByPages(5) + setPositionAndScroll(39, 39) + typeText(parseKeys("k")) + assertPosition(38, 0) + assertVisibleArea(36, 70) + } +} + +class MotionGroup_scrolljump_Test : VimTestCase() { + fun `test move up scrolls single line with scrolljump=0`() { + OptionsManager.scrolljump.set(0) + configureByPages(5) + setPositionAndScroll(25, 25) + typeText(parseKeys("k")) + assertPosition(24, 0) + assertVisibleArea(24, 58) + } + + fun `test move up scrolls single line with scrolljump=1`() { + OptionsManager.scrolljump.set(1) + configureByPages(5) + setPositionAndScroll(25, 25) + typeText(parseKeys("k")) + assertPosition(24, 0) + assertVisibleArea(24, 58) + } + + fun `test move up scrolls multiple lines with scrolljump=10`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(25, 25) + typeText(parseKeys("k")) + assertPosition(24, 0) + assertVisibleArea(15, 49) + } + + fun `test move down scrolls single line with scrolljump=0`() { + OptionsManager.scrolljump.set(0) + configureByPages(5) + setPositionAndScroll(25, 59) + typeText(parseKeys("j")) + assertPosition(60, 0) + assertVisibleArea(26, 60) + } + + fun `test move down scrolls single line with scrolljump=1`() { + OptionsManager.scrolljump.set(1) + configureByPages(5) + setPositionAndScroll(25, 59) + typeText(parseKeys("j")) + assertPosition(60, 0) + assertVisibleArea(26, 60) + } + + fun `test move down scrolls multiple lines with scrolljump=10`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(25, 59) + typeText(parseKeys("j")) + assertPosition(60, 0) + assertVisibleArea(35, 69) + } +} + +class MotionGroup_scrolloff_scrolljump_Test : VimTestCase() { + fun `test scroll up with scrolloff and scrolljump set`() { + OptionsManager.scrolloff.set(5) + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(50, 55) + typeText(parseKeys("k")) + assertPosition(54, 0) + assertVisibleArea(40, 74) + } + + fun `test scroll down with scrolloff and scrolljump set`() { + OptionsManager.scrolloff.set(5) + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(50, 79) + typeText(parseKeys("j")) + assertPosition(80, 0) + assertVisibleArea(60, 94) + } +} From 111c1ebe32be4b563bede4e578054de1845487fd Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 2 Sep 2020 15:02:16 +0100 Subject: [PATCH 06/31] Add tests for ScrollLineUpAction --- .../idea/vim/helper/EditorHelper.java | 2 +- .../action/scroll/ScrollLineUpActionTest.kt | 99 +++++++++++++++++++ 2 files changed, 100 insertions(+), 1 deletion(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index 2ed4cc2885..95f9e68b27 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -679,7 +679,7 @@ public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, i int y = editor.visualLineToY(visualLine); int height = inlayHeight + editor.getLineHeight() + exPanelHeight; Rectangle visibleArea = getVisibleArea(editor); - return scrollVertically(editor, y - visibleArea.height + height); + return scrollVertically(editor, max(0, y - visibleArea.height + height)); } /** diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt new file mode 100644 index 0000000000..698d28496c --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt @@ -0,0 +1,99 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// || +class ScrollLineUpActionTest : VimTestCase() { + fun `test scroll single line up`() { + configureByPages(5) + setPositionAndScroll(29, 29) + typeText(parseKeys("")) + assertPosition(29, 0) + assertVisibleArea(28, 62) + } + + fun `test scroll line up will keep cursor on screen`() { + configureByPages(5) + setPositionAndScroll(29, 63) + typeText(parseKeys("")) + assertPosition(62, 0) + assertVisibleArea(28, 62) + } + + fun `test scroll count lines up`() { + configureByPages(5) + setPositionAndScroll(29, 29) + typeText(parseKeys("10")) + assertPosition(29, 0) + assertVisibleArea(19, 53) + } + + fun `test scroll count lines up will keep cursor on screen`() { + configureByPages(5) + setPositionAndScroll(29, 63) + typeText(parseKeys("10")) + assertPosition(53, 0) + assertVisibleArea(19, 53) + } + + fun `test too many lines up stops at zero`() { + configureByPages(5) + setPositionAndScroll(29, 29) + typeText(parseKeys("100")) + assertPosition(29, 0) + assertVisibleArea(0, 34) + } + + fun `test too many lines up stops at zero and keeps cursor on screen`() { + configureByPages(5) + setPositionAndScroll(59, 59) + typeText(parseKeys("100")) + assertPosition(34, 0) + assertVisibleArea(0, 34) + } + + fun `test scroll up uses scrolloff and moves cursor`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(20, 44) + typeText(parseKeys("")) + assertPosition(43, 0) + assertVisibleArea(19, 53) + } + + fun `test scroll up is not affected by scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(29, 63) + typeText(parseKeys("")) + assertPosition(62, 0) + assertVisibleArea(28, 62) + } + + fun `test scroll line up in visual mode`() { + configureByPages(5) + setPositionAndScroll(29, 29) + typeText(parseKeys("Vjjjj", "")) + assertVisibleArea(28, 62) + } +} \ No newline at end of file From a5de935192db50f736b321fca8e775c954b34316 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 2 Sep 2020 17:05:01 +0100 Subject: [PATCH 07/31] Add tests for ScrollLineDownAction --- .../maddyhome/idea/vim/group/MotionGroup.java | 10 +- .../idea/vim/helper/EditorHelper.java | 20 +++- .../action/scroll/ScrollLineDownActionTest.kt | 97 +++++++++++++++++++ 3 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index cfdd8dd1fe..99132c1d96 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -812,14 +812,12 @@ public boolean scrollLine(@NotNull Editor editor, int lines) { assert lines != 0 : "lines cannot be 0"; if (lines > 0) { - int visualLine = EditorHelper.getVisualLineAtTopOfScreen(editor); - visualLine = EditorHelper.normalizeVisualLine(editor, visualLine + lines); - EditorHelper.scrollVisualLineToTopOfScreen(editor, visualLine); + final int visualLine = EditorHelper.getVisualLineAtTopOfScreen(editor); + EditorHelper.scrollVisualLineToTopOfScreen(editor, visualLine + lines); } else { - int visualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); - visualLine = EditorHelper.normalizeVisualLine(editor, visualLine + lines); - EditorHelper.scrollVisualLineToBottomOfScreen(editor, visualLine); + final int visualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); + EditorHelper.scrollVisualLineToBottomOfScreen(editor, visualLine + lines); } moveCaretToView(editor); diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index 95f9e68b27..03546e6845 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -639,8 +639,22 @@ public static void scrollVisualLineToCaretLocation(final @NotNull Editor editor, * @return Returns true if the window was moved */ public static boolean scrollVisualLineToTopOfScreen(final @NotNull Editor editor, int visualLine) { - int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, true); + final int inlayHeight = getHeightOfVisualLineInlays(editor, normalizeVisualLine(editor, visualLine), true); int y = editor.visualLineToY(visualLine) - inlayHeight; + + // Normalise Y so that we don't try to scroll the editor to a location it can't reach. The editor will handle this, + // but when we ask for the target location to move the caret to match, we'll get the incorrect value. + // E.g. from line 100 of a 175 line, with line 100 at the top of screen, hit 100. This should scroll line 175 + // to the top of the screen. With virtual space enabled, this is fine. If it's not enabled, we end up scrolling line + // 146 to the top of the screen, but the caret thinks we're going to 175, and the caret is put in the wrong location + // (To complicate things, this issue doesn't show up when running headless for tests) + if (!editor.getSettings().isAdditionalPageAtBottom()) { + // Get the max line number that can sit at the top of the screen + final int editorHeight = getVisibleArea(editor).height; + final int virtualSpaceHeight = editor.getSettings().getAdditionalLinesCount() * editor.getLineHeight(); + final int yLastLine = editor.visualLineToY(EditorHelper.getLineCount(editor)); // last line + 1 + y = Math.min(y, yLastLine + virtualSpaceHeight - editorHeight); + } return scrollVertically(editor, y); } @@ -651,7 +665,7 @@ public static boolean scrollVisualLineToTopOfScreen(final @NotNull Editor editor * @param visualLine The visual line to place in the middle of the current window */ public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, int visualLine) { - int y = editor.visualLineToY(visualLine); + int y = editor.visualLineToY(normalizeVisualLine(editor, visualLine)); int lineHeight = editor.getLineHeight(); int height = getVisibleArea(editor).height; scrollVertically(editor, y - ((height - lineHeight) / 2)); @@ -668,7 +682,7 @@ public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, int * @return True if the editor was scrolled */ public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, int visualLine) { - int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, false); + int inlayHeight = getHeightOfVisualLineInlays(editor, normalizeVisualLine(editor, visualLine), false); int exPanelHeight = 0; if (ExEntryPanel.getInstance().isActive()) { exPanelHeight = ExEntryPanel.getInstance().getHeight(); diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt new file mode 100644 index 0000000000..6d60a5af02 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt @@ -0,0 +1,97 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +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 ScrollLineDownActionTest : VimTestCase() { + fun `test scroll single line down`() { + configureByPages(5) + setPositionAndScroll(0, 34) + typeText(parseKeys("")) + assertPosition(34, 0) + assertVisibleArea(1, 35) + } + + fun `test scroll line down will keep cursor on screen`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("")) + assertPosition(1, 0) + assertVisibleArea(1, 35) + } + + fun `test scroll count lines down`() { + configureByPages(5) + setPositionAndScroll(0, 34) + typeText(parseKeys("10")) + assertPosition(34, 0) + assertVisibleArea(10, 44) + } + + fun `test scroll count lines down will keep cursor on screen`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("10")) + assertPosition(10, 0) + assertVisibleArea(10, 44) + } + + @VimBehaviorDiffers(description = "Vim has virtual space at the end of the file, IntelliJ (by default) does not") + fun `test too many lines down stops at last line`() { + configureByPages(5) // 5 * 35 = 175 + setPositionAndScroll(100, 100) + typeText(parseKeys("100")) + + // TODO: Enforce virtual space + // Vim will put the caret on line 174, and put that line at the top of the screen + // See com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen + assertPosition(146, 0) + assertVisibleArea(146, 175) + } + + fun `test scroll down uses scrolloff and moves cursor`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(20, 30) + typeText(parseKeys("")) + assertPosition(31, 0) + assertVisibleArea(21, 55) + } + + fun `test scroll down is not affected by scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(20, 20) + typeText(parseKeys("")) + assertPosition(21, 0) + assertVisibleArea(21, 55) + } + + fun `test scroll down in visual mode`() { + configureByPages(5) + setPositionAndScroll(20, 30) + typeText(parseKeys("Vjjjj", "")) + assertVisibleArea(21, 55) + } +} \ No newline at end of file From 632e9fad2e67ad70bd789d32f560be828c562a68 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 2 Sep 2020 17:18:03 +0100 Subject: [PATCH 08/31] Remove incorrect mappings for page up/down i_ and i_ are not standard Vim mappings, but can be set up in .ideavimrc if required --- .../idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt | 2 -- .../idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt index d51f7debc7..0bbfbdf8d1 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt @@ -44,8 +44,6 @@ class MotionScrollPageDownInsertModeAction : VimActionHandler.SingleExecution(), override val keyStrokesSet: Set> = setOf( listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0)), - listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.CTRL_DOWN_MASK)), - listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.CTRL_DOWN_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, KeyEvent.SHIFT_DOWN_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_DOWN, KeyEvent.SHIFT_DOWN_MASK)) ) diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt index 2a5dbc3942..87e0d5b964 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt @@ -44,8 +44,6 @@ class MotionScrollPageUpInsertModeAction : VimActionHandler.SingleExecution(), C override val keyStrokesSet: Set> = setOf( listOf(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0)), - listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.CTRL_DOWN_MASK)), - listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.CTRL_DOWN_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, KeyEvent.SHIFT_DOWN_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_KP_UP, KeyEvent.SHIFT_DOWN_MASK)) ) From 3cf42c86f3ee008883a258fb4825453ceb7c65bb Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 2 Sep 2020 18:52:03 +0100 Subject: [PATCH 09/31] Add tests for ScrollPageDownAction --- .../scroll/MotionScrollPageDownAction.kt | 4 +- .../action/scroll/ScrollPageDownActionTest.kt | 152 ++++++++++++++++++ 2 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt index 0bbfbdf8d1..c7daf16ee0 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageDownAction.kt @@ -35,6 +35,8 @@ class MotionScrollPageDownAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollFullPage(editor, cmd.count) } @@ -50,7 +52,7 @@ class MotionScrollPageDownInsertModeAction : VimActionHandler.SingleExecution(), override val type: Command.Type = Command.Type.OTHER_READONLY - override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES) + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP, CommandFlags.FLAG_CLEAR_STROKES) override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollFullPage(editor, cmd.count) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt new file mode 100644 index 0000000000..1e770adb0c --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt @@ -0,0 +1,152 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +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 + +// ||, ||, |CTRL-F| +// |i_|, |i_| +class ScrollPageDownActionTest : VimTestCase() { + fun `test scroll single page down with S-Down`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll single page down with PageDown`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll single page down with CTRL-F`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll page down in insert mode with S-Down`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("i", "")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll page down in insert mode with PageDown`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("i", "")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll count pages down with S-Down`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("3")) + assertPosition(99, 0) + assertVisibleArea(99, 133) + } + + fun `test scroll count pages down with PageDown`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("3")) + assertPosition(99, 0) + assertVisibleArea(99, 133) + } + + fun `test scroll count pages down with CTRL-F`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("3")) + assertPosition(99, 0) + assertVisibleArea(99, 133) + } + + fun `test scroll page down moves cursor to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll page down in insert mode moves cursor`() { + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("i", "")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll page down moves cursor with scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("")) + assertPosition(43, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll page down in insert mode moves cursor with scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("i", "")) + assertPosition(43, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll page down ignores scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("")) + assertPosition(33, 0) + assertVisibleArea(33, 67) + } + + @VimBehaviorDiffers(description = "IntelliJ does not have virtual space enabled by default") + fun `test scroll page down on final page moves cursor to end of file`() { + configureByPages(5) + setPositionAndScroll(145, 150) + typeText(parseKeys("")) + assertPosition(175, 0) + assertVisibleArea(146, 175) + } + + fun `test scroll page down on penultimate page`() { + configureByPages(5) + setPositionAndScroll(110, 130) + typeText(parseKeys("")) + assertPosition(143, 0) + assertVisibleArea(143, 175) + } +} \ No newline at end of file From c8a193815554d2f29033e5b95ae11396f7a43f6e Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 2 Sep 2020 23:27:16 +0100 Subject: [PATCH 10/31] Add tests for ScrollPageUpAction --- .../motion/scroll/MotionScrollPageUpAction.kt | 4 +- .../action/scroll/ScrollPageUpActionTest.kt | 150 ++++++++++++++++++ 2 files changed, 153 insertions(+), 1 deletion(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt index 87e0d5b964..0dcc267e46 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollPageUpAction.kt @@ -35,6 +35,8 @@ class MotionScrollPageUpAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollFullPage(editor, -cmd.count) } @@ -50,7 +52,7 @@ class MotionScrollPageUpInsertModeAction : VimActionHandler.SingleExecution(), C override val type: Command.Type = Command.Type.OTHER_READONLY - override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES) + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP, CommandFlags.FLAG_CLEAR_STROKES) override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollFullPage(editor, -cmd.count) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt new file mode 100644 index 0000000000..f8d9269280 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt @@ -0,0 +1,150 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// ||, ||, |CTRL-B| +// |i_|, |i_| +class ScrollPageUpActionTest : VimTestCase() { + fun `test scroll single page up with S-Up`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll single page up with PageUp`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll single page up with CTRL-B`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll page up in insert mode with S-Up`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("i", "")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll page up in insert mode with PageUp`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("i", "")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll count pages up with S-Up`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("3")) + assertPosition(64, 0) + assertVisibleArea(30, 64) + } + + fun `test scroll count pages up with PageUp`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("3")) + assertPosition(64, 0) + assertVisibleArea(30, 64) + } + + fun `test scroll count pages up with CTRL-B`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("3")) + assertPosition(64, 0) + assertVisibleArea(30, 64) + } + + fun `test scroll page up moves cursor to bottom of screen`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll page up in insert mode moves cursor`() { + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("i", "")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll page up moves cursor with scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("")) + assertPosition(120, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll page up in insert mode cursor with scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("i", "")) + assertPosition(120, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll page up ignores scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(129, 149) + typeText(parseKeys("")) + assertPosition(130, 0) + assertVisibleArea(96, 130) + } + + fun `test scroll page up on first page does not move`() { + configureByPages(5) + setPositionAndScroll(0, 25) + typeText(parseKeys("")) + assertPosition(25, 0) + assertVisibleArea(0, 34) + } + + fun `test scroll page up on second page moves cursor to previous top`() { + configureByPages(5) + setPositionAndScroll(10, 35) + typeText(parseKeys("")) + assertPosition(11, 0) + assertVisibleArea(0, 34) + } +} \ No newline at end of file From 8f90ff8a651740fb1249c4ea45303e0097067e3e Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 3 Sep 2020 00:05:01 +0100 Subject: [PATCH 11/31] Add tests for ScrollFirstScreenLinePageStartAction --- ...ionScrollFirstScreenLinePageStartAction.kt | 5 + .../jetbrains/plugins/ideavim/VimTestCase.kt | 22 ++++- ...crollFirstScreenLinePageStartActionTest.kt | 91 +++++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt index 6ec3f6c3a8..247bd94da2 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt @@ -21,12 +21,17 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollFirstScreenLinePageStartAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { var line = cmd.rawCount if (line == 0) { diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index dc55ccc0a6..a5c6b3d6b2 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -152,6 +152,7 @@ abstract class VimTestCase : UsefulTestCase() { return myFixture.editor } + @Suppress("SameParameterValue") protected fun configureByPages(pageCount: Int) { val stringBuilder = StringBuilder() repeat(pageCount * screenHeight) { @@ -160,7 +161,16 @@ abstract class VimTestCase : UsefulTestCase() { configureByText(stringBuilder.toString()) } - protected fun setPositionAndScroll(scrollToLogicalLine: Int, caretLogicalLine: Int) { + protected fun configureByLines(lineCount: Int, line: String) { + val stringBuilder = StringBuilder() + repeat(lineCount) { + stringBuilder.appendln(line) + } + configureByText(stringBuilder.toString()) + } + + @JvmOverloads + protected fun setPositionAndScroll(scrollToLogicalLine: Int, caretLogicalLine: Int, caretLogicalColumn: Int = 0) { val scrolloff = min(OptionsManager.scrolloff.value(), screenHeight / 2) val scrolljump = OptionsManager.scrolljump.value() OptionsManager.scrolljump.set(1) @@ -171,18 +181,20 @@ abstract class VimTestCase : UsefulTestCase() { val bottomLogicalLine = EditorHelper.visualLineToLogicalLine(myFixture.editor, bottomVisualLine) // Make sure we're not trying to put caret in an invalid location - val boundsTop = EditorHelper.visualLineToLogicalLine(myFixture.editor, scrollToVisualLine + scrolloff) - val boundsBottom = EditorHelper.visualLineToLogicalLine(myFixture.editor, bottomVisualLine - scrolloff) + val boundsTop = EditorHelper.visualLineToLogicalLine(myFixture.editor, + if (scrollToVisualLine > scrolloff) scrollToVisualLine + scrolloff else scrollToVisualLine) + val boundsBottom = EditorHelper.visualLineToLogicalLine(myFixture.editor, + if (bottomVisualLine > EditorHelper.getVisualLineCount(myFixture.editor) - scrolloff - 1) bottomVisualLine - scrolloff else bottomVisualLine) Assert.assertTrue("Caret line $caretLogicalLine not inside legal screen bounds (${boundsTop} - ${boundsBottom})", caretLogicalLine in boundsTop..boundsBottom) - typeText(parseKeys("${scrollToLogicalLine+scrolloff+1}z", "${caretLogicalLine+1}G")) + typeText(parseKeys("${scrollToLogicalLine+scrolloff+1}z", "${caretLogicalLine+1}G", "${caretLogicalColumn+1}|")) OptionsManager.scrolljump.set(scrolljump) // Make sure we're where we want to be assertVisibleArea(scrollToLogicalLine, bottomLogicalLine) - assertPosition(caretLogicalLine, 0) + assertPosition(caretLogicalLine, caretLogicalColumn) } protected fun typeText(keys: List): Editor { diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt new file mode 100644 index 0000000000..9a274f9a42 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt @@ -0,0 +1,91 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +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 + +// |z+| (scrolls up) +/* Without [count]: Redraw with the line just below the + window at the top of the window. Put the cursor in + that line, at the first non-blank in the line. + With [count]: just like "z". */ +class ScrollFirstScreenLinePageStartActionTest : VimTestCase() { + fun `test scrolls first line on next page to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("z+")) + assertPosition(35, 0) + assertVisibleArea(35, 69) + } + + fun `test scrolls to first non-blank in line`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(0, 20) + typeText(parseKeys("z+")) + assertPosition(35, 4) + assertVisibleArea(35, 69) + } + + fun `test scrolls first line on next page to scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("z+")) + assertPosition(35, 0) + assertVisibleArea(25, 59) + } + + fun `test scrolls first line on next page ignores scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("z+")) + assertPosition(35, 0) + assertVisibleArea(35, 69) + } + + fun `test count z+ scrolls count line to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("100z+")) + assertPosition(99, 0) + assertVisibleArea(99, 133) + } + + fun `test count z+ scrolls count line to top of screen plus scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(0, 20) + typeText(parseKeys("100z+")) + assertPosition(99, 0) + assertVisibleArea(89, 123) + } + + @VimBehaviorDiffers(description = "Requires virtual space support") + fun `test scroll on penultimate page`() { + configureByPages(5) + setPositionAndScroll(130, 145) + typeText(parseKeys("z+")) + assertPosition(165, 0) + assertVisibleArea(146, 175) + } +} \ No newline at end of file From be0adb833f1d71c9cd5d8704acf511a386143d0a Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 3 Sep 2020 01:10:58 +0100 Subject: [PATCH 12/31] Add tests for ScrollLastScreenLinePageStartAction --- ...ionScrollFirstScreenLinePageStartAction.kt | 11 +- ...tionScrollLastScreenLinePageStartAction.kt | 29 +++-- .../maddyhome/idea/vim/group/MotionGroup.java | 17 +-- ...crollFirstScreenLinePageStartActionTest.kt | 2 +- ...ScrollLastScreenLinePageStartActionTest.kt | 110 ++++++++++++++++++ 5 files changed, 142 insertions(+), 27 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt index 247bd94da2..38bdaf755b 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLinePageStartAction.kt @@ -33,11 +33,12 @@ class MotionScrollFirstScreenLinePageStartAction : VimActionHandler.SingleExecut override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { - var line = cmd.rawCount - if (line == 0) { - val nextVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor) + 1 - line = EditorHelper.visualLineToLogicalLine(editor, nextVisualLine) + 1 // rawCount is 1 based + var rawCount = cmd.rawCount + if (rawCount == 0) { + val nextVisualLine = EditorHelper.normalizeVisualLine(editor, + EditorHelper.getVisualLineAtBottomOfScreen(editor) + 1) + rawCount = EditorHelper.visualLineToLogicalLine(editor, nextVisualLine) + 1 // rawCount is 1 based } - return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, line, true) + return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, rawCount, true) } } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLinePageStartAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLinePageStartAction.kt index eea5392857..5892d0749e 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLinePageStartAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLinePageStartAction.kt @@ -21,28 +21,37 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollLastScreenLinePageStartAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { val motion = VimPlugin.getMotion() - var line = cmd.rawCount - if (line == 0) { - val prevVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor) - 1 - line = EditorHelper.visualLineToLogicalLine(editor, prevVisualLine) + 1 // rawCount is 1 based - return motion.scrollLineToLastScreenLine(editor, line, true) + + // Without [count]: Redraw with the line just above the window at the bottom of the window. Put the cursor in that + // line, at the first non-blank in the line. + if (cmd.rawCount == 0) { + val prevVisualLine = EditorHelper.normalizeVisualLine(editor, + EditorHelper.getVisualLineAtTopOfScreen(editor) - 1) + val logicalLine = EditorHelper.visualLineToLogicalLine(editor, prevVisualLine) + return motion.scrollLineToLastScreenLine(editor, logicalLine + 1, true) } + // [count]z^ first scrolls [count] to the bottom of the window, then moves the caret to the line that is now at // the top, and then move that line to the bottom of the window - line = EditorHelper.normalizeLine(editor, line) - if (motion.scrollLineToLastScreenLine(editor, line, true)) { - line = EditorHelper.getVisualLineAtTopOfScreen(editor) - line = EditorHelper.visualLineToLogicalLine(editor, line) + 1 // rawCount is 1 based - return motion.scrollLineToLastScreenLine(editor, line, true) + var logicalLine = EditorHelper.normalizeLine(editor, cmd.rawCount - 1) + if (motion.scrollLineToLastScreenLine(editor, logicalLine + 1, false)) { + logicalLine = EditorHelper.visualLineToLogicalLine(editor, EditorHelper.getVisualLineAtTopOfScreen(editor)) + return motion.scrollLineToLastScreenLine(editor, logicalLine + 1, true) } + return false } } diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index 99132c1d96..fb197266aa 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -572,31 +572,26 @@ public int moveCaretToBeforeNextCharacterOnLine(@NotNull Editor editor, @NotNull public boolean scrollLineToFirstScreenLine(@NotNull Editor editor, int rawCount, boolean start) { scrollLineToScreenLocation(editor, ScreenLocation.TOP, rawCount, start); - return true; } public boolean scrollLineToMiddleScreenLine(@NotNull Editor editor, int rawCount, boolean start) { scrollLineToScreenLocation(editor, ScreenLocation.MIDDLE, rawCount, start); - return true; } public boolean scrollLineToLastScreenLine(@NotNull Editor editor, int rawCount, boolean start) { scrollLineToScreenLocation(editor, ScreenLocation.BOTTOM, rawCount, start); - return true; } public boolean scrollColumnToFirstScreenColumn(@NotNull Editor editor) { scrollColumnToScreenColumn(editor, 0); - return true; } public boolean scrollColumnToLastScreenColumn(@NotNull Editor editor) { scrollColumnToScreenColumn(editor, EditorHelper.getScreenWidth(editor)); - return true; } @@ -1193,17 +1188,16 @@ public int moveCaretGotoLineFirst(@NotNull Editor editor, int line) { } // Scrolls current or [count] line to given screen location - // In Vim, [count] refers to a file line, so it's a logical line + // In Vim, [count] refers to a file line, so it's a one-based logical line private void scrollLineToScreenLocation(@NotNull Editor editor, @NotNull ScreenLocation screenLocation, - int line, + int rawCount, boolean start) { final int scrollOffset = getNormalizedScrollOffset(editor); - line = EditorHelper.normalizeLine(editor, line); - int visualLine = line == 0 - ? editor.getCaretModel().getVisualPosition().line - : EditorHelper.logicalLineToVisualLine(editor, line - 1); + int visualLine = rawCount == 0 + ? editor.getCaretModel().getVisualPosition().line + : EditorHelper.logicalLineToVisualLine(editor, EditorHelper.normalizeLine(editor, rawCount - 1)); // This method moves the current (or [count]) line to the specified screen location // Scroll offset is applicable, but scroll jump isn't. Offset is applied to screen lines (visual lines) @@ -1218,6 +1212,7 @@ private void scrollLineToScreenLocation(@NotNull Editor editor, EditorHelper.scrollVisualLineToBottomOfScreen(editor, visualLine + scrollOffset); break; } + if (visualLine != editor.getCaretModel().getVisualPosition().line || start) { int offset; if (start) { diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt index 9a274f9a42..834aa1a55d 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt @@ -23,7 +23,7 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |z+| (scrolls up) +// |z+| /* Without [count]: Redraw with the line just below the window at the top of the window. Put the cursor in that line, at the first non-blank in the line. diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt new file mode 100644 index 0000000000..176c6fd28a --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt @@ -0,0 +1,110 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// |z^| +/* Without [count]: Redraw with the line just above the + window at the bottom of the window. Put the cursor in + that line, at the first non-blank in the line. + With [count]: First scroll the text to put the [count] + line at the bottom of the window, then redraw with the + line which is now at the top of the window at the + bottom of the window. Put the cursor in that line, at + the first non-blank in the line. */ +class ScrollLastScreenLinePageStartActionTest : VimTestCase() { + fun `test scrolls last line on previous page to bottom of screen`() { + configureByPages(5) + setPositionAndScroll(99, 119) + typeText(parseKeys("z^")) + assertPosition(98, 0) + assertVisibleArea(64, 98) + } + + fun `test scrolls to first non-blank in line`() { + configureByLines(200, " I found it in a legendary land") + setPositionAndScroll(99, 119) + typeText(parseKeys("z^")) + assertPosition(98, 4) + assertVisibleArea(64, 98) + } + + fun `test scrolls last line on previous page to scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(99, 119) + typeText(parseKeys("z^")) + assertPosition(98, 0) + assertVisibleArea(74, 108) + } + + fun `test scrolls last line on previous page ignores scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(99, 119) + typeText(parseKeys("z^")) + assertPosition(98, 0) + assertVisibleArea(64, 98) + } + + fun `test count z^ puts count line at bottom of screen then scrolls back a page`() { + configureByPages(5) + setPositionAndScroll(140, 150) + typeText(parseKeys("100z^")) + // Put 100 at the bottom of the page. Top is 66. Scroll back a page so 66 is at bottom of page + assertPosition(65, 0) + assertVisibleArea(31, 65) + } + + fun `test z^ on first page puts cursor on first line 1`() { + configureByLines(50, " I found it in a legendary land") + setPositionAndScroll(0, 25) + typeText(parseKeys("z^")) + assertPosition(0, 4) + assertVisibleArea(0, 34) + } + + fun `test z^ on first page puts cursor on first line 2`() { + configureByLines(50, " I found it in a legendary land") + setPositionAndScroll(0, 6) + typeText(parseKeys("z^")) + assertPosition(0, 4) + assertVisibleArea(0, 34) + } + + fun `test z^ on first page ignores scrolloff and puts cursor on last line of previous page`() { + OptionsManager.scrolloff.set(10) + configureByLines(50, " I found it in a legendary land") + setPositionAndScroll(0, 6) + typeText(parseKeys("z^")) + assertPosition(0, 4) + assertVisibleArea(0, 34) + } + + fun `test z^ on second page puts cursor on previous last line`() { + configureByLines(50, " I found it in a legendary land") + setPositionAndScroll(19, 39) + typeText(parseKeys("z^")) + assertPosition(18, 4) + assertVisibleArea(0, 34) + } +} \ No newline at end of file From a7ba6d6004996cbc5c509a4af81cb448038f6ea6 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 3 Sep 2020 09:33:37 +0100 Subject: [PATCH 13/31] Add tests for ScrollFirstScreenLine actions z and zt --- .../MotionScrollFirstScreenLineAction.kt | 5 ++ .../MotionScrollFirstScreenLineStartAction.kt | 5 ++ .../scroll/ScrollFirstScreenLineActionTest.kt | 88 ++++++++++++++++++ .../ScrollFirstScreenLineStartActionTest.kt | 90 +++++++++++++++++++ 4 files changed, 188 insertions(+) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineAction.kt index d13c22dd1e..0d2b66939c 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollFirstScreenLineAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, cmd.rawCount, false) } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineStartAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineStartAction.kt index af1303391f..d5ba56de57 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineStartAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenLineStartAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollFirstScreenLineStartAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, cmd.rawCount, true) } diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt new file mode 100644 index 0000000000..34dcad3ce5 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt @@ -0,0 +1,88 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.helper.StringHelper +import com.maddyhome.idea.vim.helper.VimBehaviorDiffers +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// |zt| +/* Like "z", but leave the cursor in the same column. */ +class ScrollFirstScreenLineActionTest : VimTestCase() { + fun `test scroll current line to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("zt")) + assertPosition(19, 0) + assertVisibleArea(19, 53) + } + + fun `test scroll current line to top of screen and leave cursor in current column`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(0, 19, 14) + typeText(StringHelper.parseKeys("zt")) + assertPosition(19, 14) + assertVisibleArea(19, 53) + } + + fun `test scroll current line to top of screen minus scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("zt")) + assertPosition(19, 0) + assertVisibleArea(9, 43) + } + + fun `test scrolls count line to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("100zt")) + assertPosition(99, 0) + assertVisibleArea(99, 133) + } + + fun `test scrolls count line to top of screen minus scrolloff`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("zt")) + assertPosition(19, 0) + assertVisibleArea(19, 53) + } + + @VimBehaviorDiffers(description = "Virtual space at end of file") + fun `test invalid count scrolls last line to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("1000zt")) + assertPosition(175, 0) + assertVisibleArea(146, 175) + } + + fun `test scroll current line to top of screen ignoring scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("zt")) + assertPosition(19, 0) + assertVisibleArea(19, 53) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt new file mode 100644 index 0000000000..d5190b27bd --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt @@ -0,0 +1,90 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.helper.StringHelper +import com.maddyhome.idea.vim.helper.VimBehaviorDiffers +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// |z| +/* Redraw, line [count] at top of window (default + cursor line). Put cursor at first non-blank in the + line. */ +class ScrollFirstScreenLineStartActionTest : VimTestCase() { + fun `test scroll current line to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("z")) + assertPosition(19, 0) + assertVisibleArea(19, 53) + } + + fun `test scroll current line to top of screen and move to first non-blank`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(0, 19, 0) + typeText(StringHelper.parseKeys("z")) + assertPosition(19, 4) + assertVisibleArea(19, 53) + } + + fun `test scroll current line to top of screen minus scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("z")) + assertPosition(19, 0) + assertVisibleArea(9, 43) + } + + fun `test scrolls count line to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("100z")) + assertPosition(99, 0) + assertVisibleArea(99, 133) + } + + fun `test scrolls count line to top of screen minus scrolloff`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("z")) + assertPosition(19, 0) + assertVisibleArea(19, 53) + } + + @VimBehaviorDiffers(description = "Virtual space at end of file") + fun `test invalid count scrolls last line to top of screen`() { + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("1000z")) + assertPosition(175, 0) + assertVisibleArea(146, 175) + } + + fun `test scroll current line to top of screen ignoring scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(0, 19) + typeText(StringHelper.parseKeys("z")) + assertPosition(19, 0) + assertVisibleArea(19, 53) + } +} \ No newline at end of file From 63d9a33d808b9eb06df37076f1dfcbdf2d57c6e7 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 3 Sep 2020 09:55:40 +0100 Subject: [PATCH 14/31] Add tests for ScrollLastScreenLine actions z- and zb --- .../MotionScrollLastScreenLineAction.kt | 5 ++ .../MotionScrollLastScreenLineStartAction.kt | 5 ++ .../scroll/ScrollLastScreenLineActionTest.kt | 86 ++++++++++++++++++ .../ScrollLastScreenLineStartActionTest.kt | 88 +++++++++++++++++++ 4 files changed, 184 insertions(+) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineAction.kt index 589f8cb4cf..2a46649c29 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollLastScreenLineAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLineToLastScreenLine(editor, cmd.rawCount, false) } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineStartAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineStartAction.kt index 833a22e548..f7e233c884 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineStartAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenLineStartAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollLastScreenLineStartAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLineToLastScreenLine(editor, cmd.rawCount, true) } diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt new file mode 100644 index 0000000000..0c89ac8998 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt @@ -0,0 +1,86 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.helper.StringHelper +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// |zb| +/* Like "z-", but leave the cursor in the same column. */ +class ScrollLastScreenLineActionTest : VimTestCase() { + fun `test scroll current line to bottom of screen`() { + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("zb")) + assertPosition(60, 0) + assertVisibleArea(26, 60) + } + + fun `test scroll current line to bottom of screen and leave cursor in current column`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(40, 60, 14) + typeText(StringHelper.parseKeys("zb")) + assertPosition(60, 14) + assertVisibleArea(26, 60) + } + + fun `test scroll current line to bottom of screen minus scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("zb")) + assertPosition(60, 0) + assertVisibleArea(36, 70) + } + + fun `test scrolls count line to bottom of screen`() { + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("100zb")) + assertPosition(99, 0) + assertVisibleArea(65, 99) + } + + fun `test scrolls count line to bottom of screen minus scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("100zb")) + assertPosition(99, 0) + assertVisibleArea(75, 109) + } + + fun `test scrolls current line to bottom of screen ignoring scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("zb")) + assertPosition(60, 0) + assertVisibleArea(26, 60) + } + + fun `test scrolls correctly when less than a page to scroll`() { + configureByPages(5) + setPositionAndScroll(5, 15) + typeText(StringHelper.parseKeys("zb")) + assertPosition(15, 0) + assertVisibleArea(0, 34) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt new file mode 100644 index 0000000000..df10ec648d --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt @@ -0,0 +1,88 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.helper.StringHelper +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +// |z-| +/* Redraw, line [count] at bottom of window (default + cursor line). Put cursor at first non-blank in the + line. */ +class ScrollLastScreenLineStartActionTest : VimTestCase() { + fun `test scroll current line to bottom of screen`() { + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("z-")) + assertPosition(60, 0) + assertVisibleArea(26, 60) + } + + fun `test scroll current line to bottom of screen and move cursor to first non-blank`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(40, 60, 14) + typeText(StringHelper.parseKeys("z-")) + assertPosition(60, 4) + assertVisibleArea(26, 60) + } + + fun `test scroll current line to bottom of screen minus scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("z-")) + assertPosition(60, 0) + assertVisibleArea(36, 70) + } + + fun `test scrolls count line to bottom of screen`() { + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("100z-")) + assertPosition(99, 0) + assertVisibleArea(65, 99) + } + + fun `test scrolls count line to bottom of screen minus scrolloff`() { + OptionsManager.scrolloff.set(10) + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("100z-")) + assertPosition(99, 0) + assertVisibleArea(75, 109) + } + + fun `test scrolls current line to bottom of screen ignoring scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(40, 60) + typeText(StringHelper.parseKeys("z-")) + assertPosition(60, 0) + assertVisibleArea(26, 60) + } + + fun `test scrolls correctly when less than a page to scroll`() { + configureByPages(5) + setPositionAndScroll(5, 15) + typeText(StringHelper.parseKeys("z-")) + assertPosition(15, 0) + assertVisibleArea(0, 34) + } +} \ No newline at end of file From 7321099a0f9257f5875a331f2e77dae0c0d3fab4 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 3 Sep 2020 10:10:57 +0100 Subject: [PATCH 15/31] Add tests for ScrollMiddleScreenLine actions z. and zz --- .../MotionScrollMiddleScreenLineAction.kt | 5 ++ ...MotionScrollMiddleScreenLineStartAction.kt | 5 ++ .../ScrollMiddleScreenLineActionTest.kt | 78 ++++++++++++++++++ .../ScrollMiddleScreenLineStartActionTest.kt | 80 +++++++++++++++++++ 4 files changed, 168 insertions(+) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineAction.kt index e88f83fc8a..d8e58cc46a 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollMiddleScreenLineAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLineToMiddleScreenLine(editor, cmd.rawCount, false) } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineStartAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineStartAction.kt index 9978b8ba76..7119192aaf 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineStartAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollMiddleScreenLineStartAction.kt @@ -21,11 +21,16 @@ import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* class MotionScrollMiddleScreenLineStartAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SCROLL_JUMP) + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { return VimPlugin.getMotion().scrollLineToMiddleScreenLine(editor, cmd.rawCount, true) } diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt new file mode 100644 index 0000000000..e472412eb6 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt @@ -0,0 +1,78 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +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 + +// |zz| +/* Like "z.", but leave the cursor in the same column. */ +class ScrollMiddleScreenLineActionTest : VimTestCase() { + fun `test scrolls current line to middle of screen`() { + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("zz")) + assertPosition(45, 0) + assertVisibleArea(28, 62) + } + + fun `test scrolls current line to middle of screen and keeps cursor in the same column`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(40, 45, 14) + typeText(parseKeys("zz")) + assertPosition(45, 14) + assertVisibleArea(28, 62) + } + + fun `test scrolls count line to the middle of the screen`() { + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("100zz")) + assertPosition(99, 0) + assertVisibleArea(82, 116) + } + + fun `test scrolls count line ignoring scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("100zz")) + assertPosition(99, 0) + assertVisibleArea(82, 116) + } + + fun `test scrolls correctly when count line is in first half of first page`() { + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("10zz")) + assertPosition(9, 0) + assertVisibleArea(0, 34) + } + + @VimBehaviorDiffers(description = "Virtual space at end of file") + fun `test scrolls last line of file correctly`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("175zz")) + assertPosition(174, 0) + assertVisibleArea(146, 175) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt new file mode 100644 index 0000000000..9a37827bea --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt @@ -0,0 +1,80 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +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 + +// |z.| +/* Redraw, line [count] at center of window (default + cursor line). Put cursor at first non-blank in the + line. */ +class ScrollMiddleScreenLineStartActionTest : VimTestCase() { + fun `test scrolls current line to middle of screen`() { + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("z.")) + assertPosition(45, 0) + assertVisibleArea(28, 62) + } + + fun `test scrolls current line to middle of screen and moves cursor to first non-blank`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(40, 45, 14) + typeText(parseKeys("z.")) + assertPosition(45, 4) + assertVisibleArea(28, 62) + } + + fun `test scrolls count line to the middle of the screen`() { + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("100z.")) + assertPosition(99, 0) + assertVisibleArea(82, 116) + } + + fun `test scrolls count line ignoring scrolljump`() { + OptionsManager.scrolljump.set(10) + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("100z.")) + assertPosition(99, 0) + assertVisibleArea(82, 116) + } + + fun `test scrolls correctly when count line is in first half of first page`() { + configureByPages(5) + setPositionAndScroll(40, 45) + typeText(parseKeys("10z.")) + assertPosition(9, 0) + assertVisibleArea(0, 34) + } + + @VimBehaviorDiffers(description = "Virtual space at end of file") + fun `test scrolls last line of file correctly`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("175z.")) + assertPosition(174, 0) + assertVisibleArea(146, 175) + } +} \ No newline at end of file From 5ca0298497be59dec34d4e48f37a8080c2021314 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 3 Sep 2020 17:46:30 +0100 Subject: [PATCH 16/31] Add tests for ScrollHalfPage actions and --- .../scroll/ScrollHalfPageDownActionTest.kt | 112 ++++++++++++++++++ .../scroll/ScrollHalfPageUpActionTest.kt | 105 ++++++++++++++++ .../action/scroll/ScrollPageDownActionTest.kt | 10 +- .../action/scroll/ScrollPageUpActionTest.kt | 9 ++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt new file mode 100644 index 0000000000..81d4dcc9a3 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt @@ -0,0 +1,112 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.helper.VimBehaviorDiffers +import com.maddyhome.idea.vim.option.OptionsManager +import junit.framework.Assert +import org.jetbrains.plugins.ideavim.VimTestCase + +// || +/* Scroll window Downwards in the buffer. The number of + lines comes from the 'scroll' option (default: half a + screen). If [count] given, first set 'scroll' option + to [count]. The cursor is moved the same number of + lines down in the file (if possible; when lines wrap + and when hitting the end of the file there may be a + difference). When the cursor is on the last line of + the buffer nothing happens and a beep is produced. + See also 'startofline' option. */ +class ScrollHalfPageDownActionTest : VimTestCase() { + fun `test scroll half window downwards keeps cursor on same relative line`() { + configureByPages(5) + setPositionAndScroll(20, 25) + typeText(parseKeys("")) + assertPosition(42, 0) + assertVisibleArea(37, 71) + } + + fun `test scroll downwards on last line causes beep`() { + configureByPages(5) + setPositionAndScroll(146, 175) + typeText(parseKeys("")) + assertPosition(175, 0) + assertVisibleArea(146, 175) + assertTrue(VimPlugin.isError()) + } + + fun `test scroll downwards in bottom half of last page moves to the last line`() { + configureByPages(5) + setPositionAndScroll(146, 165) + typeText(parseKeys("")) + assertPosition(175, 0) + assertVisibleArea(146, 175) + } + + fun `test scroll downwards in top half of last page moves cursor down half a page`() { + configureByPages(5) + setPositionAndScroll(146, 150) + typeText(parseKeys("")) + assertPosition(167, 0) + assertVisibleArea(146, 175) + } + + fun `test scroll count lines downwards`() { + configureByPages(5) + setPositionAndScroll(100, 130) + typeText(parseKeys("10")) + assertPosition(140, 0) + assertVisibleArea(110, 144) + } + + fun `test scroll count downwards modifies scroll option`() { + configureByPages(5) + setPositionAndScroll(100, 110) + typeText(parseKeys("10")) + Assert.assertEquals(OptionsManager.scroll.value(), 10) + } + + fun `test scroll downwards uses scroll option`() { + OptionsManager.scroll.set(10) + configureByPages(5) + setPositionAndScroll(100, 110) + typeText(parseKeys("")) + assertPosition(120, 0) + assertVisibleArea(110, 144) + } + + fun `test count scroll downwards is limited to single page`() { + configureByPages(5) + setPositionAndScroll(100, 110) + typeText(parseKeys("1000")) + assertPosition(145, 0) + assertVisibleArea(135, 169) + } + + @VimBehaviorDiffers(description = "IdeaVim does not support the 'startofline' options") + fun `test scroll downwards puts cursor on first non-blank column`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(20, 25, 14) + typeText(parseKeys("")) + assertPosition(42, 4) + assertVisibleArea(37, 71) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt new file mode 100644 index 0000000000..ad19cf807b --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt @@ -0,0 +1,105 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.helper.VimBehaviorDiffers +import com.maddyhome.idea.vim.option.OptionsManager +import junit.framework.Assert +import org.jetbrains.plugins.ideavim.VimTestCase + +// || +/* Scroll window Upwards in the buffer. The number of + lines comes from the 'scroll' option (default: half a + screen). If [count] given, first set the 'scroll' + option to [count]. The cursor is moved the same + number of lines up in the file (if possible; when + lines wrap and when hitting the end of the file there + may be a difference). When the cursor is on the first + line of the buffer nothing happens and a beep is + produced. See also 'startofline' option. */ +class ScrollHalfPageUpActionTest : VimTestCase() { + fun `test scroll half window upwards keeps cursor on same relative line`() { + configureByPages(5) + setPositionAndScroll(50, 60) + typeText(parseKeys("")) + assertPosition(43, 0) + assertVisibleArea(33, 67) + } + + fun `test scroll upwards on first line causes beep`() { + configureByPages(5) + setPositionAndScroll(0, 0) + typeText(parseKeys("")) + assertPosition(0, 0) + assertVisibleArea(0, 34) + assertTrue(VimPlugin.isError()) + } + + fun `test scroll upwards in first half of first page moves to first line`() { + configureByPages(5) + setPositionAndScroll(5, 10) + typeText(parseKeys("")) + assertPosition(0, 0) + assertVisibleArea(0, 34) + } + + fun `test scroll count lines upwards`() { + configureByPages(5) + setPositionAndScroll(50, 53) + typeText(parseKeys("10")) + assertPosition(43, 0) + assertVisibleArea(40, 74) + } + + fun `test scroll count modifies scroll option`() { + configureByPages(5) + setPositionAndScroll(50, 53) + typeText(parseKeys("10")) + Assert.assertEquals(OptionsManager.scroll.value(), 10) + } + + fun `test scroll upwards uses scroll option`() { + OptionsManager.scroll.set(10) + configureByPages(5) + setPositionAndScroll(50, 53) + typeText(parseKeys("")) + assertPosition(43, 0) + assertVisibleArea(40, 74) + } + + fun `test count scroll upwards is limited to a single page`() { + configureByPages(5) + setPositionAndScroll(100, 134) + typeText(parseKeys("50")) + assertPosition(99, 0) + assertVisibleArea(65, 99) + } + + @VimBehaviorDiffers(description = "IdeaVim does not support the 'startofline' options") + fun `test scroll up puts cursor on first non-blank column`() { + configureByLines(100, " I found it in a legendary land") + setPositionAndScroll(50, 60, 14) + typeText(parseKeys("")) + assertPosition(43, 4) + assertVisibleArea(33, 67) + } +} + diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt index 1e770adb0c..13bb146384 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt @@ -18,6 +18,7 @@ package org.jetbrains.plugins.ideavim.action.scroll +import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -134,7 +135,7 @@ class ScrollPageDownActionTest : VimTestCase() { } @VimBehaviorDiffers(description = "IntelliJ does not have virtual space enabled by default") - fun `test scroll page down on final page moves cursor to end of file`() { + fun `test scroll page down on last page moves cursor to end of file`() { configureByPages(5) setPositionAndScroll(145, 150) typeText(parseKeys("")) @@ -149,4 +150,11 @@ class ScrollPageDownActionTest : VimTestCase() { assertPosition(143, 0) assertVisibleArea(143, 175) } + + fun `test scroll page down on last line causes beep`() { + configureByPages(5) + setPositionAndScroll(146, 175) + typeText(parseKeys("")) + assertTrue(VimPlugin.isError()) + } } \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt index f8d9269280..875bd93a7b 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt @@ -18,8 +18,10 @@ package org.jetbrains.plugins.ideavim.action.scroll +import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.OptionsManager +import junit.framework.Assert import org.jetbrains.plugins.ideavim.VimTestCase // ||, ||, |CTRL-B| @@ -140,6 +142,13 @@ class ScrollPageUpActionTest : VimTestCase() { assertVisibleArea(0, 34) } + fun `test scroll page up on first page causes beep`() { + configureByPages(5) + setPositionAndScroll(0, 25) + typeText(parseKeys("")) + Assert.assertTrue(VimPlugin.isError()) + } + fun `test scroll page up on second page moves cursor to previous top`() { configureByPages(5) setPositionAndScroll(10, 35) From 62601686aaf8729fc3af8103a63b2312f4b71cb1 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Sun, 6 Sep 2020 20:26:55 +0100 Subject: [PATCH 17/31] Add internal action to show inline inlays --- resources/META-INF/plugin.xml | 3 +- ...nlaysAction.kt => AddBlockInlaysAction.kt} | 2 +- .../action/internal/AddInlineInlaysAction.kt | 58 +++++++++++++++++++ .../idea/vim/helper/EditorHelper.java | 7 ++- 4 files changed, 65 insertions(+), 5 deletions(-) rename src/com/maddyhome/idea/vim/action/internal/{AddInlaysAction.kt => AddBlockInlaysAction.kt} (99%) create mode 100644 src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt diff --git a/resources/META-INF/plugin.xml b/resources/META-INF/plugin.xml index 4fb46ab712..c63001a4b0 100644 --- a/resources/META-INF/plugin.xml +++ b/resources/META-INF/plugin.xml @@ -78,7 +78,8 @@ - + + diff --git a/src/com/maddyhome/idea/vim/action/internal/AddInlaysAction.kt b/src/com/maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt similarity index 99% rename from src/com/maddyhome/idea/vim/action/internal/AddInlaysAction.kt rename to src/com/maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt index a19fd26eb8..c538029018 100644 --- a/src/com/maddyhome/idea/vim/action/internal/AddInlaysAction.kt +++ b/src/com/maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt @@ -41,7 +41,7 @@ import java.util.* import javax.swing.UIManager import kotlin.math.max -class AddInlaysAction : AnAction() { +class AddBlockInlaysAction : AnAction() { override fun actionPerformed(e: AnActionEvent) { val dataContext = e.dataContext val editor = getEditor(dataContext) ?: return diff --git a/src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt b/src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt new file mode 100644 index 0000000000..7c32fd1096 --- /dev/null +++ b/src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt @@ -0,0 +1,58 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.maddyhome.idea.vim.action.internal + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.openapi.actionSystem.AnAction +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.CommonDataKeys +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.editor.VisualPosition +import com.maddyhome.idea.vim.helper.EditorHelper +import java.util.* +import kotlin.math.max + +class AddInlineInlaysAction : AnAction() { + companion object { + private val random = Random() + } + + override fun actionPerformed(e: AnActionEvent) { + val dataContext = e.dataContext + val editor = getEditor(dataContext) ?: return + val inlayModel = editor.inlayModel + val currentVisualLine = editor.caretModel.primaryCaret.visualPosition.line + var i = random.nextInt(10) + val lineLength = EditorHelper.getLineLength(editor, EditorHelper.visualLineToLogicalLine(editor, currentVisualLine)) + while (i < lineLength) { + val relatesToPrecedingText = random.nextInt(10) > 7 + val text = "a".repeat(max(1, random.nextInt(7))) + val offset = EditorHelper.visualPositionToOffset(editor, VisualPosition(currentVisualLine, i)) + // We don't need a custom renderer, just use the standard parameter hint renderer + inlayModel.addInlineElement(offset, relatesToPrecedingText, HintRenderer(if (relatesToPrecedingText) ":$text" else "$text:")) + // Every 20 chars +/- 5 chars + i+= 20 + (random.nextInt(10) - 5) + } + } + + private fun getEditor(dataContext: DataContext): Editor? { + return CommonDataKeys.EDITOR.getData(dataContext) + } +} \ No newline at end of file diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index 03546e6845..ec8767660d 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -90,15 +90,15 @@ public static int getLineLength(final @NotNull Editor editor) { * characters if there are "real" tabs in the line. * * @param editor The editor - * @param line The logical line within the file + * @param logicalLine The logical line within the file * @return The number of characters in the specified line */ - public static int getLineLength(final @NotNull Editor editor, final int line) { + public static int getLineLength(final @NotNull Editor editor, final int logicalLine) { if (getLineCount(editor) == 0) { return 0; } else { - return Math.max(0, editor.offsetToLogicalPosition(editor.getDocument().getLineEndOffset(line)).column); + return Math.max(0, editor.offsetToLogicalPosition(editor.getDocument().getLineEndOffset(logicalLine)).column); } } @@ -462,6 +462,7 @@ public static int getLeadingCharacterOffset(final @NotNull Editor editor, final * @return The file offset of the visual position */ public static int visualPositionToOffset(final @NotNull Editor editor, final @NotNull VisualPosition pos) { + // [202] return editor.visualPositionToOffset(pos); return editor.logicalPositionToOffset(editor.visualToLogicalPosition(pos)); } From 2091bbc02570fb82637fe92f646a11fe72c746ab Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Tue, 8 Sep 2020 17:35:45 +0100 Subject: [PATCH 18/31] Improve cursor position handling with inlay hints E.g. navigation around Kotlin type annotations, replacing a character with a preceding parameter hint --- .../select/SelectEnableBlockModeAction.kt | 3 +- .../select/SelectEnableCharacterModeAction.kt | 3 +- .../motion/select/SelectToggleVisualMode.kt | 5 +- .../idea/vim/ex/handler/SortHandler.kt | 5 +- .../argtextobj/VimArgTextObjExtension.java | 3 +- .../exchange/VimExchangeExtension.kt | 10 +- .../surround/VimSurroundExtension.kt | 6 +- .../VimTextObjEntireExtension.java | 3 +- .../maddyhome/idea/vim/group/ChangeGroup.java | 38 +++--- .../maddyhome/idea/vim/group/MotionGroup.java | 7 +- .../maddyhome/idea/vim/group/copy/PutGroup.kt | 5 +- .../idea/vim/group/visual/VisualGroup.kt | 24 +--- .../maddyhome/idea/vim/helper/InlayHelper.kt | 68 ++++++++++ .../idea/vim/helper/ModeExtensions.kt | 2 +- .../idea/vim/listener/ListenerManager.kt | 19 +-- .../plugins/ideavim/NeovimTesting.kt | 1 + .../jetbrains/plugins/ideavim/VimTestCase.kt | 8 ++ .../delete/DeleteCharacterLeftActionTest.kt | 117 +++++++++++++++++ .../delete/DeleteCharacterRightActionTest.kt | 121 ++++++++++++++++++ .../leftright/MotionArrowLeftActionTest.kt | 48 +++++++ .../leftright/MotionArrowRightActionTest.kt | 48 +++++++ 21 files changed, 471 insertions(+), 73 deletions(-) create mode 100644 src/com/maddyhome/idea/vim/helper/InlayHelper.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt diff --git a/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableBlockModeAction.kt b/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableBlockModeAction.kt index ed8774f722..a82f976f87 100644 --- a/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableBlockModeAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableBlockModeAction.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.CommandState +import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.helper.EditorHelper @@ -41,7 +42,7 @@ class SelectEnableBlockModeAction : VimActionHandler.SingleExecution() { val lineEnd = EditorHelper.getLineEndForOffset(editor, editor.caretModel.primaryCaret.offset) editor.caretModel.primaryCaret.run { vimSetSystemSelectionSilently(offset, (offset + 1).coerceAtMost(lineEnd)) - moveToOffset((offset + 1).coerceAtMost(lineEnd)) + moveToInlayAwareOffset((offset + 1).coerceAtMost(lineEnd)) vimLastColumn = visualPosition.column } return VimPlugin.getVisualMotion().enterSelectMode(editor, CommandState.SubMode.VISUAL_BLOCK) diff --git a/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableCharacterModeAction.kt b/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableCharacterModeAction.kt index 220ce3aa17..9826b61251 100644 --- a/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableCharacterModeAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/select/SelectEnableCharacterModeAction.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.CommandState +import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.helper.EditorHelper @@ -41,7 +42,7 @@ class SelectEnableCharacterModeAction : VimActionHandler.SingleExecution() { val lineEnd = EditorHelper.getLineEndForOffset(editor, caret.offset) caret.run { vimSetSystemSelectionSilently(offset, (offset + 1).coerceAtMost(lineEnd)) - moveToOffset((offset + 1).coerceAtMost(lineEnd)) + moveToInlayAwareOffset((offset + 1).coerceAtMost(lineEnd)) vimLastColumn = visualPosition.column } } diff --git a/src/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt b/src/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt index 81772d6932..0d92f72287 100644 --- a/src/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt +++ b/src/com/maddyhome/idea/vim/action/motion/select/SelectToggleVisualMode.kt @@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Editor import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.CommandState +import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.group.visual.updateCaretState import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.helper.commandState @@ -45,7 +46,7 @@ class SelectToggleVisualMode : VimActionHandler.SingleExecution() { if (subMode != CommandState.SubMode.VISUAL_LINE) { editor.caretModel.runForEachCaret { if (it.offset + VimPlugin.getVisualMotion().selectionAdj == it.selectionEnd) { - it.moveToOffset(it.offset + VimPlugin.getVisualMotion().selectionAdj) + it.moveToInlayAwareOffset(it.offset + VimPlugin.getVisualMotion().selectionAdj) } } } @@ -54,7 +55,7 @@ class SelectToggleVisualMode : VimActionHandler.SingleExecution() { if (subMode != CommandState.SubMode.VISUAL_LINE) { editor.caretModel.runForEachCaret { if (it.offset == it.selectionEnd && it.visualLineStart <= it.offset - VimPlugin.getVisualMotion().selectionAdj) { - it.moveToOffset(it.offset - VimPlugin.getVisualMotion().selectionAdj) + it.moveToInlayAwareOffset(it.offset - VimPlugin.getVisualMotion().selectionAdj) } } } diff --git a/src/com/maddyhome/idea/vim/ex/handler/SortHandler.kt b/src/com/maddyhome/idea/vim/ex/handler/SortHandler.kt index 0a46061559..9e39c44a29 100644 --- a/src/com/maddyhome/idea/vim/ex/handler/SortHandler.kt +++ b/src/com/maddyhome/idea/vim/ex/handler/SortHandler.kt @@ -28,6 +28,7 @@ import com.maddyhome.idea.vim.ex.ExCommand import com.maddyhome.idea.vim.ex.ExException import com.maddyhome.idea.vim.ex.flags import com.maddyhome.idea.vim.ex.ranges.LineRange +import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.inBlockSubMode import java.util.* @@ -51,7 +52,7 @@ class SortHandler : CommandHandler.SingleExecution() { val primaryCaret = editor.caretModel.primaryCaret val range = getLineRange(editor, primaryCaret, cmd) val worked = VimPlugin.getChange().sortRange(editor, range, lineComparator) - primaryCaret.moveToOffset(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, range.startLine)) + primaryCaret.moveToInlayAwareOffset(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, range.startLine)) return worked } @@ -61,7 +62,7 @@ class SortHandler : CommandHandler.SingleExecution() { if (!VimPlugin.getChange().sortRange(editor, range, lineComparator)) { worked = false } - caret.moveToOffset(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, range.startLine)) + caret.moveToInlayAwareOffset(VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, range.startLine)) } return worked diff --git a/src/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java b/src/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java index b48ce34fb9..298a54963b 100644 --- a/src/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java +++ b/src/com/maddyhome/idea/vim/extension/argtextobj/VimArgTextObjExtension.java @@ -11,6 +11,7 @@ import com.maddyhome.idea.vim.extension.VimExtension; import com.maddyhome.idea.vim.extension.VimExtensionHandler; import com.maddyhome.idea.vim.handler.TextObjectActionHandler; +import com.maddyhome.idea.vim.helper.InlayHelperKt; import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor; import com.maddyhome.idea.vim.listener.VimListenerSuppressor; import org.jetbrains.annotations.NotNull; @@ -234,7 +235,7 @@ public void execute(@NotNull Editor editor, @NotNull DataContext context) { if (commandState.getMode() == CommandState.Mode.VISUAL) { vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true); } else { - caret.moveToOffset(range.getStartOffset()); + InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset()); } } } diff --git a/src/com/maddyhome/idea/vim/extension/exchange/VimExchangeExtension.kt b/src/com/maddyhome/idea/vim/extension/exchange/VimExchangeExtension.kt index 02bc121fa5..181532bd1c 100644 --- a/src/com/maddyhome/idea/vim/extension/exchange/VimExchangeExtension.kt +++ b/src/com/maddyhome/idea/vim/extension/exchange/VimExchangeExtension.kt @@ -43,11 +43,9 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister import com.maddyhome.idea.vim.extension.VimExtensionHandler import com.maddyhome.idea.vim.group.MarkGroup -import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.* import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.StringHelper.stringToKeys -import com.maddyhome.idea.vim.helper.fileSize -import com.maddyhome.idea.vim.helper.subMode import com.maddyhome.idea.vim.key.OperatorFunction /** @@ -199,14 +197,14 @@ class VimExchangeExtension: VimExtension { fun fixCursor(ex1: Exchange, ex2: Exchange, reverse: Boolean) { val primaryCaret = editor.caretModel.primaryCaret if(reverse) { - primaryCaret.moveToOffset(editor.getMarkOffset(ex1.start)) + primaryCaret.moveToInlayAwareOffset(editor.getMarkOffset(ex1.start)) } else { if (ex1.start.logicalLine == ex2.start.logicalLine) { val horizontalOffset = ex1.end.col - ex2.end.col - primaryCaret.moveToLogicalPosition(LogicalPosition(ex1.start.logicalLine, ex1.start.col - horizontalOffset)) + primaryCaret.moveToInlayAwareLogicalPosition(LogicalPosition(ex1.start.logicalLine, ex1.start.col - horizontalOffset)) } else if(ex1.end.logicalLine - ex1.start.logicalLine != ex2.end.logicalLine - ex2.start.logicalLine) { val verticalOffset = ex1.end.logicalLine - ex2.end.logicalLine - primaryCaret.moveToLogicalPosition(LogicalPosition(ex1.start.logicalLine - verticalOffset, ex1.start.col)) + primaryCaret.moveToInlayAwareLogicalPosition(LogicalPosition(ex1.start.logicalLine - verticalOffset, ex1.start.col)) } } } diff --git a/src/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt b/src/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt index ca7bb948b8..45b758bfd8 100644 --- a/src/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt +++ b/src/com/maddyhome/idea/vim/extension/surround/VimSurroundExtension.kt @@ -175,10 +175,8 @@ class VimSurroundExtension : VimExtension { val change = VimPlugin.getChange() val leftSurround = pair.first val primaryCaret = editor.caretModel.primaryCaret - primaryCaret.moveToOffset(range.startOffset) - change.insertText(editor, primaryCaret, leftSurround) - primaryCaret.moveToOffset(range.endOffset + leftSurround.length) - change.insertText(editor, primaryCaret, pair.second) + change.insertText(editor, primaryCaret, range.startOffset, leftSurround) + change.insertText(editor, primaryCaret, range.endOffset + leftSurround.length, pair.second) // Jump back to start executeNormal(StringHelper.parseKeys("`["), editor) } diff --git a/src/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java b/src/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java index 23d997a444..db51c2e2df 100644 --- a/src/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java +++ b/src/com/maddyhome/idea/vim/extension/textobjentire/VimTextObjEntireExtension.java @@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.extension.VimExtension; import com.maddyhome.idea.vim.extension.VimExtensionHandler; import com.maddyhome.idea.vim.handler.TextObjectActionHandler; +import com.maddyhome.idea.vim.helper.InlayHelperKt; import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor; import com.maddyhome.idea.vim.listener.VimListenerSuppressor; import org.jetbrains.annotations.NotNull; @@ -140,7 +141,7 @@ public void execute(@NotNull Editor editor, @NotNull DataContext context) { if (commandState.getMode() == CommandState.Mode.VISUAL) { vimSetSelection(caret, range.getStartOffset(), range.getEndOffset() - 1, true); } else { - caret.moveToOffset(range.getStartOffset()); + InlayHelperKt.moveToInlayAwareOffset(caret, range.getStartOffset()); } } } diff --git a/src/com/maddyhome/idea/vim/group/ChangeGroup.java b/src/com/maddyhome/idea/vim/group/ChangeGroup.java index bde1c84ecc..efa73e3cdb 100644 --- a/src/com/maddyhome/idea/vim/group/ChangeGroup.java +++ b/src/com/maddyhome/idea/vim/group/ChangeGroup.java @@ -37,6 +37,7 @@ import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.event.EditorMouseListener; import com.intellij.openapi.editor.ex.EditorEx; +import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.impl.TextRangeInterval; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; @@ -627,8 +628,7 @@ private void repeatInsert(@NotNull Editor editor, @NotNull DataContext context, final String pad = EditorHelper.pad(editor, context, logicalLine + i, repeatColumn); if (pad.length() > 0) { final int offset = editor.getDocument().getLineEndOffset(logicalLine + i); - caret.moveToOffset(offset); - insertText(editor, caret, pad); + insertText(editor, caret, offset, pad); } } if (repeatColumn >= MotionGroup.LAST_COLUMN) { @@ -714,7 +714,7 @@ public boolean deleteCharacter(@NotNull Editor editor, @NotNull Caret caret, int final boolean res = deleteText(editor, new TextRange(caret.getOffset(), endOffset), SelectionType.CHARACTER_WISE); final int pos = caret.getOffset(); final int norm = EditorHelper.normalizeOffset(editor, caret.getLogicalPosition().line, pos, isChange); - if (norm != pos) { + if (norm != pos || editor.offsetToVisualPosition(norm) != EditorUtil.inlayAwareOffsetToVisualPosition(editor, norm)) { MotionGroup.moveCaret(editor, caret, norm); } @@ -1033,13 +1033,12 @@ public boolean changeCharacter(@NotNull Editor editor, @NotNull Caret caret, int // Indent new line if we replaced with a newline if (ch == '\n') { - caret.moveToOffset(offset + 1); - insertText(editor, caret, space); + insertText(editor, caret, offset + 1, space); int slen = space.length(); if (slen == 0) { slen++; } - caret.moveToOffset(offset + slen); + InlayHelperKt.moveToInlayAwareOffset(caret, offset + slen); } return true; @@ -1332,12 +1331,11 @@ else if (append) { if (column < MotionGroup.LAST_COLUMN && lineLength < column) { final String pad = EditorHelper.pad(editor, context, line, column); final int offset = editor.getDocument().getLineEndOffset(line); - caret.moveToOffset(offset); - insertText(editor, caret, pad); + insertText(editor, caret, offset, pad); } if (range.isMultiple() || !append) { - caret.moveToOffset(editor.logicalPositionToOffset(new LogicalPosition(line, column))); + InlayHelperKt.moveToInlayAwareLogicalPosition(caret, new LogicalPosition(line, column)); } if (range.isMultiple()) { setInsertRepeat(lines, column, append); @@ -1576,12 +1574,19 @@ public void indentLines(@NotNull Editor editor, * @param caret The caret to start insertion in * @param str The text to insert */ + public void insertText(@NotNull Editor editor, @NotNull Caret caret, int offset, @NotNull String str) { + editor.getDocument().insertString(offset, str); + InlayHelperKt.moveToInlayAwareOffset(caret, offset + str.length()); + + VimPlugin.getMark().setMark(editor, MarkGroup.MARK_CHANGE_POS, offset); + } + public void insertText(@NotNull Editor editor, @NotNull Caret caret, @NotNull String str) { - int start = caret.getOffset(); - editor.getDocument().insertString(start, str); - caret.moveToOffset(start + str.length()); + insertText(editor, caret, caret.getOffset(), str); + } - VimPlugin.getMark().setMark(editor, MarkGroup.MARK_CHANGE_POS, start); + public void insertText(@NotNull Editor editor, @NotNull Caret caret, @NotNull LogicalPosition start, @NotNull String str) { + insertText(editor, caret, editor.logicalPositionToOffset(start), str); } public void indentMotion(@NotNull Editor editor, @@ -1640,8 +1645,7 @@ public void indentRange(@NotNull Editor editor, int len = EditorHelper.getLineLength(editor, l); if (len > from) { LogicalPosition spos = new LogicalPosition(l, from); - caret.moveToOffset(editor.logicalPositionToOffset(spos)); - insertText(editor, caret, indent); + insertText(editor, caret, spos, indent); } } } @@ -1850,7 +1854,7 @@ public boolean changeNumberVisualMode(final @NotNull Editor editor, replaceText(editor, rangeToReplace.getFirst().getStartOffset(), rangeToReplace.getFirst().getEndOffset(), newNumber); } - caret.moveToOffset(selectedRange.getStartOffset()); + InlayHelperKt.moveToInlayAwareOffset(caret, selectedRange.getStartOffset()); return true; } @@ -1885,7 +1889,7 @@ public boolean changeNumber(final @NotNull Editor editor, @NotNull Caret caret, } else { replaceText(editor, range.getFirst().getStartOffset(), range.getFirst().getEndOffset(), newNumber); - caret.moveToOffset(range.getFirst().getStartOffset() + newNumber.length() - 1); + InlayHelperKt.moveToInlayAwareOffset(caret, range.getFirst().getStartOffset() + newNumber.length() - 1); return true; } } diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index fb197266aa..e56e00ea03 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -285,8 +285,11 @@ public static void moveCaret(@NotNull Editor editor, @NotNull Caret caret, int o return; } - if (caret.getOffset() != offset) { - caret.moveToOffset(offset); + // Always move the caret. It will be smart enough to not do anything if the offsets are the same, but it will also + // ensure that it's in the correct location relative to any inline inlays + final int oldOffset = caret.getOffset(); + InlayHelperKt.moveToInlayAwareOffset(caret, offset); + if (oldOffset != offset) { UserDataManager.setVimLastColumn(caret, caret.getVisualPosition().column); if (caret == editor.getCaretModel().getPrimaryCaret()) { scrollCaretIntoView(editor); diff --git a/src/com/maddyhome/idea/vim/group/copy/PutGroup.kt b/src/com/maddyhome/idea/vim/group/copy/PutGroup.kt index 4a5137b39e..1a438bb780 100644 --- a/src/com/maddyhome/idea/vim/group/copy/PutGroup.kt +++ b/src/com/maddyhome/idea/vim/group/copy/PutGroup.kt @@ -46,6 +46,7 @@ import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.group.MarkGroup import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.visual.VimSelection +import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.fileSize import com.maddyhome.idea.vim.option.ClipboardOptionsData @@ -127,7 +128,7 @@ class PutGroup { ApplicationManager.getApplication().runWriteAction { VimPlugin.getChange().deleteRange(editor, caret, range, selection.type, false) } - caret.moveToOffset(range.startOffset) + caret.moveToInlayAwareOffset(range.startOffset) } } @@ -259,7 +260,7 @@ class PutGroup { EditorHelper.getOrderedCaretsList(editor).forEach { caret -> val startOffset = prepareDocumentAndGetStartOffsets(editor, caret, text.typeInRegister, data, additionalData).first() val pointMarker = editor.document.createRangeMarker(startOffset, startOffset) - caret.moveToOffset(startOffset) + caret.moveToInlayAwareOffset(startOffset) carets[caret] = pointMarker } diff --git a/src/com/maddyhome/idea/vim/group/visual/VisualGroup.kt b/src/com/maddyhome/idea/vim/group/visual/VisualGroup.kt index dc935071ba..a3d7d857fe 100644 --- a/src/com/maddyhome/idea/vim/group/visual/VisualGroup.kt +++ b/src/com/maddyhome/idea/vim/group/visual/VisualGroup.kt @@ -28,17 +28,7 @@ import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.group.ChangeGroup import com.maddyhome.idea.vim.group.MotionGroup -import com.maddyhome.idea.vim.helper.EditorHelper -import com.maddyhome.idea.vim.helper.fileSize -import com.maddyhome.idea.vim.helper.inBlockSubMode -import com.maddyhome.idea.vim.helper.inSelectMode -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.sort -import com.maddyhome.idea.vim.helper.subMode -import com.maddyhome.idea.vim.helper.vimLastColumn -import com.maddyhome.idea.vim.helper.vimSelectionStart +import com.maddyhome.idea.vim.helper.* /** * @author Alex Plate @@ -52,7 +42,7 @@ import com.maddyhome.idea.vim.helper.vimSelectionStart fun Caret.vimSetSelection(start: Int, end: Int = start, moveCaretToSelectionEnd: Boolean = false) { vimSelectionStart = start setVisualSelection(start, end, this) - if (moveCaretToSelectionEnd && !editor.inBlockSubMode) moveToOffset(end) + if (moveCaretToSelectionEnd && !editor.inBlockSubMode) moveToInlayAwareOffset(end) } /** @@ -213,7 +203,7 @@ fun moveCaretOneCharLeftFromSelectionEnd(editor: Editor, predictedMode: CommandS editor.caretModel.allCarets.forEach { caret -> val lineEnd = EditorHelper.getLineEndForOffset(editor, caret.offset) val lineStart = EditorHelper.getLineStartForOffset(editor, caret.offset) - if (caret.offset == lineEnd && lineEnd != lineStart) caret.moveToOffset(caret.offset - 1) + if (caret.offset == lineEnd && lineEnd != lineStart) caret.moveToInlayAwareOffset(caret.offset - 1) } } return @@ -223,9 +213,9 @@ fun moveCaretOneCharLeftFromSelectionEnd(editor: Editor, predictedMode: CommandS if (caret.selectionEnd <= 0) return@forEach if (EditorHelper.getLineStartForOffset(editor, caret.selectionEnd - 1) != caret.selectionEnd - 1 && caret.selectionEnd > 1 && editor.document.text[caret.selectionEnd - 1] == '\n') { - caret.moveToOffset(caret.selectionEnd - 2) + caret.moveToInlayAwareOffset(caret.selectionEnd - 2) } else { - caret.moveToOffset(caret.selectionEnd - 1) + caret.moveToInlayAwareOffset(caret.selectionEnd - 1) } } } @@ -263,7 +253,7 @@ private fun setVisualSelection(selectionStart: Int, selectionEnd: Int, caret: Ca if (lastColumn >= MotionGroup.LAST_COLUMN) { aCaret.vimSetSystemSelectionSilently(aCaret.selectionStart, lineEndOffset) val newOffset = (lineEndOffset - VimPlugin.getVisualMotion().selectionAdj).coerceAtLeast(lineStartOffset) - aCaret.moveToOffset(newOffset) + aCaret.moveToInlayAwareOffset(newOffset) } val visualPosition = editor.offsetToVisualPosition(aCaret.selectionEnd) if (aCaret.offset == aCaret.selectionEnd && visualPosition != aCaret.visualPosition) { @@ -280,7 +270,7 @@ private fun setVisualSelection(selectionStart: Int, selectionEnd: Int, caret: Ca } } - editor.caretModel.primaryCaret.moveToOffset(selectionEnd) + editor.caretModel.primaryCaret.moveToInlayAwareOffset(selectionEnd) } else -> Unit } diff --git a/src/com/maddyhome/idea/vim/helper/InlayHelper.kt b/src/com/maddyhome/idea/vim/helper/InlayHelper.kt new file mode 100644 index 0000000000..21e37b83fd --- /dev/null +++ b/src/com/maddyhome/idea/vim/helper/InlayHelper.kt @@ -0,0 +1,68 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.maddyhome.idea.vim.helper + +import com.intellij.injected.editor.EditorWindow +import com.intellij.openapi.editor.* + +/** + * Move the caret to the given offset, handling inline inlays + * + * Inline inlays take up a single visual column. The caret can be positioned on the visual column of the inlay or the + * text. For Vim, we always want to position the caret before the text (when rendered as a block, this means over the + * text, and not over the inlay). Caret.moveToOffset will position itself correctly when an inlay relates to following + * text - it correctly adds one to the visual column. However, it does not add one if the inlay relates to preceding + * text. + * + * I believe this is an incorrect implementation of EditorUtil.inlayAwareOffsetToVisualPosition. When adding an + * inlay, it is added at an offset, and a new visual column is inserted there. When moving to an offset, that visual + * column is always there, regardless of whether the inlay relates to preceding or following text. + * + * It is safe to call this method if the caret hasn't actually moved. In fact, it is a good idea to do so, as it will + * make sure that if the document has changed to place an inlay at the caret position, the caret is re-positioned + * appropriately + */ +fun Caret.moveToInlayAwareOffset(offset: Int) { + val newVisualPosition = inlayAwareOffsetToVisualPosition(editor, offset) + if (newVisualPosition != visualPosition) { + this.moveToVisualPosition(inlayAwareOffsetToVisualPosition(editor, offset)) + } +} + +fun Caret.moveToInlayAwareLogicalPosition(pos: LogicalPosition) { + moveToInlayAwareOffset(editor.logicalPositionToOffset(pos)) +} + +// This is the same as EditorUtil.inlayAwareOffsetToVisualPosition, except it always skips the inlay, regardless of +// its "relates to preceding text" state +private fun inlayAwareOffsetToVisualPosition(editor: Editor, offset: Int): VisualPosition { + var logicalPosition = editor.offsetToLogicalPosition(offset) + val e = if (editor is EditorWindow) { + logicalPosition = editor.injectedToHost(logicalPosition) + editor.delegate + } + else { + editor + } + var pos = e.logicalToVisualPosition(logicalPosition) + while (editor.inlayModel.getInlineElementAt(pos) != null) { + pos = VisualPosition(pos.line, pos.column + 1) + } + return pos +} \ No newline at end of file diff --git a/src/com/maddyhome/idea/vim/helper/ModeExtensions.kt b/src/com/maddyhome/idea/vim/helper/ModeExtensions.kt index 46998150af..95bfccea3a 100644 --- a/src/com/maddyhome/idea/vim/helper/ModeExtensions.kt +++ b/src/com/maddyhome/idea/vim/helper/ModeExtensions.kt @@ -78,7 +78,7 @@ fun Editor.exitSelectMode(adjustCaretPosition: Boolean) { val lineEnd = EditorHelper.getLineEndForOffset(this, it.offset) val lineStart = EditorHelper.getLineStartForOffset(this, it.offset) if (it.offset == lineEnd && it.offset != lineStart) { - it.moveToOffset(it.offset - 1) + it.moveToInlayAwareOffset(it.offset - 1) } } } diff --git a/src/com/maddyhome/idea/vim/listener/ListenerManager.kt b/src/com/maddyhome/idea/vim/listener/ListenerManager.kt index 4f7cc31277..79b3a043b9 100644 --- a/src/com/maddyhome/idea/vim/listener/ListenerManager.kt +++ b/src/com/maddyhome/idea/vim/listener/ListenerManager.kt @@ -49,21 +49,8 @@ import com.maddyhome.idea.vim.group.EditorGroup import com.maddyhome.idea.vim.group.FileGroup import com.maddyhome.idea.vim.group.MotionGroup import com.maddyhome.idea.vim.group.SearchGroup -import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl -import com.maddyhome.idea.vim.group.visual.VimVisualTimer -import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd -import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently -import com.maddyhome.idea.vim.helper.EditorHelper -import com.maddyhome.idea.vim.helper.StatisticReporter -import com.maddyhome.idea.vim.helper.disabledForThisEditor -import com.maddyhome.idea.vim.helper.exitSelectMode -import com.maddyhome.idea.vim.helper.exitVisualMode -import com.maddyhome.idea.vim.helper.inSelectMode -import com.maddyhome.idea.vim.helper.inVisualMode -import com.maddyhome.idea.vim.helper.isEndAllowed -import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere -import com.maddyhome.idea.vim.helper.subMode -import com.maddyhome.idea.vim.helper.vimLastColumn +import com.maddyhome.idea.vim.group.visual.* +import com.maddyhome.idea.vim.helper.* import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.remove import com.maddyhome.idea.vim.option.OptionsManager @@ -359,7 +346,7 @@ object VimListenerManager { val lineEnd = EditorHelper.getLineEndForOffset(editor, caret.offset) val lineStart = EditorHelper.getLineStartForOffset(editor, caret.offset) cutOffEnd = if (caret.offset == lineEnd && lineEnd != lineStart) { - caret.moveToOffset(caret.offset - 1) + caret.moveToInlayAwareOffset(caret.offset - 1) true } else { false diff --git a/test/org/jetbrains/plugins/ideavim/NeovimTesting.kt b/test/org/jetbrains/plugins/ideavim/NeovimTesting.kt index 867328df39..89bfa331ce 100644 --- a/test/org/jetbrains/plugins/ideavim/NeovimTesting.kt +++ b/test/org/jetbrains/plugins/ideavim/NeovimTesting.kt @@ -104,6 +104,7 @@ annotation class TestWithoutNeovim(val reason: SkipNeovimReason, val description enum class SkipNeovimReason { PLUGIN, MULTICARET, + INLAYS, OPTION, UNCLEAR, NON_ASCII, diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index a5c6b3d6b2..1a14d4bf80 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -26,6 +26,7 @@ import com.intellij.openapi.application.WriteAction import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.LogicalPosition +import com.intellij.openapi.editor.VisualPosition import com.intellij.openapi.editor.colors.EditorColors import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileTypes.FileType @@ -225,6 +226,13 @@ abstract class VimTestCase : UsefulTestCase() { Assert.assertEquals(LogicalPosition(line, column), actualPosition) } + fun assertVisualPosition(visualLine: Int, visualColumn: Int) { + val carets = myFixture.editor.caretModel.allCarets + Assert.assertEquals("Wrong amount of carets", 1, carets.size) + val actualPosition = carets[0].visualPosition + Assert.assertEquals(VisualPosition(visualLine, visualColumn), actualPosition) + } + fun assertOffset(vararg expectedOffsets: Int) { val carets = myFixture.editor.caretModel.allCarets if (expectedOffsets.size == 2 && carets.size == 1) { diff --git a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt new file mode 100644 index 0000000000..fcefe3be05 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt @@ -0,0 +1,117 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.change.delete + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import org.jetbrains.plugins.ideavim.VimTestCase + +// |X| +class DeleteCharacterLeftActionTest : VimTestCase() { + fun `test delete single character`() { + val keys = parseKeys("X") + val before = "I f${c}ound it in a legendary land" + val after = "I ${c}ound it in a legendary land" + configureByText(before) + typeText(keys) + myFixture.checkResult(after) + } + + fun `test delete multiple characters`() { + val keys = parseKeys("5X") + val before = "I found$c it in a legendary land" + val after = "I $c it in a legendary land" + configureByText(before) + typeText(keys) + myFixture.checkResult(after) + } + + fun `test deletes min of count and start of line`() { + val keys = parseKeys("25X") + val before = """ + A Discovery + + I found$c it in a legendary land + all rocks and lavender and tufted grass, + where it was settled on some sodden sand + hard by the torrent of a mountain pass. + """.trimIndent() + val after = """ + A Discovery + + $c it in a legendary land + all rocks and lavender and tufted grass, + where it was settled on some sodden sand + hard by the torrent of a mountain pass. + """.trimIndent() + configureByText(before) + typeText(keys) + myFixture.checkResult(after) + } + + fun `test delete with inlay relating to preceding text`() { + val keys = parseKeys("X") + val before = "I fo${c}und it in a legendary land" + val after = "I f${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 3 ('o'). The 'u' is moved to the right one visual column, and now lives at offset + // 4, visual column 5. + // Kotlin type annotations are a real world example of inlays related to preceding text. + // Hitting 'X' on the character before the inlay should place the cursor after the inlay + // Before: "I fo«:test»|u|nd it in a legendary land." + // After: "I f«:test»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + + typeText(keys) + myFixture.checkResult(after) + + // It doesn't matter if the inlay is related to preceding or following text. Deleting visual column 3 moves the + // inlay one visual column to the left, from column 4 to 3. The cursor starts at offset 4, pushed to 5 by the inlay. + // 'X' moves the cursor one column to the left (along with the text), which puts it at offset 4. But offset 4 can + // now mean visual column 3 or 4 - the inlay or the text. Make sure the cursor is positioned on the text. + assertVisualPosition(0, 4) + } + + fun `test delete with inlay relating to following text`() { + // This should have the same behaviour as related to preceding text + val keys = parseKeys("X") + val before = "I fo${c}und it in a legendary land" + val after = "I f${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 5 ('u' - because the inlay pushes it one visual column to the right). + // Kotlin parameter hints are a real world example of inlays related to following text. + // Hitting 'X' on the character before the inlay should place the cursor after the inlay + // Before: "I fo«test:»|u|nd it in a legendary land." + // After: "I f«test:»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer("test:")) + + typeText(keys) + myFixture.checkResult(after) + + // It doesn't matter if the inlay is related to preceding or following text. Deleting visual column 3 moves the + // inlay one visual column to the left, from column 4 to 3. The cursor starts at offset 4, pushed to 5 by the inlay. + // 'X' moves the cursor one column to the left (along with the text), which puts it at offset 4. But offset 4 can + // now mean visual column 3 or 4 - the inlay or the text. Make sure the cursor is positioned on the text. + assertVisualPosition(0, 4) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt new file mode 100644 index 0000000000..133ce793c1 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt @@ -0,0 +1,121 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.change.delete + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import org.jetbrains.plugins.ideavim.VimTestCase + +// |x| +class DeleteCharacterRightActionTest : VimTestCase() { + fun `test delete single character`() { + val keys = parseKeys("x") + val before = "I ${c}found it in a legendary land" + val after = "I ${c}ound it in a legendary land" + configureByText(before) + typeText(keys) + myFixture.checkResult(after) + } + + fun `test delete multiple characters`() { + val keys = parseKeys("5x") + val before = "I ${c}found it in a legendary land" + val after = "I $c it in a legendary land" + configureByText(before) + typeText(keys) + myFixture.checkResult(after) + } + + fun `test deletes min of count and end of line`() { + val keys = parseKeys("20x") + val before = """ + A Discovery + + I found it in a legendary l${c}and + all rocks and lavender and tufted grass, + where it was settled on some sodden sand + hard by the torrent of a mountain pass. + """.trimIndent() + val after = """ + A Discovery + + I found it in a legendary ${c}l + all rocks and lavender and tufted grass, + where it was settled on some sodden sand + hard by the torrent of a mountain pass. + """.trimIndent() + configureByText(before) + typeText(keys) + myFixture.checkResult(after) + } + + fun `test delete with inlay relating to preceding text`() { + val keys = parseKeys("x") + val before = "I f${c}ound it in a legendary land" + val after = "I f${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 3 ('o'). The 'u' is moved to the right one visual column, and now lives at offset + // 4, visual column 5. + // Kotlin type annotations are a real world example of inlays related to preceding text. + // Hitting 'x' on the character before the inlay should place the cursor after the inlay + // Before: "I f|o|«:test»und it in a legendary land." + // After: "I f«:test»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + + typeText(keys) + myFixture.checkResult(after) + + // It doesn't matter if the inlay is related to preceding or following text. Deleting visual column 3 moves the + // inlay one visual column to the left, from column 4 to 3. 'x' doesn't move the logical position/offset of the + // cursor, but offset 3 can now refer to the inlay as well as text - visual column 3 and 4. Make sure the cursor is + // positioned on the text, not the inlay. + // Note that the inlay isn't deleted - deleting a character from the end of a variable name shouldn't delete the + // type annotation + assertVisualPosition(0, 4) + } + + fun `test delete with inlay relating to following text`() { + // This should have the same behaviour as related to preceding text + val keys = parseKeys("x") + val before = "I f${c}ound it in a legendary land" + val after = "I f${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 5 ('u' - because the inlay pushes it one visual column to the right). + // Kotlin parameter hints are a real world example of inlays related to following text. + // Hitting 'x' on the character before the inlay should place the cursor after the inlay + // Before: "I f|o|«test:»und it in a legendary land." + // After: "I f«test:»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer("test:")) + + typeText(keys) + myFixture.checkResult(after) + + // It doesn't matter if the inlay is related to preceding or following text. Deleting visual column 3 moves the + // inlay one visual column to the left, from column 4 to 3. 'x' doesn't move the logical position/offset of the + // cursor, but offset 3 can now refer to the inlay as well as text - visual column 3 and 4. Make sure the cursor is + // positioned on the text, not the inlay. + // Note that the inlay isn't deleted - deleting a character from the end of a variable name shouldn't delete the + // type annotation + assertVisualPosition(0, 4) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt index 2ed6aeb74e..e7f33e344e 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt @@ -20,7 +20,9 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright +import com.intellij.codeInsight.daemon.impl.HintRenderer import com.maddyhome.idea.vim.command.CommandState +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.KeyModelOptionData import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.TestWithoutNeovim @@ -31,6 +33,52 @@ import org.jetbrains.plugins.ideavim.VimTestOption import org.jetbrains.plugins.ideavim.VimTestOptionType class MotionArrowLeftActionTest : VimOptionTestCase(KeyModelOptionData.name) { + @VimOptionDefaultAll + fun `test with inlay related to preceding text`() { + val keys = parseKeys("h") + val before = "I fou${c}nd it in a legendary land" + val after = "I fo${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 5 ('u' - because the inlay pushes it one visual column to the right). + // Kotlin parameter hints are a real world example of inlays related to following text. + // Hitting 'l' on the character before the inlay should place the cursor after the inlay + // Before: "I f|o|«test:»und it in a legendary land." + // After: "I f«test:»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + + typeText(keys) + myFixture.checkResult(after) + + // The cursor starts at offset 5 and moves to offset 4. Offset 4 contains both the inlay and the next character, at + // visual positions 4 and 5 respectively. We always want the cursor to move to the next character, not the inlay. + assertVisualPosition(0, 5) + } + + @VimOptionDefaultAll + fun `test with inlay related to following text`() { + val keys = parseKeys("h") + val before = "I fou${c}nd it in a legendary land" + val after = "I fo${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 5 ('u' - because the inlay pushes it one visual column to the right). + // Kotlin parameter hints are a real world example of inlays related to following text. + // Hitting 'l' on the character before the inlay should place the cursor after the inlay + // Before: "I f|o|«test:»und it in a legendary land." + // After: "I fo«test:»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, false, HintRenderer("test:")) + + typeText(keys) + myFixture.checkResult(after) + + // The cursor starts at offset 5 and moves to offset 4. Offset 4 contains both the inlay and the next character, at + // visual positions 4 and 5 respectively. We always want the cursor to move to the next character, not the inlay. + assertVisualPosition(0, 5) + } + @TestWithoutNeovim(SkipNeovimReason.OPTION) @VimOptionDefaultAll fun `test visual default options`() { diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt index 2fec62c7dd..e6fc13aaad 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt @@ -20,7 +20,9 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright +import com.intellij.codeInsight.daemon.impl.HintRenderer import com.maddyhome.idea.vim.command.CommandState +import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.option.KeyModelOptionData import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.TestWithoutNeovim @@ -31,6 +33,52 @@ import org.jetbrains.plugins.ideavim.VimTestOption import org.jetbrains.plugins.ideavim.VimTestOptionType class MotionArrowRightActionTest : VimOptionTestCase(KeyModelOptionData.name) { + @VimOptionDefaultAll + fun `test with inlay related to preceding text`() { + val keys = StringHelper.parseKeys("l") + val before = "I f${c}ound it in a legendary land" + val after = "I fo${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 5 ('u' - because the inlay pushes it one visual column to the right). + // Kotlin parameter hints are a real world example of inlays related to following text. + // Hitting 'l' on the character before the inlay should place the cursor after the inlay + // Before: "I f|o|«test:»und it in a legendary land." + // After: "I f«test:»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + + typeText(keys) + myFixture.checkResult(after) + + // The cursor starts at offset 3 and moves to offset 4. Offset 4 contains both the inlay and the next character, at + // visual positions 4 and 5 respectively. We always want the cursor to move to the next character, not the inlay. + assertVisualPosition(0, 5) + } + + @VimOptionDefaultAll + fun `test with inlay related to following text`() { + val keys = StringHelper.parseKeys("l") + val before = "I f${c}ound it in a legendary land" + val after = "I fo${c}und it in a legendary land" + configureByText(before) + + // The inlay is inserted at offset 4 (0 based) - the 'u' in "found". It occupies visual column 4, and is associated + // with the text in visual column 5 ('u' - because the inlay pushes it one visual column to the right). + // Kotlin parameter hints are a real world example of inlays related to following text. + // Hitting 'l' on the character before the inlay should place the cursor after the inlay + // Before: "I f|o|«test:»und it in a legendary land." + // After: "I fo«test:»|u|nd it in a legendary land." + myFixture.editor.inlayModel.addInlineElement(4, false, HintRenderer("test:")) + + typeText(keys) + myFixture.checkResult(after) + + // The cursor starts at offset 3 and moves to offset 4. Offset 4 contains both the inlay and the next character, at + // visual positions 4 and 5 respectively. We always want the cursor to move to the next character, not the inlay. + assertVisualPosition(0, 5) + } + @TestWithoutNeovim(SkipNeovimReason.OPTION) @VimOptionDefaultAll fun `test visual default options`() { From 53a687fd535dc84b857df29b25a00ad7b3e0e46a Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Tue, 15 Sep 2020 17:07:42 +0100 Subject: [PATCH 19/31] Fix issues with side scrolling and inline inlays Fixes VIM-1556, fixes VIM-1770, fixes VIM-2110 --- .../scroll/MotionScrollColumnLeftAction.kt | 2 +- .../scroll/MotionScrollColumnRightAction.kt | 2 +- .../MotionScrollFirstScreenColumnAction.kt | 2 +- .../MotionScrollLastScreenColumnAction.kt | 2 +- .../maddyhome/idea/vim/group/MotionGroup.java | 153 ++++++--------- .../idea/vim/helper/EditorHelper.java | 134 +++++++++---- .../maddyhome/idea/vim/helper/InlayHelper.kt | 12 +- .../jetbrains/plugins/ideavim/VimTestCase.kt | 29 ++- .../ScrollFirstScreenColumnActionTest.kt | 113 +++++++++++ .../ScrollLastScreenColumnActionTest.kt | 133 +++++++++++++ ...up_ScrollCaretIntoViewHorizontally_Test.kt | 177 ++++++++++++++++++ ...oup_ScrollCaretIntoViewVertically_Test.kt} | 2 +- 12 files changed, 619 insertions(+), 142 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt create mode 100644 test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt rename test/org/jetbrains/plugins/ideavim/group/motion/{MotionGroup_ScrollCaretIntoView_Test.kt => MotionGroup_ScrollCaretIntoViewVertically_Test.kt} (98%) diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnLeftAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnLeftAction.kt index 0f01583900..532dd78885 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnLeftAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnLeftAction.kt @@ -32,6 +32,6 @@ class MotionScrollColumnLeftAction : VimActionHandler.SingleExecution() { override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP) override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { - return VimPlugin.getMotion().scrollColumn(editor, cmd.count) + return VimPlugin.getMotion().scrollColumns(editor, cmd.count) } } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnRightAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnRightAction.kt index 75c9320030..9545bc38d2 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnRightAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollColumnRightAction.kt @@ -31,6 +31,6 @@ class MotionScrollColumnRightAction : VimActionHandler.SingleExecution() { override val flags: EnumSet = EnumSet.of(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP) override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { - return VimPlugin.getMotion().scrollColumn(editor, -cmd.count) + return VimPlugin.getMotion().scrollColumns(editor, -cmd.count) } } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenColumnAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenColumnAction.kt index 59e5ba5048..cfc7624582 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenColumnAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollFirstScreenColumnAction.kt @@ -27,6 +27,6 @@ class MotionScrollFirstScreenColumnAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { - return VimPlugin.getMotion().scrollColumnToFirstScreenColumn(editor) + return VimPlugin.getMotion().scrollCaretColumnToFirstScreenColumn(editor) } } diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenColumnAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenColumnAction.kt index 8bc00d6953..47d9697314 100644 --- a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenColumnAction.kt +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollLastScreenColumnAction.kt @@ -27,6 +27,6 @@ class MotionScrollLastScreenColumnAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.OTHER_READONLY override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { - return VimPlugin.getMotion().scrollColumnToLastScreenColumn(editor) + return VimPlugin.getMotion().scrollCaretColumnToLastScreenColumn(editor) } } diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index e56e00ea03..924448360f 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -174,12 +174,12 @@ private static void moveCaretToView(@NotNull Editor editor) { int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor); int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); int caretVisualLine = editor.getCaretModel().getVisualPosition().line; - int newline = caretVisualLine; + int newVisualLine = caretVisualLine; if (caretVisualLine < topVisualLine + scrollOffset) { - newline = EditorHelper.normalizeVisualLine(editor, topVisualLine + scrollOffset); + newVisualLine = EditorHelper.normalizeVisualLine(editor, topVisualLine + scrollOffset); } else if (caretVisualLine >= bottomVisualLine - scrollOffset) { - newline = EditorHelper.normalizeVisualLine(editor, bottomVisualLine - scrollOffset); + newVisualLine = EditorHelper.normalizeVisualLine(editor, bottomVisualLine - scrollOffset); } int sideScrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); @@ -193,7 +193,7 @@ else if (caretVisualLine >= bottomVisualLine - scrollOffset) { if (col >= EditorHelper.getLineLength(editor) - 1) { col = UserDataManager.getVimLastColumn(editor.getCaretModel().getPrimaryCaret()); } - int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor); + int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor, newVisualLine); int caretColumn = col; int newColumn = caretColumn; if (caretColumn < visualColumn + sideScrollOffset) { @@ -203,14 +203,14 @@ else if (caretColumn >= visualColumn + width - sideScrollOffset) { newColumn = visualColumn + width - sideScrollOffset - 1; } - if (newline == caretVisualLine && newColumn != caretColumn) { + if (newVisualLine == caretVisualLine && newColumn != caretColumn) { col = newColumn; } - newColumn = EditorHelper.normalizeVisualColumn(editor, newline, newColumn, CommandStateHelper.isEndAllowed(CommandStateHelper.getMode(editor))); + newColumn = EditorHelper.normalizeVisualColumn(editor, newVisualLine, newColumn, CommandStateHelper.isEndAllowed(CommandStateHelper.getMode(editor))); - if (newline != caretVisualLine || newColumn != oldColumn) { - int offset = EditorHelper.visualPositionToOffset(editor, new VisualPosition(newline, newColumn)); + if (newVisualLine != caretVisualLine || newColumn != oldColumn) { + int offset = EditorHelper.visualPositionToOffset(editor, new VisualPosition(newVisualLine, newColumn)); moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), offset); UserDataManager.setVimLastColumn(editor.getCaretModel().getPrimaryCaret(), col); @@ -588,13 +588,21 @@ public boolean scrollLineToLastScreenLine(@NotNull Editor editor, int rawCount, return true; } - public boolean scrollColumnToFirstScreenColumn(@NotNull Editor editor) { - scrollColumnToScreenColumn(editor, 0); + public boolean scrollCaretColumnToFirstScreenColumn(@NotNull Editor editor) { + final VisualPosition caretVisualPosition = editor.getCaretModel().getVisualPosition(); + final int scrollOffset = Math.min(OptionsManager.INSTANCE.getSidescrolloff().value(), EditorHelper.getScreenWidth(editor) / 2); + // TODO: Should the offset be applied to visual columns? This includes inline inlays and folds + final int column = Math.max(0, caretVisualPosition.column - scrollOffset); + EditorHelper.scrollColumnToLeftOfScreen(editor, caretVisualPosition.line, column); return true; } - public boolean scrollColumnToLastScreenColumn(@NotNull Editor editor) { - scrollColumnToScreenColumn(editor, EditorHelper.getScreenWidth(editor)); + public boolean scrollCaretColumnToLastScreenColumn(@NotNull Editor editor) { + final VisualPosition caretVisualPosition = editor.getCaretModel().getVisualPosition(); + final int scrollOffset = Math.min(OptionsManager.INSTANCE.getSidescrolloff().value(), EditorHelper.getScreenWidth(editor) / 2); + // TODO: Should the offset be applied to visual columns? This includes inline inlays and folds + final int column = EditorHelper.normalizeVisualColumn(editor, caretVisualPosition.line, caretVisualPosition.column + scrollOffset, false); + EditorHelper.scrollColumnToRightOfScreen(editor, caretVisualPosition.line, column); return true; } @@ -739,58 +747,36 @@ private static int getScrollJump(@NotNull Editor editor, int height) { private static void scrollCaretIntoViewHorizontally(@NotNull Editor editor, @NotNull VisualPosition position) { - final int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor); - final int column = position.column; - final int width = EditorHelper.getScreenWidth(editor); + final int currentVisualLeftColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor, position.line); + final int currentVisualRightColumn = EditorHelper.getVisualColumnAtRightOfScreen(editor, position.line); + final int caretColumn = position.column; + + final int halfWidth = EditorHelper.getScreenWidth(editor) / 2; + final int scrollOffset = Math.min(OptionsManager.INSTANCE.getSidescrolloff().value(), halfWidth); final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); - final boolean scrollJump = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP); + final boolean allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP); + int sidescroll = OptionsManager.INSTANCE.getSidescroll().value(); - int scrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); - int scrollJumpSize = 0; - if (scrollJump) { - scrollJumpSize = Math.max(0, OptionsManager.INSTANCE.getSidescroll().value() - 1); - if (scrollJumpSize == 0) { - scrollJumpSize = width / 2; - } - } + final int offsetLeft = caretColumn - currentVisualLeftColumn - scrollOffset; + final int offsetRight = caretColumn - (currentVisualRightColumn - scrollOffset); + if (offsetLeft < 0 || offsetRight > 0) { + int diff = offsetLeft < 0 ? -offsetLeft : offsetRight; - int visualLeft = visualColumn + scrollOffset; - int visualRight = visualColumn + width - scrollOffset; - if (scrollOffset >= width / 2) { - scrollOffset = width / 2; - visualLeft = visualColumn + scrollOffset; - visualRight = visualColumn + width - scrollOffset; - if (visualLeft == visualRight) { - visualRight++; - } - } - - scrollJumpSize = Math.min(scrollJumpSize, width / 2 - scrollOffset); - - int diff; - if (column < visualLeft) { - diff = column - visualLeft + 1; - scrollJumpSize = -scrollJumpSize; - } - else { - diff = column - visualRight + 1; - if (diff < 0) { - diff = 0; - } - } - - if (diff != 0) { - int col; - if (Math.abs(diff) > width / 2) { - col = column - width / 2 - 1; + if ((allowSidescroll && sidescroll == 0) || diff >= halfWidth || offsetRight >= offsetLeft) { + EditorHelper.scrollColumnToMiddleOfScreen(editor, position.line, caretColumn); } else { - col = visualColumn + diff + scrollJumpSize; + if (allowSidescroll && diff < sidescroll) { + diff = sidescroll; + } + if (offsetLeft < 0) { + EditorHelper.scrollColumnToLeftOfScreen(editor, position.line, Math.max(0, currentVisualLeftColumn - diff)); + } else { + EditorHelper.scrollColumnToRightOfScreen(editor, position.line, + EditorHelper.normalizeVisualColumn(editor, position.line, currentVisualRightColumn + diff, false)); + } } - - col = Math.max(0, col); - scrollColumnToLeftOfScreen(editor, col); } } @@ -920,33 +906,6 @@ public int moveCaretToJump(@NotNull Editor editor, int count) { } } - private void scrollColumnToScreenColumn(@NotNull Editor editor, int column) { - int scrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); - int width = EditorHelper.getScreenWidth(editor); - if (scrollOffset > width / 2) { - scrollOffset = width / 2; - } - if (column <= width / 2) { - if (column < scrollOffset + 1) { - column = scrollOffset + 1; - } - } - else { - if (column > width - scrollOffset) { - column = width - scrollOffset; - } - } - - int visualColumn = editor.getCaretModel().getVisualPosition().column; - scrollColumnToLeftOfScreen(editor, EditorHelper - .normalizeVisualColumn(editor, editor.getCaretModel().getVisualPosition().line, visualColumn - column + 1, - false)); - } - - private static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int column) { - EditorHelper.scrollHorizontally(editor, column * EditorHelper.getColumnWidth(editor)); - } - public int moveCaretToMiddleColumn(@NotNull Editor editor, @NotNull Caret caret) { final int width = EditorHelper.getScreenWidth(editor) / 2; final int len = EditorHelper.getLineLength(editor); @@ -982,15 +941,19 @@ public int moveCaretToLineEnd(@NotNull Editor editor, @NotNull Caret caret) { return moveCaretToLineEnd(editor, editor.visualToLogicalPosition(visualEndOfLine).line, true); } - public boolean scrollColumn(@NotNull Editor editor, int columns) { - int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor); - visualColumn = EditorHelper - .normalizeVisualColumn(editor, editor.getCaretModel().getVisualPosition().line, visualColumn + columns, false); - - scrollColumnToLeftOfScreen(editor, visualColumn); - + public boolean scrollColumns(@NotNull Editor editor, int columns) { + final VisualPosition caretVisualPosition = editor.getCaretModel().getVisualPosition(); + if (columns > 0) { + final int visualColumn = EditorHelper.normalizeVisualColumn(editor, caretVisualPosition.line, + EditorHelper.getVisualColumnAtLeftOfScreen(editor, caretVisualPosition.line) + columns, false); + EditorHelper.scrollColumnToLeftOfScreen(editor, caretVisualPosition.line, visualColumn); + } + else { + final int visualColumn = EditorHelper.normalizeVisualColumn(editor, caretVisualPosition.line, + EditorHelper.getVisualColumnAtRightOfScreen(editor, caretVisualPosition.line) + columns, false); + EditorHelper.scrollColumnToRightOfScreen(editor, caretVisualPosition.line, visualColumn); + } moveCaretToView(editor); - return true; } @@ -1007,18 +970,18 @@ public int moveCaretToLineStart(@NotNull Editor editor, int line) { } public int moveCaretToLineScreenStart(@NotNull Editor editor, @NotNull Caret caret) { - final int col = EditorHelper.getVisualColumnAtLeftOfScreen(editor); + final int col = EditorHelper.getVisualColumnAtLeftOfScreen(editor, caret.getVisualPosition().line); return moveCaretToColumn(editor, caret, col, false); } public int moveCaretToLineScreenStartSkipLeading(@NotNull Editor editor, @NotNull Caret caret) { - final int col = EditorHelper.getVisualColumnAtLeftOfScreen(editor); + final int col = EditorHelper.getVisualColumnAtLeftOfScreen(editor, caret.getVisualPosition().line); final int logicalLine = caret.getLogicalPosition().line; return EditorHelper.getLeadingCharacterOffset(editor, logicalLine, col); } public int moveCaretToLineScreenEnd(@NotNull Editor editor, @NotNull Caret caret, boolean allowEnd) { - final int col = EditorHelper.getVisualColumnAtLeftOfScreen(editor) + EditorHelper.getScreenWidth(editor) - 1; + final int col = EditorHelper.getVisualColumnAtRightOfScreen(editor, caret.getVisualPosition().line); return moveCaretToColumn(editor, caret, col, allowEnd); } diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index ec8767660d..63ddcb5cf2 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.DataContext; import com.intellij.openapi.editor.*; +import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.vfs.VirtualFile; @@ -212,31 +213,25 @@ public static int getScreenWidth(final @NotNull Editor editor) { } /** - * Gets the number of pixels per column of text. - * + * Gets the visual column at the left of the screen for the given visual line. * @param editor The editor - * @return The number of pixels + * @param visualLine The visual line to use to check for inlays and support non-proportional fonts + * @return The visual column number */ - public static int getColumnWidth(final @NotNull Editor editor) { - Rectangle rect = getVisibleArea(editor); - if (rect.width == 0) return 0; - Point pt = new Point(rect.width, 0); - VisualPosition vp = editor.xyToVisualPosition(pt); - if (vp.column == 0) return 0; - - return rect.width / vp.column; + public static int getVisualColumnAtLeftOfScreen(final @NotNull Editor editor, int visualLine) { + final Rectangle area = getVisibleArea(editor); + return getFullVisualColumn(editor, area.x, editor.visualLineToY(visualLine), area.x, area.x + area.width); } /** - * Gets the column currently displayed at the left edge of the editor. - * + * Gets the visual column at the right of the screen for the given visual line. * @param editor The editor - * @return The column number + * @param visualLine The visual line to use to check for inlays and support non-proportional fonts + * @return The visual column number */ - public static int getVisualColumnAtLeftOfScreen(final @NotNull Editor editor) { - int cw = getColumnWidth(editor); - if (cw == 0) return 0; - return (getVisibleArea(editor).x + cw - 1) / cw; + public static int getVisualColumnAtRightOfScreen(final @NotNull Editor editor, int visualLine) { + final Rectangle area = getVisibleArea(editor); + return getFullVisualColumn(editor, area.x + area.width, editor.visualLineToY(visualLine), area.x, area.x + area.width); } /** @@ -618,8 +613,8 @@ public static void scrollVisualLineToCaretLocation(final @NotNull Editor editor, // We try to keep the caret in the same location, but only if there's enough space all around for the line's // inlays. E.g. caret on top screen line and the line has inlays above, or caret on bottom screen line and has // inlays below - final int topInlayHeight = EditorHelper.getHeightOfVisualLineInlays(editor, visualLine, true); - final int bottomInlayHeight = EditorHelper.getHeightOfVisualLineInlays(editor, visualLine, false); + final int topInlayHeight = EditorUtil.getInlaysHeight(editor, visualLine, true); + final int bottomInlayHeight = EditorUtil.getInlaysHeight(editor, visualLine, false); int inlayOffset = 0; if (topInlayHeight > caretScreenOffset) { @@ -640,8 +635,7 @@ public static void scrollVisualLineToCaretLocation(final @NotNull Editor editor, * @return Returns true if the window was moved */ public static boolean scrollVisualLineToTopOfScreen(final @NotNull Editor editor, int visualLine) { - final int inlayHeight = getHeightOfVisualLineInlays(editor, normalizeVisualLine(editor, visualLine), true); - int y = editor.visualLineToY(visualLine) - inlayHeight; + int y = EditorUtil.getVisualLineAreaStartY(editor, normalizeVisualLine(editor, visualLine)); // Normalise Y so that we don't try to scroll the editor to a location it can't reach. The editor will handle this, // but when we ask for the target location to move the caret to match, we'll get the incorrect value. @@ -683,7 +677,6 @@ public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, int * @return True if the editor was scrolled */ public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, int visualLine) { - int inlayHeight = getHeightOfVisualLineInlays(editor, normalizeVisualLine(editor, visualLine), false); int exPanelHeight = 0; if (ExEntryPanel.getInstance().isActive()) { exPanelHeight = ExEntryPanel.getInstance().getHeight(); @@ -691,10 +684,39 @@ public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, i if (ExEntryPanel.getInstanceWithoutShortcuts().isActive()) { exPanelHeight += ExEntryPanel.getInstanceWithoutShortcuts().getHeight(); } - int y = editor.visualLineToY(visualLine); - int height = inlayHeight + editor.getLineHeight() + exPanelHeight; - Rectangle visibleArea = getVisibleArea(editor); - return scrollVertically(editor, max(0, y - visibleArea.height + height)); + final int y = EditorUtil.getVisualLineAreaEndY(editor, normalizeVisualLine(editor, visualLine)) + exPanelHeight; + final Rectangle visibleArea = getVisibleArea(editor); + return scrollVertically(editor, max(0, y - visibleArea.height)); + } + + public static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) { + int inlayWidth = 0; + if (visualColumn > 0) { + final var inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn - 1)); + inlayWidth += inlay != null && !inlay.isRelatedToPrecedingText() ? inlay.getWidthInPixels() : 0; + } + final int columnLeftX = editor.visualPositionToXY(new VisualPosition(visualLine, visualColumn)).x; + EditorHelper.scrollHorizontally(editor, columnLeftX - inlayWidth); + } + + public static void scrollColumnToMiddleOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) { + final Point point = editor.visualPositionToXY(new VisualPosition(visualLine, visualColumn)); + final int screenWidth = EditorHelper.getVisibleArea(editor).width; + + // Snap the column to the nearest standard column grid. This positions us nicely if there are an odd or even number + // of columns. It also works with inline inlays and folds. It is slightly inaccurate for proportional fonts, but is + // still a good solution. Besides, what kind of monster uses Vim with proportional fonts? + final int standardColumnWidth = EditorUtil.getPlainSpaceWidth(editor); + final int something = ((point.x - (screenWidth / 2)) / standardColumnWidth) * standardColumnWidth; + EditorHelper.scrollHorizontally(editor, something); + } + + public static void scrollColumnToRightOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) { + var inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn + 1)); + int inlayWidth = inlay != null && inlay.isRelatedToPrecedingText() ? inlay.getWidthInPixels() : 0; + final int columnRightX = editor.visualPositionToXY(new VisualPosition(visualLine, visualColumn + 1)).x; + final int screenWidth = EditorHelper.getVisibleArea(editor).width; + EditorHelper.scrollHorizontally(editor, columnRightX + inlayWidth - 1 - screenWidth); } /** @@ -821,6 +843,8 @@ private static int scrollFullPageUp(final @NotNull Editor editor, int pages) { } private static int getFullVisualLine(final @NotNull Editor editor, int y, int topBound, int bottomBound) { + // Note that we ignore inlays here. We're interested in the bounds of the text line. Scrolling will handle inlays as + // it sees fit (e.g. scrolling a line to the bottom will make sure inlays below the line are visible). int line = editor.yToVisualLine(y); int yActual = editor.visualLineToY(line); if (yActual < topBound) { @@ -832,15 +856,55 @@ else if (yActual + editor.getLineHeight() > bottomBound) { return line; } - private static int getHeightOfVisualLineInlays(final @NotNull Editor editor, int visualLine, boolean above) { - InlayModel inlayModel = editor.getInlayModel(); - int inlayHeight = 0; - // [Version Update] 202+ Inlay is parametrized - //noinspection rawtypes - for (Inlay inlay : inlayModel.getBlockElementsForVisualLine(visualLine, above)) { - inlayHeight += inlay.getHeightInPixels(); + private static int getFullVisualColumn(final @NotNull Editor editor, int x, int y, int leftBound, int rightBound) { + // Mapping XY to a visual position will return the position of the closest character, rather than the position of + // the character grid that contains the XY. This means two things. Firstly, we don't get back the visual position of + // an inline inlay, and secondly, we can get the character to the left or right of X. This is the same logic for + // positioning the caret when you click in the editor. + // Note that visualPos.leansRight will be true for the right half side of the character grid + VisualPosition closestVisualPosition = editor.xyToVisualPosition(new Point(x, y)); + + // Make sure we get the character that contains this XY, not the editor's decision about closest character. The + // editor will give us the next character if X is over half way through the character grid. + int xActualLeft = editor.visualPositionToXY(closestVisualPosition).x; + if (xActualLeft > x) { + closestVisualPosition = getPreviousNonInlayVisualPosition(editor, closestVisualPosition); + xActualLeft = editor.visualPositionToXY(closestVisualPosition).x; + } + + if (xActualLeft >= leftBound) { + final int xActualRight = editor.visualPositionToXY(new VisualPosition(closestVisualPosition.line, closestVisualPosition.column + 1)).x - 1; + if (xActualRight <= rightBound) { + return closestVisualPosition.column; + } + + return getPreviousNonInlayVisualPosition(editor, closestVisualPosition).column; + } + else { + return getNextNonInlayVisualPosition(editor, closestVisualPosition).column; + } + } + + private static VisualPosition getNextNonInlayVisualPosition(@NotNull Editor editor, VisualPosition position) { + final InlayModel inlayModel = editor.getInlayModel(); + final int lineLength = EditorHelper.getVisualLineLength(editor, position.line); + position = new VisualPosition(position.line, position.column + 1); + while (position.column < lineLength && inlayModel.hasInlineElementAt(position)) { + position = new VisualPosition(position.line, position.column + 1); + } + return position; + } + + private static VisualPosition getPreviousNonInlayVisualPosition(@NotNull Editor editor, VisualPosition position) { + if (position.column == 0) { + return position; + } + final InlayModel inlayModel = editor.getInlayModel(); + position = new VisualPosition(position.line, position.column - 1); + while (position.column >= 0 && inlayModel.hasInlineElementAt(position)) { + position = new VisualPosition(position.line, position.column - 1); } - return inlayHeight; + return position; } /** diff --git a/src/com/maddyhome/idea/vim/helper/InlayHelper.kt b/src/com/maddyhome/idea/vim/helper/InlayHelper.kt index 21e37b83fd..bc56a30f5b 100644 --- a/src/com/maddyhome/idea/vim/helper/InlayHelper.kt +++ b/src/com/maddyhome/idea/vim/helper/InlayHelper.kt @@ -39,9 +39,15 @@ import com.intellij.openapi.editor.* * appropriately */ fun Caret.moveToInlayAwareOffset(offset: Int) { - val newVisualPosition = inlayAwareOffsetToVisualPosition(editor, offset) - if (newVisualPosition != visualPosition) { - this.moveToVisualPosition(inlayAwareOffsetToVisualPosition(editor, offset)) + // If the target offset is collapsed inside a fold, move directly to the offset, expanding the fold + if (editor.foldingModel.isOffsetCollapsed(offset)) { + moveToOffset(offset) + } + else { + val newVisualPosition = inlayAwareOffsetToVisualPosition(editor, offset) + if (newVisualPosition != visualPosition) { + moveToVisualPosition(newVisualPosition) + } } } diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index 1a14d4bf80..30188c0f0d 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -134,22 +134,23 @@ abstract class VimTestCase : UsefulTestCase() { protected val screenHeight: Int get() = 35 - protected fun setEditorVisibleSize() { - EditorTestUtil.setEditorVisibleSize(myFixture.editor, screenWidth, screenHeight) + protected fun setEditorVisibleSize(width: Int, height: Int) { + EditorTestUtil.setEditorVisibleSize(myFixture.editor, width, height) } + protected fun configureByText(content: String) = configureByText(PlainTextFileType.INSTANCE, content) protected fun configureByJavaText(content: String) = configureByText(JavaFileType.INSTANCE, content) protected fun configureByXmlText(content: String) = configureByText(XmlFileType.INSTANCE, content) private fun configureByText(fileType: FileType, content: String): Editor { myFixture.configureByText(fileType, content) - setEditorVisibleSize() + setEditorVisibleSize(screenWidth, screenHeight) return myFixture.editor } protected fun configureByFileName(fileName: String): Editor { myFixture.configureByText(fileName, "\n") - setEditorVisibleSize() + setEditorVisibleSize(screenWidth, screenHeight) return myFixture.editor } @@ -170,6 +171,15 @@ abstract class VimTestCase : UsefulTestCase() { configureByText(stringBuilder.toString()) } + protected fun configureByColumns(columnCount: Int) { + val content = buildString { + repeat(columnCount) { + append('0' + (it % 10)) + } + } + configureByText(content) + } + @JvmOverloads protected fun setPositionAndScroll(scrollToLogicalLine: Int, caretLogicalLine: Int, caretLogicalColumn: Int = 0) { val scrolloff = min(OptionsManager.scrolloff.value(), screenHeight / 2) @@ -255,6 +265,17 @@ abstract class VimTestCase : UsefulTestCase() { Assert.assertEquals("Bottom logical lines don't match", bottomLogicalLine, actualLogicalBottom) } + fun assertVisibleLineBounds(logicalLine: Int, leftLogicalColumn: Int, rightLogicalColumn: Int) { + val visualLine = EditorHelper.logicalLineToVisualLine(myFixture.editor, logicalLine) + val actualLeftVisualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(myFixture.editor, visualLine) + val actualLeftLogicalColumn = myFixture.editor.visualToLogicalPosition(VisualPosition(visualLine, actualLeftVisualColumn)).column + val actualRightVisualColumn = EditorHelper.getVisualColumnAtRightOfScreen(myFixture.editor, visualLine) + val actualRightLogicalColumn = myFixture.editor.visualToLogicalPosition(VisualPosition(visualLine, actualRightVisualColumn)).column + + Assert.assertEquals(leftLogicalColumn, actualLeftLogicalColumn) + Assert.assertEquals(rightLogicalColumn, actualRightLogicalColumn) + } + fun assertMode(expectedMode: CommandState.Mode) { val mode = CommandState.getInstance(myFixture.editor).mode Assert.assertEquals(expectedMode, mode) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt new file mode 100644 index 0000000000..881fe3142d --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt @@ -0,0 +1,113 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.openapi.editor.Inlay +import com.intellij.openapi.editor.ex.util.EditorUtil +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 +import org.junit.Assert + +/* zs Scroll the text horizontally to position the cursor + at the start (left side) of the screen. This only + works when 'wrap' is off. + */ +class ScrollFirstScreenColumnActionTest : VimTestCase() { + fun `test scroll caret column to first screen column`() { + configureByColumns(200) + typeText(parseKeys("100|", "zs")) + assertVisibleLineBounds(0, 99, 178) + } + + fun `test scroll caret column to first screen column with sidescrolloff`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("100|", "zs")) + assertVisibleLineBounds(0, 89, 168) + } + + fun `test scroll at or near start of line`() { + configureByColumns(200) + typeText(parseKeys("5|", "zs")) + assertVisibleLineBounds(0, 4, 83) + } + + fun `test scroll at or near start of line with sidescrolloff does nothing`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("5|", "zs")) + assertVisibleLineBounds(0, 0, 79) + } + + @VimBehaviorDiffers(description = "Vim scrolls caret to first screen column, filling with virtual space") + fun `test scroll end of line to first screen column`() { + configureByColumns(200) + typeText(parseKeys("$", "zs")) + // See also editor.settings.isVirtualSpace and editor.settings.additionalColumnsCount + assertVisibleLineBounds(0, 123, 202) + } + + fun `test first screen column includes previous inline inlay associated with following text`() { + // The inlay is associated with the caret, on the left, so should appear before it when scrolling columns + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(99, false, HintRenderer("test:"))!! + typeText(parseKeys("100|", "zs")) + val visibleArea = myFixture.editor.scrollingModel.visibleArea + val textWidth = visibleArea.width - inlay.widthInPixels + val availableColumns = textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) + + // The first visible text column will be 99, with the inlay positioned to the left of it + assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) + Assert.assertEquals(visibleArea.x, inlay.bounds!!.x) + } + + fun `test first screen column does not include previous inline inlay associated with preceding text`() { + // The inlay is associated with the column before the caret, so should not affect scrolling + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(99, true, HintRenderer(":test"))!! + typeText(parseKeys("100|", "zs")) + assertVisibleLineBounds(0, 99, 178) + } + + fun `test first screen column does not include subsequent inline inlay associated with following text`() { + // The inlay is associated with the column after the caret, so should not affect scrolling + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(100, false, HintRenderer("test:"))!! + typeText(parseKeys("100|", "zs")) + val availableColumns = getAvailableColumns(inlay) + assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) + } + + fun `test first screen column does not include subsequent inline inlay associated with preceding text`() { + // The inlay is associated with the caret column, but appears to the right of the column, so does not affect scrolling + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(100, true, HintRenderer(":test"))!! + typeText(parseKeys("100|", "zs")) + val availableColumns = getAvailableColumns(inlay) + assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) + } + + private fun getAvailableColumns(inlay: Inlay): Int { + val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlay.widthInPixels + return textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt new file mode 100644 index 0000000000..ff754b3b42 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt @@ -0,0 +1,133 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.openapi.editor.Inlay +import com.intellij.openapi.editor.ex.util.EditorUtil +import com.maddyhome.idea.vim.helper.StringHelper +import com.maddyhome.idea.vim.option.OptionsManager +import org.junit.Assert +import org.jetbrains.plugins.ideavim.VimTestCase + +/* ze Scroll the text horizontally to position the cursor + at the end (right side) of the screen. This only + works when 'wrap' is off. + */ +class ScrollLastScreenColumnActionTest : VimTestCase() { + fun `test scroll caret column to last screen column`() { + configureByColumns(200) + typeText(StringHelper.parseKeys("100|", "ze")) + assertVisibleLineBounds(0, 20, 99) + } + + fun `test scroll caret column to last screen column with sidescrolloff`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(StringHelper.parseKeys("100|", "ze")) + assertVisibleLineBounds(0, 30, 109) + } + + fun `test scroll at or near start of line does nothing`() { + configureByColumns(200) + typeText(StringHelper.parseKeys("10|", "ze")) + assertVisibleLineBounds(0, 0, 79) + } + + fun `test scroll end of line to last screen column`() { + configureByColumns(200) + typeText(StringHelper.parseKeys("$", "ze")) + assertVisibleLineBounds(0, 120, 199) + } + + fun `test scroll end of line to last screen column with sidescrolloff`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(StringHelper.parseKeys("$", "ze")) + // See myFixture.editor.settings.additionalColumnsCount + assertVisibleLineBounds(0, 120, 199) + } + + fun `test scroll caret column to last screen column with sidescrolloff containing an inline inlay`() { + // The offset should include space for the inlay + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(101, true, HintRenderer(":test"))!! + typeText(StringHelper.parseKeys("100|", "ze")) + val availableColumns = getAvailableColumns(inlay) + // Rightmost text column will still be the same, even if it's offset by an inlay + // TODO: Should the offset include the visual column taken up by the inlay? + // Note that the values for this test are -1 when compared to other tests. That's because the inlay takes up a + // visual column, and scrolling doesn't distinguish the type of visual column + // We need to decide if folds and/or inlays should be included in offsets, and figure out how to reasonably implement it + assertVisibleLineBounds(0, 108 - availableColumns + 1, 108) + } + + fun `test last screen column does not include previous inline inlay associated with preceding text`() { + // The inlay is associated with the column before the caret, appears on the left of the caret, so does not affect + // the last visible column + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(99, true, HintRenderer(":test"))!! + typeText(StringHelper.parseKeys("100|", "ze")) + val availableColumns = getAvailableColumns(inlay) + assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) + } + + fun `test last screen column does not include previous inline inlay associated with following text`() { + // The inlay is associated with the caret, but appears on the left, so does not affect the last visible column + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(99, false, HintRenderer("test:"))!! + typeText(StringHelper.parseKeys("100|", "ze")) + val availableColumns = getAvailableColumns(inlay) + assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) + } + + fun `test last screen column includes subsequent inline inlay associated with preceding text`() { + // The inlay is inserted after the caret and relates to the caret column. It should still be visible + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(100, true, HintRenderer(":test"))!! + typeText(StringHelper.parseKeys("100|", "ze")) + val visibleArea = myFixture.editor.scrollingModel.visibleArea + val textWidth = visibleArea.width - inlay.widthInPixels + val availableColumns = textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) + + // The last visible text column will be 99, but it will be positioned before the inlay + assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) + + // We have to assert the location of the inlay + Assert.assertTrue("Inlay bounds must be greater than last text location", + inlay.bounds!!.x > (visibleArea.x + textWidth)) + Assert.assertTrue("Inlay bounds must be less than width of screen", + inlay.bounds!!.x + inlay.bounds!!.width <= (visibleArea.x + visibleArea.width + 1)) + } + + fun `test last screen column does not include subsequent inline inlay associated with following text`() { + // The inlay is inserted after the caret, and relates to text after the caret. It should not affect the last visible + // column + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(100, false, HintRenderer("test:"))!! + typeText(StringHelper.parseKeys("100|", "ze")) + assertVisibleLineBounds(0, 20, 99) + } + + private fun getAvailableColumns(inlay: Inlay): Int { + val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlay.widthInPixels + return textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt new file mode 100644 index 0000000000..92353e81b2 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt @@ -0,0 +1,177 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.group.motion + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.openapi.editor.ex.util.EditorUtil +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +@Suppress("ClassName") +class MotionGroup_ScrollCaretIntoViewHorizontally_Test : VimTestCase() { + fun `test moving right scrolls half screen to right by default`() { + configureByColumns(200) + typeText(parseKeys("80|", "l")) // 1 based + assertPosition(0, 80) // 0 based + assertVisibleLineBounds(0, 40, 119) // 0 based + } + + fun `test moving right scrolls half screen to right by default 2`() { + configureByColumns(200) + setEditorVisibleSize(100, screenHeight) + typeText(parseKeys("100|", "l")) + assertVisibleLineBounds(0, 50, 149) + } + + fun `test moving right scrolls half screen if moving too far 1`() { + configureByColumns(400) + typeText(parseKeys("70|", "41l")) // Move more than half screen width, but scroll less + assertVisibleLineBounds(0, 70, 149) + } + + fun `test moving right scrolls half screen if moving too far 2`() { + configureByColumns(400) + typeText(parseKeys("50|", "200l")) // Move and scroll more than half screen width + assertVisibleLineBounds(0, 209, 288) + } + + fun `test moving right with sidescroll 1`() { + OptionsManager.sidescroll.set(1) + configureByColumns(200) + typeText(parseKeys("80|", "l")) + assertVisibleLineBounds(0, 1, 80) + } + + fun `test moving right with sidescroll 2`() { + OptionsManager.sidescroll.set(2) + configureByColumns(200) + typeText(parseKeys("80|", "l")) + assertVisibleLineBounds(0, 2, 81) + } + + fun `test moving right with sidescrolloff`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("70|", "l")) + assertVisibleLineBounds(0, 30, 109) + } + + fun `test moving right with sidescroll and sidescrolloff`() { + OptionsManager.sidescroll.set(1) + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("70|", "l")) + assertVisibleLineBounds(0, 1, 80) + } + + fun `test moving right with large sidescrolloff keeps cursor centred`() { + OptionsManager.sidescrolloff.set(999) + configureByColumns(200) + typeText(parseKeys("50|", "l")) + assertVisibleLineBounds(0, 10, 89) + } + + fun `test moving right with inline inlay`() { + OptionsManager.sidescroll.set(1) + configureByColumns(200) + val inlayRenderer = HintRenderer(":test") + val inlay = myFixture.editor.inlayModel.addInlineElement(110, true, inlayRenderer)!! + typeText(parseKeys("100|", "20l")) + // These columns are hard to calculate, because the visible offset depends on the rendered width of the inlay + // Also, because we're scrolling right (adding columns to the right) we make the right most column line up + val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlayRenderer.calcWidthInPixels(inlay) + val availableColumns = textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) + assertVisibleLineBounds(0, 119 - availableColumns + 1, 119) + } + + fun `test moving left scrolls half screen to left by default`() { + configureByColumns(200) + typeText(parseKeys("80|zs", "h")) + assertPosition(0, 78) + assertVisibleLineBounds(0, 38, 117) + } + + fun `test moving left scrolls half screen to left by default 2`() { + configureByColumns(200) + setEditorVisibleSize(100, screenHeight) + typeText(parseKeys("100|zs", "h")) + assertVisibleLineBounds(0, 48, 147) + } + + fun `test moving left scrolls half screen if moving too far 1`() { + configureByColumns(400) + typeText(parseKeys("170|zs", "41h")) // Move more than half screen width, but scroll less + assertVisibleLineBounds(0, 88, 167) + } + + fun `test moving left scrolls half screen if moving too far 2`() { + configureByColumns(400) + typeText(parseKeys("290|zs", "200h")) // Move more than half screen width, but scroll less + assertVisibleLineBounds(0, 49, 128) + } + + fun `test moving left with sidescroll 1`() { + OptionsManager.sidescroll.set(1) + configureByColumns(200) + typeText(parseKeys("100|zs", "h")) + assertVisibleLineBounds(0, 98, 177) + } + + fun `test moving left with sidescroll 2`() { + OptionsManager.sidescroll.set(2) + configureByColumns(200) + typeText(parseKeys("100|zs", "h")) + assertVisibleLineBounds(0, 97, 176) + } + + fun `test moving left with sidescrolloff`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("120|zs", "h")) + assertVisibleLineBounds(0, 78, 157) + } + + fun `test moving left with sidescroll and sidescrolloff`() { + OptionsManager.sidescroll.set(1) + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("120|zs", "h")) + assertVisibleLineBounds(0, 108, 187) + } + + fun `test moving left with inline inlay`() { + OptionsManager.sidescroll.set(1) + configureByColumns(200) + val inlayRenderer = HintRenderer(":test") + val inlay = myFixture.editor.inlayModel.addInlineElement(110, true, inlayRenderer)!! + typeText(parseKeys("120|zs", "20h")) + // These columns are hard to calculate, because the visible offset depends on the rendered width of the inlay + val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlayRenderer.calcWidthInPixels(inlay) + val availableColumns = textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) + assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) + } + + fun `test moving left with large sidescrolloff keeps cursor centred`() { + OptionsManager.sidescrolloff.set(999) + configureByColumns(200) + typeText(parseKeys("50|", "h")) + assertVisibleLineBounds(0, 8, 87) + } +} \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewVertically_Test.kt similarity index 98% rename from test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt rename to test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewVertically_Test.kt index a88e89b265..c726bbd70c 100644 --- a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoView_Test.kt +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewVertically_Test.kt @@ -24,7 +24,7 @@ import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase @Suppress("ClassName") -class MotionGroup_ScrollCaretIntoView_Test : VimTestCase() { +class MotionGroup_ScrollCaretIntoViewVertically_Test : VimTestCase() { fun `test moving up causes scrolling up`() { configureByPages(5) setPositionAndScroll(19, 24) From df3a5335154d0e89e0f6f14459df5af03ea50150 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 16 Sep 2020 08:50:01 +0100 Subject: [PATCH 20/31] Fix arithmetic for scrolling columns --- .../idea/vim/helper/EditorHelper.java | 12 ++-- .../ScrollLastScreenColumnActionTest.kt | 6 +- .../ideavim/helper/EditorHelperTest.kt | 65 +++++++++++++++++++ 3 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/helper/EditorHelperTest.kt diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index 63ddcb5cf2..62892f32a5 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -231,7 +231,7 @@ public static int getVisualColumnAtLeftOfScreen(final @NotNull Editor editor, in */ public static int getVisualColumnAtRightOfScreen(final @NotNull Editor editor, int visualLine) { final Rectangle area = getVisibleArea(editor); - return getFullVisualColumn(editor, area.x + area.width, editor.visualLineToY(visualLine), area.x, area.x + area.width); + return getFullVisualColumn(editor, area.x + area.width - 1, editor.visualLineToY(visualLine), area.x, area.x + area.width); } /** @@ -707,16 +707,18 @@ public static void scrollColumnToMiddleOfScreen(@NotNull Editor editor, int visu // of columns. It also works with inline inlays and folds. It is slightly inaccurate for proportional fonts, but is // still a good solution. Besides, what kind of monster uses Vim with proportional fonts? final int standardColumnWidth = EditorUtil.getPlainSpaceWidth(editor); - final int something = ((point.x - (screenWidth / 2)) / standardColumnWidth) * standardColumnWidth; - EditorHelper.scrollHorizontally(editor, something); + final int x = point.x - (screenWidth / standardColumnWidth / 2 * standardColumnWidth); + EditorHelper.scrollHorizontally(editor, x); } public static void scrollColumnToRightOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) { var inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn + 1)); int inlayWidth = inlay != null && inlay.isRelatedToPrecedingText() ? inlay.getWidthInPixels() : 0; - final int columnRightX = editor.visualPositionToXY(new VisualPosition(visualLine, visualColumn + 1)).x; + + // Scroll to the start of the next column, minus a screenwidth + final int nextColumnX = editor.visualPositionToXY(new VisualPosition(visualLine, visualColumn + 1)).x; final int screenWidth = EditorHelper.getVisibleArea(editor).width; - EditorHelper.scrollHorizontally(editor, columnRightX + inlayWidth - 1 - screenWidth); + EditorHelper.scrollHorizontally(editor, nextColumnX + inlayWidth - screenWidth); } /** diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt index ff754b3b42..f1ef80123e 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt @@ -111,10 +111,8 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) // We have to assert the location of the inlay - Assert.assertTrue("Inlay bounds must be greater than last text location", - inlay.bounds!!.x > (visibleArea.x + textWidth)) - Assert.assertTrue("Inlay bounds must be less than width of screen", - inlay.bounds!!.x + inlay.bounds!!.width <= (visibleArea.x + visibleArea.width + 1)) + Assert.assertEquals(visibleArea.x + textWidth, inlay.bounds!!.x) + Assert.assertEquals(visibleArea.x + visibleArea.width, inlay.bounds!!.x + inlay.bounds!!.width) } fun `test last screen column does not include subsequent inline inlay associated with following text`() { diff --git a/test/org/jetbrains/plugins/ideavim/helper/EditorHelperTest.kt b/test/org/jetbrains/plugins/ideavim/helper/EditorHelperTest.kt new file mode 100644 index 0000000000..867d3c8453 --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/helper/EditorHelperTest.kt @@ -0,0 +1,65 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.helper + +import com.maddyhome.idea.vim.helper.EditorHelper +import org.jetbrains.plugins.ideavim.VimTestCase +import org.junit.Assert + +class EditorHelperTest : VimTestCase() { + fun `test scroll column to left of screen`() { + configureByColumns(100) + EditorHelper.scrollColumnToLeftOfScreen(myFixture.editor, 0, 2) + val visibleArea = myFixture.editor.scrollingModel.visibleArea + val columnWidth = visibleArea.width / screenWidth + Assert.assertEquals(2 * columnWidth, visibleArea.x) + } + + fun `test scroll column to right of screen`() { + configureByColumns(100) + val column = screenWidth + 2 + EditorHelper.scrollColumnToRightOfScreen(myFixture.editor, 0, column) + val visibleArea = myFixture.editor.scrollingModel.visibleArea + val columnWidth = visibleArea.width / screenWidth + Assert.assertEquals((column - screenWidth + 1) * columnWidth, visibleArea.x) + } + + fun `test scroll column to middle of screen with even number of columns`() { + configureByColumns(200) + // For an 80 column screen, moving a column to the centre should position it in column 41 (1 based) - 40 columns on + // the left, mid point, 39 columns on the right + // Put column 100 into position 41 -> offset is 59 columns + EditorHelper.scrollColumnToMiddleOfScreen(myFixture.editor, 0, 99) + val visibleArea = myFixture.editor.scrollingModel.visibleArea + val columnWidth = visibleArea.width / screenWidth + Assert.assertEquals(59 * columnWidth, visibleArea.x) + } + + fun `test scroll column to middle of screen with odd number of columns`() { + configureByColumns(200) + setEditorVisibleSize(81, 25) + // For an 81 column screen, moving a column to the centre should position it in column 41 (1 based) - 40 columns on + // the left, mid point, 40 columns on the right + // Put column 100 into position 41 -> offset is 59 columns + EditorHelper.scrollColumnToMiddleOfScreen(myFixture.editor, 0, 99) + val visibleArea = myFixture.editor.scrollingModel.visibleArea + val columnWidth = visibleArea.width / screenWidth + Assert.assertEquals(59 * columnWidth, visibleArea.x) + } +} \ No newline at end of file From d693906905e8e0c01c9670e10f2b92443dad9a2c Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 16 Sep 2020 11:31:18 +0100 Subject: [PATCH 21/31] Add tests and fixes for ScrollColumnRightAction --- .../maddyhome/idea/vim/group/MotionGroup.java | 4 +- .../idea/vim/helper/EditorHelper.java | 26 ++- .../jetbrains/plugins/ideavim/VimTestCase.kt | 11 +- .../scroll/ScrollColumnRightActionTest.kt | 149 ++++++++++++++++++ 4 files changed, 181 insertions(+), 9 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index 924448360f..c90780e8ee 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -949,8 +949,8 @@ public boolean scrollColumns(@NotNull Editor editor, int columns) { EditorHelper.scrollColumnToLeftOfScreen(editor, caretVisualPosition.line, visualColumn); } else { - final int visualColumn = EditorHelper.normalizeVisualColumn(editor, caretVisualPosition.line, - EditorHelper.getVisualColumnAtRightOfScreen(editor, caretVisualPosition.line) + columns, false); + // Don't normalise the rightmost column, or we break virtual space + final int visualColumn = EditorHelper.getVisualColumnAtRightOfScreen(editor, caretVisualPosition.line) + columns; EditorHelper.scrollColumnToRightOfScreen(editor, caretVisualPosition.line, visualColumn); } moveCaretToView(editor); diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index 62892f32a5..491251961f 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -712,13 +712,29 @@ public static void scrollColumnToMiddleOfScreen(@NotNull Editor editor, int visu } public static void scrollColumnToRightOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) { - var inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn + 1)); - int inlayWidth = inlay != null && inlay.isRelatedToPrecedingText() ? inlay.getWidthInPixels() : 0; + int targetVisualColumn = visualColumn; - // Scroll to the start of the next column, minus a screenwidth - final int nextColumnX = editor.visualPositionToXY(new VisualPosition(visualLine, visualColumn + 1)).x; + // Requested column might be an inlay (because we do simple arithmetic on visual position, and inlays and folds have + // a visual position). If it is an inlay and is related to preceding text, we want to display it, so use it as the + // target column. If it's an inlay related to following text, we don't want to display it at the right of the + // screen, show the previous column + var inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn)); + if (inlay != null && !inlay.isRelatedToPrecedingText()) { + targetVisualColumn = visualColumn - 1; + } + else { + // If the target column is followed by an inlay which is associated with it, make the inlay the target column so + // it is visible + inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn + 1)); + if (inlay != null && inlay.isRelatedToPrecedingText()) { + targetVisualColumn = visualColumn + 1; + } + } + + // Scroll to the left edge of the target column, minus a screenwidth, and adjusted for inlays + final int targetColumnRightX = editor.visualPositionToXY(new VisualPosition(visualLine, targetVisualColumn + 1)).x; final int screenWidth = EditorHelper.getVisibleArea(editor).width; - EditorHelper.scrollHorizontally(editor, nextColumnX + inlayWidth - screenWidth); + EditorHelper.scrollHorizontally(editor, targetColumnRightX - screenWidth); } /** diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index 30188c0f0d..2741cd70c3 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -272,8 +272,15 @@ abstract class VimTestCase : UsefulTestCase() { val actualRightVisualColumn = EditorHelper.getVisualColumnAtRightOfScreen(myFixture.editor, visualLine) val actualRightLogicalColumn = myFixture.editor.visualToLogicalPosition(VisualPosition(visualLine, actualRightVisualColumn)).column - Assert.assertEquals(leftLogicalColumn, actualLeftLogicalColumn) - Assert.assertEquals(rightLogicalColumn, actualRightLogicalColumn) + val expected = ScreenBounds(leftLogicalColumn, rightLogicalColumn) + val actual = ScreenBounds(actualLeftLogicalColumn, actualRightLogicalColumn) + Assert.assertEquals(expected, actual) + } + + private data class ScreenBounds(val leftLogicalColumn: Int, val rightLogicalColumn: Int) { + override fun toString(): String { + return "[$leftLogicalColumn-$rightLogicalColumn]" + } } fun assertMode(expectedMode: CommandState.Mode) { diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt new file mode 100644 index 0000000000..4ba01d606c --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt @@ -0,0 +1,149 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.intellij.codeInsight.daemon.impl.HintRenderer +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 + +/* +z or *zh* *z* +zh Move the view on the text [count] characters to the + left, thus scroll the text [count] characters to the + right. This only works when 'wrap' is off. + */ +class ScrollColumnRightActionTest : VimTestCase() { + fun `test scrolls column to right`() { + configureByColumns(200) + typeText(parseKeys("100|", "zh")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 58, 137) + } + + fun `test scrolls column to right with zLeft`() { + configureByColumns(200) + typeText(parseKeys("100|", "z")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 58, 137) + } + + @VimBehaviorDiffers(description = "Vim has virtual space at the end of line. IdeaVim will scroll up to length of longest line") + fun `test scroll last column to right moves cursor 1`() { + configureByColumns(200) + typeText(parseKeys("$")) + // Assert we got initial scroll correct + // We'd need virtual space to scroll this. We're over 200 due to editor.settings.additionalColumnsCount + assertVisibleLineBounds(0, 123, 202) + + typeText(parseKeys("zh")) + assertPosition(0, 199) + assertVisibleLineBounds(0, 122, 201) + } + + @VimBehaviorDiffers(description = "Vim has virtual space at the end of line. IdeaVim will scroll up to length of longest line") + fun `test scroll last column to right moves cursor 2`() { + configureByText(buildString { + repeat(300) { append("0") } + appendln() + repeat(200) { append("0") } + }) + typeText(parseKeys("j$")) + // Assert we got initial scroll correct + // Note, this matches Vim - we've scrolled to centre (but only because the line above allows us to scroll without + // virtual space) + assertVisibleLineBounds(1, 159, 238) + + typeText(parseKeys("zh")) + assertPosition(1, 199) + assertVisibleLineBounds(1, 158, 237) + } + + fun `test scrolls count columns to right`() { + configureByColumns(200) + typeText(parseKeys("100|", "10zh")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 49, 128) + } + + fun `test scrolls count columns to right with zLeft`() { + configureByColumns(200) + typeText(parseKeys("100|", "10z")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 49, 128) + } + + fun `test scrolls column to right with sidescrolloff moves cursor`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("100|", "ze", "zh")) + assertPosition(0, 98) + assertVisibleLineBounds(0, 29, 108) + } + + fun `test scroll column to right ignores sidescroll`() { + OptionsManager.sidescroll.set(10) + configureByColumns(200) + typeText(parseKeys("100|")) + // Assert we got initial scroll correct + // sidescroll=10 means we don't get the sidescroll jump of half a screen and the cursor is positioned at the right edge + assertPosition(0, 99) + assertVisibleLineBounds(0, 20, 99) + + typeText(parseKeys("zh")) // Moves cursor, but not by sidescroll jump + assertPosition(0, 98) + assertVisibleLineBounds(0, 19, 98) + } + + fun `test scroll column to right on first page does nothing`() { + configureByColumns(200) + typeText(parseKeys("10|", "zh")) + assertPosition(0, 9) + assertVisibleLineBounds(0, 0, 79) + } + + fun `test scroll column to right correctly scrolls inline inlay associated with preceding text`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(130, true, HintRenderer(":test")) + typeText(parseKeys("100|")) + // Text at end of line is: 89:test0123 + assertVisibleLineBounds(0, 59, 133) // 75 characters wide + typeText(parseKeys("3zh")) // 89:test0 + assertVisibleLineBounds(0, 56, 130) // 75 characters + typeText(parseKeys("zh")) // 89:test + assertVisibleLineBounds(0, 55, 129) // 75 characters + typeText(parseKeys("zh")) // 8 + assertVisibleLineBounds(0, 49, 128) // 80 characters + } + + fun `test scroll column to right correctly scrolls inline inlay associated with following text`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(130, false, HintRenderer("test:")) + typeText(parseKeys("100|")) + // Text at end of line is: 89test:0123 + assertVisibleLineBounds(0, 59, 133) // 75 characters wide + typeText(parseKeys("3zh")) // 89test:0 + assertVisibleLineBounds(0, 56, 130) // 75 characters + typeText(parseKeys("zh")) // 89 + assertVisibleLineBounds(0, 50, 129) // 80 characters + typeText(parseKeys("zh")) // 9 + assertVisibleLineBounds(0, 49, 128) // 80 characters + } +} \ No newline at end of file From 64502fb31b408a4205881eb3f20b6653fe45aeef Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 16 Sep 2020 12:11:48 +0100 Subject: [PATCH 22/31] Reformat comments --- .../ScrollFirstScreenColumnActionTest.kt | 8 ++++--- .../scroll/ScrollFirstScreenLineActionTest.kt | 7 ++++-- ...crollFirstScreenLinePageStartActionTest.kt | 12 +++++----- .../ScrollFirstScreenLineStartActionTest.kt | 10 +++++---- .../scroll/ScrollHalfPageDownActionTest.kt | 22 ++++++++++--------- .../scroll/ScrollHalfPageUpActionTest.kt | 22 ++++++++++--------- .../ScrollLastScreenColumnActionTest.kt | 8 ++++--- .../scroll/ScrollLastScreenLineActionTest.kt | 6 +++-- ...ScrollLastScreenLinePageStartActionTest.kt | 20 +++++++++-------- .../ScrollLastScreenLineStartActionTest.kt | 10 +++++---- .../action/scroll/ScrollLineDownActionTest.kt | 7 +++++- .../action/scroll/ScrollLineUpActionTest.kt | 8 ++++++- .../ScrollMiddleScreenLineActionTest.kt | 8 +++++-- .../ScrollMiddleScreenLineStartActionTest.kt | 10 +++++---- .../action/scroll/ScrollPageDownActionTest.kt | 13 +++++++++-- .../action/scroll/ScrollPageUpActionTest.kt | 13 +++++++++-- 16 files changed, 120 insertions(+), 64 deletions(-) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt index 881fe3142d..bdeeb6ecbc 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt @@ -27,9 +27,11 @@ import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase import org.junit.Assert -/* zs Scroll the text horizontally to position the cursor - at the start (left side) of the screen. This only - works when 'wrap' is off. +/* + *zs* +zs Scroll the text horizontally to position the cursor + at the start (left side) of the screen. This only + works when 'wrap' is off. */ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test scroll caret column to first screen column`() { diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt index 34dcad3ce5..65108e8d22 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineActionTest.kt @@ -23,8 +23,11 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |zt| -/* Like "z", but leave the cursor in the same column. */ +/* + *zt* +zt Like "z", but leave the cursor in the same + column. + */ class ScrollFirstScreenLineActionTest : VimTestCase() { fun `test scroll current line to top of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt index 834aa1a55d..0fca21c145 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLinePageStartActionTest.kt @@ -23,11 +23,13 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |z+| -/* Without [count]: Redraw with the line just below the - window at the top of the window. Put the cursor in - that line, at the first non-blank in the line. - With [count]: just like "z". */ +/* + *z+* +z+ Without [count]: Redraw with the line just below the + window at the top of the window. Put the cursor in + that line, at the first non-blank in the line. + With [count]: just like "z". + */ class ScrollFirstScreenLinePageStartActionTest : VimTestCase() { fun `test scrolls first line on next page to top of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt index d5190b27bd..1d8f4b262d 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenLineStartActionTest.kt @@ -23,10 +23,12 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |z| -/* Redraw, line [count] at top of window (default - cursor line). Put cursor at first non-blank in the - line. */ +/* + *z* +z Redraw, line [count] at top of window (default + cursor line). Put cursor at first non-blank in the + line. + */ class ScrollFirstScreenLineStartActionTest : VimTestCase() { fun `test scroll current line to top of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt index 81d4dcc9a3..c20cadce11 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageDownActionTest.kt @@ -25,16 +25,18 @@ import com.maddyhome.idea.vim.option.OptionsManager import junit.framework.Assert import org.jetbrains.plugins.ideavim.VimTestCase -// || -/* Scroll window Downwards in the buffer. The number of - lines comes from the 'scroll' option (default: half a - screen). If [count] given, first set 'scroll' option - to [count]. The cursor is moved the same number of - lines down in the file (if possible; when lines wrap - and when hitting the end of the file there may be a - difference). When the cursor is on the last line of - the buffer nothing happens and a beep is produced. - See also 'startofline' option. */ +/* + *CTRL-D* +CTRL-D Scroll window Downwards in the buffer. The number of + lines comes from the 'scroll' option (default: half a + screen). If [count] given, first set 'scroll' option + to [count]. The cursor is moved the same number of + lines down in the file (if possible; when lines wrap + and when hitting the end of the file there may be a + difference). When the cursor is on the last line of + the buffer nothing happens and a beep is produced. + See also 'startofline' option. + */ class ScrollHalfPageDownActionTest : VimTestCase() { fun `test scroll half window downwards keeps cursor on same relative line`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt index ad19cf807b..c9cc5bd3a1 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfPageUpActionTest.kt @@ -25,16 +25,18 @@ import com.maddyhome.idea.vim.option.OptionsManager import junit.framework.Assert import org.jetbrains.plugins.ideavim.VimTestCase -// || -/* Scroll window Upwards in the buffer. The number of - lines comes from the 'scroll' option (default: half a - screen). If [count] given, first set the 'scroll' - option to [count]. The cursor is moved the same - number of lines up in the file (if possible; when - lines wrap and when hitting the end of the file there - may be a difference). When the cursor is on the first - line of the buffer nothing happens and a beep is - produced. See also 'startofline' option. */ +/* + *CTRL-U* +CTRL-U Scroll window Upwards in the buffer. The number of + lines comes from the 'scroll' option (default: half a + screen). If [count] given, first set the 'scroll' + option to [count]. The cursor is moved the same + number of lines up in the file (if possible; when + lines wrap and when hitting the end of the file there + may be a difference). When the cursor is on the first + line of the buffer nothing happens and a beep is + produced. See also 'startofline' option. + */ class ScrollHalfPageUpActionTest : VimTestCase() { fun `test scroll half window upwards keeps cursor on same relative line`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt index f1ef80123e..50fdb57b0d 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt @@ -26,9 +26,11 @@ import com.maddyhome.idea.vim.option.OptionsManager import org.junit.Assert import org.jetbrains.plugins.ideavim.VimTestCase -/* ze Scroll the text horizontally to position the cursor - at the end (right side) of the screen. This only - works when 'wrap' is off. +/* + *ze* +ze Scroll the text horizontally to position the cursor + at the end (right side) of the screen. This only + works when 'wrap' is off. */ class ScrollLastScreenColumnActionTest : VimTestCase() { fun `test scroll caret column to last screen column`() { diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt index 0c89ac8998..394a7cb24c 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineActionTest.kt @@ -22,8 +22,10 @@ import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |zb| -/* Like "z-", but leave the cursor in the same column. */ +/* + *zb* +zb Like "z-", but leave the cursor in the same column. + */ class ScrollLastScreenLineActionTest : VimTestCase() { fun `test scroll current line to bottom of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt index 176c6fd28a..92a4e5a04f 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLinePageStartActionTest.kt @@ -22,15 +22,17 @@ import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |z^| -/* Without [count]: Redraw with the line just above the - window at the bottom of the window. Put the cursor in - that line, at the first non-blank in the line. - With [count]: First scroll the text to put the [count] - line at the bottom of the window, then redraw with the - line which is now at the top of the window at the - bottom of the window. Put the cursor in that line, at - the first non-blank in the line. */ +/* + *z^* +z^ Without [count]: Redraw with the line just above the + window at the bottom of the window. Put the cursor in + that line, at the first non-blank in the line. + With [count]: First scroll the text to put the [count] + line at the bottom of the window, then redraw with the + line which is now at the top of the window at the + bottom of the window. Put the cursor in that line, at + the first non-blank in the line. + */ class ScrollLastScreenLinePageStartActionTest : VimTestCase() { fun `test scrolls last line on previous page to bottom of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt index df10ec648d..c49d3c3069 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenLineStartActionTest.kt @@ -22,10 +22,12 @@ import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |z-| -/* Redraw, line [count] at bottom of window (default - cursor line). Put cursor at first non-blank in the - line. */ +/* + *z-* +z- Redraw, line [count] at bottom of window (default + cursor line). Put cursor at first non-blank in the + line. + */ class ScrollLastScreenLineStartActionTest : VimTestCase() { fun `test scroll current line to bottom of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt index 6d60a5af02..1ad857bff2 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineDownActionTest.kt @@ -23,7 +23,12 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// || +/* + *CTRL-E* +CTRL-E Scroll window [count] lines downwards in the buffer. + The text moves upwards on the screen. + Mnemonic: Extra lines. + */ class ScrollLineDownActionTest : VimTestCase() { fun `test scroll single line down`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt index 698d28496c..15a415de0f 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLineUpActionTest.kt @@ -22,7 +22,13 @@ import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// || +/* + *CTRL-Y* +CTRL-Y Scroll window [count] lines upwards in the buffer. + The text moves downwards on the screen. + Note: When using the MS-Windows key bindings CTRL-Y is + remapped to redo. + */ class ScrollLineUpActionTest : VimTestCase() { fun `test scroll single line up`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt index e472412eb6..8eb9e572fc 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineActionTest.kt @@ -23,8 +23,12 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |zz| -/* Like "z.", but leave the cursor in the same column. */ +/* + *zz* +zz Like "z.", but leave the cursor in the same column. + Careful: If caps-lock is on, this command becomes + "ZZ": write buffer and exit! + */ class ScrollMiddleScreenLineActionTest : VimTestCase() { fun `test scrolls current line to middle of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt index 9a37827bea..4f0795457c 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollMiddleScreenLineStartActionTest.kt @@ -23,10 +23,12 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// |z.| -/* Redraw, line [count] at center of window (default - cursor line). Put cursor at first non-blank in the - line. */ +/* + *z.* +z. Redraw, line [count] at center of window (default + cursor line). Put cursor at first non-blank in the + line. + */ class ScrollMiddleScreenLineStartActionTest : VimTestCase() { fun `test scrolls current line to middle of screen`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt index 13bb146384..1a9a653f40 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageDownActionTest.kt @@ -24,8 +24,17 @@ import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase -// ||, ||, |CTRL-F| -// |i_|, |i_| +/* + or ** ** + or ** *CTRL-F* +CTRL-F Scroll window [count] pages Forwards (downwards) in + the buffer. See also 'startofline' option. + When there is only one window the 'window' option + might be used. + + move window one page down *i_* + move window one page down *i_* + */ class ScrollPageDownActionTest : VimTestCase() { fun `test scroll single page down with S-Down`() { configureByPages(5) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt index 875bd93a7b..a324c4f38d 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollPageUpActionTest.kt @@ -24,8 +24,17 @@ import com.maddyhome.idea.vim.option.OptionsManager import junit.framework.Assert import org.jetbrains.plugins.ideavim.VimTestCase -// ||, ||, |CTRL-B| -// |i_|, |i_| +/* + or ** ** + or ** *CTRL-B* +CTRL-B Scroll window [count] pages Backwards (upwards) in the + buffer. See also 'startofline' option. + When there is only one window the 'window' option + might be used. + + move window one page up *i_* + move window one page up *i_* + */ class ScrollPageUpActionTest : VimTestCase() { fun `test scroll single page up with S-Up`() { configureByPages(5) From eabe43061c2e39ba2911826394ee2486879b5924 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 16 Sep 2020 16:59:14 +0100 Subject: [PATCH 23/31] Add tests and fixes for ScrollColumnLeftAction --- .../maddyhome/idea/vim/group/MotionGroup.java | 13 +- .../idea/vim/helper/EditorHelper.java | 24 ++- .../scroll/ScrollColumnLeftActionTest.kt | 140 ++++++++++++++++++ 3 files changed, 170 insertions(+), 7 deletions(-) create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index c90780e8ee..03260f62e2 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -944,8 +944,19 @@ public int moveCaretToLineEnd(@NotNull Editor editor, @NotNull Caret caret) { public boolean scrollColumns(@NotNull Editor editor, int columns) { final VisualPosition caretVisualPosition = editor.getCaretModel().getVisualPosition(); if (columns > 0) { - final int visualColumn = EditorHelper.normalizeVisualColumn(editor, caretVisualPosition.line, + // TODO: Don't add columns to visual position. This includes inlays and folds + int visualColumn = EditorHelper.normalizeVisualColumn(editor, caretVisualPosition.line, EditorHelper.getVisualColumnAtLeftOfScreen(editor, caretVisualPosition.line) + columns, false); + + // If the target column has an inlay preceding it, move passed it. This inlay will have been (incorrectly) + // included in the simple visual position, so it's ok to step over. If we don't do this, scrollColumnToLeftOfScreen + // can get stuck trying to make sure the inlay is visible. + // A better solution is to not use VisualPosition everywhere, especially for arithmetic + final Inlay inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(caretVisualPosition.line, visualColumn - 1)); + if (inlay != null && !inlay.isRelatedToPrecedingText()) { + visualColumn++; + } + EditorHelper.scrollColumnToLeftOfScreen(editor, caretVisualPosition.line, visualColumn); } else { diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index 491251961f..1d0c41bdfc 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -690,13 +690,25 @@ public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, i } public static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) { - int inlayWidth = 0; - if (visualColumn > 0) { - final var inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn - 1)); - inlayWidth += inlay != null && !inlay.isRelatedToPrecedingText() ? inlay.getWidthInPixels() : 0; + int targetVisualColumn = visualColumn; + + // Requested column might be an inlay (because we do simple arithmetic on visual position, and inlays and folds have + // a visual position). If it is an inlay and is related to following text, we want to display it, so use it as the + // target column. If it's an inlay related to preceding text, we don't want to display it at the left of the screen, + // show the next column instead + Inlay inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn)); + if (inlay != null && inlay.isRelatedToPrecedingText()) { + targetVisualColumn = visualColumn + 1; + } + else if (visualColumn > 0) { + inlay = editor.getInlayModel().getInlineElementAt(new VisualPosition(visualLine, visualColumn - 1)); + if (inlay != null && !inlay.isRelatedToPrecedingText()) { + targetVisualColumn = visualColumn - 1; + } } - final int columnLeftX = editor.visualPositionToXY(new VisualPosition(visualLine, visualColumn)).x; - EditorHelper.scrollHorizontally(editor, columnLeftX - inlayWidth); + + final int columnLeftX = editor.visualPositionToXY(new VisualPosition(visualLine, targetVisualColumn)).x; + EditorHelper.scrollHorizontally(editor, columnLeftX); } public static void scrollColumnToMiddleOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) { diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt new file mode 100644 index 0000000000..5ea70c9d2c --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt @@ -0,0 +1,140 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.intellij.codeInsight.daemon.impl.HintRenderer +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 + +/* +z or *zl* *z* +zl Move the view on the text [count] characters to the + right, thus scroll the text [count] characters to the + left. This only works when 'wrap' is off. + */ +class ScrollColumnLeftActionTest : VimTestCase() { + fun `test scrolls column to left`() { + configureByColumns(200) + typeText(parseKeys("100|", "zl")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 60, 139) + } + + fun `test scrolls column to left with zRight`() { + configureByColumns(200) + typeText(parseKeys("100|", "z")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 60, 139) + } + + fun `test scroll first column to left moves cursor`() { + configureByColumns(200) + typeText(parseKeys("100|", "zs", "zl")) + assertPosition(0, 100) + assertVisibleLineBounds(0, 100, 179) + } + + fun `test scrolls count columns to left`() { + configureByColumns(200) + typeText(parseKeys("100|", "10zl")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 69, 148) + } + + fun `test scrolls count columns to left with zRight`() { + configureByColumns(200) + typeText(parseKeys("100|", "10z")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 69, 148) + } + + fun `test scrolls column to left with sidescrolloff moves cursor`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("100|", "zs", "zl")) + assertPosition(0, 100) + assertVisibleLineBounds(0, 90, 169) + } + + fun `test scroll column to left ignores sidescroll`() { + OptionsManager.sidescroll.set(10) + configureByColumns(200) + typeText(parseKeys("100|")) + // Assert we got initial scroll correct + // sidescroll=10 means we don't get the sidescroll jump of half a screen and the cursor is positioned at the right edge + assertPosition(0, 99) + assertVisibleLineBounds(0, 20, 99) + + // Scrolls, but doesn't use sidescroll jump + typeText(parseKeys("zl")) + assertPosition(0, 99) + assertVisibleLineBounds(0, 21, 100) + } + + fun `test scroll column to left on last page enters virtual space`() { + configureByColumns(200) + typeText(parseKeys("200|", "ze", "zl")) + assertPosition(0, 199) + assertVisibleLineBounds(0, 121, 200) + typeText(parseKeys("zl")) + assertPosition(0, 199) + assertVisibleLineBounds(0, 122, 201) + typeText(parseKeys("zl")) + assertPosition(0, 199) + assertVisibleLineBounds(0, 123, 202) + } + + @VimBehaviorDiffers(description = "Vim has virtual space at end of line") + fun `test scroll columns to left on last page does not have full virtual space`() { + configureByColumns(200) + typeText(parseKeys("200|", "ze", "50zl")) + assertPosition(0, 199) + // Vim is 179-258 + // See also editor.settings.additionalColumnCount + assertVisibleLineBounds(0, 123, 202) + } + + fun `test scroll column to left correctly scrolls inline inlay associated with preceding text`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(67, true, HintRenderer(":test")) + typeText(parseKeys("100|")) + // Text at start of line is: 456:test7 + assertVisibleLineBounds(0, 64, 138) + typeText(parseKeys("2zl")) // 6:test7 + assertVisibleLineBounds(0, 66, 140) + typeText(parseKeys("zl")) // 7 + assertVisibleLineBounds(0, 67, 146) + } + + fun `test scroll column to left correctly scrolls inline inlay associated with following text`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(67, false, HintRenderer("test:")) + typeText(parseKeys("100|")) + // Text at start of line is: 456test:78 + assertVisibleLineBounds(0, 64, 138) + typeText(parseKeys("2zl")) // 6test:78 + assertVisibleLineBounds(0, 66, 140) + typeText(parseKeys("zl")) // test:78 + assertVisibleLineBounds(0, 67, 141) + typeText(parseKeys("zl")) // 8 + assertVisibleLineBounds(0, 68, 147) + } +} \ No newline at end of file From fa17af8d3310013dd7979924b8d4c2044d68502f Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 16 Sep 2020 18:58:21 +0100 Subject: [PATCH 24/31] Fix keeping caret on screen with preceding inlay --- .../idea/vim/group/DigraphGroup.java | 2 +- .../maddyhome/idea/vim/group/MotionGroup.java | 53 +++++++++++-------- .../idea/vim/helper/EditorHelper.java | 32 ++++++----- .../idea/vim/option/OptionsManager.kt | 2 +- .../scroll/ScrollColumnRightActionTest.kt | 17 ++++++ 5 files changed, 69 insertions(+), 37 deletions(-) diff --git a/src/com/maddyhome/idea/vim/group/DigraphGroup.java b/src/com/maddyhome/idea/vim/group/DigraphGroup.java index 9a186ac775..2495a0fb99 100644 --- a/src/com/maddyhome/idea/vim/group/DigraphGroup.java +++ b/src/com/maddyhome/idea/vim/group/DigraphGroup.java @@ -81,7 +81,7 @@ public boolean parseCommandLine(@NotNull Editor editor, @NotNull String args) { } private void showDigraphs(@NotNull Editor editor) { - int width = EditorHelper.getScreenWidth(editor); + int width = EditorHelper.getApproximateScreenWidth(editor); if (width < 10) { width = 80; } diff --git a/src/com/maddyhome/idea/vim/group/MotionGroup.java b/src/com/maddyhome/idea/vim/group/MotionGroup.java index 03260f62e2..8c1e56e1e2 100755 --- a/src/com/maddyhome/idea/vim/group/MotionGroup.java +++ b/src/com/maddyhome/idea/vim/group/MotionGroup.java @@ -171,36 +171,40 @@ else if (cmd.getAction() instanceof TextObjectActionHandler) { private static void moveCaretToView(@NotNull Editor editor) { final int scrollOffset = getNormalizedScrollOffset(editor); - int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor); - int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); - int caretVisualLine = editor.getCaretModel().getVisualPosition().line; - int newVisualLine = caretVisualLine; + final int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor); + final int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor); + final int caretVisualLine = editor.getCaretModel().getVisualPosition().line; + final int newVisualLine; if (caretVisualLine < topVisualLine + scrollOffset) { newVisualLine = EditorHelper.normalizeVisualLine(editor, topVisualLine + scrollOffset); } else if (caretVisualLine >= bottomVisualLine - scrollOffset) { newVisualLine = EditorHelper.normalizeVisualLine(editor, bottomVisualLine - scrollOffset); } - - int sideScrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); - int width = EditorHelper.getScreenWidth(editor); - if (sideScrollOffset > width / 2) { - sideScrollOffset = width / 2; + else { + newVisualLine = caretVisualLine; } - int col = editor.getCaretModel().getVisualPosition().column; - int oldColumn = col; + final int sideScrollOffset = getNormalizedSideScrollOffset(editor); + + final int oldColumn = editor.getCaretModel().getVisualPosition().column; + int col = oldColumn; if (col >= EditorHelper.getLineLength(editor) - 1) { col = UserDataManager.getVimLastColumn(editor.getCaretModel().getPrimaryCaret()); } - int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor, newVisualLine); + + final int leftVisualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor, newVisualLine); + final int rightVisualColumn = EditorHelper.getVisualColumnAtRightOfScreen(editor, newVisualLine); + int caretColumn = col; int newColumn = caretColumn; - if (caretColumn < visualColumn + sideScrollOffset) { - newColumn = visualColumn + sideScrollOffset; + + // TODO: Visual column arithmetic will be inaccurate as it include columns for inlays and folds + if (caretColumn < leftVisualColumn + sideScrollOffset) { + newColumn = leftVisualColumn + sideScrollOffset; } - else if (caretColumn >= visualColumn + width - sideScrollOffset) { - newColumn = visualColumn + width - sideScrollOffset - 1; + else if (caretColumn > rightVisualColumn - sideScrollOffset) { + newColumn = rightVisualColumn - sideScrollOffset; } if (newVisualLine == caretVisualLine && newColumn != caretColumn) { @@ -270,10 +274,15 @@ private static int getScrollOption(int rawCount) { } private static int getNormalizedScrollOffset(final @NotNull Editor editor) { - int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); + final int scrollOffset = OptionsManager.INSTANCE.getScrolloff().value(); return EditorHelper.normalizeScrollOffset(editor, scrollOffset); } + private static int getNormalizedSideScrollOffset(final @NotNull Editor editor) { + final int sideScrollOffset = OptionsManager.INSTANCE.getSidescrolloff().value(); + return EditorHelper.normalizeSideScrollOffset(editor, sideScrollOffset); + } + public static void moveCaret(@NotNull Editor editor, @NotNull Caret caret, int offset) { if (offset < 0 || offset > editor.getDocument().getTextLength() || !caret.isValid()) return; @@ -590,7 +599,7 @@ public boolean scrollLineToLastScreenLine(@NotNull Editor editor, int rawCount, public boolean scrollCaretColumnToFirstScreenColumn(@NotNull Editor editor) { final VisualPosition caretVisualPosition = editor.getCaretModel().getVisualPosition(); - final int scrollOffset = Math.min(OptionsManager.INSTANCE.getSidescrolloff().value(), EditorHelper.getScreenWidth(editor) / 2); + final int scrollOffset = getNormalizedSideScrollOffset(editor); // TODO: Should the offset be applied to visual columns? This includes inline inlays and folds final int column = Math.max(0, caretVisualPosition.column - scrollOffset); EditorHelper.scrollColumnToLeftOfScreen(editor, caretVisualPosition.line, column); @@ -599,7 +608,7 @@ public boolean scrollCaretColumnToFirstScreenColumn(@NotNull Editor editor) { public boolean scrollCaretColumnToLastScreenColumn(@NotNull Editor editor) { final VisualPosition caretVisualPosition = editor.getCaretModel().getVisualPosition(); - final int scrollOffset = Math.min(OptionsManager.INSTANCE.getSidescrolloff().value(), EditorHelper.getScreenWidth(editor) / 2); + final int scrollOffset = getNormalizedSideScrollOffset(editor); // TODO: Should the offset be applied to visual columns? This includes inline inlays and folds final int column = EditorHelper.normalizeVisualColumn(editor, caretVisualPosition.line, caretVisualPosition.column + scrollOffset, false); EditorHelper.scrollColumnToRightOfScreen(editor, caretVisualPosition.line, column); @@ -751,8 +760,8 @@ private static void scrollCaretIntoViewHorizontally(@NotNull Editor editor, final int currentVisualRightColumn = EditorHelper.getVisualColumnAtRightOfScreen(editor, position.line); final int caretColumn = position.column; - final int halfWidth = EditorHelper.getScreenWidth(editor) / 2; - final int scrollOffset = Math.min(OptionsManager.INSTANCE.getSidescrolloff().value(), halfWidth); + final int halfWidth = EditorHelper.getApproximateScreenWidth(editor) / 2; + final int scrollOffset = getNormalizedSideScrollOffset(editor); final EnumSet flags = CommandState.getInstance(editor).getExecutingCommandFlags(); final boolean allowSidescroll = !flags.contains(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP); @@ -907,7 +916,7 @@ public int moveCaretToJump(@NotNull Editor editor, int count) { } public int moveCaretToMiddleColumn(@NotNull Editor editor, @NotNull Caret caret) { - final int width = EditorHelper.getScreenWidth(editor) / 2; + final int width = EditorHelper.getApproximateScreenWidth(editor) / 2; final int len = EditorHelper.getLineLength(editor); return moveCaretToColumn(editor, caret, Math.max(0, Math.min(len - 1, width)), false); diff --git a/src/com/maddyhome/idea/vim/helper/EditorHelper.java b/src/com/maddyhome/idea/vim/helper/EditorHelper.java index 1d0c41bdfc..8f5640aee6 100644 --- a/src/com/maddyhome/idea/vim/helper/EditorHelper.java +++ b/src/com/maddyhome/idea/vim/helper/EditorHelper.java @@ -183,8 +183,18 @@ public static int normalizeScrollOffset(final @NotNull Editor editor, int scroll } /** - * Gets the number of lines than can be displayed on the screen at one time. This is rounded down to the - * nearest whole line if there is a partial line visible at the bottom of the screen. + * Best efforts to ensure the side scroll offset doesn't overlap itself and remains a sensible value. Inline inlays + * can cause this to work incorrectly. + * @param editor The editor to use to normalize the side scroll offset + * @param sideScrollOffset The value of the 'sidescroll' option + * @return The side scroll offset value to use + */ + public static int normalizeSideScrollOffset(final @NotNull Editor editor, int sideScrollOffset) { + return Math.min(sideScrollOffset, getApproximateScreenWidth(editor) / 2); + } + + /** + * Gets the number of lines than can be displayed on the screen at one time. * * Note that this value is only approximate and should be avoided whenever possible! * @@ -192,24 +202,20 @@ public static int normalizeScrollOffset(final @NotNull Editor editor, int scroll * @return The number of screen lines */ private static int getApproximateScreenHeight(final @NotNull Editor editor) { - int lh = editor.getLineHeight(); - Rectangle area = getVisibleArea(editor); - int height = area.y + area.height - getVisualLineAtTopOfScreen(editor) * lh; - return height / lh; + return getVisibleArea(editor).height / editor.getLineHeight(); } /** - * Gets the number of characters that are visible on a screen line + * Gets the number of characters that are visible on a screen line, based on screen width and assuming a fixed width + * font. It does not include inlays or folds. + * + * Note that this value is only approximate and should be avoided whenever possible! * * @param editor The editor * @return The number of screen columns */ - public static int getScreenWidth(final @NotNull Editor editor) { - Rectangle rect = getVisibleArea(editor); - Point pt = new Point(rect.width, 0); - VisualPosition vp = editor.xyToVisualPosition(pt); - - return vp.column; + public static int getApproximateScreenWidth(final @NotNull Editor editor) { + return getVisibleArea(editor).width / EditorUtil.getPlainSpaceWidth(editor); } /** diff --git a/src/com/maddyhome/idea/vim/option/OptionsManager.kt b/src/com/maddyhome/idea/vim/option/OptionsManager.kt index f335f2b7f1..48516b6b4c 100644 --- a/src/com/maddyhome/idea/vim/option/OptionsManager.kt +++ b/src/com/maddyhome/idea/vim/option/OptionsManager.kt @@ -314,7 +314,7 @@ object OptionsManager { cols.sortBy { it.name } extra.sortBy { it.name } - var width = EditorHelper.getScreenWidth(editor) + var width = EditorHelper.getApproximateScreenWidth(editor) if (width < 20) { width = 80 } diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt index 4ba01d606c..27648cc417 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt @@ -146,4 +146,21 @@ class ScrollColumnRightActionTest : VimTestCase() { typeText(parseKeys("zh")) // 9 assertVisibleLineBounds(0, 49, 128) // 80 characters } + + fun `test scroll column to right with preceding inline inlay moves cursor at end of screen`() { + configureByColumns(200) + val inlay = myFixture.editor.inlayModel.addInlineElement(90, false, HintRenderer("test:"))!! + typeText(parseKeys("100|", "ze", "zh")) + assertPosition(0, 98) + assertVisibleLineBounds(0, 24, 98) + typeText(parseKeys("zh")) + assertPosition(0, 97) + assertVisibleLineBounds(0, 23, 97) + typeText(parseKeys("zh")) + assertPosition(0, 96) + assertVisibleLineBounds(0, 22, 96) + typeText(parseKeys("zh")) + assertPosition(0, 95) + assertVisibleLineBounds(0, 21, 95) + } } \ No newline at end of file From 007f33be0b1ad10087f7b1a933b7664fbd5f3df9 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Wed, 16 Sep 2020 23:47:39 +0100 Subject: [PATCH 25/31] Add zL scroll half screen width action --- resources/META-INF/includes/VimActions.xml | 1 + .../scroll/MotionScrollHalfWidthLeftAction.kt | 53 ++++++++ src/com/maddyhome/idea/vim/package-info.java | 2 +- .../scroll/ScrollHalfWidthLeftActionTest.kt | 124 ++++++++++++++++++ 4 files changed, 179 insertions(+), 1 deletion(-) create mode 100644 src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthLeftAction.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt diff --git a/resources/META-INF/includes/VimActions.xml b/resources/META-INF/includes/VimActions.xml index ce6b6ea360..5c06c63005 100644 --- a/resources/META-INF/includes/VimActions.xml +++ b/resources/META-INF/includes/VimActions.xml @@ -150,6 +150,7 @@ + diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthLeftAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthLeftAction.kt new file mode 100644 index 0000000000..e6dffc4242 --- /dev/null +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthLeftAction.kt @@ -0,0 +1,53 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.maddyhome.idea.vim.action.motion.scroll + +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.editor.Editor +import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags +import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* + +/* +For the following four commands the cursor follows the screen. If the +character that the cursor is on is moved off the screen, the cursor is moved +to the closest character that is on the screen. The value of 'sidescroll' is +not used. + + *zH* +zH Move the view on the text half a screenwidth to the + left, thus scroll the text half a screenwidth to the + right. This only works when 'wrap' is off. + +[count] is used but undocumented. + */ +class MotionScrollHalfWidthLeftAction : VimActionHandler.SingleExecution() { + override val type: Command.Type = Command.Type.OTHER_READONLY + + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP) + + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { + // Vim's screen width is the full screen width, including columns used for gutters. + return VimPlugin.getMotion().scrollColumns(editor, cmd.count * (EditorHelper.getApproximateScreenWidth(editor) / 2)); + } +} \ No newline at end of file diff --git a/src/com/maddyhome/idea/vim/package-info.java b/src/com/maddyhome/idea/vim/package-info.java index 9072e4b257..5ff599aea7 100644 --- a/src/com/maddyhome/idea/vim/package-info.java +++ b/src/com/maddyhome/idea/vim/package-info.java @@ -459,7 +459,7 @@ * |zE| TO BE IMPLEMENTED * |zF| TO BE IMPLEMENTED * |zG| TO BE IMPLEMENTED - * |zH| TO BE IMPLEMENTED + * |zH| {@link com.maddyhome.idea.vim.action.motion.scroll.MotionScrollHalfWidthLeftAction} * |zL| TO BE IMPLEMENTED * |zM| {@link com.maddyhome.idea.vim.action.fold.VimCollapseAllRegions} * |zN| TO BE IMPLEMENTED diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt new file mode 100644 index 0000000000..8b56aa0ecc --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt @@ -0,0 +1,124 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.intellij.codeInsight.daemon.impl.HintRenderer +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 + +/* +For the following four commands the cursor follows the screen. If the +character that the cursor is on is moved off the screen, the cursor is moved +to the closest character that is on the screen. The value of 'sidescroll' is +not used. + + *zH* +zH Move the view on the text half a screenwidth to the + left, thus scroll the text half a screenwidth to the + right. This only works when 'wrap' is off. + +[count] is used but undocumented. + */ +class ScrollHalfWidthLeftActionTest : VimTestCase() { + fun `test scroll half page width`() { + configureByColumns(200) + typeText(parseKeys("zL")) + assertVisibleLineBounds(0, 40, 119) + } + + fun `test scroll keeps cursor in place if already in scrolled area`() { + configureByColumns(200) + typeText(parseKeys("50|", "zL")) + assertPosition(0, 49) + assertVisibleLineBounds(0, 40, 119) + } + + fun `test scroll moves cursor if moves off screen 1`() { + configureByColumns(200) + typeText(parseKeys("zL")) + assertPosition(0, 40) + assertVisibleLineBounds(0, 40, 119) + } + + fun `test scroll moves cursor if moves off screen 2`() { + configureByColumns(200) + typeText(parseKeys("10|", "zL")) + assertPosition(0, 40) + assertVisibleLineBounds(0, 40, 119) + } + + fun `test scroll count half page widths`() { + configureByColumns(300) + typeText(parseKeys("3zL")) + assertPosition(0, 120) + assertVisibleLineBounds(0, 120, 199) + } + + fun `test scroll half page width with sidescrolloff`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("zL")) + assertPosition(0, 50) + assertVisibleLineBounds(0, 40, 119) + } + + fun `test scroll half page width ignores sidescroll`() { + OptionsManager.sidescroll.set(10) + configureByColumns(200) + typeText(parseKeys("zL")) + assertPosition(0, 40) + assertVisibleLineBounds(0, 40, 119) + } + + @VimBehaviorDiffers(description = "Vim has virtual space at end of line") + fun `test scroll at end of line does not use full virtual space`() { + configureByColumns(200) + typeText(parseKeys("200|", "ze", "zL")) + assertPosition(0, 199) + assertVisibleLineBounds(0, 123, 202) + } + + @VimBehaviorDiffers(description = "Vim has virtual space at end of line") + fun `test scroll near end of line does not use full virtual space`() { + configureByColumns(200) + typeText(parseKeys("190|", "ze", "zL")) + assertPosition(0, 189) + assertVisibleLineBounds(0, 123, 202) + } + + fun `test scroll includes inlay visual column in half page width`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(20, true, HintRenderer(":test")) + typeText(parseKeys("zL")) + // The inlay is included in the count of scrolled visual columns + assertPosition(0, 39) + assertVisibleLineBounds(0, 39, 118) + } + + fun `test scroll with inlay in scrolled area and left of the cursor`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(20, true, HintRenderer(":test")) + typeText(parseKeys("30|", "zL")) + // The inlay is included in the count of scrolled visual columns + assertPosition(0, 39) + assertVisibleLineBounds(0, 39, 118) + } +} \ No newline at end of file From 26dae9b4e05ba8b59fd04c65bd701b4b9056febb Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 17 Sep 2020 14:35:43 +0100 Subject: [PATCH 26/31] Add zH scroll half screen width action --- resources/META-INF/includes/VimActions.xml | 1 + .../MotionScrollHalfWidthRightAction.kt | 53 ++++++++ src/com/maddyhome/idea/vim/package-info.java | 2 +- .../scroll/ScrollHalfWidthRightActionTest.kt | 116 ++++++++++++++++++ 4 files changed, 171 insertions(+), 1 deletion(-) create mode 100644 src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthRightAction.kt create mode 100644 test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt diff --git a/resources/META-INF/includes/VimActions.xml b/resources/META-INF/includes/VimActions.xml index 5c06c63005..b193bcd501 100644 --- a/resources/META-INF/includes/VimActions.xml +++ b/resources/META-INF/includes/VimActions.xml @@ -151,6 +151,7 @@ + diff --git a/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthRightAction.kt b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthRightAction.kt new file mode 100644 index 0000000000..902f14156b --- /dev/null +++ b/src/com/maddyhome/idea/vim/action/motion/scroll/MotionScrollHalfWidthRightAction.kt @@ -0,0 +1,53 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.maddyhome.idea.vim.action.motion.scroll + +import com.intellij.openapi.actionSystem.DataContext +import com.intellij.openapi.editor.Editor +import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.command.Command +import com.maddyhome.idea.vim.command.CommandFlags +import com.maddyhome.idea.vim.handler.VimActionHandler +import com.maddyhome.idea.vim.helper.EditorHelper +import com.maddyhome.idea.vim.helper.enumSetOf +import java.util.* + +/* +For the following four commands the cursor follows the screen. If the +character that the cursor is on is moved off the screen, the cursor is moved +to the closest character that is on the screen. The value of 'sidescroll' is +not used. + + *zH* +zH Move the view on the text half a screenwidth to the + left, thus scroll the text half a screenwidth to the + right. This only works when 'wrap' is off. + +[count] is used but undocumented. + */ +class MotionScrollHalfWidthRightAction : VimActionHandler.SingleExecution() { + override val type: Command.Type = Command.Type.OTHER_READONLY + + override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_IGNORE_SIDE_SCROLL_JUMP) + + override fun execute(editor: Editor, context: DataContext, cmd: Command): Boolean { + // Vim's screen width is the full screen width, including columns used for gutters. + return VimPlugin.getMotion().scrollColumns(editor, -cmd.count * (EditorHelper.getApproximateScreenWidth(editor) / 2)); + } +} \ No newline at end of file diff --git a/src/com/maddyhome/idea/vim/package-info.java b/src/com/maddyhome/idea/vim/package-info.java index 5ff599aea7..77858793db 100644 --- a/src/com/maddyhome/idea/vim/package-info.java +++ b/src/com/maddyhome/idea/vim/package-info.java @@ -460,7 +460,7 @@ * |zF| TO BE IMPLEMENTED * |zG| TO BE IMPLEMENTED * |zH| {@link com.maddyhome.idea.vim.action.motion.scroll.MotionScrollHalfWidthLeftAction} - * |zL| TO BE IMPLEMENTED + * |zL| {@link com.maddyhome.idea.vim.action.motion.scroll.MotionScrollHalfWidthRightAction} * |zM| {@link com.maddyhome.idea.vim.action.fold.VimCollapseAllRegions} * |zN| TO BE IMPLEMENTED * |zO| {@link com.maddyhome.idea.vim.action.fold.VimExpandRegionRecursively} diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt new file mode 100644 index 0000000000..4bc090e4dd --- /dev/null +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt @@ -0,0 +1,116 @@ +/* + * IdeaVim - Vim emulator for IDEs based on the IntelliJ platform + * Copyright (C) 2003-2020 The IdeaVim authors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package org.jetbrains.plugins.ideavim.action.scroll + +import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.maddyhome.idea.vim.helper.StringHelper.parseKeys +import com.maddyhome.idea.vim.option.OptionsManager +import org.jetbrains.plugins.ideavim.VimTestCase + +/* +For the following four commands the cursor follows the screen. If the +character that the cursor is on is moved off the screen, the cursor is moved +to the closest character that is on the screen. The value of 'sidescroll' is +not used. + + *zH* +zH Move the view on the text half a screenwidth to the + left, thus scroll the text half a screenwidth to the + right. This only works when 'wrap' is off. + +[count] is used but undocumented. + */ +class ScrollHalfWidthRightActionTest : VimTestCase() { + fun `test scroll half page width`() { + configureByColumns(200) + typeText(parseKeys("200|", "ze", "zH")) + assertPosition(0, 159) + assertVisibleLineBounds(0, 80, 159) + } + + fun `test scroll keeps cursor in place if already in scrolled area`() { + configureByColumns(200) + typeText(parseKeys("100|", "zs", "zH")) + assertPosition(0, 99) + // Scroll right 40 characters 99 -> 59 + assertVisibleLineBounds(0, 59, 138) + } + + fun `test scroll moves cursor if moves off screen`() { + configureByColumns(200) + typeText(parseKeys("100|", "ze", "zH")) + assertPosition(0, 79) + assertVisibleLineBounds(0, 0, 79) + } + + fun `test scroll count half page widths`() { + configureByColumns(400) + typeText(parseKeys("350|", "ze", "3zH")) + assertPosition(0, 229) + assertVisibleLineBounds(0, 150, 229) + } + + fun `test scroll half page width with sidescrolloff`() { + OptionsManager.sidescrolloff.set(10) + configureByColumns(200) + typeText(parseKeys("150|", "ze", "zH")) + assertPosition(0, 109) + assertVisibleLineBounds(0, 40, 119) + } + + fun `test scroll half page width ignores sidescroll`() { + OptionsManager.sidescroll.set(10) + configureByColumns(200) + typeText(parseKeys("200|", "ze", "zH")) + assertPosition(0, 159) + assertVisibleLineBounds(0, 80, 159) + } + + fun `test scroll at start of line does nothing`() { + configureByColumns(200) + typeText(parseKeys("zH")) + assertPosition(0, 0) + assertVisibleLineBounds(0, 0, 79) + } + + fun `test scroll near start of line does nothing`() { + configureByColumns(200) + typeText(parseKeys("10|", "zH")) + assertPosition(0, 9) + assertVisibleLineBounds(0, 0, 79) + } + + fun `test scroll includes inlay visual column in half page width`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(180, true, HintRenderer(":test")) + typeText(parseKeys("190|", "ze", "zH")) + // The inlay is included in the count of scrolled visual columns + assertPosition(0, 150) + assertVisibleLineBounds(0, 71, 150) + } + + fun `test scroll with inlay and cursor in scrolled area`() { + configureByColumns(200) + myFixture.editor.inlayModel.addInlineElement(180, true, HintRenderer(":test")) + typeText(parseKeys("170|", "ze", "zH")) + // The inlay is after the cursor, and does not affect scrolling + assertPosition(0, 129) + assertVisibleLineBounds(0, 50, 129) + } +} \ No newline at end of file From 78f1c8499ac5535f1bbdecbf85c373bc57eadcda Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 17 Sep 2020 14:55:41 +0100 Subject: [PATCH 27/31] Use test inlay renderer for consistent width --- .../delete/DeleteCharacterLeftActionTest.kt | 6 ++--- .../delete/DeleteCharacterRightActionTest.kt | 6 ++--- .../leftright/MotionArrowLeftActionTest.kt | 14 ++++------- .../leftright/MotionArrowRightActionTest.kt | 14 ++++------- .../scroll/ScrollColumnLeftActionTest.kt | 6 ++--- .../scroll/ScrollColumnRightActionTest.kt | 24 +++++++++---------- .../ScrollFirstScreenColumnActionTest.kt | 12 +++++----- .../scroll/ScrollHalfWidthLeftActionTest.kt | 6 ++--- .../scroll/ScrollHalfWidthRightActionTest.kt | 6 ++--- .../ScrollLastScreenColumnActionTest.kt | 16 ++++++------- ...up_ScrollCaretIntoViewHorizontally_Test.kt | 12 ++++------ 11 files changed, 54 insertions(+), 68 deletions(-) diff --git a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt index fcefe3be05..74e5591af2 100644 --- a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt @@ -18,7 +18,7 @@ package org.jetbrains.plugins.ideavim.action.change.delete -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import org.jetbrains.plugins.ideavim.VimTestCase @@ -78,7 +78,7 @@ class DeleteCharacterLeftActionTest : VimTestCase() { // Hitting 'X' on the character before the inlay should place the cursor after the inlay // Before: "I fo«:test»|u|nd it in a legendary land." // After: "I f«:test»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) typeText(keys) myFixture.checkResult(after) @@ -103,7 +103,7 @@ class DeleteCharacterLeftActionTest : VimTestCase() { // Hitting 'X' on the character before the inlay should place the cursor after the inlay // Before: "I fo«test:»|u|nd it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer("test:")) + EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt index 133ce793c1..506480116d 100644 --- a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt @@ -18,7 +18,7 @@ package org.jetbrains.plugins.ideavim.action.change.delete -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import org.jetbrains.plugins.ideavim.VimTestCase @@ -78,7 +78,7 @@ class DeleteCharacterRightActionTest : VimTestCase() { // Hitting 'x' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«:test»und it in a legendary land." // After: "I f«:test»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) typeText(keys) myFixture.checkResult(after) @@ -105,7 +105,7 @@ class DeleteCharacterRightActionTest : VimTestCase() { // Hitting 'x' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer("test:")) + EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt index e7f33e344e..3e435c8cd6 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt @@ -20,17 +20,11 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.KeyModelOptionData -import org.jetbrains.plugins.ideavim.SkipNeovimReason -import org.jetbrains.plugins.ideavim.TestWithoutNeovim -import org.jetbrains.plugins.ideavim.VimOptionDefaultAll -import org.jetbrains.plugins.ideavim.VimOptionTestCase -import org.jetbrains.plugins.ideavim.VimOptionTestConfiguration -import org.jetbrains.plugins.ideavim.VimTestOption -import org.jetbrains.plugins.ideavim.VimTestOptionType +import org.jetbrains.plugins.ideavim.* class MotionArrowLeftActionTest : VimOptionTestCase(KeyModelOptionData.name) { @VimOptionDefaultAll @@ -46,7 +40,7 @@ class MotionArrowLeftActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) typeText(keys) myFixture.checkResult(after) @@ -69,7 +63,7 @@ class MotionArrowLeftActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I fo«test:»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, false, HintRenderer("test:")) + EditorTestUtil.addInlay(myFixture.editor, 4, false, 40) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt index e6fc13aaad..be99e0529d 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt @@ -20,17 +20,11 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.option.KeyModelOptionData -import org.jetbrains.plugins.ideavim.SkipNeovimReason -import org.jetbrains.plugins.ideavim.TestWithoutNeovim -import org.jetbrains.plugins.ideavim.VimOptionDefaultAll -import org.jetbrains.plugins.ideavim.VimOptionTestCase -import org.jetbrains.plugins.ideavim.VimOptionTestConfiguration -import org.jetbrains.plugins.ideavim.VimTestOption -import org.jetbrains.plugins.ideavim.VimTestOptionType +import org.jetbrains.plugins.ideavim.* class MotionArrowRightActionTest : VimOptionTestCase(KeyModelOptionData.name) { @VimOptionDefaultAll @@ -46,7 +40,7 @@ class MotionArrowRightActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) typeText(keys) myFixture.checkResult(after) @@ -69,7 +63,7 @@ class MotionArrowRightActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I fo«test:»|u|nd it in a legendary land." - myFixture.editor.inlayModel.addInlineElement(4, false, HintRenderer("test:")) + EditorTestUtil.addInlay(myFixture.editor, 4, false, 40) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt index 5ea70c9d2c..b803880747 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt @@ -18,7 +18,7 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -114,7 +114,7 @@ class ScrollColumnLeftActionTest : VimTestCase() { fun `test scroll column to left correctly scrolls inline inlay associated with preceding text`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(67, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 67, true, 40) typeText(parseKeys("100|")) // Text at start of line is: 456:test7 assertVisibleLineBounds(0, 64, 138) @@ -126,7 +126,7 @@ class ScrollColumnLeftActionTest : VimTestCase() { fun `test scroll column to left correctly scrolls inline inlay associated with following text`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(67, false, HintRenderer("test:")) + EditorTestUtil.addInlay(myFixture.editor, 67, false, 40) typeText(parseKeys("100|")) // Text at start of line is: 456test:78 assertVisibleLineBounds(0, 64, 138) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt index 27648cc417..2ca57889a0 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt @@ -18,7 +18,7 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -121,35 +121,35 @@ class ScrollColumnRightActionTest : VimTestCase() { fun `test scroll column to right correctly scrolls inline inlay associated with preceding text`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(130, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 130, true, 40) typeText(parseKeys("100|")) - // Text at end of line is: 89:test0123 + // Text at end of line is: 89:inlay0123 assertVisibleLineBounds(0, 59, 133) // 75 characters wide - typeText(parseKeys("3zh")) // 89:test0 + typeText(parseKeys("3zh")) // 89:inlay0 assertVisibleLineBounds(0, 56, 130) // 75 characters - typeText(parseKeys("zh")) // 89:test + typeText(parseKeys("zh")) // 89:inlay assertVisibleLineBounds(0, 55, 129) // 75 characters - typeText(parseKeys("zh")) // 8 + typeText(parseKeys("zh")) // 8 assertVisibleLineBounds(0, 49, 128) // 80 characters } fun `test scroll column to right correctly scrolls inline inlay associated with following text`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(130, false, HintRenderer("test:")) + EditorTestUtil.addInlay(myFixture.editor, 130, false, 40) typeText(parseKeys("100|")) - // Text at end of line is: 89test:0123 + // Text at end of line is: 89inlay:0123 assertVisibleLineBounds(0, 59, 133) // 75 characters wide - typeText(parseKeys("3zh")) // 89test:0 + typeText(parseKeys("3zh")) // 89inlay:0 assertVisibleLineBounds(0, 56, 130) // 75 characters - typeText(parseKeys("zh")) // 89 + typeText(parseKeys("zh")) // 89 assertVisibleLineBounds(0, 50, 129) // 80 characters - typeText(parseKeys("zh")) // 9 + typeText(parseKeys("zh")) // 9 assertVisibleLineBounds(0, 49, 128) // 80 characters } fun `test scroll column to right with preceding inline inlay moves cursor at end of screen`() { configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(90, false, HintRenderer("test:"))!! + EditorTestUtil.addInlay(myFixture.editor, 90, false, 40) typeText(parseKeys("100|", "ze", "zh")) assertPosition(0, 98) assertVisibleLineBounds(0, 24, 98) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt index bdeeb6ecbc..0f56a8d13f 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt @@ -18,9 +18,9 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.codeInsight.daemon.impl.HintRenderer import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.ex.util.EditorUtil +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -71,7 +71,7 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column includes previous inline inlay associated with following text`() { // The inlay is associated with the caret, on the left, so should appear before it when scrolling columns configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(99, false, HintRenderer("test:"))!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 99, false, 40) typeText(parseKeys("100|", "zs")) val visibleArea = myFixture.editor.scrollingModel.visibleArea val textWidth = visibleArea.width - inlay.widthInPixels @@ -85,7 +85,7 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column does not include previous inline inlay associated with preceding text`() { // The inlay is associated with the column before the caret, so should not affect scrolling configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(99, true, HintRenderer(":test"))!! + EditorTestUtil.addInlay(myFixture.editor, 99, true, 40) typeText(parseKeys("100|", "zs")) assertVisibleLineBounds(0, 99, 178) } @@ -93,7 +93,7 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column does not include subsequent inline inlay associated with following text`() { // The inlay is associated with the column after the caret, so should not affect scrolling configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(100, false, HintRenderer("test:"))!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 100, false, 40) typeText(parseKeys("100|", "zs")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) @@ -102,13 +102,13 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column does not include subsequent inline inlay associated with preceding text`() { // The inlay is associated with the caret column, but appears to the right of the column, so does not affect scrolling configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(100, true, HintRenderer(":test"))!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 100, true, 40) typeText(parseKeys("100|", "zs")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) } - private fun getAvailableColumns(inlay: Inlay): Int { + private fun getAvailableColumns(inlay: Inlay<*>): Int { val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlay.widthInPixels return textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) } diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt index 8b56aa0ecc..9d315ef6db 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt @@ -18,7 +18,7 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -106,7 +106,7 @@ class ScrollHalfWidthLeftActionTest : VimTestCase() { fun `test scroll includes inlay visual column in half page width`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(20, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 20, true, 40) typeText(parseKeys("zL")) // The inlay is included in the count of scrolled visual columns assertPosition(0, 39) @@ -115,7 +115,7 @@ class ScrollHalfWidthLeftActionTest : VimTestCase() { fun `test scroll with inlay in scrolled area and left of the cursor`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(20, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 20, true, 40) typeText(parseKeys("30|", "zL")) // The inlay is included in the count of scrolled visual columns assertPosition(0, 39) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt index 4bc090e4dd..b52696b365 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt @@ -18,7 +18,7 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.codeInsight.daemon.impl.HintRenderer +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase @@ -98,7 +98,7 @@ class ScrollHalfWidthRightActionTest : VimTestCase() { fun `test scroll includes inlay visual column in half page width`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(180, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 180, true, 40) typeText(parseKeys("190|", "ze", "zH")) // The inlay is included in the count of scrolled visual columns assertPosition(0, 150) @@ -107,7 +107,7 @@ class ScrollHalfWidthRightActionTest : VimTestCase() { fun `test scroll with inlay and cursor in scrolled area`() { configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(180, true, HintRenderer(":test")) + EditorTestUtil.addInlay(myFixture.editor, 180, true, 40) typeText(parseKeys("170|", "ze", "zH")) // The inlay is after the cursor, and does not affect scrolling assertPosition(0, 129) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt index 50fdb57b0d..1c84f589f2 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt @@ -18,13 +18,13 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.codeInsight.daemon.impl.HintRenderer import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.ex.util.EditorUtil +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.option.OptionsManager -import org.junit.Assert import org.jetbrains.plugins.ideavim.VimTestCase +import org.junit.Assert /* *ze* @@ -70,7 +70,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { // The offset should include space for the inlay OptionsManager.sidescrolloff.set(10) configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(101, true, HintRenderer(":test"))!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 101, true, 40) typeText(StringHelper.parseKeys("100|", "ze")) val availableColumns = getAvailableColumns(inlay) // Rightmost text column will still be the same, even if it's offset by an inlay @@ -85,7 +85,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { // The inlay is associated with the column before the caret, appears on the left of the caret, so does not affect // the last visible column configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(99, true, HintRenderer(":test"))!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 99, true, 40) typeText(StringHelper.parseKeys("100|", "ze")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) @@ -94,7 +94,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { fun `test last screen column does not include previous inline inlay associated with following text`() { // The inlay is associated with the caret, but appears on the left, so does not affect the last visible column configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(99, false, HintRenderer("test:"))!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 99, false, 40) typeText(StringHelper.parseKeys("100|", "ze")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) @@ -103,7 +103,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { fun `test last screen column includes subsequent inline inlay associated with preceding text`() { // The inlay is inserted after the caret and relates to the caret column. It should still be visible configureByColumns(200) - val inlay = myFixture.editor.inlayModel.addInlineElement(100, true, HintRenderer(":test"))!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 100, true, 40) typeText(StringHelper.parseKeys("100|", "ze")) val visibleArea = myFixture.editor.scrollingModel.visibleArea val textWidth = visibleArea.width - inlay.widthInPixels @@ -121,12 +121,12 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { // The inlay is inserted after the caret, and relates to text after the caret. It should not affect the last visible // column configureByColumns(200) - myFixture.editor.inlayModel.addInlineElement(100, false, HintRenderer("test:"))!! + EditorTestUtil.addInlay(myFixture.editor, 100, false, 40) typeText(StringHelper.parseKeys("100|", "ze")) assertVisibleLineBounds(0, 20, 99) } - private fun getAvailableColumns(inlay: Inlay): Int { + private fun getAvailableColumns(inlay: Inlay<*>): Int { val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlay.widthInPixels return textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) } diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt index 92353e81b2..8d2a751059 100644 --- a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt @@ -18,8 +18,8 @@ package org.jetbrains.plugins.ideavim.group.motion -import com.intellij.codeInsight.daemon.impl.HintRenderer import com.intellij.openapi.editor.ex.util.EditorUtil +import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase @@ -91,12 +91,11 @@ class MotionGroup_ScrollCaretIntoViewHorizontally_Test : VimTestCase() { fun `test moving right with inline inlay`() { OptionsManager.sidescroll.set(1) configureByColumns(200) - val inlayRenderer = HintRenderer(":test") - val inlay = myFixture.editor.inlayModel.addInlineElement(110, true, inlayRenderer)!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 110, true, 40) typeText(parseKeys("100|", "20l")) // These columns are hard to calculate, because the visible offset depends on the rendered width of the inlay // Also, because we're scrolling right (adding columns to the right) we make the right most column line up - val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlayRenderer.calcWidthInPixels(inlay) + val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlay.widthInPixels val availableColumns = textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) assertVisibleLineBounds(0, 119 - availableColumns + 1, 119) } @@ -159,11 +158,10 @@ class MotionGroup_ScrollCaretIntoViewHorizontally_Test : VimTestCase() { fun `test moving left with inline inlay`() { OptionsManager.sidescroll.set(1) configureByColumns(200) - val inlayRenderer = HintRenderer(":test") - val inlay = myFixture.editor.inlayModel.addInlineElement(110, true, inlayRenderer)!! + val inlay = EditorTestUtil.addInlay(myFixture.editor, 110, true, 40) typeText(parseKeys("120|zs", "20h")) // These columns are hard to calculate, because the visible offset depends on the rendered width of the inlay - val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlayRenderer.calcWidthInPixels(inlay) + val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlay.widthInPixels val availableColumns = textWidth / EditorUtil.getPlainSpaceWidth(myFixture.editor) assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) } From 2dc54ea882941fa1014f6e043afa2eac9b88a796 Mon Sep 17 00:00:00 2001 From: Matt Ellis Date: Thu, 17 Sep 2020 16:15:27 +0100 Subject: [PATCH 28/31] Use deterministic width for inlays --- test/org/jetbrains/plugins/ideavim/VimTestCase.kt | 14 ++++++++++---- .../change/delete/DeleteCharacterLeftActionTest.kt | 5 ++--- .../delete/DeleteCharacterRightActionTest.kt | 5 ++--- .../motion/leftright/MotionArrowLeftActionTest.kt | 5 ++--- .../motion/leftright/MotionArrowRightActionTest.kt | 5 ++--- .../action/scroll/ScrollColumnLeftActionTest.kt | 5 ++--- .../action/scroll/ScrollColumnRightActionTest.kt | 7 +++---- .../scroll/ScrollFirstScreenColumnActionTest.kt | 9 ++++----- .../action/scroll/ScrollHalfWidthLeftActionTest.kt | 5 ++--- .../scroll/ScrollHalfWidthRightActionTest.kt | 5 ++--- .../scroll/ScrollLastScreenColumnActionTest.kt | 11 +++++------ ...onGroup_ScrollCaretIntoViewHorizontally_Test.kt | 5 ++--- 12 files changed, 38 insertions(+), 43 deletions(-) diff --git a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt index 2741cd70c3..7cc8d27be8 100644 --- a/test/org/jetbrains/plugins/ideavim/VimTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimTestCase.kt @@ -23,11 +23,9 @@ import com.intellij.ide.highlighter.JavaFileType import com.intellij.ide.highlighter.XmlFileType import com.intellij.openapi.application.PathManager import com.intellij.openapi.application.WriteAction -import com.intellij.openapi.editor.Caret -import com.intellij.openapi.editor.Editor -import com.intellij.openapi.editor.LogicalPosition -import com.intellij.openapi.editor.VisualPosition +import com.intellij.openapi.editor.* import com.intellij.openapi.editor.colors.EditorColors +import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileTypes.FileType import com.intellij.openapi.fileTypes.PlainTextFileType @@ -388,6 +386,14 @@ abstract class VimTestCase : UsefulTestCase() { protected val fileManager: FileEditorManagerEx get() = FileEditorManagerEx.getInstanceEx(myFixture.project) + protected fun addInlay(offset: Int, relatesToPrecedingText: Boolean, widthInColumns: Int): Inlay<*> { + // Enforce deterministic tests for inlays. Default text char width is different per platform (e.g. Windows is 7 and + // Mac is 8) and using the same inlay width on all platforms can cause columns to be on or off screen unexpectedly. + // If inlay width is related to character width, we will scale correctly across different platforms + val columnWidth = EditorUtil.getPlainSpaceWidth(myFixture.editor) + return EditorTestUtil.addInlay(myFixture.editor, offset, relatesToPrecedingText, widthInColumns * columnWidth)!! + } + companion object { const val c = EditorTestUtil.CARET_TAG const val s = EditorTestUtil.SELECTION_START_TAG diff --git a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt index 74e5591af2..406f523b1b 100644 --- a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterLeftActionTest.kt @@ -18,7 +18,6 @@ package org.jetbrains.plugins.ideavim.action.change.delete -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import org.jetbrains.plugins.ideavim.VimTestCase @@ -78,7 +77,7 @@ class DeleteCharacterLeftActionTest : VimTestCase() { // Hitting 'X' on the character before the inlay should place the cursor after the inlay // Before: "I fo«:test»|u|nd it in a legendary land." // After: "I f«:test»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) + addInlay(4, true, 5) typeText(keys) myFixture.checkResult(after) @@ -103,7 +102,7 @@ class DeleteCharacterLeftActionTest : VimTestCase() { // Hitting 'X' on the character before the inlay should place the cursor after the inlay // Before: "I fo«test:»|u|nd it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) + addInlay(4, true, 5) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt index 506480116d..934b14cb5b 100644 --- a/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/change/delete/DeleteCharacterRightActionTest.kt @@ -18,7 +18,6 @@ package org.jetbrains.plugins.ideavim.action.change.delete -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import org.jetbrains.plugins.ideavim.VimTestCase @@ -78,7 +77,7 @@ class DeleteCharacterRightActionTest : VimTestCase() { // Hitting 'x' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«:test»und it in a legendary land." // After: "I f«:test»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) + addInlay(4, true, 5) typeText(keys) myFixture.checkResult(after) @@ -105,7 +104,7 @@ class DeleteCharacterRightActionTest : VimTestCase() { // Hitting 'x' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) + addInlay(4, true, 5) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt index 3e435c8cd6..41c2cd9a80 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowLeftActionTest.kt @@ -20,7 +20,6 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.KeyModelOptionData @@ -40,7 +39,7 @@ class MotionArrowLeftActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) + addInlay(4, true, 5) typeText(keys) myFixture.checkResult(after) @@ -63,7 +62,7 @@ class MotionArrowLeftActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I fo«test:»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, false, 40) + addInlay(4, false, 5) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt index be99e0529d..7d12fcc75b 100644 --- a/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/motion/leftright/MotionArrowRightActionTest.kt @@ -20,7 +20,6 @@ package org.jetbrains.plugins.ideavim.action.motion.leftright -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.option.KeyModelOptionData @@ -40,7 +39,7 @@ class MotionArrowRightActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I f«test:»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, true, 40) + addInlay(4, true, 5) typeText(keys) myFixture.checkResult(after) @@ -63,7 +62,7 @@ class MotionArrowRightActionTest : VimOptionTestCase(KeyModelOptionData.name) { // Hitting 'l' on the character before the inlay should place the cursor after the inlay // Before: "I f|o|«test:»und it in a legendary land." // After: "I fo«test:»|u|nd it in a legendary land." - EditorTestUtil.addInlay(myFixture.editor, 4, false, 40) + addInlay(4, false, 5) typeText(keys) myFixture.checkResult(after) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt index b803880747..e7c8f3e670 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnLeftActionTest.kt @@ -18,7 +18,6 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -114,7 +113,7 @@ class ScrollColumnLeftActionTest : VimTestCase() { fun `test scroll column to left correctly scrolls inline inlay associated with preceding text`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 67, true, 40) + addInlay(67, true, 5) typeText(parseKeys("100|")) // Text at start of line is: 456:test7 assertVisibleLineBounds(0, 64, 138) @@ -126,7 +125,7 @@ class ScrollColumnLeftActionTest : VimTestCase() { fun `test scroll column to left correctly scrolls inline inlay associated with following text`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 67, false, 40) + addInlay(67, false, 5) typeText(parseKeys("100|")) // Text at start of line is: 456test:78 assertVisibleLineBounds(0, 64, 138) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt index 2ca57889a0..2114420e76 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollColumnRightActionTest.kt @@ -18,7 +18,6 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -121,7 +120,7 @@ class ScrollColumnRightActionTest : VimTestCase() { fun `test scroll column to right correctly scrolls inline inlay associated with preceding text`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 130, true, 40) + addInlay(130, true, 5) typeText(parseKeys("100|")) // Text at end of line is: 89:inlay0123 assertVisibleLineBounds(0, 59, 133) // 75 characters wide @@ -135,7 +134,7 @@ class ScrollColumnRightActionTest : VimTestCase() { fun `test scroll column to right correctly scrolls inline inlay associated with following text`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 130, false, 40) + addInlay(130, false, 5) typeText(parseKeys("100|")) // Text at end of line is: 89inlay:0123 assertVisibleLineBounds(0, 59, 133) // 75 characters wide @@ -149,7 +148,7 @@ class ScrollColumnRightActionTest : VimTestCase() { fun `test scroll column to right with preceding inline inlay moves cursor at end of screen`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 90, false, 40) + addInlay(90, false, 5) typeText(parseKeys("100|", "ze", "zh")) assertPosition(0, 98) assertVisibleLineBounds(0, 24, 98) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt index 0f56a8d13f..772fa5bfe0 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollFirstScreenColumnActionTest.kt @@ -20,7 +20,6 @@ package org.jetbrains.plugins.ideavim.action.scroll import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.ex.util.EditorUtil -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -71,7 +70,7 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column includes previous inline inlay associated with following text`() { // The inlay is associated with the caret, on the left, so should appear before it when scrolling columns configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 99, false, 40) + val inlay = addInlay(99, false, 5) typeText(parseKeys("100|", "zs")) val visibleArea = myFixture.editor.scrollingModel.visibleArea val textWidth = visibleArea.width - inlay.widthInPixels @@ -85,7 +84,7 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column does not include previous inline inlay associated with preceding text`() { // The inlay is associated with the column before the caret, so should not affect scrolling configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 99, true, 40) + addInlay(99, true, 5) typeText(parseKeys("100|", "zs")) assertVisibleLineBounds(0, 99, 178) } @@ -93,7 +92,7 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column does not include subsequent inline inlay associated with following text`() { // The inlay is associated with the column after the caret, so should not affect scrolling configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 100, false, 40) + val inlay = addInlay(100, false, 5) typeText(parseKeys("100|", "zs")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) @@ -102,7 +101,7 @@ class ScrollFirstScreenColumnActionTest : VimTestCase() { fun `test first screen column does not include subsequent inline inlay associated with preceding text`() { // The inlay is associated with the caret column, but appears to the right of the column, so does not affect scrolling configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 100, true, 40) + val inlay = addInlay(100, true, 5) typeText(parseKeys("100|", "zs")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99, 99 + availableColumns - 1) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt index 9d315ef6db..2339373e8f 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthLeftActionTest.kt @@ -18,7 +18,6 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.VimBehaviorDiffers import com.maddyhome.idea.vim.option.OptionsManager @@ -106,7 +105,7 @@ class ScrollHalfWidthLeftActionTest : VimTestCase() { fun `test scroll includes inlay visual column in half page width`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 20, true, 40) + addInlay(20, true, 5) typeText(parseKeys("zL")) // The inlay is included in the count of scrolled visual columns assertPosition(0, 39) @@ -115,7 +114,7 @@ class ScrollHalfWidthLeftActionTest : VimTestCase() { fun `test scroll with inlay in scrolled area and left of the cursor`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 20, true, 40) + addInlay(20, true, 5) typeText(parseKeys("30|", "zL")) // The inlay is included in the count of scrolled visual columns assertPosition(0, 39) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt index b52696b365..2e3eb23282 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollHalfWidthRightActionTest.kt @@ -18,7 +18,6 @@ package org.jetbrains.plugins.ideavim.action.scroll -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase @@ -98,7 +97,7 @@ class ScrollHalfWidthRightActionTest : VimTestCase() { fun `test scroll includes inlay visual column in half page width`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 180, true, 40) + addInlay(180, true, 5) typeText(parseKeys("190|", "ze", "zH")) // The inlay is included in the count of scrolled visual columns assertPosition(0, 150) @@ -107,7 +106,7 @@ class ScrollHalfWidthRightActionTest : VimTestCase() { fun `test scroll with inlay and cursor in scrolled area`() { configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 180, true, 40) + addInlay(180, true, 5) typeText(parseKeys("170|", "ze", "zH")) // The inlay is after the cursor, and does not affect scrolling assertPosition(0, 129) diff --git a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt index 1c84f589f2..fc48c5ec3c 100644 --- a/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt +++ b/test/org/jetbrains/plugins/ideavim/action/scroll/ScrollLastScreenColumnActionTest.kt @@ -20,7 +20,6 @@ package org.jetbrains.plugins.ideavim.action.scroll import com.intellij.openapi.editor.Inlay import com.intellij.openapi.editor.ex.util.EditorUtil -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase @@ -70,7 +69,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { // The offset should include space for the inlay OptionsManager.sidescrolloff.set(10) configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 101, true, 40) + val inlay = addInlay(101, true, 5) typeText(StringHelper.parseKeys("100|", "ze")) val availableColumns = getAvailableColumns(inlay) // Rightmost text column will still be the same, even if it's offset by an inlay @@ -85,7 +84,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { // The inlay is associated with the column before the caret, appears on the left of the caret, so does not affect // the last visible column configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 99, true, 40) + val inlay = addInlay(99, true, 5) typeText(StringHelper.parseKeys("100|", "ze")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) @@ -94,7 +93,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { fun `test last screen column does not include previous inline inlay associated with following text`() { // The inlay is associated with the caret, but appears on the left, so does not affect the last visible column configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 99, false, 40) + val inlay = addInlay(99, false, 5) typeText(StringHelper.parseKeys("100|", "ze")) val availableColumns = getAvailableColumns(inlay) assertVisibleLineBounds(0, 99 - availableColumns + 1, 99) @@ -103,7 +102,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { fun `test last screen column includes subsequent inline inlay associated with preceding text`() { // The inlay is inserted after the caret and relates to the caret column. It should still be visible configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 100, true, 40) + val inlay = addInlay(100, true, 5) typeText(StringHelper.parseKeys("100|", "ze")) val visibleArea = myFixture.editor.scrollingModel.visibleArea val textWidth = visibleArea.width - inlay.widthInPixels @@ -121,7 +120,7 @@ class ScrollLastScreenColumnActionTest : VimTestCase() { // The inlay is inserted after the caret, and relates to text after the caret. It should not affect the last visible // column configureByColumns(200) - EditorTestUtil.addInlay(myFixture.editor, 100, false, 40) + addInlay(100, false, 5) typeText(StringHelper.parseKeys("100|", "ze")) assertVisibleLineBounds(0, 20, 99) } diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt index 8d2a751059..be84857ade 100644 --- a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_ScrollCaretIntoViewHorizontally_Test.kt @@ -19,7 +19,6 @@ package org.jetbrains.plugins.ideavim.group.motion import com.intellij.openapi.editor.ex.util.EditorUtil -import com.intellij.testFramework.EditorTestUtil import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.option.OptionsManager import org.jetbrains.plugins.ideavim.VimTestCase @@ -91,7 +90,7 @@ class MotionGroup_ScrollCaretIntoViewHorizontally_Test : VimTestCase() { fun `test moving right with inline inlay`() { OptionsManager.sidescroll.set(1) configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 110, true, 40) + val inlay = addInlay(110, true, 5) typeText(parseKeys("100|", "20l")) // These columns are hard to calculate, because the visible offset depends on the rendered width of the inlay // Also, because we're scrolling right (adding columns to the right) we make the right most column line up @@ -158,7 +157,7 @@ class MotionGroup_ScrollCaretIntoViewHorizontally_Test : VimTestCase() { fun `test moving left with inline inlay`() { OptionsManager.sidescroll.set(1) configureByColumns(200) - val inlay = EditorTestUtil.addInlay(myFixture.editor, 110, true, 40) + val inlay = addInlay(110, true, 5) typeText(parseKeys("120|zs", "20h")) // These columns are hard to calculate, because the visible offset depends on the rendered width of the inlay val textWidth = myFixture.editor.scrollingModel.visibleArea.width - inlay.widthInPixels From cd0d5034023a80bd52c5c959a8c1f2f018c8f953 Mon Sep 17 00:00:00 2001 From: Alex Plate Date: Tue, 22 Sep 2020 10:41:03 +0300 Subject: [PATCH 29/31] Convert test to option test --- .../idea/vim/option/OptionsManager.kt | 12 +- .../plugins/ideavim/VimOptionTestCase.kt | 9 +- .../MotionGroup_scrolloff_scrolljump_Test.kt | 107 +++++++++++------- 3 files changed, 85 insertions(+), 43 deletions(-) diff --git a/src/com/maddyhome/idea/vim/option/OptionsManager.kt b/src/com/maddyhome/idea/vim/option/OptionsManager.kt index e00b92d111..6989d3bdec 100644 --- a/src/com/maddyhome/idea/vim/option/OptionsManager.kt +++ b/src/com/maddyhome/idea/vim/option/OptionsManager.kt @@ -66,8 +66,8 @@ object OptionsManager { val number = addOption(ToggleOption("number", "nu", false)) val relativenumber = addOption(ToggleOption("relativenumber", "rnu", false)) val scroll = addOption(NumberOption("scroll", "scr", 0)) - val scrolljump = addOption(NumberOption("scrolljump", "sj", 1, -100, Integer.MAX_VALUE)) - val scrolloff = addOption(NumberOption("scrolloff", "so", 0)) + val scrolljump = addOption(NumberOption(ScrollJumpData.name, "sj", 1, -100, Integer.MAX_VALUE)) + val scrolloff = addOption(NumberOption(ScrollOffData.name, "so", 0)) val selection = addOption(BoundStringOption("selection", "sel", "inclusive", arrayOf("old", "inclusive", "exclusive"))) val selectmode = addOption(SelectModeOptionData.option) val showcmd = addOption(ToggleOption("showcmd", "sc", true)) // Vim: Off by default on platforms with possibly slow tty. On by default elsewhere. @@ -566,4 +566,12 @@ object VirtualEditData { const val onemore = "onemore" val allValues = arrayOf("block", "insert", "all", onemore) +} + +object ScrollOffData { + const val name = "scrolloff" +} + +object ScrollJumpData { + const val name = "scrolljump" } \ No newline at end of file diff --git a/test/org/jetbrains/plugins/ideavim/VimOptionTestCase.kt b/test/org/jetbrains/plugins/ideavim/VimOptionTestCase.kt index 09519f74b1..6650d26531 100644 --- a/test/org/jetbrains/plugins/ideavim/VimOptionTestCase.kt +++ b/test/org/jetbrains/plugins/ideavim/VimOptionTestCase.kt @@ -20,6 +20,7 @@ package org.jetbrains.plugins.ideavim import com.maddyhome.idea.vim.option.BoundStringOption import com.maddyhome.idea.vim.option.ListOption +import com.maddyhome.idea.vim.option.NumberOption import com.maddyhome.idea.vim.option.OptionsManager import com.maddyhome.idea.vim.option.ToggleOption @@ -80,6 +81,11 @@ abstract class VimOptionTestCase(option: String, vararg otherOptions: String) : option.set(it.values.first()) } + VimTestOptionType.NUMBER -> { + if (option !is NumberOption) kotlin.test.fail("${it.option} is not a number option. Change it for method `${testMethod.name}`") + + option.set(it.values.first().toInt()) + } } } } @@ -106,5 +112,6 @@ annotation class VimTestOption( enum class VimTestOptionType { LIST, TOGGLE, - VALUE + VALUE, + NUMBER } diff --git a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt index 06a5cca357..3db1124350 100644 --- a/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt +++ b/test/org/jetbrains/plugins/ideavim/group/motion/MotionGroup_scrolloff_scrolljump_Test.kt @@ -21,14 +21,21 @@ package org.jetbrains.plugins.ideavim.group.motion import com.maddyhome.idea.vim.helper.StringHelper.parseKeys -import com.maddyhome.idea.vim.option.OptionsManager -import org.jetbrains.plugins.ideavim.VimTestCase +import com.maddyhome.idea.vim.option.ScrollJumpData +import com.maddyhome.idea.vim.option.ScrollOffData +import org.jetbrains.plugins.ideavim.SkipNeovimReason +import org.jetbrains.plugins.ideavim.TestWithoutNeovim +import org.jetbrains.plugins.ideavim.VimOptionTestCase +import org.jetbrains.plugins.ideavim.VimOptionTestConfiguration +import org.jetbrains.plugins.ideavim.VimTestOption +import org.jetbrains.plugins.ideavim.VimTestOptionType // These tests are sanity tests for scrolloff and scrolljump, with actions that move the cursor. Other actions that are // affected by scrolloff or scrolljump should include that in the action specific tests -class MotionGroup_scrolloff_Test : VimTestCase() { +class MotionGroup_scrolloff_Test : VimOptionTestCase(ScrollOffData.name) { + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["0"])) fun `test move up shows no context with scrolloff=0`() { - OptionsManager.scrolloff.set(0) configureByPages(5) setPositionAndScroll(25, 25) typeText(parseKeys("k")) @@ -36,8 +43,9 @@ class MotionGroup_scrolloff_Test : VimTestCase() { assertVisibleArea(24, 58) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["1"])) fun `test move up shows context line with scrolloff=1`() { - OptionsManager.scrolloff.set(1) configureByPages(5) setPositionAndScroll(25, 26) typeText(parseKeys("k")) @@ -45,8 +53,9 @@ class MotionGroup_scrolloff_Test : VimTestCase() { assertVisibleArea(24, 58) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["10"])) fun `test move up shows context lines with scrolloff=10`() { - OptionsManager.scrolloff.set(10) configureByPages(5) setPositionAndScroll(25, 35) typeText(parseKeys("k")) @@ -54,8 +63,9 @@ class MotionGroup_scrolloff_Test : VimTestCase() { assertVisibleArea(24, 58) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["0"])) fun `test move down shows no context with scrolloff=0`() { - OptionsManager.scrolloff.set(0) configureByPages(5) setPositionAndScroll(25, 59) typeText(parseKeys("j")) @@ -63,8 +73,9 @@ class MotionGroup_scrolloff_Test : VimTestCase() { assertVisibleArea(26, 60) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["1"])) fun `test move down shows context line with scrolloff=1`() { - OptionsManager.scrolloff.set(1) configureByPages(5) setPositionAndScroll(25, 58) typeText(parseKeys("j")) @@ -72,8 +83,9 @@ class MotionGroup_scrolloff_Test : VimTestCase() { assertVisibleArea(26, 60) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["10"])) fun `test move down shows context lines with scrolloff=10`() { - OptionsManager.scrolloff.set(10) configureByPages(5) setPositionAndScroll(25, 49) typeText(parseKeys("j")) @@ -81,37 +93,21 @@ class MotionGroup_scrolloff_Test : VimTestCase() { assertVisibleArea(26, 60) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["999"])) fun `test scrolloff=999 keeps cursor in centre of screen`() { - OptionsManager.scrolloff.set(999) configureByPages(5) setPositionAndScroll(25, 42) typeText(parseKeys("j")) assertPosition(43, 0) assertVisibleArea(26, 60) } - - fun `test negative scrolljump treated as percentage 1`() { - OptionsManager.scrolljump.set(-50) - configureByPages(5) - setPositionAndScroll(39, 39) - typeText(parseKeys("k")) - assertPosition(38, 0) - assertVisibleArea(22, 56) - } - - fun `test negative scrolljump treated as percentage 2`() { - OptionsManager.scrolljump.set(-10) - configureByPages(5) - setPositionAndScroll(39, 39) - typeText(parseKeys("k")) - assertPosition(38, 0) - assertVisibleArea(36, 70) - } } -class MotionGroup_scrolljump_Test : VimTestCase() { +class MotionGroup_scrolljump_Test : VimOptionTestCase(ScrollJumpData.name) { + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["0"])) fun `test move up scrolls single line with scrolljump=0`() { - OptionsManager.scrolljump.set(0) configureByPages(5) setPositionAndScroll(25, 25) typeText(parseKeys("k")) @@ -119,8 +115,9 @@ class MotionGroup_scrolljump_Test : VimTestCase() { assertVisibleArea(24, 58) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["1"])) fun `test move up scrolls single line with scrolljump=1`() { - OptionsManager.scrolljump.set(1) configureByPages(5) setPositionAndScroll(25, 25) typeText(parseKeys("k")) @@ -128,8 +125,9 @@ class MotionGroup_scrolljump_Test : VimTestCase() { assertVisibleArea(24, 58) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["10"])) fun `test move up scrolls multiple lines with scrolljump=10`() { - OptionsManager.scrolljump.set(10) configureByPages(5) setPositionAndScroll(25, 25) typeText(parseKeys("k")) @@ -137,8 +135,9 @@ class MotionGroup_scrolljump_Test : VimTestCase() { assertVisibleArea(15, 49) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["0"])) fun `test move down scrolls single line with scrolljump=0`() { - OptionsManager.scrolljump.set(0) configureByPages(5) setPositionAndScroll(25, 59) typeText(parseKeys("j")) @@ -146,8 +145,9 @@ class MotionGroup_scrolljump_Test : VimTestCase() { assertVisibleArea(26, 60) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["1"])) fun `test move down scrolls single line with scrolljump=1`() { - OptionsManager.scrolljump.set(1) configureByPages(5) setPositionAndScroll(25, 59) typeText(parseKeys("j")) @@ -155,20 +155,44 @@ class MotionGroup_scrolljump_Test : VimTestCase() { assertVisibleArea(26, 60) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["10"])) fun `test move down scrolls multiple lines with scrolljump=10`() { - OptionsManager.scrolljump.set(10) configureByPages(5) setPositionAndScroll(25, 59) typeText(parseKeys("j")) assertPosition(60, 0) assertVisibleArea(35, 69) } + + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["-50"])) + fun `test negative scrolljump treated as percentage 1`() { + configureByPages(5) + setPositionAndScroll(39, 39) + typeText(parseKeys("k")) + assertPosition(38, 0) + assertVisibleArea(22, 56) + } + + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration(VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["-10"])) + fun `test negative scrolljump treated as percentage 2`() { + configureByPages(5) + setPositionAndScroll(39, 39) + typeText(parseKeys("k")) + assertPosition(38, 0) + assertVisibleArea(36, 70) + } } -class MotionGroup_scrolloff_scrolljump_Test : VimTestCase() { +class MotionGroup_scrolloff_scrolljump_Test : VimOptionTestCase(ScrollJumpData.name, ScrollOffData.name) { + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration( + VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["10"]), + VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["5"]) + ) fun `test scroll up with scrolloff and scrolljump set`() { - OptionsManager.scrolloff.set(5) - OptionsManager.scrolljump.set(10) configureByPages(5) setPositionAndScroll(50, 55) typeText(parseKeys("k")) @@ -176,9 +200,12 @@ class MotionGroup_scrolloff_scrolljump_Test : VimTestCase() { assertVisibleArea(40, 74) } + @TestWithoutNeovim(SkipNeovimReason.OPTION) + @VimOptionTestConfiguration( + VimTestOption(ScrollJumpData.name, VimTestOptionType.NUMBER, ["10"]), + VimTestOption(ScrollOffData.name, VimTestOptionType.NUMBER, ["5"]) + ) fun `test scroll down with scrolloff and scrolljump set`() { - OptionsManager.scrolloff.set(5) - OptionsManager.scrolljump.set(10) configureByPages(5) setPositionAndScroll(50, 79) typeText(parseKeys("j")) From 0a863f32b28f7981db70f0111f592fdb2e549cdf Mon Sep 17 00:00:00 2001 From: Alex Plate Date: Tue, 22 Sep 2020 10:48:15 +0300 Subject: [PATCH 30/31] Small formatting --- .../maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt | 2 +- .../maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/com/maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt b/src/com/maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt index c538029018..e87c3a1e2b 100644 --- a/src/com/maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt +++ b/src/com/maddyhome/idea/vim/action/internal/AddBlockInlaysAction.kt @@ -111,7 +111,7 @@ class AddBlockInlaysAction : AnAction() { return if (text == null) 0 else fontMetrics.stringWidth(text) } - private inner class MyFontMetrics internal constructor(editor: Editor, familyName: String?, size: Int) { + private inner class MyFontMetrics(editor: Editor, familyName: String?, size: Int) { val metrics: FontMetrics fun isActual(editor: Editor, familyName: String, size: Int): Boolean { val font = metrics.font diff --git a/src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt b/src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt index 7c32fd1096..ecf96e5a89 100644 --- a/src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt +++ b/src/com/maddyhome/idea/vim/action/internal/AddInlineInlaysAction.kt @@ -48,7 +48,7 @@ class AddInlineInlaysAction : AnAction() { // We don't need a custom renderer, just use the standard parameter hint renderer inlayModel.addInlineElement(offset, relatesToPrecedingText, HintRenderer(if (relatesToPrecedingText) ":$text" else "$text:")) // Every 20 chars +/- 5 chars - i+= 20 + (random.nextInt(10) - 5) + i += 20 + (random.nextInt(10) - 5) } } From 1d8ac4fc02d2427d5630862d73f83a4cd4e3008a Mon Sep 17 00:00:00 2001 From: Alex Plate Date: Wed, 23 Sep 2020 09:50:00 +0300 Subject: [PATCH 31/31] Move scroll data objects up to avoid conflicts --- .../maddyhome/idea/vim/option/OptionsManager.kt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/com/maddyhome/idea/vim/option/OptionsManager.kt b/src/com/maddyhome/idea/vim/option/OptionsManager.kt index 6989d3bdec..5c7f1c4b82 100644 --- a/src/com/maddyhome/idea/vim/option/OptionsManager.kt +++ b/src/com/maddyhome/idea/vim/option/OptionsManager.kt @@ -552,6 +552,14 @@ object IdeaStatusIcon { val allValues = arrayOf(enabled, gray, disabled) } +object ScrollOffData { + const val name = "scrolloff" +} + +object ScrollJumpData { + const val name = "scrolljump" +} + object StrictMode { val on: Boolean get() = OptionsManager.ideastrictmode.isSet @@ -567,11 +575,3 @@ object VirtualEditData { const val onemore = "onemore" val allValues = arrayOf("block", "insert", "all", onemore) } - -object ScrollOffData { - const val name = "scrolloff" -} - -object ScrollJumpData { - const val name = "scrolljump" -} \ No newline at end of file