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

Compare commits

...

165 Commits

Author SHA1 Message Date
3cc9ce73b9
Set plugin version to chylex-47 2025-05-16 17:00:52 +02:00
9370fb7809
Disable key bindings when completion popups are active 2025-05-16 17:00:41 +02:00
a62a356dfb
Make gj/gk jump over soft wraps 2025-05-16 12:39:56 +02:00
5a25c53808
Make camelCase motions adjust based on direction of visual selection 2025-05-16 12:39:56 +02:00
19cd5bf53d
Make search highlights temporary 2025-05-16 12:39:56 +02:00
799a2271bd
Exit insert mode after refactoring 2025-05-16 12:39:56 +02:00
46314beaa7
Add action to run last macro in all opened files 2025-05-16 12:39:56 +02:00
aa0b299d1a
Stop macro execution after a failed search 2025-05-16 12:39:56 +02:00
b5ca0b57fe
Revert per-caret registers 2025-05-16 12:39:56 +02:00
be69de15b4
Apply scrolloff after executing native IDEA actions 2025-05-16 12:39:56 +02:00
d9bb4a581e
Stay on same line after reindenting 2025-05-16 12:39:56 +02:00
4f4ec08958
Update search register when using f/t 2025-05-16 12:39:55 +02:00
b4ed59b08f
Automatically add unambiguous imports after running a macro 2025-05-16 12:39:55 +02:00
c6174f4395
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2025-05-16 12:39:55 +02:00
b5416da9a4
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2025-05-16 12:39:55 +02:00
9474364910
Add support for count for visual and line motion surround 2025-05-16 12:39:55 +02:00
afb7d29871
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2025-05-16 12:39:55 +02:00
6afd363731
Fix(VIM-696) Restore visual mode after undo/redo, and disable incompatible actions 2025-05-16 12:39:55 +02:00
0880a78bfe
Respect count with <Action> mappings 2025-05-16 12:39:55 +02:00
e6c506811f
Change matchit plugin to use HTML patterns in unrecognized files 2025-05-16 12:39:55 +02:00
d3142f4574
Reset insert mode when switching active editor 2025-05-16 12:39:55 +02:00
bf54c86622
Remove notifications about configuration options 2025-05-16 12:06:25 +02:00
9aed3c4eb6
Remove update checker 2025-05-16 12:06:25 +02:00
cd3c40c855
Set custom plugin version 2025-05-16 12:06:25 +02:00
dependabot[bot]
ee70ecd92e Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.1.20-2.0.1 to 2.1.21-2.0.1.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.1.20-2.0.1...2.1.21-2.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 18:28:17 +03:00
dependabot[bot]
f3cf3f03b1 Bump org.jetbrains.kotlin:kotlin-stdlib from 2.1.20 to 2.1.21
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 2.1.20 to 2.1.21.
- [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.20...v2.1.21)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 18:28:01 +03:00
dependabot[bot]
4c46d734cc Bump io.ktor:ktor-client-core from 3.1.2 to 3.1.3
Bumps [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) from 3.1.2 to 3.1.3.
- [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.2...3.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-14 18:27:52 +03:00
Alex Plate
e932fe2059
Add com.ugarosa.idea.edgemotion as a dependant plugin 2025-05-14 12:02:06 +03:00
Alex Plate
d3f248d2f9
Refactor reset to avoid service initialization during the dispose 2025-05-09 19:04:10 +02:00
Alex Plate
f623c1eef9
Fix(VIM-3784): Integrate a notification that will warn new GoLand users that they use IdeaVim 2025-05-09 18:39:17 +02:00
Alex Plate
f03734e235
Fix(VIM-3786): Disable IdeaVim in the AI assistant 2025-05-09 16:31:44 +02:00
Alex Plate
85acdc2e24
Fix(VIM-3881): Execute undo/redo directly for the remote dev scenarios 2025-05-09 16:20:27 +02:00
Alex Plate
afa70e20e1
Correct the rider test 2025-05-09 15:27:43 +02:00
Alex Plate
73f3d328c5
Try to wait longer till Rider will start 2025-05-09 13:51:40 +02:00
Alex Plate
df3224b02d
Start Rider with printing the logs to the output 2025-05-09 13:34:51 +02:00
Alex Plate
0e56a4e86e
Remove background Rider start to see the problem when loading 2025-05-09 13:32:22 +02:00
Alex Plate
66ce949cc5
Do not use installer for Rider tests 2025-05-09 13:00:50 +02:00
Alex Plate
ab635972cc
Remove a lot of deprecated methods in IdeaVim 2025-05-09 12:58:47 +02:00
Alex Plate
7fbf321cbd
Do not use installer for Rider tests 2025-05-09 12:17:13 +02:00
Alex Plate
d4c24fcc7f
Remove the outdated file with implemented vimscript functions 2025-05-09 12:17:12 +02:00
Matt Ellis
fd5af31247 Report cannot track intention action
If an alt+enter intention is invoked from Search Everywhere, IdeaVim's Track Action ID shows "Cannot detect action ID" instead of explaining that there is no action ID.

Relates to VIM-2541
2025-05-09 12:11:11 +02:00
Matt Ellis
df74b75570 Stop switching fragment editors to Insert mode
Fixes VIM-1217
2025-05-09 12:10:10 +02:00
Vladimir Parfinenko
c74163e917 Fix case settings in replacement string, VIM-3510
^VIM-3510 fixed
2025-05-09 12:08:01 +02:00
Vladimir Parfinenko
d38b2885ba Fix missing backreferences in replacement string, VIM-3895
^VIM-3895 fixed
2025-05-09 12:08:01 +02:00
Vladimir Parfinenko
61666d1cfd Always print float numbers using period as a decimal separator, VIM-3894
^VIM-3894 fixed
2025-05-09 12:02:56 +02:00
Alex Plate
35ddb21fe0
Fix the deprecated method use 2025-05-09 11:09:11 +02:00
Alex Plate
ccdd708907
Get rid of the old way of action execution 2025-05-08 18:22:40 +02:00
Alex Plate
73e61e0955
Get rid of hacks for Rider regarding esc and enter 2025-05-08 17:49:15 +02:00
Alex Plate
c3fa093d32
Upgrade the code to use the 2025.1 functions 2025-05-08 17:47:35 +02:00
Alex Plate
a94d509441
Remove other code deprecations 2025-05-08 17:33:41 +02:00
Alex Plate
12fd5bc79a
Remove deprecated functions 2025-05-08 17:20:58 +02:00
Alex Plate
235368c449
Remove old comments regarding vim-engine extraction 2025-05-08 17:20:00 +02:00
Alex Plate
57ecd25640
Refactor MacKeyRepeat.kt in order to remove the deprecated methods use 2025-05-08 17:18:17 +02:00
Alex Plate
09d37ebd38
Convert MacKeyRepeat to kotlin 2025-05-08 17:11:39 +02:00
Alex Plate
81bc3f1f1b
Rename .java to .kt 2025-05-08 17:11:39 +02:00
Alex Plate
9e3058dace
Code cleanup: remove some deprecations from the source code 2025-05-08 17:04:25 +02:00
Alex Plate
6819d4f96c
Add Mia Vucinic to the contributors list 2025-05-08 13:41:11 +02:00
IdeaVim Bot
222e1471b4 Add vumi19 to contributors list 2025-05-08 09:01:43 +00:00
M. V
35b9eaae3e VIM-2263 Add a not-null assertion operator to getRegister function to since assertions won't be executed if register does not exist 2025-05-07 18:54:26 +02:00
M. V
4eee1d3192 VIM-2263 Add additional tests that check the content of the registers 2025-05-07 18:54:26 +02:00
M. V
3c2e2bfb68 VIM-3771 Add a smile command with python ascii art 2025-05-07 18:54:12 +02:00
M. V
3f75b6db6d VIM-3771 Add a smile command with java ascii art 2025-05-07 18:54:12 +02:00
M. V
5fd318bf88 VIM-3771 Add a smile command with kotlin and default ascii art 2025-05-07 18:54:12 +02:00
M. V
6d34c70a9d VIM-3771 Rename VirtualFile.kt to VimVirtualFile.kt and add a new property extension 2025-05-07 18:54:12 +02:00
dependabot[bot]
3ffe8b68f7 Bump io.ktor:ktor-serialization-kotlinx-json from 3.1.2 to 3.1.3
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 3.1.2 to 3.1.3.
- [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.2...3.1.3)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-serialization-kotlinx-json
  dependency-version: 3.1.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:30:51 +02:00
dependabot[bot]
d09fc538f7 Bump io.ktor:ktor-client-cio from 3.1.2 to 3.1.3
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 3.1.2 to 3.1.3.
- [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.2...3.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:30:20 +02:00
dependabot[bot]
8d4d6b0f27 Bump io.ktor:ktor-client-content-negotiation from 3.1.2 to 3.1.3
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 3.1.2 to 3.1.3.
- [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.2...3.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 18:13:19 +02:00
dependabot[bot]
f9f5f039db Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.1.20-2.0.0 to 2.1.20-2.0.1.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.1.20-2.0.0...2.1.20-2.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 17:57:47 +02:00
dependabot[bot]
cacb63a525 Bump io.ktor:ktor-client-auth from 3.1.2 to 3.1.3
Bumps [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) from 3.1.2 to 3.1.3.
- [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.2...3.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-07 17:56:32 +02:00
Alex Plate
518eac1476
Fix the authors update script 2025-05-06 14:28:59 +02:00
IdeaVim Bot
ad18667520 Add Ivan Yarkov to contributors list 2025-05-06 09:01:46 +00:00
Alex Plate
5bf2b51c5d
Add a comment about the change 2025-05-05 17:00:33 +02:00
Ivan.Yarkov
5959fc2824 RIDER-123506 don't insert line break on enter in insert mode for Rider 2025-05-05 16:59:57 +02:00
Alex Plate
a9f9ae3727
Fix(VIM-3786): Make a workaround to fix shift-enter in AI chat 2025-04-29 19:36:05 +03:00
Alex Plate
8bfcd13c33
Fix(VIM-3882): Register the VimShortcutAction to the editor component instead of its wrapper
It turned out the editor.getComponent returns not the editorComponent, but the parent of the component. This caused no problems until the AI plugin started to register enter/esc on the editor component directly. Since an editor component is more specific than the component with vim actions, the vim shortcuts were suppressed.

In this change, we start to register shortcuts on the editor component directly, allowing them to properly work on the same level as AI shortcuts. This is also the level where the shortcuts are supposed to be registered.
2025-04-28 17:55:18 +03:00
Alex Plate
2cd5c9db72
Add Lejia Chen to the Authors list
Lejia was providing exceptional support for the IdeaVim project for two-plus years starting from 2023.
2025-04-28 11:45:15 +03:00
Matt Ellis
70d662fe28 Remove obsolete internal options 2025-04-22 15:48:19 +03:00
Alex Plate
2f33d41713
Update the minimal version of IJ to 251 2025-04-22 08:56:07 +03:00
Alex Plate
8247392dc3
Fix some compilation warnings 2025-04-16 22:12:36 +03:00
Alex Plate
c8504e1138
Fix warning about synchronization on primitive 2025-04-16 21:35:01 +03:00
Alex Plate
443e50b55f
Add tests for case changing
Mostly to highlight that commands in format `gugu` and `gUgU` don't work for the moment
2025-04-16 19:44:38 +03:00
Alex Plate
1891216182
Fix(VIM-3878): Support ROT13 command g? 2025-04-16 19:44:38 +03:00
dependabot[bot]
515f0ca568 Bump org.junit.jupiter:junit-jupiter-params from 5.12.1 to 5.12.2
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit5) from 5.12.1 to 5.12.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 19:41:45 +03:00
dependabot[bot]
2be0228c35 Bump org.junit.jupiter:junit-jupiter-api from 5.12.1 to 5.12.2
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.12.1 to 5.12.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 19:25:31 +03:00
dependabot[bot]
cf554e9ae2 Bump org.junit.vintage:junit-vintage-engine from 5.12.1 to 5.12.2
Bumps [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit5) from 5.12.1 to 5.12.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 19:10:22 +03:00
dependabot[bot]
251cc638db Bump org.junit.jupiter:junit-jupiter from 5.12.1 to 5.12.2
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.12.1 to 5.12.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 19:10:13 +03:00
dependabot[bot]
5700738c61 Bump org.junit.jupiter:junit-jupiter-engine from 5.12.1 to 5.12.2
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.12.1 to 5.12.2.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.1...r5.12.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-16 19:09:53 +03:00
Alex Plate
916afd31b2
Fix(VIM-3870): Add missing write actions 2025-04-16 18:54:22 +03:00
Alex Plate
5d2852420a
Remove the duplication method for existing the select mode 2025-04-15 09:20:00 +03:00
Matt Ellis
a5efa5f9f2 Rename SAVE_VISUAL to SAVE_SELECTION
IdeaVim will still leave Visual mode, but the IDE's selection remains.
2025-04-10 19:32:29 +03:00
Matt Ellis
e86503798a Remove special case handling for Plug mapping
While it might not make sense to replay an incorrect `<Plug>` or `<Action>` mapping as characters where they are likely to cause unexpected behaviour as Normal commands, this is standard Vim behaviour (at least for `<Plug>`).

Note that `<Plug>` (and `<Action>`) is a special key code that cannot be typed. In Insert mode, Vim expands it to the text "<Plug>".
2025-04-10 19:32:29 +03:00
Matt Ellis
0b6ac4a9f4 Rename variable and add comments 2025-04-10 19:32:29 +03:00
Matt Ellis
2a6f7cc907 Remove unused mappingComplete parameter
Also makes all KeyConsumers internal
2025-04-10 19:32:29 +03:00
Matt Ellis
9b4f114d61 Refactor MappingProcessor
Extract single implementation for replaying unhandled keys and update to match the longest mapping that fits in the unhandled keys.
2025-04-10 19:32:29 +03:00
Matt Ellis
3155556832 Reformat and update comments for MappingProcessor
Minor refactorings, should be no changes in logic
2025-04-10 19:32:29 +03:00
Matt Ellis
07190f38c9 Fix :normal command with multi-letter mapping 2025-04-10 19:32:29 +03:00
Matt Ellis
db116faa32 Fix handling of modes in NormalCommand
All commands are called in Normal, so there is no need to check mode at execution time. The SAVE_VISUAL flag is perhaps poorly named, as it still change to Normal mode, but will save the current selection for commands that need (usually because they interact with IDE features). The `:normal` command does not need the current selection.
2025-04-10 19:32:29 +03:00
Matt Ellis
58496fa1a1 Uncomment NormalCommandTest.kt 2025-04-10 19:32:29 +03:00
dependabot[bot]
0bd12af1f4 Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.1.20-1.0.32 to 2.1.20-2.0.0.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.1.20-1.0.32...2.1.20-2.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 18:44:36 +03:00
dependabot[bot]
9f6d697e30 Bump io.ktor:ktor-client-cio from 3.1.1 to 3.1.2
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 3.1.1 to 3.1.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.1.1...3.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-09 18:44:10 +03:00
IdeaVim Bot
8ed283f7ae Add Max Siryk to contributors list 2025-04-07 09:02:16 +00:00
erotourtes
5a3d35f216 fix: typo & consistency 2025-04-07 10:47:41 +03:00
Alex Plate
13850f059d
Add a new plugin to the compatibility check 2025-04-04 18:43:03 +03:00
Alex Plate
3ddc75f5f0
Remove the call for the instrumentationTools as it's deprecated 2025-04-04 18:39:28 +03:00
Alex Plate
6c71698aae
Do not use the installer for some of the tests because currently it doesn't work on the installer with the new gradle plugin
It's unclear if this is a bug in the gradle plugin or misconfiguration
2025-04-04 18:38:29 +03:00
dependabot[bot]
664895941d Bump io.ktor:ktor-serialization-kotlinx-json from 3.1.1 to 3.1.2
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 3.1.1 to 3.1.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.1.1...3.1.2)

---
updated-dependencies:
- dependency-name: io.ktor:ktor-serialization-kotlinx-json
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 18:45:35 +03:00
dependabot[bot]
c758ac16ec Bump io.ktor:ktor-client-core from 3.1.1 to 3.1.2
Bumps [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) from 3.1.1 to 3.1.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.1.1...3.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 18:31:12 +03:00
dependabot[bot]
4c2b3b8011 Bump io.ktor:ktor-client-auth from 3.1.1 to 3.1.2
Bumps [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) from 3.1.1 to 3.1.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.1.1...3.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 18:31:03 +03:00
dependabot[bot]
ee1c4914d4 Bump io.ktor:ktor-client-content-negotiation from 3.1.1 to 3.1.2
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 3.1.1 to 3.1.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.1.1...3.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 18:16:18 +03:00
dependabot[bot]
a5f225394f Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.1.20-1.0.31 to 2.1.20-1.0.32.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.1.20-1.0.31...2.1.20-1.0.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-04-02 18:16:01 +03:00
Alex Plate
8af848cef6
Fix(VIM-3861): Fix the debug action execution 2025-03-31 12:33:18 +03:00
IdeaVim Bot
4c1d91cc37 Add Peter Hoburg to contributors list 2025-03-29 09:01:47 +00:00
Alex Plate
deca256e1c
Add clear conditions for property tests 2025-03-28 19:23:47 +02:00
Matt Ellis
6d3bde3ad5 Fix incorrect implementation of DoesNotMatchHandler 2025-03-28 19:07:06 +02:00
Matt Ellis
3b2b863c88 Update truthy expressions 2025-03-28 19:07:06 +02:00
Matt Ellis
a7e1c08589 Simplify unary arithmetic operators 2025-03-28 19:07:06 +02:00
Matt Ellis
5aa5e82d59 Rename some operator handlers 2025-03-28 19:07:06 +02:00
Matt Ellis
0db9ad505b Remove unnecessary singleton classes 2025-03-28 19:07:06 +02:00
Matt Ellis
147eb99745 Simplify case sensitive binary operators 2025-03-28 19:07:06 +02:00
Matt Ellis
bb73bdfb4a Simplify case sensitive comparisons 2025-03-28 19:07:06 +02:00
Matt Ellis
9ab1044880 Migrate various string operators to toVimString
Make it explicit they only work with String values
2025-03-28 19:07:06 +02:00
Matt Ellis
e2cc9c648f Introduce toVimString to match toVimNumber 2025-03-28 19:07:06 +02:00
Matt Ellis
a322525c2a Be more explicit with is and isnot implementation 2025-03-28 19:07:06 +02:00
Matt Ellis
51ea947ccb Move comparison operator data rules to base class 2025-03-28 19:07:06 +02:00
Matt Ellis
f6810798ef Move binary operator conversion rules to base class 2025-03-28 19:07:06 +02:00
Matt Ellis
2fee525998 Refactor logical operators 2025-03-28 19:07:06 +02:00
Matt Ellis
9e3ab12451 Combine logical handlers into same file 2025-03-28 19:07:06 +02:00
Matt Ellis
5f8d552e8a Extract error message 2025-03-28 19:07:06 +02:00
Matt Ellis
4e847f8ef4 Merge arithmetic operators to same file 2025-03-28 19:07:06 +02:00
Matt Ellis
d5901fc2c9 Merge case-sensitive handlers to same file 2025-03-28 19:07:06 +02:00
Matt Ellis
3621b91321 Make operator handlers internal 2025-03-28 19:07:06 +02:00
Alex Plate
acbf1d7bd4
Update gradle wrapper 2025-03-28 19:05:55 +02:00
CCCC-L
f079edfb25 Modify CommentaryTextObject test case 2025-03-28 18:45:42 +02:00
CCCC-L
440cab1674 CommentaryTextObject no longer contains spaces to be consistent with neovim 2025-03-28 18:45:42 +02:00
Alex Plate
9322e3b81b
Update intellij gradle plugin 2025-03-28 18:40:26 +02:00
Matt Ellis
021db84a21 Expand ~ to home directory in XDG_CONFIG_HOME
Addresses VIM-3844, VIM-2001
2025-03-28 18:18:32 +02:00
Matt Ellis
e232cb3ceb Support 32-bit Unicode codepoints in digraphs
Fixes VIM-3842
2025-03-28 18:13:14 +02:00
Peter Hoburg
174757cdb2 Removed summary from Mini.ai on the README.md 2025-03-28 18:00:26 +02:00
Peter Hoburg
ef0883bc0d finished adding summaries. 2025-03-28 18:00:26 +02:00
Peter Hoburg
0b4ad07b32 Moved more details around,
added more About sections.
2025-03-28 18:00:26 +02:00
Peter Hoburg
3d1a426566 Added back in a missing </details> tag. 2025-03-28 18:00:26 +02:00
Peter Hoburg
6f4eb838c3 Started alphabetizing and adding About: sections to the IdeaVim Plugins.md 2025-03-28 18:00:26 +02:00
Peter Hoburg
bc38ddc0f8 Alphabetized the IdeaVim Plugins section of the main README.md 2025-03-28 18:00:26 +02:00
Matt Ellis
a33b3980ab Fix unhandled key stroke after surround operation
Fixes VIM-3841
2025-03-28 17:51:14 +02:00
Matt Ellis
7917c83cb5 Add extra tests for line() function behaviour
It's not obvious that line('v') will return the opposite end of the current selection. Add some tests to highlight this.

Relates to VIM-3838
2025-03-28 17:23:41 +02:00
Matt Ellis
42229b285b Fix typo 2025-03-28 17:23:41 +02:00
Matt Ellis
421c3bbfb8 Support <C-U> in commandToKeys 2025-03-28 17:23:41 +02:00
Matt Ellis
dbd097a91a Fix caret position after :move command
Fixes VIM-3837
2025-03-28 17:23:41 +02:00
Alex Plate
30f019aa18
Fix enter for Clion Nova 2025-03-28 15:35:33 +02:00
Alex Plate
543d8dbb13
Fix(VIM-3856): Disable new way of action execution for CLion 2025-03-28 14:05:53 +02:00
Alex Plate
2800b2d5fc
Fix(VIM-3857): Fix pasting for the client 2025-03-28 13:45:13 +02:00
Alex Plate
cd27ce8004
Fix(VIM-3852): Fix incorrect cast while checking the shortcuts 2025-03-28 13:27:59 +02:00
Alex Plate
6f3cf43bae
Make a few tests for Rider
This should cover VIM-3826 issues
2025-03-27 17:34:24 +02:00
Alex Plate
b043296cde
Fix(VIM-3826): Fix action execution in Rider 243 and older versions
It still doesn't work fine when executing in format `:action ReformatCode` in 243, but with 251 format updates from Rider, it should be fine.
2025-03-27 17:20:23 +02:00
Alex Plate
9beca20037
Click Activate during testing 2025-03-27 15:17:14 +02:00
Alex Plate
b882d60416
Trying to make Rider UI tests alive 2025-03-27 15:05:38 +02:00
Alex Plate
7144d73488
Mute a slow operation related to VIM-3648 2025-03-27 14:12:55 +02:00
dependabot[bot]
ae71075134 Bump org.jetbrains.kotlin:kotlin-stdlib from 2.1.10 to 2.1.20
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 2.1.10 to 2.1.20.
- [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.10...v2.1.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 17:43:25 +02:00
dependabot[bot]
a5d4bf1a57 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.1.0.202411261347-r to 7.2.0.202503040940-r.
- [Commits](https://github.com/eclipse-jgit/jgit/compare/v7.1.0.202411261347-r...v7.2.0.202503040940-r)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 17:41:33 +02:00
dependabot[bot]
3b8a830622 Bump org.junit.jupiter:junit-jupiter-api from 5.12.0 to 5.12.1
Bumps [org.junit.jupiter:junit-jupiter-api](https://github.com/junit-team/junit5) from 5.12.0 to 5.12.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 17:41:17 +02:00
dependabot[bot]
71adcef1bf Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 2.1.10-1.0.29 to 2.1.20-1.0.31.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/2.1.10-1.0.29...2.1.20-1.0.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-26 17:40:41 +02:00
dependabot[bot]
d4d0212b04 Bump org.junit.jupiter:junit-jupiter-params from 5.12.0 to 5.12.1
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit5) from 5.12.0 to 5.12.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-19 18:51:22 +03:00
dependabot[bot]
4b3bba6a98 Bump org.junit.vintage:junit-vintage-engine from 5.12.0 to 5.12.1
Bumps [org.junit.vintage:junit-vintage-engine](https://github.com/junit-team/junit5) from 5.12.0 to 5.12.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-19 18:34:51 +03:00
dependabot[bot]
13edc1294c Bump org.junit.jupiter:junit-jupiter from 5.12.0 to 5.12.1
Bumps [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5) from 5.12.0 to 5.12.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-19 18:34:22 +03:00
dependabot[bot]
f5269a56d6 Bump org.junit.jupiter:junit-jupiter-engine from 5.12.0 to 5.12.1
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.12.0 to 5.12.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.12.0...r5.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-19 18:34:13 +03:00
266 changed files with 4851 additions and 3942 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@ -28,10 +28,12 @@ jobs:
uses: jtalk/url-health-check-action@v3
with:
url: http://127.0.0.1:8082
max-attempts: 20
max-attempts: 100
retry-delay: 10s
- name: Tests
run: gradle :tests:ui-rd-tests:testUi
env:
RIDER_LICENSE: ${{ secrets.RIDER_LICENSE }}
- name: Move video
if: always()
run: mv tests/ui-rd-tests/video build/reports

View File

@ -5,11 +5,11 @@ object Constants {
const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev"
const val NVIM_TESTS = "2024.3.3"
const val PROPERTY_TESTS = "2024.3.3"
const val LONG_RUNNING_TESTS = "2024.3.3"
const val RELEASE = "2024.3.3"
const val NVIM_TESTS = "2025.1"
const val PROPERTY_TESTS = "2025.1"
const val LONG_RUNNING_TESTS = "2025.1"
const val RELEASE = "2025.1"
const val RELEASE_DEV = "2024.3.3"
const val RELEASE_EAP = "2024.3.3"
const val RELEASE_DEV = "2025.1"
const val RELEASE_EAP = "2025.1"
}

View File

@ -23,8 +23,8 @@ object Project : Project({
vcsRoot(ReleasesVcsRoot)
// Active tests
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2024.3.3", "<default>"))
buildType(TestingBuildType("Latest EAP", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("2025.1"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(PropertyBased)

View File

@ -43,6 +43,8 @@ object Compatibility : IdeaVimBuildType({
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.peekaboo' [latest-IU] -team-city
java -jar verifier1/verifier-cli-dev-all-2.jar check-plugin '${'$'}com.julienphalip.ideavim.switch' [latest-IU] -team-city
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
""".trimIndent()
}
}

View File

@ -24,6 +24,7 @@ object PropertyBased : IdeaVimBuildType({
steps {
gradle {
clearConditions()
tasks = "clean :tests:property-tests:testPropertyBased"
buildFile = ""
enableStacktrace = true

View File

@ -12,7 +12,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.triggers.vcs
open class TestingBuildType(
private val testName: String,
private val branch: String,
private val branch: String = "<default>",
private val version: String = testName,
private val javaVersion: String? = null,
private val javaPlugin: Boolean = true,

View File

@ -26,6 +26,13 @@ Previous maintainers:
&nbsp;
Andrey Vlasovskikh
Previous support members:
* [![icon][mail]](mailto:lejia.chen@jetbrains.com)
[![icon][github-off]](#)
&nbsp;
Lejia Chen
Contributors:
* [![icon][mail]](mailto:yole@jetbrains.com)
@ -587,6 +594,22 @@ Contributors:
[![icon][github]](https://github.com/Iliya-usov)
&nbsp;
Ilya Usov
* [![icon][mail]](mailto:peterHoburg@users.noreply.github.com)
[![icon][github]](https://github.com/peterHoburg)
&nbsp;
Peter Hoburg
* [![icon][mail]](mailto:erotourtes@gmail.com)
[![icon][github]](https://github.com/erotourtes)
&nbsp;
Max Siryk
* [![icon][mail]](mailto:ivan.yarkov@jetbrains.com)
[![icon][github]](https://github.com/MToolMakerJB)
&nbsp;
Ivan Yarkov
* [![icon][mail]](mailto:mia.vucinic@jetbrains.com)
[![icon][github]](https://github.com/vumi19)
&nbsp;
Mia Vucinic
Previous contributors:

View File

@ -92,20 +92,26 @@ Here are some examples of supported vim features and commands:
[IdeaVim plugins](https://github.com/JetBrains/ideavim/wiki/IdeaVim-Plugins):
* vim-easymotion
* argtextobj
* commentary
* easymotion
* exchange
* FunctionTextObj
* highlightedyank
* indent-object
* matchit.vim
* Mini.ai
* multiple-cursors
* NERDTree
* vim-surround
* vim-multiple-cursors
* vim-commentary
* argtextobj.vim
* vim-textobj-entire
* paragraph-motion
* Peekaboo
* quick-scope
* ReplaceWithRegister
* vim-exchange
* vim-highlightedyank
* vim-paragraph-motion
* vim-indent-object
* match.it
etc
* sneak
* surround
* Switch
* textobj-entire
* Which-Key
See also:

View File

@ -21,7 +21,7 @@ repositories {
}
dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.10-1.0.29")
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.21-2.0.1")
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

@ -31,6 +31,7 @@ import kotlinx.serialization.json.putJsonObject
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.RepositoryBuilder
import org.intellij.markdown.ast.getTextInNode
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
@ -50,14 +51,14 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.0.202503040940-r")
classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:3.1.1")
classpath("io.ktor:ktor-client-cio:3.1.1")
classpath("io.ktor:ktor-client-auth:3.1.1")
classpath("io.ktor:ktor-client-content-negotiation:3.1.1")
classpath("io.ktor:ktor-serialization-kotlinx-json:3.1.1")
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")
// This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1")
@ -69,7 +70,12 @@ plugins {
kotlin("jvm") version "2.0.21"
application
id("java-test-fixtures")
id("org.jetbrains.intellij.platform") version "2.3.0"
// 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.changelog") version "2.2.1"
id("org.jetbrains.kotlinx.kover") version "0.6.1"
id("com.dorongold.task-tree") version "4.0.1"
@ -112,7 +118,11 @@ dependencies {
intellijPlatform {
// Snapshots don't use installers
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
var useInstaller = "EAP-SNAPSHOT" !in ideaVersion
if (ideaType == "RD") {
// Using Rider as a target IntelliJ Platform with `useInstaller = true` is currently not supported, please set `useInstaller = false` instead. See: https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1852
useInstaller = false
}
// Note that it is also possible to use local("...") to compile against a locally installed IDE
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
@ -127,6 +137,7 @@ dependencies {
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
plugin("AceJump", "3.8.19")
plugin("com.intellij.classic.ui", "251.23774.318")
bundledPlugins("org.jetbrains.plugins.terminal", "com.intellij.modules.json")
}
@ -150,17 +161,17 @@ dependencies {
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.12.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.12.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.12.0")
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.12.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")
// 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.0")
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2")
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
}
@ -232,6 +243,8 @@ tasks {
}
compileTestKotlin {
enabled = false
kotlinOptions {
jvmTarget = javaVersion
apiVersion = "2.0"
@ -247,6 +260,7 @@ tasks {
// a custom task (see below)
runIde {
systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
systemProperty("idea.trust.all.projects", "true")
}
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies
@ -819,7 +833,9 @@ fun updateAuthors(uncheckedEmails: Set<String>) {
org.intellij.markdown.parser.MarkdownParser(org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor())
val tree = parser.buildMarkdownTreeFromString(authors)
val contributorsSection = tree.children[24]
val contributorsSection = tree.children
.filter { it is ListCompositeNode }
.single { it.getTextInNode(authors).contains("yole") }
val existingEmails = mutableSetOf<String>()
for (child in contributorsSection.children) {
if (child.children.size > 1) {

View File

@ -16,10 +16,95 @@ in `~/.ideavimrc`. E.g. `set nosurround`.
Available plugins:
<details>
<summary><h2>easymotion</h2></summary>
<summary><h2>argtextobj: Provides a text-object 'a' argument</h2></summary>
Original plugin: [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699).
### Summary:
This plugin provides a text-object 'a' (argument).
You can d(elete), c(hange), v(select)... an argument or inner argument in familiar ways.
That is, such as 'daa'(delete-an-argument) 'cia'(change-inner-argument) 'via'(select-inner-argument).
What this script does is more than just typing
F,dt,
because it recognizes inclusion relationship of parentheses.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'vim-scripts/argtextobj.vim'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'vim-scripts/argtextobj.vim'</code>
<br/>
<code>Plug 'https://github.com/vim-scripts/argtextobj.vim'</code>
<br/>
<code>Plug 'argtextobj.vim'</code>
<br/>
<code>Plug 'https://www.vim.org/scripts/script.php?script_id=2699'</code>
<br/>
<code>set argtextobj</code>
</details>
### Instructions
By default, only the arguments inside parenthesis are considered. To extend the functionality
to other types of brackets, set `g:argtextobj_pairs` variable to a comma-separated
list of colon-separated pairs (same as VIM's `matchpairs` option), like
`let g:argtextobj_pairs="(:),{:},<:>"`. The order of pairs matters when
handling symbols that can also be operators: `func(x << 5, 20) >> 17`. To handle
this syntax parenthesis, must come before angle brackets in the list.
https://www.vim.org/scripts/script.php?script_id=2699
</details>
<details>
<summary><h2>commentary: Adds mapping for quickly commenting stuff out</h2></summary>
By [Daniel Leong](https://github.com/dhleong)
Original plugin: [commentary.vim](https://github.com/tpope/vim-commentary).
### Summary:
Comment stuff out.
Use gcc to comment out a line (takes a count), gc to comment out the target of a motion
(for example, gcap to comment out a paragraph), gc in visual mode to comment out the selection,
and gc in operator pending mode to target a comment.
You can also use it as a command, either with a range like :7,17Commentary,
or as part of a :global invocation like with :g/TODO/Commentary.
That's it.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-commentary'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'tpope/vim-commentary'</code>
<br/>
<code>Plug 'https://github.com/tpope/vim-commentary'</code>
<br/>
<code>Plug 'vim-commentary'</code>
<br/>
<code>Plug 'tcomment_vim'</code>
<br/>
<code>set commentary</code>
</details>
### Instructions
https://github.com/tpope/vim-commentary/blob/master/doc/commentary.txt
</details>
<details>
<summary><h2>easymotion: Simplifies some motions</h2></summary>
Original plugin: [vim-easymotion](https://github.com/easymotion/vim-easymotion).
### Summary:
EasyMotion provides a much simpler way to use some motions in vim.
It takes the \<number> out of \<number>w or \<number>f{char} by highlighting all possible choices
and allowing you to press one key to jump directly to the target.
### Setup:
- Install [IdeaVim-EasyMotion](https://plugins.jetbrains.com/plugin/13360-ideavim-easymotion/)
and [AceJump](https://plugins.jetbrains.com/plugin/7086-acejump/) plugins.
@ -41,80 +126,176 @@ All commands with the mappings are supported. See the [full list of supported co
</details>
<details>
<summary><h2>sneak</h2></summary>
<summary><h2>exchange: Easy text exchange operator</h2></summary>
<img src="images/sneakIcon.svg" width="80" height="80" alt="icon"/>
By [fan-tom](https://github.com/fan-tom)
Original plugin: [vim-exchange](https://github.com/tommcdo/vim-exchange).
### Summary:
Easy text exchange operator for Vim.
By [Mikhail Levchenko](https://github.com/Mishkun)
Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
Original plugin: [vim-sneak](https://github.com/justinmk/vim-sneak).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'justinmk/vim-sneak'`
### Instructions
* Type `s` and two chars to start sneaking in forward direction
* Type `S` and two chars to start sneaking in backward direction
* Type `;` or `,` to proceed with sneaking just as if you were using `f` or `t` commands
</details>
<details>
<summary><h2>NERDTree</h2></summary>
Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'preservim/nerdtree'`
- Add the following command to `~/.ideavimrc`: `Plug 'tommcdo/vim-exchange'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'preservim/nerdtree'</code>
<code>Plugin 'tommcdo/vim-exchange'</code>
<br/>
<code>Plug 'https://github.com/preservim/nerdtree'</code>
<code>Plug 'https://github.com/tommcdo/vim-exchange'</code>
<br/>
<code>Plug 'nerdtree'</code>
<code>Plug 'vim-exchange'</code>
<br/>
<code>set NERDTree</code>
<code>set exchange</code>
</details>
### Instructions
[See here](NERDTree-support.md).
https://github.com/tommcdo/vim-exchange/blob/master/doc/exchange.txt
</details>
<details>
<summary><h2>surround</h2></summary>
Original plugin: [vim-surround](https://github.com/tpope/vim-surround).
<summary><h2>FunctionTextObj: Adds text objects for manipulating functions/methods</h2></summary>
By Julien Phalip
### Summary:
An extension for IdeaVim that adds text objects for manipulating functions/methods in your code.
Similar to how iw operates on words or i" operates on quoted strings,
this plugin provides if and af to operate on functions
### Setup
Add `set functiontextobj` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
</details>
<details>
<summary><h2>highlightedyank: Highlights the yanked region</h2></summary>
By [KostkaBrukowa](https://github.com/KostkaBrukowa)
Original plugin: [vim-highlightedyank](https://github.com/machakann/vim-highlightedyank).
### Summary:
Make the yanked region apparent!
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-surround'`
- Add the following command to `~/.ideavimrc`: `Plug 'machakann/vim-highlightedyank'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'tpope/vim-surround'</code>
<code>Plugin 'machakann/vim-highlightedyank'</code>
<br/>
<code>Plug 'https://www.vim.org/scripts/script.php?script_id=1697'</code>
<code>Plug 'https://github.com/machakann/vim-highlightedyank'</code>
<br/>
<code>Plug 'vim-surround'</code>
<code>Plug 'vim-highlightedyank'</code>
<br/>
<code>set surround</code>
<code>set highlightedyank</code>
</details>
### Instructions
https://github.com/tpope/vim-surround/blob/master/doc/surround.txt
If you want to optimize highlight duration, assign a time in milliseconds:
`let g:highlightedyank_highlight_duration = "1000"`
A negative number makes the highlight persistent.
If you want to change background color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"`
If you want to change text color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"`
https://github.com/machakann/vim-highlightedyank/blob/master/doc/highlightedyank.txt
</details>
<details>
<summary><h2>multiple-cursors</h2></summary>
<summary><h2>indent-object: Adds text objects for manipulating sentences/paragraphs/etc...</h2></summary>
By [Shrikant Sharat Kandula](https://github.com/sharat87)
Original plugin: [vim-indent-object](https://github.com/michaeljsmith/vim-indent-object).
### Summary:
Vim text objects provide a convenient way to select and operate on various types of objects.
These objects include regions surrounded by various types of brackets and various parts of language
(ie sentences, paragraphs, etc).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'michaeljsmith/vim-indent-object'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'https://github.com/michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'vim-indent-object'</code>
<br/>
<code>set textobj-indent</code>
</details>
### Instructions
https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt
</details>
<details>
<summary><h2>matchit.vim: Extends the % key functionality</h2></summary>
By [Martin Yzeiri](https://github.com/myzeiri)
Original plugin: [matchit.vim](https://github.com/chrisbra/matchit).
### Summary:
In Vim, as in plain vi, the percent key, |%|, jumps the cursor from a brace, bracket, or paren to its match.
This can be configured with the 'matchpairs' option.
The matchit plugin extends this in several ways...
### Setup:
- Add the following command to `~/.ideavimrc`: `packadd matchit`
<details>
<summary>Alternative syntax</summary>
<code>Plug 'vim-matchit'</code>
<br/>
<code>Plug 'chrisbra/matchit'</code>
<br/>
<code>set matchit</code>
</details>
### Instructions
https://github.com/adelarsq/vim-matchit/blob/master/doc/matchit.txt
</details>
<details>
<summary><h2>Mini.ai: Extend and create a/i textobjects (IMPORTANT: The plugin is not related with artificial intelligence)</h2></summary>
### Summary:
Extend and create a/i textobjects
### Features:
Provides additional text object motions for handling quotes and brackets. The following motions are included:
- aq: Around any quotes.
- iq: Inside any quotes.
- ab: Around any parentheses, curly braces, and square brackets.
- ib: Inside any parentheses, curly braces, and square brackets.
Original plugin: [mini.ai](https://github.com/echasnovski/mini.ai).
### Setup:
- Add the following command to `~/.ideavimrc`: `set mini-ai`
</details>
<details>
<summary><h2>multiple-cursors: Extends multicursor support</h2></summary>
Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-cursors).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'terryma/vim-multiple-cursors'`
<details>
@ -127,7 +308,7 @@ Original plugin: [vim-multiple-cursors](https://github.com/terryma/vim-multiple-
<br/>
<code>set multiple-cursors</code>
</details>
### Instructions
At the moment, the default key binds for this plugin do not get mapped correctly in IdeaVim (see [VIM-2178](https://youtrack.jetbrains.com/issue/VIM-2178)). To enable the default key binds, add the following to your `.ideavimrc` file...
@ -153,38 +334,118 @@ xmap <leader>g<C-n> <Plug>AllOccurrences
</details>
<details>
<summary><h2>commentary</h2></summary>
<summary><h2>NERDTree: Adds NERDTree navigation to the project panel</h2></summary>
Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
By [Daniel Leong](https://github.com/dhleong)
Original plugin: [commentary.vim](https://github.com/tpope/vim-commentary).
### Summary:
Adds NERDTree navigation to the project panel.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-commentary'`
- Add the following command to `~/.ideavimrc`: `Plug 'preservim/nerdtree'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'tpope/vim-commentary'</code>
<code>Plugin 'preservim/nerdtree'</code>
<br/>
<code>Plug 'https://github.com/tpope/vim-commentary'</code>
<code>Plug 'https://github.com/preservim/nerdtree'</code>
<br/>
<code>Plug 'vim-commentary'</code>
<code>Plug 'nerdtree'</code>
<br/>
<code>Plug 'tcomment_vim'</code>
<br/>
<code>set commentary</code>
<code>set NERDTree</code>
</details>
### Instructions
https://github.com/tpope/vim-commentary/blob/master/doc/commentary.txt
[See here](NERDTree-support.md).
</details>
<details>
<summary><h2>ReplaceWithRegister</h2></summary>
<summary><h2>paragraph-motion: Extends the { and } motions to ignore whitespace on otherwise empty lines</h2></summary>
Original plugin: [vim-paragraph-motion](https://github.com/dbakker/vim-paragraph-motion).
### Summary:
Normally the { and } motions only match completely empty lines.
With this plugin lines that only contain whitespace are also matched.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'dbakker/vim-paragraph-motion'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'Improved-paragraph-motion'</code>
<br/>
<code>set vim-paragraph-motion</code>
</details>
### Instructions
https://github.com/dbakker/vim-paragraph-motion#vim-paragraph-motion
</details>
<details>
<summary><h2>Peekaboo: Extends " @ CTRL-r to show a popup of the register contents</h2></summary>
By Julien Phalip
Original plugin: [vim-peekaboo](https://github.com/junegunn/vim-peekaboo).
### Summary:
Peekaboo extends " and @ in normal mode and <CTRL-R> in insert mode so you can see the contents of the registers.
### Setup
Add `set peekaboo` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
</details>
<details>
<summary><h2>quick-scope: Always-on highlight for a unique character in every word on a line to help use f, F, etc.</h2></summary>
Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
### Summary:
An always-on highlight for a unique character in every word on a line to help you use f, F and family.
This plugin should help you get to any word on a line in two or three keystrokes with Vim's built-in f<char>
(which moves your cursor to <char>).
### Setup:
- Install [IdeaVim-Quickscope](https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope) plugin.
- Add the following command to `~/.ideavimrc`: `set quickscope`
### Instructions
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
</details>
<details>
<summary><h2>ReplaceWithRegister: Adds two-in-one command that replaces text with the contents of a register.</h2></summary>
By [igrekster](https://github.com/igrekster)
Original plugin: [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWithRegister).
### Summary:
This plugin offers a two-in-one command that replaces text covered by a
{motion}, entire line(s) or the current selection with the contents of a
register; the old text is deleted into the black-hole register, i.e. it's
gone. (But of course, the command can be easily undone.)
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'vim-scripts/ReplaceWithRegister'`
<details>
@ -203,78 +464,99 @@ Original plugin: [ReplaceWithRegister](https://github.com/vim-scripts/ReplaceWit
<br/>
<code>set ReplaceWithRegister</code>
</details>
### Instructions
https://github.com/vim-scripts/ReplaceWithRegister/blob/master/doc/ReplaceWithRegister.txt
</details>
<details>
<summary><h2>argtextobj</h2></summary>
<summary><h2>sneak: Jump to any location specified by two characters</h2></summary>
<img src="images/sneakIcon.svg" width="80" height="80" alt="icon"/>
By [Mikhail Levchenko](https://github.com/Mishkun)
Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
Original plugin: [vim-sneak](https://github.com/justinmk/vim-sneak).
### Summary:
Jump to any location specified by two characters.
Original plugin: [argtextobj.vim](https://www.vim.org/scripts/script.php?script_id=2699).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'vim-scripts/argtextobj.vim'`
- Add the following command to `~/.ideavimrc`: `Plug 'justinmk/vim-sneak'`
### Instructions
* Type `s` and two chars to start sneaking in forward direction
* Type `S` and two chars to start sneaking in backward direction
* Type `;` or `,` to proceed with sneaking just as if you were using `f` or `t` commands
</details>
<details>
<summary><h2>surround: Adds provides mappings to easily delete, change, and add surroundings in pairs</h2></summary>
Original plugin: [vim-surround](https://github.com/tpope/vim-surround).
### Summary:
Surround.vim is all about "surroundings": parentheses, brackets, quotes, XML tags, and more.
The plugin provides mappings to easily delete, change and add such surroundings in pairs.
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tpope/vim-surround'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'vim-scripts/argtextobj.vim'</code>
<code>Plugin 'tpope/vim-surround'</code>
<br/>
<code>Plug 'https://github.com/vim-scripts/argtextobj.vim'</code>
<code>Plug 'https://www.vim.org/scripts/script.php?script_id=1697'</code>
<br/>
<code>Plug 'argtextobj.vim'</code>
<code>Plug 'vim-surround'</code>
<br/>
<code>Plug 'https://www.vim.org/scripts/script.php?script_id=2699'</code>
<br/>
<code>set argtextobj</code>
<code>set surround</code>
</details>
### Instructions
By default, only the arguments inside parenthesis are considered. To extend the functionality
to other types of brackets, set `g:argtextobj_pairs` variable to a comma-separated
list of colon-separated pairs (same as VIM's `matchpairs` option), like
`let g:argtextobj_pairs="(:),{:},<:>"`. The order of pairs matters when
handling symbols that can also be operators: `func(x << 5, 20) >> 17`. To handle
this syntax parenthesis, must come before angle brackets in the list.
https://www.vim.org/scripts/script.php?script_id=2699
https://github.com/tpope/vim-surround/blob/master/doc/surround.txt
</details>
<details>
<summary><h2>exchange</h2></summary>
<summary><h2>Switch: Switch some text under the cursor based on regex patterns</h2></summary>
By Julien Phalip
Original plugin: [switch.vim](https://github.com/AndrewRadev/switch.vim).
### Summary:
The purpose of the plugin is to switch some text under the cursor based on regex patterns.
The main entry point is a single command, :Switch.
When the command is executed,
the plugin looks for one of a few specific patterns under the cursor and performs a substitution depending on it.
### Setup
Add `set switch` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
By [fan-tom](https://github.com/fan-tom)
Original plugin: [vim-exchange](https://github.com/tommcdo/vim-exchange).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'tommcdo/vim-exchange'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'tommcdo/vim-exchange'</code>
<br/>
<code>Plug 'https://github.com/tommcdo/vim-exchange'</code>
<br/>
<code>Plug 'vim-exchange'</code>
<br/>
<code>set exchange</code>
</details>
### Instructions
https://github.com/tommcdo/vim-exchange/blob/master/doc/exchange.txt
https://plugins.jetbrains.com/plugin/25899-vim-switch
</details>
<details>
<summary><h2>textobj-entire</h2></summary>
<summary><h2>textobj-entire: Adds mapping for selecting entire contents of file regardless of cursor position</h2></summary>
By [Alexandre Grison](https://github.com/agrison)
Original plugin: [vim-textobj-entire](https://github.com/kana/vim-textobj-entire).
### Summary:
vim-textobj-entire is a Vim plugin to provide text objects
(ae and ie by default) to select the entire content of a buffer.
Though these are trivial operations (e.g. ggVG), text object versions are more handy,
because you do not have to be conscious of the cursor position (e.g. vae).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'kana/vim-textobj-entire'`
<details>
@ -295,158 +577,13 @@ https://github.com/kana/vim-textobj-entire/blob/master/doc/textobj-entire.txt
</details>
<details>
<summary><h2>highlightedyank</h2></summary>
By [KostkaBrukowa](https://github.com/KostkaBrukowa)
Original plugin: [vim-highlightedyank](https://github.com/machakann/vim-highlightedyank).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'machakann/vim-highlightedyank'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'machakann/vim-highlightedyank'</code>
<br/>
<code>Plug 'https://github.com/machakann/vim-highlightedyank'</code>
<br/>
<code>Plug 'vim-highlightedyank'</code>
<br/>
<code>set highlightedyank</code>
</details>
### Instructions
If you want to optimize highlight duration, assign a time in milliseconds:
`let g:highlightedyank_highlight_duration = "1000"`
A negative number makes the highlight persistent.
If you want to change background color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"`
If you want to change text color of highlight you can provide the rgba of the color you want e.g.
`let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"`
https://github.com/machakann/vim-highlightedyank/blob/master/doc/highlightedyank.txt
</details>
<details>
<summary><h2>vim-paragraph-motion</h2></summary>
Original plugin: [vim-paragraph-motion](https://github.com/dbakker/vim-paragraph-motion).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'dbakker/vim-paragraph-motion'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/dbakker/vim-paragraph-motion'</code>
<br/>
<code>Plug 'vim-paragraph-motion'</code>
<br/>
<code>Plug 'https://github.com/vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'vim-scripts/Improved-paragraph-motion'</code>
<br/>
<code>Plug 'Improved-paragraph-motion'</code>
<br/>
<code>set vim-paragraph-motion</code>
</details>
### Instructions
https://github.com/dbakker/vim-paragraph-motion#vim-paragraph-motion
</details>
<details>
<summary><h2>vim-indent-object</h2></summary>
By [Shrikant Sharat Kandula](https://github.com/sharat87)
Original plugin: [vim-indent-object](https://github.com/michaeljsmith/vim-indent-object).
### Setup:
- Add the following command to `~/.ideavimrc`: `Plug 'michaeljsmith/vim-indent-object'`
<details>
<summary>Alternative syntax</summary>
<code>Plugin 'michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'https://github.com/michaeljsmith/vim-indent-object'</code>
<br/>
<code>Plug 'vim-indent-object'</code>
<br/>
<code>set textobj-indent</code>
</details>
### Instructions
https://github.com/michaeljsmith/vim-indent-object/blob/master/doc/indent-object.txt
</details>
<details>
<summary><h2>matchit.vim</h2></summary>
By [Martin Yzeiri](https://github.com/myzeiri)
Original plugin: [matchit.vim](https://github.com/chrisbra/matchit).
### Setup:
- Add the following command to `~/.ideavimrc`: `packadd matchit`
<details>
<summary>Alternative syntax</summary>
<code>Plug 'vim-matchit'</code>
<br/>
<code>Plug 'chrisbra/matchit'</code>
<br/>
<code>set matchit</code>
</details>
### Instructions
https://github.com/adelarsq/vim-matchit/blob/master/doc/matchit.txt
</details>
<details>
<summary><h2>IdeaVim-Quickscope</h2></summary>
Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
### Setup:
- Install [IdeaVim-Quickscope](https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope) plugin.
- Add the following command to `~/.ideavimrc`: `set quickscope`
### Instructions
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
</details>
<details>
<summary><h2>Mini.ai: Extend and create a/i textobjects (IMPORTANT: The plugin is not related with artificial intelligence)</h2></summary>
### Features:
Provides additional text object motions for handling quotes and brackets. The following motions are included:
- aq: Around any quotes.
- iq: Inside any quotes.
- ab: Around any parentheses, curly braces, and square brackets.
- ib: Inside any parentheses, curly braces, and square brackets.
Original plugin: [mini.ai](https://github.com/echasnovski/mini.ai).
### Setup:
- Add the following command to `~/.ideavimrc`: `set mini-ai`
</details>
<details>
<summary><h2>Which-Key</h2></summary>
<summary><h2>Which-Key: Displays available keybindings in popup</h2></summary>
Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
### Summary:
vim-which-key is vim port of emacs-which-key that displays available keybindings in popup.
### Setup:
- Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin.
- Add the following command to `~/.ideavimrc`: `set which-key`
@ -456,49 +593,3 @@ Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
</details>
<details>
<summary><h2>Vim Peekaboo</h2></summary>
By Julien Phalip
Original plugin: [vim-peekaboo](https://github.com/junegunn/vim-peekaboo).
### Setup
Add `set peekaboo` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
</details>
<details>
<summary><h2>FunctionTextObj</h2></summary>
By Julien Phalip
### Setup
Add `set functiontextobj` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
</details>
<details>
<summary><h2>Switch</h2></summary>
By Julien Phalip
Original plugin: [switch.vim](https://github.com/AndrewRadev/switch.vim).
### Setup
Add `set switch` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
or restart the IDE.
### Instructions
https://plugins.jetbrains.com/plugin/25899-vim-switch

View File

@ -16,11 +16,11 @@
# https://data.services.jetbrains.com/products?code=IC
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
ideaVersion=2024.3.3
ideaVersion=2025.1
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
ideaType=IC
instrumentPluginCode=true
version=SNAPSHOT
version=chylex-47
javaVersion=21
remoteRobotVersion=0.11.23
antlrVersion=4.10.1
@ -41,7 +41,6 @@ youtrackToken=
# Gradle settings
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
org.gradle.configuration-cache=true
org.gradle.caching=true
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

3
gradlew vendored
View File

@ -86,8 +86,7 @@ done
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
' "$PWD" ) || exit
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

View File

@ -20,17 +20,17 @@ repositories {
}
dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.10")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.21")
implementation("io.ktor:ktor-client-core:3.1.1")
implementation("io.ktor:ktor-client-cio:3.1.1")
implementation("io.ktor:ktor-client-content-negotiation:3.1.1")
implementation("io.ktor:ktor-serialization-kotlinx-json:3.1.1")
implementation("io.ktor:ktor-client-auth:3.1.1")
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("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.1.0.202411261347-r")
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.2.0.202503040940-r")
implementation("com.vdurmont:semver4j:3.1.0")
}

View File

@ -43,6 +43,8 @@ val knownPlugins = setOf(
"com.julienphalip.ideavim.peekaboo", // https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
"com.julienphalip.ideavim.switch", // https://plugins.jetbrains.com/plugin/25899-vim-switch
"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
)
suspend fun main() {

View File

@ -355,7 +355,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (unsubscribe) {
VimListenerManager.INSTANCE.turnOff();
}
injector.getCommandLine().fullReset();
// Use getServiceIfCreated to avoid creating the service during the dispose (this is prohibited by the platform)
@Nullable VimCommandLineService service =
ApplicationManager.getApplication().getServiceIfCreated(VimCommandLineService.class);
if (service != null) {
service.fullReset();
}
// Unregister vim actions in command mode
RegisterActions.unregisterActions();
@ -371,8 +376,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (isEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) {
stateUpdated = true;
if (SystemInfo.isMac) {
final MacKeyRepeat keyRepeat = MacKeyRepeat.getInstance();
final Boolean enabled = keyRepeat.isEnabled();
final Boolean enabled = MacKeyRepeat.INSTANCE.isEnabled();
final Boolean isKeyRepeat = getEditor().isKeyRepeat();
if ((enabled == null || !enabled) && (isKeyRepeat == null || isKeyRepeat)) {
// This system property is used in IJ ui robot to hide the startup tips
@ -382,7 +386,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (showNotification) {
if (VimPlugin.getNotifications().enableRepeatingMode() == Messages.YES) {
getEditor().setKeyRepeat(true);
keyRepeat.setEnabled(true);
MacKeyRepeat.INSTANCE.setEnabled(true);
}
else {
getEditor().setKeyRepeat(false);

View File

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

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.action
import com.google.common.collect.ImmutableSet
import com.intellij.codeInsight.completion.CompletionService
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
@ -208,6 +209,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
return ActionEnableStatus.yes("Vim only editor keys", LogLevel.INFO)
}
if (CompletionService.getCompletionService().currentCompletion != null) {
return ActionEnableStatus.no("Code completion active", LogLevel.INFO)
}
val savedShortcutConflicts = VimPlugin.getKey().savedShortcutConflicts
val info = savedShortcutConflicts[keyStroke]
return when (info?.forEditor(editor.vim)) {

View File

@ -1,78 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.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
/**
* COMPATIBILITY-LAYER: Additional class
* Please see: https://jb.gg/zo8n0r
*/
@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

@ -11,16 +11,17 @@ package com.maddyhome.idea.vim.customization.feature.terminal
import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isAlternateBufferEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isAlternateBufferModelEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isOutputEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isOutputModelEditor
import org.jetbrains.plugins.terminal.block.util.TerminalDataContextUtils.isPromptEditor
/**
* The only implementation is defined right here.
*/
// [VERSION UPDATE] 2025.1+ Add 2 new predicates
internal class IdeaVimTerminalDisablerExtension : IdeaVimDisablerExtensionPoint {
override fun isDisabledForEditor(editor: Editor): Boolean {
return editor.isPromptEditor || editor.isOutputEditor || editor.isAlternateBufferEditor
// || editor.isOutputModelEditor || editor.isAlternateBufferModelEditor
|| editor.isOutputModelEditor || editor.isAlternateBufferModelEditor
}
}

View File

@ -214,7 +214,13 @@ object VimExtensionFacade {
/** Set the current contents of the given register */
@JvmStatic
fun setRegisterForCaret(register: Char, caret: ImmutableVimCaret, keys: List<KeyStroke?>?) {
fun setRegisterForCaret(
editor: VimEditor,
context: ExecutionContext,
register: Char,
caret: ImmutableVimCaret,
keys: List<KeyStroke?>?,
) {
caret.registerStorage.setKeys(register, keys?.filterNotNull() ?: emptyList())
}
@ -282,4 +288,4 @@ fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFu
fun interface ScriptFunction {
fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
}
}

View File

@ -221,14 +221,16 @@ internal class CommentaryExtension : VimExtension {
val endOffset = editor.vim.getLineEndOffset(logicalLine, true)
val startElement = file.findElementAt(startOffset) ?: return false
var next: PsiElement? = startElement
var hasComment = false
while (next != null && next.textRange.startOffset <= endOffset) {
if (next !is PsiWhiteSpace && !isComment(next)) {
return false
when {
next is PsiWhiteSpace -> {} // Skip whitespace elementl
isComment(next) -> hasComment = true // Mark when we find a comment
else -> return false // Non-comment content found, exit early
}
next = PsiTreeUtil.nextLeaf(next, true)
}
return true
return hasComment
}
private fun isComment(element: PsiElement) =

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,7 @@
*/
package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.actions.AsyncActionExecutionService.Companion.getInstance
import com.intellij.codeInsight.actions.AsyncActionExecutionService
import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.ApplicationManager
@ -15,6 +15,7 @@ import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.TypedActionHandler
import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener
@ -33,7 +34,7 @@ import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.commandContinuation
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
import com.maddyhome.idea.vim.key.KeyHandlerKeeper
import com.maddyhome.idea.vim.listener.VimInsertListener
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimCopiedText
@ -42,7 +43,6 @@ import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
import kotlin.math.min
/**
* Provides all the insert/replace related functionality
@ -62,8 +62,21 @@ class ChangeGroup : VimChangeGroupBase() {
}
override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
doType(vimEditor, context) {
it.execute(vimEditor.ij, key, context.ij)
}
}
override fun type(vimEditor: VimEditor, context: ExecutionContext, string: String) {
doType(vimEditor, context) { handler ->
string.forEach { char ->
handler.execute(vimEditor.ij, char, context.ij)
}
}
}
private fun doType(vimEditor: VimEditor, context: ExecutionContext, action: (TypedActionHandler) -> Unit) {
val editor = (vimEditor as IjVimEditor).editor
val ijContext = context.ij
val doc = vimEditor.editor.document
val undo = injector.undo
@ -76,8 +89,9 @@ class ChangeGroup : VimChangeGroupBase() {
}
CommandProcessor.getInstance().executeCommand(
editor.project, {
ApplicationManager.getApplication()
.runWriteAction { getInstance().originalHandler.execute(editor, key, ijContext) }
ApplicationManager.getApplication().runWriteAction {
action(KeyHandlerKeeper.getInstance().originalHandler)
}
}, "", doc,
UndoConfirmationPolicy.DEFAULT, doc
)
@ -141,6 +155,7 @@ class ChangeGroup : VimChangeGroupBase() {
context: ExecutionContext,
range: TextRange,
) {
val startPos = editor.offsetToBufferPosition(caret.offset)
val startOffset = editor.getLineStartForOffset(range.startOffset)
val endOffset = editor.getLineEndForOffset(range.endOffset)
val ijEditor = (editor as IjVimEditor).editor
@ -150,7 +165,7 @@ class ChangeGroup : VimChangeGroupBase() {
var copiedText: IjVimCopiedText? = null
try {
if (injector.registerGroup.isPrimaryRegisterSupported()) {
copiedText = injector.clipboardManager.getPrimaryContent(editor, context) as IjVimCopiedText
copiedText = injector.clipboardManager.getPrimaryContent() as IjVimCopiedText
}
} catch (e: Exception) {
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
@ -165,15 +180,11 @@ class ChangeGroup : VimChangeGroupBase() {
}
}
val afterAction = {
val firstLine = editor.offsetToBufferPosition(
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
).line
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
caret.moveToOffset(newOffset)
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
}
if (project != null) {
getInstance(project)
AsyncActionExecutionService.getInstance(project)
.withExecutionAfterAction(IdeActions.ACTION_EDITOR_AUTO_INDENT_LINES, actionExecution, afterAction)
} else {
actionExecution.invoke()

View File

@ -31,10 +31,8 @@ open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(sco
// Temporary options to control work-in-progress behaviour
var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
var oldundo: Boolean by optionProperty(IjOptions.oldundo)
var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
}
/**

View File

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

View File

@ -58,7 +58,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
public void registerRequiredShortcutKeys(@NotNull VimEditor editor) {
EventFacade.getInstance()
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(getRequiredShortcutKeys()),
((IjVimEditor)editor).getEditor().getComponent());
((IjVimEditor)editor).getEditor().getContentComponent());
}
public void registerShortcutsForLookup(@NotNull LookupImpl lookup) {
@ -69,7 +69,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
void unregisterShortcutKeys(@NotNull VimEditor editor) {
EventFacade.getInstance().unregisterCustomShortcutSet(VimShortcutKeyAction.getInstance(),
((IjVimEditor)editor).getEditor().getComponent());
((IjVimEditor)editor).getEditor().getContentComponent());
}
@Override

View File

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

View File

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

View File

@ -34,15 +34,13 @@ import com.intellij.openapi.ui.Messages
import com.intellij.openapi.util.SystemInfo
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.handler.KeyMapIssue
import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.icons.VimIcons
import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ijOptions
import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.statistic.ActionTracker
import com.maddyhome.idea.vim.ui.VimEmulationConfigurable
import com.maddyhome.idea.vim.vimscript.services.VimRcService
@ -62,55 +60,11 @@ internal class NotificationService(private val project: Project?) {
@Suppress("unused")
constructor() : this(null)
fun notifyAboutIdeaPut() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"""Add <code>ideaput</code> to <code>clipboard</code> option to perform a put via the IDE<br/><b><code>set clipboard+=ideaput</code></b>""",
NotificationType.INFORMATION,
)
fun notifyAboutNewUndo() {}
notification.addAction(OpenIdeaVimRcAction(notification))
fun notifyAboutIdeaPut() {}
notification.addAction(
AppendToIdeaVimRcAction(
notification,
"set clipboard^=ideaput",
"ideaput",
) {
// Technically, we're supposed to prepend values to clipboard so that it's not added to the "exclude" item.
// Since we don't handle exclude, it's safe to append. But let's be clean.
injector.globalOptions().clipboard.prependValue(OptionConstants.clipboard_ideaput)
},
)
notification.notify(project)
}
fun notifyAboutIdeaJoin(editor: VimEditor) {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"""Put <b><code>set ideajoin</code></b> into your <code>~/.ideavimrc</code> to perform a join via the IDE""",
NotificationType.INFORMATION,
)
notification.addAction(OpenIdeaVimRcAction(notification))
notification.addAction(
AppendToIdeaVimRcAction(
notification,
"set ideajoin",
"ideajoin"
) {
// This is a global-local option. Setting it will always set the global value
injector.ijOptions(editor).ideajoin = true
},
)
notification.addAction(HelpLink(ideajoinExamplesUrl))
notification.notify(project)
}
fun notifyAboutIdeaJoin(editor: VimEditor) {}
fun enableRepeatingMode() = Messages.showYesNoDialog(
"Do you want to enable repeating keys in macOS on press and hold?\n\n" +
@ -182,8 +136,30 @@ internal class NotificationService(private val project: Project?) {
).notify(project)
}
fun notifyActionId(id: String?, candidates: List<String>? = null) {
ActionIdNotifier.notifyActionId(id, project, candidates)
/**
* Shows a notification that the user can reenable IdeaVim by clicking on the IdeaVim icon in the status bar.
*/
fun showReenableNotification(project: Project) {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"IdeaVim has been disabled. You can reenable it by clicking on the gray IdeaVim icon in the status bar.",
NotificationType.INFORMATION,
)
notification.icon = VimIcons.IDEAVIM_DISABLED
notification.addAction(object : DumbAwareAction("Reenable IdeaVim") {
override fun actionPerformed(e: AnActionEvent) {
VimPlugin.setEnabled(true)
notification.expire()
}
})
notification.notify(project)
}
fun notifyActionId(id: String?, candidates: List<String>? = null, intentionName: String?) {
ActionIdNotifier.notifyActionId(id, project, candidates, intentionName)
}
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
@ -196,7 +172,7 @@ internal class NotificationService(private val project: Project?) {
is KeyMapIssue.AddShortcut -> {
appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
}
is KeyMapIssue.RemoveShortcut -> {
appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
}
@ -261,12 +237,15 @@ internal class NotificationService(private val project: Project?) {
object ActionIdNotifier {
private var notification: Notification? = null
fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null) {
fun notifyActionId(id: String?, project: Project?, candidates: List<String>? = null, intentionName: String? = null) {
notification?.expire()
val possibleIDs = candidates?.distinct()?.sorted()
val content = when {
id != null -> "Action ID: <code>$id</code><br><br>"
possibleIDs.isNullOrEmpty() && !intentionName.isNullOrEmpty() -> {
"Intention \"$intentionName\" does not have an action ID.<br><br>"
}
possibleIDs.isNullOrEmpty() -> "<i>Cannot detect action ID</i><br><br>"
possibleIDs.size == 1 -> "Possible action ID: <code>${possibleIDs[0]}</code><br><br>"
else -> {
@ -280,16 +259,16 @@ internal class NotificationService(private val project: Project?) {
notification =
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, content, NotificationType.INFORMATION).also {
it.whenExpired { notification = null }
it.addAction(StopTracking())
it.whenExpired { notification = null }
it.addAction(StopTracking())
if (id != null || possibleIDs?.size == 1) {
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project)
if (id != null || possibleIDs?.size == 1) {
it.addAction(CopyActionId(id ?: possibleIDs?.get(0), project))
}
it.notify(project)
}
if (id != null) {
ActionTracker.Util.logTrackedAction(id)
}

View File

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

View File

@ -44,7 +44,7 @@ internal class SystemMarks {
internal fun Project.createLineBookmark(editor: Editor, line: Int, mnemonic: Char): LineBookmark? {
val bookmarksManager = BookmarksManager.getInstance(this) ?: return null
val lineBookmarkProvider = LineBookmarkProvider.find(this) ?: return null
val lineBookmarkProvider = LineBookmarkProvider.Util.find(this) ?: return null
val bookmark = lineBookmarkProvider.createBookmark(editor, line) as LineBookmark? ?: return null
val type = BookmarkType.get(mnemonic)
if (type == BookmarkType.DEFAULT) return null

View File

@ -19,6 +19,7 @@ import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.util.PlatformUtils
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
@ -36,7 +37,6 @@ import com.maddyhome.idea.vim.ide.isClionNova
import com.maddyhome.idea.vim.ide.isRider
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimCopiedText
import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
@ -127,7 +127,7 @@ internal class PutGroup : VimPutBase() {
point.dispose()
if (!caret.isValid) return@forEach
val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.copiedText.text.length)
val caretPossibleEndOffset = lastPastedRegion?.endOffset ?: (startOffset + text.text.length)
val endOffset = if (data.indent) {
doIndent(
vimEditor,
@ -179,10 +179,12 @@ internal class PutGroup : VimPutBase() {
val allContentsBefore = CopyPasteManager.getInstance().allContents
val sizeBeforeInsert = allContentsBefore.size
val firstItemBefore = allContentsBefore.firstOrNull()
logger.debug { "Copied text: ${text.copiedText}" }
val (textContent, transferableData) = text.copiedText as IjVimCopiedText
logger.debug { "Transferable classes: ${text.transferableData.joinToString { it.javaClass.name }}" }
val origContent: TextBlockTransferable =
injector.clipboardManager.setClipboardText(textContent, textContent, transferableData) as TextBlockTransferable
injector.clipboardManager.setClipboardText(
text.text,
transferableData = text.transferableData,
) as TextBlockTransferable
val allContentsAfter = CopyPasteManager.getInstance().allContents
val sizeAfterInsert = allContentsAfter.size
try {
@ -190,7 +192,7 @@ internal class PutGroup : VimPutBase() {
} finally {
val textInClipboard = (firstItemBefore as? TextBlockTransferable)
?.getTransferData(DataFlavor.stringFlavor) as? String
val textOnTop = textInClipboard != null && textInClipboard != text.copiedText.text
val textOnTop = textInClipboard != null && textInClipboard != text.text
if (sizeBeforeInsert != sizeAfterInsert || textOnTop) {
// Sometimes an inserted text replaces an existing one. E.g. on insert with + or * register
(CopyPasteManager.getInstance() as? CopyPasteManagerEx)?.run { removeContent(origContent) }
@ -205,8 +207,9 @@ internal class PutGroup : VimPutBase() {
startOffset: Int,
endOffset: Int,
): Int {
// Temp fix for VIM-2808. Should be removed after rider will fix it's issues
if (isRider() || isClionNova()) return endOffset
// Temp fix for VIM-2808 for Rider and Clion. Should be removed after rider will fix it's issues
// Disable for client due to VIM-3857
if (isRider() || isClionNova() || PlatformUtils.isJetBrainsClient()) return endOffset
val startLine = editor.offsetToBufferPosition(startOffset).line
val endLine = editor.offsetToBufferPosition(endOffset - 1).line

View File

@ -96,7 +96,7 @@ internal object IdeaSelectionControl {
} else {
logger.debug("None of carets have selection. State before adjustment: ${editor.vim.mode}")
if (editor.vim.inVisualMode) editor.vim.exitVisualMode()
if (editor.vim.inSelectMode) editor.exitSelectMode(false)
if (editor.vim.inSelectMode) editor.vim.exitSelectMode(false)
if (editor.vim.inNormalMode) {
activateMode(editor, chooseNonSelectionMode(editor))

View File

@ -17,7 +17,6 @@ import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.ProjectActivity
import com.jetbrains.rd.util.ConcurrentHashMap
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key
@ -28,6 +27,8 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import org.jetbrains.annotations.NonNls
import java.util.concurrent.ConcurrentHashMap
// We use alarm with delay to avoid many actions in case many events are fired at the same time
@ -67,11 +68,7 @@ internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
check(correctorRequester.tryEmit(Unit))
}
override fun shortcutChanged(keymap: Keymap, actionId: String) {
check(correctorRequester.tryEmit(Unit))
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
override fun shortcutsChanged(keymap: Keymap, actionIds: @NonNls Collection<String>, fromSettings: Boolean) {
check(correctorRequester.tryEmit(Unit))
}
}

View File

@ -28,6 +28,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.launch
import org.jetbrains.annotations.NonNls
import javax.swing.KeyStroke
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
@ -67,11 +68,7 @@ internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
check(keyCheckRequests.tryEmit(Unit))
}
override fun shortcutChanged(keymap: Keymap, actionId: String) {
check(keyCheckRequests.tryEmit(Unit))
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
override fun shortcutsChanged(keymap: Keymap, actionIds: @NonNls Collection<String>, fromSettings: Boolean) {
check(keyCheckRequests.tryEmit(Unit))
}
}

View File

@ -60,7 +60,7 @@ internal fun Editor.updateCaretsVisualAttributes() {
* Used when Vim emulation is disabled
*/
internal fun Editor.removeCaretsVisualAttributes() {
caretModel.allCarets.forEach { it.visualAttributes = CaretVisualAttributes.DEFAULT }
caretModel.allCarets.forEach { it.visualAttributes = CaretVisualAttributes.getDefault() }
}
internal fun Editor.hasBlockOrUnderscoreCaret() = isBlockCursorOverride() ||
@ -95,8 +95,8 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
// NOTE: At the moment, this causes project leak in tests
// IJPL-928 - this will be fixed in 2024.2
// [VERSION UPDATE] 2024.2 - remove if wrapping
// IJPL-928 - this will be fixed in 2025.2
// [VERSION UPDATE] 2025.2 - remove if wrapping
if (!ApplicationManager.getApplication().isUnitTestMode) {
(this as? EditorEx)?.setCaretVisible(true)
}

View File

@ -1,67 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
@Deprecated("Do not use context wrappers, use existing provided contexts. If no context available, use `injector.getExecutionContextManager().getEditorExecutionContext(editor)`")
internal class EditorDataContext @Deprecated("Please use `init` method") constructor(
private val editor: Editor,
private val editorContext: DataContext,
private val contextDelegate: DataContext? = null,
) : DataContext, UserDataHolder {
/**
* Returns the object corresponding to the specified data identifier. Some of the supported data identifiers are
* defined in the [PlatformDataKeys] class.
*
* @param dataId the data identifier for which the value is requested.
* @return the value, or null if no value is available in the current context for this identifier.
*/
override fun getData(dataId: String): Any? = when {
PlatformDataKeys.EDITOR.name == dataId -> editor
PlatformDataKeys.PROJECT.name == dataId -> editor.project
PlatformDataKeys.VIRTUAL_FILE.name == dataId -> EditorHelper.getVirtualFile(editor)
else -> editorContext.getData(dataId) ?: contextDelegate?.getData(dataId)
}
override fun <T : Any?> getUserData(key: Key<T>): T? {
return if (contextDelegate is UserDataHolder) {
contextDelegate.getUserData(key)
} else {
null
}
}
override fun <T : Any?> putUserData(key: Key<T>, value: T?) {
if (contextDelegate is UserDataHolder) {
contextDelegate.putUserData(key, value)
}
}
companion object {
@Suppress("DEPRECATION")
@JvmStatic
fun init(editor: Editor, contextDelegate: DataContext? = null): EditorDataContext {
val editorContext = EditorUtil.getEditorDataContext(editor)
return if (contextDelegate is EditorDataContext) {
if (editor === contextDelegate.editor) {
contextDelegate
} else {
EditorDataContext(editor, editorContext, contextDelegate.contextDelegate)
}
} else {
EditorDataContext(editor, editorContext, contextDelegate)
}
}
}
}

View File

@ -8,6 +8,7 @@
package com.maddyhome.idea.vim.helper;
import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.EditorImpl;
@ -15,6 +16,7 @@ import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileUtil;
import com.intellij.testFramework.LightVirtualFile;
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
import com.maddyhome.idea.vim.api.VimEditor;
@ -342,7 +344,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
@ -652,7 +654,21 @@ public class EditorHelper {
*/
public static boolean isFileEditor(@NotNull Editor editor) {
final VirtualFile virtualFile = getVirtualFile(editor);
return virtualFile != null && !(virtualFile instanceof LightVirtualFile);
if (virtualFile == null) return false;
if (virtualFile instanceof LightVirtualFile) {
var hostVirtualFile = getHostFileFromInjectedFile(virtualFile);
if (hostVirtualFile == null) return false;
return !(hostVirtualFile instanceof LightVirtualFile);
}
return true;
}
private static @Nullable VirtualFile getHostFileFromInjectedFile(@NotNull VirtualFile virtualFile) {
final var vf = VirtualFileUtil.originalFileOrSelf(virtualFile);
if (vf instanceof VirtualFileWindow) {
return ((VirtualFileWindow)vf).getDelegate();
}
return null;
}
/**

View File

@ -12,7 +12,9 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.util.ui.table.JBTableRowEditor
@ -21,6 +23,8 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.key.IdeaVimDisablerExtensionPoint
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inBlockSelection
import java.awt.Component
import javax.swing.JComponent
import javax.swing.JTable
@ -39,9 +43,14 @@ internal val Editor.isIdeaVimDisabledHere: Boolean
return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) ||
!ClientId.isCurrentlyUnderLocalId || // CWM-927
(ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine()) ||
IdeaVimDisablerExtensionPoint.isDisabledForEditor(this)
IdeaVimDisablerExtensionPoint.isDisabledForEditor(this) ||
isAiChat() // VIM-3786
}
private fun Editor.isAiChat(): Boolean {
return EditorHelper.getVirtualFile(this)?.name?.contains("AIAssistantInput") == true
}
private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean {
return !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialog)
&& !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialoglegacy)
@ -68,6 +77,19 @@ internal fun Editor.isPrimaryEditor(): Boolean {
return fileEditorManager.allEditors.any { fileEditor -> this == EditorUtil.getEditorEx(fileEditor) }
}
/**
* Checks if the editor should be treated like a terminal. I.e. switch to Insert mode automatically
*
* A "terminal" editor is an editor used for purposes other than mainstream editing, such as a terminal, console, log
* viewer, etc. In this instance, the editor is writable, the document is writable, but it's not backed by a real file
* and it's not the diff viewer. We also check that if it's an injected language fragment backed by a real file.
*/
internal fun Editor.isTerminalEditor(): Boolean {
return !isViewer
&& document.isWritable
&& this.editorKind == EditorKind.CONSOLE
}
// Optimized clone of com.intellij.ide.ui.laf.darcula.DarculaUIUtil.isTableCellEditor
private fun isTableCellEditor(c: Component): Boolean {
return (java.lang.Boolean.TRUE == (c as JComponent).getClientProperty("JComboBox.isTableCellEditor")) ||
@ -97,4 +119,42 @@ internal val Caret.vimLine: Int
* Get current caret line in vim notation (1-based)
*/
internal val Editor.vimLine: Int
get() = this.caretModel.currentCaret.vimLine
get() = this.caretModel.currentCaret.vimLine
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
val caretModel = this.caretModel
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
if (carets == null || carets.size == 1) {
action()
}
else {
var initialDocumentSize = this.document.textLength
var documentSizeDifference = 0
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
val restoredCarets = mutableListOf<CaretState>()
caretModel.removeSecondaryCarets()
for ((selectionStart, selectionEnd) in caretOffsets) {
if (selectionStart == selectionEnd) {
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
}
else {
caretModel.primaryCaret.setSelection(
selectionStart + documentSizeDifference,
selectionEnd + documentSizeDifference
)
}
action()
restoredCarets.add(caretModel.caretsAndSelections.single())
val documentLength = this.document.textLength
documentSizeDifference += documentLength - initialDocumentSize
initialDocumentSize = documentLength
}
caretModel.caretsAndSelections = restoredCarets
}
}

View File

@ -8,18 +8,15 @@
package com.maddyhome.idea.vim.helper
import com.intellij.openapi.actionSystem.ActionGroup
import com.intellij.execution.actions.StopAction
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.EmptyAction
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.actionSystem.impl.SimpleDataContext
import com.intellij.openapi.application.ex.ApplicationManagerEx
import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy
@ -27,9 +24,7 @@ import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.actionSystem.DocCommandGroupId
import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.registry.Registry
import com.maddyhome.idea.vim.RegisterActions
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.NativeAction
@ -39,11 +34,9 @@ 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.newapi.runFromVimKey
import org.jetbrains.annotations.NonNls
import java.awt.Component
import javax.swing.JComponent
import javax.swing.SwingUtilities
@Service
internal class IjActionExecutor : VimActionExecutor {
@ -62,8 +55,11 @@ internal class IjActionExecutor : VimActionExecutor {
override val ACTION_EXPAND_REGION_RECURSIVELY: String
get() = IdeActions.ACTION_EXPAND_REGION_RECURSIVELY
override val ACTION_EXPAND_COLLAPSE_TOGGLE: String
// [VERSION UPDATE] 2024.3+ Replace raw "ExpandCollapseToggleAction" with IdeActions.ACTION_EXPAND_COLLAPSE_TOGGLE_REGION from the platform.
get() = "ExpandCollapseToggleAction"
get() = IdeActions.ACTION_EXPAND_COLLAPSE_TOGGLE_REGION
override val ACTION_UNDO: String
get() = IdeActions.ACTION_UNDO
override val ACTION_REDO: String
get() = IdeActions.ACTION_REDO
var isRunningActionFromVim: Boolean = false
@ -75,77 +71,28 @@ internal class IjActionExecutor : VimActionExecutor {
}
val ijAction = (action as IjNativeAction).action
if (Registry.`is`("ideavim.old.action.execution", true)) {
return manualActionExecution(context, ijAction)
} else {
try {
isRunningActionFromVim = true
// The context component should be editor. This is especially important when running the `:action` commands
// because at the moment of execution, the focused component is Ex Field, not editor.
val contextComponent = editor?.ij?.contentComponent
val res = ActionManager.getInstance().tryToExecute(ijAction, null, contextComponent, "IdeaVim", true)
res.waitFor(5_000)
return res.isDone
} finally {
isRunningActionFromVim = false
}
try {
isRunningActionFromVim = true
// The context component should be editor. This is especially important when running the `:action` commands
// because at the moment of execution, the focused component is Ex Field, not editor.
val contextComponent = editor?.ij?.contentComponent
val place = ijAction.choosePlace()
val res = ActionManager.getInstance().tryToExecute(ijAction, null, contextComponent, place, true)
res.waitFor(5_000)
return res.isDone
} finally {
isRunningActionFromVim = false
}
}
private fun manualActionExecution(
context: ExecutionContext,
ijAction: AnAction,
): Boolean {
/**
* Data context that defines that some action was started from IdeaVim.
* You can call use [runFromVimKey] key to define if intellij action was started from IdeaVim
*/
val dataContext = SimpleDataContext.getSimpleContext(runFromVimKey, true, context.ij)
val actionId = ActionManager.getInstance().getId(ijAction)
@Suppress("removal", "DEPRECATION") val event = AnActionEvent(
null,
dataContext,
ActionPlaces.KEYBOARD_SHORTCUT,
ijAction.templatePresentation.clone(),
ActionManager.getInstance(),
0,
)
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems
// because rider uses an async update method. See VIM-1819.
// This method executes inside lastUpdateAndCheckDumb
// Another related issue: VIM-2604
// This is a hack to fix the tests and fix VIM-3332
// We should get rid of it in VIM-3376
if (actionId == "RunClass" || actionId == IdeActions.ACTION_COMMENT_LINE || actionId == IdeActions.ACTION_COMMENT_BLOCK) {
@Suppress("removal", "OverrideOnly", "DEPRECATION")
ijAction.beforeActionPerformedUpdate(event)
if (!event.presentation.isEnabled) return false
} else {
if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
}
if (ijAction is ActionGroup && !event.presentation.isPerformGroup) {
// Some ActionGroups should not be performed but shown as a popup
val popup = JBPopupFactory.getInstance()
.createActionGroupPopup(event.presentation.text, ijAction, dataContext, false, null, -1)
val component = dataContext.getData(PlatformDataKeys.CONTEXT_COMPONENT)
if (component != null) {
val window = SwingUtilities.getWindowAncestor(component)
if (window != null) {
popup.showInCenterOf(window)
}
return true
}
popup.showInFocusCenter()
return true
} else {
performDumbAwareWithCallbacks(ijAction, event) {
@Suppress("OverrideOnly")
ijAction.actionPerformed(event)
}
return true
}
// Note: We should find a proper place for the IdeaVim actions
// Currently, we use "IdeaVim" except a few actions
private fun AnAction.choosePlace(): String {
// StopAction works fine if `StopAction.isPlaceGlobal` returns true
// Or if there is a specific data stored in the context. This data, however, is stored
// only if the run window is in focus.
if (this is StopAction) return ActionPlaces.ACTION_SEARCH
return "IdeaVim"
}
/**

View File

@ -1,75 +0,0 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper;
import com.google.common.io.CharStreams;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
/**
* @author vlan
*/
public class MacKeyRepeat {
@VimNlsSafe public static final String FMT = "defaults %s -globalDomain ApplePressAndHoldEnabled";
@NotNull private static final MacKeyRepeat INSTANCE = new MacKeyRepeat();
@NonNls private static final String EXEC_COMMAND = "launchctl stop com.apple.SystemUIServer.agent";
@NonNls private static final String delete = "delete";
@NonNls private static final String write = "write";
@NonNls private static final String read = "read";
public static @NotNull MacKeyRepeat getInstance() {
return INSTANCE;
}
private static @NotNull String read(@NotNull InputStream stream) throws IOException {
return CharStreams.toString(new InputStreamReader(stream));
}
public @Nullable Boolean isEnabled() {
final String command = String.format(FMT, read);
try {
final Process process = Runtime.getRuntime().exec(command);
final String data = read(process.getInputStream()).trim();
try {
return Integer.parseInt(data) == 0;
}
catch (NumberFormatException e) {
return null;
}
}
catch (IOException e) {
return null;
}
}
public void setEnabled(@Nullable Boolean value) {
final String command;
if (value == null) {
command = String.format(FMT, delete);
}
else {
final String arg = value ? "0" : "1";
command = String.format(FMT, write) + " " + arg;
}
try {
final Runtime runtime = Runtime.getRuntime();
final Process defaults = runtime.exec(command);
defaults.waitFor();
final Process restartSystemUI = runtime.exec(EXEC_COMMAND);
restartSystemUI.waitFor();
}
catch (IOException | InterruptedException ignored) {
}
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package com.maddyhome.idea.vim.helper
import com.google.common.io.CharStreams
import java.io.IOException
import java.io.InputStream
import java.io.InputStreamReader
object MacKeyRepeat {
var isEnabled: Boolean?
get() {
return try {
val process = Runtime.getRuntime().exec(READ_COMMAND)
val data = read(process.inputStream).trim().toIntOrNull() ?: return null
data == 0
} catch (_: IOException) {
null
}
}
set(value) {
val command: Array<String>
if (value == null) {
command = DELETE_COMMAND
} else {
val arg = if (value) "0" else "1"
command = WRITE_COMMAND + arg
}
try {
val runtime = Runtime.getRuntime()
val defaults = runtime.exec(command)
defaults.waitFor()
val restartSystemUI: Process = runtime.exec(EXEC_COMMAND)
restartSystemUI.waitFor()
} catch (_: IOException) {
} catch (_: InterruptedException) {
}
}
private val EXEC_COMMAND = arrayOf("launchctl", "stop", "com.apple.SystemUIServer.agent")
private val READ_COMMAND = arrayOf("defaults", "read", "-globalDomain", "ApplePressAndHoldEnabled")
private val WRITE_COMMAND = arrayOf("defaults", "write", "-globalDomain", "ApplePressAndHoldEnabled")
private val DELETE_COMMAND = arrayOf("defaults", "delete", "-globalDomain", "ApplePressAndHoldEnabled")
@Throws(IOException::class)
private fun read(stream: InputStream): String {
return CharStreams.toString(InputStreamReader(stream))
}
}

View File

@ -24,28 +24,6 @@ import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.inSelectMode
/** [adjustCaretPosition] - if true, caret will be moved one char left if it's on the line end */
internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
val vimEditor = this.vim
if (!vimEditor.inSelectMode) return
vimEditor.mode = vimEditor.mode.returnTo
SelectionVimListenerSuppressor.lock().use {
this.caretModel.allCarets.forEach {
// NOTE: I think it should be write action, but the exception shows only an absence of the read action
injector.application.runReadAction { it.removeSelection() }
it.vim.vimSelectionStartClear()
if (adjustCaretPosition && !vimEditor.isEndAllowed) {
val lineEnd = IjVimEditor(this).getLineEndForOffset(it.offset)
val lineStart = IjVimEditor(this).getLineStartForOffset(it.offset)
if (it.offset == lineEnd && it.offset != lineStart) {
it.moveToInlayAwareOffset(it.offset - 1)
}
}
}
}
}
/** [adjustCaretPosition] - if true, caret will be moved one char left if it's on the line end */
internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
if (!this.inSelectMode) return

View File

@ -42,7 +42,7 @@ public class PsiHelper {
if (file == null) {
return -1;
}
StructureViewBuilder structureViewBuilder = LanguageStructureViewBuilder.INSTANCE.getStructureViewBuilder(file);
StructureViewBuilder structureViewBuilder = LanguageStructureViewBuilder.getInstance().getStructureViewBuilder(file);
if (!(structureViewBuilder instanceof TreeBasedStructureViewBuilder builder)) return -1;
StructureViewModel model = builder.createStructureViewModel(editor);

View File

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

View File

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

View File

@ -19,6 +19,8 @@ import com.intellij.openapi.fileEditor.TextEditor
import com.intellij.openapi.fileEditor.TextEditorWithPreview
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.intellij.openapi.util.registry.Registry
import com.intellij.util.PlatformUtils
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor
@ -28,6 +30,8 @@ import com.maddyhome.idea.vim.common.InsertSequence
import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.inVisualMode
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
/**
@ -40,6 +44,12 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
}
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
if (PlatformUtils.isJetBrainsClient()) {
// Note: Remote Dev has special hacks for undo/redo, so we don't use the manager.
// The action is sent directly to the backend using the internal API.
return injector.actionExecutor.executeAction(editor, injector.actionExecutor.ACTION_UNDO, context)
}
val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
val textEditor = getTextEditor(editor.ij)
@ -75,15 +85,7 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.undo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
restoreVisualMode(editor)
}
} else {
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
@ -111,6 +113,10 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
}
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
if (PlatformUtils.isJetBrainsClient()) {
return injector.actionExecutor.executeAction(editor, injector.actionExecutor.ACTION_REDO, context)
}
val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
val textEditor = getTextEditor(editor.ij)
@ -230,4 +236,21 @@ internal class UndoRedoHelper : VimTimestampBasedUndoService {
val hasChanges: Boolean
get() = changeListener.hasChanged || initialPath != editor.getPath()
}
private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().detectSelectionType(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
// visual block mode.
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
SelectionType.CHARACTER_WISE
else
detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
}
}
}

View File

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

View File

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

View File

@ -10,8 +10,6 @@ package com.maddyhome.idea.vim.listener
import com.intellij.execution.impl.ConsoleViewImpl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorKind
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.LastUsedEditorInfo
import com.maddyhome.idea.vim.VimPlugin
@ -19,8 +17,8 @@ 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.common.EditorListener
import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.inInsertMode
import com.maddyhome.idea.vim.helper.isTerminalEditor
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
@ -57,7 +55,7 @@ class IJEditorFocusListener : EditorListener {
// to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
val ijEditor = editor.ij
val isCurrentEditorTerminal = isTerminal(ijEditor)
val isCurrentEditorTerminal = ijEditor.isTerminalEditor()
KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, false)
@ -66,8 +64,10 @@ class IJEditorFocusListener : EditorListener {
VimPlugin.getChange().insertBeforeCursor(editor, context)
KeyHandler.getInstance().lastUsedEditorInfo = LastUsedEditorInfo(currentEditorHashCode, true)
}
if (isCurrentEditorTerminal && !ijEditor.inInsertMode) {
switchToInsertMode.run()
if (isCurrentEditorTerminal) {
if (!ijEditor.inInsertMode) {
switchToInsertMode.run()
}
} else if (ijEditor.isInsertMode && (oldEditorInfo.isInsertModeForced || !ijEditor.document.isWritable)) {
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
val mode = injector.vimState.mode
@ -87,12 +87,4 @@ class IJEditorFocusListener : EditorListener {
}
KeyHandler.getInstance().reset(editor)
}
// By "terminal" we refer to some editor that should switch to INSERT mode on focus
private fun isTerminal(ijEditor: Editor): Boolean {
return !ijEditor.isViewer &&
!EditorHelper.isFileEditor(ijEditor) &&
ijEditor.document.isWritable &&
ijEditor.editorKind != EditorKind.DIFF
}
}

View File

@ -16,8 +16,11 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
import com.intellij.codeInsight.template.Template
import com.intellij.codeInsight.template.TemplateEditingAdapter
import com.intellij.codeInsight.template.TemplateManagerListener
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
import com.intellij.codeInsight.template.impl.TemplateState
import com.intellij.codeInsight.template.impl.actions.NextVariableAction
import com.intellij.find.FindModelListener
import com.intellij.ide.actions.ApplyIntentionAction
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
@ -29,6 +32,7 @@ import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.impl.ScrollingModelImpl
import com.intellij.openapi.keymap.KeymapManager
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.TextRange
@ -60,6 +64,7 @@ internal object IdeaSpecifics {
private val surrounderAction =
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
private var editor: Editor? = null
private var caretOffset = -1
private var completionPrevDocumentLength: Int? = null
private var completionPrevDocumentOffset: Int? = null
@ -69,6 +74,7 @@ internal object IdeaSpecifics {
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
if (hostEditor != null) {
editor = hostEditor
caretOffset = hostEditor.caretModel.offset
}
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
@ -94,10 +100,14 @@ internal object IdeaSpecifics {
} else {
emptyList()
}
val intentionName = if (action is ApplyIntentionAction) {
action.name
}
else null
// We can still get empty ID and empty candidates. Notably, for the tool window toggle buttons on the new UI.
// We could filter out action events with `place == ActionPlaces.TOOLWINDOW_TOOLBAR_BAR`
VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id, candidates)
VimPlugin.getNotifications(event.dataContext.getData(CommonDataKeys.PROJECT)).notifyActionId(id, candidates, intentionName)
}
}
@ -122,17 +132,18 @@ internal object IdeaSpecifics {
if (VimPlugin.isNotEnabled()) return
val editor = editor
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (editor != null) {
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
val prevDocumentLength = completionPrevDocumentLength
val prevDocumentOffset = completionPrevDocumentOffset
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
if (prevDocumentLength != null && prevDocumentOffset != null) {
val register = VimPlugin.getRegister()
val addedTextLength = editor.document.textLength - prevDocumentLength
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
register.recordText(
register.recordText(
editor.document.getText(
TextRange(
prevDocumentOffset,
@ -140,31 +151,49 @@ internal object IdeaSpecifics {
)
)
)
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
repeat(caretShift.coerceAtLeast(0)) {
register.recordKeyStroke(leftArrow)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
}
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
}
}
this.completionPrevDocumentLength = null
this.completionPrevDocumentOffset = null
}
//region Enter insert mode after surround with if
if (surrounderAction == action.javaClass.name && surrounderItems.any {
action.templatePresentation.text.endsWith(
it,
)
else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) {
editor.vim.exitInsertMode(event.dataContext.vim)
KeyHandler.getInstance().reset(editor.vim)
}
) {
editor?.let {
it.vim.mode = Mode.NORMAL()
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
KeyHandler.getInstance().reset(it.vim)
//endregion
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
val scrollModel = editor.scrollingModel as ScrollingModelImpl
if (scrollModel.isScrollingNow) {
val v = scrollModel.verticalScrollOffset
val h = scrollModel.horizontalScrollOffset
scrollModel.finishAnimation()
scrollModel.scroll(h, v)
scrollModel.finishAnimation()
}
injector.scroll.scrollCaretIntoView(editor.vim)
}
}
//endregion
this.editor = null
this.caretOffset = -1
}
}

View File

@ -9,6 +9,7 @@
package com.maddyhome.idea.vim.listener
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionPlaces
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult
@ -16,6 +17,8 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.ex.AnActionListener
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorAction
import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.CaretEvent
import com.intellij.openapi.editor.event.CaretListener
import com.maddyhome.idea.vim.VimPlugin
@ -24,10 +27,16 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
import com.maddyhome.idea.vim.helper.getTopLevelEditor
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
import com.maddyhome.idea.vim.newapi.vim
internal class RiderActionListener : AnActionListener {
private var editor: Editor? = null
private fun shouldExecuteOnFrontend(action: EditorAction): Boolean {
val isInsertMode = editor?.vim?.insertMode
return isInsertMode == false && action is EnterAction
}
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
if (VimPlugin.isNotEnabled()) return
@ -35,6 +44,12 @@ internal class RiderActionListener : AnActionListener {
if (hostEditor != null) {
editor = hostEditor
}
// Fixes RIDER-123506
if (action is EditorAction) {
val key = ActionPlaces.EXECUTE_EDITOR_ACTION_ON_FRONTEND
editor?.putUserData(key, shouldExecuteOnFrontend(action))
}
}
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {

View File

@ -81,7 +81,6 @@ import com.maddyhome.idea.vim.handler.keyCheckRequests
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
import com.maddyhome.idea.vim.helper.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.forceBarCursor
@ -98,6 +97,7 @@ import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
@ -411,10 +411,21 @@ internal object VimListenerManager {
override fun selectionChanged(event: FileEditorManagerEvent) {
// We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
val editor = newEditor.editor
if (editor.isInsertMode) {
editor.vim.mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
// Breaks relativenumber for some reason
// injector.scroll.scrollCaretIntoView(editor.vim)
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event)
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
// VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event)
VimLastSelectedEditorTracker.setLastSelectedEditor(event.newEditor)
}
@ -457,7 +468,9 @@ internal object VimListenerManager {
openingEditor == null -> LocalOptionInitialisationScenario.EDIT
else -> LocalOptionInitialisationScenario.NEW
}
EditorListeners.add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario)
SlowOperations.knownIssue("VIM-3648").use {
EditorListeners.add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario)
}
firstEditorInitialised = true
} else {
// We've got a virtual file, so FileOpenedSyncListener will be called. Save data
@ -485,8 +498,6 @@ internal object VimListenerManager {
OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused)
)
}
VimStandalonePluginUpdateChecker.getInstance().pluginUsed()
}
override fun editorReleased(event: EditorFactoryEvent) {
@ -808,7 +819,7 @@ internal object VimListenerManager {
if (editor.inVisualMode) {
editor.vim.exitVisualMode()
} else if (editor.vim.inSelectMode) {
editor.exitSelectMode(false)
editor.vim.exitSelectMode(false)
KeyHandler.getInstance().reset(editor.vim)
}
}

View File

@ -18,7 +18,7 @@ internal class IntellijMark(bookmark: LineBookmark, override val col: Int, proje
private val project: WeakReference<Project?> = WeakReference(project)
override val key = BookmarksManager.getInstance(project)?.getType(bookmark)?.mnemonic!!
override val key = BookmarksManager.getInstance(project)?.getType(bookmark)?.mnemonic ?: ' '
override val line: Int
get() = getMark()?.line ?: 0
override val filepath: String

View File

@ -39,26 +39,16 @@ import java.io.IOException
@Service
internal class IjClipboardManager : VimClipboardManager {
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getPrimaryTextAndTransferableData")
override fun getPrimaryTextAndTransferableData(): Pair<String, List<Any>?>? {
override fun getPrimaryContent(): IjVimCopiedText? {
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return null
val contents = clipboard.getContents(null) ?: return null
return getTextAndTransferableData(contents)
}
override fun getPrimaryContent(editor: VimEditor, context: ExecutionContext): IjVimCopiedText? {
val (text, transferableData) = getPrimaryTextAndTransferableData() ?: return null
val (text, transferableData) = getTextAndTransferableData(contents) ?: return null
return IjVimCopiedText(text, transferableData ?: emptyList())
}
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getClipboardTextAndTransferableData")
override fun getClipboardTextAndTransferableData(): Pair<String, List<Any>?>? {
val contents = getContents() ?: return null
return getTextAndTransferableData(contents)
}
override fun getClipboardContent(editor: VimEditor, context: ExecutionContext): VimCopiedText? {
val (text, transferableData) = getClipboardTextAndTransferableData() ?: return null
val contents = getContents() ?: return null
val (text, transferableData) = getTextAndTransferableData(contents) ?: return null
return IjVimCopiedText(text, transferableData ?: emptyList())
}
@ -125,14 +115,6 @@ internal class IjClipboardManager : VimClipboardManager {
// return setPrimaryText(entry.text, entry.rawText, entry.transferableData) != null
// }
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#setPrimaryText")
override fun setPrimaryText(text: String, rawText: String, transferableData: List<Any>): Transferable? {
return handleTextSetting(text, rawText, transferableData) { content ->
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return@handleTextSetting null
clipboard.setContents(content, EmptyClipboardOwner.INSTANCE)
}
}
override fun collectCopiedText(
editor: VimEditor,
context: ExecutionContext,
@ -260,6 +242,6 @@ internal class IjClipboardManager : VimClipboardManager {
}
}
data class IjVimCopiedText(override val text: String, val transferableData: List<Any>) : VimCopiedText {
data class IjVimCopiedText(override val text: String, override val transferableData: List<Any>) : VimCopiedText {
override fun updateText(newText: String): VimCopiedText = IjVimCopiedText(newText, transferableData)
}

View File

@ -32,7 +32,7 @@ internal class IjVimApplication : VimApplicationBase() {
return ApplicationManager.getApplication().isDispatchThread
}
override fun invokeLater(action: () -> Unit, editor: VimEditor) {
override fun invokeLater(editor: VimEditor, action: () -> Unit) {
ApplicationManager.getApplication()
.invokeLater(action, ModalityState.stateForComponent((editor as IjVimEditor).editor.component))
}

View File

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

View File

@ -36,7 +36,7 @@ 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.VirtualFile
import com.maddyhome.idea.vim.api.VimVirtualFile
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.IndentConfig
import com.maddyhome.idea.vim.common.LiveRange
@ -179,21 +179,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
return editor.caretModel.allCarets.map { IjVimCaret(it) }
}
override var isFirstCaret = true
override var isReversingCarets = false
@Suppress("ideavimRunForEachCaret")
override fun forEachCaret(action: (VimCaret) -> Unit) {
if (editor.vim.inBlockSelection) {
action(IjVimCaret(editor.caretModel.primaryCaret))
} else {
editor.caretModel.runForEachCaret({
if (it.isValid) {
action(IjVimCaret(it))
}
}, false)
try {
editor.caretModel.runForEachCaret({
if (it.isValid) {
action(IjVimCaret(it))
isFirstCaret = false
}
}, false)
} finally {
isFirstCaret = true
}
}
}
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse)
isReversingCarets = reverse
try {
editor.caretModel.runForEachCaret({
action(IjVimCaret(it))
isFirstCaret = false
}, reverse)
} finally {
isFirstCaret = true
isReversingCarets = false
}
}
override fun isInForEachCaretScope(): Boolean {
@ -284,12 +301,13 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
return editor.logicalPositionToOffset(logicalPosition)
}
override fun getVirtualFile(): VirtualFile? {
override fun getVirtualFile(): VimVirtualFile? {
val vf = EditorHelper.getVirtualFile(editor)
return vf?.let {
object : VirtualFile {
object : VimVirtualFile {
override val path: String = vf.path
override val protocol: String = vf.fileSystem.protocol
override val extension: String? = vf.extension
}
}
}

View File

@ -213,16 +213,6 @@ internal class IjVimInjector : VimInjectorBase() {
override val redrawService: VimRedrawService
get() = service()
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: VimEditor): VimStateMachine {
return vimState
}
@Deprecated("Please use VimInjector.vimState", replaceWith = ReplaceWith("vimState"))
override fun commandStateFor(editor: Any): VimStateMachine {
return vimState
}
override val engineEditorHelper: EngineEditorHelper
get() = service<IjEditorHelper>()
override val editorGroup: VimEditorGroup

View File

@ -0,0 +1,99 @@
/*
* 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.troubleshooting
import com.intellij.openapi.fileEditor.FileEditor
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.EditorNotificationPanel
import com.intellij.ui.EditorNotificationProvider
import com.intellij.ui.EditorNotifications
import com.intellij.util.PlatformUtils
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.icons.VimIcons
import com.maddyhome.idea.vim.vimscript.services.VimRcService
import java.util.function.Function
import javax.swing.JComponent
private var warningExplicitlyDisabled = false
private object CommandsCounter {
var commandsBeforeAutoDisable = 10
var initialized = false
@Synchronized
fun init() {
if (initialized) return
initialized = true
KeyHandler.getInstance().addCommandListener {
commandsBeforeAutoDisable -= 1
if (commandsBeforeAutoDisable <= 0) {
warningExplicitlyDisabled = true
}
}
}
}
/**
* Warning for the new users who may install IdeaVim plugin accidentally.
*/
internal class AccidentalInstallDetectorEditorNotificationProvider : EditorNotificationProvider, DumbAware {
override fun collectNotificationData(
project: Project,
file: VirtualFile,
): Function<in FileEditor, out JComponent?>? {
CommandsCounter.init()
// Note: Currently, enable this only for GoLand as it was a request from this IDE (VIM-3784).
// However, we can enable it for other IDEs if needed.
if (!PlatformUtils.isGoIde()) return null
if (warningExplicitlyDisabled) return null
if (VimPlugin.isNotEnabled()) return null
if (VimRcService.findIdeaVimRc() != null) return null
if (!injector.enabler.isNewIdeaVimUser()) return null
return Function { fileEditor: FileEditor ->
val panel = EditorNotificationPanel(fileEditor, EditorNotificationPanel.Status.Info)
panel.text = getText()
panel.icon(VimIcons.IDEAVIM)
KeyHandler.getInstance().addCommandListener {
if (CommandsCounter.commandsBeforeAutoDisable <= 0) {
KeyHandler.getInstance().removeAllCommandListeners()
EditorNotifications.getInstance(project).removeNotificationsForProvider(this)
}
panel.text = getText()
panel.invalidate()
panel.repaint()
}
@Suppress("DialogTitleCapitalization")
panel.createActionLabel("Disable IdeaVim") {
VimPlugin.setEnabled(false)
VimPlugin.getNotifications(project).showReenableNotification(project)
EditorNotifications.getInstance(project).removeNotificationsForProvider(this)
warningExplicitlyDisabled = true
}
panel.createActionLabel("Dismiss") {
EditorNotifications.getInstance(project).removeNotificationsForProvider(this)
warningExplicitlyDisabled = true
}
panel
}
}
private fun getText(): String {
return "<html>Youre using the IdeaVim plugin. If youre not familiar with Vim, consider disabling it. This message will disappear after ${CommandsCounter.commandsBeforeAutoDisable} commands.</html>"
}
}

View File

@ -353,7 +353,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder()
.calculateCount0Snapshot());
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
int patternEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
final String pattern = searchText.substring(0, patternEnd);
@ -428,14 +428,6 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
return active;
}
/**
* @deprecated Use getVisibleText()
*/
@Deprecated(forRemoval = true)
public @NotNull String getText() {
return entry.getText();
}
@Override
public @NotNull String getVisibleText() {
return entry.getText();

View File

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

View File

@ -85,11 +85,23 @@ E545=E545: Missing colon: {0}
E546=E546: Illegal mode: {0}
E548=E548: Digit expected: {0}
E549=E549: Illegal percentage: {0}
E691=E691: Can only compare List with List
E692=E692: Invalid operation for List
E694=E694: Invalid operation for Funcrefs
E695=E695: Cannot index a Funcref
E701=E701: Invalid type for len()
E703=E703: Using a Funcref as a Number
E728=E728: Using a Dictionary as a Number
E729=E729: Using a Funcref as a String
E730=E730: Using a List as a String
E731=E731: Using a Dictionary as a String
E735=E735: Can only compare Dictionary with Dictionary
E736=E736: Invalid operation for Dictionary
E745=E745: Using a List as a Number
E774=E774: 'operatorfunc' is empty
E804=E804: Cannot use '%' with Float
E805=E805: Using a Float as a Number
E806=E806: Using a Float as a String
E808=E808: Number or Float required
e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command
E939=E939: Positive count required

View File

@ -0,0 +1,108 @@
/*
* 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.action
import com.maddyhome.idea.vim.api.injector
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class ChangeCaseTest : VimTestCase() {
/**
* Note: The tests for duplicated commands (gugu and gUgU) might fail due to issues with the test environment,
* specifically related to file refresh operations. This is a known issue with the test infrastructure
* and not with the actual functionality being tested.
*
* The tests for guu and gUU test the same functionality and should pass.
*/
@Test
fun testChangeCaseLowerLineAction() {
typeTextInFile(
injector.parser.parseKeys("guu"),
"""
H${c}ELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent(),
)
assertState(
"""
${c}hello world
${c}this is a test
${c}for case conversion
""".trimIndent()
)
}
@Disabled("Not yet supported")
@Test
fun testChangeCaseLowerLineActionDuplicated() {
typeTextInFile(
injector.parser.parseKeys("gugu"),
"""
H${c}ELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent(),
)
assertState(
"""
${c}hello world
${c}this is a test
${c}for case conversion
""".trimIndent()
)
}
@Test
fun testChangeCaseUpperLineAction() {
typeTextInFile(
injector.parser.parseKeys("gUU"),
"""
h${c}ello world
${c}this is a test
${c}for case conversion
""".trimIndent(),
)
assertState(
"""
${c}HELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent()
)
}
@Disabled("Not yet supported")
@Test
fun testChangeCaseUpperLineActionDuplicated() {
typeTextInFile(
injector.parser.parseKeys("gUgU"),
"""
h${c}ello world
${c}this is a test
${c}for case conversion
""".trimIndent(),
)
assertState(
"""
${c}HELLO WORLD
${c}THIS IS A TEST
${c}FOR CASE CONVERSION
""".trimIndent()
)
}
}

View File

@ -0,0 +1,102 @@
/*
* 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.action
import com.maddyhome.idea.vim.api.injector
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class ChangeRot13Test : VimTestCase() {
@Test
fun testChangeRot13MotionAction() {
typeTextInFile(
injector.parser.parseKeys("g?2w"),
"H${c}ello World ${c}This is a ${c}test for ROT13 ${c}encoding\n",
)
assertState("H${c}ryyb Jbeyq ${c}Guvf vf a ${c}grfg sbe ROT13 ${c}rapbqvat\n")
}
@Test
fun testChangeRot13VisualAction() {
typeTextInFile(
injector.parser.parseKeys("v2wg?"),
"H${c}ello World ${c}This is a ${c}test for ROT13 ${c}encoding\n",
)
assertState("H${c}ryyb Jbeyq Guvf vf n ${c}grfg sbe EOT13 ${c}rapbqvat\n")
}
@Test
fun testChangeRot13LineAction() {
typeTextInFile(
injector.parser.parseKeys("g??"),
"""
H${c}ello World
${c}This is a test
${c}for ROT13 encoding
""".trimIndent(),
)
assertState(
"""
${c}Uryyb Jbeyq
${c}Guvf vf n grfg
${c}sbe EBG13 rapbqvat
""".trimIndent()
)
}
@Disabled("Not yet supported")
@Test
fun testChangeRot13LineActionDuplicated() {
typeTextInFile(
injector.parser.parseKeys("g?g?"),
"""
H${c}ello World
${c}This is a test
${c}for ROT13 encoding
""".trimIndent(),
)
assertState(
"""
${c}Uryyb Jbeyq
${c}Guvf vf n grfg
${c}sbe EBG13 rapbqvat
""".trimIndent()
)
}
@Test
fun testChangeRot13NonEnglishLetters() {
typeTextInFile(
injector.parser.parseKeys("g?$"),
"${c}Привет мир! Hello world!\n",
)
assertState("${c}Привет мир! Uryyb jbeyq!\n")
}
@Test
fun testChangeRot13FullAlphabet() {
typeTextInFile(
injector.parser.parseKeys("g?$"),
"${c}abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\n",
)
assertState("${c}nopqrstuvwxyzabcdefghijklmNOPQRSTUVWXYZABCDEFGHIJKLM\n")
}
@Test
fun testChangeRot13Symbols() {
typeTextInFile(
injector.parser.parseKeys("g?$"),
"${c}!@#$%^&*()_+-=[]{}|;:'\",.<>/?\n",
)
assertState("${c}!@#$%^&*()_+-=[]{}|;:'\",.<>/?\n")
}
}

View File

@ -9,7 +9,6 @@
package org.jetbrains.plugins.ideavim.action.copy
import com.intellij.notification.ActionCenter
import com.intellij.notification.EventLog
import com.intellij.notification.Notification
import com.intellij.openapi.application.ApplicationManager
import com.maddyhome.idea.vim.VimPlugin
@ -105,7 +104,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
}
typeText(injector.parser.parseKeys("p"))
val notifications = EventLog.getLogModel(fixture.project).notifications
val notifications = ActionCenter.getNotifications(fixture.project)
kotlin.test.assertTrue(notifications.isEmpty() || notifications.last().isExpired || OptionConstants.clipboard_ideaput !in notifications.last().content)
}

View File

@ -77,4 +77,12 @@ class AsciiCommandTest : VimTestCase() {
enterCommand("ascii")
assertEquals("<⓪> 9450, Hex 24ea, Oct 22352, Digr (0", VimPlugin.getMessage())
}
@Test
fun `test shows custom digraph with 32-bit Unicode codepoint`() {
configureByText("🔴")
enterCommand("digraph cr 128308")
enterCommand("ascii")
assertEquals("<🔴> 128308, Hex 1f534, Oct 372464, Digr cr", VimPlugin.getMessage())
}
}

View File

@ -33,20 +33,26 @@ class DigraphsCommandTest : VimTestCase() {
@Test
fun `test add custom digraph`() {
enterCommand("digraph (0 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar())
}
@Test
fun `test add custom 32-bit digraph`() {
enterCommand("digraph cr 128308")
assertEquals("🔴", String(Character.toChars(injector.digraphGroup.getCharacterForDigraph('c', 'r'))))
}
@Test
fun `test add custom digraph matches reversed characters`() {
enterCommand("digraph (0 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('0', '('))
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('0', '(').toChar())
}
@Test
fun `test add multiple custom digraphs`() {
enterCommand("digraph (0 9450 (1 9312")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1'))
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar())
assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1').toChar())
}
@Test
@ -80,14 +86,14 @@ class DigraphsCommandTest : VimTestCase() {
@Test
fun `test add custom digraph with more than two characters add custom digraph with initial two characters`() {
enterCommand("digraph aaaa 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('a', 'a'))
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('a', 'a').toChar())
}
@Test
fun `test add custom digraphs until error`() {
enterCommand("digraph (0 9450 (1 9312 (2")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1'))
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar())
assertEquals('①', injector.digraphGroup.getCharacterForDigraph('(', '1').toChar())
assertPluginError(true)
assertPluginErrorMessageContains("E39: Number expected")
}
@ -95,15 +101,15 @@ class DigraphsCommandTest : VimTestCase() {
@Test
fun `test custom digraph overwrites existing custom digraph`() {
enterCommand("digraph (0 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0'))
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar())
enterCommand("digraph (0 10003")
assertEquals('✓', injector.digraphGroup.getCharacterForDigraph('(', '0'))
assertEquals('✓', injector.digraphGroup.getCharacterForDigraph('(', '0').toChar())
}
@Test
fun `test custom digraph overwrites existing default digraph`() {
enterCommand("digraph OK 9450")
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('O', 'K'))
assertEquals('⓪', injector.digraphGroup.getCharacterForDigraph('O', 'K').toChar())
}
@Test
@ -865,6 +871,244 @@ class DigraphsCommandTest : VimTestCase() {
)
}
@Test
fun `test digraph output with 32-bit custom digraphs`() {
enterCommand("digraph cr 128308") // 🔴
assertCommandOutput(
"digraphs",
"""
|NU ^@ 10 SH ^A 1 SX ^B 2 EX ^C 3 ET ^D 4 EQ ^E 5
|AK ^F 6 BL ^G 7 BS ^H 8 HT ^I 9 LF ^J 10 VT ^K 11
|FF ^L 12 CR ^M 13 SO ^N 14 SI ^O 15 DL ^P 16 D1 ^Q 17
|D2 ^R 18 D3 ^S 19 D4 ^T 20 NK ^U 21 SY ^V 22 EB ^W 23
|CN ^X 24 EM ^Y 25 SB ^Z 26 EC ^[ 27 FS ^\ 28 GS ^] 29
|RS ^^ 30 US ^_ 31 SP 32 Nb # 35 DO $ 36 At @ 64
|<( [ 91 // \ 92 )> ] 93 '> ^ 94 '! ` 96 (! { 123
|!! | 124 !) } 125 '? ~ 126 DT ^? 127 PA <80> 128 HO <81> 129
|BH <82> 130 NH <83> 131 IN <84> 132 NL <85> 133 SA <86> 134 ES <87> 135
|HS <88> 136 HJ <89> 137 VS <8a> 138 PD <8b> 139 PU <8c> 140 RI <8d> 141
|S2 <8e> 142 S3 <8f> 143 DC <90> 144 P1 <91> 145 P2 <92> 146 TS <93> 147
|CC <94> 148 MW <95> 149 SG <96> 150 EG <97> 151 SS <98> 152 GC <99> 153
|SC <9a> 154 CI <9b> 155 ST <9c> 156 OC <9d> 157 PM <9e> 158 AC <9f> 159
|NS   160 !I ¡ 161 ~! ¡ 161 Ct ¢ 162 c| ¢ 162 Pd £ 163
|$$ £ 163 Cu ¤ 164 ox ¤ 164 Ye ¥ 165 Y- ¥ 165 BB ¦ 166
||| ¦ 166 SE § 167 ': ¨ 168 Co © 169 cO © 169 -a ª 170
|<< « 171 NO ¬ 172 -, ¬ 172 -- <ad> 173 Rg ® 174 'm ¯ 175
|-= ¯ 175 DG ° 176 ~o ° 176 +- ± 177 2S ² 178 22 ² 178
|3S ³ 179 33 ³ 179 '' ´ 180 My µ 181 PI 182 pp 182
|.M · 183 ~. · 183 ', ¸ 184 1S ¹ 185 11 ¹ 185 -o º 186
|>> » 187 14 ¼ 188 12 ½ 189 34 ¾ 190 ?I ¿ 191 ~? ¿ 191
|A! À 192 A` À 192 A' Á 193 A> Â 194 A^ Â 194 A? Ã 195
|A~ Ã 195 A: Ä 196 A" Ä 196 AA Å 197 A@ Å 197 AE Æ 198
|C, Ç 199 E! È 200 E` È 200 E' É 201 E> Ê 202 E^ Ê 202
|E: Ë 203 E" Ë 203 I! Ì 204 I` Ì 204 I' Í 205 I> Î 206
|I^ Î 206 I: Ï 207 I" Ï 207 D- Ð 208 N? Ñ 209 N~ Ñ 209
|O! Ò 210 O` Ò 210 O' Ó 211 O> Ô 212 O^ Ô 212 O? Õ 213
|O~ Õ 213 O: Ö 214 *X × 215 /\ × 215 O/ Ø 216 U! Ù 217
|U` Ù 217 U' Ú 218 U> Û 219 U^ Û 219 U: Ü 220 Y' Ý 221
|TH Þ 222 Ip Þ 222 ss ß 223 a! à 224 a` à 224 a' á 225
|a> â 226 a^ â 226 a? ã 227 a~ ã 227 a: ä 228 a" ä 228
|aa å 229 a@ å 229 ae æ 230 c, ç 231 e! è 232 e` è 232
|e' é 233 e> ê 234 e^ ê 234 e: ë 235 e" ë 235 i! ì 236
|i` ì 236 i' í 237 i> î 238 i^ î 238 i: ï 239 d- ð 240
|n? ñ 241 n~ ñ 241 o! ò 242 o` ò 242 o' ó 243 o> ô 244
|o^ ô 244 o? õ 245 o~ õ 245 o: ö 246 -: ÷ 247 o/ ø 248
|u! ù 249 u` ù 249 u' ú 250 u> û 251 u^ û 251 u: ü 252
|y' ý 253 th þ 254 y: ÿ 255 y" ÿ 255 A- Ā 256 a- ā 257
|A( Ă 258 a( ă 259 A; Ą 260 a; ą 261 C' Ć 262 c' ć 263
|C> Ĉ 264 c> ĉ 265 C. Ċ 266 c. ċ 267 C< Č 268 c< č 269
|D< Ď 270 d< ď 271 D/ Đ 272 d/ đ 273 E- Ē 274 e- ē 275
|E( Ĕ 276 e( ĕ 277 E. Ė 278 e. ė 279 E; Ę 280 e; ę 281
|E< Ě 282 e< ě 283 G> Ĝ 284 g> ĝ 285 G( Ğ 286 g( ğ 287
|G. Ġ 288 g. ġ 289 G, Ģ 290 g, ģ 291 H> Ĥ 292 h> ĥ 293
|H/ Ħ 294 h/ ħ 295 I? Ĩ 296 i? ĩ 297 I- Ī 298 i- ī 299
|I( Ĭ 300 i( ĭ 301 I; Į 302 i; į 303 I. İ 304 i. ı 305
|IJ IJ 306 ij ij 307 J> Ĵ 308 j> ĵ 309 K, Ķ 310 k, ķ 311
|kk ĸ 312 L' Ĺ 313 l' ĺ 314 L, Ļ 315 l, ļ 316 L< Ľ 317
|l< ľ 318 L. Ŀ 319 l. ŀ 320 L/ Ł 321 l/ ł 322 N' Ń 323
|n' ń 324 N, Ņ 325 n, ņ 326 N< Ň 327 n< ň 328 'n ʼn 329
|NG Ŋ 330 ng ŋ 331 O- Ō 332 o- ō 333 O( Ŏ 334 o( ŏ 335
|O" Ő 336 o" ő 337 OE Œ 338 oe œ 339 R' Ŕ 340 r' ŕ 341
|R, Ŗ 342 r, ŗ 343 R< Ř 344 r< ř 345 S' Ś 346 s' ś 347
|S> Ŝ 348 s> ŝ 349 S, Ş 350 s, ş 351 S< Š 352 s< š 353
|T, Ţ 354 t, ţ 355 T< Ť 356 t< ť 357 T/ Ŧ 358 t/ ŧ 359
|U? Ũ 360 u? ũ 361 U- Ū 362 u- ū 363 U( Ŭ 364 u( ŭ 365
|U0 Ů 366 u0 ů 367 U" Ű 368 u" ű 369 U; Ų 370 u; ų 371
|W> Ŵ 372 w> ŵ 373 Y> Ŷ 374 y> ŷ 375 Y: Ÿ 376 Z' Ź 377
|z' ź 378 Z. Ż 379 z. ż 380 Z< Ž 381 z< ž 382 O9 Ơ 416
|o9 ơ 417 OI Ƣ 418 oi ƣ 419 yr Ʀ 422 U9 Ư 431 u9 ư 432
|Z/ Ƶ 437 z/ ƶ 438 ED Ʒ 439 A< Ǎ 461 a< ǎ 462 I< Ǐ 463
|i< ǐ 464 O< Ǒ 465 o< ǒ 466 U< Ǔ 467 u< ǔ 468 A1 Ǟ 478
|a1 ǟ 479 A7 Ǡ 480 a7 ǡ 481 A3 Ǣ 482 a3 ǣ 483 G/ Ǥ 484
|g/ ǥ 485 G< Ǧ 486 g< ǧ 487 K< Ǩ 488 k< ǩ 489 O; Ǫ 490
|o; ǫ 491 O1 Ǭ 492 o1 ǭ 493 EZ Ǯ 494 ez ǯ 495 j< ǰ 496
|G' Ǵ 500 g' ǵ 501 ;S ʿ 703 '< ˇ 711 '( ˘ 728 '. ˙ 729
|'0 ˚ 730 '; ˛ 731 '" ˝ 733 A% Ά 902 E% Έ 904 Y% Ή 905
|I% Ί 906 O% Ό 908 U% Ύ 910 W% Ώ 911 i3 ΐ 912 A* Α 913
|B* Β 914 G* Γ 915 D* Δ 916 E* Ε 917 Z* Ζ 918 Y* Η 919
|H* Θ 920 I* Ι 921 K* Κ 922 L* Λ 923 M* Μ 924 N* Ν 925
|C* Ξ 926 O* Ο 927 P* Π 928 R* Ρ 929 S* Σ 931 T* Τ 932
|U* Υ 933 F* Φ 934 X* Χ 935 Q* Ψ 936 W* Ω 937 J* Ϊ 938
|V* Ϋ 939 a% ά 940 e% έ 941 y% ή 942 i% ί 943 u3 ΰ 944
|a* α 945 b* β 946 g* γ 947 d* δ 948 e* ε 949 z* ζ 950
|y* η 951 h* θ 952 i* ι 953 k* κ 954 l* λ 955 m* μ 956
|n* ν 957 c* ξ 958 o* ο 959 p* π 960 r* ρ 961 *s ς 962
|s* σ 963 t* τ 964 u* υ 965 f* φ 966 x* χ 967 q* ψ 968
|w* ω 969 j* ϊ 970 v* ϋ 971 o% ό 972 u% ύ 973 w% ώ 974
|'G Ϙ 984 ,G ϙ 985 T3 Ϛ 986 t3 ϛ 987 M3 Ϝ 988 m3 ϝ 989
|K3 Ϟ 990 k3 ϟ 991 P3 Ϡ 992 p3 ϡ 993 '% ϴ 1012 j3 ϵ 1013
|IO Ё 1025 D% Ђ 1026 G% Ѓ 1027 IE Є 1028 DS Ѕ 1029 II І 1030
|YI Ї 1031 J% Ј 1032 LJ Љ 1033 NJ Њ 1034 Ts Ћ 1035 KJ Ќ 1036
|V% Ў 1038 DZ Џ 1039 A= А 1040 B= Б 1041 V= В 1042 G= Г 1043
|D= Д 1044 E= Е 1045 Z% Ж 1046 Z= З 1047 I= И 1048 J= Й 1049
|K= К 1050 L= Л 1051 M= М 1052 N= Н 1053 O= О 1054 P= П 1055
|R= Р 1056 S= С 1057 T= Т 1058 U= У 1059 F= Ф 1060 H= Х 1061
|C= Ц 1062 C% Ч 1063 S% Ш 1064 Sc Щ 1065 =" Ъ 1066 Y= Ы 1067
|%" Ь 1068 JE Э 1069 JU Ю 1070 JA Я 1071 a= а 1072 b= б 1073
|v= в 1074 g= г 1075 d= д 1076 e= е 1077 z% ж 1078 z= з 1079
|i= и 1080 j= й 1081 k= к 1082 l= л 1083 m= м 1084 n= н 1085
|o= о 1086 p= п 1087 r= р 1088 s= с 1089 t= т 1090 u= у 1091
|f= ф 1092 h= х 1093 c= ц 1094 c% ч 1095 s% ш 1096 sc щ 1097
|=' ъ 1098 y= ы 1099 %' ь 1100 je э 1101 ju ю 1102 ja я 1103
|io ё 1105 d% ђ 1106 g% ѓ 1107 ie є 1108 ds ѕ 1109 ii і 1110
|yi ї 1111 j% ј 1112 lj љ 1113 nj њ 1114 ts ћ 1115 kj ќ 1116
|v% ў 1118 dz џ 1119 Y3 Ѣ 1122 y3 ѣ 1123 O3 Ѫ 1130 o3 ѫ 1131
|F3 Ѳ 1138 f3 ѳ 1139 V3 Ѵ 1140 v3 ѵ 1141 C3 Ҁ 1152 c3 ҁ 1153
|G3 Ґ 1168 g3 ґ 1169 A+ א 1488 B+ ב 1489 G+ ג 1490 D+ ד 1491
|H+ ה 1492 W+ ו 1493 Z+ ז 1494 X+ ח 1495 Tj ט 1496 J+ י 1497
|K% ך 1498 K+ כ 1499 L+ ל 1500 M% ם 1501 M+ מ 1502 N% ן 1503
|N+ נ 1504 S+ ס 1505 E+ ע 1506 P% ף 1507 P+ פ 1508 Zj ץ 1509
|ZJ צ 1510 Q+ ק 1511 R+ ר 1512 Sh ש 1513 T+ ת 1514 ,+ ، 1548
|;+ ؛ 1563 ?+ ؟ 1567 H' ء 1569 aM آ 1570 aH أ 1571 wH ؤ 1572
|ah إ 1573 yH ئ 1574 a+ ا 1575 b+ ب 1576 tm ة 1577 t+ ت 1578
|tk ث 1579 g+ ج 1580 hk ح 1581 x+ خ 1582 d+ د 1583 dk ذ 1584
|r+ ر 1585 z+ ز 1586 s+ س 1587 sn ش 1588 c+ ص 1589 dd ض 1590
|tj ط 1591 zH ظ 1592 e+ ع 1593 i+ غ 1594 ++ ـ 1600 f+ ف 1601
|q+ ق 1602 k+ ك 1603 l+ ل 1604 m+ م 1605 n+ ن 1606 h+ ه 1607
|w+ و 1608 j+ ى 1609 y+ ي 1610 :+ ً 1611 "+ ٌ 1612 =+ ٍ 1613
|/+ َ 1614 '+ ُ 1615 1+ ِ 1616 3+ ّ 1617 0+ ْ 1618 aS ٰ 1648
|p+ پ 1662 v+ ڤ 1700 gf گ 1711 0a ۰ 1776 1a ۱ 1777 2a ۲ 1778
|3a ۳ 1779 4a ۴ 1780 5a ۵ 1781 6a ۶ 1782 7a ۷ 1783 8a ۸ 1784
|9a ۹ 1785 B. 7682 b. 7683 B_ 7686 b_ 7687 D. 7690
|d. 7691 D_ 7694 d_ 7695 D, 7696 d, 7697 F. 7710
|f. 7711 G- 7712 g- 7713 H. 7714 h. 7715 H: 7718
|h: 7719 H, 7720 h, 7721 K' 7728 k' 7729 K_ 7732
|k_ 7733 L_ 7738 l_ 7739 M' 7742 m' ḿ 7743 M. 7744
|m. 7745 N. 7748 n. 7749 N_ 7752 n_ 7753 P' 7764
|p' 7765 P. 7766 p. 7767 R. 7768 r. 7769 R_ 7774
|r_ 7775 S. 7776 s. 7777 T. 7786 t. 7787 T_ 7790
|t_ 7791 V? 7804 v? 7805 W! 7808 W` 7808 w! 7809
|w` 7809 W' 7810 w' 7811 W: 7812 w: 7813 W. 7814
|w. 7815 X. 7818 x. 7819 X: 7820 x: 7821 Y. 7822
|y. 7823 Z> 7824 z> 7825 Z_ 7828 z_ 7829 h_ 7830
|t: 7831 w0 7832 y0 7833 A2 7842 a2 7843 E2 7866
|e2 7867 E? 7868 e? 7869 I2 7880 i2 7881 O2 7886
|o2 7887 U2 7910 u2 7911 Y! 7922 Y` 7922 y! 7923
|y` 7923 Y2 7926 y2 7927 Y? 7928 y? 7929 ;' 7936
|,' 7937 ;! 7938 ,! 7939 ?; 7940 ?, 7941 !: 7942
|?: 7943 1N 8194 1M 8195 3M 8196 4M 8197 6M 8198
|1T 8201 1H 8202 -1 8208 -N 8211 -M 8212 -3 8213
|!2 8214 =2 8215 '6 8216 '9 8217 .9 8218 9' 8219
|"6 “ 8220 "9 8221 :9 8222 9" ‟ 8223 /- † 8224 /= ‡ 8225
|oo 8226 .. 8229 ,. 8230 %0 8240 1' 8242 2' 8243
|3' 8244 4' 8279 1" 8245 2" 8246 3" ‷ 8247 Ca ‸ 8248
|<1 8249 >1 8250 :X 8251 '- 8254 /f 8260 0S 8304
|4S 8308 5S 8309 6S 8310 7S 8311 8S 8312 9S 8313
|+S 8314 -S 8315 =S 8316 (S 8317 )S 8318 nS 8319
|0s 8320 1s 8321 2s 8322 3s 8323 4s 8324 5s 8325
|6s 8326 7s 8327 8s 8328 9s 8329 +s 8330 -s 8331
|=s 8332 (s 8333 )s 8334 Li 8356 Pt 8359 W= 8361
|=e 8364 Eu 8364 =R 8381 =P 8381 oC 8451 co 8453
|oF 8457 N0 8470 PO 8471 Rx 8478 SM 8480 TM 8482
|Om 8486 AO 8491 13 8531 23 8532 15 8533 25 8534
|35 8535 45 8536 16 8537 56 8538 18 8539 38 8540
|58 8541 78 8542 1R 8544 2R 8545 3R 8546 4R 8547
|5R 8548 6R 8549 7R 8550 8R 8551 9R 8552 aR 8553
|bR 8554 cR 8555 1r 8560 2r 8561 3r 8562 4r 8563
|5r 8564 6r 8565 7r 8566 8r 8567 9r 8568 ar 8569
|br 8570 cr 8571 <- 8592 -! 8593 -> 8594 -v 8595
|<> 8596 UD 8597 <= 8656 => 8658 == 8660 FA 8704
|dP 8706 TE 8707 /0 8709 DE 8710 NB 8711 (- 8712
|-) 8715 *P 8719 +Z 8721 -2 8722 -+ 8723 *- 8727
|Ob 8728 Sb 8729 RT 8730 0( 8733 00 8734 -L 8735
|-V 8736 PP 8741 AN 8743 OR 8744 (U 8745 )U 8746
|In 8747 DI 8748 Io 8750 .: 8756 :. 8757 :R 8758
|:: 8759 ?1 8764 CG 8766 ?- 8771 ?= 8773 ?2 8776
|=? 8780 HI 8787 != 8800 =3 8801 =< 8804 >= 8805
|<* 8810 *> 8811 !< 8814 !> 8815 (C 8834 )C 8835
|(_ 8838 )_ 8839 0. 8857 02 8858 -T 8869 .P 8901
|:3 8942 .3 8943 Eh 8962 <7 8968 >7 8969 7< 8970
|7> 8971 NI 8976 (A 8978 TR 8981 Iu 8992 Il 8993
|</ 9001 /> 9002 Vs 9251 1h 9280 3h 9281 2h 9282
|4h 9283 1j 9286 2j 9287 3j 9288 4j 9289 1. 9352
|2. 9353 3. 9354 4. 9355 5. 9356 6. 9357 7. 9358
|8. 9359 9. 9360 hh 9472 HH 9473 vv 9474 VV 9475
|3- 9476 3_ 9477 3! 9478 3/ 9479 4- 9480 4_ 9481
|4! 9482 4/ 9483 dr 9484 dR 9485 Dr 9486 DR 9487
|dl 9488 dL 9489 Dl 9490 LD 9491 ur 9492 uR 9493
|Ur 9494 UR 9495 ul 9496 uL 9497 Ul 9498 UL 9499
|vr 9500 vR 9501 Vr 9504 VR 9507 vl 9508 vL 9509
|Vl 9512 VL 9515 dh 9516 dH 9519 Dh 9520 DH 9523
|uh 9524 uH 9527 Uh 9528 UH 9531 vh 9532 vH 9535
|Vh 9538 VH 9547 FD 9585 BD 9586 TB 9600 LB 9604
|FB 9608 lB 9612 RB 9616 .S 9617 :S 9618 ?S 9619
|fS 9632 OS 9633 RO 9634 Rr 9635 RF 9636 RY 9637
|RH 9638 RZ 9639 RK 9640 RX 9641 sB 9642 SR 9644
|Or 9645 UT 9650 uT 9651 PR 9654 Tr 9655 Dt 9660
|dT 9661 PL 9664 Tl 9665 Db 9670 Dw 9671 LZ 9674
|0m 9675 0o 9678 0M 9679 0L 9680 0R 9681 Sn 9688
|Ic 9689 Fd 9698 Bd 9699 *2 9733 *1 9734 <H 9756
|>H 9758 0u 9786 0U 9787 SU 9788 Fm 9792 Ml 9794
|cS 9824 cH 9825 cD 9826 cC 9827 Md 9833 M8 9834
|M2 9835 Mb 9837 Mx 9838 MX 9839 OK 10003 XX 10007
|-X 10016 IS   12288 ,_ 12289 ._ 12290 +" 〃 12291 +_ 〄 12292
|*_ 12293 ;_ 12294 0_ 12295 <+ 12298 >+ 12299 <' 12300
|>' 12301 <" 『 12302 >" 12303 (" 【 12304 )" 12305 =T 12306
|=_ 12307 (' 12308 )' 12309 (I 12310 )I 12311 -? 12316
|A5 12353 a5 12354 I5 12355 i5 12356 U5 12357 u5 12358
|E5 12359 e5 12360 O5 12361 o5 12362 ka 12363 ga 12364
|ki 12365 gi 12366 ku 12367 gu 12368 ke 12369 ge 12370
|ko 12371 go 12372 sa 12373 za 12374 si 12375 zi 12376
|su 12377 zu 12378 se 12379 ze 12380 so 12381 zo 12382
|ta 12383 da 12384 ti 12385 di 12386 tU 12387 tu 12388
|du 12389 te 12390 de 12391 to 12392 do 12393 na 12394
|ni 12395 nu 12396 ne 12397 no 12398 ha 12399 ba 12400
|pa 12401 hi 12402 bi 12403 pi 12404 hu 12405 bu 12406
|pu 12407 he 12408 be 12409 pe 12410 ho 12411 bo 12412
|po 12413 ma 12414 mi 12415 mu 12416 me 12417 mo 12418
|yA 12419 ya 12420 yU 12421 yu 12422 yO 12423 yo 12424
|ra 12425 ri 12426 ru 12427 re 12428 ro 12429 wA 12430
|wa 12431 wi 12432 we 12433 wo 12434 n5 12435 vu 12436
|"5 ゛ 12443 05 ゜ 12444 *5 ゝ 12445 +5 ゞ 12446 a6 ァ 12449 A6 ア 12450
|i6 12451 I6 12452 u6 12453 U6 12454 e6 12455 E6 12456
|o6 12457 O6 12458 Ka 12459 Ga 12460 Ki 12461 Gi 12462
|Ku 12463 Gu 12464 Ke 12465 Ge 12466 Ko 12467 Go 12468
|Sa 12469 Za 12470 Si 12471 Zi 12472 Su 12473 Zu 12474
|Se 12475 Ze 12476 So 12477 Zo 12478 Ta 12479 Da 12480
|Ti 12481 Di 12482 TU 12483 Tu 12484 Du 12485 Te 12486
|De 12487 To 12488 Do 12489 Na 12490 Ni 12491 Nu 12492
|Ne 12493 No 12494 Ha 12495 Ba 12496 Pa 12497 Hi 12498
|Bi 12499 Pi 12500 Hu 12501 Bu 12502 Pu 12503 He 12504
|Be 12505 Pe 12506 Ho 12507 Bo 12508 Po 12509 Ma 12510
|Mi 12511 Mu 12512 Me 12513 Mo 12514 YA 12515 Ya 12516
|YU 12517 Yu 12518 YO 12519 Yo 12520 Ra 12521 Ri 12522
|Ru 12523 Re 12524 Ro 12525 WA 12526 Wa 12527 Wi 12528
|We 12529 Wo 12530 N6 12531 Vu 12532 KA 12533 KE 12534
|Va 12535 Vi 12536 Ve 12537 Vo 12538 .6 12539 -6 12540
|*6 12541 +6 12542 b4 12549 p4 12550 m4 12551 f4 12552
|d4 12553 t4 12554 n4 12555 l4 12556 g4 12557 k4 12558
|h4 12559 j4 12560 q4 12561 x4 12562 zh 12563 ch 12564
|sh 12565 r4 12566 z4 12567 c4 12568 s4 12569 a4 12570
|o4 12571 e4 12572 ai 12574 ei 12575 au 12576 ou 12577
|an 12578 en 12579 aN 12580 eN 12581 er 12582 i4 12583
|u4 12584 iu 12585 v4 12586 nG 12587 gn 12588 1c 12832
|2c 12833 3c 12834 4c 12835 5c 12836 6c 12837 7c 12838
|8c 12839 9c 12840 ff 64256 fi 64257 fl 64258 ft 64261
|st 64262 cr 🔴 128308
""".trimMargin()
)
}
@Test
fun `test digraph output with headers and custom digraphs`() {
enterCommand("digraphs (0 9450 (2 9313 (1 9312")

View File

@ -13,7 +13,7 @@ import com.intellij.openapi.application.ApplicationManager
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.history.HistoryConstants
import com.maddyhome.idea.vim.history.VimHistory
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
@ -240,7 +240,7 @@ class GlobalCommandTest : VimTestCase() {
@Test
fun `test check history`() {
VimPlugin.getHistory().clear()
val initialEntries = VimPlugin.getHistory().getEntries(HistoryConstants.COMMAND, 0, 0)
val initialEntries = VimPlugin.getHistory().getEntries(VimHistory.Type.Command, 0, 0)
doTest(
"g/found/d",
initialText,
@ -252,7 +252,7 @@ class GlobalCommandTest : VimTestCase() {
hard by the torrent of a mountain pass.
""".trimIndent(),
)
val entries = VimPlugin.getHistory().getEntries(HistoryConstants.COMMAND, 0, 0)
val entries = VimPlugin.getHistory().getEntries(VimHistory.Type.Command, 0, 0)
kotlin.test.assertEquals(1, entries.size - initialEntries.size)
val element = entries.last()
kotlin.test.assertEquals("g/found/d", element.entry)

View File

@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.intellij.idea.TestFor
import com.intellij.testFramework.LoggedErrorProcessor
import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.keys
import com.maddyhome.idea.vim.command.MappingMode
@ -21,6 +22,7 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
import org.jetbrains.plugins.ideavim.waitAndAssert
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
@ -105,16 +107,16 @@ class MapCommandTest : VimTestCase() {
enterCommand("imap <C-Down> <C-O>gt")
enterCommand("nmap ,f <Plug>Foo")
enterCommand("nmap <Plug>Foo iHello<Esc>")
enterCommand("imap")
assertExOutput(
assertCommandOutput("imap",
"""
|i <C-Down> <C-O>gt
|i bar <Esc>
|i foo bar
""".trimMargin(),
)
enterCommand("map")
assertExOutput(
assertCommandOutput("map",
"""
| <C-Down> gt
|n <Plug>Foo iHello<Esc>
@ -142,11 +144,9 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("map")
// Note that Vim doesn't appear to have an order. Items are kinda sorted, but also not. I.e. `m{something}` are
// grouped together, but followed later by `g{something}`. We'll sort by {lhs}, so we're at least consistent
assertExOutput(
assertCommandOutput("map",
"""
| all foo
|n normal foo
@ -163,9 +163,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("nmap")
assertExOutput(
assertCommandOutput("nmap",
"""
| all foo
|n normal foo
@ -178,9 +176,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("vmap")
assertExOutput(
assertCommandOutput("vmap",
"""
| all foo
|s select foo
@ -195,9 +191,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("smap")
assertExOutput(
assertCommandOutput("smap",
"""
| all foo
|s select foo
@ -211,9 +205,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("xmap")
assertExOutput(
assertCommandOutput("xmap",
"""
| all foo
|x visual foo
@ -227,9 +219,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("omap")
assertExOutput(
assertCommandOutput("omap",
"""
| all foo
|o op-pending foo
@ -242,9 +232,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("map!")
assertExOutput(
assertCommandOutput("map!",
"""
|c cmdline foo
|i insert foo
@ -267,9 +255,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("imap")
assertExOutput(
assertCommandOutput("imap",
"""
|i insert foo
|! insert+cmdline foo
@ -283,9 +269,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("lmap")
assertExOutput(
assertCommandOutput("lmap",
"""
|l lang foo
""".trimMargin()
@ -297,9 +281,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
addTestMaps()
enterCommand("cmap")
assertExOutput(
assertCommandOutput("cmap",
"""
|c cmdline foo
|! insert+cmdline foo
@ -313,10 +295,9 @@ class MapCommandTest : VimTestCase() {
addTestMaps() // Adds a mapping of all for NVO
enterCommand("sunmap all") // Removes Select from the NVO mapping for foo
enterCommand("map")
// Note that the formatting is exactly how Vim shows it. Messy, isn't it?
assertExOutput(
assertCommandOutput("map",
"""
|noxall foo
|n normal foo
@ -334,9 +315,8 @@ class MapCommandTest : VimTestCase() {
addTestMaps() // Adds a mapping of all for NVO
enterCommand("vunmap all") // Removes Visual+Select from the NVO mapping for foo
enterCommand("map")
assertExOutput(
assertCommandOutput("map",
"""
|no all foo
|n normal foo
@ -355,8 +335,7 @@ class MapCommandTest : VimTestCase() {
enterCommand("vmap foo baz") // Visual, Select
// Just to be sure we're set up correctly
enterCommand("map")
assertExOutput(
assertCommandOutput("map",
"""
|no foo bar
|v foo baz
@ -366,8 +345,7 @@ class MapCommandTest : VimTestCase() {
enterCommand("sunmap foo")
enterCommand("ounmap foo")
enterCommand("map")
assertExOutput(
assertCommandOutput("map",
"""
|n foo bar
|x foo baz
@ -383,8 +361,7 @@ class MapCommandTest : VimTestCase() {
enterCommand("nmap fee bap")
enterCommand("nmap zzz ppp")
enterCommand("map f")
assertExOutput(
assertCommandOutput("map f",
"""
|n fee bap
| foo bar
@ -397,8 +374,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
enterCommand("map foo bar")
enterCommand("map ")
assertExOutput(
assertCommandOutput("map ",
"""
| foo bar
""".trimMargin()
@ -410,8 +386,7 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
enterCommand("imap foo bar")
enterCommand("imap f ")
assertExOutput(
assertCommandOutput("imap f ",
"""
|i foo bar
""".trimMargin()
@ -429,7 +404,7 @@ class MapCommandTest : VimTestCase() {
}
@Test
fun testddWithMapping() {
fun `test dd with mapping starting with d`() {
configureByText(
"""
Hello$c 1
@ -460,8 +435,8 @@ class MapCommandTest : VimTestCase() {
configureByText("\n")
enterCommand("inoremap jj <Esc>")
enterCommand("imap foo bar")
enterCommand("imap")
assertExOutput(
assertCommandOutput("imap",
"""
|i foo bar
|i jj * <Esc>
@ -491,8 +466,7 @@ class MapCommandTest : VimTestCase() {
""".trimIndent(),
)
assertOffset(1)
enterCommand("nmap")
assertExOutput("n <Right> * <Nop>")
assertCommandOutput("nmap", "n <Right> * <Nop>")
}
@Test
@ -505,8 +479,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("nmap <script> ,e /e<CR>")
enterCommand("nmap <expr> ,f '/f<CR>'")
enterCommand("nmap <unique> ,g /g<CR>")
enterCommand("nmap")
assertExOutput(
assertCommandOutput("nmap",
"""
|n ,a /a<CR>
|n ,b /b<CR>
@ -613,8 +587,8 @@ class MapCommandTest : VimTestCase() {
typeText("i" + "#" + "<Esc>")
assertState("#\n")
assertMode(Mode.NORMAL())
enterCommand("imap")
assertExOutput("i # * X<C-H>#")
assertCommandOutput("imap", "i # * X<C-H>#")
}
// VIM-679 |:map|
@ -638,8 +612,7 @@ class MapCommandTest : VimTestCase() {
""".trimIndent(),
)
assertMode(Mode.NORMAL())
enterCommand("map")
assertExOutput(" <C-X>i dd")
assertCommandOutput("map", " <C-X>i dd")
typeText("<C-X>i")
assertState("bar\n")
}
@ -754,6 +727,59 @@ class MapCommandTest : VimTestCase() {
assertState("Bye\n")
}
@Test
fun `test map applies longest mapping`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcd ABCD")
typeText("i", "abcd", "<Esc>")
assertState("ABCD\n")
}
@Test
fun `test map falls back to previous longest mapping when abandoned`() {
configureByText("\n")
enterCommand("imap abc ABC")
enterCommand("imap abcd ABCD")
typeText("i", "abcg", "<Esc>")
assertState("ABCg\n")
}
@Test
fun `test map falls back to previous longest mapping when abandoned with shorter prefix`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcd ABCD")
typeText("i", "abcg", "<Esc>")
assertState("ABcg\n")
}
@Test
fun `test map falls back to previous longest mapping after timeout`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcd ABCD")
enterCommand("set timeoutlen=100")
typeText("i", "abc")
waitAndAssert(injector.globalOptions().timeoutlen + 100) {
fixture.editor.document.text == "ABc\n"
}
assertState("ABc\n")
}
@Test
fun `test map falls back to previous longest mapping after timeout with shorter prefix`() {
configureByText("\n")
enterCommand("imap ab AB")
enterCommand("imap abcde ABCDE")
enterCommand("set timeoutlen=100")
typeText("i", "abcd")
waitAndAssert(injector.globalOptions().timeoutlen + 100) {
fixture.editor.document.text == "ABcd\n"
}
assertState("ABcd\n")
}
@TestWithoutNeovim(SkipNeovimReason.PLUG)
@Test
fun testPlugMapping() {
@ -773,16 +799,6 @@ class MapCommandTest : VimTestCase() {
assertState("123${c}7890")
}
@TestWithoutNeovim(SkipNeovimReason.PLUG)
@Test
fun testIncompleteMapping() {
configureByText("123${c}4567890")
enterCommand("map <Plug>(Hi)l lll")
enterCommand("map I <Plug>(Hi)")
typeText("Ih")
assertState("12${c}34567890")
}
@Test
fun testIntersectingCommands2() {
configureByText("123${c}4567890")
@ -791,6 +807,86 @@ class MapCommandTest : VimTestCase() {
assertState("123${c}567890")
}
@Test
fun `test partial Plug mapping`() {
doTest(
listOf("i", "Xy"),
"Lorem $c ipsum dolor sit amet",
"Lorem Hello ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap <Plug>xy Hello")
enterCommand("imap X <Plug>x")
}
}
@Test
fun `test abandoned Plug mapping replays all keys as text`() {
// The default Vim behaviour for an abandoned mapping is to replay it, character by character, even if that makes no
// sense for the mode or mapping, and will move the caret or delete text or whatever. This is also true for <Plug>
// mappings, even though `<Plug>` is a special char that can't be typed by the user. When used in Insert mode, Vim
// expands the special char to the string "<Plug>".
doTest(
listOf("i", "Xz"),
"Lorem $c ipsum dolor sit amet",
"Lorem <Plug>xz ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap <Plug>xy Hello")
enterCommand("imap X <Plug>x")
}
}
@Test
fun `test partial Action mapping`() {
doTest(
listOf("i", "X(EditorToggleCase)"),
"Lorem ${c}ipsum dolor sit amet",
"Lorem IPSUM dolor sit amet",
Mode.INSERT
) {
enterCommand("imap X <Action>")
}
}
@Test
fun `test abandoned Action mapping replays all keys as text`() {
// The mapping is looking for `<Action>(...)`, so we need to feed it a key that is not part of this mapping. A space
// char will work
doTest(
listOf("i", "X( "),
"Lorem $c ipsum dolor sit amet",
"Lorem <Action>( ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap X <Action>")
}
}
@Test
fun `test abandoned Action mapping replays all keys as text 2`() {
doTest(
listOf("i", "Xz"),
"Lorem $c ipsum dolor sit amet",
"Lorem <Action>z ipsum dolor sit amet",
Mode.INSERT
) {
enterCommand("imap X <Action>")
}
}
@Test
fun `test timedout Action mapping replays all keys as text`() {
configureByText("Lorem $c ipsum dolor sit amet")
enterCommand("imap X <Action>")
enterCommand("set timeoutlen=100")
typeText("i", "X")
waitAndAssert(injector.globalOptions().timeoutlen + 100) {
fixture.editor.document.text == "Lorem <Action> ipsum dolor sit amet"
}
assertState("Lorem <Action> ipsum dolor sit amet")
}
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test
fun testMapZero() {
@ -903,7 +999,7 @@ class MapCommandTest : VimTestCase() {
@Test
fun `test rhc with triangle brackets`() {
fun `test rhs with triangle brackets`() {
configureByText("\n")
enterCommand("inoremap p <p>")
typeText("ip")
@ -942,8 +1038,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("nmap ,g :action Back<C-M>")
enterCommand("nmap ,h :action Back<C-m>")
enterCommand("nmap ,i :action Back<c-m>")
enterCommand("nmap")
assertExOutput(
assertCommandOutput("nmap",
"""
|n ,a <Action>(Back)
|n ,b <Action>(Back)
@ -970,8 +1066,8 @@ class MapCommandTest : VimTestCase() {
enterCommand("nnoremap ,g :action Back<C-M>")
enterCommand("nnoremap ,h :action Back<C-m>")
enterCommand("nnoremap ,i :action Back<c-m>")
enterCommand("nnoremap")
assertExOutput(
assertCommandOutput("nnoremap",
"""
|n ,a <Action>(Back)
|n ,b <Action>(Back)

View File

@ -148,7 +148,7 @@ class MoveCommandTest : VimTestCase() {
enterCommand("m 0")
assertState(
"""
For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
${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.
@ -173,8 +173,8 @@ class MoveCommandTest : VimTestCase() {
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Ut id dapibus augue.
|${c}Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
|${c}Ut id dapibus augue.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@ -194,7 +194,7 @@ class MoveCommandTest : VimTestCase() {
assertState(
"""
For example: homework, homework, homework, homework, homework, homework, homework, homework, homework.
See, nothing.
${c}See, nothing.
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
""".trimIndent(),
@ -216,7 +216,7 @@ class MoveCommandTest : VimTestCase() {
"""
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
See, not${c}hing.
${c}See, nothing.
For example: homework, homework, homework, homework, homework, homework, homework, homework, homework.
""".trimIndent(),
)
@ -238,8 +238,103 @@ class MoveCommandTest : VimTestCase() {
====
My mother taught me this trick: if you repeat something over and over again it loses its meaning.
See, nothing.
For example: homewor${c}k, homework, homework, homework, homework, homework, homework, homework, homework.
${c}For example: homework, homework, homework, homework, homework, homework, homework, homework, homework.
""".trimIndent(),
)
}
// VIM-3837
@Test
fun `test moving relative line positions caret correctly`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|2
|1
|${c}3
|1
|2
""".trimMargin(),
"""
|2
|1
|3
|${c}2
|1
""".trimMargin()
)
}
@Test
fun `test moving relative line positions caret correctly 2`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet ${c}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(),
"""
|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.
|Ut id dapibus augue.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
)
}
@Test
fun `test moving lines positions caret correctly with nostartofline option`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Nunc sit amet ${c}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(),
"""
|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.
|Orci varius na${c}toque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Ut id dapibus augue.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
) {
enterCommand("set nostartofline")
}
}
@Test
fun `test moving lines positions caret correctly with nostartofline option on shorter line`() {
doTest(
exCommand("+2m."), // Move the line 2 lines below, to below the current line
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id ${c}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(),
"""
|Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|Morbi nec luctus tortor, id venenatis lacus.
|Ut id dapibus augue${c}.
|Nunc sit amet tellus vel purus cursus posuere et at purus.
|Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|Pellentesque orci dolor, tristique quis rutrum non, scelerisque id dui.
""".trimMargin()
) {
enterCommand("set nostartofline")
}
}
}

View File

@ -8,33 +8,40 @@
package org.jetbrains.plugins.ideavim.ex.implementation.commands
/*
class NormalCommandTest : VimTestCase() {
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class NormalCommandTest : VimTestCase() {
@Test
fun `test simple execution`() {
doTest("normal x", "123<caret>456", "123<caret>56")
doTest(exCommand("normal x"), "123<caret>456", "123<caret>56")
}
@Test
fun `test short command`() {
doTest("norm x", "123<caret>456", "123<caret>56")
doTest(exCommand("norm x"), "123<caret>456", "123<caret>56")
}
@Test
fun `test normal command automatically exits Insert mode`() {
doTest(exCommand("normal iFoo"), "123<caret>456", "123Fo<caret>o456", Mode.NORMAL())
}
@Test
fun `test multiple commands`() {
doTest("normal xiNewText", "123<caret>456", "123NewTex<caret>t56")
doTest(exCommand("normal xiNewText"), "123<caret>456", "123NewTex<caret>t56")
}
@Test
fun `test range single stroke`() {
doTest(".norm x", "123<caret>456", "<caret>23456")
fun `test normal command with current line range moves caret to start of line before executing command`() {
doTest(exCommand(".norm x"), "123<caret>456", "<caret>23456")
}
@Test
fun `test range multiple strokes`() {
fun `test normal command with multi-line range`() {
doTest(
"1,3norm x",
exCommand("1,3norm x"),
"""
123456
123456
@ -53,7 +60,7 @@ class NormalCommandTest : VimTestCase() {
}
@Test
fun `test with mapping`() {
fun `test normal command with single letter mapping`() {
configureByText(
"""
<caret>123456
@ -61,8 +68,8 @@ class NormalCommandTest : VimTestCase() {
123456
""".trimIndent()
)
typeText(commandToKeys("map G dd"))
typeText(commandToKeys("normal G"))
enterCommand("map G dd")
enterCommand("normal G")
assertState(
"""
<caret>123456
@ -72,7 +79,28 @@ class NormalCommandTest : VimTestCase() {
}
@Test
fun `test with disabled mapping`() {
fun `test normal command with multi-letter mapping`() {
doTest(
exCommand("normal dd"),
"""
|${c}Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""
|Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|${c}Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
) {
enterCommand("map dd G")
}
}
@Test
fun `test normal command with disabled mapping`() {
configureByText(
"""
<caret>123456
@ -80,8 +108,8 @@ class NormalCommandTest : VimTestCase() {
123456
""".trimIndent()
)
typeText(commandToKeys("map G dd"))
typeText(commandToKeys("normal! G"))
enterCommand("map G dd")
enterCommand("normal! G")
assertState(
"""
123456
@ -92,7 +120,7 @@ class NormalCommandTest : VimTestCase() {
}
@Test
fun `test from visual mode`() {
fun `test normal from Visual mode runs command on start of each line in range`() {
configureByText(
"""
<caret>123456
@ -102,8 +130,8 @@ class NormalCommandTest : VimTestCase() {
123456
""".trimIndent()
)
typeText(parseKeys("Vjj"))
typeText(commandToKeys("normal x"))
typeText("Vjj")
enterCommand("normal x") // Will give `:'<,'>normal x`
assertState(
"""
23456
@ -116,7 +144,7 @@ class NormalCommandTest : VimTestCase() {
}
@Test
fun `test execute visual mode`() {
fun `test normal command switches to Visual mode`() {
configureByText(
"""
<caret>123456
@ -126,8 +154,8 @@ class NormalCommandTest : VimTestCase() {
123456
""".trimIndent()
)
typeText(commandToKeys("normal Vjj"))
typeText(parseKeys("x"))
enterCommand("normal Vjj")
typeText("x")
assertState(
"""
<caret>123456
@ -148,8 +176,8 @@ class NormalCommandTest : VimTestCase() {
123456
""".trimIndent()
)
typeText(parseKeys("qqxq", "jVjjj"))
typeText(commandToKeys("norm @q"))
typeText("qqxq", "jVjjj")
enterCommand("norm @q")
assertState(
"""
23456
@ -165,29 +193,22 @@ class NormalCommandTest : VimTestCase() {
@Test
fun `test command executes at selection start`() {
configureByText("hello <caret>world !")
typeText(parseKeys("vw"))
typeText(parseKeys(":<C-u>norm x<CR>"))
typeText("vw")
enterCommand("<C-u>norm x")
assertState("hello <caret>orld !")
}
@Test
fun `test false escape`() {
configureByText("hello <caret>world !")
typeText(commandToKeys("norm i<Esc>"))
enterCommand("norm i<Esc>")
assertState("hello <Esc<caret>>world !")
}
@Test
fun `test C-R`() {
configureByText("myprop: \"my value\"")
typeText(commandToKeys("exe \"norm ^dei-\\<C-R>\\\"-\""))
assertState("-myprop-: \"my value\"")
}
private fun doTest(command: String, before: String, after: String) {
myFixture.configureByText("a.java", before)
typeText(commandToKeys(command))
myFixture.checkResult(after)
configureByText("""myprop: "my value"""")
enterCommand("""exe "norm ^dei-\<C-R>\"-"""")
assertState("""-myprop-: "my value"""")
}
}
*/

View File

@ -0,0 +1,109 @@
/*
* 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 com.maddyhome.idea.vim.ex.ExOutputModel
import com.maddyhome.idea.vim.vimscript.model.commands.SmileCommand
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
class SmileCommandTest : VimTestCase() {
private fun loadResourceContent(resourcePath: String): String {
return SmileCommand::class.java.getResourceAsStream(resourcePath)
?.bufferedReader()
?.use { it.readText() }
?: throw IllegalStateException("Could not load resource: $resourcePath")
}
@Test
fun `test smile command with default file`() {
configureByText("\n")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.DEFAULT_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with kotlin file`() {
configureByText("\n")
configureByFileName("Test.kt")
typeText(commandToKeys("smile"))
val output = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent = loadResourceContent(SmileCommand.KOTLIN_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with kotlin script file`() {
configureByText("\n")
configureByFileName("Test.kts")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.KOTLIN_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with java file`() {
configureByText("\n")
configureByFileName("Test.java")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.JAVA_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with python file`() {
configureByText("\n")
configureByFileName("Test.py")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.PYTHON_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with unknown file extension`() {
configureByText("\n")
configureByFileName("Test.unknown")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.DEFAULT_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
@Test
fun `test smile command with gitignore file`() {
configureByText("\n")
configureByFileName(".gitignore")
typeText(commandToKeys("smile"))
val output: String = ExOutputModel.getInstance(fixture.editor).text.trimEnd()
val expectedContent: String = loadResourceContent(SmileCommand.DEFAULT_RESOURCE_PATH).trimEnd()
assertEquals(expectedContent, output)
}
}

View File

@ -192,6 +192,32 @@ class SubstituteCommandTest : VimTestCase() {
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test ampersand group`() {
doTest(
exCommand("s/a\\|b/z&/g"),
"${c}abcdefg",
"zazbcdefg",
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test missing group`() {
doTest(
exCommand("s/b/<\\7>/"),
"${c}abc",
"a<>c",
)
}
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
@ -1328,4 +1354,60 @@ class SubstituteCommandTest : VimTestCase() {
" comment ",
)
}
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action U`() {
doTest(
exCommand("s/\\(foo\\)/\\U\\1bar/"),
"${c}a foo",
"a FOOBAR",
)
}
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action U and E`() {
doTest(
exCommand("s/\\(foo\\)/\\U\\1\\ebar/"),
"${c}a foo",
"a FOObar",
)
}
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action u`() {
doTest(
exCommand("s/\\(foo\\)/\\u\\1bar/"),
"${c}a foo",
"a Foobar",
)
}
// VIM-3510
@OptionTest(
VimOption(TestOptionConstants.smartcase, doesntAffectTest = true),
VimOption(TestOptionConstants.ignorecase, doesntAffectTest = true),
)
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
fun `test replace action u and empty group`() {
doTest(
exCommand("s/a foo\\(\\)/a foo\\u\\1bar/"),
"${c}a foo",
"a fooBar",
)
}
}

View File

@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.ex.implementation.expressions.datatypes
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFloat
import org.junit.jupiter.api.Test
import java.util.Locale
import kotlin.test.assertEquals
class VimFloatTest {
@ -23,4 +24,15 @@ class VimFloatTest {
fun `round 7 digits`() {
assertEquals("1.0", VimFloat(0.9999999).toString())
}
@Test
fun `use point as decimal separator always`() {
val oldLocale = Locale.getDefault()
Locale.setDefault(Locale.GERMANY) // In Germany, they use a comma as a decimal separator, i.e., "3,14".
try {
assertEquals("3.14", VimFloat(3.14).toString())
} finally {
Locale.setDefault(oldLocale)
}
}
}

View File

@ -11,6 +11,7 @@ package org.jetbrains.plugins.ideavim.ex.implementation.expressions.operators
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.ex.evaluate
import org.jetbrains.plugins.ideavim.productForArguments
import org.junit.jupiter.params.ParameterizedTest
@ -18,7 +19,7 @@ import org.junit.jupiter.params.provider.Arguments
import org.junit.jupiter.params.provider.MethodSource
import kotlin.test.assertEquals
class ConcatenationOperatorTest {
class ConcatenationOperatorTest : VimTestCase() {
companion object {
@JvmStatic
@ -47,7 +48,7 @@ class ConcatenationOperatorTest {
try {
VimscriptParser.parseExpression("3.4$sp1$operator${sp2}2")!!.evaluate()
} catch (e: ExException) {
assertEquals("E806: using Float as a String", e.message)
assertEquals("E806: Using a Float as a String", e.message)
}
}
@ -57,7 +58,7 @@ class ConcatenationOperatorTest {
try {
VimscriptParser.parseExpression("3.4$sp1$operator${sp2}2.2")!!.evaluate()
} catch (e: ExException) {
assertEquals("E806: using Float as a String", e.message)
assertEquals("E806: Using a Float as a String", e.message)
}
}
@ -67,7 +68,7 @@ class ConcatenationOperatorTest {
try {
VimscriptParser.parseExpression("'string'$sp1$operator${sp2}3.4")!!.evaluate()
} catch (e: ExException) {
assertEquals("E806: using Float as a String", e.message)
assertEquals("E806: Using a Float as a String", e.message)
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.expressions.operators
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.ex.evaluate
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class DoesNotMatchOperatorTest : VimTestCase() {
@Test
fun `test does not match operator returns false when pattern matches string`() {
assertFalse(evaluate("'lorem ipsum' !~ 'l*sum'").asBoolean())
}
@Test
fun `test does not match operator returns true when pattern does not match string`() {
assertTrue(evaluate("'lorem ipsum' !~ 'l*foo'").asBoolean())
}
@Test
fun `test does not match operator returns false when pattern matches case`() {
assertFalse(evaluate("'Lorem Ipsum' !~ 'L*I'").asBoolean())
}
@Test
fun `test does not match operator returns true when pattern does not match case`() {
assertTrue(evaluate("'Lorem Ipsum' !~ 'l*i'").asBoolean())
}
@Test
fun `test does not match operator with 'noignorecase' returns false with different case pattern`() {
injector.globalOptions().ignorecase = true // Default is false
assertFalse(evaluate("'Lorem Ipsum' !~ 'l*i'").asBoolean())
}
// Case-sensitive operator
@Test
fun `test case-sensitive does not match operator returns false when pattern matches string`() {
assertFalse(evaluate("'lorem ipsum' !~# 'l*sum'").asBoolean())
}
@Test
fun `test case-sensitive does not match operator returns true when pattern does not match string`() {
assertTrue(evaluate("'lorem ipsum' !~# 'l*foo'").asBoolean())
}
@Test
fun `test case-sensitive does not match operator returns true when pattern does not match string case`() {
assertTrue(evaluate("'lorem ipsum' !~# 'L*Sum'").asBoolean())
}
// Case-insensitive operator
@Test
fun `test case-insensitive does not match operator returns false when pattern matches string`() {
assertFalse(evaluate("'lorem ipsum' !~? 'l*sum'").asBoolean())
}
@Test
fun `test case-insensitive does not match operator returns true when pattern does not match string`() {
assertTrue(evaluate("'lorem ipsum' !~? 'l*foo'").asBoolean())
}
@Test
fun `test case-insensitive does not match operator returns false when pattern does not match string case`() {
assertFalse(evaluate("'lorem ipsum' !~? 'L*Sum'").asBoolean())
}
private fun evaluate(expression: String) = VimscriptParser.parseExpression(expression)!!.evaluate()
}

View File

@ -8,6 +8,9 @@
package org.jetbrains.plugins.ideavim.ex.implementation.expressions.operators
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDictionary
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFloat
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
@ -20,25 +23,67 @@ import kotlin.test.assertEquals
class FalsyOperatorTest : VimTestCase() {
@Test
fun `left expression is true`() {
assertEquals(VimInt("42"), VimscriptParser.parseExpression("42 ?? 999")!!.evaluate())
fun `test non-zero Number treated as truthy`() {
assertEquals(VimInt(42), VimscriptParser.parseExpression("42 ?? 999")!!.evaluate())
}
@Test
fun `left expression is false`() {
assertEquals(VimInt("42"), VimscriptParser.parseExpression("0 ?? 42")!!.evaluate())
fun `test non-zero negative Number treated as truthy`() {
assertEquals(VimInt(-1), VimscriptParser.parseExpression("-1 ?? 999")!!.evaluate())
}
@Test
fun `empty list as a left expression`() {
fun `test Number 0 is treated as falsy`() {
assertEquals(VimInt(42), VimscriptParser.parseExpression("0 ?? 42")!!.evaluate())
}
@Test
fun `test zero Float is treated as falsy`() {
assertEquals(VimInt(42), VimscriptParser.parseExpression("0.0 ?? 42")!!.evaluate())
}
@Test
fun `test non-zero Float treated as truthy`() {
assertEquals(VimFloat(42.0), VimscriptParser.parseExpression("42.0 ?? 999")!!.evaluate())
}
@Test
fun `test empty String treated as falsy`() {
assertEquals(VimString("string is empty"), VimscriptParser.parseExpression("'' ?? 'string is empty'")!!.evaluate())
}
@Test
fun `test String '0' treated as falsy`() {
assertEquals(VimString("string is empty"), VimscriptParser.parseExpression("'0' ?? 'string is empty'")!!.evaluate())
}
@Test
fun `test non-empty String treated as truthy`() {
assertEquals(VimString("string is not empty"), VimscriptParser.parseExpression("'string is not empty' ?? 'string is empty'")!!.evaluate())
}
@Test
fun `test empty list treated as falsy`() {
assertEquals(VimString("list is empty"), VimscriptParser.parseExpression("[] ?? 'list is empty'")!!.evaluate())
}
@Test
fun `nonempty list as a left expression`() {
fun `test non-empty List treated as truthy`() {
assertEquals(
VimList(mutableListOf(VimInt(1), VimInt(2), VimInt(3))),
VimscriptParser.parseExpression("[1, 2, 3] ?? 'list is empty'")!!.evaluate(),
)
}
@Test
fun `test empty Dictionary treated as falsy`() {
assertEquals(VimString("dict is empty"), VimscriptParser.parseExpression("{} ?? 'dict is empty'")!!.evaluate())
}
@Test
fun `test non-empty Dictionary treated as truthy`() {
val dictionary = LinkedHashMap<VimString, VimDataType>()
dictionary.put(VimString("1"), VimInt(1))
assertEquals(VimDictionary(dictionary), VimscriptParser.parseExpression("{'1': 1} ?? 'dict is empty'")!!.evaluate())
}
}

View File

@ -0,0 +1,80 @@
/*
* 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.expressions.operators
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.ex.evaluate
import org.junit.jupiter.api.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class MatchesOperatorTest : VimTestCase() {
@Test
fun `test matches operator returns true when pattern matches string`() {
assertTrue(evaluate("'lorem ipsum' =~ 'l*sum'").asBoolean())
}
@Test
fun `test matches operator returns false when pattern does not match string`() {
assertFalse(evaluate("'lorem ipsum' =~ 'l*foo'").asBoolean())
}
@Test
fun `test matches operator returns true when pattern matches case`() {
assertTrue(evaluate("'Lorem Ipsum' =~ 'L*I'").asBoolean())
}
@Test
fun `test matches operator returns false when pattern does not match case`() {
assertFalse(evaluate("'Lorem Ipsum' =~ 'l*i'").asBoolean())
}
@Test
fun `test matches operator with 'noignorecase' returns true with different case pattern`() {
injector.globalOptions().ignorecase = true // Default is false
assertTrue(evaluate("'Lorem Ipsum' =~ 'l*i'").asBoolean())
}
// Case-sensitive operator
@Test
fun `test case-sensitive matches operator returns true when pattern matches string`() {
assertTrue(evaluate("'lorem ipsum' =~# 'l*sum'").asBoolean())
}
@Test
fun `test case-sensitive matches operator returns false when pattern does not match string`() {
assertFalse(evaluate("'lorem ipsum' =~# 'l*foo'").asBoolean())
}
@Test
fun `test case-sensitive matches operator returns false when pattern does not match string case`() {
assertFalse(evaluate("'lorem ipsum' =~# 'L*Sum'").asBoolean())
}
// Case-insensitive operator
@Test
fun `test case-insensitive matches operator returns true when pattern matches string`() {
assertTrue(evaluate("'lorem ipsum' =~? 'l*sum'").asBoolean())
}
@Test
fun `test case-insensitive matches operator returns false when pattern does not match string`() {
assertFalse(evaluate("'lorem ipsum' =~? 'l*foo'").asBoolean())
}
@Test
fun `test case-insensitive matches operator returns true when pattern does not match string case`() {
assertTrue(evaluate("'lorem ipsum' =~? 'L*Sum'").asBoolean())
}
private fun evaluate(expression: String) = VimscriptParser.parseExpression(expression)!!.evaluate()
}

View File

@ -37,8 +37,7 @@ class ColFunctionTest : VimTestCase() {
// With selection - make sure to delete the '<,'> that is automatically prepended when entering Command-line mode
// with a selection
typeText("vll")
typeText(":<C-U>echo col(\"v\")<CR>") // enterCommand/assertCommandOutput cannot handle <C-U>!
assertExOutput("5")
assertCommandOutput("""<C-U>echo col("v")""", "5")
// Remove selection and check again - note that exiting Command-line mode removes selection and switches back to
// Normal. This <esc> does nothing

View File

@ -32,8 +32,7 @@ class LineFunctionTest : VimTestCase() {
// With selection - make sure to delete the '<,'> that is automatically prepended when entering Command-line mode
// with a selection
typeText("vj")
typeText(""":<C-U>echo line("v")<CR>""") // enterCommand/assertCommandOutput cannot handle <C-U>!
assertExOutput("3")
assertCommandOutput("""<C-U>echo line("v")""", "3")
// Remove selection and check again - note that exiting Command-line mode removes selection and switches back to
// Normal. This <esc> does nothing
@ -46,4 +45,50 @@ class LineFunctionTest : VimTestCase() {
"0 1 5 0 5 0"
)
}
@Test
fun `test line with selection`() {
doTest(
listOf("V2j", "q"),
"""
|1
|${c}2
|3
|4
|5
""".trimMargin(),
"""
|1
|2
|3
|4 - line(v)==${c}2
|5
""".trimMargin(),
) {
enterCommand("vmap <expr> q '<Esc>a - line(v)=='.line('v').'<Esc>'")
}
}
@Test
fun `test line with reverse selection`() {
doTest(
listOf("V2k", "q"),
"""
|1
|2
|3
|${c}4
|5
""".trimMargin(),
"""
|1
|2 - line(v)==${c}4
|3
|4
|5
""".trimMargin(),
) {
enterCommand("vmap <expr> q '<Esc>a - line(v)=='.line('v').'<Esc>'")
}
}
}

View File

@ -9,6 +9,7 @@
package org.jetbrains.plugins.ideavim.ex.implementation.variables
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
import kotlin.test.assertEquals
@ -43,10 +44,10 @@ class RegisterVariableTest : VimTestCase() {
configureByText("abcd")
enterCommand("""vnoremap <expr> y '"' . v:register . 'y'""")
typeText("vl\"zy")
val register = injector.registerGroup.getRegisters()
.filter { reg -> reg.name == 'z' }
.first()
val vimEditor = fixture.editor.vim
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
val register = injector.registerGroup.getRegisters(vimEditor, context).first { reg -> reg.name == 'z' }
assertEquals("ab", register.text)
}
}
}

View File

@ -8,7 +8,6 @@
package org.jetbrains.plugins.ideavim.extension.miniai
import com.intellij.ide.highlighter.JavaFileType
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.BeforeEach
@ -31,7 +30,6 @@ class MiniAIExtensionTest : VimTestCase() {
"<caret>This is a \"'simple'\" test",
"This is a \"<caret>\" test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -44,7 +42,6 @@ class MiniAIExtensionTest : VimTestCase() {
"'balanced'false <caret>string'balanced'",
"'balanced'false string'<caret>'",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -57,7 +54,6 @@ class MiniAIExtensionTest : VimTestCase() {
"\"balanced\"false <caret>string\"balanced\"",
"\"balanced\"false string\"<caret>\"",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -70,7 +66,6 @@ class MiniAIExtensionTest : VimTestCase() {
"`balanced`false <caret>string`balanced`",
"`balanced`false string`<caret>`",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -87,7 +82,6 @@ class MiniAIExtensionTest : VimTestCase() {
'""",
"''",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -103,7 +97,6 @@ class MiniAIExtensionTest : VimTestCase() {
"""",
"\"\"",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -120,7 +113,6 @@ class MiniAIExtensionTest : VimTestCase() {
`""",
"``",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -142,7 +134,6 @@ class MiniAIExtensionTest : VimTestCase() {
'<caret>'
""",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -161,7 +152,6 @@ print(something)
{}
""",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -183,7 +173,6 @@ print(something)
}
""".trimIndent(),
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -196,7 +185,6 @@ print(something)
"<caret>This is a `'simple'` test",
"This is a `<caret>` test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -210,7 +198,6 @@ print(something)
"<caret>This is a '\"simple\"' test",
"This is a '<caret>' test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -222,7 +209,6 @@ print(something)
"this 'simple<caret> \"test\"'",
"this '<caret>'",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -234,7 +220,6 @@ print(something)
"this 'simple<caret> \"test\"'",
"this '<caret>'",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -246,7 +231,6 @@ print(something)
"this \"simple<caret> 'test'\"",
"this \"<caret>\"",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -258,7 +242,6 @@ print(something)
"this \"simple<caret> 'test'\"",
"this \"<caret>\"",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -270,7 +253,6 @@ print(something)
"this `simple<caret> \"test\"`",
"this `<caret>`",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -282,7 +264,6 @@ print(something)
"this `simple<caret> \"test\"`",
"this `<caret>`",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -294,7 +275,6 @@ print(something)
"this 'simple<caret> \"test\"'",
"this <caret>",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -306,7 +286,6 @@ print(something)
"this 'simple<caret> \"test\"' test",
"this <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -318,7 +297,6 @@ print(something)
"this \"simple<caret> 'test'\"",
"this <caret>",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -330,7 +308,6 @@ print(something)
"this \"simple<caret> 'test'\" test",
"this <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -342,7 +319,6 @@ print(something)
"this `simple<caret> \"test\"`",
"this <caret>",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -354,7 +330,6 @@ print(something)
"this `simple<caret> \"test\"` test",
"this <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -367,7 +342,6 @@ print(something)
"this 'simple \"<caret>test\"'",
"this 'simple <caret>'",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -379,7 +353,6 @@ print(something)
"this 'simple \"<caret>test\"'",
"this 'simple '",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -392,7 +365,6 @@ print(something)
"this 'simple \"nested `<caret>test`\"'",
"this 'simple \"nested <caret>\"'",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -404,7 +376,6 @@ print(something)
"this 'simple \"nested `<caret>test`\"'",
"this 'simple \"nested <caret>\"'",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -416,7 +387,6 @@ print(something)
"<caret>This is a 'simple' test",
"This is a '<caret>' test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -429,7 +399,6 @@ print(something)
"<caret>This is a 'simple test",
"<caret>This is a 'simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -442,7 +411,6 @@ print(something)
"<caret>This is a \"simple\" test",
"This is a \"<caret>\" test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -455,7 +423,6 @@ print(something)
"<caret>This is a \"simple test",
"<caret>This is a \"simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -468,7 +435,6 @@ print(something)
"<caret>This is a `simple` test",
"This is a `<caret>` test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -481,7 +447,6 @@ print(something)
"<caret>This is a `simple test",
"<caret>This is a `simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -494,7 +459,6 @@ print(something)
"<caret>This is a 'simple' test",
"This is a <caret> test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -507,7 +471,6 @@ print(something)
"<caret>This is a 'simple test",
"<caret>This is a 'simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -520,7 +483,6 @@ print(something)
"<caret>This is a \"simple\" test",
"This is a <caret> test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -533,7 +495,6 @@ print(something)
"<caret>This is a \"simple test",
"<caret>This is a \"simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -545,7 +506,6 @@ print(something)
"<caret>This is a `simple` test",
"This is a <caret> test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -558,7 +518,6 @@ print(something)
"<caret>This is a `simple test",
"<caret>This is a `simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -570,7 +529,6 @@ print(something)
"<caret>This is a (simple) test",
"This is a (<caret>) test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -583,7 +541,6 @@ print(something)
"<caret>This is a (simple test",
"<caret>This is a (simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -596,7 +553,6 @@ print(something)
"<caret>This is a [simple] test",
"This is a [<caret>] test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -609,7 +565,6 @@ print(something)
"<caret>This is a [simple test",
"<caret>This is a [simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -622,7 +577,6 @@ print(something)
"<caret>This is a {simple} test",
"This is a {<caret>} test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -635,7 +589,6 @@ print(something)
"<caret>This is a {simple test",
"<caret>This is a {simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -648,7 +601,6 @@ print(something)
"<caret>This is a (simple) test",
"This is a <caret> test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -661,7 +613,6 @@ print(something)
"<caret>This is a (simple test",
"<caret>This is a (simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -674,7 +625,6 @@ print(something)
"<caret>This is a [simple] test",
"This is a <caret> test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -686,7 +636,6 @@ print(something)
"<caret>This is a [simple test",
"<caret>This is a [simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -699,7 +648,6 @@ print(something)
"<caret>This is a {simple} test",
"This is a <caret> test",
Mode.INSERT,
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -712,7 +660,6 @@ print(something)
"<caret>This is a {simple test",
"<caret>This is a {simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -725,7 +672,6 @@ print(something)
"<caret>This is a 'simple' test",
"This is a '<caret>' test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -738,7 +684,6 @@ print(something)
"<caret>This is a 'simple test",
"<caret>This is a 'simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -751,7 +696,6 @@ print(something)
"<caret>This is a \"simple\" test",
"This is a \"<caret>\" test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -764,7 +708,6 @@ print(something)
"<caret>This is a \"simple test",
"<caret>This is a \"simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -777,7 +720,6 @@ print(something)
"<caret>This is a `simple` test",
"This is a `<caret>` test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -790,7 +732,6 @@ print(something)
"<caret>This is a `simple test",
"<caret>This is a `simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -803,7 +744,6 @@ print(something)
"<caret>This is a 'simple' test",
"This is a <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -815,7 +755,6 @@ print(something)
"<caret>This is a 'simple test",
"<caret>This is a 'simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -828,7 +767,6 @@ print(something)
"<caret>This is a \"simple\" test",
"This is a <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -841,7 +779,6 @@ print(something)
"<caret>This is a \"simple test",
"<caret>This is a \"simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -854,7 +791,6 @@ print(something)
"<caret>This is a `simple` test",
"This is a <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -867,7 +803,6 @@ print(something)
"<caret>This is a `simple test",
"<caret>This is a `simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -880,7 +815,6 @@ print(something)
"<caret>This is a (simple) test",
"This is a (<caret>) test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -893,7 +827,6 @@ print(something)
"<caret>This is a (simple test",
"<caret>This is a (simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -906,7 +839,6 @@ print(something)
"<caret>This is a [simple] test",
"This is a [<caret>] test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -919,7 +851,6 @@ print(something)
"<caret>This is a [simple test",
"<caret>This is a [simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -932,7 +863,6 @@ print(something)
"<caret>This is a {simple} test",
"This is a {<caret>} test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -945,7 +875,6 @@ print(something)
"<caret>This is a {simple test",
"<caret>This is a {simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -958,7 +887,6 @@ print(something)
"<caret>This is a (simple) test",
"This is a <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -971,7 +899,6 @@ print(something)
"<caret>This is a (simple test",
"<caret>This is a (simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -984,7 +911,6 @@ print(something)
"<caret>This is a [simple] test",
"This is a <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -997,7 +923,6 @@ print(something)
"<caret>This is a [simple test",
"<caret>This is a [simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -1010,7 +935,6 @@ print(something)
"<caret>This is a {simple} test",
"This is a <caret> test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}
@ -1023,7 +947,6 @@ print(something)
"<caret>This is a {simple test",
"<caret>This is a {simple test",
Mode.NORMAL(),
JavaFileType.INSTANCE,
)
assertSelection(null)
}

View File

@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.extension.replacewithregister
import com.intellij.idea.TestFor
import com.intellij.openapi.application.ApplicationManager
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
@ -650,4 +651,137 @@ class ReplaceWithRegisterTest : VimTestCase() {
assertState("one two three")
enterCommand("set clipboard&")
}
@TestFor(issues = ["VIM-2263"])
@Test
fun `test replace with count from register a`() {
val before = """
${c}first line
second line
third line
fourth line
fifth line
""".trimIndent()
configureByText(before)
typeText("\"ay$")
val context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim)
val aRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegText)
val defaultRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("first line", defaultRegText )
typeText("j")
typeText("y$")
val aRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegTextAfter)
val defaultRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("second line", defaultRegTextAfter )
typeText("\"a2grr")
val expected = """
first line
${c}first line
fourth line
fifth line
""".trimIndent()
assertState(expected)
}
@TestFor(issues = ["VIM-2263"])
@Test
fun `test replace from the register a (without count)`() {
val before = """
${c}first line
second line
third line
fourth line
fifth line
""".trimIndent()
configureByText(before)
typeText("\"ay$")
val context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim)
val aRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegText)
val defaultRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("first line", defaultRegText )
typeText("j")
typeText("y$")
val aRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first line", aRegTextAfter)
val defaultRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("second line", defaultRegTextAfter )
typeText("\"agrr")
val expected = """
first line
${c}first line
third line
fourth line
fifth line
""".trimIndent()
assertState(expected)
}
@TestFor(issues = ["VIM-2263"])
@Test
fun `test replace from the register a to the end of the line`() {
val before = """
${c}first line
second line
third line
fourth line
fifth line
""".trimIndent()
configureByText(before)
typeText("\"ayiw")
val context = injector.executionContextManager.getEditorExecutionContext(fixture.editor.vim)
val aRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first", aRegText)
val defaultRegText = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("first", defaultRegText )
typeText("j")
typeText("y$")
typeText("w")
val aRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, 'a' )!!.text
assertEquals("first", aRegTextAfter)
val defaultRegTextAfter = injector.registerGroup.getRegister(fixture.editor.vim, context, '"' )!!.text
assertEquals("second line", defaultRegTextAfter )
typeText("\"agr$")
val expected = """
first line
second firs${c}t
third line
fourth line
fifth line
""".trimIndent()
assertState(expected)
}
}

View File

@ -633,6 +633,17 @@ class VimSurroundExtensionTest : VimTestCase() {
doTest(listOf(motion), before, after, Mode.NORMAL())
}
// VIM-3841
@Test
fun `test return to Normal mode after surround in Visual mode`() {
doTest(
listOf("veS\"", "i"),
"lorem ${c}ipsum dolor sit amet",
"lorem ${c}\"ipsum\" dolor sit amet",
Mode.INSERT,
)
}
companion object {
@JvmStatic
fun removeWhiteSpaceWithClosingBracketParams() = listOf(

View File

@ -32,7 +32,7 @@ class VimListenersTest : VimTestCase() {
super.setUp(testInfo)
val manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "VimListenersTest"))
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
VimListenerTestObject.disposedCounter = 0

View File

@ -67,7 +67,7 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
// Copied from FileEditorManagerTestCase to allow us to split windows
manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "EffectiveOptionChangeListenerTest"))
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
// Create a new editor that will represent a new buffer in a separate window. It will have default values

View File

@ -61,7 +61,7 @@ class OptionDeclaredScopeTest : VimTestCase() {
super.setUp(testInfo)
// Copied from FileEditorManagerTestCase to allow us to split windows
fileEditorManager = FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
fileEditorManager = FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "OptionDeclaredScopeTest"))
fixture.project.replaceService(FileEditorManager::class.java, fileEditorManager, fixture.testRootDisposable)
// Create a new editor that will represent a new buffer in a separate window. It will have default values
@ -160,7 +160,7 @@ class OptionDeclaredScopeTest : VimTestCase() {
}
val virtualFile = editor.virtualFile
if (editorWindow != null && virtualFile != null) {
if (virtualFile != null) {
editorWindow.closeFile(virtualFile)
editorWindow.requestFocus(true)
}

View File

@ -50,7 +50,7 @@ class TextWidthOptionMapperTest : VimTestCase() {
// Copied from FileEditorManagerTestCase to allow us to split windows
val manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "TextWidthOptionMapperTest"))
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
ApplicationManager.getApplication().invokeAndWait {

View File

@ -37,7 +37,7 @@ class WrapOptionMapperTest : VimTestCase() {
// Copied from FileEditorManagerTestCase to allow us to split windows
manager =
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope())
FileEditorManagerImpl(fixture.project, (fixture.project as ComponentManagerEx).getCoroutineScope().childScope(name = "WrapOptionMapperTest"))
fixture.project.replaceService(FileEditorManager::class.java, manager, fixture.testRootDisposable)
configureByText("\n")

View File

@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.ui
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ui.ShowCmd
import org.jetbrains.plugins.ideavim.SkipNeovimReason
@ -16,7 +17,6 @@ import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.waitAndAssert
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 kotlin.test.assertEquals
@ -87,15 +87,12 @@ class ShowCmdTest : VimTestCase() {
assertEquals("32d", getShowCmdText())
}
// TODO: This test fails because IdeaVim's mapping handler doesn't correctly expand unhandled keys on timeout
@Test
@Disabled
fun `test showcmd expands ambiguous mapped keys on timeout`() {
// `rrr` should timeout and replay `rr` which is mapped to `42`
enterCommand("nmap rr 42")
enterCommand("nmap rrr 55")
typeText(injector.parser.parseKeys("12rr"))
waitAndAssert { "1242" == getShowCmdText() }
waitAndAssert(injector.globalOptions().timeoutlen + 100) { "1242" == getShowCmdText() }
}
@TestWithoutNeovim(reason = SkipNeovimReason.SHOW_CMD)

View File

@ -137,7 +137,7 @@ abstract class VimTestCase {
VimPlugin.getSearch().resetState()
if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true)
injector.globalOptions().ideastrictmode = true
VimTestCase.Checks.reset()
Checks.reset()
clearClipboard()
// Make sure the entry text field gets a bounds, or we won't be able to work out caret location
@ -442,9 +442,9 @@ abstract class VimTestCase {
editor,
)
val project = fixture.project
when (VimTestCase.Checks.keyHandler) {
VimTestCase.Checks.KeyHandlerMethod.DIRECT_TO_VIM -> typeText(keys.filterNotNull(), editor, project)
VimTestCase.Checks.KeyHandlerMethod.VIA_IDE -> typeTextViaIde(keys.filterNotNull(), editor)
when (Checks.keyHandler) {
Checks.KeyHandlerMethod.DIRECT_TO_VIM -> typeText(keys.filterNotNull(), editor, project)
Checks.KeyHandlerMethod.VIA_IDE -> typeTextViaIde(keys.filterNotNull(), editor)
}
return editor
}
@ -749,7 +749,7 @@ abstract class VimTestCase {
}
protected fun assertCaretsVisualAttributes() {
if (!VimTestCase.Checks.caretShape) return
if (!Checks.caretShape) return
val editor = fixture.editor
val attributes = GuiCursorOptionHelper.getAttributes(getGuiCursorMode(editor))
val colour = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
@ -946,8 +946,8 @@ abstract class VimTestCase {
}
// Disable or enable checks for the particular test
protected inline fun setupChecks(setup: VimTestCase.Checks.() -> Unit) {
VimTestCase.Checks.setup()
protected inline fun setupChecks(setup: Checks.() -> Unit) {
Checks.setup()
}
protected fun assertExException(expectedErrorMessage: String, action: () -> Unit) {
@ -1082,11 +1082,24 @@ abstract class VimTestCase {
@JvmStatic
fun commandToKeys(command: String): List<KeyStroke> {
val keys: MutableList<KeyStroke> = ArrayList()
if (!command.startsWith(":")) {
keys.addAll(injector.parser.parseKeys(":"))
keys.addAll(injector.parser.parseKeys(":"))
var startIndex = if (command.startsWith(":")) 1 else 0
// Special case support for <C-U>
startIndex = if (command.substring(startIndex).startsWith("<C-U>", ignoreCase = true)) {
keys.addAll(injector.parser.parseKeys("<C-U>"))
startIndex + 5
}
else {
startIndex
}
// We don't parse the rest of the command, to avoid parsing special keys in e.g. maps. Note that values such as
// `<expr>` or `<args>` would be correctly handled by parseKeys
keys.addAll(injector.parser.stringToKeys(command.substring(startIndex)))
if (keys.last().keyCode != KeyEvent.VK_ENTER) {
keys.addAll(injector.parser.parseKeys("<Enter>"))
}
keys.addAll(injector.parser.stringToKeys(command)) // Avoids trying to parse 'command ... <args>' as a special char
keys.addAll(injector.parser.parseKeys("<Enter>"))
return keys
}

View File

@ -32,18 +32,21 @@ 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.0")
testImplementation("org.junit.vintage:junit-vintage-engine:5.12.2")
intellijPlatform {
// Snapshots don't use installers
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
var useInstaller = "EAP-SNAPSHOT" !in ideaVersion
if (ideaType == "RD") {
// Using Rider as a target IntelliJ Platform with `useInstaller = true` is currently not supported, please set `useInstaller = false` instead. See: https://github.com/JetBrains/intellij-platform-gradle-plugin/issues/1852
useInstaller = false
}
create(ideaType, ideaVersion, useInstaller)
testFramework(TestFrameworkType.Platform)
testFramework(TestFrameworkType.JUnit5)
bundledPlugins("com.intellij.java", "org.jetbrains.plugins.yaml")
instrumentationTools()
}
}

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