diff --git a/.github/workflows/runUiOctopusTests.yml b/.github/workflows/runUiOctopusTests.yml new file mode 100644 index 0000000000..7d7a9f23f8 --- /dev/null +++ b/.github/workflows/runUiOctopusTests.yml @@ -0,0 +1,87 @@ +name: Run Non Octopus UI Tests +on: + workflow_dispatch: + schedule: + - cron: '0 12 * * *' +jobs: + build-for-ui-test-mac-os: + if: github.repository == 'JetBrains/ideavim' + runs-on: macos-latest + steps: + - uses: actions/checkout@v4 + - name: Apply Patch + run: | + git apply src/test/java/ui/octopus.patch + - name: Setup Java + uses: actions/setup-java@v4 + with: + distribution: zulu + java-version: 11 + - name: Setup FFmpeg + uses: FedericoCarboni/setup-ffmpeg@v3 + with: + # Not strictly necessary, but it may prevent rate limit + # errors especially on GitHub-hosted macos machines. + github-token: ${{ secrets.GITHUB_TOKEN }} + - name: Setup Gradle + uses: gradle/gradle-build-action@v2.4.2 + - name: Build Plugin + run: gradle :buildPlugin + - name: Run Idea + run: | + mkdir -p build/reports + gradle :runIdeForUiTests > build/reports/idea.log & + - name: Wait for Idea started + uses: jtalk/url-health-check-action@v3 + with: + url: http://127.0.0.1:8082 + max-attempts: 20 + retry-delay: 10s + - name: Tests + run: gradle :testUi + - name: Move video + if: always() + run: mv video build/reports + - name: Move sandbox logs + if: always() + run: mv build/idea-sandbox/system/log sandbox-idea-log + - name: Save report + if: always() + uses: actions/upload-artifact@v4 + with: + name: ui-test-fails-report-mac + path: | + build/reports + sandbox-idea-log +# build-for-ui-test-linux: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/checkout@v2 +# - name: Setup Java +# uses: actions/setup-java@v2.1.0 +# with: +# distribution: zulu +# java-version: 11 +# - name: Build Plugin +# run: gradle :buildPlugin +# - name: Run Idea +# run: | +# export DISPLAY=:99.0 +# Xvfb -ac :99 -screen 0 1920x1080x16 & +# mkdir -p build/reports +# gradle :runIdeForUiTests #> build/reports/idea.log +# - name: Wait for Idea started +# uses: jtalk/url-health-check-action@1.5 +# with: +# url: http://127.0.0.1:8082 +# max-attempts: 15 +# retry-delay: 30s +# - name: Tests +# run: gradle :testUi +# - name: Save fails report +# if: ${{ failure() }} +# uses: actions/upload-artifact@v2 +# with: +# name: ui-test-fails-report-linux +# path: | +# ui-test-example/build/reports \ No newline at end of file diff --git a/CHANGES.md b/CHANGES.md index 7275d207e8..a6df8958c2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -44,6 +44,8 @@ usual beta standards. * [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape * [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode * [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction +* [VIM-3260](https://youtrack.jetbrains.com/issue/VIM-3260) Processing the offsets at the file end +* [VIM-3183](https://youtrack.jetbrains.com/issue/VIM-3183) Execute .ideavimrc on pooled thread ### Merged PRs: * [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s… diff --git a/scripts/src/main/kotlin/scripts/checkNewPluginDependencies.kt b/scripts/src/main/kotlin/scripts/checkNewPluginDependencies.kt index d1416f4e3d..4b14e65454 100644 --- a/scripts/src/main/kotlin/scripts/checkNewPluginDependencies.kt +++ b/scripts/src/main/kotlin/scripts/checkNewPluginDependencies.kt @@ -32,6 +32,7 @@ val knownPlugins = listOf( "com.github.dankinsoid.multicursor", "com.joshestein.ideavim-quickscope", "ca.alexgirard.HarpoonIJ", + "com.protoseo.input-source-auto-converter", // "cc.implicated.intellij.plugins.bunny", // I don't want to include this plugin in the list of IdeaVim plugins as I don't understand what this is for ) diff --git a/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt b/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt index 95f4ac481e..1ff5af5f1c 100644 --- a/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt +++ b/src/main/java/com/maddyhome/idea/vim/action/VimShortcutKeyAction.kt @@ -28,6 +28,7 @@ import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptions +import com.maddyhome.idea.vim.handler.enableOctopus import com.maddyhome.idea.vim.handler.isOctopusEnabled import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.HandlerInjector @@ -116,11 +117,13 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG) val editor = getEditor(e) if (editor != null && keyStroke != null) { - if (isOctopusEnabled(keyStroke, editor)) { - return ActionEnableStatus.no( - "Processing VimShortcutKeyAction for the key that is used in the octopus handler", - LogLevel.ERROR - ) + if (enableOctopus) { + if (isOctopusEnabled(keyStroke, editor)) { + return ActionEnableStatus.no( + "Processing VimShortcutKeyAction for the key that is used in the octopus handler", + LogLevel.ERROR + ) + } } if (editor.isIdeaVimDisabledHere) { return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO) diff --git a/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java b/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java index b99d24b0ee..6ac292c342 100644 --- a/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java +++ b/src/main/java/com/maddyhome/idea/vim/group/KeyGroup.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.*; +import static com.maddyhome.idea.vim.api.VimInjectorKt.injector; import static java.util.stream.Collectors.toList; /** @@ -227,10 +228,12 @@ public void registerCommandAction(@NotNull LazyVimCommand command) { private void registerRequiredShortcut(@NotNull List keys, MappingOwner owner) { for (KeyStroke key : keys) { - if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED && - !(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) && - !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) { - getRequiredShortcutKeys().add(new RequiredShortcut(key, owner)); + if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { + if (!injector.getOptionGroup().getGlobalOptions().getOctopushandler() || + !(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) && + !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) { + getRequiredShortcutKeys().add(new RequiredShortcut(key, owner)); + } } } } diff --git a/src/main/java/com/maddyhome/idea/vim/handler/CopilotKeymapCorrector.kt b/src/main/java/com/maddyhome/idea/vim/handler/CopilotKeymapCorrector.kt index 860dc0a394..ba9eab764a 100644 --- a/src/main/java/com/maddyhome/idea/vim/handler/CopilotKeymapCorrector.kt +++ b/src/main/java/com/maddyhome/idea/vim/handler/CopilotKeymapCorrector.kt @@ -67,6 +67,7 @@ private fun correctCopilotKeymap() { // This is needed to initialize the injector in case this verification is called to fast VimPlugin.getInstance() + if (!enableOctopus) return if (injector.enabler.isEnabled()) { val keymap = KeymapManagerEx.getInstanceEx().activeKeymap val res = keymap.getShortcuts("copilot.disposeInlays") diff --git a/src/main/java/com/maddyhome/idea/vim/handler/EditorHandlersChainLogger.kt b/src/main/java/com/maddyhome/idea/vim/handler/EditorHandlersChainLogger.kt index d860b28256..7e3a2c6038 100644 --- a/src/main/java/com/maddyhome/idea/vim/handler/EditorHandlersChainLogger.kt +++ b/src/main/java/com/maddyhome/idea/vim/handler/EditorHandlersChainLogger.kt @@ -34,6 +34,8 @@ internal class EditorHandlersChainLogger : ProjectActivity { private val editorHandlers = ExtensionPointName("com.intellij.editorActionHandler") override suspend fun execute(project: Project) { + if (!enableOctopus) return + val escHandlers = editorHandlers.extensionList .filter { it.action == "EditorEscape" } .joinToString("\n") { it.implementationClass } diff --git a/src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt b/src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt index 39252ab317..abbbd7a3aa 100644 --- a/src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt +++ b/src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt @@ -87,6 +87,7 @@ private fun verifyKeymap() { // This is needed to initialize the injector in case this verification is called to fast VimPlugin.getInstance() + if (!enableOctopus) return if (!injector.enabler.isEnabled()) return val keymap = KeymapManagerEx.getInstanceEx().activeKeymap diff --git a/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt b/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt index e4f077611d..ac76396d01 100644 --- a/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt +++ b/src/main/java/com/maddyhome/idea/vim/handler/VimEnterHandler.kt @@ -27,6 +27,7 @@ import com.intellij.openapi.util.UserDataHolder import com.intellij.openapi.util.removeUserData import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.key import com.maddyhome.idea.vim.group.IjOptionConstants @@ -52,7 +53,7 @@ internal val commandContinuation = Key.create("commandConti */ internal class CaretShapeEnterEditorHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - if (VimPlugin.isEnabled()) { + if (VimPlugin.isEnabled() && enableOctopus) { invokeLater { editor.updateCaretsVisualAttributes() } @@ -128,6 +129,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand if (VimPlugin.isNotEnabled()) return false if (!isHandlerEnabled(editor, dataContext)) return false if (isNotActualKeyPress(dataContext)) return false + if (!enableOctopus) return false return true } @@ -242,6 +244,7 @@ internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyH override val key: String = "" override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean { + if (!enableOctopus) return false return LookupManager.getActiveLookup(editor) != null } } @@ -257,7 +260,9 @@ internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyH */ internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - LOG.info("Esc pressed") + if (enableOctopus) { + LOG.info("Esc pressed") + } nextHandler.execute(editor, caret, dataContext) } @@ -283,7 +288,9 @@ internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandle internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - DataManager.getInstance().saveInDataContext(dataContext, Util.key, true) + if (enableOctopus) { + DataManager.getInstance().saveInDataContext(dataContext, Util.key, true) + } nextHandler.execute(editor, caret, dataContext) } @@ -311,7 +318,9 @@ internal open class StartNewLineDetectorBase(private val nextHandler: EditorActi */ internal class VimEnterLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { - LOG.info("Enter pressed") + if (enableOctopus) { + LOG.info("Enter pressed") + } nextHandler.execute(editor, caret, dataContext) } @@ -341,6 +350,7 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop } internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean { + if (!enableOctopus) return false // CMD line has a different processing mechanizm: the processing actions are registered // for the input field component. These keys are not dispatched via the octopus handler. if (editor.vim.mode is Mode.CMD_LINE) return false @@ -350,3 +360,6 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean { } return false } + +internal val enableOctopus: Boolean + get() = injector.globalOptions().octopushandler diff --git a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt index 2c6ab3943f..ba9189ea11 100644 --- a/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt +++ b/src/main/java/com/maddyhome/idea/vim/listener/VimListenerManager.kt @@ -55,6 +55,7 @@ import com.maddyhome.idea.vim.VimTypedActionHandler import com.maddyhome.idea.vim.api.LocalOptionInitialisationScenario import com.maddyhome.idea.vim.api.Options import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.coerceOffset import com.maddyhome.idea.vim.api.getLineEndForOffset import com.maddyhome.idea.vim.api.getLineStartForOffset import com.maddyhome.idea.vim.api.injector @@ -462,11 +463,17 @@ internal object VimListenerManager { if (lineEnd == endOffset - 1) { // When starting on an empty line and dragging vertically upwards onto // another line, the selection should include the entirety of the empty line - caret.setSelection(endOffset + 1, startOffset) + caret.setSelection( + ijVimEditor.coerceOffset(endOffset + 1).point, + ijVimEditor.coerceOffset(startOffset).point, + ) } else if (lineEnd == startOffset + 1 && startOffset == endOffset) { // When dragging left from EOL on a non-empty line, the selection // should include the last character on the line - caret.setSelection(lineEnd, lineEnd - 1) + caret.setSelection( + ijVimEditor.coerceOffset(lineEnd).point, + ijVimEditor.coerceOffset(lineEnd - 1).point, + ) } } //endregion diff --git a/src/test/java/org/jetbrains/plugins/ideavim/action/change/insert/InsertEnterActionTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/action/change/insert/InsertEnterActionTest.kt index 8047938d2b..f850545473 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/action/change/insert/InsertEnterActionTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/action/change/insert/InsertEnterActionTest.kt @@ -17,6 +17,8 @@ import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.testFramework.ExtensionTestUtil import com.maddyhome.idea.vim.VimPlugin +import com.maddyhome.idea.vim.api.globalOptions +import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.state.mode.Mode import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.TestWithoutNeovim @@ -45,24 +47,26 @@ class InsertEnterActionTest : VimTestCase() { forEachBean.action = "EditorEnter" forEachBean.setPluginDescriptor(PluginManagerCore.getPlugin(VimPlugin.getPluginId())!!) - if (repetitionInfo.currentRepetition == 1) { - ExtensionTestUtil.maskExtensions( - ExtensionPointName("com.intellij.editorActionHandler"), - listOf(mainBean), - fixture.testRootDisposable - ) - } else if (repetitionInfo.currentRepetition == 2) { - ExtensionTestUtil.maskExtensions( - ExtensionPointName("com.intellij.editorActionHandler"), - listOf(singleBean, mainBean), - fixture.testRootDisposable - ) - } else if (repetitionInfo.currentRepetition == 3) { - ExtensionTestUtil.maskExtensions( - ExtensionPointName("com.intellij.editorActionHandler"), - listOf(forEachBean, mainBean), - fixture.testRootDisposable - ) + if (injector.globalOptions().octopushandler) { + if (repetitionInfo.currentRepetition == 1) { + ExtensionTestUtil.maskExtensions( + ExtensionPointName("com.intellij.editorActionHandler"), + listOf(mainBean), + fixture.testRootDisposable + ) + } else if (repetitionInfo.currentRepetition == 2) { + ExtensionTestUtil.maskExtensions( + ExtensionPointName("com.intellij.editorActionHandler"), + listOf(singleBean, mainBean), + fixture.testRootDisposable + ) + } else if (repetitionInfo.currentRepetition == 3) { + ExtensionTestUtil.maskExtensions( + ExtensionPointName("com.intellij.editorActionHandler"), + listOf(forEachBean, mainBean), + fixture.testRootDisposable + ) + } } } diff --git a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetCommandTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetCommandTest.kt index 49adc198cb..a3d8381d29 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetCommandTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetCommandTest.kt @@ -164,19 +164,19 @@ class SetCommandTest : VimTestCase() { assertCommandOutput("set all", """ |--- Options --- - |noargtextobj noideatracetime scrolljump=1 notextobj-entire - | closenotebooks ideawrite=all scrolloff=0 notextobj-indent - |nocommentary noignorecase selectmode= timeout - |nodigraph noincsearch shellcmdflag=-x timeoutlen=1000 - |noexchange nomatchit shellxescape=@ notrackactionids - |nogdefault maxmapdepth=20 shellxquote={ undolevels=1000 - |nohighlightedyank more showcmd unifyjumps - | history=50 nomultiple-cursors showmode virtualedit= - |nohlsearch noNERDTree sidescroll=0 novisualbell - |noideaglobalmode nrformats=hex sidescrolloff=0 visualdelay=100 - |noideajoin nonumber nosmartcase whichwrap=b,s - | ideamarks norelativenumber startofline wrapscan - | ideastrictmode scroll=0 nosurround + |noargtextobj noideatracetime scroll=0 nosurround + | closenotebooks ideawrite=all scrolljump=1 notextobj-entire + |nocommentary noignorecase scrolloff=0 notextobj-indent + |nodigraph noincsearch selectmode= timeout + |noexchange nomatchit shellcmdflag=-x timeoutlen=1000 + |nogdefault maxmapdepth=20 shellxescape=@ notrackactionids + |nohighlightedyank more shellxquote={ undolevels=1000 + | history=50 nomultiple-cursors showcmd unifyjumps + |nohlsearch noNERDTree showmode virtualedit= + |noideaglobalmode nrformats=hex sidescroll=0 novisualbell + |noideajoin nonumber sidescrolloff=0 visualdelay=100 + | ideamarks octopushandler nosmartcase whichwrap=b,s + | ideastrictmode norelativenumber startofline wrapscan | clipboard=ideaput,autoselect,exclude:cons\|linux | excommandannotation | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 @@ -260,6 +260,7 @@ class SetCommandTest : VimTestCase() { |noNERDTree | nrformats=hex |nonumber + | octopushandler |norelativenumber |noReplaceWithRegister | scroll=0 diff --git a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetglobalCommandTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetglobalCommandTest.kt index be3306a95f..4bd1a31082 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetglobalCommandTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetglobalCommandTest.kt @@ -350,19 +350,19 @@ class SetglobalCommandTest : VimTestCase() { setOsSpecificOptionsToSafeValues() assertCommandOutput("setglobal all", """ |--- Global option values --- - |noargtextobj noideatracetime scrolljump=1 notextobj-entire - | closenotebooks ideawrite=all scrolloff=0 notextobj-indent - |nocommentary noignorecase selectmode= timeout - |nodigraph noincsearch shellcmdflag=-x timeoutlen=1000 - |noexchange nomatchit shellxescape=@ notrackactionids - |nogdefault maxmapdepth=20 shellxquote={ undolevels=1000 - |nohighlightedyank more showcmd unifyjumps - | history=50 nomultiple-cursors showmode virtualedit= - |nohlsearch noNERDTree sidescroll=0 novisualbell - |noideaglobalmode nrformats=hex sidescrolloff=0 visualdelay=100 - |noideajoin nonumber nosmartcase whichwrap=b,s - | ideamarks norelativenumber startofline wrapscan - | ideastrictmode scroll=0 nosurround + |noargtextobj noideatracetime scroll=0 nosurround + | closenotebooks ideawrite=all scrolljump=1 notextobj-entire + |nocommentary noignorecase scrolloff=0 notextobj-indent + |nodigraph noincsearch selectmode= timeout + |noexchange nomatchit shellcmdflag=-x timeoutlen=1000 + |nogdefault maxmapdepth=20 shellxescape=@ notrackactionids + |nohighlightedyank more shellxquote={ undolevels=1000 + | history=50 nomultiple-cursors showcmd unifyjumps + |nohlsearch noNERDTree showmode virtualedit= + |noideaglobalmode nrformats=hex sidescroll=0 novisualbell + |noideajoin nonumber sidescrolloff=0 visualdelay=100 + | ideamarks octopushandler nosmartcase whichwrap=b,s + | ideastrictmode norelativenumber startofline wrapscan | clipboard=ideaput,autoselect,exclude:cons\|linux | excommandannotation | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 @@ -442,6 +442,7 @@ class SetglobalCommandTest : VimTestCase() { |noNERDTree | nrformats=hex |nonumber + | octopushandler |norelativenumber |noReplaceWithRegister | scroll=0 diff --git a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetlocalCommandTest.kt b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetlocalCommandTest.kt index b4ea8649c9..8beecd4d45 100644 --- a/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetlocalCommandTest.kt +++ b/src/test/java/org/jetbrains/plugins/ideavim/ex/implementation/commands/SetlocalCommandTest.kt @@ -382,19 +382,19 @@ class SetlocalCommandTest : VimTestCase() { setOsSpecificOptionsToSafeValues() assertCommandOutput("setlocal all", """ |--- Local option values --- - |noargtextobj ideastrictmode scroll=0 nosurround - | closenotebooks noideatracetime scrolljump=1 notextobj-entire - |nocommentary ideawrite=all scrolloff=-1 notextobj-indent - |nodigraph noignorecase selectmode= timeout - |noexchange noincsearch shellcmdflag=-x timeoutlen=1000 - |nogdefault nomatchit shellxescape=@ notrackactionids - |nohighlightedyank maxmapdepth=20 shellxquote={ unifyjumps - | history=50 more showcmd virtualedit= - |nohlsearch nomultiple-cursors showmode novisualbell - |noideaglobalmode noNERDTree sidescroll=0 visualdelay=100 - |--ideajoin nrformats=hex sidescrolloff=-1 whichwrap=b,s - | ideamarks nonumber nosmartcase wrapscan - | idearefactormode= norelativenumber startofline + |noargtextobj ideastrictmode norelativenumber startofline + | closenotebooks noideatracetime scroll=0 nosurround + |nocommentary ideawrite=all scrolljump=1 notextobj-entire + |nodigraph noignorecase scrolloff=-1 notextobj-indent + |noexchange noincsearch selectmode= timeout + |nogdefault nomatchit shellcmdflag=-x timeoutlen=1000 + |nohighlightedyank maxmapdepth=20 shellxescape=@ notrackactionids + | history=50 more shellxquote={ unifyjumps + |nohlsearch nomultiple-cursors showcmd virtualedit= + |noideaglobalmode noNERDTree showmode novisualbell + |--ideajoin nrformats=hex sidescroll=0 visualdelay=100 + | ideamarks nonumber sidescrolloff=-1 whichwrap=b,s + | idearefactormode= octopushandler nosmartcase wrapscan | clipboard=ideaput,autoselect,exclude:cons\|linux | excommandannotation | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 @@ -480,6 +480,7 @@ class SetlocalCommandTest : VimTestCase() { |noNERDTree | nrformats=hex |nonumber + | octopushandler |norelativenumber |noReplaceWithRegister | scroll=0 diff --git a/src/test/java/ui/UiTests.kt b/src/test/java/ui/UiTests.kt index 9398a036bc..0115c24daf 100644 --- a/src/test/java/ui/UiTests.kt +++ b/src/test/java/ui/UiTests.kt @@ -95,6 +95,7 @@ class UiTests { `simple enter in insert mode`(editor) testMilticaretEnter(editor) `simple enter in select mode`(editor) + testMilticaretEnterInSelectMode(editor) reenableIdeaVim(editor) createFile("MyTest.java", this@uiTest) @@ -700,4 +701,46 @@ class UiTests { editor.injectText(testTextForEditor) vimExit() } + + // For VIM-3186 + private fun ContainerFixture.testMilticaretEnterInSelectMode(editor: Editor) { + println("Run testMilticaretEnter...") + + keyboard { + pressing(KeyEvent.VK_ALT) { + pressing(KeyEvent.VK_SHIFT) { + findText("One").click() + findText("Three").click() + findText("Five").click() + } + } + + enterText("$") + enterText("v") + pressing(KeyEvent.VK_CONTROL) { enterText("g") } + enter() + } + + assertEquals(3, editor.caretCount) + + assertEquals( + """ + |One Tw + | + |Three Fou + | + |Fiv + | + """.trimMargin(), editor.text + ) + + // Reset state + keyboard { + escape() + escape() + } + assertEquals(1, editor.caretCount) + editor.injectText(testTextForEditor) + vimExit() + } } diff --git a/src/test/java/ui/octopus.patch b/src/test/java/ui/octopus.patch new file mode 100644 index 0000000000..d3affee6c2 --- /dev/null +++ b/src/test/java/ui/octopus.patch @@ -0,0 +1,17 @@ +Index: vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt +IDEA additional info: +Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP +<+>UTF-8 +=================================================================== +diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt +--- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt (revision 2cc7ce5b316be5665406dcf8d3e41116ccbfb0b0) ++++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt (date 1706273373741) +@@ -302,7 +302,7 @@ + public val ideaglobalmode: ToggleOption = addOption(ToggleOption("ideaglobalmode", GLOBAL, "ideaglobalmode", false)) + public val ideastrictmode: ToggleOption = addOption(ToggleOption("ideastrictmode", GLOBAL, "ideastrictmode", false)) + public val ideatracetime: ToggleOption = addOption(ToggleOption("ideatracetime", GLOBAL, "ideatracetime", false)) +- public val octopushandler: ToggleOption = addOption(ToggleOption("octopushandler", GLOBAL, "octopushandler", true)) ++ public val octopushandler: ToggleOption = addOption(ToggleOption("octopushandler", GLOBAL, "octopushandler", false)) + } + + private class MultikeyMap(vararg entries: Option) { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertEnterAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertEnterAction.kt index 529f143f6c..1f2f4513a2 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertEnterAction.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/change/insert/InsertEnterAction.kt @@ -10,8 +10,8 @@ package com.maddyhome.idea.vim.action.change.insert import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.Mode import com.maddyhome.idea.vim.api.ExecutionContext -import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.CommandFlags @@ -21,19 +21,24 @@ import com.maddyhome.idea.vim.helper.enumSetOf import java.util.* @CommandOrMotion(keys = ["", ""], modes = [Mode.INSERT]) -public class InsertEnterAction : VimActionHandler.ForEachCaret() { +public class InsertEnterAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.INSERT override val flags: EnumSet = enumSetOf(CommandFlags.FLAG_SAVE_STROKE) override fun execute( editor: VimEditor, - caret: VimCaret, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments, ): Boolean { - injector.changeGroup.processEnter(editor, caret, context) + if (injector.globalOptions().octopushandler) { + editor.forEachNativeCaret({ caret -> + injector.changeGroup.processEnter(editor, caret, context) + }) + } else { + injector.changeGroup.processEnter(editor, context) + } injector.scroll.scrollCaretIntoView(editor) return true } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectEnterAction.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectEnterAction.kt index 30143ec9ad..475e96a5fc 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectEnterAction.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/action/motion/select/SelectEnterAction.kt @@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.action.motion.select import com.intellij.vim.annotations.CommandOrMotion import com.intellij.vim.annotations.Mode import com.maddyhome.idea.vim.api.ExecutionContext -import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimEditor +import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.command.Command import com.maddyhome.idea.vim.command.OperatorArguments @@ -23,18 +23,23 @@ import com.maddyhome.idea.vim.handler.VimActionHandler */ @CommandOrMotion(keys = [""], modes = [Mode.SELECT]) -public class SelectEnterAction : VimActionHandler.ForEachCaret() { +public class SelectEnterAction : VimActionHandler.SingleExecution() { override val type: Command.Type = Command.Type.INSERT override fun execute( editor: VimEditor, - caret: VimCaret, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments, ): Boolean { - injector.changeGroup.processEnter(editor, caret, context) + if (injector.globalOptions().octopushandler) { + editor.forEachNativeCaret({ caret -> + injector.changeGroup.processEnter(editor, caret, context) + }) + } else { + injector.changeGroup.processEnter(editor, context) + } return true } } diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/EngineEditorHelper.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/EngineEditorHelper.kt index fc74be7d4a..6cd2bcc6f2 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/EngineEditorHelper.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/EngineEditorHelper.kt @@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.api import com.maddyhome.idea.vim.common.Graphemes +import com.maddyhome.idea.vim.common.Offset import com.maddyhome.idea.vim.common.TextRange import java.nio.CharBuffer @@ -293,3 +294,9 @@ public fun VimEditor.isLineEmpty(line: Int, allowBlanks: Boolean): Boolean { } return false } + +public fun VimEditor.coerceOffset(offset: Int): Offset { + if (offset < 0) return Offset(0) + if (offset > this.fileSize()) return Offset(this.fileSize().toInt()) + return Offset(offset) +} diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/OptionProperties.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/OptionProperties.kt index a551b28178..0ae577f890 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/OptionProperties.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/OptionProperties.kt @@ -55,6 +55,7 @@ public open class GlobalOptions(scope: OptionAccessScope): OptionsPropertiesBase public var ideaglobalmode: Boolean by optionProperty(Options.ideaglobalmode) public var ideastrictmode: Boolean by optionProperty(Options.ideastrictmode) public var ideatracetime: Boolean by optionProperty(Options.ideatracetime) + public var octopushandler: Boolean by optionProperty(Options.octopushandler) } /** diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt index 9abb931848..b3e28f23a1 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/Options.kt @@ -302,6 +302,7 @@ public object Options { public val ideaglobalmode: ToggleOption = addOption(ToggleOption("ideaglobalmode", GLOBAL, "ideaglobalmode", false)) public val ideastrictmode: ToggleOption = addOption(ToggleOption("ideastrictmode", GLOBAL, "ideastrictmode", false)) public val ideatracetime: ToggleOption = addOption(ToggleOption("ideatracetime", GLOBAL, "ideatracetime", false)) + public val octopushandler: ToggleOption = addOption(ToggleOption("octopushandler", GLOBAL, "octopushandler", true)) } private class MultikeyMap(vararg entries: Option) { diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt index 6f2b62801b..6e097661cd 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroup.kt @@ -39,6 +39,7 @@ public interface VimChangeGroup { public fun processEscape(editor: VimEditor, context: ExecutionContext?, operatorArguments: OperatorArguments) public fun processEnter(editor: VimEditor, caret: VimCaret, context: ExecutionContext) + public fun processEnter(editor: VimEditor, context: ExecutionContext) public fun processPostChangeModeSwitch(editor: VimEditor, context: ExecutionContext, toSwitch: Mode) diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt index 598210da2b..73e43fe5c0 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimChangeGroupBase.kt @@ -576,6 +576,31 @@ public abstract class VimChangeGroupBase : VimChangeGroup { injector.registerGroup.storeTextSpecial(LAST_INSERTED_TEXT_REGISTER, textToPutRegister.toString()) } + /** + * Processes the Enter key by running the first successful action registered for "ENTER" keystroke. + * + * If this is REPLACE mode we need to turn off OVERWRITE before and then turn OVERWRITE back on after sending the + * "ENTER" key. + * + * @param editor The editor to press "Enter" in + * @param context The data context + */ + override fun processEnter(editor: VimEditor, context: ExecutionContext) { + if (editor.vimStateMachine.mode is Mode.REPLACE) { + editor.insertMode = true + } + val enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0) + val actions = injector.keyGroup.getActions(editor, enterKeyStroke) + for (action in actions) { + if (injector.actionExecutor.executeAction(editor, action, context)) { + break + } + } + if (editor.vimStateMachine.mode is Mode.REPLACE) { + editor.insertMode = false + } + } + /** * Performs a mode switch after change action * @param editor The editor to switch mode in diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt index 62327f16af..6c2fc36f09 100644 --- a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt +++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimKeyGroupBase.kt @@ -166,11 +166,14 @@ public abstract class VimKeyGroupBase : VimKeyGroup { private fun registerKeyMapping(fromKeys: List, owner: MappingOwner) { val oldSize = requiredShortcutKeys.size for (key in fromKeys) { - if (key.keyChar == KeyEvent.CHAR_UNDEFINED && - !(key.keyCode == KeyEvent.VK_ESCAPE && key.modifiers == 0) && - !(key.keyCode == KeyEvent.VK_ENTER && key.modifiers == 0) - ) { - requiredShortcutKeys.add(RequiredShortcut(key, owner)) + if (key.keyChar == KeyEvent.CHAR_UNDEFINED) { + if ( + !injector.globalOptions().octopushandler || + !(key.keyCode == KeyEvent.VK_ESCAPE && key.modifiers == 0) && + !(key.keyCode == KeyEvent.VK_ENTER && key.modifiers == 0) + ) { + requiredShortcutKeys.add(RequiredShortcut(key, owner)) + } } } if (requiredShortcutKeys.size != oldSize) {