1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-07-04 20:38:52 +02:00

Compare commits

...

59 Commits

Author SHA1 Message Date
0f0a73c139
Set plugin version to chylex-49 2025-06-10 02:58:25 +02:00
9eccf626db
Make g0/g^/g$ work with soft wraps 2025-06-10 02:58:15 +02:00
f53f980a01
Make gj/gk jump over soft wraps 2025-06-10 01:43:13 +02:00
54e3f7053c
Make camelCase motions adjust based on direction of visual selection 2025-06-10 01:43:13 +02:00
c7a8f6b4aa
Make search highlights temporary 2025-06-10 01:43:13 +02:00
f680f0b2cd
Exit insert mode after refactoring 2025-06-10 01:43:13 +02:00
10131a8d57
Add action to run last macro in all opened files 2025-06-10 01:43:13 +02:00
f5b194002c
Stop macro execution after a failed search 2025-06-10 01:43:13 +02:00
41e4adbd6a
Revert per-caret registers 2025-06-10 01:43:11 +02:00
191bf9967e
Apply scrolloff after executing native IDEA actions 2025-06-10 01:42:50 +02:00
ed84d20d4a
Stay on same line after reindenting 2025-06-10 01:42:50 +02:00
3e829b8949
Update search register when using f/t 2025-06-10 01:42:50 +02:00
28b511131b
Automatically add unambiguous imports after running a macro 2025-06-10 01:42:50 +02:00
cc2a8d25e2
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2025-06-10 01:42:50 +02:00
fb9f83821b
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2025-06-10 01:42:50 +02:00
fcb9fd8169
Add support for count for visual and line motion surround 2025-06-10 01:42:50 +02:00
8793889f05
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2025-06-10 01:42:50 +02:00
f294059e81
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2025-06-10 01:42:50 +02:00
c7a9fbf374
Respect count with <Action> mappings 2025-06-10 01:42:50 +02:00
9b04739f15
Change matchit plugin to use HTML patterns in unrecognized files 2025-06-10 01:42:50 +02:00
f68c56b968
Reset insert mode when switching active editor 2025-06-10 01:42:50 +02:00
0282d99692
Remove notifications about configuration options 2025-06-10 01:42:49 +02:00
03be3e1be2
Remove update checker 2025-06-10 01:42:49 +02:00
7c233f5e1a
Set custom plugin version 2025-06-10 01:42:49 +02:00
IdeaVim Bot
ef2d87ff6b Add Thomas Canava to contributors list 2025-06-07 09:02:03 +00:00
Alex Plate
75cd79312c
Which-Key plugin don't use VimShortcutKeyAction anymore, so we can hide it
The change was here: 2a1191a260
2025-06-06 17:02:35 +03:00
Thomas Canava
b868e0cb81 test: Update tests to match new keys 2025-06-06 15:34:44 +03:00
Thomas Canava
30c972ee1e fix: Vim macro not working with arrows 2025-06-06 15:34:44 +03:00
761f6f5fb9 Implement pumvisible() function 2025-06-06 15:21:26 +03:00
Matt Ellis
1e3738314a Add support for := to print line number
Fixes VIM-3921
2025-06-06 15:15:13 +03:00
dependabot[bot]
f47388175b Bump org.junit.jupiter:junit-jupiter-api from 5.12.2 to 5.13.0
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.12.2 to 5.13.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-api
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 19:40:36 +03:00
dependabot[bot]
779de5e29c Bump org.junit.jupiter:junit-jupiter-params from 5.12.2 to 5.13.0
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit5) from 5.12.2 to 5.13.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-params
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 19:23:51 +03:00
dependabot[bot]
1f0cca17cf Bump org.junit.jupiter:junit-jupiter from 5.12.2 to 5.13.0
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.12.2 to 5.13.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 19:06:42 +03:00
dependabot[bot]
d09592824c Bump org.junit.jupiter:junit-jupiter-engine from 5.12.2 to 5.13.0
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.12.2 to 5.13.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

---
updated-dependencies:
- dependency-name: org.junit.jupiter:junit-jupiter-engine
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 19:06:27 +03:00
dependabot[bot]
725d60a56e Bump org.junit.vintage:junit-vintage-engine from 5.12.2 to 5.13.0
Bumps [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit5) from 5.12.2 to 5.13.0.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.2...r5.13.0)

---
updated-dependencies:
- dependency-name: org.junit.vintage:junit-vintage-engine
  dependency-version: 5.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-04 19:06:01 +03:00
Alex Plate
77a106f9c6
Throw ProcessCanceledException instead of silently ignoring it in VimTypedActionHandler
This is a requirement from the platform
2025-05-30 19:41:41 +03:00
Alex Plate
1f66bdf5ed
Refactor tests in ChangeActionTest.kt for better readability by reformatting doTest calls and improving code style consistency. 2025-05-30 15:12:43 +03:00
Alex Plate
7c4cfe44ae
Remove unused MutableBoolean data class from KeyHandler 2025-05-30 14:20:25 +03:00
Alex Plate
b58c1a42d2
Remove unused mappingCompleted parameter from handleKey methods
This updates method signatures and removes the redundant parameter throughout the codebase. Adds a deprecated overload for backward compatibility.
2025-05-30 14:16:40 +03:00
Alex Plate
02fab15aa9
Add tests that verify the behaviour with oldundo flag 2025-05-28 16:45:06 +03:00
Alex Plate
6c9d85cce0
Add a big number of undo tests for macros
This is a part of work for VIM-3935. Since we'll have to change the `CommandProcessor` execution, this may affect the "undo" command behavior. To ensure the stability, we add undo tests before making a refactoring.
2025-05-28 12:40:56 +03:00
Alex Plate
360080e8c0
Enable some disabled tests 2025-05-28 12:14:08 +03:00
Alex Plate
e41831798e
Add a big number of undo tests for ex commands
This is a part of work for VIM-3935. Since we'll have to change the `CommandProcessor` execution, this may affect the "undo" command behavior. To ensure the stability, we add undo tests before making a refactoring.
2025-05-28 11:48:38 +03:00
Alex Plate
c93c9e539c
Add a big number of undo tests
This is a part of work for VIM-3935. Since we'll have to change the `CommandProcessor` execution, this may affect the "undo" command behavior. To ensure the stability, we add undo tests before making a refactoring.
2025-05-28 09:46:05 +03:00
Alex Plate
149e3f92ae
Update .gitignore to exclude local Claude settings file 2025-05-28 09:40:53 +03:00
Alex Plate
233bad0070
Turn off TeamCity emulation when running tests locally
IJ platform runs additional project leak checks when it detects teamcity run. It was quite complicated to understand why tests were failing on TC, but not locally, so I decided to enable TeamCity emulation to have these checks locally.

However, it turned out that in addition to that, an IJ platform also collects CPU statistics on TeamCity, which may take around a minute. This dramatically affects the performance of local execution. So, this property is turned off.
2025-05-28 09:38:54 +03:00
Alex Plate
b44308c9ef
Remove skipping of command execution if it happens not on the EDT
This requirement was a fix for VIM-318 introduced in ac654d70fa.
However, now we always run IdeaVim on EDT. Also, this check prevents the migration of IdeaVim to the background thread: VIM-3935
2025-05-27 18:30:16 +03:00
Alex Plate
8c612afed6
VIM-3929: Re-enable IdeaVim in diff editors 2025-05-27 13:07:43 +03:00
Alex Plate
2c057e937a
Fix(VIM-3929): IdeaVim is disabled in non-file based editors of IDE
This change should fix issues in Rider evaluate window and other places.

However, we may face small issues as IdeaVim will be disabled in more places than it used to be. However, this approach looks safer as before that we were disabling some random keys
2025-05-23 19:34:17 +03:00
Alex Plate
2548008116
Add missing switches to EDT during test 2025-05-23 17:14:26 +03:00
Alex Plate
c43eb1212e
Bring back CommandState to fix the compatibility with the external plugins 2025-05-23 16:53:01 +03:00
Alex Plate
4c11399b43
Bring the compatibility with the latest EAP version
Removed a single test on JSON. There are always problems with the json plugin in IJ. In 2025.2 EAP it was unbundled.
2025-05-23 16:48:37 +03:00
Alex Plate
18f3ba6fe1
Rethrow ProcessCancelledException 2025-05-23 16:22:04 +03:00
Alex Plate
2435136734
Remove vimscript roadmap file 2025-05-23 16:00:37 +03:00
Alex Plate
4d2a7df6b4
Cleanup readme file 2025-05-23 13:19:46 +03:00
dependabot[bot]
b04e90e93c Bump org.jetbrains.intellij.platform from 2.5.0 to 2.6.0
Bumps org.jetbrains.intellij.platform from 2.5.0 to 2.6.0.

---
updated-dependencies:
- dependency-name: org.jetbrains.intellij.platform
  dependency-version: 2.6.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 19:02:43 +03:00
dependabot[bot]
4c88359f46 Bump org.eclipse.jgit:org.eclipse.jgit.ssh.apache
Bumps [org.eclipse.jgit:org.eclipse.jgit.ssh.apache](https://github.com/eclipse-jgit/jgit) from 7.2.0.202503040940-r to 7.2.1.202505142326-r.
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v7.2.0.202503040940-r...v7.2.1.202505142326-r)

---
updated-dependencies:
- dependency-name: org.eclipse.jgit:org.eclipse.jgit.ssh.apache
  dependency-version: 7.2.1.202505142326-r
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-21 19:02:23 +03:00
Alex Plate
8e136bf769
Update list of supported IDEs 2025-05-21 12:31:29 +03:00
Alex Plate
be73e662bd
Change wording to JetBrains IDEs on the readme
This is a new proper name for the ide family
2025-05-21 12:30:46 +03:00
141 changed files with 7901 additions and 1382 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

4
.gitignore vendored
View File

@ -32,4 +32,6 @@ vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
# Created by github automation
settings.xml
.kotlin
.kotlin
.claude/settings.local.json

View File

@ -610,6 +610,10 @@ Contributors:
[![icon][github]](https://github.com/vumi19)
&nbsp;
Mia Vucinic
* [![icon][mail]](mailto:canava.thomas@gmail.com)
[![icon][github]](https://github.com/Malandril)
&nbsp;
Thomas Canava
Previous contributors:

View File

@ -29,8 +29,8 @@ IdeaVim is a Vim engine for JetBrains IDEs.
#### Compatibility
IntelliJ IDEA, PyCharm, CLion, PhpStorm, WebStorm, RubyMine, DataGrip, GoLand, Rider, Cursive,
Android Studio and other IntelliJ platform based IDEs.
IntelliJ IDEA, PyCharm, GoLand, CLion, PhpStorm, WebStorm, RubyMine, DataGrip, DataSpell, Rider, Cursive,
Android Studio, and other [JetBrains IDEs](https://www.jetbrains.com/ides/).
Setup
------------
@ -89,35 +89,12 @@ Here are some examples of supported vim features and commands:
* Full Vim regexps for search and search/replace
* Vim web help
* `~/.ideavimrc` configuration file
[IdeaVim plugins](https://github.com/JetBrains/ideavim/wiki/IdeaVim-Plugins):
* argtextobj
* commentary
* easymotion
* exchange
* FunctionTextObj
* highlightedyank
* indent-object
* matchit.vim
* Mini.ai
* multiple-cursors
* NERDTree
* paragraph-motion
* Peekaboo
* quick-scope
* ReplaceWithRegister
* sneak
* surround
* Switch
* textobj-entire
* Which-Key
* Vim script
* IdeaVim plugins
See also:
* [Top feature requests and bugs](https://youtrack.jetbrains.com/issues/VIM?q=%23Unresolved+sort+by%3A+votes)
* [Vimscript support roadmap](vimscript-info/VIMSCRIPT_ROADMAP.md)
* [List of supported in-build functions](vimscript-info/FUNCTIONS_INFO.MD)
Files
-----
@ -271,8 +248,7 @@ IdeaVim can execute custom scripts that are written with Vim Script.
At the moment we support all language features, but not all of the built-in functions and options are supported.
Additionally, you may be interested in the
[Vim Script Discussion](https://github.com/JetBrains/ideavim/discussions/357) or
[Vim Script Roadmap](https://github.com/JetBrains/ideavim/blob/master/vimscript-info/VIMSCRIPT_ROADMAP.md).
[Vim Script Discussion](https://github.com/JetBrains/ideavim/discussions/357).
### IDE specific options

View File

@ -51,7 +51,7 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.0.202503040940-r")
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.1.202505142326-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.1.3")
@ -74,7 +74,7 @@ plugins {
// NOTE: Unignore "test block comment falls back to line comment when not available" test
// After changing this version. It supposed to work on the next version of the gradle plugin
// Or go report to the devs that this test still fails.
id("org.jetbrains.intellij.platform") version "2.5.0"
id("org.jetbrains.intellij.platform") version "2.6.0"
id("org.jetbrains.changelog") version "2.2.1"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
@ -137,8 +137,9 @@ dependencies {
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "251.23774.318")
bundledPlugins("org.jetbrains.plugins.terminal", "com.intellij.modules.json")
bundledPlugins("org.jetbrains.plugins.terminal")
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
@ -160,17 +161,17 @@ dependencies {
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.12.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.12.2")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.13.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.13.0")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
}
@ -198,12 +199,9 @@ tasks {
useJUnitPlatform()
// Set teamcity env variable locally to run additional tests for leaks.
// By default, this test runs on TC only, but this test doesn't take a lot of time,
// so we can turn it on for local development
if (environment["TEAMCITY_VERSION"] == null) {
println("Set env TEAMCITY_VERSION to X to enable project leak checks from the platform")
environment("TEAMCITY_VERSION" to "X")
}
println("Project leak checks: If you experience project leaks on TeamCity that doesn't reproduce locally")
println("Uncomment the following line in build.gradle to enable leak checks (see build.gradle config)")
// environment("TEAMCITY_VERSION" to "X")
systemProperty("ideavim.nvim.test", System.getProperty("nvim") ?: false)
@ -242,6 +240,8 @@ tasks {
}
compileTestKotlin {
enabled = false
kotlinOptions {
jvmTarget = javaVersion
apiVersion = "2.0"
@ -257,6 +257,7 @@ tasks {
// a custom task (see below)
runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
systemProperty("idea.trust.all.projects", "true")
}
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies

View File

@ -20,7 +20,7 @@ ideaVersion=2025.1
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
instrumentPluginCode=true
version=SNAPSHOT
version=chylex-49
javaVersion=21
remoteRobotVersion=0.11.23
antlrVersion=4.10.1
@ -41,7 +41,6 @@ youtrackToken=
# Gradle settings
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.configuration-cache=true
org.gradle.caching=true
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary

View File

@ -30,7 +30,7 @@ dependencies {
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.0.202503040940-r")
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.1.202505142326-r")
implementation("com.vdurmont:semver4j:3.1.0")
}

View File

@ -88,7 +88,7 @@ class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandle
LOG.info("VimTypedAction '$charTyped': $duration ms")
}
} catch (e: ProcessCanceledException) {
// Nothing
throw e
} catch (e: Throwable) {
LOG.error(e)
}

View File

@ -0,0 +1,52 @@
package com.maddyhome.idea.vim.action
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.DumbAwareAction
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
class VimRunLastMacroInOpenFiles : DumbAwareAction() {
override fun update(e: AnActionEvent) {
val lastRegister = injector.macro.lastRegister
val isEnabled = lastRegister != 0.toChar()
e.presentation.isEnabled = isEnabled
e.presentation.text = if (isEnabled) "Run Macro '${lastRegister}' in Open Files" else "Run Last Macro in Open Files"
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.EDT
}
override fun actionPerformed(e: AnActionEvent) {
val project = e.project ?: return
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return
val editors = fileEditorManager.allEditors.filterIsInstance<TextEditor>()
WriteCommandAction.writeCommandAction(project)
.withName(e.presentation.text)
.withGlobalUndo()
.withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION)
.run<RuntimeException> {
val reg = injector.macro.lastRegister
for (editor in editors) {
fileEditorManager.openFile(editor.file, true)
val vimEditor = editor.editor.vim
vimEditor.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(vimEditor)
injector.macro.playbackRegister(vimEditor, IjEditorExecutionContext(e.dataContext), reg, 1)
}
}
}
}

View File

@ -59,11 +59,8 @@ import javax.swing.KeyStroke
*
*
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
*
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
* way to get ideavim keys for this plugin. See VIM-3085
*/
class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
init {
initInjector()
@ -92,9 +89,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut execution '$keyStroke': $duration ms")
}
} catch (_: ProcessCanceledException) {
// Control-flow exceptions (like ProcessCanceledException) should never be logged
} catch (e: ProcessCanceledException) {
// Control-flow exceptions (like ProcessCanceledException) should never be logged and should be rethrown
// See {@link com.intellij.openapi.diagnostic.Logger.checkException}
throw e
} catch (throwable: Throwable) {
LOG.error(throwable)
}
@ -198,10 +196,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
VimPlugin.getChange().tabAction = true
return ActionEnableStatus.no("Tab action in insert mode", LogLevel.INFO)
}
// Debug watch, Python console, etc.
if (keyStroke in NON_FILE_EDITOR_KEYS && !EditorHelper.isFileEditor(editor)) {
return ActionEnableStatus.no("Non file editor keys", LogLevel.INFO)
}
}
if (keyStroke in VIM_ONLY_EDITOR_KEYS) {
@ -349,14 +343,6 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
private const val ACTION_ID = "VimShortcutKeyAction"
private val NON_FILE_EDITOR_KEYS: Set<KeyStroke> = ImmutableSet.builder<KeyStroke>()
.addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
.addAll(getKeyStrokes(KeyEvent.VK_ESCAPE, 0))
.addAll(getKeyStrokes(KeyEvent.VK_TAB, 0))
.addAll(getKeyStrokes(KeyEvent.VK_UP, 0))
.addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0))
.build()
private val LOG = logger<VimShortcutKeyAction>()
@JvmStatic

View File

@ -0,0 +1,74 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.command
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.VimStateMachine
import org.jetbrains.annotations.ApiStatus
@Deprecated("Use `injector.vimState`")
@ApiStatus.ScheduledForRemoval
class CommandState(private val machine: VimStateMachine) {
val mode: Mode
get() {
val myMode = machine.mode
return when (myMode) {
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING
com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE
is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT
is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL
}
}
@get:Deprecated(
"Use `KeyHandler.keyHandlerState.commandBuilder", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.commandBuilder",
"com.maddyhome.idea.vim.KeyHandler"
)
)
@get:ApiStatus.ScheduledForRemoval
val commandBuilder: CommandBuilder
get() = KeyHandler.getInstance().keyHandlerState.commandBuilder
@Deprecated(
"Use `KeyHandler.keyHandlerState.mappingState", ReplaceWith(
"KeyHandler.getInstance().keyHandlerState.mappingState",
"com.maddyhome.idea.vim.KeyHandler"
)
)
val mappingState: MappingState
get() = KeyHandler.getInstance().keyHandlerState.mappingState
enum class Mode {
// Basic modes
COMMAND, VISUAL, SELECT, INSERT, CMD_LINE, /*EX*/
// Additional modes
OP_PENDING, REPLACE /*, VISUAL_REPLACE*/, INSERT_NORMAL, INSERT_VISUAL, INSERT_SELECT
}
enum class SubMode {
NONE, VISUAL_CHARACTER, VISUAL_LINE, VISUAL_BLOCK
}
companion object {
@JvmStatic
@Deprecated("Use `injector.vimState`")
@ApiStatus.ScheduledForRemoval
fun getInstance(editor: Editor): CommandState {
return CommandState(injector.vimState)
}
}
}

View File

@ -146,7 +146,7 @@ object VimExtensionFacade {
fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
val keyHandler = KeyHandler.getInstance()
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, keyHandler.keyHandlerState) }
}
/** Returns a single key stroke from the user input similar to 'getchar()'. */
@ -221,7 +221,7 @@ object VimExtensionFacade {
caret: ImmutableVimCaret,
keys: List<KeyStroke?>?,
) {
caret.registerStorage.setKeys(editor, context, register, keys?.filterNotNull() ?: emptyList())
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
}
/** Set the current contents of the given register */
@ -288,4 +288,4 @@ fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFu
fun interface ScriptFunction {
fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
}
}

View File

@ -21,9 +21,7 @@ import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.openapi.util.Disposer
import com.intellij.util.Alarm
import com.intellij.util.Alarm.ThreadToUse
import com.jetbrains.rd.util.first
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ModeChangeListener
@ -123,9 +121,9 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
initialised = false
}
override fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) {
override fun yankPerformed(editor: VimEditor, range: TextRange) {
ensureInitialised()
highlightHandler.highlightYankRange(caretToRange)
highlightHandler.highlightYankRange(editor.ij, range)
}
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
@ -146,25 +144,22 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
private var lastEditor: Editor? = null
private val highlighters = mutableSetOf<RangeHighlighter>()
fun highlightYankRange(caretToRange: Map<ImmutableVimCaret, TextRange>) {
fun highlightYankRange(editor: Editor, range: TextRange) {
// from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
clearYankHighlighters()
val editor = caretToRange.first().key.editor.ij
lastEditor = editor
val attributes = getHighlightTextAttributes(editor)
for (range in caretToRange.values) {
for (i in 0 until range.size()) {
val highlighter = editor.markupModel.addRangeHighlighter(
range.startOffsets[i],
range.endOffsets[i],
HighlighterLayer.SELECTION,
attributes,
HighlighterTargetArea.EXACT_RANGE,
)
highlighters.add(highlighter)
}
for (i in 0 until range.size()) {
val highlighter = editor.markupModel.addRangeHighlighter(
range.startOffsets[i],
range.endOffsets[i],
HighlighterLayer.SELECTION,
attributes,
HighlighterTargetArea.EXACT_RANGE,
)
highlighters.add(highlighter)
}
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
@ -282,4 +277,4 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
return default
}
}
}
}

View File

@ -230,7 +230,7 @@ private object FileTypePatterns {
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
this.cMakePatterns
} else {
return null
this.htmlPatterns
}
}

View File

@ -29,7 +29,6 @@ import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCopiedText
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@ -154,8 +153,7 @@ private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimC
usedType = SelectionType.CHARACTER_WISE
}
val copiedText = IjVimCopiedText(usedText, (savedRegister.copiedText as IjVimCopiedText).transferableData)
val textData = PutData.TextData(savedRegister.name, copiedText, usedType)
val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData, savedRegister.name)
val putData = PutData(
textData,

View File

@ -0,0 +1,30 @@
package com.maddyhome.idea.vim.extension.surround
import com.intellij.util.text.CharSequenceSubSequence
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
override val length = text.length * count
override fun get(index: Int): Char {
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
return text[index % text.length]
}
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
return CharSequenceSubSequence(this, startIndex, endIndex)
}
override fun toString(): String {
return text.repeat(count)
}
companion object {
fun of(text: CharSequence, count: Int): CharSequence {
return when (count) {
0 -> ""
1 -> text
else -> RepeatedCharSequence(text, count)
}
}
}
}

View File

@ -15,6 +15,7 @@ import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimChangeGroup
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
@ -37,7 +38,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.extension.exportOperatorFunction
import com.maddyhome.idea.vim.group.findBlockRange
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
}
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
}
private class YSurroundHandler : ExtensionHandler {
@ -108,7 +112,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it)
performSurround(pair, range, it, count = operatorArguments.count1)
}
// it.moveToOffset(lineStartOffset)
}
@ -131,16 +135,14 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
return
}
runWriteAction {
// Leave visual mode
editor.exitVisualMode()
editor.ij.caretModel.moveToOffset(selectionStart)
// Reset the key handler so that the command trie is updated for the new mode (Normal)
// TODO: This should probably be handled by ToHandlerMapping.execute
KeyHandler.getInstance().reset(editor)
@ -164,6 +166,10 @@ internal class VimSurroundExtension : VimExtension {
companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
// Save old register values for carets
val surroundings = editor.sortedCarets()
.map {
@ -206,7 +212,7 @@ internal class VimSurroundExtension : VimExtension {
val trimmedValue = if (newSurround.shouldTrim) innerValue.trim() else innerValue
it.first + trimmedValue + it.second
} ?: innerValue
val textData = PutData.TextData(null, injector.clipboardManager.dumbCopiedText(text), SelectionType.CHARACTER_WISE)
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), null)
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false)
surrounding.caret to putData
@ -284,20 +290,41 @@ internal class VimSurroundExtension : VimExtension {
}
}
private class Operator : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = vimEditor.ij
val c = getChar(ijEditor)
if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
runWriteAction {
val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
ijEditor.runWithEveryCaretAndRestore {
applyOnce(ijEditor, change, pair, count)
}
}
else {
applyOnce(ijEditor, change, pair, count)
// Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
}
}
return true
}
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair, count: Int) {
// XXX: Will it work with line-wise or block-wise selections?
val primaryCaret = editor.caretModel.primaryCaret
val range = getSurroundRange(primaryCaret.vim)
if (range != null) {
val start = RepeatedCharSequence.of(pair.first, count)
val end = RepeatedCharSequence.of(pair.second, count)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
}
}
private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor
@ -398,15 +425,15 @@ private fun getChar(editor: Editor): Char {
return res
}
private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction {
val editor = caret.editor
val change = VimPlugin.getChange()
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
val isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine()
val rightSurround = if (tagsOnNewLines) {
val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) {
"\n" + pair.second
} else {
@ -414,7 +441,7 @@ private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCare
}
} else {
pair.second
}
}).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@ -43,7 +43,6 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
import kotlin.math.min
/**
* Provides all the insert/replace related functionality
@ -156,6 +155,7 @@ class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
@ -165,7 +165,7 @@ class ChangeGroup : VimChangeGroupBase() {
var copiedText: IjVimCopiedText? = null
try {
if (injector.registerGroup.isPrimaryRegisterSupported()) {
copiedText = injector.clipboardManager.getPrimaryContent(editor, context) as IjVimCopiedText
copiedText = injector.clipboardManager.getPrimaryContent() as IjVimCopiedText
}
} catch (e: Exception) {
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
@ -180,11 +180,7 @@ class ChangeGroup : VimChangeGroupBase() {
}
}
val afterAction = {
val firstLine = editor.offsetToBufferPosition(
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
}
if (project != null) {

View File

@ -141,7 +141,7 @@ object IjOptions {
// Temporary feature flags during development, not really intended for external use
val closenotebooks: ToggleOption =
addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true))
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which

View File

@ -0,0 +1,68 @@
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.daemon.ReferenceImporter
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import java.util.function.BooleanSupplier
internal object MacroAutoImport {
fun run(editor: Editor, dataContext: DataContext) {
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
return
}
val importers = ReferenceImporter.EP_NAME.extensionList
if (importers.isEmpty()) {
return
}
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
override fun run(indicator: ProgressIndicator) {
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
val fixes = mutableListOf<BooleanSupplier>()
file.accept(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
for (reference in element.references) {
if (reference.resolve() != null) {
continue
}
for (importer in importers) {
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
?.let(fixes::add)
}
}
super.visitElement(element)
}
})
return@nonBlocking fixes
}.executeSynchronously()
ApplicationManager.getApplication().invokeAndWait {
WriteCommandAction.writeCommandAction(project)
.withName("Auto Import")
.withGroupId("IdeaVimAutoImportAfterMacro")
.shouldRecordActionForActiveDocument(true)
.run<RuntimeException> {
fixes.forEach { it.asBoolean }
}
}
}
})
}
}

View File

@ -21,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/**
* Used to handle playback of macros
@ -89,6 +90,9 @@ internal class MacroGroup : VimMacroBase() {
} finally {
keyStack.removeFirst()
}
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
}
if (isInternalMacro) {

View File

@ -89,6 +89,9 @@ internal class MotionGroup : VimMotionGroupBase() {
}
override fun moveCaretToCurrentDisplayLineStart(editor: VimEditor, caret: ImmutableVimCaret): Motion {
if (editor.ij.softWrapModel.isSoftWrappingEnabled) {
return AbsoluteOffset(caret.ij.visualLineStart)
}
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
return moveCaretToColumn(editor, caret, col, false)
}
@ -97,6 +100,15 @@ internal class MotionGroup : VimMotionGroupBase() {
editor: VimEditor,
caret: ImmutableVimCaret,
): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
if (editor.ij.softWrapModel.isSoftWrappingEnabled) {
val offset = caret.ij.visualLineStart
val line = editor.offsetToBufferPosition(offset).line
return if (offset == editor.getLineStartOffset(line)) {
editor.getLeadingCharacterOffset(line, 0)
} else {
offset
}
}
val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
val bufferLine = caret.getLine()
return editor.getLeadingCharacterOffset(bufferLine, col)
@ -107,6 +119,9 @@ internal class MotionGroup : VimMotionGroupBase() {
caret: ImmutableVimCaret,
allowEnd: Boolean,
): Motion {
if (editor.ij.softWrapModel.isSoftWrappingEnabled) {
return AbsoluteOffset(caret.ij.visualLineEnd - 1)
}
val col = EditorHelper.getVisualColumnAtRightOfDisplay(editor.ij, caret.getVisualPosition().line)
return moveCaretToColumn(editor, caret, col, allowEnd)
}

View File

@ -12,7 +12,6 @@ import com.intellij.icons.AllIcons
import com.intellij.ide.BrowserUtil
import com.intellij.ide.actions.OpenFileAction
import com.intellij.ide.actions.RevealFileAction
import com.intellij.notification.ActionCenter
import com.intellij.notification.Notification
import com.intellij.notification.NotificationGroup
import com.intellij.notification.NotificationGroupManager
@ -34,7 +33,6 @@ import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.SystemInfo
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.handler.KeyMapIssue
import com.maddyhome.idea.vim.helper.MessageHelper
@ -42,8 +40,6 @@ import com.maddyhome.idea.vim.icons.VimIcons
import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ijOptions
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.statistic.ActionTracker
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable
import com.maddyhome.idea.vim.vimscript.services.VimRcService
@ -63,55 +59,11 @@ internal class NotificationService(private val project: Project?) {
@Suppress("unused")
constructor() : this(null)
fun notifyAboutIdeaPut() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"""Add <code>ideaput</code> to <code>clipboard</code> option to perform a put via the IDE<br/><b><code>set clipboard+=ideaput</code></b>""",
NotificationType.INFORMATION,
)
fun notifyAboutNewUndo() {}
notification.addAction(OpenIdeaVimRcAction(notification))
fun notifyAboutIdeaPut() {}
notification.addAction(
AppendToIdeaVimRcAction(
notification,
"set clipboard^=ideaput",
"ideaput",
) {
// Technically, we're supposed to prepend values to clipboard so that it's not added to the "exclude" item.
// Since we don't handle exclude, it's safe to append. But let's be clean.
injector.globalOptions().clipboard.prependValue(OptionConstants.clipboard_ideaput)
},
)
notification.notify(project)
}
fun notifyAboutIdeaJoin(editor: VimEditor) {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"""Put <b><code>set ideajoin</code></b> into your <code>~/.ideavimrc</code> to perform a join via the IDE""",
NotificationType.INFORMATION,
)
notification.addAction(OpenIdeaVimRcAction(notification))
notification.addAction(
AppendToIdeaVimRcAction(
notification,
"set ideajoin",
"ideajoin"
) {
// This is a global-local option. Setting it will always set the global value
injector.ijOptions(editor).ideajoin = true
},
)
notification.addAction(HelpLink(ideajoinExamplesUrl))
notification.notify(project)
}
fun notifyAboutIdeaJoin(editor: VimEditor) {}
fun enableRepeatingMode() = Messages.showYesNoDialog(
"Do you want to enable repeating keys in macOS on press and hold?\n\n" +
@ -219,7 +171,7 @@ internal class NotificationService(private val project: Project?) {
is KeyMapIssue.AddShortcut -> {
appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
}
is KeyMapIssue.RemoveShortcut -> {
appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
}
@ -302,20 +254,20 @@ internal class NotificationService(private val project: Project?) {
append("</ul></p>")
}
}
} + "<small>See the ${ActionCenter.getToolwindowName()} tool window for previous IDs</small>"
} + "<small>See the Notifications tool window for previous IDs</small>"
notification =
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
it.whenExpired { notification = null }
it.addAction(StopTracking())
it.whenExpired { notification = null }
it.addAction(StopTracking())
if (id != null || possibleIDs?.size == 1) {
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project)
if (id != null || possibleIDs?.size == 1) {
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project)
}
if (id != null) {
ActionTracker.Util.logTrackedAction(id)
}

View File

@ -25,10 +25,9 @@ import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
/**
* This group works with command associated with copying and pasting text
*/
@ -128,7 +127,7 @@ public class RegisterGroup extends VimRegisterGroupBase implements PersistentSta
final String text = VimPlugin.getXML().getSafeXmlText(textElement);
if (text != null) {
logger.trace("Register data parsed");
register = new Register(key, injector.getClipboardManager().dumbCopiedText(text), type);
register = new Register(key, type, text, Collections.emptyList());
}
else {
logger.trace("Cannot parse register data");

View File

@ -37,7 +37,6 @@ import com.maddyhome.idea.vim.ide.isClionNova
import com.maddyhome.idea.vim.ide.isRider
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimCopiedText
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@ -128,7 +127,7 @@ internal class PutGroup : VimPutBase() {
point.dispose()
if (!caret.isValid) return@forEach
val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.copiedText.text.length)
val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.text.length)
val endOffset = if (data.indent) {
doIndent(
vimEditor,
@ -180,10 +179,12 @@ internal class PutGroup : VimPutBase() {
val allContentsBefore = CopyPasteManager.getInstance().allContents
val sizeBeforeInsert = allContentsBefore.size
val firstItemBefore = allContentsBefore.firstOrNull()
logger.debug { "Copied text: ${text.copiedText}" }
val (textContent, transferableData) = text.copiedText as IjVimCopiedText
logger.debug { "Transferable classes: ${text.transferableData.joinToString { it.javaClass.name }}" }
val origContent: TextBlockTransferable =
injector.clipboardManager.setClipboardText(textContent, textContent, transferableData) as TextBlockTransferable
injector.clipboardManager.setClipboardText(
text.text,
transferableData = text.transferableData,
) as TextBlockTransferable
val allContentsAfter = CopyPasteManager.getInstance().allContents
val sizeAfterInsert = allContentsAfter.size
try {
@ -191,7 +192,7 @@ internal class PutGroup : VimPutBase() {
} finally {
val textInClipboard = (firstItemBefore as? TextBlockTransferable)
?.getTransferData(DataFlavor.stringFlavor) as? String
val textOnTop = textInClipboard != null && textInClipboard != text.copiedText.text
val textOnTop = textInClipboard != null && textInClipboard != text.text
if (sizeBeforeInsert != sizeAfterInsert || textOnTop) {
// Sometimes an inserted text replaces an existing one. E.g. on insert with + or * register
(CopyPasteManager.getInstance() as? CopyPasteManagerEx)?.run { removeContent(origContent) }

View File

@ -344,7 +344,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.

View File

@ -12,7 +12,9 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.util.ui.table.JBTableRowEditor
@ -21,6 +23,8 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.awt.Component
import javax.swing.JComponent
import javax.swing.JTable
@ -40,11 +44,30 @@ internal val Editor.isIdeaVimDisabledHere: Boolean
!ClientId.isCurrentlyUnderLocalId || // CWM-927
(ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine()) ||
IdeaVimDisablerExtensionPoint.isDisabledForEditor(this) ||
isAiChat() // VIM-3786
isNotFileEditorExceptAllowed()
}
private fun Editor.isAiChat(): Boolean {
return EditorHelper.getVirtualFile(this)?.name?.contains("AIAssistantInput") == true
/**
* Almost every non-file-based editor should not use Vim mode. These editors are debug watch, Python console, AI chats,
* and other fields that are smart.
*
* We may support IdeaVim in these editors, but this will require a focused work and a lot of testing.
*
* Here are issues when non-file editors were supported:
* AI Chat VIM-3786
* Debug evaluate console VIM-3929
*
* However, we still support IdeaVim in a commit window because it works fine there, and removing vim from this place will
* be quite a visible change for users.
* We detect the commit window by the name of the editor (Dummy.txt). If this causes issues, let's disable IdeaVim
* in the commit window as well.
*
* Also, we support IdeaVim in diff viewers.
*/
private fun Editor.isNotFileEditorExceptAllowed(): Boolean {
if (EditorHelper.getVirtualFile(this)?.name?.contains("Dummy.txt") == true) return false
if (EditorHelper.isDiffEditor(this)) return false
return !EditorHelper.isFileEditor(this)
}
private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean {
@ -83,8 +106,7 @@ internal fun Editor.isPrimaryEditor(): Boolean {
internal fun Editor.isTerminalEditor(): Boolean {
return !isViewer
&& document.isWritable
&& !EditorHelper.isFileEditor(this)
&& !EditorHelper.isDiffEditor(this)
&& this.editorKind == EditorKind.CONSOLE
}
// Optimized clone of com.intellij.ide.ui.laf.darcula.DarculaUIUtil.isTableCellEditor
@ -117,3 +139,41 @@ internal val Caret.vimLine: Int
*/
internal val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
if (carets == null || carets.size == 1) {
action()
}
else {
var initialDocumentSize = this.document.textLength
var documentSizeDifference = 0
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
val restoredCarets = mutableListOf<CaretState>()
caretModel.removeSecondaryCarets()
for ((selectionStart, selectionEnd) in caretOffsets) {
if (selectionStart == selectionEnd) {
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
}
else {
caretModel.primaryCaret.setSelection(
selectionStart + documentSizeDifference,
selectionEnd + documentSizeDifference
)
}
action()
restoredCarets.add(caretModel.caretsAndSelections.single())
val documentLength = this.document.textLength
documentSizeDifference += documentLength - initialDocumentSize
initialDocumentSize = documentLength
}
caretModel.caretsAndSelections = restoredCarets
}
}

View File

@ -59,7 +59,7 @@ internal object ScrollViewHelper {
// that this needs to be replaced as a more or less dumb line for line rewrite.
val topLine = getVisualLineAtTopOfScreen(editor)
val bottomLine = getVisualLineAtBottomOfScreen(editor)
val lastLine = vimEditor.getVisualLineCount() - 1
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
val scrollOffset = injector.options(vimEditor).scrolloff

View File

@ -17,6 +17,7 @@ import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.editor.markup.TextAttributes
import com.intellij.util.application
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
@ -30,6 +31,7 @@ import com.maddyhome.idea.vim.state.mode.inVisualMode
import org.jetbrains.annotations.Contract
import java.awt.Font
import java.util.*
import javax.swing.Timer
internal fun updateSearchHighlights(
pattern: String?,
@ -84,6 +86,12 @@ internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, en
)
}
val removeHighlightsEditors = mutableListOf<Editor>()
val removeHighlightsTimer = Timer(400) {
removeHighlightsEditors.forEach(::removeSearchHighlights)
removeHighlightsEditors.clear()
}
/**
* Refreshes current search highlights for all visible editors
*/
@ -125,27 +133,43 @@ private fun updateSearchHighlights(
// hlsearch (+ incsearch/noincsearch)
// Make sure the range fits this editor. Note that Vim will use the same range for all windows. E.g., given
// `:1,5s/foo`, Vim will highlight all occurrences of `foo` in the first five lines of all visible windows
val vimEditor = editor.vim
val editorLastLine = vimEditor.lineCount() - 1
val searchStartLine = searchRange?.startLine ?: 0
val searchEndLine = (searchRange?.endLine ?: -1).coerceAtMost(editorLastLine)
if (searchStartLine <= editorLastLine) {
val results =
injector.searchHelper.findAll(
vimEditor,
pattern,
searchStartLine,
searchEndLine,
shouldIgnoreCase(pattern, shouldIgnoreSmartCase)
)
if (results.isNotEmpty()) {
if (editor === currentEditor?.ij) {
currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards)
val isSearching = injector.commandLine.getActiveCommandLine() != null
application.invokeLater {
val vimEditor = editor.vim
val editorLastLine = vimEditor.lineCount() - 1
val searchStartLine = searchRange?.startLine ?: 0
val searchEndLine = (searchRange?.endLine ?: -1).coerceAtMost(editorLastLine)
if (searchStartLine <= editorLastLine) {
val visibleArea = editor.scrollingModel.visibleAreaOnScrollingFinished
val visibleTopLeft = visibleArea.location
val visibleBottomRight = visibleArea.location.apply { translate(visibleArea.width, visibleArea.height) }
val visibleStartOffset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(visibleTopLeft))
val visibleEndOffset = editor.logicalPositionToOffset(editor.xyToLogicalPosition(visibleBottomRight))
val visibleStartLine = editor.document.getLineNumber(visibleStartOffset)
val visibleEndLine = editor.document.getLineNumber(visibleEndOffset)
removeSearchHighlights(editor)
val results =
injector.searchHelper.findAll(
vimEditor,
pattern,
searchStartLine.coerceAtLeast(visibleStartLine),
searchEndLine.coerceAtMost(visibleEndLine),
shouldIgnoreCase(pattern, shouldIgnoreSmartCase)
)
if (results.isNotEmpty()) {
if (editor === currentEditor?.ij) {
currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards)
}
highlightSearchResults(editor, pattern, results, currentMatchOffset)
if (!isSearching) {
removeHighlightsEditors.add(editor)
removeHighlightsTimer.restart()
}
}
highlightSearchResults(editor, pattern, results, currentMatchOffset)
}
editor.vimLastSearch = pattern
}
editor.vimLastSearch = pattern
} else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) {
// nohlsearch + incsearch. Even though search highlights are disabled, we still show a highlight (current editor
// only), because 'incsearch' is active. But we don't show a search if Visual is active (behind Command-line of
@ -179,6 +203,7 @@ private fun updateSearchHighlights(
}
}
removeHighlightsTimer.restart()
return currentEditorCurrentMatchOffset
}
@ -204,7 +229,7 @@ private fun removeSearchHighlights(editor: Editor) {
*/
@Contract("_, _, false -> false; _, null, true -> false")
private fun shouldAddAllSearchHighlights(editor: Editor, newPattern: String?, hlSearch: Boolean): Boolean {
return hlSearch && newPattern != null && newPattern != editor.vimLastSearch && newPattern != ""
return hlSearch && newPattern != null && newPattern != ""
}
private fun findClosestMatch(

View File

@ -20,6 +20,7 @@ import com.intellij.openapi.fileEditor.TextEditorWithPreview
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.PlatformUtils
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@ -29,6 +30,8 @@ import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
/**
@ -82,15 +85,7 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.undo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
restoreVisualMode(editor)
}
} else {
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
@ -241,4 +236,21 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
val hasChanges: Boolean
get() = changeListener.hasChanged || initialPath != editor.getPath()
}
private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().detectSelectionType(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
// visual block mode.
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
SelectionType.CHARACTER_WISE
else
detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
}
}
}

View File

@ -18,7 +18,6 @@ import com.intellij.openapi.editor.VisualPosition
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
import com.maddyhome.idea.vim.api.LocalMarkStorage
import com.maddyhome.idea.vim.api.SelectionInfo
import com.maddyhome.idea.vim.common.InsertSequence
@ -98,7 +97,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
}
// TODO: Data could be lost during visual block motion
internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor()
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()

View File

@ -1,32 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.group.NotificationService
import com.maddyhome.idea.vim.icons.VimIcons
@Service(Service.Level.APP)
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
VimPlugin.getPluginId(),
updateTimestampProperty = PROPERTY_NAME,
NotificationService.IDEAVIM_STICKY_GROUP,
VimIcons.IDEAVIM,
) {
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
companion object {
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
fun getInstance(): VimStandalonePluginUpdateChecker = service()
}
}

View File

@ -64,8 +64,10 @@ class IJEditorFocusListener : EditorListener {
VimPlugin.getChange().insertBeforeCursor(editor, context)
KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, true)
}
if (isCurrentEditorTerminal && !ijEditor.inInsertMode) {
switchToInsertMode.run()
if (isCurrentEditorTerminal) {
if (!ijEditor.inInsertMode) {
switchToInsertMode.run()
}
} else if (ijEditor.isInsertMode && (oldEditorInfo.isInsertModeForced || !ijEditor.document.isWritable)) {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
val mode = injector.vimState.mode

View File

@ -16,7 +16,9 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.TemplateManagerListener
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.codeInsight.template.impl.actions.NextVariableAction
import com.intellij.find.FindModelListener
import com.intellij.ide.actions.ApplyIntentionAction
import com.intellij.openapi.actionSystem.ActionManager
@ -30,6 +32,7 @@ import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.keymap.KeymapManager
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
@ -61,6 +64,7 @@ internal object IdeaSpecifics {
private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
@ -70,6 +74,7 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
editor = hostEditor
caretOffset = hostEditor.caretModel.offset
}
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@ -127,17 +132,18 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (editor != null) {
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
register.recordText(
register.recordText(
editor.document.getText(
TextRange(
prevDocumentOffset,
@ -145,31 +151,49 @@ internal object IdeaSpecifics {
)
)
)
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
}
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) {
editor.vim.exitInsertMode(event.dataContext.vim)
KeyHandler.getInstance().reset(editor.vim)
}
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
//endregion
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
val scrollModel = editor.scrollingModel as ScrollingModelImpl
if (scrollModel.isScrollingNow) {
val v = scrollModel.verticalScrollOffset
val h = scrollModel.horizontalScrollOffset
scrollModel.finishAnimation()
scrollModel.scroll(h, v)
scrollModel.finishAnimation()
}
injector.scroll.scrollCaretIntoView(editor.vim)
}
}
//endregion
this.editor = null
this.caretOffset = -1
}
}

View File

@ -81,7 +81,6 @@ import com.maddyhome.idea.vim.handler.keyCheckRequests
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.forceBarCursor
@ -98,6 +97,7 @@ import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
@ -411,10 +411,21 @@ internal object VimListenerManager {
override fun selectionChanged(event: FileEditorManagerEvent) {
// We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
val editor = newEditor.editor
if (editor.isInsertMode) {
editor.vim.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
// Breaks relativenumber for some reason
// injector.scroll.scrollCaretIntoView(editor.vim)
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
// VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event)
VimLastSelectedEditorTracker.setLastSelectedEditor(event.newEditor)
}
@ -487,8 +498,6 @@ internal object VimListenerManager {
OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused)
)
}
VimStandalonePluginUpdateChecker.getInstance().pluginUsed()
}
override fun editorReleased(event: EditorFactoryEvent) {

View File

@ -39,7 +39,7 @@ import java.io.IOException
@Service
internal class IjClipboardManager : VimClipboardManager {
override fun getPrimaryContent(editor: VimEditor, context: ExecutionContext): IjVimCopiedText? {
override fun getPrimaryContent(): IjVimCopiedText? {
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return null
val contents = clipboard.getContents(null) ?: return null
val (text, transferableData) = getTextAndTransferableData(contents) ?: return null
@ -242,6 +242,6 @@ internal class IjClipboardManager : VimClipboardManager {
}
}
data class IjVimCopiedText(override val text: String, val transferableData: List<Any>) : VimCopiedText {
data class IjVimCopiedText(override val text: String, override val transferableData: List<Any>) : VimCopiedText {
override fun updateText(newText: String): VimCopiedText = IjVimCopiedText(newText, transferableData)
}

View File

@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.VisualPosition
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.CaretRegisterStorage
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.LocalMarkStorage
import com.maddyhome.idea.vim.api.SelectionInfo
@ -21,6 +19,7 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretBase
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.group.visual.VisualChange
@ -29,7 +28,6 @@ import com.maddyhome.idea.vim.helper.insertHistory
import com.maddyhome.idea.vim.helper.lastSelectionInfo
import com.maddyhome.idea.vim.helper.markStorage
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
import com.maddyhome.idea.vim.helper.registerStorage
import com.maddyhome.idea.vim.helper.resetVimLastColumn
import com.maddyhome.idea.vim.helper.vimInsertStart
import com.maddyhome.idea.vim.helper.vimLastColumn
@ -37,22 +35,14 @@ import com.maddyhome.idea.vim.helper.vimLastVisualOperatorRange
import com.maddyhome.idea.vim.helper.vimLine
import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.helper.vimSelectionStartClear
import com.maddyhome.idea.vim.register.VimRegisterGroup
import com.maddyhome.idea.vim.state.mode.SelectionType
internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
override val registerStorage: CaretRegisterStorage
get() {
var storage = this.caret.registerStorage
if (storage == null) {
initInjector() // To initialize injector used in CaretRegisterStorageBase
storage = CaretRegisterStorageBase(this)
this.caret.registerStorage = storage
} else if (storage.caret != this) {
storage.caret = this
}
return storage
}
override val registerStorage: VimRegisterGroup
get() = injector.registerGroup
override val markStorage: LocalMarkStorage
get() {
var storage = this.caret.markStorage

View File

@ -35,8 +35,8 @@ import com.maddyhome.idea.vim.api.VimFoldRegion
import com.maddyhome.idea.vim.api.VimIndentConfig
import com.maddyhome.idea.vim.api.VimScrollingModel
import com.maddyhome.idea.vim.api.VimSelectionModel
import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.VimVirtualFile
import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.IndentConfig
import com.maddyhome.idea.vim.common.LiveRange
@ -179,21 +179,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
return editor.caretModel.allCarets.map { IjVimCaret(it) }
}
override var isFirstCaret = true
override var isReversingCarets = false
@Suppress("ideavimRunForEachCaret")
override fun forEachCaret(action: (VimCaret) -> Unit) {
if (editor.vim.inBlockSelection) {
action(IjVimCaret(editor.caretModel.primaryCaret))
} else {
editor.caretModel.runForEachCaret({
if (it.isValid) {
action(IjVimCaret(it))
}
}, false)
try {
editor.caretModel.runForEachCaret({
if (it.isValid) {
action(IjVimCaret(it))
isFirstCaret = false
}
}, false)
} finally {
isFirstCaret = true
}
}
}
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse)
isReversingCarets = reverse
try {
editor.caretModel.runForEachCaret({
action(IjVimCaret(it))
isFirstCaret = false
}, reverse)
} finally {
isFirstCaret = true
isReversingCarets = false
}
}
override fun isInForEachCaretScope(): Boolean {
@ -497,6 +514,10 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
}
}
override fun getSoftWrapStartAtOffset(offset: Int): Int? {
return editor.softWrapModel.getSoftWrap(offset)?.start
}
override fun <T : ImmutableVimCaret> findLastVersionOfCaret(caret: T): T {
return caret
}

View File

@ -353,7 +353,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder()
.calculateCount0Snapshot());
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
int patternEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
final String pattern = searchText.substring(0, patternEnd);

View File

@ -0,0 +1,37 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.vimscript.model.functions.handlers
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.vim.annotations.VimscriptFunction
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.vimscript.model.VimLContext
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
@VimscriptFunction(name = "pumvisible")
internal class PopupMenuVisibleFunctionHandler : FunctionHandler() {
override val minimumNumberOfArguments = 0
override val maximumNumberOfArguments = 0
override fun doFunction(
argumentValues: List<Expression>,
editor: VimEditor,
context: ExecutionContext,
vimContext: VimLContext,
): VimDataType {
return if (CompletionService.getCompletionService().currentCompletion == null)
VimInt.ZERO
else
VimInt.ONE
}
}

View File

@ -1,12 +1,4 @@
<!--
~ Copyright 2003-2023 The IdeaVim authors
~
~ Use of this source code is governed by an MIT-style
~ license that can be found in the LICENSE.txt file or at
~ https://opensource.org/licenses/MIT.
-->
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
<name>IdeaVim</name>
<id>IdeaVIM</id>
<description><![CDATA[
@ -21,7 +13,7 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul>
]]></description>
<version>SNAPSHOT</version>
<version>chylex</version>
<vendor>JetBrains</vendor>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
@ -150,10 +142,12 @@
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">
<group id="com.chylex.intellij.vim" text="Vim" popup="true">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</action>
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
</group>
<!-- Internal -->
<!--suppress PluginXmlI18n -->
<action id="VimInternalAddBlockInlays" class="com.maddyhome.idea.vim.action.internal.AddBlockInlaysAction" text="Add Test Block Inlays | IdeaVim Internal" internal="true"/>
@ -171,5 +165,6 @@
</group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions>
</idea-plugin>

View File

@ -1,3 +1,4 @@
{
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler"
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler",
"pumvisible": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.PopupMenuVisibleFunctionHandler"
}

View File

@ -15,7 +15,6 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
/**
@ -114,10 +113,8 @@ class ChangeActionTest : VimTestCase() {
)
}
// Turn it on after typing via handlers are implemented for tests
// VIM-620 |i_CTRL-O|
@Test
@Disabled
fun testInsertSingleCommandAndNewLineInserting8() {
doTest(
listOf("i", "<C-O>", "gh", "d"),
@ -142,24 +139,44 @@ class ChangeActionTest : VimTestCase() {
// VIM-321 |d| |count|
@Test
fun testDeleteEmptyRange() {
doTest("d0", "${c}hello\n", "hello\n", Mode.NORMAL())
doTest(
"d0",
"${c}hello\n",
"hello\n",
Mode.NORMAL(),
)
}
// VIM-157 |~|
@Test
fun testToggleCharCase() {
doTest("~~", "${c}hello world\n", "HEllo world\n", Mode.NORMAL())
doTest(
"~~",
"${c}hello world\n",
"HEllo world\n",
Mode.NORMAL(),
)
}
// VIM-157 |~|
@Test
fun testToggleCharCaseLineEnd() {
doTest("~~", "hello wor${c}ld\n", "hello worLD\n", Mode.NORMAL())
doTest(
"~~",
"hello wor${c}ld\n",
"hello worLD\n",
Mode.NORMAL(),
)
}
@Test
fun testToggleCaseMotion() {
doTest("g~w", "${c}FooBar Baz\n", "fOObAR Baz\n", Mode.NORMAL())
doTest(
"g~w",
"${c}FooBar Baz\n",
"fOObAR Baz\n",
Mode.NORMAL(),
)
}
@Test
@ -174,7 +191,12 @@ class ChangeActionTest : VimTestCase() {
@Test
fun testChangeLowerCase() {
doTest("guw", "${c}FooBar Baz\n", "foobar Baz\n", Mode.NORMAL())
doTest(
"guw",
"${c}FooBar Baz\n",
"foobar Baz\n",
Mode.NORMAL(),
)
}
@Test
@ -216,13 +238,13 @@ class ChangeActionTest : VimTestCase() {
one
two ${c}three
four
""".trimIndent(),
"""
one
two hello world! three
four
""".trimIndent(),
Mode.INSERT,
)
@ -236,12 +258,12 @@ class ChangeActionTest : VimTestCase() {
"""
one
${c}two
""".trimIndent(),
"""
one
""".trimIndent(),
Mode.NORMAL(),
)
@ -256,12 +278,12 @@ class ChangeActionTest : VimTestCase() {
"""
one ${c}two
three
""".trimIndent(),
"""
one
three
""".trimIndent(),
Mode.NORMAL(),
)
@ -274,15 +296,15 @@ class ChangeActionTest : VimTestCase() {
"dw",
"""
one ${c}two
three
""".trimIndent(),
"""
one
three
""".trimIndent(),
Mode.NORMAL(),
)
@ -293,12 +315,14 @@ class ChangeActionTest : VimTestCase() {
fun testDeleteLastWordBeforeEOLAndWhitespace() {
doTest(
"dw",
"""one ${c}two
three
""",
"""one
three
""",
"""
one ${c}two
three
""".trimIndent(),
"""
one
three
""".trimIndent(),
Mode.NORMAL(),
)
assertOffset(3)
@ -312,7 +336,7 @@ class ChangeActionTest : VimTestCase() {
"""
one ${c}two
three four
""".trimIndent(),
"one four\n",
Mode.NORMAL(),
@ -353,12 +377,12 @@ class ChangeActionTest : VimTestCase() {
"""
foo(${c}bar
baz
""".trimIndent(),
"""
foo(
baz
""".trimIndent(),
Mode.NORMAL(),
)
@ -372,12 +396,12 @@ class ChangeActionTest : VimTestCase() {
"""
fo${c}o
bar
""".trimIndent(),
"""
fo
bar
""".trimIndent(),
Mode.NORMAL(),
)
@ -392,7 +416,7 @@ class ChangeActionTest : VimTestCase() {
"""
one
two
""".trimIndent(),
"two\n",
Mode.NORMAL(),
@ -443,14 +467,14 @@ class ChangeActionTest : VimTestCase() {
bar
baz
quux
""".trimIndent(),
"""
${c}o
r
z
quux
""".trimIndent(),
Mode.NORMAL(),
)
@ -465,14 +489,14 @@ class ChangeActionTest : VimTestCase() {
bar
baz
quux
""".trimIndent(),
"""
${c}o
r
z
quux
""".trimIndent(),
Mode.NORMAL(),
)
@ -517,7 +541,7 @@ quux
"3J",
"""
foo.
bar
""".dotToSpace().trimIndent(),
"foo bar",
@ -542,14 +566,16 @@ quux
fun testDeleteJoinVisualLinesSpaces() {
doTest(
"v2jJ",
""" a$c 1
b 2
c 3
quux
""",
""" a 1 b 2 c 3
quux
""",
"""
a$c 1
b 2
c 3
quux
""".trimIndent(),
"""
a 1 b 2 c 3
quux
""".trimIndent(),
Mode.NORMAL(),
)
}
@ -600,15 +626,15 @@ quux
listOf("l", "<C-V>", "jj", "x"),
"""
foo
bar
""".trimIndent(),
"""
fo
br
""".trimIndent(),
Mode.NORMAL(),
)
@ -624,13 +650,13 @@ quux
foo
x
bar
""".trimIndent(),
"""
fo
x
br
""".trimIndent(),
Mode.NORMAL(),
)
@ -643,7 +669,7 @@ quux
"""
foo
bar
""".trimIndent(),
)
typeText(injector.parser.parseKeys("<C-V>" + "j" + "x"))
@ -651,7 +677,7 @@ quux
"""
oo
ar
""".trimIndent(),
)
}
@ -659,20 +685,35 @@ quux
// |r|
@Test
fun testReplaceOneChar() {
doTest("rx", "b${c}ar\n", "b${c}xr\n", Mode.NORMAL())
doTest(
"rx",
"b${c}ar\n",
"b${c}xr\n",
Mode.NORMAL(),
)
}
// |r|
@VimBehaviorDiffers(originalVimAfter = "foXX${c}Xr\n")
@Test
fun testReplaceMultipleCharsWithCount() {
doTest("3rX", "fo${c}obar\n", "fo${c}XXXr\n", Mode.NORMAL())
doTest(
"3rX",
"fo${c}obar\n",
"fo${c}XXXr\n",
Mode.NORMAL(),
)
}
// |r|
@Test
fun testReplaceMultipleCharsWithCountPastEndOfLine() {
doTest("6rX", "fo${c}obar\n", "fo${c}obar\n", Mode.NORMAL())
doTest(
"6rX",
"fo${c}obar\n",
"fo${c}obar\n",
Mode.NORMAL(),
)
}
// |r|
@ -684,12 +725,12 @@ quux
"""
fo${c}obar
foobaz
""".trimIndent(),
"""
foZZZZ
ZZZZZz
""".trimIndent(),
Mode.NORMAL(),
)
@ -700,13 +741,15 @@ quux
fun testReplaceOneCharWithNewline() {
doTest(
"r<Enter>",
""" fo${c}obar
foobaz
""",
""" fo
bar
foobaz
""",
"""
fo${c}obar
foobaz
""".trimIndent(),
"""
fo
bar
foobaz
""".trimIndent(),
Mode.NORMAL(),
)
}
@ -717,13 +760,15 @@ foobaz
fun testReplaceCharWithNewlineAndCountAddsOnlySingleNewline() {
doTest(
"3r<Enter>",
""" fo${c}obar
foobaz
""",
""" fo
r
foobaz
""",
"""
fo${c}obar
foobaz
""".trimIndent(),
"""
fo
r
foobaz
""".trimIndent(),
Mode.NORMAL(),
)
}
@ -731,7 +776,12 @@ foobaz
// |s|
@Test
fun testReplaceOneCharWithText() {
doTest("sxy<Esc>", "b${c}ar\n", "bx${c}yr\n", Mode.NORMAL())
doTest(
"sxy<Esc>",
"b${c}ar\n",
"bx${c}yr\n",
Mode.NORMAL(),
)
}
// |s|
@ -753,12 +803,12 @@ foobaz
"""
foo${c}bar
biff
""".trimIndent(),
"""
fooxy${c}z
biff
""".trimIndent(),
Mode.NORMAL(),
)
@ -767,7 +817,12 @@ foobaz
// |R|
@Test
fun testReplaceMode() {
doTest("Rbaz<Esc>", "foo${c}bar\n", "fooba${c}z\n", Mode.NORMAL())
doTest(
"Rbaz<Esc>",
"foo${c}bar\n",
"fooba${c}z\n",
Mode.NORMAL(),
)
}
// |R| |i_<Insert>|
@ -803,12 +858,12 @@ foobaz
"""
${c}foo baz
baz quux
""".trimIndent(),
"""
foo baz
fo${c}o quux
""".trimIndent(),
Mode.NORMAL(),
)
@ -822,16 +877,16 @@ foobaz
$c- 1
- 2
- 3
""".trimIndent(),
)
typeText(injector.parser.parseKeys("d$" + "j" + "."))
assertState(
"""
- 3
""".trimIndent(),
)
}
@ -847,7 +902,7 @@ foobaz
$c- 1
- 2
- 3
""".trimIndent(),
)
typeText(injector.parser.parseKeys("A" + "<BS>" + "<Esc>" + "j" + "."))
@ -856,7 +911,7 @@ foobaz
-
-
- 3
""".trimIndent(),
)
@ -866,7 +921,7 @@ foobaz
$c- 1
- 2
- 3
""".trimIndent(),
)
typeText(injector.parser.parseKeys("A" + "4" + "<BS>" + "<Esc>" + "j" + "."))
@ -875,7 +930,7 @@ foobaz
- 1
- 2
- 3
""".trimIndent(),
)
@ -885,7 +940,7 @@ foobaz
$c- 1
- 2
- 3
""".trimIndent(),
)
typeText(injector.parser.parseKeys("A" + "<BS>" + "4" + "<Esc>" + "j" + "."))
@ -894,7 +949,7 @@ foobaz
- 4
- 4
- 3
""".trimIndent(),
)
}
@ -1023,12 +1078,12 @@ foobaz
"""
${c}lorem ipsum dolor sit amet
lorem ipsum dolor sit amet
""".trimIndent(),
"""
psum dolor sit amet
${c}lorem ipsum dolor sit amet
""".trimIndent(),
Mode.NORMAL(),
)
@ -1043,12 +1098,12 @@ foobaz
gaganis ${c}gaganis gaganis
gaganis gaganis gaganis
gaganis gaganis gaganis
""".trimIndent(),
"""
gaganis s gaganis
gaganis ${c}gaganis gaganis
""".trimIndent(),
Mode.NORMAL(),
)

View File

@ -284,4 +284,206 @@ class MacroActionTest : VimTestCase() {
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
}
// Undo tests for macros
@Test
fun `test undo after simple macro execution`() {
configureByText("${c}Hello world")
typeText(injector.parser.parseKeys("qa" + "dw" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState(c)
// Single undo should restore the state after a simple macro
typeText("u")
assertState("${c}world")
}
@Test
fun `test undo after macro with insert mode`() {
val initialText = "${c}test"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "A" + " added" + "<Esc>" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("test added adde${c}d")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "test" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState("test$c")
}
@Test
fun `test undo after executing macro multiple times`() {
val initialText = "${c}one two three four"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "dw" + "q"))
typeText(injector.parser.parseKeys("3@a"))
assertState(c)
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "one two three four" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
// Verify that multiple undos were needed (one for each macro execution)
assertTrue(undoCount == 2, "Expected 2 undos for 2 macro executions, but needed $undoCount")
}
@Test
fun `test undo after nested macro execution`() {
val initialText = "${c}line1\nline2\nline3"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "dd" + "q"))
typeText(injector.parser.parseKeys("qb" + "j@a" + "q"))
typeText(injector.parser.parseKeys("@b"))
assertState("${c}line2")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "line1\nline2\nline3" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
}
@Test
fun `test undo macro with multiple operations`() {
val initialText = "${c}foo bar baz"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "dw" + "A" + "!" + "<Esc>" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("bar baz${c}!")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "foo bar baz" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
// Verify that multiple undos were needed for the multiple operations in the macro
assertTrue(undoCount > 1, "Expected multiple undos for macro with multiple operations")
}
@Test
fun `test undo macro with visual mode operations`() {
val initialText = "${c}select this text"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "viw" + "d" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("${c}this text")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "select this text" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
}
@Test
fun `test undo macro with change operation`() {
val initialText = "${c}change(this)"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "ci(" + "that" + "<Esc>" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("change(tha${c}t)")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "change(this)" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
}
@Test
fun `test undo after repeating last macro`() {
val initialText = "${c}word1 word2 word3"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "dw" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("${c}word3")
typeText(injector.parser.parseKeys("@@"))
assertState(c)
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "word1 word2 word3" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
// Each macro execution should create its own undo entry
assertTrue(undoCount == 3, "Expected 3 undos for 3 macro executions, but was $undoCount")
}
@Test
fun `test undo macro with ex command`() {
val initialText = "${c}line1\nline2\nline3"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + ":d<CR>" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("${c}line3")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "line1\nline2\nline3" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
}
@Test
fun `test undo macro with substitute command`() {
val initialText = "${c}hello world hello"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + ":s/hello/goodbye<CR>" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("${c}goodbye world goodbye")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "hello world hello" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
}
@Test
fun `test multiple undo after macro`() {
val initialText = "${c}one two three"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "dw" + "i" + "first " + "<Esc>" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("firstfirst${c} two three")
typeText(injector.parser.parseKeys("w"))
typeText(injector.parser.parseKeys("@a"))
assertState("firstfirst first${c} three")
// Verify that undo can restore the original state
var undoCount = 0
while (fixture.editor.document.text != "one two three" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState(initialText)
// Should need multiple undos since we executed the macro twice
assertTrue(undoCount >= 2, "Expected at least 2 undos for 2 macro executions")
}
@Test
fun `test undo restores cursor position after macro`() {
val initialText = "${c}start middle end"
configureByText(initialText)
typeText(injector.parser.parseKeys("qa" + "w" + "dw" + "q"))
typeText(injector.parser.parseKeys("@a"))
assertState("start en${c}d")
// Verify that undo can restore both text and cursor position
var undoCount = 0
while (fixture.editor.document.text != "start middle end" && undoCount < 10) {
typeText("u")
undoCount++
}
assertState("start ${c}middle end")
}
}

View File

@ -875,15 +875,6 @@ class MotionActionTest : VimTestCase() {
assertOffset(9)
}
// VIM-965 |[m|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT, "File type specific")
@Test
fun testMethodMovingInNonJavaFile() {
configureByJsonText("{\"foo\": \"${c}bar\"}\n")
typeText(injector.parser.parseKeys("[m"))
assertState("{\"foo\": \"${c}bar\"}\n")
}
// VIM-331 |w|
@TestWithoutNeovim(reason = SkipNeovimReason.UNCLEAR)
@Test

View File

@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import kotlin.test.assertNotNull
@ -1822,7 +1821,6 @@ $c five six se${c}ven eight
)
}
@Disabled("Action execution in tests is broken for 2024.2")
@Test
fun testInsertNewLineAboveAction() {
typeTextInFile(
@ -1849,7 +1847,6 @@ $c five six se${c}ven eight
)
}
@Disabled("Action execution in tests is broken for 2024.2")
@VimBehaviorDiffers(originalVimAfter = "${c}\n${c}\nabcde\n${c}\n${c}\nabcde\n")
@Test
fun testInsertNewLineAboveActionWithMultipleCaretsInLine() {
@ -1864,7 +1861,6 @@ $c five six se${c}ven eight
assertState("${c}\nabcde\n${c}\nabcde\n")
}
@Disabled("Action execution in tests is broken for 2024.2")
@Test
fun testInsertNewLineBelowAction() {
typeTextInFile(
@ -2185,16 +2181,18 @@ rtyfg${c}hzxc"""
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.storeText(vimEditor, context, '*', "fgh")
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(
IjVimEditor(editor),
context,
editor.vim.primaryCaret(),
TextRange(16, 19),
SelectionType.CHARACTER_WISE,
false
)
ApplicationManager.getApplication().invokeAndWait {
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(
IjVimEditor(editor),
context,
editor.vim.primaryCaret(),
TextRange(16, 19),
SelectionType.CHARACTER_WISE,
false
)
}
}
typeText(injector.parser.parseKeys("\"*P"))
val after = "fg${c}hqfg${c}hwe asd zxc rty fg${c}hfgh vbn"

View File

@ -0,0 +1,557 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action.change
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
/**
* Tests for complex undo scenarios involving motion and change operations
* These tests verify that undo correctly restores both text and cursor position
*/
class ComplexUndoTest : VimTestCase() {
@Test
fun `test undo change inside parentheses with cursor movement`() {
// This is the example from the user's request
configureByText("a${c}bc(xxx)def")
typeText("ci(")
typeText("yyy")
typeText("<Esc>")
assertState("abc(yy${c}y)def")
typeText("u")
assertState("a${c}bc(xxx)def")
}
@Test
fun `test undo change inside parentheses with cursor movement with oldundo`() {
// This is the example from the user's request
configureByText("a${c}bc(xxx)def")
try {
enterCommand("set oldundo")
typeText("ci(")
typeText("yyy")
typeText("<Esc>")
assertState("abc(yy${c}y)def")
typeText("u")
assertState("abc(yyy${c})def")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo delete inside brackets with cursor movement`() {
configureByText("fo${c}o[bar]baz")
typeText("di[")
assertState("foo[${c}]baz")
typeText("u")
assertState("fo${c}o[bar]baz")
}
@Test
fun `test undo delete inside brackets with cursor movement with oldundo`() {
configureByText("fo${c}o[bar]baz")
try {
enterCommand("set oldundo")
typeText("di[")
assertState("foo[${c}]baz")
typeText("u")
assertState("fo${c}o[bar]baz")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo change inside quotes with cursor movement`() {
configureByText("Say ${c}hello \"world\" today")
typeText("ci\"")
typeText("universe")
typeText("<Esc>")
assertState("Say hello \"univers${c}e\" today")
typeText("u")
assertState("Say ${c}hello \"world\" today")
}
@Test
fun `test undo change inside quotes with cursor movement with oldundo`() {
configureByText("Say ${c}hello \"world\" today")
try {
enterCommand("set oldundo")
typeText("ci\"")
typeText("universe")
typeText("<Esc>")
assertState("Say hello \"univers${c}e\" today")
typeText("u")
assertState("Say hello \"universe${c}\" today")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo delete word with cursor at different position`() {
configureByText("The qu${c}ick brown fox")
typeText("daw") // Delete a word (including surrounding spaces)
assertState("The ${c}brown fox")
typeText("u")
assertState("The qu${c}ick brown fox")
}
@Test
fun `test undo delete word with cursor at different position with oldundo`() {
configureByText("The qu${c}ick brown fox")
try {
enterCommand("set oldundo")
typeText("daw") // Delete a word (including surrounding spaces)
assertState("The ${c}brown fox")
typeText("u")
assertState("The qu${c}ick brown fox")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo change paragraph with cursor movement`() {
configureByText(
"""
First paragraph.
Sec${c}ond paragraph
with multiple lines.
Third paragraph.
""".trimIndent()
)
typeText("cip")
typeText("New content")
typeText("<Esc>")
assertState(
"""
First paragraph.
New conten${c}t
Third paragraph.
""".trimIndent()
)
typeText("u")
assertState(
"""
First paragraph.
Sec${c}ond paragraph
with multiple lines.
Third paragraph.
""".trimIndent()
)
}
@Test
fun `test undo change paragraph with cursor movement with oldundo`() {
configureByText(
"""
First paragraph.
Sec${c}ond paragraph
with multiple lines.
Third paragraph.
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("cip")
typeText("New content")
typeText("<Esc>")
assertState(
"""
First paragraph.
New conten${c}t
Third paragraph.
""".trimIndent()
)
typeText("u")
assertState(
"""
First paragraph.
New content${c}
Third paragraph.
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo delete to search result`() {
configureByText("abc${c}defghijklmnop")
typeText("d/jkl<CR>") // Delete to search result
assertState("abc${c}jklmnop")
typeText("u")
assertState("abc${c}defghijklmnop")
}
@Test
fun `test undo delete to search result with oldundo`() {
configureByText("abc${c}defghijklmnop")
try {
enterCommand("set oldundo")
typeText("d/jkl<CR>") // Delete to search result
assertState("abc${c}jklmnop")
typeText("u")
assertState("abc${c}defghijklmnop")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo change to mark with cursor movement`() {
configureByText(
"""
Li${c}ne 1
Line 2
Line 3
Line 4
""".trimIndent()
)
typeText("ma") // Set mark 'a'
typeText("2j") // Move down 2 lines
assertState(
"""
Line 1
Line 2
Li${c}ne 3
Line 4
""".trimIndent()
)
typeText("c'a") // Change to mark 'a'
typeText("Changed")
typeText("<Esc>")
assertState(
"""
Change${c}d
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line 2
Li${c}ne 3
Line 4
""".trimIndent()
)
}
@Test
fun `test undo change to mark with cursor movement with oldundo`() {
configureByText(
"""
Li${c}ne 1
Line 2
Line 3
Line 4
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("ma") // Set mark 'a'
typeText("2j") // Move down 2 lines
assertState(
"""
Line 1
Line 2
Li${c}ne 3
Line 4
""".trimIndent()
)
typeText("c'a") // Change to mark 'a'
typeText("Changed")
typeText("<Esc>")
assertState(
"""
Change${c}d
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Changed${c}
Line 4
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo substitute with cursor movement`() {
configureByText("${c}Hello world hello")
typeText(":s/hello/goodbye/gi<CR>") // Substitute with flags
assertState("${c}goodbye world goodbye")
typeText("u")
assertState("${c}Hello world hello")
}
@Test
fun `test undo substitute with cursor movement with oldundo`() {
configureByText("${c}Hello world hello")
try {
enterCommand("set oldundo")
typeText(":s/hello/goodbye/gi<CR>") // Substitute with flags
assertState("${c}goodbye world goodbye")
typeText("u")
assertState("${c}Hello world hello")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo multiple operations in sequence`() {
configureByText("${c}abc def ghi")
// First operation: delete word
typeText("dw")
assertState("${c}def ghi")
// Second operation: change word
typeText("cw")
typeText("xyz")
typeText("<Esc>")
assertState("xy${c}z ghi")
// Third operation: append at end
typeText("A")
typeText(" jkl")
typeText("<Esc>")
assertState("xyz ghi jk${c}l")
// Undo all operations
typeText("u")
assertState("xyz ghi${c}")
typeText("u")
assertState("${c}def ghi")
typeText("u")
assertState("${c}abc def ghi")
}
@Test
fun `test undo multiple operations in sequence with oldundo`() {
configureByText("${c}abc def ghi")
try {
enterCommand("set oldundo")
// First operation: delete word
typeText("dw")
assertState("${c}def ghi")
// Second operation: change word
typeText("cw")
typeText("xyz")
typeText("<Esc>")
assertState("xy${c}z ghi")
// Third operation: append at end
typeText("A")
typeText(" jkl")
typeText("<Esc>")
assertState("xyz ghi jk${c}l")
// Undo all operations
typeText("u")
assertState("xyz ghi jkl${c}")
typeText("u")
assertState("xyz ghi${c}")
typeText("u")
assertState("xyz${c} ghi")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo with text objects and counts`() {
configureByText("function(${c}arg1, arg2, arg3)")
typeText("d2f,") // Delete to 2nd comma
assertState("function(${c} arg3)")
typeText("u")
assertState("function(${c}arg1, arg2, arg3)")
}
@Test
fun `test undo with text objects and counts with oldundo`() {
configureByText("function(${c}arg1, arg2, arg3)")
try {
enterCommand("set oldundo")
typeText("d2f,") // Delete to 2nd comma
assertState("function(${c} arg3)")
typeText("u")
assertState("function(${c}arg1, arg2, arg3)")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo inner word at end of word`() {
configureByText("The quic${c}k brown fox")
typeText("diw")
assertState("The ${c} brown fox")
typeText("u")
assertState("The quic${c}k brown fox")
}
@Test
fun `test undo inner word at end of word with oldundo`() {
configureByText("The quic${c}k brown fox")
try {
enterCommand("set oldundo")
typeText("diw")
assertState("The ${c} brown fox")
typeText("u")
assertState("The quic${c}k brown fox")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo change with register and motion`() {
configureByText("${c}Hello world")
typeText("\"aciw") // Change inner word into register 'a'
typeText("Goodbye")
typeText("<Esc>")
assertState("Goodby${c}e world")
typeText("u")
assertState("${c}Hello world")
}
@Test
fun `test undo change with register and motion with oldundo`() {
configureByText("${c}Hello world")
try {
enterCommand("set oldundo")
typeText("\"aciw") // Change inner word into register 'a'
typeText("Goodbye")
typeText("<Esc>")
assertState("Goodby${c}e world")
typeText("u")
assertState("Goodbye${c} world")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo join with indentation handling`() {
configureByText(
"""
if (condition) {
${c}doSomething();
doMore();
}
""".trimIndent()
)
typeText("J")
assertState(
"""
if (condition) {
doSomething();${c} doMore();
}
""".trimIndent()
)
typeText("u")
assertState(
"""
if (condition) {
${c}doSomething();
doMore();
}
""".trimIndent()
)
}
@Test
fun `test undo join with indentation handling with oldundo`() {
configureByText(
"""
if (condition) {
${c}doSomething();
doMore();
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("J")
assertState(
"""
if (condition) {
doSomething();${c} doMore();
}
""".trimIndent()
)
typeText("u")
assertState(
"""
if (condition) {
${c}doSomething();
doMore();
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo replace mode changes`() {
configureByText("${c}Hello world")
typeText("R")
typeText("Goodbye")
typeText("<Esc>")
assertState("Goodby${c}eorld")
typeText("u")
assertState("${c}Hello world")
}
@Test
fun `test undo replace mode changes with oldundo`() {
configureByText("${c}Hello world")
try {
enterCommand("set oldundo")
typeText("R")
typeText("Goodbye")
typeText("<Esc>")
assertState("Goodby${c}eorld")
typeText("u")
assertState("Goodbye${c}orld")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -9,7 +9,6 @@
package org.jetbrains.plugins.ideavim.action.change
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class OperatorActionTest : VimTestCase() {
@ -154,7 +153,6 @@ class OperatorActionTest : VimTestCase() {
}
@Test
@Disabled(":set does not correctly parse the quotes in the lambda syntax")
// The parser is treating the second double-quote char as a comment. The argument to the command is parsed as:
// opfunc={ arg -> execute "`[v`]rx
// The map command is properly handled - the `<CR>g@` is correctly understood, and the full lambda is passed to the

View File

@ -85,4 +85,239 @@ class ChangeCaseToggleCharacterActionTest : VimTestCase() {
this.enterCommand("set whichwrap=~")
}
}
@Test
fun `test undo after toggle case single character`() {
configureByText("Hello ${c}World")
typeText("~")
assertState("Hello w${c}orld")
typeText("u")
assertState("Hello ${c}World")
}
@Test
fun `test undo after toggle case single character with oldundo`() {
configureByText("Hello ${c}World")
try {
enterCommand("set oldundo")
typeText("~")
assertState("Hello w${c}orld")
typeText("u")
assertState("Hello ${c}World")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after toggle case multiple characters`() {
configureByText("${c}hello WORLD")
typeText("5~")
assertState("HELLO${c} WORLD")
typeText("u")
assertState("${c}hello WORLD")
}
@Test
fun `test undo after toggle case multiple characters with oldundo`() {
configureByText("${c}hello WORLD")
try {
enterCommand("set oldundo")
typeText("5~")
assertState("HELLO${c} WORLD")
typeText("u")
assertState("${c}hello WORLD")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential toggle case`() {
configureByText("${c}aBc")
typeText("~")
assertState("A${c}Bc")
typeText("~")
assertState("Ab${c}c")
typeText("~")
assertState("Ab${c}C")
// Undo third toggle
typeText("u")
assertState("Ab${c}c")
// Undo second toggle
typeText("u")
assertState("A${c}Bc")
// Undo first toggle
typeText("u")
assertState("${c}aBc")
}
@Test
fun `test multiple undo after sequential toggle case with oldundo`() {
configureByText("${c}aBc")
try {
enterCommand("set oldundo")
typeText("~")
assertState("A${c}Bc")
typeText("~")
assertState("Ab${c}c")
typeText("~")
assertState("Ab${c}C")
// Undo third toggle
typeText("u")
assertState("Ab${c}c")
// Undo second toggle
typeText("u")
assertState("A${c}Bc")
// Undo first toggle
typeText("u")
assertState("${c}aBc")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo toggle case motion`() {
configureByText("${c}hello world")
typeText("g~w")
assertState("${c}HELLO world")
typeText("u")
assertState("${c}hello world")
}
@Test
fun `test undo toggle case motion with oldundo`() {
configureByText("${c}hello world")
try {
enterCommand("set oldundo")
typeText("g~w")
assertState("${c}HELLO world")
typeText("u")
assertState("${c}hello world")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo uppercase motion`() {
configureByText("${c}hello world")
typeText("gUw")
assertState("${c}HELLO world")
typeText("u")
assertState("${c}hello world")
}
@Test
fun `test undo uppercase motion with oldundo`() {
configureByText("${c}hello world")
try {
enterCommand("set oldundo")
typeText("gUw")
assertState("${c}HELLO world")
typeText("u")
assertState("${c}hello world")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo lowercase motion`() {
configureByText("${c}HELLO WORLD")
typeText("guw")
assertState("${c}hello WORLD")
typeText("u")
assertState("${c}HELLO WORLD")
}
@Test
fun `test undo lowercase motion with oldundo`() {
configureByText("${c}HELLO WORLD")
try {
enterCommand("set oldundo")
typeText("guw")
assertState("${c}hello WORLD")
typeText("u")
assertState("${c}HELLO WORLD")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo toggle case line`() {
configureByText("${c}Hello World")
typeText("g~~")
assertState("${c}hELLO wORLD")
typeText("u")
assertState("${c}Hello World")
}
@Test
fun `test undo toggle case line with oldundo`() {
configureByText("${c}Hello World")
try {
enterCommand("set oldundo")
typeText("g~~")
assertState("${c}hELLO wORLD")
typeText("u")
assertState("${c}Hello World")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo uppercase line`() {
configureByText("${c}Hello World")
typeText("gUU")
assertState("${c}HELLO WORLD")
typeText("u")
assertState("${c}Hello World")
}
@Test
fun `test undo uppercase line with oldundo`() {
configureByText("${c}Hello World")
try {
enterCommand("set oldundo")
typeText("gUU")
assertState("${c}HELLO WORLD")
typeText("u")
assertState("${c}Hello World")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo lowercase line`() {
configureByText("${c}HELLO WORLD")
typeText("guu")
assertState("${c}hello world")
typeText("u")
assertState("${c}HELLO WORLD")
}
@Test
fun `test undo lowercase line with oldundo`() {
configureByText("${c}HELLO WORLD")
try {
enterCommand("set oldundo")
typeText("guu")
assertState("${c}hello world")
typeText("u")
assertState("${c}HELLO WORLD")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -12,7 +12,6 @@ package org.jetbrains.plugins.ideavim.action.change.change
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class ChangeMotionActionTest : VimTestCase() {
@ -150,9 +149,7 @@ class ChangeMotionActionTest : VimTestCase() {
}
// VIM-276 |c| |F|
@Suppress("unused")
@Test
@Disabled
fun testChangeLinesToBackwards() {
doTest(
"cFc",
@ -208,4 +205,280 @@ class ChangeMotionActionTest : VimTestCase() {
""".trimIndent()
)
}
@Test
fun `test undo after change word`() {
configureByText("Hello ${c}world and more")
typeText("cw")
typeText("Vim")
typeText("<Esc>")
assertState("Hello Vi${c}m and more")
typeText("u")
assertState("Hello ${c}world and more")
}
@Test
fun `test undo after change word with oldundo`() {
configureByText("Hello ${c}world and more")
try {
enterCommand("set oldundo")
typeText("cw")
typeText("Vim")
typeText("<Esc>")
assertState("Hello Vi${c}m and more")
typeText("u")
assertState("Hello Vim${c} and more")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after change line`() {
configureByText(
"""
First line
${c}Second line with text
Third line
""".trimIndent()
)
typeText("cc")
typeText("Changed line")
typeText("<Esc>")
assertState(
"""
First line
Changed lin${c}e
Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
${c}Second line with text
Third line
""".trimIndent()
)
}
@Test
fun `test undo after change line with oldundo`() {
configureByText(
"""
First line
${c}Second line with text
Third line
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("cc")
typeText("Changed line")
typeText("<Esc>")
assertState(
"""
First line
Changed lin${c}e
Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
Changed line${c}
Third line
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after change to end of line`() {
configureByText("Start ${c}middle end")
typeText("C")
typeText("new ending")
typeText("<Esc>")
assertState("Start new endin${c}g")
typeText("u")
assertState("Start ${c}middle end")
}
@Test
fun `test undo after change to end of line with oldundo`() {
configureByText("Start ${c}middle end")
try {
enterCommand("set oldundo")
typeText("C")
typeText("new ending")
typeText("<Esc>")
assertState("Start new endin${c}g")
typeText("u")
assertState("Start new ending${c}")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after change with motion`() {
configureByText("The ${c}quick brown fox")
typeText("c3w")
typeText("slow")
typeText("<Esc>")
assertState("The slo${c}w")
typeText("u")
assertState("The ${c}quick brown fox")
}
@Test
fun `test undo after change with motion with oldundo`() {
configureByText("The ${c}quick brown fox")
try {
enterCommand("set oldundo")
typeText("c3w")
typeText("slow")
typeText("<Esc>")
assertState("The slo${c}w")
typeText("u")
assertState("The slow${c}")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo change with motion and caret movement`() {
configureByText("a${c}bc(xxx)def")
typeText("ci(")
typeText("yyy")
typeText("<Esc>")
assertState("abc(yy${c}y)def")
typeText("u")
assertState("a${c}bc(xxx)def")
}
@Test
fun `test undo change with motion and caret movement with oldundo`() {
configureByText("a${c}bc(xxx)def")
try {
enterCommand("set oldundo")
typeText("ci(")
typeText("yyy")
typeText("<Esc>")
assertState("abc(yy${c}y)def")
typeText("u")
assertState("abc(yyy${c})def")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential changes`() {
configureByText("${c}one two three")
typeText("cw")
typeText("ONE")
typeText("<Esc>")
assertState("ON${c}E two three")
typeText("w")
typeText("cw")
typeText("TWO")
typeText("<Esc>")
assertState("ONE TW${c}O three")
// Undo second change
typeText("u")
assertState("ONE ${c}two three")
// Undo first change
typeText("u")
assertState("${c}one two three")
}
@Test
fun `test multiple undo after sequential changes with oldundo`() {
configureByText("${c}one two three")
try {
enterCommand("set oldundo")
typeText("cw")
typeText("ONE")
typeText("<Esc>")
assertState("ON${c}E two three")
typeText("w")
typeText("cw")
typeText("TWO")
typeText("<Esc>")
assertState("ONE TW${c}O three")
// Undo second change
typeText("u")
assertState("ONE TWO${c} three")
// Undo first change
typeText("u")
assertState("ONE ${c} three")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo change character`() {
configureByText("a${c}bcdef")
typeText("s")
typeText("X")
typeText("<Esc>")
assertState("a${c}Xcdef")
typeText("u")
assertState("a${c}bcdef")
}
@Test
fun `test undo change character with oldundo`() {
configureByText("a${c}bcdef")
try {
enterCommand("set oldundo")
typeText("s")
typeText("X")
typeText("<Esc>")
assertState("a${c}Xcdef")
typeText("u")
assertState("aX${c}cdef")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo change multiple characters`() {
configureByText("abc${c}defghijk")
typeText("3s")
typeText("XXX")
typeText("<Esc>")
assertState("abcXX${c}Xghijk")
typeText("u")
assertState("abc${c}defghijk")
}
@Test
fun `test undo change multiple characters with oldundo`() {
configureByText("abc${c}defghijk")
try {
enterCommand("set oldundo")
typeText("3s")
typeText("XXX")
typeText("<Esc>")
assertState("abcXX${c}Xghijk")
typeText("u")
assertState("abcXXX${c}ghijk")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -47,4 +47,245 @@ class ChangeNumberDecActionTest : VimTestCase() {
Mode.NORMAL(),
)
}
@Test
fun `test undo after decrement number`() {
configureByText("The answer is ${c}42")
typeText("<C-X>")
assertState("The answer is 4${c}1")
typeText("u")
assertState("The answer is ${c}42")
}
@Test
fun `test undo after decrement number with oldundo`() {
configureByText("The answer is ${c}42")
try {
enterCommand("set oldundo")
typeText("<C-X>")
assertState("The answer is 4${c}1")
typeText("u")
assertState("The answer is ${c}42")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after decrement number with caret move`() {
configureByText("The answer ${c}is 42")
typeText("<C-X>")
assertState("The answer is 4${c}1")
typeText("u")
assertState("The answer ${c}is 42")
}
@Test
fun `test undo after decrement number with caret move with oldundo`() {
configureByText("The answer ${c}is 42")
try {
enterCommand("set oldundo")
typeText("<C-X>")
assertState("The answer is 4${c}1")
typeText("u")
assertState("The answer ${c}is 42")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after decrement with count`() {
configureByText("Count: ${c}20")
typeText("5<C-X>")
assertState("Count: 1${c}5")
typeText("u")
assertState("Count: ${c}20")
}
@Test
fun `test undo after decrement with count with oldundo`() {
configureByText("Count: ${c}20")
try {
enterCommand("set oldundo")
typeText("5<C-X>")
assertState("Count: 1${c}5")
typeText("u")
assertState("Count: ${c}20")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after decrement negative number`() {
configureByText("Temperature: ${c}-5 degrees")
typeText("<C-X>")
assertState("Temperature: -${c}6 degrees")
typeText("u")
assertState("Temperature: ${c}-5 degrees")
}
@Test
fun `test undo after decrement negative number with oldundo`() {
configureByText("Temperature: ${c}-5 degrees")
try {
enterCommand("set oldundo")
typeText("<C-X>")
assertState("Temperature: -${c}6 degrees")
typeText("u")
assertState("Temperature: ${c}-5 degrees")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential decrements`() {
configureByText("Value: ${c}100")
typeText("<C-X>")
assertState("Value: 9${c}9")
typeText("<C-X>")
assertState("Value: 9${c}8")
typeText("<C-X>")
assertState("Value: 9${c}7")
// Undo third decrement
typeText("u")
assertState("Value: 9${c}8")
// Undo second decrement
typeText("u")
assertState("Value: 9${c}9")
// Undo first decrement
typeText("u")
assertState("Value: ${c}100")
}
@Test
fun `test multiple undo after sequential decrements with oldundo`() {
configureByText("Value: ${c}100")
try {
enterCommand("set oldundo")
typeText("<C-X>")
assertState("Value: 9${c}9")
typeText("<C-X>")
assertState("Value: 9${c}8")
typeText("<C-X>")
assertState("Value: 9${c}7")
// Undo third decrement
typeText("u")
assertState("Value: 9${c}8")
// Undo second decrement
typeText("u")
assertState("Value: 9${c}9")
// Undo first decrement
typeText("u")
assertState("Value: ${c}100")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo decrement with visual selection`() {
configureByText("""
${c}10
20
30
""".trimIndent())
typeText("Vj<C-X>") // Visual select first two lines and decrement
assertState("""
${c}9
19
30
""".trimIndent())
typeText("u")
assertState("""
${c}10
20
30
""".trimIndent())
}
@Test
fun `test undo decrement with visual selection with oldundo`() {
configureByText("""
${c}10
20
30
""".trimIndent())
try {
enterCommand("set oldundo")
typeText("Vj<C-X>") // Visual select first two lines and decrement
assertState("""
${c}9
19
30
""".trimIndent())
typeText("u")
assertState("""
${c}10
20
30
""".trimIndent())
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo increment and decrement combination`() {
configureByText("Number: ${c}50")
typeText("<C-A>")
assertState("Number: 5${c}1")
typeText("<C-X>")
assertState("Number: 5${c}0")
typeText("<C-X>")
assertState("Number: 4${c}9")
// Undo second decrement
typeText("u")
assertState("Number: 5${c}0")
// Undo first decrement
typeText("u")
assertState("Number: 5${c}1")
// Undo increment
typeText("u")
assertState("Number: ${c}50")
}
@Test
fun `test undo increment and decrement combination with oldundo`() {
configureByText("Number: ${c}50")
try {
enterCommand("set oldundo")
typeText("<C-A>")
assertState("Number: 5${c}1")
typeText("<C-X>")
assertState("Number: 5${c}0")
typeText("<C-X>")
assertState("Number: 4${c}9")
// Undo second decrement
typeText("u")
assertState("Number: 5${c}0")
// Undo first decrement
typeText("u")
assertState("Number: 5${c}1")
// Undo increment
typeText("u")
assertState("Number: ${c}50")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -19,4 +19,231 @@ class ChangeNumberIncActionTest : VimTestCase() {
fun `test inc fancy number`() {
doTest("<C-A>", "1${c}0X0", "10X1", Mode.NORMAL())
}
@Test
fun `test undo after increment number`() {
configureByText("The answer is ${c}42")
typeText("<C-A>")
assertState("The answer is 4${c}3")
typeText("u")
assertState("The answer is ${c}42")
}
@Test
fun `test undo after increment number with oldundo`() {
configureByText("The answer is ${c}42")
try {
enterCommand("set oldundo")
typeText("<C-A>")
assertState("The answer is 4${c}3")
typeText("u")
assertState("The answer is ${c}42")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after increment with count`() {
configureByText("Count: ${c}10")
typeText("5<C-A>")
assertState("Count: 1${c}5")
typeText("u")
assertState("Count: ${c}10")
}
@Test
fun `test undo after increment with count with oldundo`() {
configureByText("Count: ${c}10")
try {
enterCommand("set oldundo")
typeText("5<C-A>")
assertState("Count: 1${c}5")
typeText("u")
assertState("Count: ${c}10")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after increment negative number`() {
configureByText("Temperature: ${c}-5 degrees")
typeText("<C-A>")
assertState("Temperature: -${c}4 degrees")
typeText("u")
assertState("Temperature: ${c}-5 degrees")
}
@Test
fun `test undo after increment negative number with oldundo`() {
configureByText("Temperature: ${c}-5 degrees")
try {
enterCommand("set oldundo")
typeText("<C-A>")
assertState("Temperature: -${c}4 degrees")
typeText("u")
assertState("Temperature: ${c}-5 degrees")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential increments`() {
configureByText("Value: ${c}100")
typeText("<C-A>")
assertState("Value: 10${c}1")
typeText("<C-A>")
assertState("Value: 10${c}2")
typeText("<C-A>")
assertState("Value: 10${c}3")
// Undo third increment
typeText("u")
assertState("Value: 10${c}2")
// Undo second increment
typeText("u")
assertState("Value: 10${c}1")
// Undo first increment
typeText("u")
assertState("Value: ${c}100")
}
@Test
fun `test multiple undo after sequential increments with oldundo`() {
configureByText("Value: ${c}100")
try {
enterCommand("set oldundo")
typeText("<C-A>")
assertState("Value: 10${c}1")
typeText("<C-A>")
assertState("Value: 10${c}2")
typeText("<C-A>")
assertState("Value: 10${c}3")
// Undo third increment
typeText("u")
assertState("Value: 10${c}2")
// Undo second increment
typeText("u")
assertState("Value: 10${c}1")
// Undo first increment
typeText("u")
assertState("Value: ${c}100")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo increment with visual selection`() {
configureByText(
"""
${c}10
20
30
""".trimIndent()
)
typeText("Vj<C-A>") // Visual select first two lines and increment
assertState(
"""
${c}11
21
30
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}10
20
30
""".trimIndent()
)
}
@Test
fun `test undo increment with visual selection with oldundo`() {
configureByText(
"""
${c}10
20
30
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("Vj<C-A>") // Visual select first two lines and increment
assertState(
"""
${c}11
21
30
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}10
20
30
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo increment octal number`() {
// OCT is disabled by default
configureByText("Octal: ${c}0777")
typeText("<C-A>")
assertState("Octal: 077${c}8")
typeText("u")
assertState("Octal: ${c}0777")
}
@Test
fun `test undo increment octal number with oldundo`() {
// OCT is disabled by default
configureByText("Octal: ${c}0777")
try {
enterCommand("set oldundo")
typeText("<C-A>")
assertState("Octal: 077${c}8")
typeText("u")
assertState("Octal: ${c}0777")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo increment hex number`() {
configureByText("Hex: ${c}0xff")
typeText("<C-A>")
assertState("Hex: 0x10${c}0")
typeText("u")
assertState("Hex: ${c}0xff")
}
@Test
fun `test undo increment hex number with oldundo`() {
configureByText("Hex: ${c}0xff")
try {
enterCommand("set oldundo")
typeText("<C-A>")
assertState("Hex: 0x10${c}0")
typeText("u")
assertState("Hex: ${c}0xff")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -130,4 +130,81 @@ class DeleteCharacterLeftActionTest : VimTestCase() {
assertVisualPosition(0, 55)
assertVisibleLineBounds(0, 15, 94)
}
@Test
fun `test undo after deleting character left`() {
configureByText("foo f${c}oo")
typeText("X")
assertState("foo ${c}oo")
typeText("u")
assertState("foo f${c}oo")
}
@Test
fun `test undo after deleting character left with oldundo`() {
configureByText("foo f${c}oo")
try {
enterCommand("set oldundo")
typeText("X")
assertState("foo ${c}oo")
typeText("u")
assertState("foo f${c}oo")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after deleting multiple characters left`() {
configureByText("abcdef${c}ghijk")
typeText("3X")
assertState("abc${c}ghijk")
typeText("u")
assertState("abcdef${c}ghijk")
}
@Test
fun `test undo after deleting multiple characters left with oldundo`() {
configureByText("abcdef${c}ghijk")
try {
enterCommand("set oldundo")
typeText("3X")
assertState("abc${c}ghijk")
typeText("u")
assertState("abcdef${c}ghijk")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential deletes`() {
configureByText("foo bar ${c}baz")
typeText("XXX")
assertState("foo b${c}baz")
typeText("u")
assertState("foo ba${c}baz")
typeText("u")
assertState("foo bar${c}baz")
typeText("u")
assertState("foo bar ${c}baz")
}
@Test
fun `test multiple undo after sequential deletes with oldundo`() {
configureByText("foo bar ${c}baz")
try {
enterCommand("set oldundo")
typeText("XXX")
assertState("foo b${c}baz")
typeText("u")
assertState("foo ba${c}baz")
typeText("u")
assertState("foo bar${c}baz")
typeText("u")
assertState("foo bar ${c}baz")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -113,4 +113,31 @@ class DeleteCharacterRightActionTest : VimTestCase() {
// type annotation
assertVisualPosition(0, 4)
}
@Test
fun `undo after deleting character`() {
configureByText("foo ${c}foo")
typeText("xx")
assertState("foo ${c}o")
typeText("u")
assertState("foo ${c}oo")
typeText("u")
assertState("foo ${c}foo")
}
@Test
fun `undo after deleting character with oldundo`() {
configureByText("foo ${c}foo")
try {
enterCommand("set oldundo")
typeText("xx")
assertState("foo ${c}o")
typeText("u")
assertState("foo ${c}oo")
typeText("u")
assertState("foo ${c}foo")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -32,4 +32,230 @@ class DeleteEndOfLineActionTest : VimTestCase() {
Mode.NORMAL(),
)
}
@Test
fun `test undo after delete to end of line`() {
configureByText("Hello ${c}world and more text")
typeText("D")
assertState("Hello$c ")
typeText("u")
assertState("Hello ${c}world and more text")
}
@Test
fun `test undo after delete to end of line with count`() {
configureByText(
"""
First ${c}line with text
Second line
Third line
Fourth line
""".trimIndent()
)
typeText("2D")
assertState(
"""
First$c
Third line
Fourth line
""".trimIndent()
)
typeText("u")
assertState(
"""
First ${c}line with text
Second line
Third line
Fourth line
""".trimIndent()
)
}
@Test
fun `test undo after delete to end of line at different positions`() {
configureByText("abc${c}defghijk")
typeText("D")
assertState("ab${c}c")
typeText("u")
assertState("abc${c}defghijk")
// Move to different position and delete again
typeText("0")
assertState("${c}abcdefghijk")
typeText("D")
assertState("$c")
typeText("u")
assertState("${c}abcdefghijk")
}
@Test
fun `test multiple undo after sequential delete to end of line`() {
configureByText(
"""
${c}First line
Second line
Third line
""".trimIndent()
)
typeText("D")
assertState(
"""
$c
Second line
Third line
""".trimIndent()
)
typeText("j")
typeText("D")
assertState(
"""
$c
Third line
""".trimIndent()
)
// Undo second delete
typeText("u")
assertState(
"""
${c}Second line
Third line
""".trimIndent()
)
// Undo first delete
typeText("u")
assertState(
"""
${c}First line
Second line
Third line
""".trimIndent()
)
}
@Test
fun `test undo after delete to end of line with oldundo`() {
configureByText("Hello ${c}world and more text")
try {
enterCommand("set oldundo")
typeText("D")
assertState("Hello$c ")
typeText("u")
assertState("Hello ${c}world and more text")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after delete to end of line with count with oldundo`() {
configureByText(
"""
First ${c}line with text
Second line
Third line
Fourth line
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("2D")
assertState(
"""
First$c
Third line
Fourth line
""".trimIndent()
)
typeText("u")
assertState(
"""
First ${c}line with text
Second line
Third line
Fourth line
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after delete to end of line at different positions with oldundo`() {
configureByText("abc${c}defghijk")
try {
enterCommand("set oldundo")
typeText("D")
assertState("ab${c}c")
typeText("u")
assertState("abc${c}defghijk")
// Move to different position and delete again
typeText("0")
assertState("${c}abcdefghijk")
typeText("D")
assertState("$c")
typeText("u")
assertState("${c}abcdefghijk")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential delete to end of line with oldundo`() {
configureByText(
"""
${c}First line
Second line
Third line
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("D")
assertState(
"""
$c
Second line
Third line
""".trimIndent()
)
typeText("j")
typeText("D")
assertState(
"""
$c
Third line
""".trimIndent()
)
// Undo second delete
typeText("u")
assertState(
"""
${c}Second line
Third line
""".trimIndent()
)
// Undo first delete
typeText("u")
assertState(
"""
${c}
Second line
Third line
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -16,6 +16,7 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.impl.OptionTest
import org.jetbrains.plugins.ideavim.impl.TraceOptions
import org.jetbrains.plugins.ideavim.impl.VimOption
import org.junit.jupiter.api.Test
@TraceOptions
class DeleteJoinLinesSpacesActionTest : VimTestCase() {
@ -88,4 +89,280 @@ class DeleteJoinLinesSpacesActionTest : VimTestCase() {
Mode.NORMAL(),
)
}
@Test
fun `test undo after join lines`() {
configureByText(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
typeText("J")
assertState(
"""
First line
Second line${c} Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
}
@Test
fun `test undo after join multiple lines`() {
configureByText(
"""
${c}Line 1
Line 2
Line 3
Line 4
""".trimIndent()
)
typeText("3J")
assertState(
"""
Line 1 Line 2$c Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}Line 1
Line 2
Line 3
Line 4
""".trimIndent()
)
}
@Test
fun `test undo after multiple sequential joins`() {
configureByText(
"""
${c}One
Two
Three
Four
""".trimIndent()
)
typeText("J")
assertState(
"""
One${c} Two
Three
Four
""".trimIndent()
)
typeText("J")
assertState(
"""
One Two${c} Three
Four
""".trimIndent()
)
// Undo second join
typeText("u")
assertState(
"""
One${c} Two
Three
Four
""".trimIndent()
)
// Undo first join
typeText("u")
assertState(
"""
${c}One
Two
Three
Four
""".trimIndent()
)
}
@Test
fun `test undo join with special whitespace handling`() {
configureByText(
"""
${c}foo {
bar
}
""".trimIndent()
)
typeText("J")
assertState(
"""
foo {${c} bar
}
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}foo {
bar
}
""".trimIndent()
)
}
@Test
fun `test undo after join lines with oldundo`() {
configureByText(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("J")
assertState(
"""
First line
Second line${c} Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after join multiple lines with oldundo`() {
configureByText(
"""
${c}Line 1
Line 2
Line 3
Line 4
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("3J")
assertState(
"""
Line 1 Line 2$c Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}Line 1
Line 2
Line 3
Line 4
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after multiple sequential joins with oldundo`() {
configureByText(
"""
${c}One
Two
Three
Four
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("J")
assertState(
"""
One${c} Two
Three
Four
""".trimIndent()
)
typeText("J")
assertState(
"""
One Two${c} Three
Four
""".trimIndent()
)
// Undo second join
typeText("u")
assertState(
"""
One${c} Two
Three
Four
""".trimIndent()
)
// Undo first join
typeText("u")
assertState(
"""
${c}One
Two
Three
Four
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo join with special whitespace handling with oldundo`() {
configureByText(
"""
${c}foo {
bar
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("J")
assertState(
"""
foo {${c} bar
}
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}foo {
bar
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -257,4 +257,298 @@ class DeleteMotionActionTest : VimTestCase() {
typeText("dd")
assertStatusLineCleared()
}
@Test
fun `test undo after delete motion with word`() {
configureByText("Hello ${c}world and more text")
typeText("dw")
assertState("Hello ${c}and more text")
typeText("u")
assertState("Hello ${c}world and more text")
}
@Test
fun `test undo after delete motion with word with oldundo`() {
configureByText("Hello ${c}world and more text")
try {
enterCommand("set oldundo")
typeText("dw")
assertState("Hello ${c}and more text")
typeText("u")
assertState("Hello ${c}world and more text")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after delete line`() {
configureByText(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
typeText("dd")
assertState(
"""
First line
${c}Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
}
@Test
fun `test undo after delete line with oldundo`() {
configureByText(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("dd")
assertState(
"""
First line
${c}Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
${c}Second line
Third line
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after delete multiple lines`() {
configureByText(
"""
First line
${c}Second line
Third line
Fourth line
Fifth line
""".trimIndent()
)
typeText("3dd")
assertState(
"""
First line
${c}Fifth line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
${c}Second line
Third line
Fourth line
Fifth line
""".trimIndent()
)
}
@Test
fun `test undo after delete multiple lines with oldundo`() {
configureByText(
"""
First line
${c}Second line
Third line
Fourth line
Fifth line
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("3dd")
assertState(
"""
First line
${c}Fifth line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
${c}Second line
Third line
Fourth line
Fifth line
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after delete with different motions`() {
configureByText("The ${c}quick brown fox jumps")
typeText("d3w")
assertState("The ${c}jumps")
typeText("u")
assertState("The ${c}quick brown fox jumps")
// Test with $ motion
typeText("d$")
assertState("The$c ")
typeText("u")
assertState("The ${c}quick brown fox jumps")
// Test with 0 motion
typeText("d0")
assertState("${c}quick brown fox jumps")
typeText("u")
assertState("The ${c}quick brown fox jumps")
}
@Test
fun `test undo after delete with different motions with oldundo`() {
configureByText("The ${c}quick brown fox jumps")
try {
enterCommand("set oldundo")
typeText("d3w")
assertState("The ${c}jumps")
typeText("u")
assertState("The ${c}quick brown fox jumps")
// Test with $ motion
typeText("d$")
assertState("The$c ")
typeText("u")
assertState("The ${c}quick brown fox jumps")
// Test with 0 motion
typeText("d0")
assertState("${c}quick brown fox jumps")
typeText("u")
assertState("The ${c}quick brown fox jumps")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo delete with motion that includes caret movement`() {
configureByText("a${c}bc(xxx)def")
typeText("di(")
assertState("abc(${c})def")
typeText("u")
assertState("a${c}bc(xxx)def")
}
@Test
fun `test undo delete with motion that includes caret movement with oldundo`() {
configureByText("a${c}bc(xxx)def")
try {
enterCommand("set oldundo")
typeText("di(")
assertState("abc(${c})def")
typeText("u")
assertState("a${c}bc(xxx)def")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after delete to mark`() {
configureByText(
"""
Line 1
Li${c}ne 2
Line 3
Line 4
""".trimIndent()
)
typeText("ma") // Set mark a
typeText("jj")
assertState(
"""
Line 1
Line 2
Line 3
Li${c}ne 4
""".trimIndent()
)
typeText("d'a") // Delete to mark a
assertState(
"""
${c}Line 1
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line 2
Line 3
Li${c}ne 4
""".trimIndent()
)
}
@Test
fun `test undo after delete to mark with oldundo`() {
configureByText(
"""
Line 1
Li${c}ne 2
Line 3
Line 4
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("ma") // Set mark a
typeText("jj")
assertState(
"""
Line 1
Line 2
Line 3
Li${c}ne 4
""".trimIndent()
)
typeText("d'a") // Delete to mark a
assertState(
"""
${c}Line 1
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line 2
Line 3
Li${c}ne 4
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -52,4 +52,76 @@ class InsertDeleteInsertedTextActionTest : VimTestCase() {
Mode.NORMAL(),
)
}
@Test
fun `test undo after delete inserted text in insert mode`() {
configureByText("Hello ${c}world")
typeText("i")
typeText("beautiful ")
assertState("Hello beautiful ${c}world")
assertMode(Mode.INSERT)
typeText("<C-U>")
assertState("Hello ${c}world")
assertMode(Mode.INSERT)
typeText("<Esc>")
typeText("u")
assertState("Hello ${c}world")
}
@Test
fun `test undo after delete inserted text in insert mode with oldundo`() {
configureByText("Hello ${c}world")
try {
enterCommand("set oldundo")
typeText("i")
typeText("beautiful ")
assertState("Hello beautiful ${c}world")
assertMode(Mode.INSERT)
typeText("<C-U>")
assertState("Hello ${c}world")
assertMode(Mode.INSERT)
typeText("<Esc>")
typeText("u")
assertState("Hello ${c}world")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo preserves text before insertion point`() {
configureByText("Start${c}End")
typeText("i")
typeText(" Middle ")
assertState("Start Middle ${c}End")
assertMode(Mode.INSERT)
typeText("<C-U>")
assertState("Start${c}End")
assertMode(Mode.INSERT)
typeText("<Esc>")
assertState("Star${c}tEnd")
typeText("u")
assertState("Start${c}End")
}
@Test
fun `test undo preserves text before insertion point with oldundo`() {
configureByText("Start${c}End")
try {
enterCommand("set oldundo")
typeText("i")
typeText(" Middle ")
assertState("Start Middle ${c}End")
assertMode(Mode.INSERT)
typeText("<C-U>")
assertState("Start${c}End")
assertMode(Mode.INSERT)
typeText("<Esc>")
assertState("Star${c}tEnd")
typeText("u")
assertState("Start${c}End")
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -15,7 +15,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
import kotlin.test.assertTrue
@ -85,7 +84,6 @@ class InsertNewLineAboveActionTest : VimTestCase() {
doTest("O", before, after, Mode.INSERT)
}
@Disabled("Action execution in tests is broken for 2024.2")
@Test
fun `test insert new line above with multiple carets`() {
val before = """ I fou${c}nd it in a legendary land

View File

@ -12,7 +12,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class InsertNewLineBelowActionTest : VimTestCase() {
@ -80,7 +79,6 @@ class InsertNewLineBelowActionTest : VimTestCase() {
doTest("o", before, after, Mode.INSERT)
}
@Disabled("Action execution in tests is broken for 2024.2")
@Test
fun `test insert new line below with multiple carets`() {
val before = """ I fou${c}nd it in a legendary land

View File

@ -136,4 +136,266 @@ class ShiftLeftTest : VimTestCase() {
""".trimIndent(),
)
}
@Test
fun `test undo after shift left single line`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("<<")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
}
@Test
fun `test undo after shift left with motion`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("<2j") // Shift left 3 lines
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
}
@Test
fun `test undo after shift left visual mode`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("Vj<") // Visual select 2 lines and shift left
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c} line1()
line2()
line3()
}
""".trimIndent()
)
}
@Test
fun `test undo shift left in insert mode`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("i<C-D>")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("<Esc>")
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
}
@Test
fun `test undo after shift left single line with oldundo`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("<<")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after shift left with motion with oldundo`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("<2j") // Shift left 3 lines
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after shift left visual mode with oldundo`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("Vj<") // Visual select 2 lines and shift left
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c} line1()
line2()
line3()
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo shift left in insert mode with oldundo`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("i<C-D>")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("<Esc>")
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -208,4 +208,416 @@ class ShiftRightTest : VimTestCase() {
""".trimIndent(),
)
}
@Test
fun `test undo after shift right single line`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText(">>")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
}
@Test
fun `test undo after shift right with motion`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText(">2j") // Shift right 3 lines
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
}
@Test
fun `test undo after shift right visual mode`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("Vj>") // Visual select 2 lines and shift right
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
}
@Test
fun `test multiple undo after sequential shifts right`() {
configureByText("${c}unindented line")
typeText(">>")
assertState(" ${c}unindented line")
typeText(">>")
assertState(" ${c}unindented line")
typeText(">>")
assertState(" ${c}unindented line")
// Undo third shift
typeText("u")
assertState(" ${c}unindented line")
// Undo second shift
typeText("u")
assertState(" ${c}unindented line")
// Undo first shift
typeText("u")
assertState("${c}unindented line")
}
@Test
fun `test undo shift right in insert mode`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("i<C-T>")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("<Esc>")
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
}
@Test
fun `test undo shift right and left combination`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText(">>") // Shift right
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("<<") // Shift left
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
// Undo shift left
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
// Undo shift right
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
}
@Test
fun `test undo after shift right single line with oldundo`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText(">>")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after shift right with motion with oldundo`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText(">2j") // Shift right 3 lines
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after shift right visual mode with oldundo`() {
configureByText(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("Vj>") // Visual select 2 lines and shift right
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
typeText("u")
assertState(
"""
func main() {
${c}line1()
line2()
line3()
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential shifts right with oldundo`() {
configureByText("${c}unindented line")
try {
enterCommand("set oldundo")
typeText(">>")
assertState(" ${c}unindented line")
typeText(">>")
assertState(" ${c}unindented line")
typeText(">>")
assertState(" ${c}unindented line")
// Undo third shift
typeText("u")
assertState(" ${c}unindented line")
// Undo second shift
typeText("u")
assertState(" ${c}unindented line")
// Undo first shift
typeText("u")
assertState("${c}unindented line")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo shift right in insert mode with oldundo`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("i<C-T>")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("<Esc>")
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo shift right and left combination with oldundo`() {
configureByText(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText(">>") // Shift right
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
typeText("<<") // Shift left
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
// Undo shift left
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
// Undo shift right
typeText("u")
assertState(
"""
func main() {
${c}println("Hello")
}
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -80,7 +80,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
kotlin.test.assertTrue(notifications.isEmpty() || notifications.last().isExpired || OptionConstants.clipboard_ideaput !in notifications.last().content)
}
private fun notifications(): MutableList<Notification> {
private fun notifications(): List<Notification> {
return ActionCenter.getNotifications(fixture.project)
}

View File

@ -216,4 +216,294 @@ class PutTestAfterCursorActionTest : VimTestCase() {
)
}
}
@Test
fun `test undo after put after cursor`() {
configureByText("Hello ${c}world")
typeText("yy")
typeText("p")
assertState(
"""
Hello world
${c}Hello world
""".trimIndent()
)
typeText("u")
assertState("Hello ${c}world")
}
@Test
fun `test undo after put character after cursor`() {
configureByText("abc${c}def")
typeText("yl") // Yank 'd'
typeText("h") // Move left
assertState("ab${c}cdef")
typeText("p")
assertState("abc${c}ddef")
typeText("u")
assertState("ab${c}cdef")
}
@Test
fun `test undo after put word after cursor`() {
configureByText("The ${c}quick brown fox")
typeText("yiw") // Yank "quick"
typeText("w") // Move to "brown"
assertState("The quick ${c}brown fox")
typeText("p")
assertState("The quick bquic${c}krown fox")
typeText("u")
assertState("The quick ${c}brown fox")
}
@Test
fun `test multiple undo after sequential puts after cursor`() {
configureByText("${c}Hello")
typeText("yy")
typeText("p")
assertState(
"""
Hello
${c}Hello
""".trimIndent()
)
typeText("p")
assertState(
"""
Hello
Hello
${c}Hello
""".trimIndent()
)
// Undo second put
typeText("u")
assertState(
"""
Hello
${c}Hello
""".trimIndent()
)
// Undo first put
typeText("u")
assertState(
"""
${c}Hello
""".trimIndent()
)
}
@Test
fun `test undo put and move cursor`() {
configureByText("${c}abc def")
typeText("yiw") // Yank "abc"
typeText("w") // Move to "def"
assertState("abc ${c}def")
typeText("gp") // Put and move cursor after pasted text
assertState("abc dabc${c}ef")
typeText("u")
assertState("abc ${c}def")
}
@Test
fun `test undo put visual block after cursor`() {
configureByText(
"""
${c}abc
def
ghi
""".trimIndent()
)
typeText("<C-V>jjl") // Visual block select first 2 columns of all lines
typeText("y")
typeText("$")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
typeText("p")
assertState(
"""
abc${c}ab
defde
ghigh
""".trimIndent()
)
typeText("u")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
}
@Test
fun `test undo after put after cursor with oldundo`() {
configureByText("Hello ${c}world")
try {
enterCommand("set oldundo")
typeText("yy")
typeText("p")
assertState(
"""
Hello world
${c}Hello world
""".trimIndent()
)
typeText("u")
assertState("Hello ${c}world")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after put character after cursor with oldundo`() {
configureByText("abc${c}def")
try {
enterCommand("set oldundo")
typeText("yl") // Yank 'd'
typeText("h") // Move left
assertState("ab${c}cdef")
typeText("p")
assertState("abc${c}ddef")
typeText("u")
assertState("ab${c}cdef")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after put word after cursor with oldundo`() {
configureByText("The ${c}quick brown fox")
try {
enterCommand("set oldundo")
typeText("yiw") // Yank "quick"
typeText("w") // Move to "brown"
assertState("The quick ${c}brown fox")
typeText("p")
assertState("The quick bquic${c}krown fox")
typeText("u")
assertState("The quick ${c}brown fox")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential puts after cursor with oldundo`() {
configureByText("${c}Hello")
try {
enterCommand("set oldundo")
typeText("yy")
typeText("p")
assertState(
"""
Hello
${c}Hello
""".trimIndent()
)
typeText("p")
assertState(
"""
Hello
Hello
${c}Hello
""".trimIndent()
)
// Undo second put
typeText("u")
assertState(
"""
Hello
${c}Hello
""".trimIndent()
)
// Undo first put
typeText("u")
assertState(
"""
${c}Hello
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo put and move cursor with oldundo`() {
configureByText("${c}abc def")
try {
enterCommand("set oldundo")
typeText("yiw") // Yank "abc"
typeText("w") // Move to "def"
assertState("abc ${c}def")
typeText("gp") // Put and move cursor after pasted text
assertState("abc dabc${c}ef")
typeText("u")
assertState("abc ${c}def")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo put visual block after cursor with oldundo`() {
configureByText(
"""
${c}abc
def
ghi
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("<C-V>jjl") // Visual block select first 2 columns of all lines
typeText("y")
typeText("$")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
typeText("p")
assertState(
"""
abc${c}ab
defde
ghigh
""".trimIndent()
)
typeText("u")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -56,4 +56,257 @@ class PutTextBeforeCursorActionTest : VimTestCase() {
""".trimIndent()
assertState(after)
}
@Test
fun `test undo after put before cursor`() {
configureByText("Hello ${c}world")
typeText("yy")
typeText("P")
assertState(
"""
${c}Hello world
Hello world
""".trimIndent()
)
typeText("u")
assertState("Hello ${c}world")
}
@Test
fun `test undo after put character before cursor`() {
configureByText("abc${c}def")
typeText("yl") // Yank 'd'
typeText("h") // Move left
assertState("ab${c}cdef")
typeText("P")
assertState("ab${c}dcdef")
typeText("u")
assertState("ab${c}cdef")
}
@Test
fun `test undo after put word before cursor`() {
configureByText("The ${c}quick brown fox")
typeText("yiw") // Yank "quick"
typeText("w") // Move to "brown"
assertState("The quick ${c}brown fox")
typeText("P")
assertState("The quick quic${c}kbrown fox")
typeText("u")
assertState("The quick ${c}brown fox")
}
@Test
fun `test multiple undo after sequential puts`() {
configureByText("${c}Hello")
typeText("yy")
typeText("P")
assertState(
"""
${c}Hello
Hello
""".trimIndent()
)
typeText("P")
assertState(
"""
${c}Hello
Hello
Hello
""".trimIndent()
)
// Undo second put
typeText("u")
assertState(
"""
${c}Hello
Hello
""".trimIndent()
)
// Undo first put
typeText("u")
assertState(
"""
${c}Hello
""".trimIndent()
)
}
@Test
fun `test undo put visual block`() {
configureByText(
"""
${c}abc
def
ghi
""".trimIndent()
)
typeText("<C-V>jjl") // Visual block select first 2 columns of all lines
typeText("y")
typeText("$")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
typeText("P")
assertState(
"""
ab${c}abc
dedef
ghghi
""".trimIndent()
)
typeText("u")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
}
@Test
fun `test undo after put before cursor with oldundo`() {
configureByText("Hello ${c}world")
try {
enterCommand("set oldundo")
typeText("yy")
typeText("P")
assertState(
"""
${c}Hello world
Hello world
""".trimIndent()
)
typeText("u")
assertState("Hello ${c}world")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after put character before cursor with oldundo`() {
configureByText("abc${c}def")
try {
enterCommand("set oldundo")
typeText("yl") // Yank 'd'
typeText("h") // Move left
assertState("ab${c}cdef")
typeText("P")
assertState("ab${c}dcdef")
typeText("u")
assertState("ab${c}cdef")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo after put word before cursor with oldundo`() {
configureByText("The ${c}quick brown fox")
try {
enterCommand("set oldundo")
typeText("yiw") // Yank "quick"
typeText("w") // Move to "brown"
assertState("The quick ${c}brown fox")
typeText("P")
assertState("The quick quic${c}kbrown fox")
typeText("u")
assertState("The quick ${c}brown fox")
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test multiple undo after sequential puts with oldundo`() {
configureByText("${c}Hello")
try {
enterCommand("set oldundo")
typeText("yy")
typeText("P")
assertState(
"""
${c}Hello
Hello
""".trimIndent()
)
typeText("P")
assertState(
"""
${c}Hello
Hello
Hello
""".trimIndent()
)
// Undo second put
typeText("u")
assertState(
"""
${c}Hello
Hello
""".trimIndent()
)
// Undo first put
typeText("u")
assertState(
"""
${c}Hello
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test undo put visual block with oldundo`() {
configureByText(
"""
${c}abc
def
ghi
""".trimIndent()
)
try {
enterCommand("set oldundo")
typeText("<C-V>jjl") // Visual block select first 2 columns of all lines
typeText("y")
typeText("$")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
typeText("P")
assertState(
"""
ab${c}abc
dedef
ghghi
""".trimIndent()
)
typeText("u")
assertState(
"""
ab${c}c
def
ghi
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -136,7 +136,6 @@ class MotionDownActionTest : VimTestCase() {
}
@Test
@Disabled
fun `test motion down in visual block mode with dollar motion2`() {
val keys = "i<C-O>d<ESC>"
val before = """

View File

@ -9,12 +9,10 @@
package org.jetbrains.plugins.ideavim.action.motion.updown
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class MotionUpCtrlPAction : VimTestCase() {
@Test
@Disabled("This one should be fixed")
fun `test last column empty`() {
val keys = "o<Esc><End><C-P>"
val before = """

View File

@ -132,9 +132,18 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before)
val vimEditor = editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(vimEditor, context, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
ApplicationManager.getApplication().invokeAndWait {
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(
vimEditor,
context,
vimEditor.primaryCaret(),
TextRange(16, 19),
SelectionType.CHARACTER_WISE,
false
)
}
}
typeText(commandToKeys("pu"))
val after = """
@ -170,9 +179,18 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before)
val vimEditor = editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(vimEditor, context, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
ApplicationManager.getApplication().invokeAndWait {
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(
vimEditor,
context,
vimEditor.primaryCaret(),
TextRange(16, 19),
SelectionType.CHARACTER_WISE,
false
)
}
}
typeText(commandToKeys("pu"))
val after = """
@ -209,9 +227,18 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before)
val vimEditor = editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(vimEditor, context, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
ApplicationManager.getApplication().invokeAndWait {
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(
vimEditor,
context,
vimEditor.primaryCaret(),
TextRange(16, 19),
SelectionType.CHARACTER_WISE,
false
)
}
}
typeText(commandToKeys("4pu"))
val after = """
@ -248,9 +275,18 @@ class MultipleCaretsTest : VimTestCase() {
val editor = configureByText(before)
val vimEditor = editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(vimEditor, context, vimEditor.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
ApplicationManager.getApplication().invokeAndWait {
ApplicationManager.getApplication().runWriteAction {
VimPlugin.getRegister()
.storeText(
vimEditor,
context,
vimEditor.primaryCaret(),
TextRange(16, 19),
SelectionType.CHARACTER_WISE,
false
)
}
}
typeText(commandToKeys("4pu"))
val after = """

View File

@ -202,4 +202,219 @@ class CopyCommandTest : VimTestCase() {
)
assertPluginError(false)
}
@Test
fun `test copy line and undo`() {
configureByText(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nunc tincidunt viverra ligula non ${c}scelerisque.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor.
""".trimIndent()
)
enterCommand("copy .")
assertState(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nunc tincidunt viverra ligula non scelerisque.
${c}Nunc tincidunt viverra ligula non scelerisque.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor.
""".trimIndent()
)
typeText("u")
assertState(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nunc tincidunt viverra ligula non ${c}scelerisque.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor.
""".trimIndent()
)
}
@Test
fun `test copy range and undo`() {
configureByText(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Morbi nec luctus tortor, id venenatis lacus.
Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
Ut id dapibus augue.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
""".trimIndent()
)
enterCommand("2,3copy $")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|""".trimMargin()
)
typeText("u")
assertState(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Morbi nec luctus tortor, id venenatis lacus.
Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
Ut id dapibus augue.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
""".trimIndent()
)
}
@Test
fun `test t command synonym and undo`() {
configureByText(
"""
Line 1
Line 2
Line ${c}3
Line 4
""".trimIndent()
)
enterCommand("t-1")
assertState(
"""
Line 1
Line 2
${c}Line 3
Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line 2
Line ${c}3
Line 4
""".trimIndent()
)
}
@Test
fun `test copy line and undo with oldundo`() {
configureByText(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nunc tincidunt viverra ligula non ${c}scelerisque.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor.
""".trimIndent()
)
try {
enterCommand("set oldundo")
enterCommand("copy .")
assertState(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nunc tincidunt viverra ligula non scelerisque.
${c}Nunc tincidunt viverra ligula non scelerisque.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor.
""".trimIndent()
)
typeText("u")
assertState(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nunc tincidunt viverra ligula non ${c}scelerisque.
Fusce sit amet mi ut purus volutpat vulputate vitae sed tortor.
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test copy range and undo with oldundo`() {
configureByText(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Morbi nec luctus tortor, id venenatis lacus.
Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
Ut id dapibus augue.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
""".trimIndent()
)
try {
enterCommand("set oldundo")
enterCommand("2,3copy $")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|""".trimMargin()
)
typeText("u")
assertState(
"""
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Morbi nec luctus tortor, id venenatis lacus.
Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
Ut id dapibus augue.
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test t command synonym and undo with oldundo`() {
configureByText(
"""
Line 1
Line 2
Line ${c}3
Line 4
""".trimIndent()
)
try {
enterCommand("set oldundo")
enterCommand("t-1")
assertState(
"""
Line 1
Line 2
${c}Line 3
Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line 2
Line ${c}3
Line 4
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -441,4 +441,233 @@ class DeleteLinesCommandTest : VimTestCase() {
"Ut id dapibus augue.^J"
)
}
@Test
fun `test delete line and undo`() {
configureByText(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus${c} augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
enterCommand("d")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
typeText("u")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus${c} augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete multiple lines with range and undo`() {
configureByText(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
enterCommand("2,4d")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
typeText("u")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete with count and undo`() {
configureByText(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
enterCommand("d 3")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
typeText("u")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test delete line and undo with oldundo`() {
configureByText(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus${c} augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
try {
enterCommand("set oldundo")
enterCommand("d")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
typeText("u")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus${c} augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test delete multiple lines with range and undo with oldundo`() {
configureByText(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
try {
enterCommand("set oldundo")
enterCommand("2,4d")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
typeText("u")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel ${c}purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test delete with count and undo with oldundo`() {
configureByText(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
try {
enterCommand("set oldundo")
enterCommand("d 3")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
typeText("u")
assertState(
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|${c}Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -251,4 +251,207 @@ class JoinLinesCommandTest : VimTestCase() {
""".trimIndent(),
)
}
@Test
fun `test join lines and undo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
enterCommand("j")
assertState(
"""
Line 1
${c}Line 2 Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
}
@Test
fun `test join range and undo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
Line 4
Line 5
""".trimIndent()
)
enterCommand("2,4j")
assertState(
"""
Line 1
${c}Line 2 Line 3 Line 4
Line 5
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
Line 4
Line 5
""".trimIndent()
)
}
@Test
fun `test join with count and undo`() {
configureByText(
"""
${c}Lorem ipsum dolor sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
enterCommand("j 3")
assertState(
"""
${c}Lorem ipsum dolor sit amet, consectetur adipiscing elit Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}Lorem ipsum dolor sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
fun `test join lines and undo with oldundo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
try {
enterCommand("set oldundo")
enterCommand("j")
assertState(
"""
Line 1
${c}Line 2 Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test join range and undo with oldundo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
Line 4
Line 5
""".trimIndent()
)
try {
enterCommand("set oldundo")
enterCommand("2,4j")
assertState(
"""
Line 1
${c}Line 2 Line 3 Line 4
Line 5
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
Line 4
Line 5
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test join with count and undo with oldundo`() {
configureByText(
"""
${c}Lorem ipsum dolor sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
try {
enterCommand("set oldundo")
enterCommand("j 3")
assertState(
"""
${c}Lorem ipsum dolor sit amet, consectetur adipiscing elit Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText("u")
assertState(
"""
${c}Lorem ipsum dolor sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -14,6 +14,42 @@ import org.junit.jupiter.api.Test
@Suppress("SpellCheckingInspection")
class MoveCommandTest : VimTestCase() {
@Test
fun `test move line up and undo`() {
configureByText(
"""
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
See, nothing.
""".trimIndent(),
)
enterCommand("m 0")
assertState(
"""
${c}For example: homework, homework, homework, homework, homework, homework, homework, homework, homework.
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
See, nothing.
""".trimIndent(),
)
typeText("u")
assertState(
"""
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
See, nothing.
""".trimIndent(),
)
}
@Test
fun `test selection marks after moving line up`() {
configureByText(
@ -337,4 +373,45 @@ class MoveCommandTest : VimTestCase() {
enterCommand("set nostartofline")
}
}
@Test
fun `test move line up and undo with oldundo`() {
configureByText(
"""
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
See, nothing.
""".trimIndent(),
)
try {
enterCommand("set oldundo")
enterCommand("m 0")
assertState(
"""
${c}For example: homework, homework, homework, homework, homework, homework, homework, homework, homework.
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
See, nothing.
""".trimIndent(),
)
typeText("u")
assertState(
"""
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
See, nothing.
""".trimIndent(),
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -211,4 +211,250 @@ class NormalCommandTest : VimTestCase() {
enterCommand("""exe "norm ^dei-\<C-R>\"-"""")
assertState("""-myprop-: "my value"""")
}
@Test
fun `test normal command delete and undo`() {
configureByText(
"""
|Line 1
|Line ${c}2
|Line 3
""".trimMargin()
)
enterCommand("normal x")
assertState(
"""
|Line 1
|Line
|Line 3
""".trimMargin()
)
typeText("u")
assertState(
"""
|Line 1
|Line ${c}2
|Line 3
""".trimMargin()
)
}
@Test
fun `test normal command with range and undo`() {
configureByText(
"""
|First ${c}line
|Second line
|Third line
|Fourth line
""".trimMargin()
)
enterCommand("2,3normal A!")
assertState(
"""
|First line
|Second line!
|Third line${c}!
|Fourth line
""".trimMargin()
)
typeText("u")
assertState(
"""
|First ${c}line
|Second line
|Third line
|Fourth line
""".trimMargin()
)
}
@Test
fun `test normal command insert and undo`() {
configureByText(
"""
|${c}Hello world
""".trimMargin()
)
enterCommand("normal iTest ")
assertState(
"""
|Test${c} Hello world
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}Hello world
""".trimMargin()
)
}
@Test
fun `test normal command complex operation and undo`() {
configureByText(
"""
|Line ${c}1
|Line 2
|Line 3
""".trimMargin()
)
enterCommand("normal ddp")
assertState(
"""
|Line 2
|${c}Line 1
|Line 3
""".trimMargin()
)
typeText("u")
assertState(
"""
|Line ${c}1
|Line 2
|Line 3
""".trimMargin()
)
}
@Test
fun `test normal command delete and undo with oldundo`() {
configureByText(
"""
|Line 1
|Line ${c}2
|Line 3
""".trimMargin()
)
try {
enterCommand("set oldundo")
enterCommand("normal x")
assertState(
"""
|Line 1
|Line
|Line 3
""".trimMargin()
)
typeText("u")
assertState(
"""
|Line 1
|Line ${c}2
|Line 3
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test normal command with range and undo with oldundo`() {
configureByText(
"""
|First ${c}line
|Second line
|Third line
|Fourth line
""".trimMargin()
)
try {
enterCommand("set oldundo")
enterCommand("2,3normal A!")
assertState(
"""
|First line
|Second line!
|Third line${c}!
|Fourth line
""".trimMargin()
)
typeText("u")
assertState(
"""
|First ${c}line
|Second line
|Third line
|Fourth line
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test normal command insert and undo with oldundo`() {
configureByText(
"""
|${c}Hello world
""".trimMargin()
)
try {
enterCommand("set oldundo")
enterCommand("normal iTest ")
assertState(
"""
|Test${c} Hello world
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}Hello world
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test normal command complex operation and undo with oldundo`() {
configureByText(
"""
|Line ${c}1
|Line 2
|Line 3
""".trimMargin()
)
try {
enterCommand("set oldundo")
enterCommand("normal ddp")
assertState(
"""
|Line 2
|${c}Line 1
|Line 3
""".trimMargin()
)
typeText("u")
assertState(
"""
|Line ${c}1
|Line 2
|Line 3
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.ex.implementation.commands
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class PrintLineNumberTest : VimTestCase() {
@Test
fun `test print last line number`() {
configureByLines(10, "Lorem ipsum dolor sit amet")
enterCommand("=")
assertStatusLineMessageContains("10")
}
@Test
fun `test print current line number`() {
configureByLines(10, "Lorem ipsum dolor sit amet")
typeText("4j")
enterCommand(".=")
assertStatusLineMessageContains("5")
}
@Test
fun `test print specific line number`() {
configureByLines(10, "Lorem ipsum dolor sit amet")
enterCommand("7=")
assertStatusLineMessageContains("7")
}
@Test
fun `test print line number of last part of range`() {
configureByLines(10, "Lorem ipsum dolor sit amet")
enterCommand("1,5=")
assertStatusLineMessageContains("5")
}
@Test
fun `test trailing characters raises an error`() {
configureByLines(10, "Lorem ipsum dolor sit amet")
enterCommand("=foo")
assertPluginError(true)
assertPluginErrorMessageContains("E488: Trailing characters: foo")
}
@Test
fun `test # flag prints line content and number`() {
configureByText("""
|Lorem ipsum dolor sit amet
|...consectetur adipiscing elit
|Maecenas efficitur nec odio vel malesuada
""".trimMargin().dotToTab())
enterCommand("2=#")
assertStatusLineMessageContains("2 \t\t\tconsectetur adipiscing elit")
}
@Test
fun `test l flag prints line content as printable string and number`() {
configureByText("""
|Lorem ipsum dolor sit amet
|...consectetur adipiscing elit
|Maecenas efficitur nec odio vel malesuada
""".trimMargin().dotToTab())
enterCommand("2=l")
assertStatusLineMessageContains("2 ^I^I^Iconsectetur adipiscing elit")
}
@Test
fun `test l and p flags print line content as printable string and number`() {
configureByText("""
|Lorem ipsum dolor sit amet
|...consectetur adipiscing elit
|Maecenas efficitur nec odio vel malesuada
""".trimMargin().dotToTab())
enterCommand("2=lp")
assertStatusLineMessageContains("2 ^I^I^Iconsectetur adipiscing elit")
}
@Test
fun `test p flag prints line content and number`() {
configureByText("""
|Lorem ipsum dolor sit amet
|...consectetur adipiscing elit
|Maecenas efficitur nec odio vel malesuada
""".trimMargin().dotToTab())
enterCommand("2=p")
assertStatusLineMessageContains("2 \t\t\tconsectetur adipiscing elit")
}
@Test
fun `test p and # flag prints line content and number`() {
configureByText("""
|Lorem ipsum dolor sit amet
|...consectetur adipiscing elit
|Maecenas efficitur nec odio vel malesuada
""".trimMargin().dotToTab())
enterCommand("2=p#")
assertStatusLineMessageContains("2 \t\t\tconsectetur adipiscing elit")
}
}

View File

@ -37,4 +37,205 @@ class PutCommandTest : VimTestCase() {
typeText(commandToKeys("put"))
assertState("Hello World!\n" + "<caret>Hello \n")
}
@Test
fun `test put and undo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
""".trimIndent()
)
typeText(injector.parser.parseKeys("yy"))
enterCommand("put")
assertState(
"""
Line 1
Line 2
${c}Line 2
Line 3
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
""".trimIndent()
)
}
@Test
fun `test put from register and undo`() {
configureByText(
"""
First line
Second ${c}line
Third line
""".trimIndent()
)
typeText(injector.parser.parseKeys("\"ayy"))
enterCommand("put a")
assertState(
"""
First line
Second line
${c}Second line
Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
Second ${c}line
Third line
""".trimIndent()
)
}
@Test
fun `test put with line number and undo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
typeText(injector.parser.parseKeys("yy"))
enterCommand("1put")
assertState(
"""
Line 1
${c}Line 2
Line 2
Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
}
@Test
fun `test put and undo with oldundo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
""".trimIndent()
)
typeText(injector.parser.parseKeys("yy"))
try {
enterCommand("set oldundo")
enterCommand("put")
assertState(
"""
Line 1
Line 2
${c}Line 2
Line 3
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test put from register and undo with oldundo`() {
configureByText(
"""
First line
Second ${c}line
Third line
""".trimIndent()
)
typeText(injector.parser.parseKeys("\"ayy"))
try {
enterCommand("set oldundo")
enterCommand("put a")
assertState(
"""
First line
Second line
${c}Second line
Third line
""".trimIndent()
)
typeText("u")
assertState(
"""
First line
Second ${c}line
Third line
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test put with line number and undo with oldundo`() {
configureByText(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
typeText(injector.parser.parseKeys("yy"))
try {
enterCommand("set oldundo")
enterCommand("1put")
assertState(
"""
Line 1
${c}Line 2
Line 2
Line 3
Line 4
""".trimIndent()
)
typeText("u")
assertState(
"""
Line 1
Line ${c}2
Line 3
Line 4
""".trimIndent()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -110,7 +110,7 @@ class RegistersCommandTest : VimTestCase() {
assertExOutput(
"""
|Type Name Content
| c "a ^IHello World^M^[
| c "a ^IHello World^J^[
""".trimMargin(),
)
}

View File

@ -839,4 +839,292 @@ class SortCommandTest : VimTestCase() {
typeText(commandToKeys("sort"))
assertState(" a\n b\n c\n whatever\n zee")
}
@Test
fun `test sort and undo`() {
configureByText(
"""
|zebra
|${c}apple
|banana
|cherry
""".trimMargin()
)
typeText(commandToKeys("sort"))
assertState(
"""
|${c}apple
|banana
|cherry
|zebra
""".trimMargin()
)
typeText("u")
assertState(
"""
|zebra
|${c}apple
|banana
|cherry
""".trimMargin()
)
}
@Test
fun `test sort with range and undo`() {
configureByText(
"""
|header
|zebra
|${c}apple
|banana
|cherry
|footer
""".trimMargin()
)
typeText(commandToKeys("2,5sort"))
assertState(
"""
|header
|${c}apple
|banana
|cherry
|zebra
|footer
""".trimMargin()
)
typeText("u")
assertState(
"""
|header
|zebra
|${c}apple
|banana
|cherry
|footer
""".trimMargin()
)
}
@Test
fun `test sort with options and undo`() {
configureByText(
"""
|${c}10
|2
|100
|20
""".trimMargin()
)
typeText(commandToKeys("sort n"))
assertState(
"""
|${c}2
|10
|20
|100
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}10
|2
|100
|20
""".trimMargin()
)
}
@Test
fun `test reverse sort and undo`() {
configureByText(
"""
|${c}apple
|banana
|cherry
|date
""".trimMargin()
)
typeText(commandToKeys("sort!"))
assertState(
"""
|${c}date
|cherry
|banana
|apple
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}apple
|banana
|cherry
|date
""".trimMargin()
)
}
@Test
fun `test sort and undo with oldundo`() {
configureByText(
"""
|zebra
|${c}apple
|banana
|cherry
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("sort"))
assertState(
"""
|${c}apple
|banana
|cherry
|zebra
""".trimMargin()
)
typeText("u")
assertState(
"""
|zebra
|${c}apple
|banana
|cherry
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test sort with range and undo with oldundo`() {
configureByText(
"""
|header
|zebra
|${c}apple
|banana
|cherry
|footer
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("2,5sort"))
assertState(
"""
|header
|${c}apple
|banana
|cherry
|zebra
|footer
""".trimMargin()
)
typeText("u")
assertState(
"""
|header
|zebra
|${c}apple
|banana
|cherry
|footer
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test sort with options and undo with oldundo`() {
configureByText(
"""
|${c}10
|2
|100
|20
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("sort n"))
assertState(
"""
|${c}2
|10
|20
|100
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}10
|2
|100
|20
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@Test
fun `test reverse sort and undo with oldundo`() {
configureByText(
"""
|${c}apple
|banana
|cherry
|date
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("sort!"))
assertState(
"""
|${c}date
|cherry
|banana
|apple
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}apple
|banana
|cherry
|date
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -17,6 +17,7 @@ import org.jetbrains.plugins.ideavim.impl.OptionTest
import org.jetbrains.plugins.ideavim.impl.TraceOptions
import org.jetbrains.plugins.ideavim.impl.VimOption
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
/**
* @author Alex Plate
@ -1410,4 +1411,294 @@ class SubstituteCommandTest : VimTestCase() {
"a fooBar",
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute and undo`() {
configureByText(
"""
|Hello ${c}world
|Hello world
|Hello world
""".trimMargin()
)
typeText(commandToKeys("s/world/universe/"))
assertState(
"""
|${c}Hello universe
|Hello world
|Hello world
""".trimMargin()
)
typeText("u")
assertState(
"""
|Hello ${c}world
|Hello world
|Hello world
""".trimMargin()
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute global and undo`() {
configureByText(
"""
|${c}Hello world world world
|Hello world
""".trimMargin()
)
typeText(commandToKeys("s/world/universe/g"))
assertState(
"""
|${c}Hello universe universe universe
|Hello world
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}Hello world world world
|Hello world
""".trimMargin()
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute with range and undo`() {
configureByText(
"""
|First line
|${c}Hello world
|Hello world
|Hello world
|Last line
""".trimMargin()
)
typeText(commandToKeys("2,4s/world/universe/"))
assertState(
"""
|First line
|Hello universe
|Hello universe
|${c}Hello universe
|Last line
""".trimMargin()
)
typeText("u")
assertState(
"""
|First line
|${c}Hello world
|Hello world
|Hello world
|Last line
""".trimMargin()
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute all lines and undo`() {
configureByText(
"""
|${c}Hello world
|Hello world
|Hello world
""".trimMargin()
)
typeText(commandToKeys("%s/world/universe/"))
assertState(
"""
|Hello universe
|Hello universe
|${c}Hello universe
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}Hello world
|Hello world
|Hello world
""".trimMargin()
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute and undo with oldundo`() {
configureByText(
"""
|Hello ${c}world
|Hello world
|Hello world
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("s/world/universe/"))
assertState(
"""
|${c}Hello universe
|Hello world
|Hello world
""".trimMargin()
)
typeText("u")
assertState(
"""
|Hello ${c}world
|Hello world
|Hello world
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute global and undo with oldundo`() {
configureByText(
"""
|${c}Hello world world world
|Hello world
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("s/world/universe/g"))
assertState(
"""
|${c}Hello universe universe universe
|Hello world
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}Hello world world world
|Hello world
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute with range and undo with oldundo`() {
configureByText(
"""
|First line
|${c}Hello world
|Hello world
|Hello world
|Last line
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("2,4s/world/universe/"))
assertState(
"""
|First line
|Hello universe
|Hello universe
|${c}Hello universe
|Last line
""".trimMargin()
)
typeText("u")
assertState(
"""
|First line
|${c}Hello world
|Hello world
|Hello world
|Last line
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test substitute all lines and undo with oldundo`() {
configureByText(
"""
|${c}Hello world
|Hello world
|Hello world
""".trimMargin()
)
try {
enterCommand("set oldundo")
typeText(commandToKeys("%s/world/universe/"))
assertState(
"""
|Hello universe
|Hello universe
|${c}Hello universe
""".trimMargin()
)
typeText("u")
assertState(
"""
|${c}Hello world
|Hello world
|Hello world
""".trimMargin()
)
} finally {
enterCommand("set nooldundo")
}
}
}

View File

@ -50,4 +50,4 @@ class RegisterVariableTest : VimTestCase() {
assertEquals("ab", register.text)
}
}
}

View File

@ -39,7 +39,7 @@ class ExecutableTextRangesTests : VimTestCase() {
val scriptString = """
set rnu
if 1
-0=a " some line that parser cannot recognize (it should be ignored)
-0§a " some line that parser cannot recognize (it should be ignored)
let y = 76
endif
""".trimIndent()
@ -61,7 +61,7 @@ class ExecutableTextRangesTests : VimTestCase() {
oh, hi Mark
" ideavim ignore end
if 1
-0=a " some line that parser cannot recognize (it should be ignored)
-0§a " some line that parser cannot recognize (it should be ignored)
let y = 76
endif
""".trimIndent()
@ -74,4 +74,4 @@ class ExecutableTextRangesTests : VimTestCase() {
val letCommand = ifStatement.conditionToBody.first().second.first()
assertEquals(TextRange(149, 162), letCommand.rangeInScript)
}
}
}

View File

@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import com.maddyhome.idea.vim.vimscript.parser.errors.IdeavimErrorListener
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.productForArguments
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.params.ParameterizedTest
import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
@ -191,7 +190,6 @@ class FunctionDeclarationTests : VimTestCase() {
// https://youtrack.jetbrains.com/issue/VIM-2654
@Test
@Disabled
fun `return with omitted expression`() {
VimscriptParser.parse(
"""

View File

@ -0,0 +1,34 @@
/*
* Copyright 2003-2025 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.register
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class RegisterMacroTest : VimTestCase() {
@Test
fun `test that macros work correctly with arrows`() {
doTest(
"qa\$as<esc><down>as<esc>q<down>@a", // Register a macro that adds s at the end of two lines and reruns it
"""
1
2
3
4
""".trimMargin(),
"""
1s
2s
3s
4s
""".trimMargin()
)
}
}

View File

@ -16,7 +16,6 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInfo
import java.nio.file.Files
@ -44,33 +43,6 @@ class ReloadVimRcTest : VimTestCase() {
kotlin.test.assertTrue(VimRcFileState.equalTo(document))
}
// TODO
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
@Test
@Disabled
fun `test equalTo with whitespaces`() {
val s = " " // Just to see whitespaces in the following code
"""
map x y
set myPlugin
map z t
""".trimIndent()
"""
map x y
set myPlugin$s$s$s$s$s$s
map z t
""".trimIndent()
// val lines = convertFileToLines(origFile)
// VimRcFileState.saveFileState("", lines)
//
// val document = editorFactory.createDocument(changedFile)
//
// assertTrue(VimRcFileState.equalTo(document))
}
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
@Test
fun `test equalTo with whitespaces and comments`() {

View File

@ -11,7 +11,6 @@ import com.intellij.application.options.CodeStyle
import com.intellij.ide.ClipboardSynchronizer
import com.intellij.ide.bookmark.BookmarksManager
import com.intellij.ide.highlighter.XmlFileType
import com.intellij.json.JsonFileType
import com.intellij.lang.Language
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
@ -284,8 +283,6 @@ abstract class VimTestCase {
protected fun configureByText(content: String) = configureByText(PlainTextFileType.INSTANCE, content)
protected fun configureByXmlText(content: String) = configureByText(XmlFileType.INSTANCE, content)
protected fun configureByJsonText(@Suppress("SameParameterValue") content: String) =
configureByText(JsonFileType.INSTANCE, content)
protected fun configureAndGuard(content: String) {
val ranges = extractBrackets(content)

View File

@ -32,7 +32,7 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
intellijPlatform {
// Snapshots don't use installers
@ -59,12 +59,9 @@ tasks {
useJUnitPlatform()
// Set teamcity env variable locally to run additional tests for leaks.
// By default, this test runs on TC only, but this test doesn't take a lot of time,
// so we can turn it on for local development
if (environment["TEAMCITY_VERSION"] == null) {
println("Set env TEAMCITY_VERSION to X to enable project leak checks from the platform")
environment("TEAMCITY_VERSION" to "X")
}
println("Project leak checks: If you experience project leaks on TeamCity that doesn't reproduce locally")
println("Uncomment the following line in build.gradle to enable leak checks (see build.gradle config)")
// environment("TEAMCITY_VERSION" to "X")
}
}

View File

@ -202,4 +202,121 @@ class ShiftLeftCommandTest : VimJavaTestCase() {
""".trimMargin()
assertState(after)
}
@Test
fun `test shift left and undo`() {
configureByJavaText(
"""
|public class Example {
| public static void main(String[] args) {
| System.out.println("Hello");
| ${c}System.out.println("World");
| }
|}
""".trimMargin()
)
enterCommand("<")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| System.out.println("Hello");
| ${c}System.out.println("World");
| }
|}
""".trimMargin()
)
typeText("u")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| System.out.println("Hello");
| ${c}System.out.println("World");
| }
|}
""".trimMargin()
)
}
@Test
fun `test shift left with range and undo`() {
configureByJavaText(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| System.out.println("!");
| }
|}
""".trimMargin()
)
enterCommand("3,5<")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| System.out.println("Hello");
| System.out.println("World");
| ${c}System.out.println("!");
| }
|}
""".trimMargin()
)
typeText("u")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| System.out.println("!");
| }
|}
""".trimMargin()
)
}
@Test
fun `test shift left with count and undo`() {
configureByJavaText(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| }
|}
""".trimMargin()
)
enterCommand("< 2")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| System.out.println("Hello");
| ${c}System.out.println("World");
| }
|}
""".trimMargin()
)
typeText("u")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| }
|}
""".trimMargin()
)
}
}

View File

@ -182,4 +182,121 @@ class ShiftRightCommandTest : VimJavaTestCase() {
""".trimMargin()
assertState(after)
}
@Test
fun `test shift right and undo`() {
configureByJavaText(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| }
|}
""".trimMargin()
)
enterCommand(">")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| }
|}
""".trimMargin()
)
typeText("u")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| }
|}
""".trimMargin()
)
}
@Test
fun `test shift right with range and undo`() {
configureByJavaText(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| System.out.println("!");
| }
|}
""".trimMargin()
)
enterCommand("3,5>")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| System.out.println("Hello");
| System.out.println("World");
| ${c}System.out.println("!");
| }
|}
""".trimMargin()
)
typeText("u")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| System.out.println("!");
| }
|}
""".trimMargin()
)
}
@Test
fun `test shift right with count and undo`() {
configureByJavaText(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| }
|}
""".trimMargin()
)
enterCommand("> 2")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| System.out.println("Hello");
| ${c}System.out.println("World");
| }
|}
""".trimMargin()
)
typeText("u")
assertState(
"""
|public class Example {
| public static void main(String[] args) {
| ${c}System.out.println("Hello");
| System.out.println("World");
| }
|}
""".trimMargin()
)
}
}

View File

@ -25,7 +25,7 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
intellijPlatform {
// Snapshots don't use installers

View File

@ -25,7 +25,7 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.0")
intellijPlatform {
// Snapshots don't use installers

View File

@ -15,7 +15,7 @@ val javaVersion: String by project
val remoteRobotVersion: String by project
dependencies {
testFixturesImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
testFixturesImplementation("org.junit.jupiter:junit-jupiter:5.13.0")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testFixturesImplementation(testFixtures(project(":"))) // The root project

View File

@ -45,13 +45,13 @@ afterEvaluate {
}
dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.12.2")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.13.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.0")
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
// Can be removed when IJPL-159134 is fixed
// testRuntimeOnly("junit:junit:4.13.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.12.2")
testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.13.0")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")

View File

@ -123,6 +123,7 @@ command:
| FILE | EXIT | E_LOWERCASE | EDIT_FILE | DUMP_LINE | DIGRAPH | DEL_MARKS | D_LOWERCASE | DEL_LINES | DELCMD
| T_LOWERCASE | COPY | CMD_CLEAR | BUFFER_LIST | BUFFER_CLOSE | B_LOWERCASE | BUFFER | ASCII
| ACTIONLIST | ACTION | LOCKVAR | UNLOCKVAR | PACKADD | TABMOVE
| ASSIGN // `:=` print last line number
)
bangModifier = BANG?
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR)) (NEW_LINE | BAR)*

View File

@ -104,15 +104,32 @@ class KeyHandler {
*/
fun handleKey(editor: VimEditor, key: KeyStroke, context: ExecutionContext, keyState: KeyHandlerState) {
commandListener.forEach { it() }
handleKey(editor, key, context, allowKeyMappings = true, mappingCompleted = false, keyState)
handleKey(editor, key, context, allowKeyMappings = true, keyState)
}
/**
* Handling input keys with additional parameters
*
* @param allowKeyMappings If we allow key mappings or not
* @param mappingCompleted No longer used
*/
fun handleKey(
editor: VimEditor,
key: KeyStroke,
context: ExecutionContext,
allowKeyMappings: Boolean,
keyState: KeyHandlerState,
) {
val result = processKey(key, editor, allowKeyMappings, KeyProcessResult.SynchronousKeyProcessBuilder(keyState))
if (result is KeyProcessResult.Executable) {
result.execute(editor, context)
}
}
@Deprecated(
"Use `handleKey(editor, key, context, allowKeyMappings, keyState)` instead.",
replaceWith = ReplaceWith("handleKey(editor, key, context, allowKeyMappings, keyState)")
)
@ApiStatus.ScheduledForRemoval
fun handleKey(
editor: VimEditor,
key: KeyStroke,
@ -121,10 +138,7 @@ class KeyHandler {
mappingCompleted: Boolean,
keyState: KeyHandlerState,
) {
val result = processKey(key, editor, allowKeyMappings, KeyProcessResult.SynchronousKeyProcessBuilder(keyState))
if (result is KeyProcessResult.Executable) {
result.execute(editor, context)
}
handleKey(editor, key, context, allowKeyMappings, keyState)
}
/**
@ -255,12 +269,11 @@ class KeyHandler {
return
}
}
if (injector.application.isMainThread()) {
val action: Runnable = ActionRunner(editor, context, command, keyState, operatorArguments)
val cmdAction = command.action
val name = cmdAction.id
injector.actionExecutor.executeCommand(editor, action, name, action)
}
val action: Runnable = ActionRunner(editor, context, command, keyState, operatorArguments)
val cmdAction = command.action
val name = cmdAction.id
injector.actionExecutor.executeCommand(editor, action, name, action)
}
/**
@ -386,8 +399,6 @@ class KeyHandler {
@JvmStatic
fun getInstance(): KeyHandler = instance
}
data class MutableBoolean(var value: Boolean)
}
/**

View File

@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
class RedoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
class UndoAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change
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.VimChangeGroup
@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
/**
* @author vlan
*/
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL])
@CommandOrMotion(keys = [], modes = [])
class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE

View File

@ -8,7 +8,6 @@
package com.maddyhome.idea.vim.action.change.change
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.VimChangeGroup
@ -22,7 +21,7 @@ import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
/**
* @author vlan
*/
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL])
@CommandOrMotion(keys = [], modes = [])
class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.CHANGE

View File

@ -69,15 +69,10 @@ class InsertRegisterAction : VimActionHandler.SingleExecution() {
*/
@RWLockLabel.SelfSynchronized
private fun insertRegister(editor: VimEditor, context: ExecutionContext, key: Char): Boolean {
val register: Register? = injector.registerGroup.getRegister(editor, context, key)
val register: Register? = injector.registerGroup.getRegister(key)
if (register != null) {
val textData = PutData.TextData(
register.name,
injector.clipboardManager.dumbCopiedText(register.text),
SelectionType.CHARACTER_WISE
)
val putData =
PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
val textData = PutData.TextData(register.text, SelectionType.CHARACTER_WISE, emptyList(), register.name)
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = true)
injector.put.putText(editor, context, putData)
return true
}

View File

@ -10,7 +10,6 @@ package com.maddyhome.idea.vim.action.copy
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.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Argument
@ -36,33 +35,40 @@ sealed class PutTextBaseAction(
val count = operatorArguments.count1
val sortedCarets = editor.sortedCarets()
return if (sortedCarets.size > 1) {
val caretToPutData = sortedCarets.associateWith { getPutDataForCaret(editor, context, it, count) }
val putData = getPutData(count)
val splitText = putData.textData?.rawText?.split('\n')?.dropLastWhile(String::isEmpty)
val caretToPutData = if (splitText != null && splitText.size == sortedCarets.size) {
sortedCarets.mapIndexed { index, caret -> caret to putData.copy(textData = putData.textData.copy(rawText = splitText[splitText.lastIndex - index])) }.toMap()
} else {
sortedCarets.associateWith { putData }
}
var result = true
caretToPutData.forEach {
result = injector.put.putTextForCaret(editor, it.key, context, it.value) && result
}
result
} else {
val putData = getPutDataForCaret(editor, context, sortedCarets.single(), count)
injector.put.putText(editor, context, putData)
injector.put.putText(editor, context, getPutData(count))
}
}
private fun getPutDataForCaret(
editor: VimEditor,
context: ExecutionContext,
caret: ImmutableVimCaret,
count: Int,
private fun getPutData(count: Int,
): PutData {
val registerService = injector.registerGroup
val registerChar = if (caret.editor.carets().size == 1) {
registerService.currentRegister
} else {
registerService.getCurrentRegisterForMulticaret()
}
val register = caret.registerStorage.getRegister(editor, context, registerChar)
val textData = register?.let { TextData(register) }
return PutData(textData, null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
return PutData(getRegisterTextData(), null, count, insertTextBeforeCaret, indent, caretAfterInsertedText, -1)
}
}
fun getRegisterTextData(): TextData? {
val register = injector.registerGroup.getRegister(injector.registerGroup.currentRegister)
return register?.let {
TextData(
register.text ?: injector.parser.toPrintableString(register.keys),
register.type,
register.transferableData,
register.name,
)
}
}

Some files were not shown because too many files have changed in this diff Show More