1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-07-06 00:04:32 +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
75 changed files with 6957 additions and 689 deletions

2
.gitignore vendored
View File

@ -33,3 +33,5 @@ vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
settings.xml
.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"
@ -139,7 +139,7 @@ dependencies {
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"))
@ -161,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")
}
@ -199,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)

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=chylex-48
version=chylex-49
javaVersion=21
remoteRobotVersion=0.11.23
antlrVersion=4.10.1

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

@ -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()'. */

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
@ -255,7 +254,7 @@ 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 {

View File

@ -44,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 {

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
@ -514,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

@ -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
@ -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)
@ -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(),
)
}
@ -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|
@ -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|
@ -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>|

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

@ -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()

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

@ -81,7 +81,7 @@ sealed class TillCharacterMotion(
else if (direction == Direction.FORWARDS) "s-1"
else "s+1"
injector.searchGroup.setLastSearchState(argument.character.let { if (it == '.') "\\." else it.toString() }, offset, direction)
injector.searchGroup.setLastSearchState(argument.character.let { if (it in "`^$.*[~/\\") "\\$it" else it.toString() }, offset, direction)
return res.toMotionOrError()
}

View File

@ -211,6 +211,7 @@ interface VimEditor {
fun createIndentBySize(size: Int): String
fun getFoldRegionAtOffset(offset: Int): VimFoldRegion?
fun getSoftWrapStartAtOffset(offset: Int): Int?
/**
* Mostly related to Fleet. After the editor is modified, the carets are modified. You can't use the old caret

View File

@ -280,8 +280,7 @@ internal object MappingProcessor : KeyConsumer {
// Replay the rest of the keys, with mapping applied, as though they were typed
unhandledKeys.subList(subsequence.size, unhandledKeys.size).forEach {
KeyHandler.getInstance()
.handleKey(editor, it, context, allowKeyMappings = true, mappingCompleted = false, keyState)
KeyHandler.getInstance().handleKey(editor, it, context, allowKeyMappings = true, keyState)
}
return
}
@ -296,7 +295,7 @@ internal object MappingProcessor : KeyConsumer {
// though they were typed.
val keyHandler = KeyHandler.getInstance()
unhandledKeys.forEachIndexed { index, it ->
keyHandler.handleKey(editor, it, context, allowKeyMappings = index != 0, mappingCompleted = false, keyState)
keyHandler.handleKey(editor, it, context, allowKeyMappings = index != 0, keyState)
}
}
}

View File

@ -13,6 +13,7 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretListener
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeOffset
import com.maddyhome.idea.vim.command.Argument
@ -226,7 +227,15 @@ sealed class MotionActionHandler : EditorActionHandlerBase(false) {
StrictMode.assert(caret.isPrimary, "Block selection mode must only operate on primary caret")
}
val normalisedOffset = prepareMoveToAbsoluteOffset(editor, cmd, offset)
val normalisedOffset = prepareMoveToAbsoluteOffset(editor, cmd, offset).let {
if (offset.intendedColumn == VimMotionGroupBase.LAST_COLUMN) {
val softWrapStart = editor.getSoftWrapStartAtOffset(it)
if (softWrapStart != null) softWrapStart - 1 else it
}
else {
it
}
}
StrictMode.assert(normalisedOffset == offset.offset, "Adjusted offset should be normalised by action")
// Set before moving, so it can be applied during move, especially important for LAST_COLUMN and visual block mode

View File

@ -102,7 +102,7 @@ class ToKeysMappingInfo(
while (keyHandler.keyStack.hasStroke()) {
val keyStroke = keyHandler.keyStack.feedStroke()
val allowKeyMappings = isRecursive && !(first && lhsIsPrefixOfRhs)
keyHandler.handleKey(editor, keyStroke, context, allowKeyMappings, false, keyState)
keyHandler.handleKey(editor, keyStroke, context, allowKeyMappings, keyState)
first = false
}
} finally {
@ -144,7 +144,7 @@ class ToExpressionMappingInfo(
for (keyStroke in toKeys) {
val allowKeyMappings = isRecursive && !(first && lhsIsPrefixOfRhs)
val keyHandler = KeyHandler.getInstance()
keyHandler.handleKey(editor, keyStroke, context, allowKeyMappings, false, keyState)
keyHandler.handleKey(editor, keyStroke, context, allowKeyMappings, keyState)
first = false
}
}

View File

@ -21,8 +21,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.common.LiveRange
import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
@ -788,6 +788,10 @@ class VimRegex(pattern: String) {
return null
}
override fun getSoftWrapStartAtOffset(offset: Int): Int? {
return null
}
override fun <T : ImmutableVimCaret> findLastVersionOfCaret(caret: T): T? {
return null
}

View File

@ -54,7 +54,7 @@ data class NormalCommand(val range: Range, val modifier: CommandModifier, val ar
val keyHandler = KeyHandler.getInstance()
keyHandler.reset(editor)
for (key in keys) {
keyHandler.handleKey(editor, key, context, useMappings, false, keyHandler.keyHandlerState)
keyHandler.handleKey(editor, key, context, useMappings, keyHandler.keyHandlerState)
}
// Exit if state leaves as insert or cmd_line

View File

@ -0,0 +1,62 @@
/*
* 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.vimscript.model.commands
import com.intellij.vim.annotations.ExCommand
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.ex.exExceptionMessage
import com.maddyhome.idea.vim.ex.ranges.Range
import com.maddyhome.idea.vim.helper.EngineStringHelper
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
@ExCommand(command = "=")
data class PrintLineNumberCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
Command.SingleExecution(range, modifier, argument) {
init {
defaultRange = "$"
}
override val argFlags: CommandHandlerFlags =
flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
override fun processCommand(
editor: VimEditor,
context: ExecutionContext,
operatorArguments: OperatorArguments,
): ExecutionResult {
if (argument.isNotEmpty() && argument[0] !in "l#p") {
throw exExceptionMessage("E488", argument) // E488: Trailing characters: $argument
}
val line1 = range.getLineRange(editor, editor.currentCaret()).endLine1
// `l` means output the line like `:list` - show unprintable chars, and include `^` and `$`
// `#` means output the line with the line number
// `p` means output the line like `:print`
// The flags can be combined, so `l#` means line number and `:list`. Normally, Vim displays this over two lines.
// Since we're outputting to the single line status bar, if any flags are specified, we treat it like `#` was
// specified - we always show line number.
val content = if (argument.isNotEmpty()) {
val text = editor.getLineText(line1 - 1)
if (argument.contains("l")) {
val keys = injector.parser.stringToKeys(text)
EngineStringHelper.toPrintableCharacters(keys)
}
else text
}
else ""
injector.messages.showStatusBarMessage(editor, "$line1 $content")
return ExecutionResult.Success
}
}

View File

@ -1,6 +1,7 @@
{
"&": "com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand",
"<": "com.maddyhome.idea.vim.vimscript.model.commands.ShiftLeftCommand",
"=": "com.maddyhome.idea.vim.vimscript.model.commands.PrintLineNumberCommand",
">": "com.maddyhome.idea.vim.vimscript.model.commands.ShiftRightCommand",
"@": "com.maddyhome.idea.vim.vimscript.model.commands.RepeatCommand",
"N[ext]": "com.maddyhome.idea.vim.vimscript.model.commands.PreviousFileCommand",

View File

@ -1,72 +0,0 @@
# Vimscript
## IdeaVim 1.7.0
- [x] expressions: binary, unary, ternary, function calls, sublists, options, registers, variables, lists, dictionaries
- [x] `if` condition
- [x] `for` and `while` loops
- [x] `try`/`catch`/`finally`
- [x] function definition (without flags)
- [x] `break`, `continue`, `throw`, `return`
- [x] scopes: `g:`, `s:`, `l:`, `a:`, `b:`, `w:`, `t:` (the `v:` scope is not supported yet)
- [x] `map <expr>`
### IdeaVim 1.8.0
- [x] move `s:` scoped variables to `Script` class
- [x] move `l:` and `a:` scoped variables to the `FunctionDeclaration` class
- [x] `closure` function flag
- [x] `..` as concatenation operator
- [x] falsy operator `??`
- [x] access dictionary value by `dict.key`
- [x] `abort` function flag
- [x] `range` function flag
- [x] `call` command
- [x] optional arguments `...`
- [x] funcref type
- [x] lambdas
- [x] function as method
- [x] `function` function
- [x] `funcref` function
- [x] `dict` function flag
- [x] anonymous functions
- [x] default value in functions e.g. `function F1(a, b = 10)`
- [x] `has("ide")` or "ide" option
- [x] reduce number of rules in grammar
- [x] classic package structure
### IdeaVim 1.9.0
- [x] support `for [a, b] in {expr}`
- [x] pass scopes to functions e.g. `for k in keys(s:)`
- [x] curly-braces-names
- [x] `finish` statement
- [x] pass Lists and Dictionaries by reference
- [x] variable locking
- [x] rewrite OptionManager to vim data types
- [x] scoped options
- [x] `normal` command
- [x] expression register (`<C-R>=`)
## Plans for the next releases:
### IdeaVim 1.10.0
- [ ] `Result` class that would store the exceptions
- [ ] throwing multiple exceptions at once
- [ ] exception wrapping in try/catch
- [ ] store exception messages in property file
- [ ] store vim exception stacktrace
### Pool of things that might be added soon
- [ ] executing context (script / command line) & better parent for executable
- [ ] classloading
- [ ] all the let command's cases (e.g. registers)
- [ ] vim "special" type
- [ ] `v:` scope
- [ ] update tests to JUnit 5
- [ ] rethink vimscript tests
- [ ] loggers
- [ ] todos, warnings
- [ ] expressions in substitute command (`\=`)
- [ ] vim status line and beautiful exceptions output
- [ ] improve `w:` and `t:` scopes
- [ ] context dependent parsing e.g. `dict.key`
- [ ] add `-range` option to `command` command
- [ ] better strings (e.g. `"\<Esc"`)