1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2024-11-25 16:42:55 +01:00

Compare commits

...

118 Commits

Author SHA1 Message Date
0c3544c1fe
Set plugin version to chylex-20 2023-11-17 07:56:06 +01:00
29813f12fb
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2023-11-17 07:56:06 +01:00
0bb5739adc
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2023-11-17 07:56:06 +01:00
4e5e94cd98
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2023-11-17 07:56:06 +01:00
8b6925e5e4
Add support for count for visual and line motion surround 2023-11-17 07:56:05 +01:00
e451ebf361
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2023-11-17 07:56:05 +01:00
056d704297
[VIM-696] Restore visual mode after undo/redo, and disable incompatible actions 2023-11-17 07:56:05 +01:00
dbb0f79113
Respect count with <Action> mappings 2023-11-17 07:56:05 +01:00
1bc6dfac1c
Prevent IdeaVIM from stealing key binding that confirms in-place refactoring 2023-11-17 07:17:31 +01:00
de449adcb9
Change matchit plugin to use HTML patterns in unrecognized files 2023-11-17 07:17:31 +01:00
2ef9742b71
Reset insert mode when switching active editor 2023-11-17 07:17:30 +01:00
a2833aa088
Remove update checker 2023-11-17 07:17:30 +01:00
13ebac83c6
Set custom plugin version 2023-11-17 06:50:20 +01:00
dependabot[bot]
c3134b9426 Bump org.junit.jupiter:junit-jupiter-engine from 5.10.0 to 5.10.1
Bumps [org.junit.jupiter:junit-jupiter-engine](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.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>
2023-11-15 17:36:01 +02:00
dependabot[bot]
06c036d373 Bump org.jetbrains.kotlin:kotlin-stdlib from 1.9.10 to 1.9.20
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.9.10 to 1.9.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/v1.9.10...v1.9.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>
2023-11-15 17:34:21 +02:00
dependabot[bot]
2f8bd29725 Bump com.squareup.okhttp3:okhttp from 4.11.0 to 4.12.0
Bumps [com.squareup.okhttp3:okhttp](https://github.com/square/okhttp) from 4.11.0 to 4.12.0.
- [Changelog](https://github.com/square/okhttp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/square/okhttp/compare/parent-4.11.0...parent-4.12.0)

---
updated-dependencies:
- dependency-name: com.squareup.okhttp3:okhttp
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-15 17:23:00 +02:00
dependabot[bot]
1eae211b41 Bump io.ktor:ktor-client-core from 2.3.4 to 2.3.6
Bumps [io.ktor:ktor-client-core](https://github.com/ktorio/ktor) from 2.3.4 to 2.3.6.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.6/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.4...2.3.6)

---
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>
2023-11-15 17:21:35 +02:00
IdeaVim Bot
b02eb7a422 Update changelog. Action id - 6862042155 2023-11-14 10:06:35 +00:00
Alex Plate
3db31e9347
Fix(VIM-3159): Shift-enter now works in normal mode again 2023-11-14 11:12:49 +02:00
Alex Plate
1dc6045ae1
Fix(VIM-3157): Do not invoke enter in invokeLater for python console 2023-11-13 12:25:49 +02:00
IdeaVim Bot
2436164b1e Update changelog. Action id - 6848428489 2023-11-13 10:07:34 +00:00
Alex Plate
c13fc8a805
Fix(VIM-3168): Do not switch to block caret after enter if the IdeaVim is disabled 2023-11-13 11:39:07 +02:00
Alex Plate
41025d78de
Fix(VIM-3165): Do not process enter key as IdeaVim shortcut if it's not an actual keypress 2023-11-13 11:23:26 +02:00
Filipp Vakhitov
b3ad222cdc Change default undo behavior 2023-11-10 19:22:43 +02:00
Alex Plate
efd9ed0a5f
Add logs around pressing of esc and enter keys of the user 2023-11-10 17:17:13 +02:00
Alex Plate
9d20061924
Do not register VimShortcutKeyAction for esc and enter keys
This is no more needed as we have a different way of getting the key. Also, this causes a bug VIM-3169
2023-11-10 17:17:13 +02:00
Filipp Vakhitov
ddfe8cf361 Support temporary options 2023-11-10 16:01:22 +02:00
Filipp Vakhitov
93c83f773a Add generated JSON files to gitignore 2023-11-10 16:01:22 +02:00
Filipp Vakhitov
876e16fa9e Support registering commands from json 2023-11-10 16:01:21 +02:00
Filipp Vakhitov
37067d5c72 Fix typos 2023-11-10 16:01:21 +02:00
Filipp Vakhitov
083ac8cfa3 Convert RegisterActions to Kotlin 2023-11-10 16:01:21 +02:00
Filipp Vakhitov
fe6c1ae452 Add LazyVimCommand and CommandProvider 2023-11-10 16:01:21 +02:00
Filipp Vakhitov
30165f5047 Support keypad keys in Vim key notation 2023-11-10 16:01:21 +02:00
Filipp Vakhitov
3046c61447 Generating json by annotations 2023-11-10 16:01:21 +02:00
Filipp Vakhitov
ced50bb2e8 Mark xml related classes for commands as deprecated 2023-11-10 16:01:21 +02:00
Filipp Vakhitov
dee84bcc63 Annotate commands 2023-11-10 16:01:21 +02:00
dependabot[bot]
0f0bafb66a Bump io.ktor:ktor-client-auth from 2.3.4 to 2.3.6
Bumps [io.ktor:ktor-client-auth](https://github.com/ktorio/ktor) from 2.3.4 to 2.3.6.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.6/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.4...2.3.6)

---
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>
2023-11-08 16:23:42 +00:00
dependabot[bot]
7cdc3611a5 Bump io.ktor:ktor-client-content-negotiation from 2.3.5 to 2.3.6
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 2.3.5 to 2.3.6.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.6/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.5...2.3.6)

---
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>
2023-11-08 18:06:47 +02:00
dependabot[bot]
6eda6aebba Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 1.9.10-1.0.13 to 1.9.20-1.0.14.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/1.9.10-1.0.13...1.9.20-1.0.14)

---
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>
2023-11-08 17:56:23 +02:00
dependabot[bot]
2d23c81ebb Bump io.ktor:ktor-serialization-kotlinx-json from 2.3.5 to 2.3.6
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 2.3.5 to 2.3.6.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.6/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.5...2.3.6)

---
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>
2023-11-08 17:54:12 +02:00
dependabot[bot]
5602058849 Bump org.junit.jupiter:junit-jupiter-params from 5.10.0 to 5.10.1
Bumps [org.junit.jupiter:junit-jupiter-params](https://github.com/junit-team/junit5) from 5.10.0 to 5.10.1.
- [Release notes](https://github.com/junit-team/junit5/releases)
- [Commits](https://github.com/junit-team/junit5/compare/r5.10.0...r5.10.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>
2023-11-08 17:53:43 +02:00
IdeaVim Bot
b1ec021c1e Update changelog. Action id - 6782896069 2023-11-07 10:07:22 +00:00
IdeaVim Bot
7073b2410b Preparation to 2.7.0 release 2023-11-07 07:51:06 +00:00
aleksei.plate@jetbrains.com
ab9068bc0a TeamCity change in 'Ideavim / IdeaVim releases' project: parameters of 'Publish minor release' build configuration were updated 2023-11-07 07:46:22 +00:00
Alex Plate
0c66fb474e
Fix(VIM-3130): Change the build version to 2023.1.2
There is a problem with building IdeaVim on newer version. Some of the internal classes
was migrated from java to kotlin. However, this change is not binary compatible because of a kotlin bug.
Building IdeaVim on java version of this class fixes this problem.
2023-11-07 09:43:11 +02:00
IdeaVim Bot
fef6c651ea Update changelog. Action id - 6754174825 2023-11-04 10:06:18 +00:00
Alex Plate
620f54344f
Fix(VIM-3138): Do not try to register disposer if the caret is already disposed 2023-11-03 19:19:17 +02:00
Alex Plate
ef1259a87a
Print the name of the branch when we check it out 2023-11-03 18:32:57 +02:00
Alex Plate
5ef4af6b55
Revert "Testing CI integrations: simple change"
This reverts commit b294bdd013.
2023-11-03 18:20:00 +02:00
Alex Plate
6d17304e4e
Revert "Testing CI integration: build plugin instead of pushing it"
This reverts commit dc95c7fc2c.
2023-11-03 18:19:48 +02:00
Alex Plate
2e4062b5db
Fix the incorrect checkout command 2023-11-03 18:14:30 +02:00
Alex Plate
b294bdd013
Testing CI integrations: simple change 2023-11-03 17:41:16 +02:00
Alex Plate
dc95c7fc2c
Testing CI integration: build plugin instead of pushing it 2023-11-03 17:40:06 +02:00
Alex Plate
dfe8c43c33
Fix VIM-3146 for Rider
Now we process IdeaVim esc before Rider's esc
2023-11-03 17:23:41 +02:00
Alex Plate
3e54ad5a68
Get rid of ordering with terminalEnter
This caused a blocker issue VIM-3124. The problem is that this ordering doesn't work well in PyCharm.
Unfortunately, this means that we have to reopen VIM-3122
2023-11-03 16:36:45 +02:00
Alex Plate
288c66d8a2
Refactor execution of vim script
Now we set the flag `executingVimscript` during execution of any vimscript and we run initialization of delayed plugins after every call for execute.

This is needed to properly initialize plugins after call for `source` command. Previously this command initialized extensions as they met in the script, what may cause incorrect behaviour. With this update, we unified an approach for executing vim script.
2023-11-03 13:27:38 +02:00
Alex Plate
44c8a97f44
Consider the case when we load .ideavimrc file from using the source command
This includes updating the "ReloadIdeaVimRc" button and setting the correct mapping owner

Previously, the `source` command loaded ~/.ideavimrc file as a regular file, thus several features didn't work properly.
This refactoring was caused by this PR: https://github.com/JetBrains/ideavim/pull/736
2023-11-03 13:27:38 +02:00
Alex Plate
60c27b1dea
Add documentation about some aspects of IdeaVim implementation 2023-11-03 13:27:38 +02:00
IdeaVim Bot
ce8b77b240 Update changelog. Action id - 6743778172 2023-11-03 10:08:12 +00:00
IdeaVim Bot
718c5fb30e Update changelog after merging PR 2023-11-03 09:31:18 +00:00
419160724c Fix(VIM-2933): Reloading/sourcing .ideavimrc does not initialize new plugins 2023-11-03 11:27:41 +02:00
dependabot[bot]
c905dfe6d8 Bump io.ktor:ktor-client-cio from 2.3.4 to 2.3.5
Bumps [io.ktor:ktor-client-cio](https://github.com/ktorio/ktor) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.5/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.4...2.3.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-03 07:17:54 +00:00
dependabot[bot]
70eb008412 Bump io.ktor:ktor-client-content-negotiation from 2.3.4 to 2.3.5
Bumps [io.ktor:ktor-client-content-negotiation](https://github.com/ktorio/ktor) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.5/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.4...2.3.5)

---
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>
2023-11-03 09:02:20 +02:00
dependabot[bot]
93feaadacf Bump org.jetbrains.intellij from 1.15.0 to 1.16.0
Bumps org.jetbrains.intellij from 1.15.0 to 1.16.0.

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-03 06:53:44 +00:00
dependabot[bot]
0b7610607d Bump io.ktor:ktor-serialization-kotlinx-json from 2.3.4 to 2.3.5
Bumps [io.ktor:ktor-serialization-kotlinx-json](https://github.com/ktorio/ktor) from 2.3.4 to 2.3.5.
- [Release notes](https://github.com/ktorio/ktor/releases)
- [Changelog](https://github.com/ktorio/ktor/blob/2.3.5/CHANGELOG.md)
- [Commits](https://github.com/ktorio/ktor/compare/2.3.4...2.3.5)

---
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>
2023-11-03 08:50:30 +02:00
IdeaVim Bot
922fea5395 Update changelog after merging PR 2023-11-03 06:49:12 +00:00
Matt Ellis
1841b7c4e6 Allow tilde forward slash on Windows 2023-11-03 08:46:57 +02:00
Alex Plate
ed966faaf4
Update changelog 2023-10-30 11:23:01 +02:00
Alex Plate
03efeed6ae
Use the correct version of IdeaVim in changelog 2023-10-30 09:42:21 +02:00
Alex Plate
675c5ae480
Update changelog 2023-10-30 09:12:35 +02:00
Alex Plate
d575b22e2e
Set the origin branch for "release" 2023-10-30 09:08:30 +02:00
Alex Plate
5e4ee1b60f
Fix the issue with the status bar icon
In the 2023.3 EAP call to isAvailable seems to happen much earlier than it used to be.
This caused the fact that `injector` is not yet initialised at that moment and we fail with an exception. All other status bar icons are also not loaded because of this exception.
Adding `VimPlugin.getInstance()` is a quick workaround to initialize the needed injector
2023-10-28 08:53:21 +03:00
IdeaVim Bot
d8ce20c2f9 Preparation to 2.6.0 release 2023-10-27 19:04:28 +00:00
Alex Plate
b164dc1b55
Revert "Remove deprecated MarkGroup.java"
This reverts commit fdd32cb954.
2023-10-27 21:41:18 +03:00
filipp
530eba3d00 Update tests for disabled oldundo option 2023-10-27 19:18:53 +03:00
filipp
808066f2e2 Disable new undo before release 2023-10-27 19:18:53 +03:00
Alex Plate
cb3e683c8e
Use different API for creating editor disposable 2023-10-27 17:00:06 +03:00
Alex Plate
6ff57775ed
Add has feature info to the changelog 2023-10-27 17:00:06 +03:00
Alex Plate
6c07687a86
Fix enter in read-only files
VIM-3122
2023-10-27 17:00:06 +03:00
Alex Plate
497a8c19c5
Update information about new ShowHoverInfo action 2023-10-27 17:00:06 +03:00
IdeaVim Bot
c13f7468ef Update changelog. Action id - 6665767330 2023-10-27 10:06:58 +00:00
IdeaVim Bot
847872cdb6 Update changelog after merging PR 2023-10-27 09:47:54 +00:00
Matt Ellis
f0abe5d80d Fix range for fall back comment mode
Callback to reset caret was always called, even if action was unavailable. Fall back action would therefore act on current line only.
2023-10-27 12:45:02 +03:00
IdeaVim Bot
465c5b9e77 Update changelog after merging PR 2023-10-27 09:31:25 +00:00
fb78cdd304 Add operating system type to has() function 2023-10-27 12:28:06 +03:00
IdeaVim Bot
5b17fe2410 Update changelog after merging PR 2023-10-27 08:08:57 +00:00
Matt Ellis
5fd54dccd3
Add support for ShowHoverInfo action to 2023.1 and 2023.2 (#733)
* Fix(VIM-2106) Implement ShowHoverInfo if not provided by platform

* Bump minimum build to 2023.1 RTM
2023-10-27 11:06:11 +03:00
Alex Plate
1695afd915
Use 2023.2.4 version for releases 2023-10-27 10:42:36 +03:00
Alex Plate
5ab549ae96
Switch to 2023.2.4 for github, nvim, proprty, and long running tests as the compilation with the latest EAP is now broken 2023-10-27 10:42:16 +03:00
Alex Plate
35123e7c1e
Remove the unused property in TC configuration 2023-10-27 10:40:49 +03:00
Alex Plate
1badade841
Fix incorrect condition for update changelog action 2023-10-27 10:34:36 +03:00
Alex Plate
b357625529
Add check that actions should work only on the original repo
Because of some reason actions started on
https://github.com/JetBrains/ideavim/pull/731
pull request and updated it.
With this change, forks won't be affected by forked actions. If the action is still needed on fork, these conditions can be changes
2023-10-27 10:33:26 +03:00
filipp
9ccd39d724 Better VIM-696 2023-10-26 16:38:12 +03:00
filipp
c876079e04 Fix(VIM-1639): Ctrl-o and Ctrl-i jumping in files of different projects
A commit with proper notation to run all the "Fixed tasks"
2023-10-26 13:38:45 +03:00
IdeaVim Bot
162c1c59fe Update changelog. Action id - 6652550069 2023-10-26 10:07:46 +00:00
filipp
06ef1c1182 VIM-1639 Ctrl-o and Ctrl-i jumping in files of different projects 2023-10-26 10:23:16 +03:00
filipp
a9ba9789fd Fix(VIM-696): Vim selection issue after undo 2023-10-25 16:55:32 +03:00
filipp
fdd32cb954 Remove deprecated MarkGroup.java 2023-10-25 16:55:32 +03:00
IdeaVim Bot
9fd7d86998 Add pWydmuch to contributors list 2023-10-25 09:03:21 +00:00
IdeaVim Bot
5973903313 Update changelog after merging PR 2023-10-25 06:13:45 +00:00
Patryk Wydmuch
75e4b19b88 Fix md links in doc 2023-10-25 09:10:24 +03:00
Alex Plate
af7bdb55a1
Fix rider issues with the new handler 2023-10-20 17:42:05 +03:00
Alex Plate
69af9aeff0
Fix(IDEA-300030): Mapping with enter to action may not work 2023-10-20 16:16:28 +03:00
Alex Plate
88f4192d61
Fix tests 2023-10-20 12:32:05 +03:00
Alex Plate
96db8a326e
Add teamcity tests for stable versions of IJ 2023-10-20 11:16:50 +03:00
Alex Plate
8c06767fdc
Show error instead of warning in case of exception 2023-10-20 11:10:55 +03:00
Alex Plate
25877e369b
Add comment saying that the ideaglobalmode option is experimental 2023-10-20 10:15:42 +03:00
Alex Plate
0271a475a2
Update docs 2023-10-20 10:15:42 +03:00
IdeaVim Bot
eef3ab5a15 Update changelog. Action id - 6481002995 2023-10-11 10:07:13 +00:00
Alex Plate
26f48c5820
Fix(VIM-3095): Fix missing ellipsis digraph 2023-10-11 12:27:00 +03:00
IdeaVim Bot
236ca36c79 Update changelog after merging PR 2023-10-11 08:11:10 +00:00
Matt Ellis
405b9ba7ea Fix(VIM-2562): Fix hang with multi-width chars in command line 2023-10-11 11:08:08 +03:00
Alex Plate
ab9bd76d34
Update isOctopusEnabled function 2023-10-11 09:38:30 +03:00
Alex Plate
677da7d80a
Create a test with different combination of enter handlers 2023-10-11 09:18:51 +03:00
Alex Plate
b3ad2fd715
Next handler may be null 2023-10-10 16:01:24 +03:00
Alex Plate
97ca6ce5b8
Dialogs are now closed by esc 2023-10-10 15:33:49 +03:00
Alex Plate
e1abc4374e
Correct the shape of the caret after entering the cell in py notebooks 2023-10-10 13:08:26 +03:00
Alex Plate
9eeeb15c6c
Enable octopus handler by default 2023-10-10 11:39:14 +03:00
333 changed files with 2817 additions and 842 deletions

1
.gitattributes vendored Normal file
View File

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

View File

@ -14,6 +14,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- name: Fetch origin repo - name: Fetch origin repo

View File

@ -12,6 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -12,6 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -12,6 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -8,7 +8,7 @@ permissions:
jobs: jobs:
dependabot: dependabot:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.actor == 'dependabot[bot]' }} if: ${{ github.actor == 'dependabot[bot]' && github.repository == 'JetBrains/ideavim' }}
steps: steps:
- name: Auto-merge Dependabot PR - name: Auto-merge Dependabot PR
run: gh pr merge --auto --rebase "$PR_URL" run: gh pr merge --auto --rebase "$PR_URL"

View File

@ -11,7 +11,7 @@ on:
jobs: jobs:
build: build:
if: github.event.pull_request.merged == true if: github.event.pull_request.merged == true && github.repository == 'JetBrains/ideavim'
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:

View File

@ -5,6 +5,7 @@ on:
- cron: '0 12 * * *' - cron: '0 12 * * *'
jobs: jobs:
build-for-ui-test-mac-os: build-for-ui-test-mac-os:
if: github.repository == 'JetBrains/ideavim'
runs-on: macos-latest runs-on: macos-latest
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

View File

@ -14,6 +14,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- name: Fetch origin repo - name: Fetch origin repo

View File

@ -14,6 +14,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- name: Fetch origin repo - name: Fetch origin repo

View File

@ -15,6 +15,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -15,6 +15,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@ -12,6 +12,7 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: github.repository == 'JetBrains/ideavim'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

3
.gitignore vendored
View File

@ -23,6 +23,9 @@
# Generated by gradle task "generateGrammarSource" # Generated by gradle task "generateGrammarSource"
src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
# Generated JSONs for lazy classloading
/vim-engine/src/main/resources/ksp-generated
/src/main/resources/ksp-generated
# Created by github automation # Created by github automation
settings.xml settings.xml

View File

@ -6,6 +6,7 @@
<option name="CONTINUATION_INDENT_SIZE" value="4" /> <option name="CONTINUATION_INDENT_SIZE" value="4" />
</value> </value>
</option> </option>
<option name="LINE_SEPARATOR" value="&#10;" />
<JavaCodeStyleSettings> <JavaCodeStyleSettings>
<option name="FIELD_NAME_PREFIX" value="my" /> <option name="FIELD_NAME_PREFIX" value="my" />
<option name="STATIC_FIELD_NAME_PREFIX" value="our" /> <option name="STATIC_FIELD_NAME_PREFIX" value="our" />

View File

@ -5,13 +5,11 @@ object Constants {
const val EAP_CHANNEL = "eap" const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev" const val DEV_CHANNEL = "Dev"
const val VERSION = "2.4.0" const val GITHUB_TESTS = "2023.1.2"
const val NVIM_TESTS = "2023.1.2"
const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT" const val PROPERTY_TESTS = "2023.1.2"
const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT" const val LONG_RUNNING_TESTS = "2023.1.2"
const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT" const val QODANA_TESTS = "2023.1.2"
const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
const val RELEASE = "2023.1.2" const val RELEASE = "2023.1.2"
const val RELEASE_DEV = "2023.1.2" const val RELEASE_DEV = "2023.1.2"

View File

@ -23,6 +23,8 @@ object Project : Project({
vcsRoot(GitHubPullRequest) vcsRoot(GitHubPullRequest)
// Active tests // Active tests
buildType(TestingBuildType("2023.2", "<default>", version = "2023.2.3"))
buildType(TestingBuildType("2023.1", "<default>", version = "2023.1.5"))
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT")) buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))

View File

@ -118,6 +118,7 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
then then
git checkout release git checkout release
echo checkout release branch echo checkout release branch
git branch --set-upstream-to=origin/release release
git push --tags git push --tags
git push origin --force git push origin --force
fi fi

View File

@ -0,0 +1,20 @@
package patches.buildTypes
import jetbrains.buildServer.configs.kotlin.v2019_2.*
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
/*
This patch script was generated by TeamCity on settings change in UI.
To apply the patch, change the buildType with id = 'ReleaseMinor'
accordingly, and delete the patch script.
*/
changeBuildType(RelativeId("ReleaseMinor")) {
params {
expect {
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:3cd3e867-282c-451f-b958-bc95d56a8450", display = ParameterDisplay.HIDDEN)
}
update {
password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:7bc0eb3a-b86a-4ebd-b622-d4ef12d7e1d3", display = ParameterDisplay.HIDDEN)
}
}
}

View File

@ -483,6 +483,10 @@ Contributors:
[![icon][github]](https://github.com/ludwig-jb) [![icon][github]](https://github.com/ludwig-jb)
&nbsp; &nbsp;
ludwig-jb ludwig-jb
* [![icon][mail]](mailto:pvydmuch@gmail.com)
[![icon][github]](https://github.com/pWydmuch)
&nbsp;
pWydmuch
Previous contributors: Previous contributors:

View File

@ -25,11 +25,53 @@ usual beta standards.
## To Be Released ## To Be Released
### Fixes:
* [VIM-3130](https://youtrack.jetbrains.com/issue/VIM-3130) Change the build version to 2023.1.2
* [VIM-3168](https://youtrack.jetbrains.com/issue/VIM-3168) Do not switch to block caret after enter if the IdeaVim is disabled
* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
## 2.7.0, 2023-11-07
### Fixes:
* [VIM-2933](https://youtrack.jetbrains.com/issue/VIM-2933) Reloading/sourcing .ideavimrc does not initialize new plugins
* [VIM-3138](https://youtrack.jetbrains.com/issue/VIM-3138) Do not try to register disposer if the caret is already disposed
### Merged PRs:
* [734](https://github.com/JetBrains/ideavim/pull/734) by [Matt Ellis](https://github.com/citizenmatt): Support `~/` on Windows
* [736](https://github.com/JetBrains/ideavim/pull/736) by [chylex](https://github.com/chylex): Fix(VIM-2933): Reloading/sourcing .ideavimrc does not initialize new plugins
## 2.6.3, 2023-10-30
### Changes:
- 2.6.0 and 2.6.1 releases are broken. Version 2.6.3 reverts IdeaVim plugin to the working state as for 2.5.1.
## 2.6.0, 2023-10-27
This version of IdeaVim contains a lot of issues. Version 2.6.3 reverts these changes.
### Features:
* `ShowHoverInfo` action can be used in mappings to open a tooltip that is shown by
mouse hovering | [VIM-2106](https://youtrack.jetbrains.com/issue/VIM-2106)
* `has` Vim Script function supports the most common OS checks: win32, win64, linux, mac, macunix, osx, osxdarwin, bsd, sun, unix
* See https://github.com/JetBrains/ideavim#vim-script for details about Vim Script
### Fixes: ### Fixes:
* [VIM-3060](https://youtrack.jetbrains.com/issue/VIM-3060) Clipboard interaction stopped working * [VIM-3060](https://youtrack.jetbrains.com/issue/VIM-3060) Clipboard interaction stopped working
* [VIM-3095](https://youtrack.jetbrains.com/issue/VIM-3095) Fix missing ellipsis digraph
* [VIM-2562](https://youtrack.jetbrains.com/issue/VIM-2562) Fix hang with multi-width chars in command line
* [VIM-696](https://youtrack.jetbrains.com/issue/VIM-696) Vim selection issue after undo
* [VIM-1639](https://youtrack.jetbrains.com/issue/VIM-1639) Ctrl-o and Ctrl-i jumping in files of different projects
### Merged PRs: ### Merged PRs:
* [697](https://github.com/JetBrains/ideavim/pull/697) by [Matt Ellis](https://github.com/citizenmatt): Support per-window "global" values for local-to-window options * [697](https://github.com/JetBrains/ideavim/pull/697) by [Matt Ellis](https://github.com/citizenmatt): Support per-window "global" values for local-to-window options
* [717](https://github.com/JetBrains/ideavim/pull/717) by [Matt Ellis](https://github.com/citizenmatt): Fix(VIM-2562): Fix hang with multi-width chars in command line
* [732](https://github.com/JetBrains/ideavim/pull/732) by [pWydmuch](https://github.com/pWydmuch): Fix md links in doc
* [733](https://github.com/JetBrains/ideavim/pull/733) by [Matt Ellis](https://github.com/citizenmatt): Add support for ShowHoverInfo action to 2023.1 and 2023.2
* [729](https://github.com/JetBrains/ideavim/pull/729) by [chylex](https://github.com/chylex): Add operating system type to `has()` function
* [726](https://github.com/JetBrains/ideavim/pull/726) by [Matt Ellis](https://github.com/citizenmatt): Fix range for fall back comment mode
## 2.5.0, 2023-09-01 ## 2.5.0, 2023-09-01

View File

@ -255,8 +255,7 @@ Ex commands or via `:map` command mappings:
##### Some popular actions: ##### Some popular actions:
``` ```
QuickJavaDoc - Quick Documentation (not only for java, all languages) ShowHoverInfo - Quick Documentation and Error Description
ShowErrorDescription - Show description of the error under the caret (cursor hovering)
QuickImplementations - Quick Definition QuickImplementations - Quick Definition
``` ```

View File

@ -8,6 +8,7 @@
plugins { plugins {
kotlin("jvm") kotlin("jvm")
kotlin("plugin.serialization") version "1.8.21"
} }
group = "com.intellij" group = "com.intellij"
@ -18,6 +19,6 @@ repositories {
} }
dependencies { dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13") compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
} }

View File

@ -0,0 +1,55 @@
/*
* 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.intellij.vim.annotations
// TODO support numpad keys parsing, see :keycodes
/**
* It's not necessary a Vim command
* This annotation may be used for:
* - commands
* - motions
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class CommandOrMotion(val keys: Array<String>, vararg val modes: Mode)
annotation class TextObject(val keys: String)
enum class Mode(val abbrev: Char) {
/**
* Indicates this key mapping applies to Normal mode
*/
NORMAL('N'),
/**
* Indicates this key mapping applies to Visual mode
*/
VISUAL('X'),
/**
* Indicates this key mapping applies to Select mode
*/
SELECT('S'),
/**
* Indicates this key mapping applies to Operator Pending mode
*/
OP_PENDING('O'),
/**
* Indicates this key mapping applies to Insert mode
*/
INSERT('I'),
/**
* Indicates this key mapping applies to Command Line mode
*/
CMD_LINE('C'),
}

View File

@ -0,0 +1,14 @@
/*
* 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.intellij.vim.processors
import kotlinx.serialization.Serializable
@Serializable
data class CommandBean(val keys: String, val `class`: String, val modes: String)

View File

@ -0,0 +1,62 @@
/*
* 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.intellij.vim.processors
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFile
import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.intellij.vim.annotations.CommandOrMotion
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import java.nio.file.Files
import kotlin.io.path.Path
import kotlin.io.path.writeText
class CommandOrMotionProcessor(private val environment: SymbolProcessorEnvironment): SymbolProcessor {
private val visitor = CommandOrMotionVisitor()
private val commands = mutableListOf<CommandBean>()
private val json = Json { prettyPrint = true }
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
val generatedDirPath = Path(environment.options["generated_directory"]!!)
Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["commands_file"]!!)
val fileContent = json.encodeToString(commands)
filePath.writeText(fileContent)
return emptyList()
}
private inner class CommandOrMotionVisitor : KSVisitorVoid() {
@OptIn(KspExperimental::class)
override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
val commandAnnotation = classDeclaration.getAnnotationsByType(CommandOrMotion::class).firstOrNull() ?: return
for (key in commandAnnotation.keys) {
commands.add(
CommandBean(key, classDeclaration.qualifiedName!!.asString(), commandAnnotation.modes.map { it.abbrev }.joinToString(separator = ""))
)
}
}
override fun visitFile(file: KSFile, data: Unit) {
file.declarations.forEach { it.accept(this, Unit) }
}
}
}

View File

@ -20,6 +20,7 @@ import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.intellij.vim.annotations.ExCommand import com.intellij.vim.annotations.ExCommand
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.nio.file.Files
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.writeText import kotlin.io.path.writeText
@ -31,7 +32,11 @@ class ExCommandProcessor(private val environment: SymbolProcessorEnvironment): S
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getAllFiles().forEach { it.accept(visitor, Unit) } resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
val filePath = Path(environment.options["generated_directory"]!!, environment.options["ex_commands_file"]!!)
val generatedDirPath = Path(environment.options["generated_directory"]!!)
Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["ex_commands_file"]!!)
val fileContent = json.encodeToString(commandToClass) val fileContent = json.encodeToString(commandToClass)
filePath.writeText(fileContent) filePath.writeText(fileContent)

View File

@ -20,6 +20,7 @@ import com.google.devtools.ksp.symbol.KSVisitorVoid
import com.intellij.vim.annotations.VimscriptFunction import com.intellij.vim.annotations.VimscriptFunction
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.nio.file.Files
import kotlin.io.path.Path import kotlin.io.path.Path
import kotlin.io.path.writeText import kotlin.io.path.writeText
@ -31,7 +32,11 @@ class VimscriptFunctionProcessor(private val environment: SymbolProcessorEnviron
override fun process(resolver: Resolver): List<KSAnnotated> { override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getAllFiles().forEach { it.accept(visitor, Unit) } resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
val filePath = Path(environment.options["generated_directory"]!!, environment.options["vimscript_functions_file"]!!)
val generatedDirPath = Path(environment.options["generated_directory"]!!)
Files.createDirectories(generatedDirPath)
val filePath = generatedDirPath.resolve(environment.options["vimscript_functions_file"]!!)
val fileContent = json.encodeToString(nameToClass) val fileContent = json.encodeToString(nameToClass)
filePath.writeText(fileContent) filePath.writeText(fileContent)

View File

@ -0,0 +1,20 @@
/*
* 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.intellij.vim.providers
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.intellij.vim.processors.CommandOrMotionProcessor
class CommandOrMotionProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return CommandOrMotionProcessor(environment)
}
}

View File

@ -1,2 +1,3 @@
com.intellij.vim.providers.VimscriptFunctionProcessorProvider com.intellij.vim.providers.CommandOrMotionProcessorProvider
com.intellij.vim.providers.ExCommandProcessorProvider com.intellij.vim.providers.ExCommandProcessorProvider
com.intellij.vim.providers.VimscriptFunctionProcessorProvider

View File

@ -52,11 +52,11 @@ buildscript {
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r") classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.4") classpath("io.ktor:ktor-client-core:2.3.6")
classpath("io.ktor:ktor-client-cio:2.3.4") classpath("io.ktor:ktor-client-cio:2.3.5")
classpath("io.ktor:ktor-client-auth:2.3.4") classpath("io.ktor:ktor-client-auth:2.3.6")
classpath("io.ktor:ktor-client-content-negotiation:2.3.4") classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.4") classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
// This comes from the changelog plugin // This comes from the changelog plugin
// classpath("org.jetbrains:markdown:0.3.1") // classpath("org.jetbrains:markdown:0.3.1")
@ -69,7 +69,7 @@ plugins {
kotlin("jvm") version "1.8.21" kotlin("jvm") version "1.8.21"
application application
id("org.jetbrains.intellij") version "1.15.0" id("org.jetbrains.intellij") version "1.16.0"
id("org.jetbrains.changelog") version "2.2.0" id("org.jetbrains.changelog") version "2.2.0"
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
@ -82,9 +82,10 @@ plugins {
} }
ksp { ksp {
arg("generated_directory", "$projectDir/src/main/resources") arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
arg("vimscript_functions_file", "intellij_vimscript_functions.json") arg("vimscript_functions_file", "intellij_vimscript_functions.json")
arg("ex_commands_file", "intellij_ex_commands.json") arg("ex_commands_file", "intellij_ex_commands.json")
arg("commands_file", "intellij_commands.json")
} }
afterEvaluate { afterEvaluate {
@ -136,13 +137,13 @@ dependencies {
api(project(":vim-engine")) api(project(":vim-engine"))
ksp(project(":annotation-processors")) ksp(project(":annotation-processors"))
compileOnly(project(":annotation-processors")) implementation(project(":annotation-processors"))
testApi("com.squareup.okhttp3:okhttp:4.11.0") testApi("com.squareup.okhttp3:okhttp:4.12.0")
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0") testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
} }
configurations { configurations {

View File

@ -1,6 +1,6 @@
Welcome to the IdeaVim wiki! Welcome to the IdeaVim wiki!
- List of IdeaVim plugins: [[plugins|IdeaVim Plugins]] - List of IdeaVim plugins: [plugins](IdeaVim%20Plugins.md)
- Examples of `ideajoin` option (also known as "smart join"): [["ideajoin" examples|ideajoin-examples]] - Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples.md)
- List of "set" commands: [["set" commands|set-commands]] - List of "set" commands: ["set" commands](set-commands.md)
- Docs about "select" mode in vim: [[select mode|Select-mode]] - Docs about "select" mode in vim: [select mode](Select-mode.md)

View File

@ -77,7 +77,7 @@ Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
### Instructions ### Instructions
[[See here|NERDTree-support]]. [See here](NERDTree-support.md).
</details> </details>

View File

@ -8,14 +8,15 @@
# suppress inspection "UnusedProperty" for whole file # suppress inspection "UnusedProperty" for whole file
ideaVersion=2023.1.2 ideaVersion=2023.2
downloadIdeaSources=true downloadIdeaSources=true
instrumentPluginCode=true instrumentPluginCode=true
version=SNAPSHOT version=chylex-20
javaVersion=17 javaVersion=17
remoteRobotVersion=0.11.17 remoteRobotVersion=0.11.17
antlrVersion=4.10.1 antlrVersion=4.10.1
kotlin.incremental.useClasspathSnapshot=false
# Please don't forget to update kotlin version in buildscript section # Please don't forget to update kotlin version in buildscript section
kotlinVersion=1.8.21 kotlinVersion=1.8.21

View File

@ -20,13 +20,13 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.10") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
implementation("io.ktor:ktor-client-core:2.3.4") implementation("io.ktor:ktor-client-core:2.3.6")
implementation("io.ktor:ktor-client-cio:2.3.4") implementation("io.ktor:ktor-client-cio:2.3.5")
implementation("io.ktor:ktor-client-content-negotiation:2.3.4") implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.4") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
implementation("io.ktor:ktor-client-auth:2.3.4") implementation("io.ktor:ktor-client-auth:2.3.6")
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r") implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
// This is needed for jgit to connect to ssh // This is needed for jgit to connect to ssh

View File

@ -9,6 +9,7 @@
package scripts.release package scripts.release
import com.vdurmont.semver4j.Semver import com.vdurmont.semver4j.Semver
import org.eclipse.jgit.api.CreateBranchCommand
import org.eclipse.jgit.api.Git import org.eclipse.jgit.api.Git
import org.eclipse.jgit.lib.ObjectId import org.eclipse.jgit.lib.ObjectId
import org.eclipse.jgit.lib.Repository import org.eclipse.jgit.lib.Repository
@ -84,9 +85,14 @@ internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, O
} }
internal fun Git.checkoutBranch(name: String) { internal fun Git.checkoutBranch(name: String) {
println("Checking out $name")
val shouldCreateBranch = this.branchList().call().any { it.name == "refs/heads/$name" }.not() val shouldCreateBranch = this.branchList().call().any { it.name == "refs/heads/$name" }.not()
checkout() val checkoutCommand = checkout()
.setCreateBranch(shouldCreateBranch) .setCreateBranch(shouldCreateBranch)
.setName(name) .setName(name)
.call() if (shouldCreateBranch) {
// Without starting point the branch will be created on HEAD.
checkoutCommand.setStartPoint("origin/$name").setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
}
checkoutCommand.call()
} }

View File

@ -1,79 +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;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.maddyhome.idea.vim.group.KeyGroup;
import com.maddyhome.idea.vim.handler.ActionBeanClass;
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
import com.maddyhome.idea.vim.key.MappingOwner;
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.event.KeyEvent;
public class RegisterActions {
public static final ExtensionPointName<ActionBeanClass> VIM_ACTIONS_EP =
ExtensionPointName.create("IdeaVIM.vimAction");
/**
* Register all the key/action mappings for the plugin.
*/
public static void registerActions() {
registerVimCommandActions();
registerEmptyShortcuts();
registerEpListener();
}
private static void registerEpListener() {
// IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload actions on every EP change.
VIM_ACTIONS_EP.addChangeListener(() -> {
unregisterActions();
registerActions();
}, VimPlugin.getInstance());
}
public static @Nullable EditorActionHandlerBase findAction(@NotNull String id) {
return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream()
.filter(vimActionBean -> vimActionBean.getActionId().equals(id)).findFirst().map(ActionBeanClass::getInstance)
.orElse(null);
}
public static @NotNull EditorActionHandlerBase findActionOrDie(@NotNull String id) {
EditorActionHandlerBase action = findAction(id);
if (action == null) throw new RuntimeException("Action " + id + " is not registered");
return action;
}
public static void unregisterActions() {
KeyGroup keyGroup = VimPlugin.getKeyIfCreated();
if (keyGroup != null) {
keyGroup.unregisterCommandActions();
}
}
private static void registerVimCommandActions() {
KeyGroup parser = VimPlugin.getKey();
VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map(IjVimActionsInitiator::new)
.forEach(parser::registerCommandAction);
}
private static void registerEmptyShortcuts() {
final KeyGroup parser = VimPlugin.getKey();
// The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
// still need to register the shortcut, to make sure the editor doesn't swallow it.
parser
.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System.INSTANCE);
}
}

View File

@ -0,0 +1,100 @@
/*
* 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
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.extensions.ExtensionPointName
import com.maddyhome.idea.vim.action.EngineCommandProvider
import com.maddyhome.idea.vim.action.IntellijCommandProvider
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.handler.ActionBeanClass
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import com.maddyhome.idea.vim.key.MappingOwner
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator
import com.maddyhome.idea.vim.newapi.globalIjOptions
import java.awt.event.KeyEvent
import javax.swing.KeyStroke
public object RegisterActions {
@Deprecated("Please use @CommandOrMotion annotation instead")
internal val VIM_ACTIONS_EP: ExtensionPointName<ActionBeanClass> = ExtensionPointName.create("IdeaVIM.vimAction")
/**
* Register all the key/action mappings for the plugin.
*/
@JvmStatic
public fun registerActions() {
registerVimCommandActions()
if (!injector.globalIjOptions().commandOrMotionAnnotation) {
registerEmptyShortcuts()
registerEpListener()
}
}
@Deprecated("Moving to annotations approach instead of xml")
private fun registerEpListener() {
// IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
// but let's support dynamic plugins in a more classic way and reload actions on every EP change.
VIM_ACTIONS_EP.addChangeListener({
unregisterActions()
registerActions()
}, VimPlugin.getInstance())
}
public fun findAction(id: String): EditorActionHandlerBase? {
if (injector.globalIjOptions().commandOrMotionAnnotation) {
val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
return commandBean.instance
} else {
return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream()
.filter { vimActionBean: ActionBeanClass -> vimActionBean.actionId == id }
.findFirst().map { obj: ActionBeanClass -> obj.instance }
.orElse(null)
}
}
public fun findActionOrDie(id: String): EditorActionHandlerBase {
return findAction(id) ?: throw RuntimeException("Action $id is not registered")
}
@JvmStatic
public fun unregisterActions() {
val keyGroup = VimPlugin.getKeyIfCreated()
keyGroup?.unregisterCommandActions()
}
private fun registerVimCommandActions() {
val parser = VimPlugin.getKey()
if (injector.globalIjOptions().commandOrMotionAnnotation) {
EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
} else {
VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map { bean: ActionBeanClass? ->
IjVimActionsInitiator(
bean!!
)
}
.forEach { actionHolder: IjVimActionsInitiator? ->
parser.registerCommandAction(
actionHolder!!
)
}
}
}
// todo do we really need this?
private fun registerEmptyShortcuts() {
val parser = VimPlugin.getKey()
// The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
// still need to register the shortcut, to make sure the editor doesn't swallow it.
parser
.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System)
}
}

View File

@ -306,11 +306,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
* This is required to ensure that all options are correctly initialised and registered. Must be before any commands * This is required to ensure that all options are correctly initialised and registered. Must be before any commands
* are executed.</li> * are executed.</li>
* <li>~/.ideavimrc execution<br> * <li>~/.ideavimrc execution<br>
* <ul>
* <li>4.1 executes commands from the .ideavimrc file and 4.2 initializes extensions.</li>
* <li>4.1 MUST BE BEFORE 4.2. This is a flow of vim/IdeaVim initialization, firstly .ideavimrc is executed and then
* the extensions are initialized.</li>
* </ul>
* </li> * </li>
* <li>Components initialization<br> * <li>Components initialization<br>
* This should happen after ideavimrc execution because VimListenerManager accesses `number` option * This should happen after ideavimrc execution because VimListenerManager accesses `number` option
@ -339,13 +334,9 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
VimInjectorKt.getInjector().getOptionGroup().initialiseOptions(); VimInjectorKt.getInjector().getOptionGroup().initialiseOptions();
// 4) ~/.ideavimrc execution // 4) ~/.ideavimrc execution
// 4.1) Execute ~/.ideavimrc
// Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window // Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window
registerIdeavimrc(VimInjectorKt.getInjector().getFallbackWindow()); registerIdeavimrc(VimInjectorKt.getInjector().getFallbackWindow());
// 4.2) Initialize extensions. Always after 4.1
VimExtensionRegistrar.enableDelayedExtensions();
// Turing on should be performed after all commands registration // Turing on should be performed after all commands registration
getSearch().turnOn(); getSearch().turnOn();
VimListenerManager.INSTANCE.turnOn(); VimListenerManager.INSTANCE.turnOn();

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action package com.maddyhome.idea.vim.action
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -14,6 +16,7 @@ import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.VimActionHandler import com.maddyhome.idea.vim.handler.VimActionHandler
@CommandOrMotion(keys = [":"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
internal class ExEntryAction : VimActionHandler.SingleExecution() { internal class ExEntryAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY

View File

@ -0,0 +1,13 @@
/*
* 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.action
public object IntellijCommandProvider : CommandProvider {
override val commandListFileName: String = "intellij_commands.json"
}

View File

@ -0,0 +1,74 @@
/*
* 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.action
import com.intellij.codeInsight.hint.HintManagerImpl
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.CommonDataKeys
import com.intellij.openapi.actionSystem.PerformWithDocumentsCommitted
import com.intellij.openapi.actionSystem.PopupAction
import com.intellij.openapi.actionSystem.impl.ActionConfigurationCustomizer
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorMouseHoverPopupManager
import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseEventArea
import com.intellij.openapi.project.DumbAware
import java.awt.event.MouseEvent
// [VERSION UPDATE] 233+ Remove class
// The ShowHoverInfo action is built into the platform (using a nicer EditorMouseHoverPopupManager API)
public class VimActionConfigurationCustomizer : ActionConfigurationCustomizer {
public override fun customize(actionManager: ActionManager) {
// If the ShowHoverInfo action doesn't exist in the platform, add our own implementation
if (actionManager.getAction("ShowHoverInfo") == null) {
actionManager.registerAction("ShowHoverInfo", VimShowHoverInfoAction())
}
}
private class VimShowHoverInfoAction : AnAction(), HintManagerImpl.ActionToIgnore, PopupAction, DumbAware,
PerformWithDocumentsCommitted {
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
override fun update(e: AnActionEvent) {
val dataContext = e.dataContext
val editor = CommonDataKeys.EDITOR.getData(dataContext)
if (editor == null) {
e.presentation.isEnabledAndVisible = false
}
}
override fun actionPerformed(e: AnActionEvent) {
val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return
val editorMouseEvent = createFakeEditorMouseEvent(editor)
EditorMouseHoverPopupManager.getInstance().showInfoTooltip(editorMouseEvent)
}
private fun createFakeEditorMouseEvent(editor: Editor): EditorMouseEvent {
val xy = editor.offsetToXY(editor.caretModel.offset)
val mouseEvent =
MouseEvent(editor.component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, xy.x, xy.y, 0, false)
val editorMouseEvent = EditorMouseEvent(
editor,
mouseEvent,
EditorMouseEventArea.EDITING_AREA,
editor.caretModel.offset,
editor.caretModel.logicalPosition,
editor.caretModel.visualPosition,
true,
null,
null,
null
)
return editorMouseEvent
}
}
}

View File

@ -14,10 +14,14 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnAction import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionWrapper import com.intellij.openapi.actionSystem.AnActionWrapper
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.application.invokeLater import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionManager
import com.intellij.openapi.keymap.KeymapManager
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.project.DumbAware import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
@ -28,7 +32,6 @@ import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.group.IjOptions import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.handler.enableOctopus
import com.maddyhome.idea.vim.handler.isOctopusEnabled import com.maddyhome.idea.vim.handler.isOctopusEnabled
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.HandlerInjector import com.maddyhome.idea.vim.helper.HandlerInjector
@ -95,25 +98,25 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
override fun update(e: AnActionEvent) { override fun update(e: AnActionEvent) {
val start = if (traceTime) System.currentTimeMillis() else null val start = if (traceTime) System.currentTimeMillis() else null
val actionEnableStatus = isEnabled(e)
e.presentation.isEnabled = actionEnableStatus.isEnabled
actionEnableStatus.printLog()
if (start != null) {
val keyStroke = getKeyStroke(e) val keyStroke = getKeyStroke(e)
val actionEnableStatus = isEnabled(e, keyStroke)
e.presentation.isEnabled = actionEnableStatus.isEnabled
actionEnableStatus.printLog(keyStroke)
if (start != null) {
val duration = System.currentTimeMillis() - start val duration = System.currentTimeMillis() - start
LOG.info("VimShortcut update '$keyStroke': $duration ms") LOG.info("VimShortcut update '$keyStroke': $duration ms")
} }
} }
private fun isEnabled(e: AnActionEvent): ActionEnableStatus { private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG) if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
val editor = getEditor(e) val editor = getEditor(e)
val keyStroke = getKeyStroke(e)
if (editor != null && keyStroke != null) { if (editor != null && keyStroke != null) {
if (enableOctopus) {
if (isOctopusEnabled(keyStroke, editor)) { if (isOctopusEnabled(keyStroke, editor)) {
return ActionEnableStatus.no("Octopus handler is enabled", LogLevel.DEBUG) return ActionEnableStatus.no(
} "Processing VimShortcutKeyAction for the key that is used in the octopus handler",
LogLevel.ERROR
)
} }
if (editor.isIdeaVimDisabledHere) { if (editor.isIdeaVimDisabledHere) {
return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO) return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO)
@ -160,6 +163,14 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
return ActionEnableStatus.no("App code template is active", LogLevel.INFO) return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
} }
val nextTemplateVariableShortcuts = KeymapManager.getInstance().activeKeymap.getShortcuts(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
if (nextTemplateVariableShortcuts.any { it is KeyboardShortcut && it.firstKeyStroke == keyStroke }) {
val handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
if (handler.isEnabled(editor, null, e.dataContext)) {
return ActionEnableStatus.no("Next template variable or finish in-place refactoring", LogLevel.INFO)
}
}
if (editor.inInsertMode) { if (editor.inInsertMode) {
if (keyCode == KeyEvent.VK_TAB) { if (keyCode == KeyEvent.VK_TAB) {
// TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view // TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
@ -361,10 +372,12 @@ private class ActionEnableStatus(
val message: String, val message: String,
val logLevel: LogLevel, val logLevel: LogLevel,
) { ) {
fun printLog() { fun printLog(keyStroke: KeyStroke?) {
val message = "IdeaVim keys are enabled = $isEnabled for key '$keyStroke': $message"
when (logLevel) { when (logLevel) {
LogLevel.INFO -> LOG.info("IdeaVim keys are enabled = $isEnabled: $message") LogLevel.INFO -> LOG.info(message)
LogLevel.DEBUG -> LOG.debug("IdeaVim keys are enabled = $isEnabled: $message") LogLevel.DEBUG -> LOG.debug(message)
LogLevel.ERROR -> LOG.error(message)
} }
} }
@ -377,5 +390,5 @@ private class ActionEnableStatus(
} }
private enum class LogLevel { private enum class LogLevel {
DEBUG, INFO, DEBUG, INFO, ERROR,
} }

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action.change package com.maddyhome.idea.vim.action.change
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
@ -47,6 +49,7 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
return result return result
} }
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])
internal class OperatorAction : VimActionHandler.SingleExecution() { internal class OperatorAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
@ -97,6 +100,7 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
} }
} }
@CommandOrMotion(keys = ["g@"], modes = [Mode.VISUAL])
internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() { internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action.change package com.maddyhome.idea.vim.action.change
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
@ -18,6 +20,7 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
internal class RepeatChangeAction : VimActionHandler.SingleExecution() { internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_WRITABLE override val type: Command.Type = Command.Type.OTHER_WRITABLE

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action.change.delete package com.maddyhome.idea.vim.action.change.delete
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -17,6 +19,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.newapi.ijOptions import com.maddyhome.idea.vim.newapi.ijOptions
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() { public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
override fun runAsMulticaret( override fun runAsMulticaret(

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action.change.delete package com.maddyhome.idea.vim.action.change.delete
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
@ -16,6 +18,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
import com.maddyhome.idea.vim.newapi.ijOptions import com.maddyhome.idea.vim.newapi.ijOptions
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() { public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action.change.delete package com.maddyhome.idea.vim.action.change.delete
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -23,6 +25,7 @@ import java.util.*
/** /**
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() { public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action.change.delete package com.maddyhome.idea.vim.action.change.delete
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -23,6 +25,7 @@ import java.util.*
/** /**
* @author vlan * @author vlan
*/ */
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() { public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE

View File

@ -8,6 +8,8 @@
package com.maddyhome.idea.vim.action.editor package com.maddyhome.idea.vim.action.editor
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.maddyhome.idea.vim.action.ComplicatedKeysAction import com.maddyhome.idea.vim.action.ComplicatedKeysAction
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
@ -23,6 +25,7 @@ import java.awt.event.KeyEvent
import java.util.* import java.util.*
import javax.swing.KeyStroke import javax.swing.KeyStroke
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE), ComplicatedKeysAction { internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK)),
@ -31,6 +34,7 @@ internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BA
override val type: Command.Type = Command.Type.DELETE override val type: Command.Type = Command.Type.DELETE
} }
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE), ComplicatedKeysAction { internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)),
@ -39,6 +43,7 @@ internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELET
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
} }
@CommandOrMotion(keys = ["<Down>", "<kDown>"], modes = [Mode.INSERT])
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN), ComplicatedKeysAction { internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)),
@ -48,6 +53,7 @@ internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CA
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
} }
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), ComplicatedKeysAction { internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_DOWN_MASK)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_DOWN_MASK)),
@ -57,6 +63,7 @@ internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), Co
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
} }
@CommandOrMotion(keys = ["<Up>", "<kUp>"], modes = [Mode.INSERT])
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP), ComplicatedKeysAction { internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)), listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)),
@ -66,6 +73,7 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
} }
@CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL])
internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() { internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
override val type: Command.Type = Command.Type.OTHER_READONLY override val type: Command.Type = Command.Type.OTHER_READONLY

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.action.ex package com.maddyhome.idea.vim.action.ex
import com.intellij.vim.annotations.CommandOrMotion
import com.intellij.vim.annotations.Mode
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.action.ComplicatedKeysAction import com.maddyhome.idea.vim.action.ComplicatedKeysAction
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
@ -23,6 +25,7 @@ import javax.swing.KeyStroke
* *
* The mapping for this action means that the ex command is executed as a write action * The mapping for this action means that the ex command is executed as a write action
*/ */
@CommandOrMotion(keys = ["<CR>", "<C-M>", "<C-J>"], modes = [Mode.CMD_LINE])
public class ProcessExEntryAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction { public class ProcessExEntryAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = override val keyStrokesSet: Set<List<KeyStroke>> =
parseKeysSet("<CR>", "<C-M>", 0x0a.toChar().toString(), 0x0d.toChar().toString()) parseKeysSet("<CR>", "<C-M>", 0x0a.toChar().toString(), 0x0d.toChar().toString())

View File

@ -82,6 +82,31 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
} }
} }
/**
* During vim initialization process, it firstly loads the .vimrc file, then executes scripts from the plugins folder.
* This practically means that the .vimrc file is initialized first, then the plugins are loaded.
* See `:h initialization`
*
* In IdeaVim we don't have a separate plugins folder to load it after .ideavimrc load. However, we can collect
* the list of plugins mentioned in the .ideavimrc and load them after .ideavimrc execution is finished.
*
* Why this matters? Because this affects the order of commands are executed. For example:
* ```
* plug 'tommcdo/vim-exchange'
* let g:exchange_no_mappings=1
* ```
* Here the user will expect that the exchange plugin won't have default mappings. However, if we load vim-exchange
* immediately, this variable won't be initialized at the moment of plugin initialization.
*
* There is also a tricky case for mappings override:
* ```
* plug 'tommcdo/vim-exchange'
* map X <Plug>(ExchangeLine)
* ```
* For this case, a plugin with a good implementation detects that there is already a defined mapping for
* `<Plug>(ExchangeLine)` and doesn't register the default cxx mapping. However, such detection requires the mapping
* to be defined before the plugin initialization.
*/
@JvmStatic @JvmStatic
fun enableDelayedExtensions() { fun enableDelayedExtensions() {
delayedExtensionEnabling.forEach { delayedExtensionEnabling.forEach {

View File

@ -11,6 +11,7 @@ import com.intellij.codeInsight.actions.AsyncActionExecutionService
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.application.runWriteAction import com.intellij.openapi.application.runWriteAction
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Ref import com.intellij.openapi.util.Ref
import com.intellij.psi.PsiComment import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement import com.intellij.psi.PsiElement
@ -74,17 +75,24 @@ internal class CommentaryExtension : VimExtension {
listOf(IdeActions.ACTION_COMMENT_BLOCK, IdeActions.ACTION_COMMENT_LINE) listOf(IdeActions.ACTION_COMMENT_BLOCK, IdeActions.ACTION_COMMENT_LINE)
} }
val res = Ref.create<Boolean>(true) val project = editor.ij.project!!
AsyncActionExecutionService.getInstance(editor.ij.project!!).withExecutionAfterAction(actions[0], { val callback = { afterCommenting(mode, editor, resetCaret, range) }
res.set(injector.actionExecutor.executeAction(actions[0], context)) actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) }
}, { afterCommenting(mode, editor, resetCaret, range) })
if (!res.get()) {
AsyncActionExecutionService.getInstance(editor.ij.project!!).withExecutionAfterAction(actions[1], {
res.set(injector.actionExecutor.executeAction(actions[1], context))
}, { afterCommenting(mode, editor, resetCaret, range) })
} }
res.get()
} }
private fun executeActionWithCallbackOnSuccess(
action: String,
project: Project,
context: ExecutionContext,
callback: () -> Unit,
): Boolean {
val res = Ref.create<Boolean>(false)
AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
action,
{ res.set(injector.actionExecutor.executeAction(action, context)) },
{ if (res.get()) callback() })
return res.get()
} }
private fun afterCommenting( private fun afterCommenting(

View File

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

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

@ -12,16 +12,14 @@ import com.intellij.openapi.editor.Editor
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimCaret 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.VimEditor
import com.maddyhome.idea.vim.api.endsWithNewLine import com.maddyhome.idea.vim.api.endsWithNewLine
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.setChangeMarks import com.maddyhome.idea.vim.api.setChangeMarks
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.command.OperatorArguments import com.maddyhome.idea.vim.command.OperatorArguments
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.extension.ExtensionHandler import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.extension.VimExtension import com.maddyhome.idea.vim.extension.VimExtension
@ -33,12 +31,18 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
import com.maddyhome.idea.vim.key.OperatorFunction 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.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
import com.maddyhome.idea.vim.put.PutData import com.maddyhome.idea.vim.put.PutData
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@ -79,7 +83,7 @@ internal class VimSurroundExtension : VimExtension {
override val isRepeatable = true override val isRepeatable = true
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
setOperatorFunction(Operator()) setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
} }
} }
@ -100,7 +104,7 @@ internal class VimSurroundExtension : VimExtension {
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset) val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
if (lastNonWhiteSpaceOffset != null) { if (lastNonWhiteSpaceOffset != null) {
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1) val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
performSurround(pair, range, it) performSurround(pair, range, it, count = operatorArguments.count1)
} }
// it.moveToOffset(lineStartOffset) // it.moveToOffset(lineStartOffset)
} }
@ -120,15 +124,13 @@ internal class VimSurroundExtension : VimExtension {
private class VSurroundHandler : ExtensionHandler { private class VSurroundHandler : ExtensionHandler {
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) { override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
// NB: Operator ignores SelectionType anyway // 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 return
} }
runWriteAction { runWriteAction {
// Leave visual mode // Leave visual mode
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij) executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
editor.ij.caretModel.moveToOffset(selectionStart)
} }
} }
} }
@ -149,6 +151,10 @@ internal class VimSurroundExtension : VimExtension {
companion object { companion object {
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) { fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
}
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
// Save old register values for carets // Save old register values for carets
val surroundings = editor.sortedCarets() val surroundings = editor.sortedCarets()
.map { .map {
@ -255,21 +261,42 @@ internal class VimSurroundExtension : VimExtension {
} }
} }
private class Operator : OperatorFunction { private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean { override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
val ijEditor = editor.ij val editor = vimEditor.ij
val c = getChar(ijEditor) val c = getChar(editor)
if (c.code == 0) return true if (c.code == 0) return true
val pair = getOrInputPair(c, ijEditor) ?: return false val pair = getOrInputPair(c, editor) ?: return false
// XXX: Will it work with line-wise or block-wise selections?
val range = getSurroundRange(editor.currentCaret()) ?: return false runWriteAction {
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE) val change = VimPlugin.getChange()
if (supportsMultipleCursors) {
editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair, count)
}
}
else {
applyOnce(editor, change, pair, count)
// Jump back to start // Jump back to start
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
}
}
return true return true
} }
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, 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? { private fun getSurroundRange(caret: VimCaret): TextRange? {
val editor = caret.editor val editor = caret.editor
val ijEditor = editor.ij val ijEditor = editor.ij
@ -348,15 +375,15 @@ internal class VimSurroundExtension : VimExtension {
} }
} }
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) { private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
runWriteAction { runWriteAction {
val editor = caret.editor val editor = caret.editor
val change = VimPlugin.getChange() 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 isEOF = range.endOffset == editor.text().length
val hasNewLine = editor.endsWithNewLine() val hasNewLine = editor.endsWithNewLine()
val rightSurround = if (tagsOnNewLines) { val rightSurround = (if (tagsOnNewLines) {
if (isEOF && !hasNewLine) { if (isEOF && !hasNewLine) {
"\n" + pair.second "\n" + pair.second
} else { } else {
@ -364,7 +391,7 @@ internal class VimSurroundExtension : VimExtension {
} }
} else { } else {
pair.second pair.second
} }).let { RepeatedCharSequence.of(it, count) }
change.insertText(editor, caret, range.startOffset, leftSurround) change.insertText(editor, caret, range.startOffset, leftSurround)
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround) change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)

View File

@ -16,12 +16,14 @@ import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.LogicalPosition import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.actions.EnterAction
import com.intellij.openapi.editor.event.EditorMouseEvent import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.impl.TextRangeInterval import com.intellij.openapi.editor.impl.TextRangeInterval
import com.intellij.openapi.ui.MessageType import com.intellij.openapi.ui.MessageType
import com.intellij.openapi.ui.popup.Balloon import com.intellij.openapi.ui.popup.Balloon
import com.intellij.openapi.ui.popup.JBPopupFactory import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.CodeStyleManager
import com.intellij.psi.util.PsiUtilBase import com.intellij.psi.util.PsiUtilBase
@ -52,6 +54,7 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.handler.Motion import com.maddyhome.idea.vim.handler.Motion
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
import com.maddyhome.idea.vim.handler.commandContinuation
import com.maddyhome.idea.vim.helper.CharacterHelper import com.maddyhome.idea.vim.helper.CharacterHelper
import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
import com.maddyhome.idea.vim.helper.CharacterHelper.charType import com.maddyhome.idea.vim.helper.CharacterHelper.charType
@ -69,8 +72,10 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
import com.maddyhome.idea.vim.newapi.IjVimCaret import com.maddyhome.idea.vim.newapi.IjVimCaret
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import java.math.BigInteger import java.math.BigInteger
@ -116,6 +121,35 @@ public class ChangeGroup : VimChangeGroupBase() {
injector.scroll.scrollCaretIntoView(vimEditor) injector.scroll.scrollCaretIntoView(vimEditor)
} }
/**
* If this is REPLACE mode we need to turn off OVERWRITE before and then turn OVERWRITE back on after sending the
* "ENTER" key.
*/
override fun processEnter(
editor: VimEditor,
caret: VimCaret,
context: ExecutionContext,
) {
if (editor.mode is Mode.REPLACE) {
editor.insertMode = true
}
try {
val continuation = (context.context as UserDataHolder).getUserData(commandContinuation)
val ijEditor = editor.ij
val ij = context.ij
val ijCaret = caret.ij
if (continuation != null) {
continuation.execute(ijEditor, ijCaret, ij)
} else {
EnterAction().handler.execute(ijEditor, ijCaret, ij)
}
} finally {
if (editor.mode is Mode.REPLACE) {
editor.insertMode = false
}
}
}
override fun getDeleteRangeAndType2( override fun getDeleteRangeAndType2(
editor: VimEditor, editor: VimEditor,
caret: VimCaret, caret: VimCaret,

View File

@ -89,14 +89,17 @@ public class FileGroup extends VimFileBase {
@Nullable VirtualFile findFile(@NotNull String filename, @NotNull Project project) { @Nullable VirtualFile findFile(@NotNull String filename, @NotNull Project project) {
VirtualFile found; VirtualFile found;
if (filename.length() > 2 && filename.charAt(0) == '~' && filename.charAt(1) == File.separatorChar) { // Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
String homefile = filename.substring(2); // it only supports forward slash on Unix (tested on Mac)
// VFS works with both directory separators (tested on Mac and Windows)
if (filename.startsWith("~/") || filename.startsWith("~\\")) {
String relativePath = filename.substring(2);
String dir = System.getProperty("user.home"); String dir = System.getProperty("user.home");
if (logger.isDebugEnabled()) { if (logger.isDebugEnabled()) {
logger.debug("home dir file"); logger.debug("home dir file");
logger.debug("looking for " + homefile + " in " + dir); logger.debug("looking for " + relativePath + " in " + dir);
} }
found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, homefile)); found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, relativePath));
} }
else { else {
found = LocalFileSystem.getInstance().findFileByIoFile(new File(filename)); found = LocalFileSystem.getInstance().findFileByIoFile(new File(filename));

View File

@ -31,11 +31,11 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
public var visualdelay: Int by optionProperty(IjOptions.visualdelay) public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
// Temporary options to control work-in-progress behaviour // Temporary options to control work-in-progress behaviour
public var octopushandler: Boolean by optionProperty(IjOptions.octopushandler)
public var oldundo: Boolean by optionProperty(IjOptions.oldundo) public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps) public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation) public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation) public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
} }
/** /**

View File

@ -80,12 +80,12 @@ public object IjOptions {
"lookupkeys", "lookupkeys",
"<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>") "<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
) )
public val octopushandler: ToggleOption = addOption(ToggleOption("octopushandler", GLOBAL, "octopushandler", false))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true))
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false)) public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true)) public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true))
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100)) public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which // This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
// derives from Option<VimInt> // derives from Option<VimInt>

View File

@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.EventFacade;
import com.maddyhome.idea.vim.VimPlugin; import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.action.ComplicatedKeysAction; import com.maddyhome.idea.vim.action.ComplicatedKeysAction;
import com.maddyhome.idea.vim.action.VimShortcutKeyAction; import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
import com.maddyhome.idea.vim.action.change.LazyVimCommand;
import com.maddyhome.idea.vim.api.*; import com.maddyhome.idea.vim.api.*;
import com.maddyhome.idea.vim.command.MappingMode; import com.maddyhome.idea.vim.command.MappingMode;
import com.maddyhome.idea.vim.ex.ExOutputModel; import com.maddyhome.idea.vim.ex.ExOutputModel;
@ -208,6 +209,25 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
registerRequiredShortcut(Collections.singletonList(keyStroke), owner); registerRequiredShortcut(Collections.singletonList(keyStroke), owner);
} }
public void registerCommandAction(@NotNull LazyVimCommand command) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
initIdentityChecker();
for (List<KeyStroke> keys : command.getKeys()) {
checkCommand(command.getModes(), command, keys);
}
}
for (List<KeyStroke> keyStrokes : command.getKeys()) {
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE);
for (MappingMode mappingMode : command.getModes()) {
Node<VimActionsInitiator> node = getKeyRoot(mappingMode);
NodesKt.addLeafs(node, keyStrokes, command);
}
}
}
@Deprecated
public void registerCommandAction(@NotNull VimActionsInitiator actionHolder) { public void registerCommandAction(@NotNull VimActionsInitiator actionHolder) {
IjVimActionsInitiator holder = (IjVimActionsInitiator)actionHolder; IjVimActionsInitiator holder = (IjVimActionsInitiator)actionHolder;
@ -254,7 +274,9 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) { private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
for (KeyStroke key : keys) { for (KeyStroke key : keys) {
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) { if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
key.getKeyCode() != KeyEvent.VK_ESCAPE &&
key.getKeyCode() != KeyEvent.VK_ENTER) {
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner)); getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
} }
} }

View File

@ -28,7 +28,7 @@ import java.util.Set;
@Deprecated @Deprecated
@ApiStatus.ScheduledForRemoval(inVersion = "2.3") @ApiStatus.ScheduledForRemoval(inVersion = "2.3")
public class MarkGroup { public class MarkGroup {
public List<Jump> jumps = VimInjectorKt.injector.getJumpService().getJumps(); public List<Jump> jumps = VimInjectorKt.injector.getJumpService().getJumps("");
public void saveJumpLocation(@NotNull Editor editor) { public void saveJumpLocation(@NotNull Editor editor) {
VimInjectorKt.injector.getJumpService().saveJumpLocation(new IjVimEditor(editor)); VimInjectorKt.injector.getJumpService().saveJumpLocation(new IjVimEditor(editor));
@ -54,7 +54,7 @@ public class MarkGroup {
@Nullable @Nullable
public Jump getJump(int count) { public Jump getJump(int count) {
return VimInjectorKt.injector.getJumpService().getJump(count); return VimInjectorKt.injector.getJumpService().getJump("", count);
} }
@Nullable @Nullable
@ -115,7 +115,7 @@ public class MarkGroup {
} }
public int getJumpSpot() { public int getJumpSpot() {
return VimInjectorKt.injector.getJumpService().getJumpSpot(); return VimInjectorKt.injector.getJumpService().getJumpSpot("");
} }
public void updateMarkFromDelete(@Nullable VimEditor editor, public void updateMarkFromDelete(@Nullable VimEditor editor,
@ -133,6 +133,6 @@ public class MarkGroup {
} }
public void dropLastJump() { public void dropLastJump() {
VimInjectorKt.injector.getJumpService().dropLastJump(); VimInjectorKt.injector.getJumpService().dropLastJump("");
} }
} }

View File

@ -33,6 +33,8 @@ import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimMotionGroupBase import com.maddyhome.idea.vim.api.VimMotionGroupBase
import com.maddyhome.idea.vim.api.addJump import com.maddyhome.idea.vim.api.addJump
import com.maddyhome.idea.vim.api.anyNonWhitespace import com.maddyhome.idea.vim.api.anyNonWhitespace
import com.maddyhome.idea.vim.api.getJump
import com.maddyhome.idea.vim.api.getJumpSpot
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
import com.maddyhome.idea.vim.api.getVisualLineCount import com.maddyhome.idea.vim.api.getVisualLineCount
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
@ -163,8 +165,8 @@ internal class MotionGroup : VimMotionGroupBase() {
override fun moveCaretToJump(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Motion { override fun moveCaretToJump(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Motion {
val jumpService = injector.jumpService val jumpService = injector.jumpService
val spot = jumpService.getJumpSpot() val spot = jumpService.getJumpSpot(editor)
val (line, col, fileName) = jumpService.getJump(count) ?: return Motion.Error val (line, col, fileName) = jumpService.getJump(editor, count) ?: return Motion.Error
val vf = EditorHelper.getVirtualFile(editor.ij) ?: return Motion.Error val vf = EditorHelper.getVirtualFile(editor.ij) ?: return Motion.Error
val lp = BufferPosition(line, col, false) val lp = BufferPosition(line, col, false)
val lpNative = LogicalPosition(line, col, false) val lpNative = LogicalPosition(line, col, false)

View File

@ -15,6 +15,7 @@ import com.intellij.openapi.components.Storage
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.PlaceInfo import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.PlaceInfo
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.RecentPlacesListener import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.RecentPlacesListener
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil import com.intellij.openapi.util.text.StringUtil
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimJumpServiceBase import com.maddyhome.idea.vim.api.VimJumpServiceBase
@ -40,24 +41,34 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
} }
} }
// We do not delete old project records.
// Rationale: It's more likely that users will want to review their old projects and access their jump history
// (e.g., recent files), than for the 100 jumps (max number of records) to consume enough space to be noticeable.
override fun getState(): Element { override fun getState(): Element {
val jumpsElem = Element("jumps") val projectsElem = Element("projects")
for ((project, jumps) in projectToJumps) {
val projectElement = Element("project").setAttribute("id", project)
for (jump in jumps) { for (jump in jumps) {
val jumpElem = Element("jump") val jumpElem = Element("jump")
jumpElem.setAttribute("line", jump.line.toString()) jumpElem.setAttribute("line", jump.line.toString())
jumpElem.setAttribute("column", jump.col.toString()) jumpElem.setAttribute("column", jump.col.toString())
jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath)) jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
jumpsElem.addContent(jumpElem) projectElement.addContent(jumpElem)
if (logger.isDebug()) { if (logger.isDebug()) {
logger.debug("saved jump = $jump") logger.debug("saved jump = $jump")
} }
} }
return jumpsElem projectsElem.addContent(projectElement)
}
return projectsElem
} }
override fun loadState(state: Element) { override fun loadState(state: Element) {
val jumpList = state.getChildren("jump") val projectElements = state.getChildren("project")
for (jumpElement in jumpList) { for (projectElement in projectElements) {
val jumps = mutableListOf<Jump>()
val jumpElements = projectElement.getChildren("jump")
for (jumpElement in jumpElements) {
val jump = Jump( val jump = Jump(
Integer.parseInt(jumpElement.getAttributeValue("line")), Integer.parseInt(jumpElement.getAttributeValue("line")),
Integer.parseInt(jumpElement.getAttributeValue("column")), Integer.parseInt(jumpElement.getAttributeValue("column")),
@ -65,14 +76,16 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
) )
jumps.add(jump) jumps.add(jump)
} }
if (logger.isDebug()) { if (logger.isDebug()) {
logger.debug("jumps=$jumps") logger.debug("jumps=$jumps")
} }
val projectId = projectElement.getAttributeValue("id")
projectToJumps[projectId] = jumps
}
} }
} }
internal class JumpsListener : RecentPlacesListener { internal class JumpsListener(val project: Project) : RecentPlacesListener {
override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) { override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
if (!injector.globalIjOptions().unifyjumps) return if (!injector.globalIjOptions().unifyjumps) return
@ -81,7 +94,7 @@ internal class JumpsListener : RecentPlacesListener {
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
// we do not want jumps that were processed before // we do not want jumps that were processed before
val jump = buildJump(changePlace) ?: return val jump = buildJump(changePlace) ?: return
jumpService.addJump(jump, true) jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true)
} }
} }
@ -93,7 +106,7 @@ internal class JumpsListener : RecentPlacesListener {
if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
// we do not want jumps that were processed before // we do not want jumps that were processed before
val jump = buildJump(changePlace) ?: return val jump = buildJump(changePlace) ?: return
jumpService.removeJump(jump) jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump)
} }
} }

View File

@ -12,6 +12,7 @@ import com.intellij.serviceContainer.BaseKeyedLazyInstance
import com.intellij.util.SmartList import com.intellij.util.SmartList
import com.intellij.util.xmlb.annotations.Attribute import com.intellij.util.xmlb.annotations.Attribute
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
import javax.swing.KeyStroke import javax.swing.KeyStroke
/** /**
@ -36,6 +37,8 @@ import javax.swing.KeyStroke
* The reason is startup performance. Using the extension points you don't even have to load classes of actions. * The reason is startup performance. Using the extension points you don't even have to load classes of actions.
* So, all actions are loaded on demand, including classes in classloader. * So, all actions are loaded on demand, including classes in classloader.
*/ */
@Deprecated(message = "Please use CommandOrMotion annotation")
@ScheduledForRemoval(inVersion = "2.9.0")
internal class ActionBeanClass : BaseKeyedLazyInstance<EditorActionHandlerBase>() { internal class ActionBeanClass : BaseKeyedLazyInstance<EditorActionHandlerBase>() {
@Attribute("implementation") @Attribute("implementation")
var implementation: String? = null var implementation: String? = null

View File

@ -0,0 +1,48 @@
/*
* 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.handler
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
/**
* Logs the chain of handlers for esc and enter
*
* As we made a migration to the new way of handling esc keys (VIM-2974), we may face several issues around that
* One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
* the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
*
* This is a logger that logs the chain of handlers.
*
* Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
* otherwise, so let's use it as long as we can.
*/
internal class EditorHandlersChainLogger : StartupActivity {
@Suppress("UnresolvedPluginConfigReference")
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
override fun runActivity(project: Project) {
val escHandlers = editorHandlers.extensionList
.filter { it.action == "EditorEscape" }
.joinToString("\n") { it.implementationClass }
val enterHandlers = editorHandlers.extensionList
.filter { it.action == "EditorEnter" }
.joinToString("\n") { it.implementationClass }
LOG.info("Esc handlers chain:\n$escHandlers")
LOG.info("Enter handlers chain:\n$enterHandlers")
}
companion object {
val LOG = logger<EditorHandlersChainLogger>()
}
}

View File

@ -8,27 +8,65 @@
package com.maddyhome.idea.vim.handler package com.maddyhome.idea.vim.handler
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.ide.DataManager
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.invokeLater
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler import com.intellij.openapi.editor.actionSystem.EditorActionHandler
import com.intellij.openapi.editor.impl.CaretModelImpl
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.removeUserData
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key import com.maddyhome.idea.vim.api.key
import com.maddyhome.idea.vim.command.CommandState import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.helper.mode import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.inNormalMode
import com.maddyhome.idea.vim.helper.isPrimaryEditor
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
import com.maddyhome.idea.vim.newapi.actionStartedFromVim import com.maddyhome.idea.vim.newapi.actionStartedFromVim
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.mode
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
internal val commandContinuation = Key.create<EditorActionHandler>("commandContinuation")
/**
* Handler that corrects the shape of the caret in python notebooks.
*
* By default, py notebooks show a thin caret after entering the cell.
* However, we're in normal mode, so this handler fixes it.
*/
internal class CaretShapeEnterEditorHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
if (VimPlugin.isEnabled()) {
invokeLater {
editor.updateCaretsVisualAttributes()
}
}
nextHandler.execute(editor, caret, dataContext)
}
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return nextHandler.isEnabled(editor, caret, dataContext)
}
}
/** /**
* This handler doesn't work in tests for ex commands * This handler doesn't work in tests for ex commands
*/ */
internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() { internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler?) : EditorActionHandler() {
abstract fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) abstract fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?)
open fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean { open fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
@ -37,64 +75,223 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
final override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { final override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
if (isThisHandlerEnabled(editor, caret, dataContext)) { if (isThisHandlerEnabled(editor, caret, dataContext)) {
val executeInInvokeLater = executeInInvokeLater(editor)
val executionHandler = {
try {
(dataContext as? UserDataHolder)?.putUserData(commandContinuation, nextHandler)
executeHandler(editor, caret, dataContext) executeHandler(editor, caret, dataContext)
} else { } finally {
nextHandler.execute(editor, caret, dataContext) (dataContext as? UserDataHolder)?.removeUserData(commandContinuation)
} }
} }
@Suppress("RedundantIf") if (executeInInvokeLater) {
// This `invokeLater` is used to escape the potential `runForEachCaret` function.
//
// The `runForEachCaret` function is disallowed to be called recursively. However, with this new handler, we lose
// control if we execute the code inside this function or not. See IDEA-300030 for details.
// This means the code in IdeaVim MUST NOT call `runForEachCaret` function. While this is possible for most cases,
// the user may make a mapping to some intellij action where the `runForEachCaret` is called. This breaks
// the condition (see VIM-3103 for example).
// Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
// done by scheduling the execution of our code later via the invokeLater function.
ApplicationManager.getApplication().invokeLater(executionHandler)
} else {
executionHandler()
}
} else {
nextHandler?.execute(editor, caret, dataContext)
}
}
private fun executeInInvokeLater(editor: Editor): Boolean {
// Currently we have a workaround for the PY console VIM-3157
if (FileDocumentManager.getInstance().getFile(editor.document)?.name == "Python Console.py") return false
return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
}
private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean { private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean {
if (!VimPlugin.isEnabled()) return false if (!VimPlugin.isEnabled()) return false
if (!isHandlerEnabled(editor, dataContext)) return false if (!isHandlerEnabled(editor, dataContext)) return false
if (dataContext?.actionStartedFromVim == true) return false if (isNotActualKeyPress(dataContext)) return false
if (!enableOctopus) return false
return true return true
} }
/**
* In some cases IJ runs handlers to imitate "enter" or other key. In such cases we should not process it on the
* IdeaVim side because the user may have mappings on enter the we'll get an unexpected behaviour.
* This method should return true if we detect that this handler is called in such case and this is not an
* actual keypress from the user.
*/
private fun isNotActualKeyPress(dataContext: DataContext?): Boolean {
if (dataContext != null) {
// This flag is set when the enter handlers are executed as a part of moving the comment on the new line
val dataManager = DataManager.getInstance()
if (dataManager.loadFromDataContext(dataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) == true) {
return true
}
if (dataManager.loadFromDataContext(dataContext, ShiftEnterDetector.Util.key) == true) {
return true
}
}
if (dataContext?.actionStartedFromVim == true) return true
return false
}
final override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean { final override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return isThisHandlerEnabled(editor, caret, dataContext) || nextHandler.isEnabled(editor, caret, dataContext) return isThisHandlerEnabled(editor, caret, dataContext)
|| nextHandler?.isEnabled(editor, caret, dataContext) == true
} }
} }
/** /**
* Known conflicts & solutions: * Known conflicts & solutions:
* - Smart step into - set handler after * - Smart step into - set handler after
* - Python notebooks - set handler before - test needed! * - Python notebooks - set handler after
* - Ace jump - set handler after * - Ace jump - set handler after
* - Lookup - doesn't intersect with enter anymore * - Lookup - doesn't intersect with enter anymore
* - App code - set handler after * - App code - set handler after
* - Template - doesn't intersect with enter anymore * - Template - doesn't intersect with enter anymore
* - rd.client.editor.enter - set handler before. Otherwise, rider will add new line on enter even in normal mode
*
* This rule is disabled due to VIM-3124
* - before terminalEnter - not necessary, but terminalEnter causes "file is read-only" tooltip for readonly files VIM-3122
* - `first` is set to satisfy sorting condition "before terminalEnter".
*
*
* DO NOT add handlers that force to add "first" ordering. This doesn't work with jupyterCommandModeEnterKeyHandler (see VIM-3124)
*/ */
internal class VimEnterHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) { internal class VimEnterHandler(nextHandler: EditorActionHandler?) : VimKeyHandler(nextHandler) {
override val key: String = "<CR>" override val key: String = "<CR>"
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
if (!super.isHandlerEnabled(editor, dataContext)) return false
// This is important for one-line editors, to turn off enter.
// Some one-line editors rely on the fact that there are no enter actions registered. For example, hash search in git
// See VIM-2974 for example where it was broken
return !editor.isOneLineMode
}
} }
/** /**
* Known conflicts & solutions: * Known conflicts & solutions:
* *
* - Smart step into - set handler after * - Smart step into - set handler after
* - Python notebooks - set handler before - test needed * - Python notebooks - set handler before - yes, we have `<CR>` as "after" and `<esc>` as before. I'm not completely sure
* why this combination is correct, but other versions don't work.
* - Ace jump - set handler after * - Ace jump - set handler after
* - Lookup - It disappears after putting our esc before templateEscape. But I'm not sure why it works like that * - Lookup - It disappears after putting our esc before templateEscape. But I'm not sure why it works like that
* - App code - Need to review * - App code - Need to review
* - Template - Need to review * - Template - Need to review
* - before backend.escape - to handle our handlers before Rider processing. Also, without this rule, we get problems like VIM-3146
*/ */
internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) { internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
override val key: String = "<Esc>" override val key: String = "<Esc>"
/**
* Also, we need to pass esc to IDE if we're in normal mode and there is nothing to cancel
* (e.g. we still can cancel numbers, or cancel the replace character mode)
*/
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean { override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
return editor.mode != CommandState.Mode.COMMAND || val ideaVimSupportDialog =
editor.vimStateMachine?.commandBuilder?.count != 0 || injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
editor.vimStateMachine?.isReplaceCharacter == true
return editor.isPrimaryEditor() ||
EditorHelper.isFileEditor(editor) && !editor.vim.mode.inNormalMode ||
ideaVimSupportDialog && !editor.vim.mode.inNormalMode
} }
} }
internal abstract class VimKeyHandler(nextHandler: EditorActionHandler) : OctopusHandler(nextHandler) { /**
* Rider uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
* designer to get all the esc presses, and if there is a completion close it and do not pass the execution further.
* This doesn't work the same as in IJ.
* In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
* behaviour causes a lot of complaining from users. Since the rider handler gets execution control, we don't
* receive an event and don't exit the insert mode.
* To fix it, this special handler exists only for rider and stands before the rider's handler. We don't execute the
* handler from rider because the autocompletion is closed automatically anyway.
*/
internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
override val key: String = "<Esc>"
override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
return LookupManager.getActiveLookup(editor) != null
}
}
/**
* Empty logger for esc presses
*
* As we made a migration to the new way of handling esc keys (VIM-2974), we may face several issues around that
* One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
* the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
* This handler, that should stand in front of handlers change, just logs the event of pressing the key
* and passes the execution.
*/
internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
LOG.info("Esc pressed")
nextHandler.execute(editor, caret, dataContext)
}
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return nextHandler.isEnabled(editor, caret, dataContext)
}
companion object {
val LOG = logger<VimEscLoggerHandler>()
}
}
/**
* Workaround to support shift-enter in normal mode.
* IJ executes enter handler on shift-enter. This causes an issue that IdeaVim thinks that this is just an enter key.
* This thing should be refactored, but for now we'll use this workaround VIM-3159
*/
internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
nextHandler.execute(editor, caret, dataContext)
}
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return nextHandler.isEnabled(editor, caret, dataContext)
}
object Util {
val key = Key.create<Boolean>("vim.is.shift.enter")
}
companion object {
val LOG = logger<VimEscLoggerHandler>()
}
}
/**
* Empty logger for enter presses
*
* As we made a migration to the new way of handling enter keys (VIM-2974), we may face several issues around that
* One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
* the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
* This handler, that should stand in front of handlers change, just logs the event of pressing the key
* and passes the execution.
*/
internal class VimEnterLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
LOG.info("Enter pressed")
nextHandler.execute(editor, caret, dataContext)
}
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return nextHandler.isEnabled(editor, caret, dataContext)
}
companion object {
val LOG = logger<VimEnterLoggerHandler>()
}
}
internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : OctopusHandler(nextHandler) {
abstract val key: String abstract val key: String
@ -111,27 +308,12 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler) : Octopu
} }
internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean { internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
if (!enableOctopus) return false // CMD line has a different processing mechanizm: the processing actions are registered
when { // for the input field component. These keys are not dispatched via the octopus handler.
s.keyCode == KeyEvent.VK_ENTER -> return editor.mode in listOf( if (editor.vim.mode is Mode.CMD_LINE) return false
CommandState.Mode.COMMAND, when (s.keyCode) {
CommandState.Mode.INSERT, KeyEvent.VK_ENTER -> return true
CommandState.Mode.VISUAL, KeyEvent.VK_ESCAPE -> return true
)
s.keyCode == KeyEvent.VK_ESCAPE -> return editor.mode in listOf(
CommandState.Mode.COMMAND,
CommandState.Mode.INSERT,
CommandState.Mode.VISUAL,
)
} }
return false return false
} }
/**
* Experiment: At the moment, IdeaVim intersects all shortcuts and sends the to [KeyHandler]
* However, this doesn't seem to be a good solution as other handlers are overridden by vim.
* If this option is enabled, vim will connect to IDE via EditorActionHandler extension point
* what seems to be a way better solution as this is a correct way to override editor actions like enter, right, etc.
*/
internal val enableOctopus: Boolean
get() = injector.globalIjOptions().octopushandler

View File

@ -8,11 +8,13 @@
package com.maddyhome.idea.vim.helper package com.maddyhome.idea.vim.helper
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretVisualAttributes import com.intellij.openapi.editor.CaretVisualAttributes
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
@ -79,6 +81,7 @@ private fun Editor.guicursorMode(): GuiCursorMode {
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
private fun Editor.updatePrimaryCaretVisualAttributes() { private fun Editor.updatePrimaryCaretVisualAttributes() {
if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this) caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
@ -86,6 +89,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
} }
private fun Editor.updateSecondaryCaretsVisualAttributes() { private fun Editor.updateSecondaryCaretsVisualAttributes() {
if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them // IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this) val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
this.caretModel.allCarets.forEach { this.caretModel.allCarets.forEach {

View File

@ -335,7 +335,7 @@ public class EditorHelper {
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight); final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
@NotNull final VimEditor editor1 = new IjVimEditor(editor); @NotNull final 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); 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. // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.

View File

@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
import com.intellij.codeWithMe.ClientId import com.intellij.codeWithMe.ClientId
import com.intellij.openapi.editor.Caret import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.CaretState
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.ex.util.EditorUtil import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.group.IjOptionConstants import com.maddyhome.idea.vim.group.IjOptionConstants
import com.maddyhome.idea.vim.newapi.globalIjOptions 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 java.awt.Component
import javax.swing.JComponent import javax.swing.JComponent
import javax.swing.JTable import javax.swing.JTable
@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
*/ */
internal val Editor.vimLine: Int 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

@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.normalizeVisualColumn import com.maddyhome.idea.vim.api.normalizeVisualColumn
import com.maddyhome.idea.vim.api.options import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.command.CommandFlags import com.maddyhome.idea.vim.command.CommandFlags
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import kotlin.math.max import kotlin.math.max
import kotlin.math.min import kotlin.math.min
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -56,7 +56,7 @@ internal object ScrollViewHelper {
// that this needs to be replaced as a more or less dumb line for line rewrite. // that this needs to be replaced as a more or less dumb line for line rewrite.
val topLine = getVisualLineAtTopOfScreen(editor) val topLine = getVisualLineAtTopOfScreen(editor)
val bottomLine = getVisualLineAtBottomOfScreen(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 // 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 val scrollOffset = injector.options(vimEditor).scrolloff

View File

@ -11,18 +11,18 @@ package com.maddyhome.idea.vim.helper
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.impl.UndoManagerImpl
import com.intellij.openapi.command.undo.UndoManager import com.intellij.openapi.command.undo.UndoManager
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.common.ChangesListener
import com.maddyhome.idea.vim.group.IjOptions
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
import com.maddyhome.idea.vim.newapi.globalIjOptions import com.maddyhome.idea.vim.newapi.globalIjOptions
import com.maddyhome.idea.vim.newapi.ij 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.UndoRedoBase import com.maddyhome.idea.vim.undo.UndoRedoBase
/** /**
@ -30,15 +30,6 @@ import com.maddyhome.idea.vim.undo.UndoRedoBase
*/ */
@Service @Service
internal class UndoRedoHelper : UndoRedoBase() { internal class UndoRedoHelper : UndoRedoBase() {
init {
fun onOldUndoChanged() {
UndoManagerImpl.ourNeverAskUser = !injector.globalIjOptions().oldundo
}
injector.optionGroup.addGlobalOptionChangeListener(IjOptions.oldundo, ::onOldUndoChanged)
onOldUndoChanged()
}
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean { override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
val ijContext = context.context as DataContext val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
@ -50,9 +41,15 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (injector.globalIjOptions().oldundo) { if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) } SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
restoreVisualMode(editor)
} else { } else {
performUntilFileChanges(editor, { undoManager.isUndoAvailable(fileEditor) }, { undoManager.undo(fileEditor) }) // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
undoManager.undo(fileEditor)
if (hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor) // execute one more time if the previous undo just restored selection
}
// remove selection
editor.carets().forEach { editor.carets().forEach {
val ijCaret = it.ij val ijCaret = it.ij
val hasSelection = ijCaret.hasSelection() val hasSelection = ijCaret.hasSelection()
@ -73,6 +70,10 @@ internal class UndoRedoHelper : UndoRedoBase() {
return false return false
} }
private fun hasSelection(editor: VimEditor): Boolean {
return editor.primaryCaret().ij.hasSelection()
}
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean { override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
val ijContext = context.context as DataContext val ijContext = context.context as DataContext
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
@ -81,8 +82,9 @@ internal class UndoRedoHelper : UndoRedoBase() {
if (undoManager.isRedoAvailable(fileEditor)) { if (undoManager.isRedoAvailable(fileEditor)) {
if (injector.globalIjOptions().oldundo) { if (injector.globalIjOptions().oldundo) {
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) } SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
restoreVisualMode(editor)
} else { } else {
performUntilFileChanges(editor, { undoManager.isRedoAvailable(fileEditor) }, { undoManager.redo(fileEditor) }) undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction { CommandProcessor.getInstance().runUndoTransparentAction {
editor.carets().forEach { it.ij.removeSelection() } editor.carets().forEach { it.ij.removeSelection() }
} }
@ -92,27 +94,20 @@ internal class UndoRedoHelper : UndoRedoBase() {
return false return false
} }
private fun performUntilFileChanges(editor: VimEditor?, check: () -> Boolean, action: Runnable) { private fun restoreVisualMode(editor: VimEditor) {
if (editor == null) return if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val vimDocument = editor.document val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
val changeListener = object : ChangesListener { // Visual block selection is restored into multiple carets, so multi-carets that form a block are always
var hasChanged = false // 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
override fun documentChanged(change: ChangesListener.Change) { VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
hasChanged = true
} }
} }
val oldPath = editor.getPath()
vimDocument.addChangeListener(changeListener)
while (check() && !changeListener.hasChanged && !ifFilePathChanged(editor, oldPath)) {
action.run()
}
vimDocument.removeChangeListener(changeListener)
}
private fun ifFilePathChanged(editor: VimEditor, oldPath: String?): Boolean {
return editor.getPath() != oldPath
}
} }

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.isEnabled() || "dev" in VimPlugin.getVersion()
companion object {
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
val instance: VimStandalonePluginUpdateChecker = service()
}
}

View File

@ -47,7 +47,7 @@ import com.intellij.openapi.util.Key
import com.intellij.openapi.util.removeUserData import com.intellij.openapi.util.removeUserData
import com.intellij.openapi.vfs.VirtualFile import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ExceptionUtil import com.intellij.util.ExceptionUtil
import com.jetbrains.rd.util.lifetime.intersect import com.jetbrains.rd.util.lifetime.Lifetime
import com.maddyhome.idea.vim.EventFacade import com.maddyhome.idea.vim.EventFacade
import com.maddyhome.idea.vim.KeyHandler import com.maddyhome.idea.vim.KeyHandler
import com.maddyhome.idea.vim.VimKeyListener import com.maddyhome.idea.vim.VimKeyListener
@ -72,7 +72,6 @@ import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
import com.maddyhome.idea.vim.helper.GuicursorChangeListener import com.maddyhome.idea.vim.helper.GuicursorChangeListener
import com.maddyhome.idea.vim.helper.StrictMode 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.exitSelectMode
import com.maddyhome.idea.vim.helper.exitVisualMode import com.maddyhome.idea.vim.helper.exitVisualMode
import com.maddyhome.idea.vim.helper.forceBarCursor import com.maddyhome.idea.vim.helper.forceBarCursor
@ -89,6 +88,8 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.inSelectMode import com.maddyhome.idea.vim.state.mode.inSelectMode
import com.maddyhome.idea.vim.state.mode.mode import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType import com.maddyhome.idea.vim.state.mode.selectionType
@ -211,7 +212,8 @@ internal object VimListenerManager {
fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) { fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
val pluginLifetime = VimPlugin.getInstance().createLifetime() val pluginLifetime = VimPlugin.getInstance().createLifetime()
val editorLifetime = (editor as EditorImpl).disposable.createLifetime() val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
val disposable = editorLifetime.intersect(pluginLifetime).createNestedDisposable("MyLifetimedDisposable") val disposable =
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
editor.contentComponent.addKeyListener(VimKeyListener) editor.contentComponent.addKeyListener(VimKeyListener)
Disposer.register(disposable) { editor.contentComponent.removeKeyListener(VimKeyListener) } Disposer.register(disposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
@ -266,6 +268,16 @@ internal object VimListenerManager {
class VimFileEditorManagerListener : FileEditorManagerListener { class VimFileEditorManagerListener : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) { override fun selectionChanged(event: FileEditorManagerEvent) {
if (!VimPlugin.isEnabled()) return if (!VimPlugin.isEnabled()) return
val newEditor = event.newEditor
if (newEditor is TextEditor) {
val editor = newEditor.editor
if (editor.isInsertMode) {
VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
KeyHandler.getInstance().reset(editor.vim)
}
}
MotionGroup.fileEditorManagerSelectionChangedCallback(event) MotionGroup.fileEditorManagerSelectionChangedCallback(event)
FileGroup.fileEditorManagerSelectionChangedCallback(event) FileGroup.fileEditorManagerSelectionChangedCallback(event)
SearchGroup.fileEditorManagerSelectionChangedCallback(event) SearchGroup.fileEditorManagerSelectionChangedCallback(event)
@ -330,8 +342,6 @@ internal object VimListenerManager {
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused)) event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
} }
VimStandalonePluginUpdateChecker.instance.pluginUsed()
} }
override fun editorReleased(event: EditorFactoryEvent) { override fun editorReleased(event: EditorFactoryEvent) {

View File

@ -11,7 +11,10 @@ package com.maddyhome.idea.vim.newapi
import com.maddyhome.idea.vim.api.VimActionsInitiator import com.maddyhome.idea.vim.api.VimActionsInitiator
import com.maddyhome.idea.vim.handler.ActionBeanClass import com.maddyhome.idea.vim.handler.ActionBeanClass
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
import org.jetbrains.annotations.ApiStatus
@Deprecated(message = "Please use CommandOrMotion annotation")
@ApiStatus.ScheduledForRemoval(inVersion = "2.9.0")
internal class IjVimActionsInitiator(val bean: ActionBeanClass) : VimActionsInitiator { internal class IjVimActionsInitiator(val bean: ActionBeanClass) : VimActionsInitiator {
override fun getInstance(): EditorActionHandlerBase = bean.instance override fun getInstance(): EditorActionHandlerBase = bean.instance
} }

View File

@ -22,7 +22,6 @@ import com.maddyhome.idea.vim.api.VimCaret
import com.maddyhome.idea.vim.api.VimCaretBase import com.maddyhome.idea.vim.api.VimCaretBase
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimVisualPosition import com.maddyhome.idea.vim.api.VimVisualPosition
import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.common.EditorLine import com.maddyhome.idea.vim.common.EditorLine
import com.maddyhome.idea.vim.common.LiveRange import com.maddyhome.idea.vim.common.LiveRange
import com.maddyhome.idea.vim.common.Offset import com.maddyhome.idea.vim.common.Offset
@ -39,14 +38,17 @@ import com.maddyhome.idea.vim.helper.vimLastVisualOperatorRange
import com.maddyhome.idea.vim.helper.vimLine import com.maddyhome.idea.vim.helper.vimLine
import com.maddyhome.idea.vim.helper.vimSelectionStart import com.maddyhome.idea.vim.helper.vimSelectionStart
import com.maddyhome.idea.vim.helper.vimSelectionStartClear import com.maddyhome.idea.vim.helper.vimSelectionStartClear
import com.maddyhome.idea.vim.state.mode.SelectionType
internal class IjVimCaret(val caret: Caret) : VimCaretBase() { internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
init { init {
if (caret.isValid) {
Disposer.register(caret) { Disposer.register(caret) {
(registerStorage as CaretRegisterStorageBase).clearListener() (registerStorage as CaretRegisterStorageBase).clearListener()
} }
} }
}
override val registerStorage: CaretRegisterStorage override val registerStorage: CaretRegisterStorage
get() { get() {

View File

@ -64,6 +64,11 @@ import java.lang.System.identityHashCode
@ApiStatus.Internal @ApiStatus.Internal
internal class IjVimEditor(editor: Editor) : MutableLinearEditor() { internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
companion object {
// For cases where Editor does not have a project (for some reason)
// It's something IJ Platform related and stored here because of this reason
const val DEFAULT_PROJECT_ID = "no project"
}
// All the editor actions should be performed with top level editor!!! // All the editor actions should be performed with top level editor!!!
// Be careful: all the EditorActionHandler implementation should correctly process InjectedEditors // Be careful: all the EditorActionHandler implementation should correctly process InjectedEditors
@ -369,6 +374,8 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
return EditorHelper.getVirtualFile(editor)?.getUrl()?.let { VirtualFileManager.extractProtocol(it) } return EditorHelper.getVirtualFile(editor)?.getUrl()?.let { VirtualFileManager.extractProtocol(it) }
} }
override val projectId = editor.project?.basePath ?: DEFAULT_PROJECT_ID
override fun visualPositionToOffset(position: VimVisualPosition): Offset { override fun visualPositionToOffset(position: VimVisualPosition): Offset {
return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight)).offset return editor.visualPositionToOffset(VisualPosition(position.line, position.column, position.leansRight)).offset
} }

View File

@ -32,6 +32,10 @@ internal class IjVimLogger(private val logger: Logger) : VimLogger {
logger.error(message) logger.error(message)
} }
override fun error(message: String, e: Throwable) {
logger.error(message, e)
}
override fun info(message: String) { override fun info(message: String) {
logger.info(message) logger.info(message)
} }

View File

@ -18,7 +18,6 @@ import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Document import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider import com.intellij.openapi.editor.toolbar.floating.AbstractFloatingToolbarProvider
import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent import com.intellij.openapi.editor.toolbar.floating.FloatingToolbarComponent
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.util.Disposer import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.io.FileUtil import com.intellij.openapi.util.io.FileUtil
@ -31,10 +30,11 @@ import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.troubleshooting.Troubleshooter import com.maddyhome.idea.vim.troubleshooting.Troubleshooter
import com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup.Companion.ACTION_GROUP import com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup.Companion.ACTION_GROUP
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
import com.maddyhome.idea.vim.vimscript.services.VimRcService
import com.maddyhome.idea.vim.vimscript.services.VimRcService.VIMRC_FILE_NAME import com.maddyhome.idea.vim.vimscript.services.VimRcService.VIMRC_FILE_NAME
import com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc import com.maddyhome.idea.vim.vimscript.services.VimRcService.executeIdeaVimRc
import org.jetbrains.annotations.TestOnly import org.jetbrains.annotations.TestOnly
import java.nio.file.Path
import kotlin.io.path.readText
/** /**
* This file contains a "reload ~/.ideavimrc file" action functionality. * This file contains a "reload ~/.ideavimrc file" action functionality.
@ -67,8 +67,7 @@ internal object VimRcFileState : VimrcFileState {
} }
override fun saveFileState(filePath: String) { override fun saveFileState(filePath: String) {
val vimRcFile = VimRcService.findIdeaVimRc() val ideaVimRcText = Path.of(filePath).let {
val ideaVimRcText = vimRcFile?.let {
kotlin.runCatching { it.readText() } kotlin.runCatching { it.readText() }
.onFailure { LOG.error(it) } .onFailure { LOG.error(it) }
.getOrNull() .getOrNull()
@ -149,7 +148,6 @@ internal class ReloadVimRc : DumbAwareAction() {
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
val editor = e.getData(PlatformDataKeys.EDITOR) ?: return val editor = e.getData(PlatformDataKeys.EDITOR) ?: return
FileDocumentManager.getInstance().saveDocumentAsIs(editor.document)
injector.keyGroup.removeKeyMapping(MappingOwner.IdeaVim.InitScript) injector.keyGroup.removeKeyMapping(MappingOwner.IdeaVim.InitScript)
Troubleshooter.instance.removeByType("old-action-notation-in-mappings") Troubleshooter.instance.removeByType("old-action-notation-in-mappings")

View File

@ -21,6 +21,7 @@ import com.intellij.openapi.wm.WindowManager
import com.intellij.openapi.wm.impl.status.EditorBasedWidget import com.intellij.openapi.wm.impl.status.EditorBasedWidget
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
import com.intellij.util.Consumer import com.intellij.util.Consumer
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.globalOptions import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EngineStringHelper import com.maddyhome.idea.vim.helper.EngineStringHelper
@ -86,7 +87,10 @@ internal class ShowCmdStatusBarWidgetFactory : StatusBarWidgetFactory/*, LightEd
// Nothing // Nothing
} }
override fun isAvailable(project: Project): Boolean = injector.globalOptions().showcmd override fun isAvailable(project: Project): Boolean {
VimPlugin.getInstance()
return injector.globalOptions().showcmd
}
override fun createWidget(project: Project): StatusBarWidget = Widget(project) override fun createWidget(project: Project): StatusBarWidget = Widget(project)

View File

@ -371,7 +371,11 @@ public class ExTextField extends JTextField {
void toggleInsertReplace() { void toggleInsertReplace() {
ExDocument doc = (ExDocument)getDocument(); ExDocument doc = (ExDocument)getDocument();
doc.toggleInsertReplace(); doc.toggleInsertReplace();
// Hide/show the caret so its new shape is immediately visible
caret.setVisible(false);
resetCaret(); resetCaret();
caret.setVisible(true);
} }
private void resetCaret() { private void resetCaret() {
@ -414,20 +418,15 @@ public class ExTextField extends JTextField {
private boolean hasFocus; private boolean hasFocus;
public void setAttributes(GuiCursorAttributes attributes) { public void setAttributes(GuiCursorAttributes attributes) {
final boolean active = isActive();
// Hide the currently visible caret // Note: do not call anything that causes a layout in this method! E.g. setVisible. This method is used as a
if (isVisible()) { // callback whenever the caret moves, and causing a layout at this point can cause issues such as an infinite
setVisible(false); // loop in the layout algorithm with multi-width characters such as emoji or non-Latin characters (I don't know
} // why the layout algorithm gets stuck, but we can easily avoid it)
// See VIM-2562
mode = attributes.getType(); mode = attributes.getType();
thickness = mode == GuiCursorType.BLOCK ? 100 : attributes.getThickness(); thickness = mode == GuiCursorType.BLOCK ? 100 : attributes.getThickness();
// Make sure the caret is visible, but only if we're active, otherwise we'll kick off the flasher timer unnecessarily
if (active) {
setVisible(true);
}
} }
@Override @Override

View File

@ -11,6 +11,8 @@ package com.maddyhome.idea.vim.vimscript
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.vfs.VirtualFileManager
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -18,6 +20,7 @@ import com.maddyhome.idea.vim.api.VimScriptExecutorBase
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.ex.ExException import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.FinishException import com.maddyhome.idea.vim.ex.FinishException
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar
import com.maddyhome.idea.vim.history.HistoryConstants import com.maddyhome.idea.vim.history.HistoryConstants
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_COMMAND_REGISTER import com.maddyhome.idea.vim.register.RegisterConstants.LAST_COMMAND_REGISTER
@ -34,9 +37,12 @@ import java.io.IOException
internal class Executor : VimScriptExecutorBase() { internal class Executor : VimScriptExecutorBase() {
private val logger = logger<Executor>() private val logger = logger<Executor>()
override var executingVimscript = false override var executingVimscript = false
override var executingIdeaVimRcConfiguration = false
@Throws(ExException::class) @Throws(ExException::class)
override fun execute(script: String, editor: VimEditor, context: ExecutionContext, skipHistory: Boolean, indicateErrors: Boolean, vimContext: VimLContext?): ExecutionResult { override fun execute(script: String, editor: VimEditor, context: ExecutionContext, skipHistory: Boolean, indicateErrors: Boolean, vimContext: VimLContext?): ExecutionResult {
try {
injector.vimscriptExecutor.executingVimscript = true
var finalResult: ExecutionResult = ExecutionResult.Success var finalResult: ExecutionResult = ExecutionResult.Success
val myScript = VimscriptParser.parse(script) val myScript = VimscriptParser.parse(script)
@ -83,16 +89,41 @@ internal class Executor : VimScriptExecutorBase() {
} }
} }
return finalResult return finalResult
} finally {
injector.vimscriptExecutor.executingVimscript = false
// Initialize any extensions that were enabled during execution of this vimscript
// See the doc of this function for details
VimExtensionRegistrar.enableDelayedExtensions()
}
} }
override fun executeFile(file: File, editor: VimEditor, indicateErrors: Boolean) { override fun executeFile(file: File, editor: VimEditor, fileIsIdeaVimRcConfig: Boolean, indicateErrors: Boolean) {
val context = DataContext.EMPTY_CONTEXT.vim val context = DataContext.EMPTY_CONTEXT.vim
try { try {
if (fileIsIdeaVimRcConfig) {
injector.vimscriptExecutor.executingIdeaVimRcConfiguration = true
}
ensureFileIsSaved(file)
execute(file.readText(), editor, context, skipHistory = true, indicateErrors) execute(file.readText(), editor, context, skipHistory = true, indicateErrors)
} catch (ignored: IOException) { } catch (ignored: IOException) {
LOG.error(ignored) LOG.error(ignored)
} finally {
if (fileIsIdeaVimRcConfig) {
injector.vimrcFileState.saveFileState(file.absolutePath)
injector.vimscriptExecutor.executingIdeaVimRcConfiguration = false
} }
} }
}
private fun ensureFileIsSaved(file: File) {
val documentManager = FileDocumentManager.getInstance()
VirtualFileManager.getInstance().findFileByNioPath(file.toPath())
?.let(documentManager::getCachedDocument)
?.takeIf(documentManager::isDocumentUnsaved)
?.let(documentManager::saveDocumentAsIs)
}
@Throws(ExException::class) @Throws(ExException::class)
override fun executeLastCommand(editor: VimEditor, context: ExecutionContext): Boolean { override fun executeLastCommand(editor: VimEditor, context: ExecutionContext): Boolean {

View File

@ -8,6 +8,8 @@
package com.maddyhome.idea.vim.vimscript.model.functions.handlers package com.maddyhome.idea.vim.vimscript.model.functions.handlers
import com.intellij.openapi.util.SystemInfoRt
import com.intellij.util.system.CpuArch
import com.intellij.vim.annotations.VimscriptFunction import com.intellij.vim.annotations.VimscriptFunction
import com.maddyhome.idea.vim.api.ExecutionContext import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
@ -23,7 +25,7 @@ internal class HasFunctionHandler : FunctionHandler() {
override val minimumNumberOfArguments = 1 override val minimumNumberOfArguments = 1
override val maximumNumberOfArguments = 2 override val maximumNumberOfArguments = 2
private val supportedFeatures = setOf("ide") private val supportedFeatures = Features.discover()
override fun doFunction( override fun doFunction(
argumentValues: List<Expression>, argumentValues: List<Expression>,
@ -41,4 +43,38 @@ internal class HasFunctionHandler : FunctionHandler() {
VimInt.ZERO VimInt.ZERO
} }
} }
private object Features {
fun discover(): Set<String> {
val result = mutableSetOf("ide")
collectOperatingSystemType(result)
return result
}
private fun collectOperatingSystemType(result: MutableSet<String>) {
if (SystemInfoRt.isWindows) {
result.add("win32")
if (CpuArch.CURRENT.width == 64) {
result.add("win64")
}
} else if (SystemInfoRt.isLinux) {
result.add("linux")
} else if (SystemInfoRt.isMac) {
result.add("mac")
result.add("macunix")
result.add("osx")
result.add("osxdarwin")
} else if (SystemInfoRt.isFreeBSD) {
result.add("bsd")
} else if (SystemInfoRt.isSolaris) {
result.add("sun")
}
if (SystemInfoRt.isUnix) {
result.add("unix")
}
}
}
} }

View File

@ -11,4 +11,11 @@
<listener class="com.maddyhome.idea.vim.listener.RiderActionListener" <listener class="com.maddyhome.idea.vim.listener.RiderActionListener"
topic="com.intellij.openapi.actionSystem.ex.AnActionListener"/> topic="com.intellij.openapi.actionSystem.ex.AnActionListener"/>
</projectListeners> </projectListeners>
<extensions defaultExtensionNs="com.intellij">
<editorActionHandler action="EditorEscape"
implementationClass="com.maddyhome.idea.vim.handler.VimEscForRiderHandler"
id="ideavim-rider-esc"
order="first, before idea.only.escape"/>
</extensions>
</idea-plugin> </idea-plugin>

View File

@ -226,12 +226,12 @@
<!-- Change --> <!-- Change -->
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/> <!-- <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>-->
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/> <!-- <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>-->
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/>
@ -329,8 +329,8 @@
<vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/> <vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
<vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/> <vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
<vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/> <vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="N" keys="«C-R»"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="NX" keys="U,«C-R»"/>
<vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="N"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="NX"/>
<!-- Keys --> <!-- Keys -->
<vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/> <vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>

View File

@ -1,12 +1,4 @@
<!-- <idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
~ 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">
<name>IdeaVim</name> <name>IdeaVim</name>
<id>IdeaVIM</id> <id>IdeaVIM</id>
<description><![CDATA[ <description><![CDATA[
@ -21,13 +13,13 @@
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li> <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
</ul> </ul>
]]></description> ]]></description>
<version>SNAPSHOT</version> <version>chylex</version>
<vendor>JetBrains</vendor> <vendor>JetBrains</vendor>
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version --> <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
<!-- Check for [Version Update] tag in YouTrack as well --> <!-- Check for [Version Update] tag in YouTrack as well -->
<!-- Also, please update the value in build.gradle.kts file--> <!-- Also, please update the value in build.gradle.kts file-->
<idea-version since-build="231.7515.13"/> <idea-version since-build="232"/>
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) --> <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
<depends>com.intellij.modules.platform</depends> <depends>com.intellij.modules.platform</depends>
@ -78,10 +70,12 @@
which (at least for 2020.1) has some long running activities that block other startup extensions. None of the which (at least for 2020.1) has some long running activities that block other startup extensions. None of the
core platform activities have IDs, so we can't use "before ID". We have to use "first" --> core platform activities have IDs, so we can't use "before ID". We have to use "first" -->
<postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/> <postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/>
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.EditorHandlersChainLogger"/>
<editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/> <editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/>
<actionPromoter implementation="com.maddyhome.idea.vim.key.VimActionsPromoter" order="last"/> <actionPromoter implementation="com.maddyhome.idea.vim.key.VimActionsPromoter" order="last"/>
<actionConfigurationCustomizer implementation="com.maddyhome.idea.vim.action.VimActionConfigurationCustomizer"/>
<spellchecker.bundledDictionaryProvider implementation="com.maddyhome.idea.vim.VimBundledDictionaryProvider"/> <spellchecker.bundledDictionaryProvider implementation="com.maddyhome.idea.vim.VimBundledDictionaryProvider"/>
@ -107,12 +101,25 @@
<!-- Do not care about red handlers in order. They are necessary for proper ordering, and they'll be resolved when needed --> <!-- Do not care about red handlers in order. They are necessary for proper ordering, and they'll be resolved when needed -->
<editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.VimEnterHandler" <editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.VimEnterHandler"
id="ideavim-enter" id="ideavim-enter"
order="first, before editorEnter, after smart-step-into-enter, after AceHandlerEnter, before jupyterCommandModeEnterKeyHandler, after swift.placeholder.enter"/> order="before editorEnter, before rd.client.editor.enter, after smart-step-into-enter, after AceHandlerEnter, after jupyterCommandModeEnterKeyHandler, after swift.placeholder.enter"/>
<editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.CaretShapeEnterEditorHandler"
id="ideavim-enter-shape"
order="before jupyterCommandModeEnterKeyHandler"/>
<!-- "first" is not defined for this handler as it leads to "unsatisfied ordering exception". Not sure exectly why, but it appears in tests--> <!-- "first" is not defined for this handler as it leads to "unsatisfied ordering exception". Not sure exectly why, but it appears in tests-->
<editorActionHandler action="EditorEscape" implementationClass="com.maddyhome.idea.vim.handler.VimEscHandler" <editorActionHandler action="EditorEscape" implementationClass="com.maddyhome.idea.vim.handler.VimEscHandler"
id="ideavim-esc" id="ideavim-esc"
order="after smart-step-into-escape, after AceHandlerEscape, before jupyterCommandModeEscKeyHandler, before templateEscape"/> order="after smart-step-into-escape, after AceHandlerEscape, before jupyterCommandModeEscKeyHandler, before templateEscape, before backend.escape"/>
<editorActionHandler action="EditorEscape" implementationClass="com.maddyhome.idea.vim.handler.VimEscLoggerHandler"
id="ideavim-esc-logger"
order="first"/>
<editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.VimEnterLoggerHandler"
id="ideavim-enter-logger"
order="first"/>
<editorActionHandler action="EditorStartNewLine"
implementationClass="com.maddyhome.idea.vim.handler.ShiftEnterDetector"
id="ideavim-shift-enter-detector"
order="first"/>
</extensions> </extensions>
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/> <xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>
@ -142,5 +149,6 @@
</group> </group>
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/> <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
</actions> </actions>
</idea-plugin> </idea-plugin>

View File

@ -1,11 +0,0 @@
{
"actionl[ist]": "com.maddyhome.idea.vim.vimscript.model.commands.ActionListCommand",
"b[uffer]": "com.maddyhome.idea.vim.vimscript.model.commands.BufferCommand",
"ls": "com.maddyhome.idea.vim.vimscript.model.commands.BufferListCommand",
"files": "com.maddyhome.idea.vim.vimscript.model.commands.BufferListCommand",
"buffers": "com.maddyhome.idea.vim.vimscript.model.commands.BufferListCommand",
"!": "com.maddyhome.idea.vim.vimscript.model.commands.CmdFilterCommand",
"g[lobal]": "com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand",
"v[global]": "com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand",
"h[elp]": "com.maddyhome.idea.vim.vimscript.model.commands.HelpCommand"
}

View File

@ -1,6 +0,0 @@
{
"line": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.LineFunctionHandler",
"col": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.ColFunctionHandler",
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler",
"submatch": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler"
}

View File

@ -10,11 +10,13 @@ package org.jetbrains.plugins.ideavim
import com.maddyhome.idea.vim.RegisterActions.VIM_ACTIONS_EP import com.maddyhome.idea.vim.RegisterActions.VIM_ACTIONS_EP
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.MappingMode import com.maddyhome.idea.vim.command.MappingMode
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.handler.ActionBeanClass import com.maddyhome.idea.vim.handler.ActionBeanClass
import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandNode
import com.maddyhome.idea.vim.key.CommandPartNode import com.maddyhome.idea.vim.key.CommandPartNode
import com.maddyhome.idea.vim.newapi.globalIjOptions
import org.jetbrains.plugins.ideavim.impl.OptionTest import org.jetbrains.plugins.ideavim.impl.OptionTest
import org.jetbrains.plugins.ideavim.impl.VimOption import org.jetbrains.plugins.ideavim.impl.VimOption
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -84,6 +86,7 @@ class RegisterActionsTest : VimTestCase() {
VimOption(TestOptionConstants.whichwrap, doesntAffectTest = true), VimOption(TestOptionConstants.whichwrap, doesntAffectTest = true),
) )
fun `test unregister extension`() { fun `test unregister extension`() {
if (injector.globalIjOptions().commandOrMotionAnnotation) return
val before = "I ${c}found it in a legendary land" val before = "I ${c}found it in a legendary land"
val after = "I f${c}ound it in a legendary land" val after = "I f${c}ound it in a legendary land"
var motionRightAction: ActionBeanClass? = null var motionRightAction: ActionBeanClass? = null

View File

@ -47,7 +47,6 @@ class TestIjOptionConstants {
const val ideamarks = "ideamarks" const val ideamarks = "ideamarks"
const val idearefactormode = "idearefactormode" const val idearefactormode = "idearefactormode"
const val ideavimsupport = "ideavimsupport" const val ideavimsupport = "ideavimsupport"
const val octopushandler = "octopushandler"
const val trackactionids = "trackactionids" const val trackactionids = "trackactionids"
const val unifyjumps = "unifyjumps" const val unifyjumps = "unifyjumps"
} }

View File

@ -740,7 +740,6 @@ abstract class VimTestCase {
when (keyChar) { when (keyChar) {
is CharType.CharDetected -> { is CharType.CharDetected -> {
fixture.type(keyChar.char) fixture.type(keyChar.char)
PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue()
} }
is CharType.EditorAction -> { is CharType.EditorAction -> {
@ -764,6 +763,7 @@ abstract class VimTestCase {
} }
} }
} }
PlatformTestUtil.dispatchAllInvocationEventsInIdeEventQueue()
key = inputModel.nextKeyStroke() key = inputModel.nextKeyStroke()
} }
} }

View File

@ -10,10 +10,10 @@ package org.jetbrains.plugins.ideavim.action
import com.intellij.codeInsight.folding.CodeFoldingManager import com.intellij.codeInsight.folding.CodeFoldingManager
import com.intellij.codeInsight.folding.impl.FoldingUtil import com.intellij.codeInsight.folding.impl.FoldingUtil
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.ReturnTo import com.maddyhome.idea.vim.state.mode.ReturnTo
import com.maddyhome.idea.vim.state.mode.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -881,6 +881,18 @@ foobaz
// VIM-511 |.| // VIM-511 |.|
@TestWithoutNeovim(SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(SkipNeovimReason.DIFFERENT)
@Test @Test
@VimBehaviorDiffers(originalVimAfter = """
class C {
C(int i) {
i = 3;
}
C(int i) {
i = 3;
}
}
""", description = """The bracket should be on the new line.
|This behaviour was explicitely broken as we migrate to the new handlers and I can't support it"""
)
fun testAutoCompleteCurlyBraceWithEnterWithinFunctionBody() { fun testAutoCompleteCurlyBraceWithEnterWithinFunctionBody() {
configureByJavaText( configureByJavaText(
""" """
@ -896,8 +908,7 @@ foobaz
i = 3; i = 3;
} }
C(int i) { C(int i) {
i = 3; i = 3;}
}
} }
""", """,
) )

View File

@ -10,9 +10,8 @@ package org.jetbrains.plugins.ideavim.action
import com.google.common.collect.Lists import com.google.common.collect.Lists
import com.maddyhome.idea.vim.api.VimEditor import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.handler.enableOctopus
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
@ -155,10 +154,8 @@ class MarkTest : VimTestCase() {
// Currently broken, needs investigation // Currently broken, needs investigation
// Because of some reason system mark is recreated. As we're on a different column at this moment, this breaks test // Because of some reason system mark is recreated. As we're on a different column at this moment, this breaks test
if (!enableOctopus) {
// assertEquals(2, mark.col) // assertEquals(2, mark.col)
} }
}
// |m| // |m|
@Test @Test
@ -177,10 +174,8 @@ class MarkTest : VimTestCase() {
// Currently broken, needs investigation // Currently broken, needs investigation
// Because of some reason system mark is recreated. As we're on a different column at this moment, this breaks test // Because of some reason system mark is recreated. As we're on a different column at this moment, this breaks test
if (!enableOctopus) {
// assertEquals(6, mark.col) // assertEquals(6, mark.col)
} }
}
// |m| |`| // |m| |`|
@Test @Test

View File

@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.action.change package org.jetbrains.plugins.ideavim.action.change
import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
@ -30,9 +31,11 @@ class UndoActionTest : VimTestCase() {
kotlin.test.assertFalse(editor.caretModel.primaryCaret.hasSelection()) kotlin.test.assertFalse(editor.caretModel.primaryCaret.hasSelection())
} }
// Not yet supported @Test
fun `undo after selection`() { @TestFor(issues = ["VIM-696"])
val keys = listOf("v3eld", "u") fun `test undo after selection`() {
if (!optionsIjNoEditor().oldundo) {
val keys = listOf("dwv3eld", "u")
val before = """ val before = """
Lorem Ipsum Lorem Ipsum
@ -41,10 +44,18 @@ class UndoActionTest : VimTestCase() {
Sed in orci mauris. Sed in orci mauris.
Cras id tellus in ex imperdiet egestas. Cras id tellus in ex imperdiet egestas.
""".trimIndent() """.trimIndent()
val after = before val after = """
Lorem Ipsum
${c}ipsum dolor sit amet,
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
doTest(keys, before, after, Mode.NORMAL()) doTest(keys, before, after, Mode.NORMAL())
kotlin.test.assertFalse(hasSelection()) kotlin.test.assertFalse(hasSelection())
} }
}
@Test @Test
fun `test undo with count`() { fun `test undo with count`() {
@ -69,30 +80,31 @@ class UndoActionTest : VimTestCase() {
kotlin.test.assertFalse(hasSelection()) kotlin.test.assertFalse(hasSelection())
} }
@Test // @Test
fun `test cursor movements do not require additional undo`() { // @TestFor(issues = ["VIM-308"])
if (!optionsIjNoEditor().oldundo) { // fun `test cursor movements do not require additional undo`() {
val keys = listOf("a1<Esc>ea2<Esc>ea3<Esc>", "uu") // if (!optionsIjNoEditor().oldundo) {
val before = """ // val keys = listOf("a1<Esc>ea2<Esc>ea3<Esc>", "uu")
Lorem Ipsum // val before = """
// Lorem Ipsum
${c}Lorem ipsum dolor sit amet, //
consectetur adipiscing elit // ${c}Lorem ipsum dolor sit amet,
Sed in orci mauris. // consectetur adipiscing elit
Cras id tellus in ex imperdiet egestas. // Sed in orci mauris.
""".trimIndent() // Cras id tellus in ex imperdiet egestas.
val after = """ // """.trimIndent()
Lorem Ipsum // val after = """
// Lorem Ipsum
I1 found$c it in a legendary land //
consectetur adipiscing elit // I1 found$c it in a legendary land
Sed in orci mauris. // consectetur adipiscing elit
Cras id tellus in ex imperdiet egestas. // Sed in orci mauris.
""".trimIndent() // Cras id tellus in ex imperdiet egestas.
doTest(keys, before, after, Mode.NORMAL()) // """.trimIndent()
kotlin.test.assertFalse(hasSelection()) // doTest(keys, before, after, Mode.NORMAL())
} // kotlin.test.assertFalse(hasSelection())
} // }
// }
private fun hasSelection(): Boolean { private fun hasSelection(): Boolean {
val editor = fixture.editor val editor = fixture.editor

View File

@ -8,14 +8,65 @@
package org.jetbrains.plugins.ideavim.action.change.insert package org.jetbrains.plugins.ideavim.action.change.insert
import com.intellij.ide.plugins.PluginManagerCore
import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.Caret
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.testFramework.ExtensionTestUtil
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.RepeatedTest
import org.junit.jupiter.api.RepetitionInfo
class InsertEnterActionTest : VimTestCase() { class InsertEnterActionTest : VimTestCase() {
@Test @BeforeEach
fun setUp(repetitionInfo: RepetitionInfo) {
// Set up a different combination of handlers for enter action
// There is a specific that due to IDEA-300030 the existing for "forEach" handler may affect our handlers execution.
val mainBean = EditorActionHandlerBean()
mainBean.implementationClass = "com.maddyhome.idea.vim.handler.VimEnterHandler"
mainBean.action = "EditorEnter"
mainBean.setPluginDescriptor(PluginManagerCore.getPlugin(VimPlugin.getPluginId())!!)
val singleBean = EditorActionHandlerBean()
singleBean.implementationClass = DestroyerHandlerSingle::class.java.name
singleBean.action = "EditorEnter"
singleBean.setPluginDescriptor(PluginManagerCore.getPlugin(VimPlugin.getPluginId())!!)
val forEachBean = EditorActionHandlerBean()
forEachBean.implementationClass = DestroyerHandlerForEach::class.java.name
forEachBean.action = "EditorEnter"
forEachBean.setPluginDescriptor(PluginManagerCore.getPlugin(VimPlugin.getPluginId())!!)
if (repetitionInfo.currentRepetition == 1) {
ExtensionTestUtil.maskExtensions(
ExtensionPointName("com.intellij.editorActionHandler"),
listOf(mainBean),
fixture.testRootDisposable
)
} else if (repetitionInfo.currentRepetition == 2) {
ExtensionTestUtil.maskExtensions(
ExtensionPointName("com.intellij.editorActionHandler"),
listOf(singleBean, mainBean),
fixture.testRootDisposable
)
} else if (repetitionInfo.currentRepetition == 3) {
ExtensionTestUtil.maskExtensions(
ExtensionPointName("com.intellij.editorActionHandler"),
listOf(singleBean, mainBean),
fixture.testRootDisposable
)
}
}
@RepeatedTest(3)
fun `test insert enter`() { fun `test insert enter`() {
val before = """Lorem ipsum dolor sit amet, val before = """Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit |${c}consectetur adipiscing elit
@ -31,8 +82,25 @@ class InsertEnterActionTest : VimTestCase() {
doTest(listOf("i", "<Enter>"), before, after, Mode.INSERT) doTest(listOf("i", "<Enter>"), before, after, Mode.INSERT)
} }
@RepeatedTest(3)
fun `test insert enter multicaret`() {
val before = """Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|${c}Cras id tellus in ex imperdiet egestas.
""".trimMargin()
val after = """Lorem ipsum dolor sit amet,
|
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|
|${c}Cras id tellus in ex imperdiet egestas.
""".trimMargin()
doTest(listOf("i", "<Enter>"), before, after, Mode.INSERT)
}
@TestWithoutNeovim(SkipNeovimReason.CTRL_CODES) @TestWithoutNeovim(SkipNeovimReason.CTRL_CODES)
@Test @RepeatedTest(3)
fun `test insert enter with C-M`() { fun `test insert enter with C-M`() {
val before = """Lorem ipsum dolor sit amet, val before = """Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit |${c}consectetur adipiscing elit
@ -49,7 +117,7 @@ class InsertEnterActionTest : VimTestCase() {
} }
@TestWithoutNeovim(SkipNeovimReason.OPTION) @TestWithoutNeovim(SkipNeovimReason.OPTION)
@Test @RepeatedTest(3)
fun `test insert enter scrolls view up at scrolloff`() { fun `test insert enter scrolls view up at scrolloff`() {
configureByLines(50, "Lorem ipsum dolor sit amet,") configureByLines(50, "Lorem ipsum dolor sit amet,")
enterCommand("set scrolloff=10") enterCommand("set scrolloff=10")
@ -59,3 +127,30 @@ class InsertEnterActionTest : VimTestCase() {
assertVisibleArea(6, 40) assertVisibleArea(6, 40)
} }
} }
/**
* An empty handler that works as run "for each caret"
*/
internal class DestroyerHandlerForEach(private val nextHandler: EditorActionHandler) : EditorActionHandler(true) {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
nextHandler.execute(editor, caret, dataContext)
}
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return nextHandler.isEnabled(editor, caret, dataContext)
}
}
/**
* An empty handler that works as run "single time"
*/
internal class DestroyerHandlerSingle(private val nextHandler: EditorActionHandler) : EditorActionHandler(false) {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
nextHandler.execute(editor, caret, dataContext)
}
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return nextHandler.isEnabled(editor, caret, dataContext)
}
}

View File

@ -11,9 +11,8 @@ package org.jetbrains.plugins.ideavim.ex
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.handler.enableOctopus
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import com.maddyhome.idea.vim.vimscript.model.commands.EchoCommand import com.maddyhome.idea.vim.vimscript.model.commands.EchoCommand
import com.maddyhome.idea.vim.vimscript.model.commands.LetCommand import com.maddyhome.idea.vim.vimscript.model.commands.LetCommand
@ -49,13 +48,9 @@ class CommandParserTest : VimTestCase() {
caretShape = false caretShape = false
} }
val before = "I ${c}found it in a legendary land" val before = "I ${c}found it in a legendary land"
val after = if (enableOctopus) { val after = """I :>>
"""I :>>
|${c}found it in a legendary land |${c}found it in a legendary land
""".trimMargin() """.trimMargin()
} else {
"I :>>${c}found it in a legendary land"
}
doTest(exCommand(">>"), before, after) { doTest(exCommand(">>"), before, after) {
VimPlugin.setEnabled(false) VimPlugin.setEnabled(false)
} }

View File

@ -7,20 +7,24 @@
*/ */
package org.jetbrains.plugins.ideavim.ex.implementation.commands package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.intellij.idea.TestFor
import com.intellij.openapi.actionSystem.DataContext import com.intellij.openapi.actionSystem.DataContext
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.state.mode.Mode import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
import com.maddyhome.idea.vim.history.HistoryConstants import com.maddyhome.idea.vim.history.HistoryConstants
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import com.maddyhome.idea.vim.state.mode.Mode
import org.jetbrains.plugins.ideavim.SkipNeovimReason import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.waitAndAssert import org.jetbrains.plugins.ideavim.waitAndAssert
import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import javax.swing.JTextArea import javax.swing.JTextArea
import kotlin.test.assertIs
/** /**
* @author vlan * @author vlan
@ -664,6 +668,29 @@ n ,f <Plug>Foo
) )
} }
@TestWithoutNeovim(SkipNeovimReason.ACTION_COMMAND)
@Test
fun `action execution has correct ordering`() {
configureByJavaText(
"""
-----
1<caret>2345
abcde
-----
""".trimIndent(),
)
typeText(commandToKeys("map k <Action>(EditorDown)x"))
typeText(injector.parser.parseKeys("k"))
assertState(
"""
-----
12345
a${c}cde
-----
""".trimIndent(),
)
}
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
fun `test execute mapping with a delay`() { fun `test execute mapping with a delay`() {
@ -849,7 +876,11 @@ n ,f <Plug>Foo
indicateErrors = true, indicateErrors = true,
null, null,
) )
val exception = assertThrows<Throwable> {
typeText(injector.parser.parseKeys("t")) typeText(injector.parser.parseKeys("t"))
}
assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E121: Undefined variable: s:mapping") assertPluginErrorMessageContains("E121: Undefined variable: s:mapping")
} }
@ -891,8 +922,13 @@ n ,f <Plug>Foo
----- -----
""".trimIndent() """.trimIndent()
configureByJavaText(text) configureByJavaText(text)
val exception = assertThrows<Throwable> {
typeText(commandToKeys("inoremap <expr> <cr> unknownFunction() ? '\\<C-y>' : '\\<C-g>u\\<CR>'")) typeText(commandToKeys("inoremap <expr> <cr> unknownFunction() ? '\\<C-y>' : '\\<C-g>u\\<CR>'"))
typeText(injector.parser.parseKeys("i<CR>")) typeText(injector.parser.parseKeys("i<CR>"))
}
assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
assertPluginError(true) assertPluginError(true)
assertPluginErrorMessageContains("E117: Unknown function: unknownFunction") assertPluginErrorMessageContains("E117: Unknown function: unknownFunction")
assertState(text) assertState(text)
@ -1035,4 +1071,30 @@ n ,i <Action>(Back)
injector.historyGroup.getEntries(HistoryConstants.COMMAND, 0, 0).last().entry, injector.historyGroup.getEntries(HistoryConstants.COMMAND, 0, 0).last().entry,
) )
} }
@TestFor(issues = ["VIM-3103"])
@TestWithoutNeovim(reason = SkipNeovimReason.ACTION_COMMAND)
@Test
fun `test map enter to action`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,
${c}consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
typeText(commandToKeys("map <Enter> <Action>(EditorSelectWord)"))
typeText("<Enter>")
assertState("""
Lorem Ipsum
Lorem ipsum dolor sit amet,
${s}${c}consectetur${se} adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent())
}
} }

View File

@ -164,20 +164,19 @@ class SetCommandTest : VimTestCase() {
assertCommandOutput("set all", assertCommandOutput("set all",
""" """
|--- Options --- |--- Options ---
|noargtextobj ideawrite=all scrolljump=1 notextobj-indent |noargtextobj noideatracetime scrolljump=1 notextobj-entire
| closenotebooks noignorecase scrolloff=0 timeout | closenotebooks ideawrite=all scrolloff=0 notextobj-indent
|nocommentary noincsearch selectmode= timeoutlen=1000 |nocommentary noignorecase selectmode= timeout
|nodigraph nomatchit shellcmdflag=-x notrackactionids |nodigraph noincsearch shellcmdflag=-x timeoutlen=1000
|noexchange maxmapdepth=20 shellxescape=@ undolevels=1000 |noexchange nomatchit shellxescape=@ notrackactionids
|nogdefault more shellxquote={ unifyjumps |nogdefault maxmapdepth=20 shellxquote={ undolevels=1000
|nohighlightedyank nomultiple-cursors showcmd virtualedit= |nohighlightedyank more showcmd unifyjumps
| history=50 noNERDTree showmode novisualbell | history=50 nomultiple-cursors showmode virtualedit=
|nohlsearch nrformats=hex sidescroll=0 visualdelay=100 |nohlsearch noNERDTree sidescroll=0 novisualbell
|noideaglobalmode nonumber sidescrolloff=0 whichwrap=b,s |noideaglobalmode nrformats=hex sidescrolloff=0 visualdelay=100
|noideajoin nooctopushandler nosmartcase wrapscan |noideajoin nonumber nosmartcase whichwrap=b,s
| ideamarks oldundo startofline | ideamarks norelativenumber startofline wrapscan
| ideastrictmode norelativenumber nosurround | ideastrictmode scroll=0 nosurround
|noideatracetime scroll=0 notextobj-entire
| clipboard=ideaput,autoselect,exclude:cons\|linux | clipboard=ideaput,autoselect,exclude:cons\|linux
| excommandannotation | excommandannotation
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
@ -195,7 +194,6 @@ class SetCommandTest : VimTestCase() {
| shell=/dummy/path/to/bash | shell=/dummy/path/to/bash
|novim-paragraph-motion |novim-paragraph-motion
| viminfo='100,<50,s10,h | viminfo='100,<50,s10,h
| vimscriptfunctionannotation
| |
""".trimMargin()) """.trimMargin())
} }
@ -262,8 +260,6 @@ class SetCommandTest : VimTestCase() {
|noNERDTree |noNERDTree
| nrformats=hex | nrformats=hex
|nonumber |nonumber
|nooctopushandler
| oldundo
|norelativenumber |norelativenumber
|noReplaceWithRegister |noReplaceWithRegister
| scroll=0 | scroll=0
@ -291,7 +287,6 @@ class SetCommandTest : VimTestCase() {
| unifyjumps | unifyjumps
|novim-paragraph-motion |novim-paragraph-motion
| viminfo='100,<50,s10,h | viminfo='100,<50,s10,h
| vimscriptfunctionannotation
| virtualedit= | virtualedit=
|novisualbell |novisualbell
| visualdelay=100 | visualdelay=100

View File

@ -350,20 +350,19 @@ class SetglobalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues() setOsSpecificOptionsToSafeValues()
assertCommandOutput("setglobal all", """ assertCommandOutput("setglobal all", """
|--- Global option values --- |--- Global option values ---
|noargtextobj ideawrite=all scrolljump=1 notextobj-indent |noargtextobj noideatracetime scrolljump=1 notextobj-entire
| closenotebooks noignorecase scrolloff=0 timeout | closenotebooks ideawrite=all scrolloff=0 notextobj-indent
|nocommentary noincsearch selectmode= timeoutlen=1000 |nocommentary noignorecase selectmode= timeout
|nodigraph nomatchit shellcmdflag=-x notrackactionids |nodigraph noincsearch shellcmdflag=-x timeoutlen=1000
|noexchange maxmapdepth=20 shellxescape=@ undolevels=1000 |noexchange nomatchit shellxescape=@ notrackactionids
|nogdefault more shellxquote={ unifyjumps |nogdefault maxmapdepth=20 shellxquote={ undolevels=1000
|nohighlightedyank nomultiple-cursors showcmd virtualedit= |nohighlightedyank more showcmd unifyjumps
| history=50 noNERDTree showmode novisualbell | history=50 nomultiple-cursors showmode virtualedit=
|nohlsearch nrformats=hex sidescroll=0 visualdelay=100 |nohlsearch noNERDTree sidescroll=0 novisualbell
|noideaglobalmode nonumber sidescrolloff=0 whichwrap=b,s |noideaglobalmode nrformats=hex sidescrolloff=0 visualdelay=100
|noideajoin nooctopushandler nosmartcase wrapscan |noideajoin nonumber nosmartcase whichwrap=b,s
| ideamarks oldundo startofline | ideamarks norelativenumber startofline wrapscan
| ideastrictmode norelativenumber nosurround | ideastrictmode scroll=0 nosurround
|noideatracetime scroll=0 notextobj-entire
| clipboard=ideaput,autoselect,exclude:cons\|linux | clipboard=ideaput,autoselect,exclude:cons\|linux
| excommandannotation | excommandannotation
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
@ -381,7 +380,6 @@ class SetglobalCommandTest : VimTestCase() {
| shell=/dummy/path/to/bash | shell=/dummy/path/to/bash
|novim-paragraph-motion |novim-paragraph-motion
| viminfo='100,<50,s10,h | viminfo='100,<50,s10,h
| vimscriptfunctionannotation
|""".trimMargin() |""".trimMargin()
) )
} }
@ -444,8 +442,6 @@ class SetglobalCommandTest : VimTestCase() {
|noNERDTree |noNERDTree
| nrformats=hex | nrformats=hex
|nonumber |nonumber
|nooctopushandler
| oldundo
|norelativenumber |norelativenumber
|noReplaceWithRegister |noReplaceWithRegister
| scroll=0 | scroll=0
@ -473,7 +469,6 @@ class SetglobalCommandTest : VimTestCase() {
| unifyjumps | unifyjumps
|novim-paragraph-motion |novim-paragraph-motion
| viminfo='100,<50,s10,h | viminfo='100,<50,s10,h
| vimscriptfunctionannotation
| virtualedit= | virtualedit=
|novisualbell |novisualbell
| visualdelay=100 | visualdelay=100

View File

@ -382,20 +382,19 @@ class SetlocalCommandTest : VimTestCase() {
setOsSpecificOptionsToSafeValues() setOsSpecificOptionsToSafeValues()
assertCommandOutput("setlocal all", """ assertCommandOutput("setlocal all", """
|--- Local option values --- |--- Local option values ---
|noargtextobj noideatracetime scroll=0 notextobj-entire |noargtextobj ideastrictmode scroll=0 nosurround
| closenotebooks ideawrite=all scrolljump=1 notextobj-indent | closenotebooks noideatracetime scrolljump=1 notextobj-entire
|nocommentary noignorecase scrolloff=-1 timeout |nocommentary ideawrite=all scrolloff=-1 notextobj-indent
|nodigraph noincsearch selectmode= timeoutlen=1000 |nodigraph noignorecase selectmode= timeout
|noexchange nomatchit shellcmdflag=-x notrackactionids |noexchange noincsearch shellcmdflag=-x timeoutlen=1000
|nogdefault maxmapdepth=20 shellxescape=@ unifyjumps |nogdefault nomatchit shellxescape=@ notrackactionids
|nohighlightedyank more shellxquote={ virtualedit= |nohighlightedyank maxmapdepth=20 shellxquote={ unifyjumps
| history=50 nomultiple-cursors showcmd novisualbell | history=50 more showcmd virtualedit=
|nohlsearch noNERDTree showmode visualdelay=100 |nohlsearch nomultiple-cursors showmode novisualbell
|noideaglobalmode nrformats=hex sidescroll=0 whichwrap=b,s |noideaglobalmode noNERDTree sidescroll=0 visualdelay=100
|--ideajoin nonumber sidescrolloff=-1 wrapscan |--ideajoin nrformats=hex sidescrolloff=-1 whichwrap=b,s
| ideamarks nooctopushandler nosmartcase | ideamarks nonumber nosmartcase wrapscan
| idearefactormode= oldundo startofline | idearefactormode= norelativenumber startofline
| ideastrictmode norelativenumber nosurround
| clipboard=ideaput,autoselect,exclude:cons\|linux | clipboard=ideaput,autoselect,exclude:cons\|linux
| excommandannotation | excommandannotation
| guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175 | guicursor=n-v-c:block-Cursor/lCursor,ve:ver35-Cursor,o:hor50-Cursor,i-ci:ver25-Cursor/lCursor,r-cr:hor20-Cursor/lCursor,sm:block-Cursor-blinkwait175-blinkoff150-blinkon175
@ -413,7 +412,6 @@ class SetlocalCommandTest : VimTestCase() {
| undolevels=-123456 | undolevels=-123456
|novim-paragraph-motion |novim-paragraph-motion
| viminfo='100,<50,s10,h | viminfo='100,<50,s10,h
| vimscriptfunctionannotation
|""".trimMargin() |""".trimMargin()
) )
} }
@ -482,8 +480,6 @@ class SetlocalCommandTest : VimTestCase() {
|noNERDTree |noNERDTree
| nrformats=hex | nrformats=hex
|nonumber |nonumber
|nooctopushandler
| oldundo
|norelativenumber |norelativenumber
|noReplaceWithRegister |noReplaceWithRegister
| scroll=0 | scroll=0
@ -511,7 +507,6 @@ class SetlocalCommandTest : VimTestCase() {
| unifyjumps | unifyjumps
|novim-paragraph-motion |novim-paragraph-motion
| viminfo='100,<50,s10,h | viminfo='100,<50,s10,h
| vimscriptfunctionannotation
| virtualedit= | virtualedit=
|novisualbell |novisualbell
| visualdelay=100 | visualdelay=100

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