1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

..

100 Commits

Author SHA1 Message Date
1acf5d682d Set plugin version to chylex-51 2025-07-20 10:11:27 +02:00
f2832f7dc2 Preserve visual mode after executing IDE action 2025-07-20 10:11:27 +02:00
fca7b518b3 Make g0/g^/g$ work with soft wraps 2025-07-19 19:47:02 +02:00
8aa4178449 Make gj/gk jump over soft wraps 2025-07-19 19:47:02 +02:00
2e035f1260 Make camelCase motions adjust based on direction of visual selection 2025-07-19 19:47:02 +02:00
c5f17a68f5 Make search highlights temporary 2025-07-19 19:47:02 +02:00
68b7788fe4 Exit insert mode after refactoring 2025-07-19 19:47:02 +02:00
3299059ab9 Add action to run last macro in all opened files 2025-07-19 19:47:02 +02:00
548ed30b5b Stop macro execution after a failed search 2025-07-19 19:47:02 +02:00
b171ccb96c Revert per-caret registers 2025-07-19 19:47:02 +02:00
93affef6d3 Apply scrolloff after executing native IDEA actions 2025-07-19 19:47:02 +02:00
0b788153cd Stay on same line after reindenting 2025-07-19 19:47:02 +02:00
70d8167e17 Update search register when using f/t 2025-07-19 19:47:02 +02:00
67c3dec51b Automatically add unambiguous imports after running a macro 2025-07-19 19:47:02 +02:00
3dab706f37 Fix(VIM-3986): Exception when pasting register contents containing new line 2025-07-19 19:47:02 +02:00
5228bca65e Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2025-07-19 19:46:34 +02:00
853a208eba Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2025-07-19 19:46:34 +02:00
fd9297edb1 Add support for count for visual and line motion surround 2025-07-19 19:46:34 +02:00
e01e4d8ecd Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2025-07-19 19:46:34 +02:00
b304692c4e Fix(VIM-696): Restore visual mode after undo/redo, and disable incompatible actions 2025-07-19 19:46:33 +02:00
05b9f44a0b Respect count with <Action> mappings 2025-07-19 15:32:32 +02:00
d878b119c4 Change matchit plugin to use HTML patterns in unrecognized files 2025-07-19 15:32:32 +02:00
def57128b0 Reset insert mode when switching active editor 2025-07-19 15:32:32 +02:00
fd4422bf95 Remove notifications about configuration options 2025-07-19 15:32:32 +02:00
d10a0c4ee7 Set custom plugin version 2025-07-19 15:32:30 +02:00
Alex Plate
f12b0b04f6 Fix the contribution name of Jakub 2025-07-18 16:06:45 +03:00
Alex Plate
ea4fc85e5b Add a new plugin for the verification 2025-07-18 16:04:00 +03:00
Alex Plate
4af8fc1868 Update TC configuration to run only needed tests 2025-07-18 16:04:00 +03:00
Alex Plate
1482ac0335 Fix(VIM-3970): Get rid of VimStandalonePluginUpdateChecker 2025-07-18 16:03:59 +03:00
IdeaVim Bot
79168b00f3 Add zuberol to contributors list 2025-07-18 09:03:36 +00:00
zuberol
07990847c6 Merge pull request #1223 from JetBrains/feat/VIM-3791-nerdtree-gg-G-jumps
Feat(VIM-3791): support for "G" and "gg" motions inside the NERDtree
2025-07-17 11:44:07 +02:00
dependabot[bot]
8c40e19c44 Bump org.mockito.kotlin:mockito-kotlin from 5.4.0 to 6.0.0
Bumps [org.mockito.kotlin:mockito-kotlin](https://github.com/mockito/mockito-kotlin) from 5.4.0 to 6.0.0.
- [Release notes](https://github.com/mockito/mockito-kotlin/releases)
- [Commits](https://github.com/mockito/mockito-kotlin/compare/5.4.0...v6.0.0)

---
updated-dependencies:
- dependency-name: org.mockito.kotlin:mockito-kotlin
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-16 19:15:13 +03:00
dependabot[bot]
371769c508 Bump io.ktor:ktor-client-cio from 3.2.1 to 3.2.2
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 3.2.1 to 3.2.2.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.2.1...3.2.2)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-cio
  dependency-version: 3.2.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-16 19:14:55 +03:00
dependabot[bot]
7ee34d0b27 Bump io.ktor:ktor-client-cio from 3.2.0 to 3.2.1
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 3.2.0 to 3.2.1.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.2.0...3.2.1)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-cio
  dependency-version: 3.2.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-09 18:57:15 +03:00
dependabot[bot]
d1ec7d617d Bump org.junit.jupiter:junit-jupiter-engine from 5.13.2 to 5.13.3
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit-framework) from 5.13.2 to 5.13.3.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.2...r5.13.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-09 18:56:55 +03:00
dependabot[bot]
898fd0537d Bump org.junit.jupiter:junit-jupiter from 5.13.2 to 5.13.3
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework) from 5.13.2 to 5.13.3.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.2...r5.13.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-09 18:56:22 +03:00
Xinhe Wang
353603b546 Fix(VIM-3266): Set FileSaveCloseAction to OTHER_SELF_SYNCHRONIZED 2025-07-07 10:55:53 +03:00
Xinhe Wang
2f7f0dcacb Fix(VIM-3044): correct the behavior of I in (linewise) Visual mode
For visual selections spanning multiple lines, keep caret position
if it's on the first line. Otherwise move the caret to the start of
the first selected line.
2025-07-07 10:55:30 +03:00
Xinhe Wang
af9023af4b Rename insertBeforeCursor and insertAfterCursor helpers
The terminology used in IntelliJ is "caret".
2025-07-07 10:55:30 +03:00
Xinhe Wang
c393c902b2 Fix(VIM-2375): do not save file with ZQ
ZQ is defined to `Quit without checking for changes (same as ":q!").`
2025-07-04 14:26:56 +03:00
Xinhe Wang
c355cb7ed7 Make VimChangeGroup::changeCaseMotion not accept non-Motion argument
Error is logged if `ChangeCaseMotion` actions receive non-`Motion` args
2025-07-03 17:25:38 +03:00
Xinhe Wang
0803a1c195 Fix(VIM-2413): correct the range of line-wise case change commands
The start of the range is the leftmost non-whitespace character
OR the current position, whichever is closer to the left.
2025-07-03 17:25:38 +03:00
Xinhe Wang
5208412b46 Refine Argument.Motion::isLineWiseMotion and VimMotionGroupBase::getMotionRange
This does not change any actual behavior.
2025-07-03 17:25:38 +03:00
IdeaVim Bot
78c463cf7b Add Xinhe Wang to contributors list 2025-07-03 09:02:01 +00:00
dependabot[bot]
8f5a44bf44 Bump org.junit.vintage:junit-vintage-engine from 5.13.1 to 5.13.2
Bumps [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit-framework) from 5.13.1 to 5.13.2.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.1...r5.13.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-07-02 19:19:55 +03:00
Xinhe Wang
2377408028 Fix(VIM-2142): support gU and gu in VISUAL mode
Note that this implementation assumes that the 'gU' / 'gu' command in
visual mode is equivalent to 'U' / 'u'. While 'v_gU' and 'v_gu' are not
explicitly documented in Vim help, we treat these commands as identical
based on observed behavior, without examining Vim's source code.
2025-07-02 18:56:29 +03:00
Alex Plate
246425b1fb Mark IdeaVim as a plugin that supports vim configuration
In this way, we'll be able to actively promote IdeaVim as a suggested plugin in the IDE.

GO-17806
2025-06-27 19:43:08 +03:00
Jakub
4eadfc1fba feat: support for "G" and "gg" motions inside the NERDtree 2025-06-27 13:51:44 +02:00
dependabot[bot]
d3c945cd6d Bump org.junit.jupiter:junit-jupiter-api from 5.13.1 to 5.13.2
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit-framework) from 5.13.1 to 5.13.2.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.1...r5.13.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-25 19:35:31 +03:00
dependabot[bot]
2ac46129ac Bump org.junit.jupiter:junit-jupiter-params from 5.13.1 to 5.13.2
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit-framework) from 5.13.1 to 5.13.2.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.1...r5.13.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-25 19:14:44 +03:00
dependabot[bot]
c8d40be1ce Bump org.junit.jupiter:junit-jupiter from 5.13.1 to 5.13.2
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit-framework) from 5.13.1 to 5.13.2.
- [Release notes](https://github.com/junit-team/junit-framework/releases)
- [Commits](https://github.com/junit-team/junit-framework/compare/r5.13.1...r5.13.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-25 19:14:31 +03:00
dependabot[bot]
97159a33fe Bump org.jetbrains.kotlin:kotlin-stdlib from 2.1.21 to 2.2.0
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 2.1.21 to 2.2.0.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v2.1.21...v2.2.0)

---
updated-dependencies:
- dependency-name: org.jetbrains.kotlin:kotlin-stdlib
  dependency-version: 2.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-25 19:13:44 +03:00
Alex Plate
861d585102 Also, specify new task names in build scripts 2025-06-25 18:43:01 +03:00
Alex Plate
126925b4eb Fix long running and property tests
Because of some changes, if we define the test using `testIde` registering, they're not executed properly and don't work.
Now, we don't exclude these tests from the main test execution, so they have to be excluded explicitly
2025-06-25 18:33:50 +03:00
Alex Plate
9302c0a057 Use intellij.spellchecker module for 2025.2+ builds of IJ 2025-06-25 17:35:46 +03:00
Alex Plate
ddea72f803 Update kotlin version to 2.2.0
This is required to support IJ 2025.2 platform, which uses 2.2.0 for compilation.
2025-06-25 17:23:54 +03:00
Alex Plate
e991aa922c Try to increase timeout for UI tests for Rider 2025-06-24 17:24:56 +03:00
dependabot[bot]
5ffaa7b084 Bump org.junit.jupiter:junit-jupiter-api from 5.13.0 to 5.13.1
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.13.0 to 5.13.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 19:10:53 +03:00
dependabot[bot]
0d4183129d 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.1.202505142326-r to 7.3.0.202506031305-r.
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v7.2.1.202505142326-r...v7.3.0.202506031305-r)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 19:10:13 +03:00
dependabot[bot]
8a7fbac389 Bump org.junit.jupiter:junit-jupiter-params from 5.13.0 to 5.13.1
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit5) from 5.13.0 to 5.13.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 18:53:18 +03:00
dependabot[bot]
dbab006f83 Bump io.ktor:ktor-client-content-negotiation from 3.1.3 to 3.2.0
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 3.1.3 to 3.2.0.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/3.1.3...3.2.0)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-client-content-negotiation
  dependency-version: 3.2.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 18:52:47 +03:00
dependabot[bot]
3149de7b73 Bump org.junit.vintage:junit-vintage-engine from 5.13.0 to 5.13.1
Bumps [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit5) from 5.13.0 to 5.13.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-11 18:30:27 +03:00
dependabot[bot]
28a71f0e09 Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.1.21-2.0.1 to 2.1.21-2.0.2.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.1.21-2.0.1...2.1.21-2.0.2)

---
updated-dependencies:
- dependency-name: com.google.devtools.ksp:symbol-processing-api
  dependency-version: 2.1.21-2.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-11 18:30:20 +03:00
dependabot[bot]
fc7d4e614b Bump org.junit.jupiter:junit-jupiter from 5.13.0 to 5.13.1
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.13.0 to 5.13.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-11 18:30:12 +03:00
dependabot[bot]
5b1aade876 Bump org.junit.jupiter:junit-jupiter-engine from 5.13.0 to 5.13.1
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.13.0 to 5.13.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.13.0...r5.13.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-11 18:29:52 +03: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
114 changed files with 7427 additions and 946 deletions

4
.gitignore vendored
View File

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

View File

@@ -5,7 +5,7 @@
<option name="executionName" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="externalSystemIdString" value="GRADLE" />
<option name="scriptParameters" value="" />
<option name="scriptParameters" value="-x :tests:property-tests:test -x :tests:long-running-tests:test" />
<option name="taskDescriptions">
<list />
</option>
@@ -19,6 +19,7 @@
<ExternalSystemDebugServerProcess>true</ExternalSystemDebugServerProcess>
<ExternalSystemReattachDebugProcess>true</ExternalSystemReattachDebugProcess>
<DebugAllEnabled>false</DebugAllEnabled>
<RunAsTest>false</RunAsTest>
<method v="2" />
</configuration>
</component>

View File

@@ -45,6 +45,7 @@ object Compatibility : IdeaVimBuildType({
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.functiontextobj' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.miksuki.HighlightCursor' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.ugarosa.idea.edgemotion' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}cn.mumukehao.plugin' [latest-IU] -team-city
""".trimIndent()
}
}

View File

@@ -25,7 +25,7 @@ object LongRunning : IdeaVimBuildType({
steps {
gradle {
tasks = "clean :tests:long-running-tests:testLongRunning"
tasks = "clean :tests:long-running-tests:test"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -39,7 +39,7 @@ object Nvim : IdeaVimBuildType({
""".trimIndent()
}
gradle {
tasks = "clean test -Dnvim"
tasks = "clean test -x :tests:property-tests:test -x :tests:long-running-tests:test -Dnvim"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -25,7 +25,7 @@ object PropertyBased : IdeaVimBuildType({
steps {
gradle {
clearConditions()
tasks = "clean :tests:property-tests:testPropertyBased"
tasks = "clean :tests:property-tests:test"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -115,7 +115,7 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
}
script {
name = "Run tests"
scriptContent = "./gradlew test"
scriptContent = "./gradlew test -x :tests:property-tests:test -x :tests:long-running-tests:test"
}
gradle {
name = "Publish release"

View File

@@ -40,7 +40,7 @@ open class TestingBuildType(
steps {
gradle {
clearConditions()
tasks = "clean test"
tasks = "clean test -x :tests:property-tests:test -x :tests:long-running-tests:test"
buildFile = ""
enableStacktrace = true
jdkHome = "/usr/lib/jvm/java-21-amazon-corretto"

View File

@@ -15,7 +15,7 @@ object GitHub : Project({
name = "Pull Requests checks"
description = "Automatic checking of GitHub Pull Requests"
buildType(GithubBuildType("clean test", "Tests"))
buildType(GithubBuildType("clean test -x :tests:property-tests:test -x :tests:long-running-tests:test", "Tests"))
})
class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({

View File

@@ -610,6 +610,18 @@ 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
* [![icon][mail]](mailto:xinhe.wang@jetbrains.com)
[![icon][github]](https://github.com/wxh06)
&nbsp;
Xinhe Wang
* [![icon][mail]](mailto:zuber.kuba@gmail.com)
[![icon][github]](https://github.com/zuberol)
&nbsp;
Jakub Zuber
Previous contributors:

View File

@@ -65,7 +65,7 @@ We've prepared some useful configurations for you:
And here are useful gradle commands:
* `./gradlew runIde` — start the dev version of IntelliJ IDEA with IdeaVim installed.
* `./gradlew test` — run tests.
* `./gradlew test -x :tests:property-tests:test -x :tests:long-running-tests:test` — run tests.
* `./gradlew buildPlugin` — build the plugin. The result will be located in `build/distributions`. This file can be
installed by using `Settings | Plugin | >Gear Icon< | Install Plugin from Disk...`. You can stay with your personal build
for a few days or send it to a friend for testing.

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

@@ -8,7 +8,7 @@
plugins {
kotlin("jvm")
kotlin("plugin.serialization") version "2.0.21"
kotlin("plugin.serialization") version "2.2.0"
}
val kotlinxSerializationVersion: String by project
@@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.21-2.0.1")
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.21-2.0.2")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib")

View File

@@ -35,6 +35,8 @@ import org.intellij.markdown.ast.impl.ListCompositeNode
import org.jetbrains.changelog.Changelog
import org.jetbrains.intellij.platform.gradle.TestFrameworkType
import org.jetbrains.intellij.platform.gradle.tasks.aware.SplitModeAware
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
import org.kohsuke.github.GHUser
import java.net.HttpURLConnection
import java.net.URL
@@ -46,19 +48,19 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.21")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:2.2.0")
classpath("com.github.AlexPl292:mark-down-to-slack:1.1.2")
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.3.0.202506031305-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.1.3")
classpath("io.ktor:ktor-client-cio:3.1.3")
classpath("io.ktor:ktor-client-auth:3.1.3")
classpath("io.ktor:ktor-client-content-negotiation:3.1.3")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.1.3")
classpath("io.ktor:ktor-client-core:3.2.2")
classpath("io.ktor:ktor-client-cio:3.2.2")
classpath("io.ktor:ktor-client-auth:3.2.2")
classpath("io.ktor:ktor-client-content-negotiation:3.2.2")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.2.2")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@@ -67,19 +69,19 @@ buildscript {
plugins {
java
kotlin("jvm") version "2.0.21"
kotlin("jvm") version "2.2.0"
application
id("java-test-fixtures")
// 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"
id("com.dorongold.task-tree") version "4.0.1"
id("com.google.devtools.ksp") version "2.0.21-1.0.25"
id("com.google.devtools.ksp") version "2.2.0-2.0.2"
}
val moduleSources by configurations.registering
@@ -139,7 +141,12 @@ 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")
// VERSION UPDATE: This module is required since 2025.2
if (ideaVersion == "LATEST-EAP-SNAPSHOT") {
bundledModule("intellij.spellchecker")
}
}
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
@@ -159,19 +166,19 @@ dependencies {
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:6.0.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.3")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.3")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.13.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.13.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.13.3")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.13.3")
// 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.2")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
}
@@ -199,12 +206,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)
@@ -224,38 +228,6 @@ tasks {
options.encoding = "UTF-8"
}
compileKotlin {
kotlinOptions {
jvmTarget = javaVersion
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions
apiVersion = "2.0"
freeCompilerArgs = listOf(
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true
}
}
compileTestKotlin {
enabled = false
kotlinOptions {
jvmTarget = javaVersion
apiVersion = "2.0"
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
freeCompilerArgs += listOf("-Xskip-prerelease-check", "-Xallow-unstable-dependencies")
// allWarningsAsErrors = true
}
}
// Note that this will run the plugin installed in the IDE specified in dependencies. To run in a different IDE, use
// a custom task (see below)
runIde {
@@ -329,6 +301,23 @@ kotlin {
jvmToolchain {
languageVersion.set(JavaLanguageVersion.of(javaVersion))
}
compilerOptions {
jvmTarget.set(JvmTarget.fromTarget(javaVersion))
// See https://plugins.jetbrains.com/docs/intellij/using-kotlin.html#kotlin-standard-library
// For the list of bundled versions
apiVersion.set(KotlinVersion.KOTLIN_2_0)
freeCompilerArgs = listOf(
"-Xjvm-default=all-compatibility",
// Needed to compile the AceJump which uses kotlin beta
// Without these two option compilation fails
"-Xskip-prerelease-check",
"-Xallow-unstable-dependencies",
)
// allWarningsAsErrors = true
}
}
gradle.projectsEvaluated {

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-51
javaVersion=21
remoteRobotVersion=0.11.23
antlrVersion=4.10.1
@@ -28,7 +28,7 @@ antlrVersion=4.10.1
# Please don't forget to update kotlin version in buildscript section
# Also update kotlinxSerializationVersion version
kotlinVersion=2.0.21
kotlinVersion=2.2.0
publishToken=token
publishChannels=eap

View File

@@ -20,27 +20,25 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.21")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.2.0")
implementation("io.ktor:ktor-client-core:3.1.3")
implementation("io.ktor:ktor-client-cio:3.1.3")
implementation("io.ktor:ktor-client-content-negotiation:3.1.3")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.3")
implementation("io.ktor:ktor-client-auth:3.1.3")
implementation("io.ktor:ktor-client-core:3.2.2")
implementation("io.ktor:ktor-client-cio:3.2.2")
implementation("io.ktor:ktor-client-content-negotiation:3.2.2")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.2.2")
implementation("io.ktor:ktor-client-auth:3.2.2")
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.3.0.202506031305-r")
implementation("com.vdurmont:semver4j:3.1.0")
}
val releaseType: String? by project
tasks {
compileKotlin {
kotlinOptions {
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
}
kotlin {
compilerOptions {
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
}
}

View File

@@ -45,6 +45,8 @@ val knownPlugins = setOf(
"com.julienphalip.ideavim.functiontextobj", // https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
"com.miksuki.HighlightCursor", // https://plugins.jetbrains.com/plugin/26743-highlightcursor
"com.ugarosa.idea.edgemotion", // https://plugins.jetbrains.com/plugin/27211-edgemotion
"cn.mumukehao.plugin",
)
suspend fun main() {

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

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

View File

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

View File

@@ -0,0 +1,67 @@
package com.maddyhome.idea.vim.action.macro
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.command.impl.FinishMarkAction
import com.intellij.openapi.command.impl.StartMarkAction
import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.KeyHandler
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.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@CommandOrMotion(keys = ["z@"], modes = [Mode.NORMAL])
class PlaybackRegisterInOpenFilesAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override val argumentType: Argument.Type = Argument.Type.CHARACTER
private val playbackRegisterAction = PlaybackRegisterAction()
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
val argument = cmd.argument as? Argument.Character ?: return false
val project = editor.ij.project ?: return false
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return false
val register = argument.character.let { if (it == '@') injector.macro.lastRegister else it }
val commandName = "Execute Macro '$register' in All Open Files"
val action = Runnable {
CommandProcessor.getInstance().markCurrentCommandAsGlobal(project)
for (textEditor in fileEditorManager.allEditors.filterIsInstance<TextEditor>()) {
fileEditorManager.openFile(textEditor.file, true)
val editor = textEditor.editor
val vimEditor = editor.vim
vimEditor.mode = com.maddyhome.idea.vim.state.mode.Mode.NORMAL()
KeyHandler.Companion.getInstance().reset(vimEditor)
val startMarkAction = StartMarkAction.start(editor, project, commandName)
playbackRegisterAction.execute(vimEditor, context, cmd, operatorArguments)
FinishMarkAction.finish(project, editor, startMarkAction)
}
}
CommandProcessor.getInstance()
.executeCommand(project, action, commandName, null, UndoConfirmationPolicy.REQUEST_CONFIRMATION)
return true
}
}

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

@@ -455,6 +455,17 @@ internal class NerdTree : VimExtension {
tree.scrollRowToVisible(expectedRow)
},
)
registerCommand("gg", NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
tree.setSelectionRow(0)
tree.scrollRowToVisible(0)
})
registerCommand("G", NerdAction.Code { project, _, _ ->
val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
val lastRowIndex = tree.rowCount -1
tree.setSelectionRow(lastRowIndex)
tree.scrollRowToVisible(lastRowIndex)
})
registerCommand(
"NERDTreeMapJumpNextSibling",
"<C-J>",

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

@@ -134,7 +134,7 @@ internal object IdeaSelectionControl {
is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType)
is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType)
is Mode.INSERT -> VimPlugin.getChange()
.insertBeforeCursor(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim))
.insertBeforeCaret(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim))
is Mode.NORMAL -> Unit
else -> error("Unexpected mode: $mode")

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

@@ -25,15 +25,19 @@ import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.util.NlsContexts
import com.intellij.refactoring.actions.BaseRefactoringAction
import com.maddyhome.idea.vim.RegisterActions
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction
import com.maddyhome.idea.vim.api.VimActionExecutor
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.handler.EditorActionHandlerBase
import com.maddyhome.idea.vim.newapi.IjNativeAction
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inVisualMode
import org.jetbrains.annotations.NonNls
import java.awt.Component
import javax.swing.JComponent
@@ -70,6 +74,12 @@ internal class IjActionExecutor : VimActionExecutor {
thisLogger().error("Actions cannot be updated when write-action is running or pending")
}
val startVisualModeType = (editor?.mode as? Mode.VISUAL)?.selectionType
val startVisualCaretSelection = if (editor != null && startVisualModeType != null && action.action !is BaseRefactoringAction)
editor.primaryCaret().let { Triple(it.offset, it.selectionStart, it.selectionEnd) }
else
null
val ijAction = (action as IjNativeAction).action
try {
isRunningActionFromVim = true
@@ -79,6 +89,20 @@ internal class IjActionExecutor : VimActionExecutor {
val place = ijAction.choosePlace()
val res = ActionManager.getInstance().tryToExecute(ijAction, null, contextComponent, place, true)
res.waitFor(5_000)
if (startVisualModeType != null && startVisualCaretSelection != null) {
val primaryCaret = editor.primaryCaret()
val endVisualCaretOffset = primaryCaret.offset
if (startVisualCaretSelection.first != endVisualCaretOffset) {
if (!editor.inVisualMode || (editor.mode as Mode.VISUAL).selectionType != startVisualModeType) {
injector.visualMotionGroup.toggleVisual(editor, 1, 0, startVisualModeType)
}
primaryCaret.moveToOffset(startVisualCaretSelection.first)
primaryCaret.setSelection(startVisualCaretSelection.second, startVisualCaretSelection.third)
primaryCaret.moveToOffset(endVisualCaretOffset)
}
}
return res.isDone
} finally {
isRunningActionFromVim = false

View File

@@ -61,7 +61,7 @@ class IJEditorFocusListener : EditorListener {
val switchToInsertMode = Runnable {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
VimPlugin.getChange().insertBeforeCursor(editor, context)
VimPlugin.getChange().insertBeforeCaret(editor, context)
KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, true)
}
if (isCurrentEditorTerminal) {

View File

@@ -169,7 +169,7 @@ internal object IdeaSpecifics {
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
VimPlugin.getChange().insertBeforeCaret(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
}
}
@@ -223,7 +223,7 @@ internal object IdeaSpecifics {
// Enable insert mode if there is no selection in template
// Template with selection is handled by [com.maddyhome.idea.vim.group.visual.VisualMotionGroup.controlNonVimSelectionChange]
if (editor.vim.inNormalMode) {
VimPlugin.getChange().insertBeforeCursor(
VimPlugin.getChange().insertBeforeCaret(
editor.vim,
injector.executionContextManager.getEditorExecutionContext(editor.vim),
)

View File

@@ -20,6 +20,7 @@ import com.intellij.openapi.editor.ex.ScrollingModelEx
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.editor.impl.CaretModelImpl
import com.intellij.openapi.editor.impl.EditorImpl
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vfs.VirtualFileManager
import com.maddyhome.idea.vim.api.BufferPosition
import com.maddyhome.idea.vim.api.ExecutionContext
@@ -35,8 +36,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
@@ -150,7 +151,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
}
}
}
editor.document.insertString(atPosition, text)
editor.document.insertString(atPosition, StringUtil.convertLineSeparators(text, "\n"))
}
override fun replaceString(start: Int, end: Int, newString: String) {
@@ -514,6 +515,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

@@ -135,6 +135,8 @@
<editorNotificationProvider
implementation="com.maddyhome.idea.vim.troubleshooting.AccidentalInstallDetectorEditorNotificationProvider"/>
<dependencySupport coordinate="configuration" kind="vim" displayName="IdeaVim"/>
</extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>
@@ -142,12 +144,10 @@
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
<actions resource-bundle="messages.IdeaVimBundle">
<group id="com.chylex.intellij.vim" text="Vim" popup="true">
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">
<add-to-group group-id="ToolsMenu" anchor="last"/>
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
</group>
</action>
<!-- Internal -->
<!--suppress PluginXmlI18n -->
<action id="VimInternalAddBlockInlays" class="com.maddyhome.idea.vim.action.internal.AddBlockInlaysAction" text="Add Test Block Inlays | IdeaVim Internal" internal="true"/>

View File

@@ -78,5 +78,10 @@
"keys": "gJ",
"class": "com.maddyhome.idea.vim.action.change.delete.DeleteJoinVisualLinesAction",
"modes": "X"
},
{
"keys": "z@",
"class": "com.maddyhome.idea.vim.action.macro.PlaybackRegisterInOpenFilesAction",
"modes": "N"
}
]

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
import kotlin.test.assertNotNull
@@ -1145,6 +1144,12 @@ $c tw${c}o
"O${c}NcE thIs ${c}TEXt wIlL n${c}Ot lOoK s${c}O rIdIcuLoUs\n",
)
assertState("O${c}nce this text will n${c}ot look s${c}o ridiculous\n")
typeTextInFile(
injector.parser.parseKeys("v2wgu"),
"O${c}NcE thIs ${c}TEXt wIlL n${c}Ot lOoK s${c}O rIdIcuLoUs\n",
)
assertState("O${c}nce this text will n${c}ot look s${c}o ridiculous\n")
}
@Test
@@ -1181,6 +1186,12 @@ $c tw${c}o
"O${c}NcE thIs ${c}TEXt wIlL N${c}Ot lOoK S${c}O rIdIcuLoUs\n",
)
assertState("O${c}NCE THIS TEXT WILL N${c}OT LOOK S${c}O RIDICULOUS\n")
typeTextInFile(
injector.parser.parseKeys("v2wgU"),
"O${c}NcE thIs ${c}TEXt wIlL N${c}Ot lOoK S${c}O rIdIcuLoUs\n",
)
assertState("O${c}NCE THIS TEXT WILL N${c}OT LOOK S${c}O RIDICULOUS\n")
}
@Test
@@ -1822,7 +1833,6 @@ $c five six se${c}ven eight
)
}
@Disabled("Action execution in tests is broken for 2024.2")
@Test
fun testInsertNewLineAboveAction() {
typeTextInFile(
@@ -1849,7 +1859,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 +1873,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 +2193,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,296 @@ 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")
}
}
@Test
fun `test toggle case line caret position`() {
configureByText(" Hello ${c}World")
typeText("g~~")
assertState(" ${c}hELLO wORLD")
typeText("u")
assertState(" Hello ${c}World")
typeText("^g~~")
assertState(" ${c}hELLO wORLD")
typeText("u")
assertState(" ${c}Hello World")
typeText("hg~~")
assertState(" $c hELLO wORLD")
typeText("u")
assertState(" $c Hello World")
}
@Test
fun `test uppercase line caret position`() {
configureByText(" Hello ${c}World")
typeText("gUU")
assertState(" ${c}HELLO WORLD")
typeText("u")
assertState(" Hello ${c}World")
typeText("^gUU")
assertState(" ${c}HELLO WORLD")
typeText("u")
assertState(" ${c}Hello World")
typeText("hgUU")
assertState(" $c HELLO WORLD")
typeText("u")
assertState(" $c Hello World")
}
@Test
fun `test lowercase line caret position`() {
configureByText(" Hello ${c}World")
typeText("guu")
assertState(" ${c}hello world")
typeText("u")
assertState(" Hello ${c}World")
typeText("^guu")
assertState(" ${c}hello world")
typeText("u")
assertState(" ${c}Hello World")
typeText("hguu")
assertState(" $c hello world")
typeText("u")
assertState(" $c Hello World")
}
}

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

@@ -17,7 +17,7 @@ import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class VisualBlockInsertActionTest : VimTestCase() {
class VisualInsertActionTest : VimTestCase() {
// VIM-1379 |CTRL-V| |j| |v_b_I|
@TestWithoutNeovim(SkipNeovimReason.VISUAL_BLOCK_MODE)
@Test
@@ -101,28 +101,70 @@ class VisualBlockInsertActionTest : VimTestCase() {
)
}
@TestWithoutNeovim(SkipNeovimReason.VISUAL_BLOCK_MODE)
@Test
fun `test insert in non block mode`() {
doTest(
listOf("vwIHello<esc>"),
"""
${c}A Discovery
fun `test insert in non-block visual within single line`() {
val before = """
| A ${c}Discovery
${c}I found it in a legendary land
all rocks and ${c}lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent(),
"""
Hell${c}oA Discovery
| I ${c}found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
val after = """
|Hell${c}o A Discovery
Hell${c}oI found it in a legendary land
Hell${c}oall rocks and lavender and tufted grass,
where it was settled on some sodden sand
hard by the torrent of a mountain pass.
""".trimIndent(),
)
|Hell${c}o I found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
doTest(listOf($$"v$IHello<esc>"), before, after)
doTest(listOf("VIHello<esc>"), before, after)
}
@Test
fun `test insert in non-block visual spanning multiple lines down`() {
val before = """
| A ${c}Discovery
| I ${c}found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
val after = """
|Hell${c}o A Discovery
|Hell${c}o I found it in a legendary land
| all rocks and lavender and tufted grass,
| where it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
doTest(listOf("vjIHello<esc>"), before, after)
doTest(listOf("VjIHello<esc>"), before, after)
}
@Test
fun `test insert in non-block visual spanning multiple lines up`() {
val before = """
| A Discovery
| I found it in a legendary land
| all rocks and lavender and tufted grass${c},
| where it was settled on some sodden sand
| hard ${c}by the torrent of a mountain pass.
""".trimMargin()
val after = """
| A Discovery
| I found it in a legendary landHell${c}o
| all rocks and lavender and tufted grass,
| whereHell${c}o it was settled on some sodden sand
| hard by the torrent of a mountain pass.
""".trimMargin()
doTest(listOf("vkIHello<esc>"), before, after)
doTest(listOf("VkIHello<esc>"), before, after)
}
@TestWithoutNeovim(SkipNeovimReason.VISUAL_BLOCK_MODE)

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(),
)
}
@@ -241,12 +241,7 @@ class RegistersCommandTest : VimTestCase() {
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
injector.registerGroup.saveRegister(
vimEditor,
context,
'+',
Register('+', injector.clipboardManager.dumbCopiedText("Lorem ipsum dolor"), SelectionType.LINE_WISE)
)
injector.registerGroup.saveRegister(vimEditor, context, '+', Register('+', SelectionType.LINE_WISE, "Lorem ipsum dolor", mutableListOf()))
val clipboardContent = injector.clipboardManager.dumbCopiedText("clipboard content")
injector.clipboardManager.setClipboardContent(vimEditor, context, clipboardContent)
typeText("V<Esc>")
@@ -453,12 +448,7 @@ class RegistersCommandTest : VimTestCase() {
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val clipboardContent = injector.clipboardManager.dumbCopiedText("clipboard content")
injector.registerGroup.saveRegister(
vimEditor,
context,
'+',
Register('+', injector.clipboardManager.dumbCopiedText("Lorem ipsum dolor"), SelectionType.LINE_WISE)
)
injector.registerGroup.saveRegister(vimEditor, context, '+', Register('+', SelectionType.LINE_WISE, "Lorem ipsum dolor", mutableListOf()))
injector.clipboardManager.setClipboardContent(vimEditor, context, clipboardContent)
typeText("V<Esc>")

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()
@@ -74,4 +74,4 @@ class ExecutableTextRangesTests : VimTestCase() {
val letCommand = ifStatement.conditionToBody.first().second.first()
assertEquals(TextRange(149, 162), letCommand.rangeInScript)
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -32,7 +32,7 @@ dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testImplementation(testFixtures(project(":"))) // The root project
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2")
testImplementation("org.junit.vintage:junit-vintage-engine:5.13.2")
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

@@ -17,7 +17,7 @@ import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimJavaTestCase
import org.junit.jupiter.api.Test
class VisualBlockInsertActionJavaTest : VimJavaTestCase() {
class VisualInsertActionJavaTest : VimJavaTestCase() {
// VIM-1110 |CTRL-V| |v_b_i| |zc|
@TestWithoutNeovim(SkipNeovimReason.FOLDING)
@Test

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.2")
intellijPlatform {
// Snapshots don't use installers
@@ -47,17 +47,8 @@ tasks {
// I didn't find a better way to exclude except disabling and defining a new task with a different name
// Note that useJUnitTestPlatform() is required to prevent red code
test {
enabled = false
useJUnitPlatform()
}
// The `test` task is automatically set up with IntelliJ goodness. A custom test task needs to be configured for it
val testLongRunning by intellijPlatformTesting.testIde.registering {
task {
group = "verification"
useJUnitPlatform()
}
}
}
java {

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.2")
intellijPlatform {
// Snapshots don't use installers
@@ -48,15 +48,6 @@ tasks {
// I didn't find a better way to exclude except disabling and defining a new task with a different name
test {
useJUnitPlatform()
enabled = false
}
// The `test` task is automatically set up with IntelliJ goodness. A custom test task needs to be configured for it
val testPropertyBased by intellijPlatformTesting.testIde.registering {
task {
group = "verification"
useJUnitPlatform()
}
}
}

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.3")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
testFixturesImplementation(testFixtures(project(":"))) // The root project

View File

@@ -11,6 +11,7 @@ import com.intellij.remoterobot.data.RemoteComponent
import com.intellij.remoterobot.fixtures.CommonContainerFixture
import com.intellij.remoterobot.fixtures.DefaultXpath
import com.intellij.remoterobot.fixtures.FixtureName
import com.intellij.remoterobot.fixtures.JButtonFixture
import com.intellij.remoterobot.search.locators.byXpath
import java.time.Duration
@@ -28,6 +29,6 @@ class ManageLicensesFrame(remoteRobot: RemoteRobot, remoteComponent: RemoteCompo
/// Note: The license code is obfuscated, so we use the class `W`. But a better solution is required.
textFields(byXpath("//div[@class='W']")).first().text = System.getenv("RIDER_LICENSE")
button("Activate").click()
button("Close").click()
button(JButtonFixture.byText("Close"), timeout = Duration.ofSeconds(20)).click()
}
}

View File

@@ -10,8 +10,8 @@ plugins {
java
kotlin("jvm")
// id("org.jlleitschuh.gradle.ktlint")
id("com.google.devtools.ksp") version "2.0.21-1.0.25"
kotlin("plugin.serialization") version "2.0.21"
id("com.google.devtools.ksp") version "2.2.0-2.0.2"
kotlin("plugin.serialization") version "2.2.0"
`maven-publish`
antlr
}
@@ -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.3")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.13.3")
// 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.2")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
@@ -68,7 +68,7 @@ dependencies {
compileOnly(kotlin("reflect"))
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.mockito.kotlin:mockito-kotlin:6.0.0")
}
tasks {
@@ -88,13 +88,13 @@ tasks {
named("compileTestKotlin") {
dependsOn("generateTestGrammarSource")
}
}
compileKotlin {
kotlinOptions {
apiVersion = "2.0"
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
}
}
kotlin {
compilerOptions {
apiVersion.set(org.jetbrains.kotlin.gradle.dsl.KotlinVersion.KOTLIN_2_0)
freeCompilerArgs = listOf("-Xjvm-default=all-compatibility")
}
}
// --- Linting

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

@@ -18,10 +18,13 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.DuplicableOperatorAction
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@CommandOrMotion(keys = ["gu"], modes = [Mode.NORMAL])
class ChangeCaseLowerMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
private val logger = vimLogger<ChangeCaseLowerMotionAction>()
override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.MOTION
@@ -35,15 +38,18 @@ class ChangeCaseLowerMotionAction : ChangeEditorActionHandler.ForEachCaret(), Du
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
return argument != null &&
injector.changeGroup
.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.LOWER,
argument,
operatorArguments,
)
if (argument == null || argument !is Argument.Motion) {
logger.error("Argument is null or not Argument.Motion. argument=$argument")
return false
}
return injector.changeGroup.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.LOWER,
argument,
operatorArguments,
)
}
}

View File

@@ -18,10 +18,13 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.DuplicableOperatorAction
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@CommandOrMotion(keys = ["g~"], modes = [Mode.NORMAL])
class ChangeCaseToggleMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
private val logger = vimLogger<ChangeCaseToggleMotionAction>()
override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.MOTION
@@ -35,15 +38,18 @@ class ChangeCaseToggleMotionAction : ChangeEditorActionHandler.ForEachCaret(), D
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
return argument != null &&
injector.changeGroup
.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.TOGGLE,
argument,
operatorArguments,
)
if (argument == null || argument !is Argument.Motion) {
logger.error("Argument is null or not Argument.Motion. argument=$argument")
return false
}
return injector.changeGroup.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.TOGGLE,
argument,
operatorArguments,
)
}
}

View File

@@ -18,10 +18,13 @@ import com.maddyhome.idea.vim.command.Argument
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.DuplicableOperatorAction
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.diagnostic.vimLogger
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
@CommandOrMotion(keys = ["gU"], modes = [Mode.NORMAL])
class ChangeCaseUpperMotionAction : ChangeEditorActionHandler.ForEachCaret(), DuplicableOperatorAction {
private val logger = vimLogger<ChangeCaseUpperMotionAction>()
override val type: Command.Type = Command.Type.CHANGE
override val argumentType: Argument.Type = Argument.Type.MOTION
@@ -35,15 +38,18 @@ class ChangeCaseUpperMotionAction : ChangeEditorActionHandler.ForEachCaret(), Du
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
return argument != null &&
injector.changeGroup
.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.UPPER,
argument,
operatorArguments,
)
if (argument == null || argument !is Argument.Motion) {
logger.error("Argument is null or not Argument.Motion. argument=$argument")
return false
}
return injector.changeGroup.changeCaseMotion(
editor,
caret,
context,
VimChangeGroup.ChangeCaseType.UPPER,
argument,
operatorArguments,
)
}
}

View File

@@ -27,7 +27,7 @@ class InsertAfterCursorAction : ChangeEditorActionHandler.SingleExecution() {
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
injector.changeGroup.insertAfterCursor(editor, context)
injector.changeGroup.insertAfterCaret(editor, context)
return true
}
}

View File

@@ -47,5 +47,5 @@ private fun insertAtPreviousInsert(editor: VimEditor, context: ExecutionContext)
if (motion is Motion.AbsoluteOffset) {
caret.moveToOffset(motion.offset)
}
injector.changeGroup.insertBeforeCursor(editor, context)
injector.changeGroup.insertBeforeCaret(editor, context)
}

View File

@@ -29,7 +29,7 @@ class InsertBeforeCursorAction : ChangeEditorActionHandler.SingleExecution() {
argument: Argument?,
operatorArguments: OperatorArguments,
): Boolean {
injector.changeGroup.insertBeforeCursor(editor, context)
injector.changeGroup.insertBeforeCaret(editor, context)
return true
}
}

View File

@@ -13,6 +13,7 @@ import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeLine
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.group.visual.VimSelection
@@ -20,10 +21,17 @@ import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
import com.maddyhome.idea.vim.state.mode.SelectionType
/**
* @author vlan
* Handles the 'I' command in Visual mode.
*
* For (linewise) Visual mode, the caret positioning follows these rules (based on observation in Vim):
* - If text on multiple lines is selected AND the caret is on the first line (e.g., when selecting from bottom to top),
* the caret position remains unchanged
* - In all other cases, the caret is moved to the start of the first selected line
*
* For blockwise Visual mode, it initiates insert at the start of block on each line in the selection
*/
@CommandOrMotion(keys = ["I"], modes = [Mode.VISUAL])
class VisualBlockInsertAction : VisualOperatorActionHandler.SingleExecution() {
class VisualInsertAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.INSERT
override fun executeForAllCarets(
@@ -38,7 +46,17 @@ class VisualBlockInsertAction : VisualOperatorActionHandler.SingleExecution() {
return if (vimSelection.type == SelectionType.BLOCK_WISE) {
injector.changeGroup.initBlockInsert(editor, context, vimSelection.toVimTextRange(false), false)
} else {
injector.changeGroup.insertBeforeFirstNonBlank(editor, context)
// For visual selections spanning multiple lines, keep caret position if it's on the first line
// Otherwise move the caret to the start of the first selected line
for ((caret, selection) in caretsAndSelections) {
val range = selection.toVimTextRange()
val posStart = editor.offsetToBufferPosition(range.startOffset)
val nextLineStart = editor.getLineStartOffset(editor.normalizeLine(posStart.line + 1))
if (caret.offset >= nextLineStart || nextLineStart >= range.endOffset) {
caret.moveToOffset(injector.motion.moveCaretToLineStart(editor, posStart.line))
}
}
injector.changeGroup.insertBeforeCaret(editor, context)
true
}
}

View File

@@ -0,0 +1,32 @@
/*
* 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.action.file
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["ZQ"], modes = [Mode.NORMAL])
class FileCloseAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(
editor: VimEditor,
context: ExecutionContext,
cmd: Command,
operatorArguments: OperatorArguments,
): Boolean {
injector.file.closeFile(editor, context)
return true
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2003-2023 The IdeaVim authors
* 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
@@ -16,9 +16,9 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = ["ZQ", "ZZ"], modes = [Mode.NORMAL])
@CommandOrMotion(keys = ["ZZ"], modes = [Mode.NORMAL])
class FileSaveCloseAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_WRITABLE
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
override fun execute(
editor: VimEditor,

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

@@ -48,7 +48,7 @@ class SelectMotionArrowLeftAction : MotionActionHandler.ForEachCaret() {
editor.exitSelectModeNative(false)
if (editor.isTemplateActive()) {
logger.debug("Template is active. Activate insert mode")
injector.changeGroup.insertBeforeCursor(editor, context)
injector.changeGroup.insertBeforeCaret(editor, context)
if (caret.offset in startSelection..endSelection) {
return startSelection.toMotion()
}

View File

@@ -48,7 +48,7 @@ class SelectMotionArrowRightAction : MotionActionHandler.ForEachCaret() {
editor.exitSelectModeNative(false)
if (editor.isTemplateActive()) {
logger.debug("Template is active. Activate insert mode")
injector.changeGroup.insertBeforeCursor(editor, context)
injector.changeGroup.insertBeforeCaret(editor, context)
if (caret.offset in startSelection..endSelection) {
return endSelection.toMotion()
}

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