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

Compare commits

...

76 Commits

Author SHA1 Message Date
fd1a706e4a
Set plugin version to chylex-22 2023-12-03 09:31:23 +01:00
5b2f3e1f12
Automatically add unambiguous imports after running a macro 2023-12-03 09:31:23 +01:00
ec704fc9f9
Prevent code completion popup from appearing after running a macro 2023-12-03 07:27:33 +01:00
ef8955e9d1
Fix(VIM-3179): Respect virtual space below editor (imperfectly) 2023-12-03 04:12:13 +01:00
806f6f8eaa
Fix(VIM-3178): Workaround to support "Jump to Source" action mapping 2023-12-03 04:12:12 +01:00
6dcf0f9458
Fix(VIM-3166): Workaround to fix broken filtering of visual lines 2023-12-03 04:12:12 +01:00
8eb201a941
Add support for count for visual and line motion surround 2023-12-03 04:12:12 +01:00
a288feca2a
Fix vim-surround not working with multiple cursors
Fixes multiple cursors with vim-surround commands `cs, ds, S` (but not `ys`).
2023-12-03 04:12:12 +01:00
ffe337b145
[VIM-696] Restore visual mode after undo/redo, and disable incompatible actions 2023-12-03 04:12:12 +01:00
ee178e58d0
Respect count with <Action> mappings 2023-12-03 04:12:12 +01:00
12ac424ae0
Prevent IdeaVIM from stealing key binding that confirms in-place refactoring 2023-12-03 04:09:40 +01:00
c96ca2e405
Change matchit plugin to use HTML patterns in unrecognized files 2023-12-03 04:09:40 +01:00
0265432ce5
Reset insert mode when switching active editor 2023-12-03 04:09:39 +01:00
f7e5f15ed2
Remove update checker 2023-12-03 04:09:39 +01:00
68f21ad7c9
Set custom plugin version 2023-12-03 04:09:39 +01:00
Alex Plate
9d5aa83786
Add info that ideamarks works with global marks only 2023-12-01 15:26:56 +02:00
Alex Plate
463164cb88
[VIM-3214] Add information that ideajoin is not supported everywhere 2023-12-01 14:13:30 +02:00
Alex Plate
4809742088
Do not run tests for esc iin neovim 2023-12-01 12:13:44 +02:00
Alex Plate
9cf0e285b4
Revert "TeamCity change in 'Ideavim / IdeaVim releases' project: runners of 'Publish Dev Build' build configuration were updated"
This reverts commit b35b51c203.
2023-12-01 11:51:19 +02:00
Alex Plate
a6ca6f1cf9
Revert "TeamCity change in 'Ideavim / IdeaVim releases' project: runners of 'Publish Dev Build' build configuration were updated"
This reverts commit bd7479e1c0.
2023-12-01 11:51:18 +02:00
aleksei.plate@jetbrains.com
bd7479e1c0 TeamCity change in 'Ideavim / IdeaVim releases' project: runners of 'Publish Dev Build' build configuration were updated 2023-12-01 09:50:48 +00:00
aleksei.plate@jetbrains.com
b35b51c203 TeamCity change in 'Ideavim / IdeaVim releases' project: runners of 'Publish Dev Build' build configuration were updated 2023-12-01 09:50:17 +00:00
Alex Plate
5652774888
Exclude kotlin stdlib from the distribution 2023-12-01 11:19:18 +02:00
Alex Plate
836e9a2fbc
The dev version of IdeaVim should calculate the version based on a previous non-patched release version
As the patch versions are placed not in the master branch, we should calculate the diff between releases only for releases that are located in master branch
2023-12-01 10:53:01 +02:00
dependabot[bot]
64538c255d Bump org.jetbrains.kotlin:kotlin-stdlib from 1.9.20 to 1.9.21
Bumps [org.jetbrains.kotlin:kotlin-stdlib](https://github.com/JetBrains/kotlin) from 1.9.20 to 1.9.21.
- [Release notes](https://github.com/JetBrains/kotlin/releases)
- [Changelog](https://github.com/JetBrains/kotlin/blob/master/ChangeLog.md)
- [Commits](https://github.com/JetBrains/kotlin/compare/v1.9.20...v1.9.21)

---
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-29 17:48:06 +02:00
dependabot[bot]
62a9293dcf Bump com.google.devtools.ksp:symbol-processing-api
Bumps [com.google.devtools.ksp:symbol-processing-api](https://github.com/google/ksp) from 1.9.20-1.0.14 to 1.9.21-1.0.15.
- [Release notes](https://github.com/google/ksp/releases)
- [Commits](https://github.com/google/ksp/compare/1.9.20-1.0.14...1.9.21-1.0.15)

---
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-29 17:46:33 +02:00
IdeaVim Bot
1faae92f33 Update changelog after merging PR 2023-11-25 11:16:31 +00:00
samabcde
dee808752f Fix(VIM-3176) add test for restore selection after pasting in/below selection 2023-11-25 13:14:29 +02:00
IdeaVim Bot
5590af6995 Update changelog. Action id - 6988421362 2023-11-25 10:06:22 +00:00
filipp
5afd161fba Update minimal supported version to 2023.3 2023-11-24 20:42:48 +02:00
Alex Plate
336efa1e8b
Add some tests for one time mode 2023-11-24 17:49:04 +02:00
Alex Plate
568d5ca4ff
Fix(VIM-3090): Cmd line mode saves the visual mode
Previously, cmd line mode always returned to normal mode. However, it should keep the visual or one-time modes
2023-11-24 17:42:33 +02:00
Alex Plate
a9991f2a50
Convert ProcessGroup to kotlin 2023-11-24 17:00:06 +02:00
Alex Plate
1c8096444a
Rename .java to .kt 2023-11-24 17:00:05 +02:00
IdeaVim Bot
f424de46e6 Update changelog. Action id - 6979369267 2023-11-24 10:06:47 +00:00
Filipp Vakhitov
8fcca05565 Fix(VIM-3176): Reselecting visual selection after pasting above it select wrong lines 2023-11-23 22:53:07 +02:00
Alex Plate
ed1f3cec59
Make sure the injector is initialized in VimShortcutKeyAction 2023-11-23 16:19:13 +02:00
Alex Plate
c29a409f28
Log other actions assigned to the escape and enter actions 2023-11-23 15:07:01 +02:00
Alex Plate
1a46936ad6
Suggest fix when two escape shortcuts are assigned to the editor escape action
The case is here: https://youtrack.jetbrains.com/issue/VIM-3162/Escape-stopped-working-after-updating-to-2.7.0#focus=Comments-27-8421289.0-0
2023-11-23 15:07:00 +02:00
Alex Plate
e82abfb948
Do not perform keymap check if the plugin is disabled 2023-11-23 15:07:00 +02:00
Alex Plate
c3409be780
Fix(VIM-3206): Disable both copilot suggestion and insert mode on a single escape 2023-11-23 15:07:00 +02:00
Alex Plate
1557ab3474
Use single alarm to schedule verifications of the keymap 2023-11-23 15:07:00 +02:00
IdeaVim Bot
75fdda4fbf Update changelog. Action id - 6968202628 2023-11-23 10:06:29 +00:00
Alex Plate
4d75ef2849
Fix(VIM-3204): Add checker that verifies the configuratin of the keymap 2023-11-23 12:01:42 +02:00
Alex Plate
a1da23d1ba
Log the name of the keymap 2023-11-23 10:14:58 +02:00
Alex Plate
c4bc751df7
Fix(VIM-3084): Double update for the status bar icon 2023-11-23 09:01:18 +02:00
Alex Plate
972d89ec6e
Refactor companion object to util object 2023-11-23 09:01:17 +02:00
Alex Plate
70f040e104
Create an outline version of icon 2023-11-23 09:01:17 +02:00
Alex Plate
d4de0b49c8
Do not run ActionsTest with neovim 2023-11-23 09:01:17 +02:00
dependabot[bot]
2a42d58361 Bump io.ktor:ktor-client-cio from 2.3.5 to 2.3.6
Bumps [io.ktor:ktor-client-cio](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-cio
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 17:20:49 +02:00
dependabot[bot]
14308956d7 Bump org.jetbrains:annotations from 24.0.1 to 24.1.0
Bumps [org.jetbrains:annotations](https://github.com/JetBrains/java-annotations) from 24.0.1 to 24.1.0.
- [Release notes](https://github.com/JetBrains/java-annotations/releases)
- [Changelog](https://github.com/JetBrains/java-annotations/blob/master/CHANGELOG.md)
- [Commits](https://github.com/JetBrains/java-annotations/compare/24.0.1...24.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 17:20:29 +02:00
dependabot[bot]
71339a66d7 Bump org.junit.jupiter:junit-jupiter-api from 5.10.0 to 5.10.1
Bumps [org.junit.jupiter:junit-jupiter-api](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-api
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-11-22 17:20:10 +02:00
Alex Plate
85f0664b56
Fix the incorrect condition in release actions 2023-11-22 15:34:35 +02:00
Alex Plate
2f86ac0dfa
Log the shortcuts that are assigned to esc and enter 2023-11-22 12:20:05 +02:00
IdeaVim Bot
79d7b7a08d Update changelog. Action id - 6955729845 2023-11-22 10:06:50 +00:00
Alex Plate
b550d1990e
Fix(VIM-3195): Fix escape in injected editor 2023-11-22 12:04:37 +02:00
Alex Plate
22062f0c77
Fix(VIM-3190): Do not use octopus handler if the enter key is used with modifiers like shift or control 2023-11-22 11:51:57 +02:00
Alex Plate
515f613a53
Add tests for other actions 2023-11-22 10:17:30 +02:00
Alex Plate
615ed6b713
Fix(VIM-3203): Split action not works in normal mode 2023-11-22 10:14:37 +02:00
Alex Plate
f6eab62c3c
Fix(VIM-3184): Revert "VIM-3184: Temporally disable new handlers for the thin client"
This reverts commit 6960a34d02.
2023-11-22 09:42:59 +02:00
Alex Plate
7d1e00ff0d
Print information about the existing version id 2023-11-21 13:30:42 +02:00
Filipp Vakhitov
692439953c Rollback to working Idea Version 2023-11-21 13:18:01 +02:00
Alex Plate
6960a34d02
VIM-3184: Temporally disable new handlers for the thin client 2023-11-21 13:09:52 +02:00
Alex Plate
b3662d4e6e
Fix(VIM-3157): For for PyCharm 2023.1 2023-11-17 16:09:38 +02:00
Alex Plate
50c9b7c352
Fix(VIM-3159): Start new line before current action works in normal mode now 2023-11-17 15:23:31 +02:00
Alex Plate
f395d3b2bf
Fix(VIM-3186): Do not multiply the enter action by the amount of carets 2023-11-17 15:10:16 +02:00
filipp
4fbf6cbc50 Update minimal supported version to 2023.3 in TeamCity 2023-11-17 14:39:38 +02:00
filipp.vakhitov
9916958d6c TeamCity change in 'Ideavim' project: VCS roots of 'Tests for IntelliJ IC-2023.2' build configuration were updated 2023-11-17 12:32:34 +00:00
filipp.vakhitov
184a069c7f TeamCity change in 'Ideavim' project: general settings of 'Tests for IntelliJ IC-2023.2' build configuration were updated 2023-11-17 12:32:20 +00:00
filipp.vakhitov
0b65346633 TeamCity change in 'Ideavim' project: VCS roots of 'Tests for IntelliJ IC-2023.1' build configuration were updated 2023-11-17 12:31:30 +00:00
filipp.vakhitov
11f23dcc9e TeamCity change in 'Ideavim' project: general settings of 'Tests for IntelliJ IC-2023.1' build configuration were updated 2023-11-17 12:29:32 +00:00
filipp
f80d1defcb Add Javadoc 2023-11-17 14:00:38 +02:00
Alex Plate
e95d6343cb
Fix(VIM-3177): Formatting of commit message works again 2023-11-17 13:52:25 +02:00
filipp
a9052a068f Fix property tests 2023-11-17 13:05:30 +02:00
filipp
b1323c0d67 Fix(VIM-1611): actions related to resolving conflicts doesn't seem to work 2023-11-17 12:43:04 +02:00
filipp
87ceb8fb58 Much better undo 2023-11-17 11:59:27 +02:00
86 changed files with 1806 additions and 612 deletions

1
.gitattributes vendored Normal file
View File

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

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,14 @@ object Constants {
const val EAP_CHANNEL = "eap" const val EAP_CHANNEL = "eap"
const val DEV_CHANNEL = "Dev" const val DEV_CHANNEL = "Dev"
const val GITHUB_TESTS = "2023.1.2" // TODO it should be 2023.3 as soon as it releases
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 RELEASE = "2023.1.2" const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
const val RELEASE = "LATEST-EAP-SNAPSHOT"
const val RELEASE_DEV = "2023.1.2" const val RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
const val RELEASE_EAP = "2023.1.2" const val RELEASE_EAP = "LATEST-EAP-SNAPSHOT"
} }

View File

@ -23,8 +23,6 @@ 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

@ -20,4 +20,6 @@ object OldTests : Project({
buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false)) buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false)) buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false)) buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
}) })

View File

@ -31,6 +31,21 @@ usual beta standards.
* [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-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-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 * [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
* [VIM-3195](https://youtrack.jetbrains.com/issue/VIM-3195) Fix escape in injected editor
* [VIM-3190](https://youtrack.jetbrains.com/issue/VIM-3190) Do not use octopus handler if the enter key is used with modifiers like shift or control
* [VIM-3203](https://youtrack.jetbrains.com/issue/VIM-3203) Split action not works in normal mode
* [VIM-3184](https://youtrack.jetbrains.com/issue/VIM-3184) Revert "VIM-3184: Temporally disable new handlers for the thin client"
* [VIM-3186](https://youtrack.jetbrains.com/issue/VIM-3186) Do not multiply the enter action by the amount of carets
* [VIM-3177](https://youtrack.jetbrains.com/issue/VIM-3177) Formatting of commit message works again
* [VIM-1611](https://youtrack.jetbrains.com/issue/VIM-1611) actions related to resolving conflicts doesn't seem to work
* [VIM-3204](https://youtrack.jetbrains.com/issue/VIM-3204) Add checker that verifies the configuratin of the keymap
* [VIM-3084](https://youtrack.jetbrains.com/issue/VIM-3084) Double update for the status bar icon
* [VIM-3176](https://youtrack.jetbrains.com/issue/VIM-3176) Reselecting visual selection after pasting above it select wrong lines
* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
### Merged PRs:
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
## 2.7.0, 2023-11-07 ## 2.7.0, 2023-11-07

View File

@ -324,7 +324,7 @@ IdeaVim tips and tricks
- Use the power of IJ and Vim: - Use the power of IJ and Vim:
- `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9). - `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
- Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim. - Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
- Sync IJ bookmarks and Vim marks: `set ideamarks` - Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands). - Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`. - Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.

View File

@ -11,6 +11,8 @@ plugins {
kotlin("plugin.serialization") version "1.8.21" kotlin("plugin.serialization") version "1.8.21"
} }
val kotlinxSerializationVersion: String by project
group = "com.intellij" group = "com.intellij"
version = "SNAPSHOT" version = "SNAPSHOT"
@ -19,6 +21,10 @@ repositories {
} }
dependencies { dependencies {
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14") compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
}
} }

View File

@ -44,7 +44,7 @@ enum class Mode(val abbrev: Char) {
OP_PENDING('O'), OP_PENDING('O'),
/** /**
* Indicates this key mapping applies to Insert mode * Indicates this key mapping applies to Insert or Replace modes
*/ */
INSERT('I'), INSERT('I'),

View File

@ -53,7 +53,7 @@ buildscript {
classpath("org.kohsuke:github-api:1.305") classpath("org.kohsuke:github-api:1.305")
classpath("io.ktor:ktor-client-core:2.3.6") classpath("io.ktor:ktor-client-core:2.3.6")
classpath("io.ktor:ktor-client-cio:2.3.5") classpath("io.ktor:ktor-client-cio:2.3.6")
classpath("io.ktor:ktor-client-auth:2.3.6") classpath("io.ktor:ktor-client-auth:2.3.6")
classpath("io.ktor:ktor-client-content-negotiation:2.3.6") classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6") classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
@ -116,7 +116,7 @@ repositories {
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.0.1") compileOnly("org.jetbrains:annotations:24.1.0")
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3") testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
@ -141,7 +141,7 @@ dependencies {
testApi("com.squareup.okhttp3:okhttp:4.12.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.1")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1") testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1") testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
} }
@ -344,8 +344,6 @@ tasks {
val pluginVersion = version val pluginVersion = version
// Don't forget to update plugin.xml // Don't forget to update plugin.xml
patchPluginXml { patchPluginXml {
sinceBuild.set("231.7515.13")
// Get the latest available change notes from the changelog file // Get the latest available change notes from the changelog file
changeNotes.set( changeNotes.set(
provider { provider {
@ -524,10 +522,12 @@ tasks.register("releaseActions") {
if (tickets.isNotEmpty()) { if (tickets.isNotEmpty()) {
println("Updating statuses for tickets: $tickets") println("Updating statuses for tickets: $tickets")
setYoutrackStatus(tickets, "Fixed") setYoutrackStatus(tickets, "Fixed")
if (getVersionIdByName(version.toString()) != null) { println("Checking if version $version exists...")
val versionId = getVersionIdByName(version.toString())
if (versionId == null) {
addReleaseToYoutrack(version.toString()) addReleaseToYoutrack(version.toString())
} else { } else {
println("Version $version is already exists in YouTrack") println("Version $version already exists in YouTrack. Version id: $versionId")
} }
setYoutrackFixVersion(tickets, version.toString()) setYoutrackFixVersion(tickets, version.toString())
} else { } else {

View File

@ -3,6 +3,11 @@ Put `set ideajoin` to your `~/.ideavimrc` to enable this functionality.
Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together. Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
:warning: This feature is language-specific. This means that the IDE should implement this feature for a particular
language in order for the IDE to work as described below. If any of the examples provided below don't match your case,
please file an issue in the project related to your IDE: https://youtrack.jetbrains.com/.
Here is a list of known requests: https://youtrack.jetbrains.com/issues?q=links:VIM-3214.
* Automatic join concatenated lines: * Automatic join concatenated lines:
``` ```

View File

@ -8,20 +8,26 @@
# 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-22
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
# Also update kotlinxSerializationVersion version
kotlinVersion=1.8.21 kotlinVersion=1.8.21
publishToken=token publishToken=token
publishChannels=eap publishChannels=eap
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
# we exclude this version from the dependency and use our own version of kotlin that is specified above
kotlinxSerializationVersion=1.5.1
slackUrl= slackUrl=
youtrackToken= youtrackToken=

View File

@ -20,10 +20,10 @@ repositories {
} }
dependencies { dependencies {
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.21")
implementation("io.ktor:ktor-client-core:2.3.6") implementation("io.ktor:ktor-client-core:2.3.6")
implementation("io.ktor:ktor-client-cio:2.3.5") implementation("io.ktor:ktor-client-cio:2.3.6")
implementation("io.ktor:ktor-client-content-negotiation:2.3.6") implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6") implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
implementation("io.ktor:ktor-client-auth:2.3.6") implementation("io.ktor:ktor-client-auth:2.3.6")

View File

@ -12,7 +12,7 @@ fun main(args: Array<String>) {
println("HI!") println("HI!")
val projectDir = args[0] val projectDir = args[0]
println("Working directory: $projectDir") println("Working directory: $projectDir")
val (lastVersion, objectId) = getVersion(projectDir, onlyStable = true) val (lastVersion, objectId) = getVersion(projectDir, ReleaseType.STABLE_NO_PATCH)
println("Last version: $lastVersion, hash: ${objectId.name}") println("Last version: $lastVersion, hash: ${objectId.name}")
val branch = withRepo(projectDir) { it.branch } val branch = withRepo(projectDir) { it.branch }

View File

@ -12,7 +12,7 @@ fun main(args: Array<String>) {
println("HI!") println("HI!")
val projectDir = args[0] val projectDir = args[0]
println("Working directory: $projectDir") println("Working directory: $projectDir")
val (lastVersion, _) = getVersion(projectDir, onlyStable = false) val (lastVersion, _) = getVersion(projectDir, ReleaseType.ANY)
val nextVersion = if (lastVersion.suffixTokens.isEmpty()) { val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
lastVersion.nextMinor().withSuffix("eap.1").value lastVersion.nextMinor().withSuffix("eap.1").value

View File

@ -14,7 +14,7 @@ fun main(args: Array<String>) {
val releaseType = args[1] val releaseType = args[1]
println("Working directory: $projectDir") println("Working directory: $projectDir")
println("Release type: $releaseType") println("Release type: $releaseType")
val (lastVersion, _) = getVersion(projectDir, onlyStable = true) val (lastVersion, _) = getVersion(projectDir, ReleaseType.ONLY_STABLE)
val nextVersion = when (releaseType) { val nextVersion = when (releaseType) {
"major" -> lastVersion.nextMajor() "major" -> lastVersion.nextMajor()

View File

@ -58,7 +58,13 @@ internal fun checkBranch(rootDir: String, releaseType: String) {
} }
} }
internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, ObjectId> { enum class ReleaseType {
ANY,
ONLY_STABLE,
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
}
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build() val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
val git = Git(repository) val git = Git(repository)
println(git.log().call().first()) println(git.log().call().first())
@ -75,10 +81,10 @@ internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, O
} }
.sortedBy { it.first } .sortedBy { it.first }
val version = if (onlyStable) { val version = when (releaseType) {
versions.last { it.first.isStable } ReleaseType.ANY -> versions.last()
} else { ReleaseType.ONLY_STABLE -> versions.last { it.first.isStable }
versions.last() ReleaseType.STABLE_NO_PATCH -> versions.last { it.first.isStable && it.first.patch == 0 }
} }
return version return version

View File

@ -11,7 +11,7 @@ package com.maddyhome.idea.vim
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.project.ProjectManagerListener import com.intellij.openapi.project.ProjectManagerListener
import com.intellij.openapi.startup.StartupActivity import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.EditorHelper import com.maddyhome.idea.vim.helper.EditorHelper
import com.maddyhome.idea.vim.helper.localEditors import com.maddyhome.idea.vim.helper.localEditors
@ -20,16 +20,11 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
/** /**
* @author Alex Plate * @author Alex Plate
*/ */
// This service should be migrated to ProjectActivity. But we should cariful because simple replacement internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
// leads to deadlock in tests. I'm not sure about the exact reasons, but "invokeAndWait" inside "initialize" function
// causes this deadlock. Good new: it's easy reproducible in tests.
// Previous migration: fc7efd5484a13b40ba9bf86a1d5429e215d973f3
// Revert: 24dd84b31cffb99eb6114524859a46d02717d33f
internal class PluginStartup : StartupActivity.DumbAware/*, LightEditCompatible*/ {
private var firstInitializationOccurred = false private var firstInitializationOccurred = false
override fun runActivity(project: Project) { override suspend fun execute(project: Project) {
if (firstInitializationOccurred) return if (firstInitializationOccurred) return
firstInitializationOccurred = true firstInitializationOccurred = true

View File

@ -232,7 +232,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
getInstance().turnOnPlugin(); getInstance().turnOnPlugin();
} }
StatusBarIconFactory.Companion.updateIcon(); StatusBarIconFactory.Util.INSTANCE.updateIcon();
} }
public static String getMessage() { public static String getMessage() {
@ -264,7 +264,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
if (enabled) { if (enabled) {
Application application = ApplicationManager.getApplication(); Application application = ApplicationManager.getApplication();
if (application.isUnitTestMode()) { if (application.isUnitTestMode()) {
application.invokeAndWait(this::turnOnPlugin); turnOnPlugin();
//application.invokeAndWait(this::turnOnPlugin);
} }
else { else {
application.invokeLater(this::turnOnPlugin); application.invokeLater(this::turnOnPlugin);

View File

@ -1,74 +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.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
@ -56,7 +60,12 @@ import javax.swing.KeyStroke
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions. * These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
*/ */
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ { internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
private val traceTime = injector.globalOptions().ideatracetime private val traceTime: Boolean
get() {
// Make sure the injector is initialized
VimPlugin.getInstance()
return injector.globalOptions().ideatracetime
}
override fun actionPerformed(e: AnActionEvent) { override fun actionPerformed(e: AnActionEvent) {
LOG.trace("Executing shortcut key action") LOG.trace("Executing shortcut key action")
@ -159,6 +168,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

View File

@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
get() { get() {
val myMode = machine.mode val myMode = machine.mode
return when (myMode) { return when (myMode) {
com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING

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()
// Jump back to start if (supportsMultipleCursors) {
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor) editor.runWithEveryCaretAndRestore {
applyOnce(editor, change, pair, count)
}
}
else {
applyOnce(editor, change, pair, count)
// Jump back to start
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

@ -83,7 +83,7 @@ public object IjOptions {
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 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", false, isTemporary = true)) 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 vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true)) public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))

View File

@ -275,8 +275,8 @@ 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_ESCAPE && key.getModifiers() == 0) &&
key.getKeyCode() != KeyEvent.VK_ENTER) { !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner)); getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
} }
} }

View File

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

View File

@ -7,6 +7,8 @@
*/ */
package com.maddyhome.idea.vim.group package com.maddyhome.idea.vim.group
import com.intellij.codeInsight.completion.CompletionPhase
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProcessCanceledException
@ -19,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.MessageHelper.message import com.maddyhome.idea.vim.helper.MessageHelper.message
import com.maddyhome.idea.vim.macro.VimMacroBase import com.maddyhome.idea.vim.macro.VimMacroBase
import com.maddyhome.idea.vim.newapi.IjVimEditor import com.maddyhome.idea.vim.newapi.IjVimEditor
import com.maddyhome.idea.vim.newapi.ij
/** /**
* Used to handle playback of macros * Used to handle playback of macros
@ -71,12 +74,18 @@ internal class MacroGroup : VimMacroBase() {
} catch (e: ProcessCanceledException) { } catch (e: ProcessCanceledException) {
return@runnable return@runnable
} }
ProgressManager.getInstance().executeNonCancelableSection { getInstance().handleKey(editor, key, context) } ProgressManager.getInstance().executeNonCancelableSection {
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
getInstance().handleKey(editor, key, context)
}
if (injector.messages.isError()) return@runnable if (injector.messages.isError()) return@runnable
} }
keyStack.resetFirst() keyStack.resetFirst()
} }
keyStack.removeFirst() keyStack.removeFirst()
if (!isInternalMacro) {
MacroAutoImport.run(editor.ij, context.ij)
}
} }
if (isInternalMacro) { if (isInternalMacro) {

View File

@ -21,8 +21,11 @@ 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.KeyboardShortcut import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.ide.CopyPasteManager import com.intellij.openapi.ide.CopyPasteManager
import com.intellij.openapi.keymap.KeymapUtil import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.keymap.impl.ui.KeymapPanel
import com.intellij.openapi.options.ShowSettingsUtil import com.intellij.openapi.options.ShowSettingsUtil
import com.intellij.openapi.project.DumbAwareAction import com.intellij.openapi.project.DumbAwareAction
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
@ -32,6 +35,7 @@ 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
import com.maddyhome.idea.vim.handler.KeyMapIssue
import com.maddyhome.idea.vim.helper.MessageHelper import com.maddyhome.idea.vim.helper.MessageHelper
import com.maddyhome.idea.vim.key.ShortcutOwner import com.maddyhome.idea.vim.key.ShortcutOwner
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
@ -180,6 +184,77 @@ internal class NotificationService(private val project: Project?) {
ActionIdNotifier.notifyActionId(id, project) ActionIdNotifier.notifyActionId(id, project)
} }
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
val keymapManager = KeymapManagerEx.getInstanceEx()
val keymap = keymapManager.activeKeymap
val message = buildString {
appendLine("Current IDE keymap (${keymap.name}) has issues:<br/>")
issues.forEach {
when (it) {
is KeyMapIssue.AddShortcut -> {
appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
}
is KeyMapIssue.RemoveShortcut -> {
appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
}
}
}
}
val notification = IDEAVIM_STICKY_GROUP.createNotification(
IDEAVIM_NOTIFICATION_TITLE,
message,
NotificationType.ERROR,
)
notification.subtitle = "IDE keymap misconfigured"
notification.addAction(object : DumbAwareAction("Fix Keymap") {
override fun actionPerformed(e: AnActionEvent) {
issues.forEach {
when (it) {
is KeyMapIssue.AddShortcut -> {
keymap.addShortcut(it.actionId, KeyboardShortcut(it.keyStroke, null))
}
is KeyMapIssue.RemoveShortcut -> {
keymap.removeShortcut(it.actionId, it.shortcut)
}
}
}
LOG.info("Shortcuts updated $issues")
notification.expire()
requiredShortcutsAssigned()
}
})
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
override fun actionPerformed(e: AnActionEvent) {
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
notification.hideBalloon()
}
})
notification.addAction(object : DumbAwareAction("Ignore") {
override fun actionPerformed(e: AnActionEvent) {
LOG.info("Ignored to update shortcuts $issues")
notification.hideBalloon()
}
})
notification.notify(project)
}
private fun requiredShortcutsAssigned() {
val notification = Notification(
IDEAVIM_NOTIFICATION_ID,
IDEAVIM_NOTIFICATION_TITLE,
"Keymap fixed",
NotificationType.INFORMATION,
)
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
override fun actionPerformed(e: AnActionEvent) {
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
notification.hideBalloon()
}
})
notification.notify(project)
}
object ActionIdNotifier { object ActionIdNotifier {
private var notification: Notification? = null private var notification: Notification? = null
private const val NO_ID = "<i>Cannot detect action id</i>" private const val NO_ID = "<i>Cannot detect action id</i>"
@ -314,6 +389,8 @@ internal class NotificationService(private val project: Project?) {
const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim" const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
const val ideajoinExamplesUrl = "https://jb.gg/f9zji9" const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
private val LOG = logger<NotificationService>()
private fun createIdeaVimRcManually(message: String, project: Project?) { private fun createIdeaVimRcManually(message: String, project: Project?) {
val notification = val notification =
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING) Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)

View File

@ -1,285 +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.group;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.CapturingProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.util.execution.ParametersListUtil;
import com.intellij.util.text.CharSequenceReader;
import com.maddyhome.idea.vim.KeyHandler;
import com.maddyhome.idea.vim.VimPlugin;
import com.maddyhome.idea.vim.api.ExecutionContext;
import com.maddyhome.idea.vim.api.VimEditor;
import com.maddyhome.idea.vim.api.VimInjectorKt;
import com.maddyhome.idea.vim.api.VimProcessGroupBase;
import com.maddyhome.idea.vim.command.Command;
import com.maddyhome.idea.vim.state.mode.Mode;
import com.maddyhome.idea.vim.state.VimStateMachine;
import com.maddyhome.idea.vim.ex.ExException;
import com.maddyhome.idea.vim.ex.InvalidCommandException;
import com.maddyhome.idea.vim.helper.UiHelper;
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
import com.maddyhome.idea.vim.newapi.IjVimEditor;
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.*;
import java.util.ArrayList;
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
public class ProcessGroup extends VimProcessGroupBase {
public String getLastCommand() {
return lastCommand;
}
@Override
public void startSearchCommand(@NotNull VimEditor editor, ExecutionContext context, int count, char leader) {
if (((IjVimEditor)editor).getEditor().isOneLineMode()) // Don't allow searching in one line editors
{
return;
}
String initText = "";
String label = String.valueOf(leader);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.activate(((IjVimEditor)editor).getEditor(), ((DataContext)context.getContext()), label, initText, count);
}
@Override
public @NotNull String endSearchCommand() {
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true);
return panel.getText();
}
public void startExCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
// Don't allow ex commands in one line editors
if (editor.isOneLineMode()) return;
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd);
injector.getMarkService().setVisualSelectionMarks(editor);
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
}
@Override
public boolean processExKey(@NotNull VimEditor editor, @NotNull KeyStroke stroke) {
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
// is open. So I'll put focus back in the editor and process the key.
ExEntryPanel panel = ExEntryPanel.getInstance();
if (panel.isActive()) {
UiHelper.requestFocus(panel.getEntry());
panel.handleKey(stroke);
return true;
}
else {
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
KeyHandler.getInstance().reset(editor);
return false;
}
}
public boolean processExEntry(final @NotNull VimEditor editor, final @NotNull ExecutionContext context) {
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true);
boolean res = true;
try {
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
logger.debug("processing command");
final String text = panel.getText();
if (!panel.getLabel().equals(":")) {
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
// We should never be invoked for anything other than an actual ex command.
throw new InvalidCommandException("Expected ':' command. Got '" + panel.getLabel() + "'", text);
}
if (logger.isDebugEnabled()) logger.debug("swing=" + SwingUtilities.isEventDispatchThread());
VimInjectorKt.getInjector().getVimscriptExecutor().execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext.INSTANCE);
}
catch (ExException e) {
VimPlugin.showMessage(e.getMessage());
VimPlugin.indicateError();
res = false;
}
catch (Exception bad) {
ProcessGroup.logger.error(bad);
VimPlugin.indicateError();
res = false;
}
return res;
}
// commands executed from map command / macro should not be added to history
private boolean skipHistory(VimEditor editor) {
return VimStateMachine.Companion.getInstance(editor).getMappingState().isExecutingMap() || injector.getMacro().isExecutingMacro();
}
public void cancelExEntry(final @NotNull VimEditor editor, boolean resetCaret) {
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
KeyHandler.getInstance().reset(editor);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.deactivate(true, resetCaret);
}
@Override
public void startFilterCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd) + "!";
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
ExEntryPanel panel = ExEntryPanel.getInstance();
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
}
private @NotNull String getRange(Editor editor, @NotNull Command cmd) {
String initText = "";
if (VimStateMachine.Companion.getInstance(new IjVimEditor(editor)).getMode() instanceof Mode.VISUAL) {
initText = "'<,'>";
}
else if (cmd.getRawCount() > 0) {
if (cmd.getCount() == 1) {
initText = ".";
}
else {
initText = ".,.+" + (cmd.getCount() - 1);
}
}
return initText;
}
public @Nullable String executeCommand(@NotNull VimEditor editor, @NotNull String command, @Nullable CharSequence input, @Nullable String currentDirectoryPath)
throws ExecutionException, ProcessCanceledException {
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
final String shell = globalOptions(injector).getShell();
final String shellcmdflag = globalOptions(injector).getShellcmdflag();
final String shellxescape = globalOptions(injector).getShellxescape();
final String shellxquote = globalOptions(injector).getShellxquote();
// For Win32. See :help 'shellxescape'
final String escapedCommand = shellxquote.equals("(")
? doEscape(command, shellxescape, "^")
: command;
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
final String quotedCommand = shellxquote.equals("(")
? "(" + escapedCommand + ")"
: (shellxquote.equals("\"(")
? "\"(" + escapedCommand + ")\""
: shellxquote + escapedCommand + shellxquote);
final ArrayList<String> commands = new ArrayList<>();
commands.add(shell);
if (!shellcmdflag.isEmpty()) {
// Note that Vim also does a simple whitespace split for multiple parameters
commands.addAll(ParametersListUtil.parse(shellcmdflag));
}
commands.add(quotedCommand);
if (logger.isDebugEnabled()) {
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand));
}
final GeneralCommandLine commandLine = new GeneralCommandLine(commands);
if (currentDirectoryPath != null) {
commandLine.setWorkDirectory(currentDirectoryPath);
}
final CapturingProcessHandler handler = new CapturingProcessHandler(commandLine);
if (input != null) {
handler.addProcessListener(new ProcessAdapter() {
@Override
public void startNotified(@NotNull ProcessEvent event) {
try {
final CharSequenceReader charSequenceReader = new CharSequenceReader(input);
final BufferedWriter outputStreamWriter = new BufferedWriter(new OutputStreamWriter(handler.getProcessInput()));
copy(charSequenceReader, outputStreamWriter);
outputStreamWriter.close();
}
catch (IOException e) {
logger.error(e);
}
}
});
}
final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getInstance().getProgressIndicator();
final ProcessOutput output = handler.runProcessWithProgressIndicator(progressIndicator);
lastCommand = command;
if (output.isCancelled()) {
// TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception
throw new ProcessCanceledException();
}
final Integer exitCode = handler.getExitCode();
if (exitCode != null && exitCode != 0) {
VimPlugin.showMessage("shell returned " + exitCode);
VimPlugin.indicateError();
}
// Get stderr; stdout and strip colors, which are not handles properly.
return (output.getStderr() + output.getStdout()).replaceAll("\u001B\\[[;\\d]*m", "");
}, "IdeaVim - !" + command, true, ((IjVimEditor) editor).getEditor().getProject());
}
private String doEscape(String original, String charsToEscape, String escapeChar) {
String result = original;
for (char c : charsToEscape.toCharArray()) {
result = result.replace("" + c, escapeChar + c);
}
return result;
}
// TODO: Java 10 has a transferTo method we could use instead
private void copy(@NotNull Reader from, @NotNull Writer to) throws IOException {
char[] buf = new char[2048];
int cnt;
while ((cnt = from.read(buf)) != -1) {
to.write(buf, 0, cnt);
}
}
private String lastCommand;
private static final Logger logger = Logger.getInstance(ProcessGroup.class.getName());
}

View File

@ -0,0 +1,281 @@
/*
* 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.group
import com.intellij.execution.ExecutionException
import com.intellij.execution.configurations.GeneralCommandLine
import com.intellij.execution.process.CapturingProcessHandler
import com.intellij.execution.process.ProcessAdapter
import com.intellij.execution.process.ProcessEvent
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressIndicatorProvider
import com.intellij.openapi.progress.ProgressManager
import com.intellij.util.execution.ParametersListUtil
import com.intellij.util.text.CharSequenceReader
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.ExecutionContext
import com.maddyhome.idea.vim.api.VimEditor
import com.maddyhome.idea.vim.api.VimProcessGroupBase
import com.maddyhome.idea.vim.api.globalOptions
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.command.Command
import com.maddyhome.idea.vim.ex.ExException
import com.maddyhome.idea.vim.ex.InvalidCommandException
import com.maddyhome.idea.vim.helper.requestFocus
import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter
import java.io.Reader
import java.io.Writer
import javax.swing.KeyStroke
import javax.swing.SwingUtilities
public class ProcessGroup : VimProcessGroupBase() {
override var lastCommand: String? = null
private set
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
// Don't allow searching in one line editors
if (editor.isOneLineMode()) return
val initText = ""
val label = leader.toString()
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, label, initText, count)
}
public override fun endSearchCommand(): String {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
return panel.text
}
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
// Don't allow ex commands in one line editors
if (editor.isOneLineMode()) return
val currentMode = editor.vimStateMachine.mode
check(currentMode is ReturnableFromCmd) {
"Cannot enable cmd mode from current mode $currentMode"
}
val initText = getRange(editor, cmd)
injector.markService.setVisualSelectionMarks(editor)
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
// is open. So I'll put focus back in the editor and process the key.
val panel = ExEntryPanel.getInstance()
if (panel.isActive) {
requestFocus(panel.entry)
panel.handleKey(stroke)
return true
} else {
getInstance(editor).mode = NORMAL()
getInstance().reset(editor)
return false
}
}
public override fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean {
val panel = ExEntryPanel.getInstance()
panel.deactivate(true)
var res = true
try {
getInstance(editor).mode = NORMAL()
logger.debug("processing command")
val text = panel.text
if (panel.label != ":") {
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
// We should never be invoked for anything other than an actual ex command.
throw InvalidCommandException("Expected ':' command. Got '" + panel.label + "'", text)
}
logger.debug {
"swing=" + SwingUtilities.isEventDispatchThread()
}
injector.vimscriptExecutor.execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext)
} catch (e: ExException) {
VimPlugin.showMessage(e.message)
VimPlugin.indicateError()
res = false
} catch (bad: Exception) {
logger.error(bad)
VimPlugin.indicateError()
res = false
}
return res
}
// commands executed from map command / macro should not be added to history
private fun skipHistory(editor: VimEditor): Boolean {
return getInstance(editor).mappingState.isExecutingMap() || injector.macro.isExecutingMacro
}
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
editor.vimStateMachine.mode = NORMAL()
getInstance().reset(editor)
val panel = ExEntryPanel.getInstance()
panel.deactivate(true, resetCaret)
}
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
val initText = getRange(editor, cmd) + "!"
val currentMode = editor.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
val panel = ExEntryPanel.getInstance()
panel.activate(editor.ij, context.ij, ":", initText, 1)
}
private fun getRange(editor: VimEditor, cmd: Command): String {
var initText = ""
if (editor.vimStateMachine.mode is VISUAL) {
initText = "'<,'>"
} else if (cmd.rawCount > 0) {
initText = if (cmd.count == 1) {
"."
} else {
".,.+" + (cmd.count - 1)
}
}
return initText
}
@Throws(ExecutionException::class, ProcessCanceledException::class)
public override fun executeCommand(
editor: VimEditor,
command: String,
input: CharSequence?,
currentDirectoryPath: String?
): String? {
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
return ProgressManager.getInstance().runProcessWithProgressSynchronously<String, ExecutionException>(
{
val shell = injector.globalOptions().shell
val shellcmdflag = injector.globalOptions().shellcmdflag
val shellxescape = injector.globalOptions().shellxescape
val shellxquote = injector.globalOptions().shellxquote
// For Win32. See :help 'shellxescape'
val escapedCommand = if (shellxquote == "(") doEscape(command, shellxescape, "^")
else command
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
val quotedCommand = if (shellxquote == "(") "($escapedCommand)"
else (if (shellxquote == "\"(") "\"($escapedCommand)\""
else shellxquote + escapedCommand + shellxquote)
val commands = ArrayList<String>()
commands.add(shell)
if (shellcmdflag.isNotEmpty()) {
// Note that Vim also does a simple whitespace split for multiple parameters
commands.addAll(ParametersListUtil.parse(shellcmdflag))
}
commands.add(quotedCommand)
if (logger.isDebugEnabled) {
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand))
}
val commandLine = GeneralCommandLine(commands)
if (currentDirectoryPath != null) {
commandLine.setWorkDirectory(currentDirectoryPath)
}
val handler = CapturingProcessHandler(commandLine)
if (input != null) {
handler.addProcessListener(object : ProcessAdapter() {
override fun startNotified(event: ProcessEvent) {
try {
val charSequenceReader = CharSequenceReader(input)
val outputStreamWriter = BufferedWriter(OutputStreamWriter(handler.processInput))
copy(charSequenceReader, outputStreamWriter)
outputStreamWriter.close()
} catch (e: IOException) {
logger.error(e)
}
}
})
}
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
val output = handler.runProcessWithProgressIndicator(progressIndicator)
lastCommand = command
if (output.isCancelled) {
// TODO: Vim will use whatever text has already been written to stdout
// For whatever reason, we're not getting any here, so just throw an exception
throw ProcessCanceledException()
}
val exitCode = handler.exitCode
if (exitCode != null && exitCode != 0) {
VimPlugin.showMessage("shell returned $exitCode")
VimPlugin.indicateError()
}
(output.stderr + output.stdout).replace("\u001B\\[[;\\d]*m".toRegex(), "")
}, "IdeaVim - !$command", true, editor.ij.project
)
}
@Suppress("SameParameterValue")
private fun doEscape(original: String, charsToEscape: String, escapeChar: String): String {
var result = original
for (c in charsToEscape.toCharArray()) {
result = result.replace("" + c, escapeChar + c)
}
return result
}
// TODO: Java 10 has a transferTo method we could use instead
@Throws(IOException::class)
private fun copy(from: Reader, to: Writer) {
val buf = CharArray(2048)
var cnt: Int
while ((from.read(buf).also { cnt = it }) != -1) {
to.write(buf, 0, cnt)
}
}
public companion object {
private val logger = logger<ProcessGroup>()
}
}

View File

@ -0,0 +1,91 @@
/*
* 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.actionSystem.KeyboardShortcut
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.intellij.util.SingleAlarm
import com.jetbrains.rd.util.ConcurrentHashMap
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key
// We use alarm with delay to avoid many actions in case many events are fired at the same time
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
internal val correctorRequester = SingleAlarm({ correctCopilotKeymap() }, 1_000)
private val LOG = logger<CopilotKeymapCorrector>()
internal class CopilotKeymapCorrector : StartupActivity {
override fun runActivity(project: Project) {
correctorRequester.request()
}
}
internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
override fun activeKeymapChanged(keymap: Keymap?) {
correctorRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String) {
correctorRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
correctorRequester.request()
}
}
private val copilotHideActionMap = ConcurrentHashMap<String, Unit>()
/**
* See VIM-3206
* The user expected to both copilot suggestion and the insert mode to be exited on a single esc.
* However, for the moment, the first esc hides copilot suggestion and the second one exits insert mode.
* To fix this, we remove the esc shortcut from the copilot action if the IdeaVim is active.
*
* This workaround is not the best solution, however, I don't see the better way with the current architecture of
* actions and EditorHandlers. Firstly, I wanted to suggest to copilot to migrate to EditorActionHandler as well,
* but this doesn't seem correct for me because in this case the user will lose an ability to change the shorcut for
* it. It seems like copilot has a similar problem as we do - we don't want to make a handler for "Editor enter action",
* but a handler for the esc key press. And, moreover, be able to communicate with other plugins about the ordering.
* Before this feature is implemented, hiding the copilot suggestion on esc looks like a good workaround.
*/
private fun correctCopilotKeymap() {
// This is needed to initialize the injector in case this verification is called to fast
VimPlugin.getInstance()
if (injector.enabler.isEnabled()) {
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
val res = keymap.getShortcuts("copilot.disposeInlays")
if (res.isEmpty()) return
val escapeShortcut = res.find { it.toString() == "[pressed ESCAPE]" } ?: return
keymap.removeShortcut("copilot.disposeInlays", escapeShortcut)
copilotHideActionMap[keymap.name] = Unit
LOG.info("Remove copilot escape shortcut from keymap ${keymap.name}")
}
else {
copilotHideActionMap.forEach { (name, _) ->
val keymap = KeymapManagerEx.getInstanceEx().getKeymap(name) ?: return@forEach
val currentShortcuts = keymap.getShortcuts("copilot.disposeInlays")
if ("[pressed ESCAPE]" !in currentShortcuts.map { it.toString() }) {
keymap.addShortcut("copilot.disposeInlays", KeyboardShortcut(key("<esc>"), null))
}
LOG.info("Restore copilot escape shortcut in keymap ${keymap.name}")
}
}
}

View File

@ -8,11 +8,14 @@
package com.maddyhome.idea.vim.handler package com.maddyhome.idea.vim.handler
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
import com.intellij.openapi.extensions.ExtensionPointName import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity import com.intellij.openapi.startup.ProjectActivity
import com.maddyhome.idea.vim.api.key
/** /**
* Logs the chain of handlers for esc and enter * Logs the chain of handlers for esc and enter
@ -26,11 +29,11 @@ import com.intellij.openapi.startup.StartupActivity
* Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing * 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. * otherwise, so let's use it as long as we can.
*/ */
internal class EditorHandlersChainLogger : StartupActivity { internal class EditorHandlersChainLogger : ProjectActivity {
@Suppress("UnresolvedPluginConfigReference") @Suppress("UnresolvedPluginConfigReference")
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler") private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
override fun runActivity(project: Project) { override suspend fun execute(project: Project) {
val escHandlers = editorHandlers.extensionList val escHandlers = editorHandlers.extensionList
.filter { it.action == "EditorEscape" } .filter { it.action == "EditorEscape" }
.joinToString("\n") { it.implementationClass } .joinToString("\n") { it.implementationClass }
@ -40,6 +43,22 @@ internal class EditorHandlersChainLogger : StartupActivity {
LOG.info("Esc handlers chain:\n$escHandlers") LOG.info("Esc handlers chain:\n$escHandlers")
LOG.info("Enter handlers chain:\n$enterHandlers") LOG.info("Enter handlers chain:\n$enterHandlers")
val keymapManager = KeymapManagerEx.getInstanceEx()
val keymap = keymapManager.activeKeymap
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE).joinToString()
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER).joinToString()
LOG.info("Active keymap (${keymap.name}) shortcuts for esc: $keymapShortcutsForEsc, Shortcuts for enter: $keymapShortcutsForEnter")
val actionsForEsc = keymap.getActionIds(key("<esc>")).joinToString("\n")
val actionsForEnter = keymap.getActionIds(key("<enter>")).joinToString("\n")
LOG.info(
"Also keymap (${keymap.name}) has " +
"the following actions assigned to esc:\n$actionsForEsc " +
"\nand following actions assigned to enter:\n$actionsForEnter"
)
} }
companion object { companion object {

View File

@ -0,0 +1,128 @@
/*
* 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.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.KeyboardShortcut
import com.intellij.openapi.actionSystem.Shortcut
import com.intellij.openapi.keymap.Keymap
import com.intellij.openapi.keymap.KeymapManagerListener
import com.intellij.openapi.keymap.ex.KeymapManagerEx
import com.intellij.openapi.project.Project
import com.intellij.openapi.startup.StartupActivity
import com.intellij.util.SingleAlarm
import com.maddyhome.idea.vim.VimPlugin
import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.api.key
import javax.swing.KeyStroke
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
internal val keymapCheckRequester = SingleAlarm({ verifyKeymap() }, 5_000)
/**
* This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
*/
internal class KeymapChecker : StartupActivity {
override fun runActivity(project: Project) {
keymapCheckRequester.request()
}
}
internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
override fun activeKeymapChanged(keymap: Keymap?) {
keymapCheckRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String) {
keymapCheckRequester.request()
}
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
keymapCheckRequester.request()
}
}
/**
* After migration to the editor action handlers, we have to make sure that the keymap has a correct configuration.
* For example, that esc key is assigned to esc editor action
*
* Usually this is not a problem because this is a standard mapping, but the problem may appear in a misconfiguration
* like it was in VIM-3204
*/
private fun verifyKeymap() {
// This is needed to initialize the injector in case this verification is called to fast
VimPlugin.getInstance()
if (!injector.enabler.isEnabled()) return
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE)
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER)
val issues = ArrayList<KeyMapIssue>()
val correctShortcutMissing = keymapShortcutsForEsc
.filterIsInstance<KeyboardShortcut>()
.none { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke == null }
// We also check if there are any shortcuts starting from esc and with a second key. This should also be removed.
// For example, VIM-3162 has a case when two escapes were assigned to editor escape action
val shortcutsStartingFromEsc = keymapShortcutsForEsc
.filterIsInstance<KeyboardShortcut>()
.filter { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke != null }
if (correctShortcutMissing) {
issues += KeyMapIssue.AddShortcut(
"esc",
"editor escape",
IdeActions.ACTION_EDITOR_ESCAPE,
key("<esc>")
)
}
shortcutsStartingFromEsc.forEach {
issues += KeyMapIssue.RemoveShortcut("editor escape", IdeActions.ACTION_EDITOR_ESCAPE, it)
}
val correctEnterShortcutMissing = keymapShortcutsForEnter
.filterIsInstance<KeyboardShortcut>()
.none { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke == null }
val shortcutsStartingFromEnter = keymapShortcutsForEnter
.filterIsInstance<KeyboardShortcut>()
.filter { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke != null }
if (correctEnterShortcutMissing) {
issues += KeyMapIssue.AddShortcut(
"enter",
"editor enter",
IdeActions.ACTION_EDITOR_ENTER,
key("<enter>")
)
}
shortcutsStartingFromEnter.forEach {
issues += KeyMapIssue.RemoveShortcut("editor enter", IdeActions.ACTION_EDITOR_ENTER, it)
}
if (issues.isNotEmpty()) {
VimPlugin.getNotifications(null).notifyKeymapIssues(issues)
}
}
internal sealed interface KeyMapIssue {
data class AddShortcut(
val key: String,
val action: String,
val actionId: String,
val keyStroke: KeyStroke,
) : KeyMapIssue
data class RemoveShortcut(
val action: String,
val actionId: String,
val shortcut: Shortcut,
): KeyMapIssue
}

View File

@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.handler
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
import com.intellij.codeInsight.lookup.LookupManager import com.intellij.codeInsight.lookup.LookupManager
import com.intellij.formatting.LineWrappingUtil
import com.intellij.ide.DataManager 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.ApplicationManager
@ -18,6 +19,7 @@ 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.actions.SplitLineAction
import com.intellij.openapi.editor.impl.CaretModelImpl import com.intellij.openapi.editor.impl.CaretModelImpl
import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.util.Key import com.intellij.openapi.util.Key
@ -95,7 +97,15 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
// the condition (see VIM-3103 for example). // 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 // 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. // done by scheduling the execution of our code later via the invokeLater function.
ApplicationManager.getApplication().invokeLater(executionHandler) //
// We run this job only once for a primary caret. In the handler itself, we'll multiply the execution by the
// number of carets. If we run this job for each caret, we may end up in the issue like VIM-3186.
// However, I think that we may do some refactoring to run this job for each caret (if needed).
//
// For the moment, the known case when the caret is null - work in injected editor - VIM-3195
if (caret == null || caret == editor.caretModel.primaryCaret) {
ApplicationManager.getApplication().invokeLater(executionHandler)
}
} else { } else {
executionHandler() executionHandler()
} }
@ -106,7 +116,11 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
private fun executeInInvokeLater(editor: Editor): Boolean { private fun executeInInvokeLater(editor: Editor): Boolean {
// Currently we have a workaround for the PY console VIM-3157 // Currently we have a workaround for the PY console VIM-3157
if (FileDocumentManager.getInstance().getFile(editor.document)?.name == "Python Console.py") return false val fileName = FileDocumentManager.getInstance().getFile(editor.document)?.name
if (
fileName == "Python Console.py" || // This is the name in 232+
fileName == "Python Console" // This is the name in 231
) return false
return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
} }
@ -131,7 +145,20 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
return true return true
} }
if (dataManager.loadFromDataContext(dataContext, ShiftEnterDetector.Util.key) == true) { // From VIM-3177
val wrapLongLineDuringFormattingInProgress = dataManager
.loadFromDataContext(dataContext, LineWrappingUtil.WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY)
if (wrapLongLineDuringFormattingInProgress == true) {
return true
}
// From VIM-3203
val splitLineInProgress = dataManager.loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY)
if (splitLineInProgress == true) {
return true
}
if (dataManager.loadFromDataContext(dataContext, StartNewLineDetectorBase.Util.key) == true) {
return true return true
} }
} }
@ -244,11 +271,17 @@ internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler)
} }
/** /**
* Workaround to support shift-enter in normal mode. * Workaround to support "Start New Line" action in normal mode.
* IJ executes enter handler on shift-enter. This causes an issue that IdeaVim thinks that this is just an enter key. * IJ executes enter handler on "Start New Line". 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 * This thing should be refactored, but for now we'll use this workaround VIM-3159
*
* The Same thing happens with "Start New Line Before Current" action.
*/ */
internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler) : EditorActionHandler() { internal class StartNewLineDetector(nextHandler: EditorActionHandler) : StartNewLineDetectorBase(nextHandler)
internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandler) :
StartNewLineDetectorBase(nextHandler)
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) { override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true) DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
nextHandler.execute(editor, caret, dataContext) nextHandler.execute(editor, caret, dataContext)
@ -259,7 +292,7 @@ internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler)
} }
object Util { object Util {
val key = Key.create<Boolean>("vim.is.shift.enter") val key = Key.create<Boolean>("vim.is.start.new.line")
} }
companion object { companion object {
@ -311,9 +344,9 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
// CMD line has a different processing mechanizm: the processing actions are registered // CMD line has a different processing mechanizm: the processing actions are registered
// for the input field component. These keys are not dispatched via the octopus handler. // for the input field component. These keys are not dispatched via the octopus handler.
if (editor.vim.mode is Mode.CMD_LINE) return false if (editor.vim.mode is Mode.CMD_LINE) return false
when (s.keyCode) { when {
KeyEvent.VK_ENTER -> return true s.keyCode == KeyEvent.VK_ENTER && s.modifiers == 0 -> return true
KeyEvent.VK_ESCAPE -> return true s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
} }
return false return false
} }

View File

@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
get() { get() {
val mode = this.vim.vimStateMachine.mode val mode = this.vim.vimStateMachine.mode
return when (mode) { return when (mode) {
Mode.CMD_LINE -> CommandState.Mode.CMD_LINE is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
Mode.INSERT -> CommandState.Mode.INSERT Mode.INSERT -> CommandState.Mode.INSERT
is Mode.NORMAL -> CommandState.Mode.COMMAND is Mode.NORMAL -> CommandState.Mode.COMMAND
is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING

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

@ -15,10 +15,12 @@ import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.AnActionResult import com.intellij.openapi.actionSystem.AnActionResult
import com.intellij.openapi.actionSystem.DataContextWrapper import com.intellij.openapi.actionSystem.DataContextWrapper
import com.intellij.openapi.actionSystem.EmptyAction
import com.intellij.openapi.actionSystem.IdeActions import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.actionSystem.PlatformDataKeys
import com.intellij.openapi.actionSystem.ex.ActionManagerEx import com.intellij.openapi.actionSystem.ex.ActionManagerEx
import com.intellij.openapi.actionSystem.ex.ActionUtil import com.intellij.openapi.actionSystem.ex.ActionUtil
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
import com.intellij.openapi.command.CommandProcessor import com.intellij.openapi.command.CommandProcessor
import com.intellij.openapi.command.UndoConfirmationPolicy import com.intellij.openapi.command.UndoConfirmationPolicy
import com.intellij.openapi.components.Service import com.intellij.openapi.components.Service
@ -39,6 +41,8 @@ import com.maddyhome.idea.vim.newapi.IjNativeAction
import com.maddyhome.idea.vim.newapi.ij import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.runFromVimKey import com.maddyhome.idea.vim.newapi.runFromVimKey
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.Component
import javax.swing.JComponent
import javax.swing.SwingUtilities import javax.swing.SwingUtilities
@Service @Service
@ -150,11 +154,44 @@ internal class IjActionExecutor : VimActionExecutor {
* @param context The context to run it in * @param context The context to run it in
*/ */
override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean { override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
val aMgr = ActionManager.getInstance() val action = getAction(name, context)
val action = aMgr.getAction(name)
return action != null && executeAction(null, IjNativeAction(action), context) return action != null && executeAction(null, IjNativeAction(action), context)
} }
private fun getAction(name: String, context: ExecutionContext): AnAction? {
val actionManager = ActionManager.getInstance()
val action = actionManager.getAction(name)
if (action !is EmptyAction) return action
// But if the action is an instance of EmptyAction, the fun begins
var component: Component? = context.ij.getData(PlatformDataKeys.CONTEXT_COMPONENT) ?: return null
while (component != null) {
if (component !is JComponent) {
component = component.parent
continue
}
val listOfActions = ActionUtil.getActions(component)
if (listOfActions.isEmpty()) {
component = component.getParent()
continue
}
fun AnAction.getId(): String? {
return actionManager.getId(this)
?: (shortcutSet as? ProxyShortcutSet)?.actionId
}
for (action in listOfActions) {
if (action.getId() == name) {
return action
}
}
component = component.getParent()
}
return null
}
override fun executeCommand( override fun executeCommand(
editor: VimEditor?, editor: VimEditor?,
runnable: Runnable, runnable: Runnable,

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

@ -14,12 +14,16 @@ import com.intellij.openapi.command.CommandProcessor
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.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
/** /**
@ -38,24 +42,20 @@ 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 {
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
undoManager.undo(fileEditor) editor.runWithChangeTracking {
if (hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) { undoManager.undo(fileEditor)
undoManager.undo(fileEditor) // execute one more time if the previous undo just restored selection
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
undoManager.undo(fileEditor)
}
} }
// remove selection CommandProcessor.getInstance().runUndoTransparentAction {
editor.carets().forEach { removeSelections(editor)
val ijCaret = it.ij
val hasSelection = ijCaret.hasSelection()
if (hasSelection) {
val selectionStart = ijCaret.selectionStart
CommandProcessor.getInstance().runUndoTransparentAction {
it.ij.removeSelection()
it.ij.moveToOffset(selectionStart)
}
}
} }
} }
@ -78,14 +78,79 @@ 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 {
undoManager.redo(fileEditor) undoManager.redo(fileEditor)
CommandProcessor.getInstance().runUndoTransparentAction { CommandProcessor.getInstance().runUndoTransparentAction {
editor.carets().forEach { it.ij.removeSelection() } editor.carets().forEach { it.ij.removeSelection() }
} }
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
editor.runWithChangeTracking {
undoManager.redo(fileEditor)
// We execute undo one more time if the previous one just restored selection
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
undoManager.redo(fileEditor)
}
}
CommandProcessor.getInstance().runUndoTransparentAction {
removeSelections(editor)
}
} }
return true return true
} }
return false return false
} }
private fun removeSelections(editor: VimEditor) {
editor.carets().forEach {
val ijCaret = it.ij
if (!ijCaret.hasSelection()) return@forEach
val selectionStart = ijCaret.selectionStart
ijCaret.removeSelection()
ijCaret.moveToOffset(selectionStart)
}
}
private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
val tracker = ChangeTracker(this)
tracker.block()
}
private class ChangeTracker(private val editor: VimEditor) {
private val initialPath = editor.getPath()
private val changeListener = object : ChangesListener {
var hasChanged = false
override fun documentChanged(change: ChangesListener.Change) {
hasChanged = true
}
}
init {
editor.document.addChangeListener(changeListener)
}
val hasChanges: Boolean
get() = changeListener.hasChanged || initialPath != editor.getPath()
}
private fun restoreVisualMode(editor: VimEditor) {
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
// identified as visual block mode, leading to false positives.
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
// visual block mode.
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
SelectionType.CHARACTER_WISE
else
detectedMode
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
}
}
} }

View File

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

@ -70,9 +70,10 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
import com.maddyhome.idea.vim.group.visual.VimVisualTimer import com.maddyhome.idea.vim.group.visual.VimVisualTimer
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd 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.handler.correctorRequester
import com.maddyhome.idea.vim.handler.keymapCheckRequester
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 +90,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
@ -128,11 +131,14 @@ internal object VimListenerManager {
fun turnOn() { fun turnOn() {
GlobalListeners.enable() GlobalListeners.enable()
EditorListeners.addAll() EditorListeners.addAll()
correctorRequester.request()
keymapCheckRequester.request()
} }
fun turnOff() { fun turnOff() {
GlobalListeners.disable() GlobalListeners.disable()
EditorListeners.removeAll() EditorListeners.removeAll()
correctorRequester.request()
} }
object GlobalListeners { object GlobalListeners {
@ -267,6 +273,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)
@ -331,8 +347,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

@ -18,6 +18,7 @@ import com.intellij.openapi.editor.CaretStateTransferableData
import com.intellij.openapi.editor.RawText import com.intellij.openapi.editor.RawText
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData import com.intellij.openapi.editor.richcopy.view.RtfTransferableData
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.IndexNotReadyException import com.intellij.openapi.project.IndexNotReadyException
import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiDocumentManager
import com.intellij.util.ui.EmptyClipboardOwner import com.intellij.util.ui.EmptyClipboardOwner
@ -100,8 +101,7 @@ internal class IjClipboardManager : VimClipboardManager {
// This thing enables alternative context resolve for dumb mode. // This thing enables alternative context resolve for dumb mode.
// Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled // Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled
// [VERSION UPDATE] 2023.2+ Enable alternative context back DumbService.getInstance(project).withAlternativeResolveEnabled {
// DumbService.getInstance(project).withAlternativeResolveEnabled {
for (processor in CopyPastePostProcessor.EP_NAME.extensionList) { for (processor in CopyPastePostProcessor.EP_NAME.extensionList) {
try { try {
logger.debug { "Copy paste processor: ${processor.javaClass.name}" } logger.debug { "Copy paste processor: ${processor.javaClass.name}" }
@ -116,7 +116,7 @@ internal class IjClipboardManager : VimClipboardManager {
} catch (ignore: IndexNotReadyException) { } catch (ignore: IndexNotReadyException) {
} }
} }
// } }
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length))) transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
// These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with // These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with

View File

@ -52,6 +52,7 @@ import java.awt.Point
import java.awt.event.MouseEvent import java.awt.event.MouseEvent
import javax.swing.Icon import javax.swing.Icon
import javax.swing.SwingConstants import javax.swing.SwingConstants
import javax.swing.Timer
@NonNls @NonNls
internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon" internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
@ -73,6 +74,14 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
override fun createWidget(project: Project): StatusBarWidget { override fun createWidget(project: Project): StatusBarWidget {
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(IjOptions.ideastatusicon) { updateAll() } VimPlugin.getOptionGroup().addGlobalOptionChangeListener(IjOptions.ideastatusicon) { updateAll() }
// Double update the status bar icon with 5-second delay
// There is an issue VIM-3084 that must probably caused by some race between status bar icon initialization
// and .ideavimrc reading. I believe this is a simple fix for it.
val timer = Timer(5_000) { updateAll() }
timer.isRepeats = false
timer.start()
return VimStatusBar() return VimStatusBar()
} }
@ -89,10 +98,10 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
statusBarWidgetsManager.updateWidget(this) statusBarWidgetsManager.updateWidget(this)
} }
updateIcon() Util.updateIcon()
} }
companion object { object Util {
fun updateIcon() { fun updateIcon() {
val projectManager = ProjectManager.getInstanceIfCreated() ?: return val projectManager = ProjectManager.getInstanceIfCreated() ?: return
for (project in projectManager.openProjects) { for (project in projectManager.openProjects) {

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

@ -12,6 +12,10 @@
topic="com.intellij.ide.ui.LafManagerListener"/> topic="com.intellij.ide.ui.LafManagerListener"/>
<listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter" <listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter"
topic="com.intellij.ide.ui.LafManagerListener"/> topic="com.intellij.ide.ui.LafManagerListener"/>
<listener class="com.maddyhome.idea.vim.handler.IdeaVimKeymapChangedListener"
topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
<listener class="com.maddyhome.idea.vim.handler.IdeaVimCorrectorKeymapChangedListener"
topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
</applicationListeners> </applicationListeners>
<projectListeners> <projectListeners>
<listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener" <listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"

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.8109.175"/> <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>
@ -79,11 +71,12 @@
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"/> <postStartupActivity implementation="com.maddyhome.idea.vim.handler.EditorHandlersChainLogger"/>
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.KeymapChecker"/>
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.CopilotKeymapCorrector"/>
<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"/>
@ -125,8 +118,12 @@
id="ideavim-enter-logger" id="ideavim-enter-logger"
order="first"/> order="first"/>
<editorActionHandler action="EditorStartNewLine" <editorActionHandler action="EditorStartNewLine"
implementationClass="com.maddyhome.idea.vim.handler.ShiftEnterDetector" implementationClass="com.maddyhome.idea.vim.handler.StartNewLineDetector"
id="ideavim-shift-enter-detector" id="ideavim-start-new-line-detector"
order="first"/>
<editorActionHandler action="EditorStartNewLineBefore"
implementationClass="com.maddyhome.idea.vim.handler.StartNewLineBeforeCurrentDetector"
id="ideavim-start-new-line-before-current-detector"
order="first"/> order="first"/>
</extensions> </extensions>
@ -157,5 +154,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

@ -0,0 +1,11 @@
<!--
- 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.
-->
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 40 40" fill-opacity="0" stroke="#6E6E6E" stroke-width="3">
<path d="M 28.019 4 L 15.988 24.119 L 15.988 4 L 4 4 L 4 36 L 17.953 36 L 36 4z"/>
</svg>

After

Width:  |  Height:  |  Size: 446 B

View File

@ -771,7 +771,11 @@ abstract class VimTestCase {
private fun KeyStroke.getChar(editor: Editor): CharType { private fun KeyStroke.getChar(editor: Editor): CharType {
if (keyChar != KeyEvent.CHAR_UNDEFINED) return CharType.CharDetected(keyChar) if (keyChar != KeyEvent.CHAR_UNDEFINED) return CharType.CharDetected(keyChar)
if (isOctopusEnabled(this, editor)) { if (isOctopusEnabled(this, editor)) {
if (keyCode in setOf(KeyEvent.VK_ENTER)) return CharType.CharDetected(keyCode.toChar()) if (keyCode in setOf(KeyEvent.VK_ENTER)) {
if (modifiers == 0) {
return CharType.CharDetected(keyCode.toChar())
}
}
if (keyCode == KeyEvent.VK_ESCAPE) return CharType.EditorAction("EditorEscape") if (keyCode == KeyEvent.VK_ESCAPE) return CharType.EditorAction("EditorEscape")
} }
return CharType.UNDEFINED return CharType.UNDEFINED

View File

@ -0,0 +1,101 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action
import com.intellij.idea.TestFor
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class ActionsTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-3203"])
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
fun `split line 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()
)
fixture.performEditorAction("EditorSplitLine")
assertState(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c
consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3159"])
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
fun `start new line before`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
fixture.performEditorAction("EditorStartNewLineBefore")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3159"])
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
fun `start new line`() {
configureByText(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
fixture.performEditorAction("EditorStartNewLine")
assertState(
"""
Lorem Ipsum
Lorem ipsum dolor sit amet, consectetur adipiscing elit
$c
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action
import com.intellij.idea.TestFor
import org.jetbrains.plugins.ideavim.SkipNeovimReason
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class EscapeTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-3190"])
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
fun `mapping to control esc`() {
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("nmap <C-Esc> k"))
typeText("<C-Esc>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
fun `mapping to alt esc`() {
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("nmap <A-Esc> k"))
typeText("<A-Esc>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
fun `mapping to shift esc`() {
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("nmap <S-Esc> k"))
typeText("<S-Esc>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
}

View File

@ -11,10 +11,8 @@ import com.maddyhome.idea.vim.api.injector
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.Disabled
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
// [VERSION UPDATE] 232+ enable tests
@Suppress("unused") @Suppress("unused")
class ReformatCodeTest : VimTestCase() { class ReformatCodeTest : VimTestCase() {
@Test @Test
@ -23,7 +21,6 @@ class ReformatCodeTest : VimTestCase() {
} }
@Test @Test
@Disabled
fun testEmpty() { fun testEmpty() {
configureByJavaText("<caret>") configureByJavaText("<caret>")
typeText(injector.parser.parseKeys("gqq")) typeText(injector.parser.parseKeys("gqq"))
@ -32,7 +29,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testWithCount() { fun testWithCount() {
configureByJavaText("class C {\n\tint a;\n\tint <caret>b;\n\tint c;\n\tint d;\n}\n") configureByJavaText("class C {\n\tint a;\n\tint <caret>b;\n\tint c;\n\tint d;\n}\n")
typeText(injector.parser.parseKeys("2gqq")) typeText(injector.parser.parseKeys("2gqq"))
@ -41,7 +37,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testWithUpMotion() { fun testWithUpMotion() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint b;\n" + "\tint <caret>c;\n" + "\tint d;\n" + "}\n") configureByJavaText("class C {\n" + "\tint a;\n" + "\tint b;\n" + "\tint <caret>c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("gqk")) typeText(injector.parser.parseKeys("gqk"))
@ -50,7 +45,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testWithRightMotion() { fun testWithRightMotion() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n") configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("gql")) typeText(injector.parser.parseKeys("gql"))
@ -59,7 +53,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testWithTextObject() { fun testWithTextObject() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n") configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("gqi{")) typeText(injector.parser.parseKeys("gqi{"))
@ -75,7 +68,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testWithCountsAndDownMotion() { fun testWithCountsAndDownMotion() {
configureByJavaText("class C {\n" + "\tint <caret>a;\n" + "\tint b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n") configureByJavaText("class C {\n" + "\tint <caret>a;\n" + "\tint b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("2gqj")) typeText(injector.parser.parseKeys("2gqj"))
@ -84,7 +76,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testVisual() { fun testVisual() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n") configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("v" + "l" + "gq")) typeText(injector.parser.parseKeys("v" + "l" + "gq"))
@ -93,7 +84,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testLinewiseVisual() { fun testLinewiseVisual() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n") configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
typeText(injector.parser.parseKeys("V" + "l" + "gq")) typeText(injector.parser.parseKeys("V" + "l" + "gq"))
@ -102,7 +92,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testVisualMultiline() { fun testVisualMultiline() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n") configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("v" + "j" + "gq")) typeText(injector.parser.parseKeys("v" + "j" + "gq"))
@ -111,7 +100,6 @@ class ReformatCodeTest : VimTestCase() {
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT) @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
@Test @Test
@Disabled
fun testVisualBlock() { fun testVisualBlock() {
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n") configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
typeText(injector.parser.parseKeys("<C-V>" + "j" + "gq")) typeText(injector.parser.parseKeys("<C-V>" + "j" + "gq"))

View File

@ -10,6 +10,7 @@
package org.jetbrains.plugins.ideavim.action.change.delete package org.jetbrains.plugins.ideavim.action.change.delete
import com.intellij.notification.ActionCenter
import com.intellij.notification.EventLog import com.intellij.notification.EventLog
import com.intellij.notification.Notification import com.intellij.notification.Notification
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
@ -20,14 +21,12 @@ import org.jetbrains.plugins.ideavim.VimTestCase
import org.jetbrains.plugins.ideavim.impl.OptionTest import org.jetbrains.plugins.ideavim.impl.OptionTest
import org.jetbrains.plugins.ideavim.impl.TraceOptions import org.jetbrains.plugins.ideavim.impl.TraceOptions
import org.jetbrains.plugins.ideavim.impl.VimOption import org.jetbrains.plugins.ideavim.impl.VimOption
import org.junit.jupiter.api.Disabled
/** /**
* @author Alex Plate * @author Alex Plate
*/ */
@TraceOptions(TestIjOptionConstants.ideajoin) @TraceOptions(TestIjOptionConstants.ideajoin)
class JoinNotificationTest : VimTestCase() { class JoinNotificationTest : VimTestCase() {
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"])) @OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
fun `test notification shown for no ideajoin`() { fun `test notification shown for no ideajoin`() {
val before = "I found${c} it\n in a legendary land" val before = "I found${c} it\n in a legendary land"
@ -45,7 +44,6 @@ class JoinNotificationTest : VimTestCase() {
} }
} }
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["true"])) @OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["true"]))
fun `test notification not shown for ideajoin`() { fun `test notification not shown for ideajoin`() {
val before = "I found${c} it\n in a legendary land" val before = "I found${c} it\n in a legendary land"
@ -58,8 +56,7 @@ class JoinNotificationTest : VimTestCase() {
} }
private fun notifications(): MutableList<Notification> { private fun notifications(): MutableList<Notification> {
TODO() return ActionCenter.getNotifications(fixture.project)
// return ActionCenter.getNotifications(fixture.project)
} }
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"])) @OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))

View File

@ -60,7 +60,7 @@ class InsertEnterActionTest : VimTestCase() {
} else if (repetitionInfo.currentRepetition == 3) { } else if (repetitionInfo.currentRepetition == 3) {
ExtensionTestUtil.maskExtensions( ExtensionTestUtil.maskExtensions(
ExtensionPointName("com.intellij.editorActionHandler"), ExtensionPointName("com.intellij.editorActionHandler"),
listOf(singleBean, mainBean), listOf(forEachBean, mainBean),
fixture.testRootDisposable fixture.testRootDisposable
) )
} }

View File

@ -12,14 +12,38 @@ 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
class InsertSingleCommandActionTest : VimTestCase() { class InsertSingleCommandActionTest : SingleCommandActionTest() {
override val command: String = "i"
override val mode: Mode = Mode.INSERT
}
class ReplaceSingleCommandActionTest : SingleCommandActionTest() {
override val command: String = "R"
override val mode: Mode = Mode.REPLACE
}
abstract class SingleCommandActionTest : VimTestCase() {
abstract val command: String
abstract val mode: Mode
@Test
fun `one operation`() {
doTest(
listOf(command, "<C-O>", "l"),
"I found ${c}it in a legendary land",
"I found i${c}t in a legendary land",
mode,
)
}
@Test @Test
fun `test enter visual`() { fun `test enter visual`() {
doTest( doTest(
listOf("i", "<C-O>", "vlll", "<Esc>"), listOf(command, "<C-O>", "vlll", "<Esc>"),
"I found ${c}it in a legendary land", "I found ${c}it in a legendary land",
"I found it ${c}in a legendary land", "I found it ${c}in a legendary land",
Mode.INSERT, mode,
) )
} }
} }

View File

@ -8,6 +8,7 @@
package org.jetbrains.plugins.ideavim.action.copy package org.jetbrains.plugins.ideavim.action.copy
import com.intellij.notification.ActionCenter
import com.intellij.notification.EventLog import com.intellij.notification.EventLog
import com.intellij.notification.Notification import com.intellij.notification.Notification
import com.maddyhome.idea.vim.VimPlugin import com.maddyhome.idea.vim.VimPlugin
@ -22,11 +23,9 @@ import org.jetbrains.plugins.ideavim.impl.OptionTest
import org.jetbrains.plugins.ideavim.impl.TraceOptions import org.jetbrains.plugins.ideavim.impl.TraceOptions
import org.jetbrains.plugins.ideavim.impl.VimOption import org.jetbrains.plugins.ideavim.impl.VimOption
import org.jetbrains.plugins.ideavim.rangeOf import org.jetbrains.plugins.ideavim.rangeOf
import org.junit.jupiter.api.Disabled
@TraceOptions(TestOptionConstants.clipboard) @TraceOptions(TestOptionConstants.clipboard)
class IdeaPutNotificationsTest : VimTestCase() { class IdeaPutNotificationsTest : VimTestCase() {
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""])) @OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))
fun `test notification exists if no ideaput`() { fun `test notification exists if no ideaput`() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
@ -47,7 +46,6 @@ class IdeaPutNotificationsTest : VimTestCase() {
} }
} }
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [OptionConstants.clipboard_ideaput])) @OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [OptionConstants.clipboard_ideaput]))
fun `test no notification on ideaput`() { fun `test no notification on ideaput`() {
val before = "${c}I found it in a legendary land" val before = "${c}I found it in a legendary land"
@ -63,8 +61,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
} }
private fun notifications(): MutableList<Notification> { private fun notifications(): MutableList<Notification> {
TODO() return ActionCenter.getNotifications(fixture.project)
// return ActionCenter.getNotifications(fixture.project)
} }
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""])) @OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))

View File

@ -0,0 +1,122 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action.motion.search
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Test
class SearchEntryFwdActionTest : VimTestCase() {
@Test
fun `search in visual mode`() {
doTest(
"v/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|${s}consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}i${se}d tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.VISUAL(SelectionType.CHARACTER_WISE),
)
}
@Test
fun `search in one time visual mode`() {
doTest(
"i<C-O>v/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.INSERT,
)
}
@Test
fun `search in one time visual mode from replace`() {
doTest(
"R<C-O>v/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.REPLACE,
)
}
@Test
fun `search in op pending`() {
doTest(
"d/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.NORMAL(),
)
}
@Test
fun `search in op pending from one time mode`() {
doTest(
"i<C-O>d/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.INSERT,
)
}
@Disabled("Ctrl-o doesn't work yet in select mode")
@Test
fun `search in one time from select mode`() {
doTest(
"gh<C-O>/id<CR>",
"""Lorem ipsum dolor sit amet,
|${c}consectetur adipiscing elit
|Sed in orci mauris.
|Cras id tellus in ex imperdiet egestas.
""".trimMargin(),
"""Lorem ipsum dolor sit amet,
|consectetur adipiscing elit
|Sed in orci mauris.
|Cras ${c}id tellus in ex imperdiet egestas.
""".trimMargin(),
Mode.SELECT(SelectionType.CHARACTER_WISE),
)
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2003-2023 The IdeaVim authors
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE.txt file or at
* https://opensource.org/licenses/MIT.
*/
package org.jetbrains.plugins.ideavim.action.motion.updown
import com.intellij.idea.TestFor
import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test
class EnterNormalActionTest : VimTestCase() {
@Test
@TestFor(issues = ["VIM-3190"])
fun `mapping to control enter`() {
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("nmap <C-Enter> k"))
typeText("<C-Enter>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
fun `mapping to alt enter`() {
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("nmap <A-Enter> k"))
typeText("<A-Enter>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
@Test
@TestFor(issues = ["VIM-3190"])
fun `mapping to shift enter`() {
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("nmap <S-Enter> k"))
typeText("<S-Enter>")
assertState(
"""
Lorem Ipsum
$c
Lorem ipsum dolor sit amet, consectetur adipiscing elit
Sed in orci mauris.
Cras id tellus in ex imperdiet egestas.
""".trimIndent()
)
}
}

View File

@ -8,15 +8,105 @@
package org.jetbrains.plugins.ideavim.ex.implementation.commands package org.jetbrains.plugins.ideavim.ex.implementation.commands
import com.intellij.idea.TestFor
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.plugins.ideavim.VimTestCase import org.jetbrains.plugins.ideavim.VimTestCase
import org.junit.jupiter.api.Test import org.junit.jupiter.api.Test
class MarksCommandTest : VimTestCase() { class MarksCommandTest : VimTestCase() {
// https://youtrack.jetbrains.com/issue/VIM-2223
@Test @Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting to the same line`() {
configureByText(
"""${c}I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|hard by the torrent of a mountain pass.
""".trimMargin(),
)
typeText(injector.parser.parseKeys("V3j" + "y" + "P" + "gv"))
assertState(
"""I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|hard by the torrent of a mountain pass.
|${s}I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|${c}hard by the torrent of a mountain pass.${se}
""".trimMargin(),
)
}
@Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting to the same line reversed selection`() {
configureByText(
"""I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|${c}hard by the torrent of a mountain pass.
""".trimMargin(),
)
typeText(injector.parser.parseKeys("V3k" + "y" + "P" + "gv"))
assertState(
"""I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|hard by the torrent of a mountain pass.
|${s}${c}I found it in a legendary land
|all rocks and lavender and tufted grass,
|where it was settled on some sodden sand
|hard by the torrent of a mountain pass.${se}
""".trimMargin(),
)
}
@Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting inside selection expanded selection`() {
configureByText(
"""
${c}line1
line2
""".trimIndent(),
)
typeText(injector.parser.parseKeys("Vj" + "y" + "j" + "P" + "gv"))
assertState(
"""
${s}line1
line1
line2
line2${se}
""".trimIndent(),
)
}
@Test
@TestFor(issues = ["VIM-3176"])
fun `test gv after pasting below selection not changing selection`() {
configureByText(
"""
${c}line1
line2
not selected
""".trimIndent(),
)
typeText(injector.parser.parseKeys("Vj" + "y" + "j" + "p" + "gv"))
assertState(
"""
${s}line1
line2
${se}line1
line2
not selected
""".trimIndent(),
)
}
@Test
@TestFor(issues = ["VIM-2223"])
fun `test gv after replacing a line`() { fun `test gv after replacing a line`() {
configureByText( configureByText(
"""I found it in a legendary land """I found it in a legendary land
@ -35,8 +125,8 @@ class MarksCommandTest : VimTestCase() {
) )
} }
// https://youtrack.jetbrains.com/issue/VIM-1684
@Test @Test
@TestFor(issues = ["VIM-1684"])
fun `test reselecting different text length`() { fun `test reselecting different text length`() {
configureByText( configureByText(
""" """
@ -53,8 +143,8 @@ class MarksCommandTest : VimTestCase() {
) )
} }
// https://youtrack.jetbrains.com/issue/VIM-2491
@Test @Test
@TestFor(issues = ["VIM-2491"])
fun `test mapping with gv`() { fun `test mapping with gv`() {
configureByText("Oh, hi ${c}Andy Tom John") configureByText("Oh, hi ${c}Andy Tom John")
typeText(commandToKeys("xnoremap p pgvy")) typeText(commandToKeys("xnoremap p pgvy"))

View File

@ -11,10 +11,10 @@ package org.jetbrains.plugins.ideavim.propertybased
import com.intellij.ide.IdeEventQueue import com.intellij.ide.IdeEventQueue
import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.Editor
import com.intellij.testFramework.PlatformTestUtil import com.intellij.testFramework.PlatformTestUtil
import com.maddyhome.idea.vim.action.change.LazyVimCommand
import com.maddyhome.idea.vim.api.injector import com.maddyhome.idea.vim.api.injector
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.key.CommandNode import com.maddyhome.idea.vim.key.CommandNode
import com.maddyhome.idea.vim.newapi.ij
import com.maddyhome.idea.vim.newapi.vim import com.maddyhome.idea.vim.newapi.vim
import org.jetbrains.jetCheck.Generator import org.jetbrains.jetCheck.Generator
import org.jetbrains.jetCheck.ImperativeCommand import org.jetbrains.jetCheck.ImperativeCommand
@ -99,7 +99,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
val usedKey = env.generateValue(keyGenerator, null) val usedKey = env.generateValue(keyGenerator, null)
val node = currentNode[usedKey] val node = currentNode[usedKey]
env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${node.actionHolder.ij.actionId}" else ""}") env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${(node.actionHolder as LazyVimCommand).actionId}" else ""}")
VimTestCase.typeText(listOf(usedKey), editor, editor.project) VimTestCase.typeText(listOf(usedKey), editor, editor.project)
IdeEventQueue.getInstance().flushQueue() IdeEventQueue.getInstance().flushQueue()

View File

@ -15,6 +15,7 @@ plugins {
} }
val kotlinVersion: String by project val kotlinVersion: String by project
val kotlinxSerializationVersion: String by project
// group 'org.jetbrains.ideavim' // group 'org.jetbrains.ideavim'
// version 'SNAPSHOT' // version 'SNAPSHOT'
@ -35,18 +36,18 @@ afterEvaluate {
} }
dependencies { dependencies {
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0") testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion") testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion") compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
compileOnly("org.jetbrains:annotations:24.0.1") compileOnly("org.jetbrains:annotations:24.1.0")
ksp(project(":annotation-processors")) ksp(project(":annotation-processors"))
implementation(project(":annotation-processors")) implementation(project(":annotation-processors"))
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0") compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion")
} }
tasks { tasks {

View File

@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.key.Node
import com.maddyhome.idea.vim.state.VimStateMachine import com.maddyhome.idea.vim.state.VimStateMachine
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.ReturnableFromCmd
import com.maddyhome.idea.vim.state.mode.returnTo import com.maddyhome.idea.vim.state.mode.returnTo
import java.awt.event.InputEvent import java.awt.event.InputEvent
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@ -498,7 +499,7 @@ public class KeyHandler {
val text = injector.processGroup.endSearchCommand() val text = injector.processGroup.endSearchCommand()
commandBuilder.popCommandPart() // Pop ProcessExEntryAction commandBuilder.popCommandPart() // Pop ProcessExEntryAction
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
editorState.mode = Mode.NORMAL() editorState.mode = editorState.mode.returnTo()
} }
} }
@ -545,7 +546,9 @@ public class KeyHandler {
// state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument. // state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument.
injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key) injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key)
commandBuilder.commandState = CurrentCommandState.NEW_COMMAND commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
editorState.mode = Mode.CMD_LINE val currentMode = editorState.mode
check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" }
editorState.mode = Mode.CMD_LINE(currentMode)
} }
else -> Unit else -> Unit

View File

@ -17,6 +17,12 @@ import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream import kotlinx.serialization.json.decodeFromStream
import java.io.InputStream import java.io.InputStream
/**
* An interface defining the contract for providers responsible for reading and parsing JSON files.
* These files contain a list of command beans that are intended to be lazily loaded during runtime.
* The primary functionality of this interface is to transform the JSON data into a collection of
* {@code LazyVimCommand} instances.
*/
public interface CommandProvider { public interface CommandProvider {
public val commandListFileName: String public val commandListFileName: String
@ -29,12 +35,7 @@ public interface CommandProvider {
.map { .map {
val keys = it.value.map { bean -> injector.parser.parseKeys(bean.keys) }.toSet() val keys = it.value.map { bean -> injector.parser.parseKeys(bean.keys) }.toSet()
val modes = it.value.first().modes.map { mode -> MappingMode.parseModeChar(mode) }.toSet() val modes = it.value.first().modes.map { mode -> MappingMode.parseModeChar(mode) }.toSet()
LazyVimCommand( LazyVimCommand(keys, modes, it.key, classLoader)
keys,
modes,
it.key,
classLoader
)
} }
} }

View File

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

View File

@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import javax.swing.KeyStroke import javax.swing.KeyStroke
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL]) @CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction { public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
override val keyStrokesSet: Set<List<KeyStroke>> = setOf( override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
injector.parser.parseKeys("u"), injector.parser.parseKeys("u"),

View File

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

View File

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

View File

@ -30,6 +30,7 @@ public class FilterVisualLinesAction : VimActionHandler.SingleExecution() {
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE) override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean { override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
injector.markService.setVisualSelectionMarks(editor)
injector.processGroup.startFilterCommand(editor, context, cmd) injector.processGroup.startFilterCommand(editor, context, cmd)
editor.exitVisualMode() editor.exitVisualMode()
return true return true

View File

@ -144,7 +144,7 @@ public interface VimChangeGroup {
operatorArguments: OperatorArguments, operatorArguments: OperatorArguments,
) )
public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret
public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret

View File

@ -35,8 +35,9 @@ import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_START
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
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.SelectionType import com.maddyhome.idea.vim.state.mode.SelectionType
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.toReturnTo
import org.jetbrains.annotations.NonNls import org.jetbrains.annotations.NonNls
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
import java.util.* import java.util.*
@ -202,7 +203,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
* @param caret The caret to start insertion in * @param caret The caret to start insertion in
* @param str The text to insert * @param str The text to insert
*/ */
override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret { override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret {
(editor as MutableVimEditor).insertText(Offset(offset), str) (editor as MutableVimEditor).insertText(Offset(offset), str)
val newCaret = caret.moveToInlayAwareOffset(offset + str.length) val newCaret = caret.moveToInlayAwareOffset(offset + str.length)
@ -614,7 +615,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
* @param editor The editor to put into NORMAL mode for one command * @param editor The editor to put into NORMAL mode for one command
*/ */
override fun processSingleCommand(editor: VimEditor) { override fun processSingleCommand(editor: VimEditor) {
getInstance(editor).mode = Mode.NORMAL(returnTo = ReturnTo.INSERT) getInstance(editor).mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo)
clearStrokes(editor) clearStrokes(editor)
} }

View File

@ -166,7 +166,10 @@ public abstract class VimKeyGroupBase : VimKeyGroup {
private fun registerKeyMapping(fromKeys: List<KeyStroke>, owner: MappingOwner) { private fun registerKeyMapping(fromKeys: List<KeyStroke>, owner: MappingOwner) {
val oldSize = requiredShortcutKeys.size val oldSize = requiredShortcutKeys.size
for (key in fromKeys) { for (key in fromKeys) {
if (key.keyChar == KeyEvent.CHAR_UNDEFINED && key.keyCode != KeyEvent.VK_ESCAPE && key.keyCode != KeyEvent.VK_ENTER) { if (key.keyChar == KeyEvent.CHAR_UNDEFINED &&
!(key.keyCode == KeyEvent.VK_ESCAPE && key.modifiers == 0) &&
!(key.keyCode == KeyEvent.VK_ENTER && key.modifiers == 0)
) {
requiredShortcutKeys.add(RequiredShortcut(key, owner)) requiredShortcutKeys.add(RequiredShortcut(key, owner))
} }
} }

View File

@ -334,13 +334,13 @@ public abstract class VimMarkServiceBase : VimMarkService {
val startPosition = selectionInfo.start val startPosition = selectionInfo.start
var newStartPosition = selectionInfo.start var newStartPosition = selectionInfo.start
if (startPosition != null && insStart.line < startPosition.line) { if (startPosition != null && insStart.line <= startPosition.line) {
newStartPosition = BufferPosition(startPosition.line + lines, startPosition.column, startPosition.leansForward) newStartPosition = BufferPosition(startPosition.line + lines, startPosition.column, startPosition.leansForward)
} }
val endPosition = selectionInfo.end val endPosition = selectionInfo.end
var newEndPosition = endPosition var newEndPosition = endPosition
if (endPosition != null && insStart.line < endPosition.line) { if (endPosition != null && insStart.line <= endPosition.line) {
newEndPosition = BufferPosition(endPosition.line + lines, endPosition.column, endPosition.leansForward) newEndPosition = BufferPosition(endPosition.line + lines, endPosition.column, endPosition.leansForward)
} }

View File

@ -13,11 +13,11 @@ import javax.swing.KeyStroke
public interface VimProcessGroup { public interface VimProcessGroup {
public val lastCommand: String? public val lastCommand: String?
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext?, count: Int, leader: Char) public fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char)
public fun endSearchCommand(): String public fun endSearchCommand(): String
public fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean public fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean
public fun startFilterCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) public fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
public fun startExCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) public fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean
public fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) public fun cancelExEntry(editor: VimEditor, resetCaret: Boolean)

View File

@ -23,7 +23,7 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
override val lastCommand: String override val lastCommand: String
get() = TODO("Not yet implemented") get() = TODO("Not yet implemented")
override fun startSearchCommand(editor: VimEditor, context: ExecutionContext?, count: Int, leader: Char) { override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@ -35,11 +35,11 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun startFilterCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) { public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun startExCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) { public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

View File

@ -11,9 +11,9 @@ package com.maddyhome.idea.vim.helper
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.api.options import com.maddyhome.idea.vim.api.options
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.common.TextRange import com.maddyhome.idea.vim.common.TextRange
import com.maddyhome.idea.vim.options.OptionConstants import com.maddyhome.idea.vim.options.OptionConstants
import com.maddyhome.idea.vim.state.VimStateMachine
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
import com.maddyhome.idea.vim.state.mode.isSingleModeActive import com.maddyhome.idea.vim.state.mode.isSingleModeActive
@ -41,7 +41,7 @@ public val VimEditor.isEndAllowed: Boolean
public fun VimEditor.isEndAllowed(mode: Mode): Boolean { public fun VimEditor.isEndAllowed(mode: Mode): Boolean {
return when (mode) { return when (mode) {
is Mode.INSERT, is Mode.VISUAL, is Mode.SELECT -> true is Mode.INSERT, is Mode.VISUAL, is Mode.SELECT -> true
is Mode.NORMAL, Mode.CMD_LINE, Mode.REPLACE, is Mode.OP_PENDING -> { is Mode.NORMAL, is Mode.CMD_LINE, Mode.REPLACE, is Mode.OP_PENDING -> {
// One day we'll use a proper insert_normal mode // One day we'll use a proper insert_normal mode
if (mode.isSingleModeActive) true else usesVirtualSpace if (mode.isSingleModeActive) true else usesVirtualSpace
} }

View File

@ -8,8 +8,5 @@
package com.maddyhome.idea.vim.helper package com.maddyhome.idea.vim.helper
import org.jetbrains.annotations.NonNls @VimNlsSafe
// [VERSION UPDATE] 203+ replace this annotation with @VimNlsSafe
@NonNls
public annotation class VimNlsSafe public annotation class VimNlsSafe

View File

@ -237,7 +237,7 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT
is Mode.VISUAL -> MappingMode.VISUAL is Mode.VISUAL -> MappingMode.VISUAL
is Mode.SELECT -> MappingMode.SELECT is Mode.SELECT -> MappingMode.SELECT
Mode.CMD_LINE -> MappingMode.CMD_LINE is Mode.CMD_LINE -> MappingMode.CMD_LINE
is Mode.OP_PENDING -> MappingMode.OP_PENDING is Mode.OP_PENDING -> MappingMode.OP_PENDING
} }
} }

View File

@ -16,11 +16,7 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
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.command.Argument import com.maddyhome.idea.vim.command.Argument
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.CHARACTER_WISE
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.common.Offset import com.maddyhome.idea.vim.common.Offset
import com.maddyhome.idea.vim.common.argumentCaptured import com.maddyhome.idea.vim.common.argumentCaptured
import com.maddyhome.idea.vim.common.offset import com.maddyhome.idea.vim.common.offset
@ -29,8 +25,12 @@ import com.maddyhome.idea.vim.extension.ExtensionHandler
import com.maddyhome.idea.vim.group.visual.VimSelection import com.maddyhome.idea.vim.group.visual.VimSelection
import com.maddyhome.idea.vim.group.visual.VimSelection.Companion.create import com.maddyhome.idea.vim.group.visual.VimSelection.Companion.create
import com.maddyhome.idea.vim.helper.VimNlsSafe import com.maddyhome.idea.vim.helper.VimNlsSafe
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.helper.vimStateMachine import com.maddyhome.idea.vim.helper.vimStateMachine
import com.maddyhome.idea.vim.state.VimStateMachine
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
import com.maddyhome.idea.vim.state.mode.mode
import com.maddyhome.idea.vim.state.mode.selectionType
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
import java.awt.event.KeyEvent import java.awt.event.KeyEvent
@ -250,7 +250,12 @@ public class ToActionMappingInfo(
LOG.debug("Executing 'ToAction' mapping...") LOG.debug("Executing 'ToAction' mapping...")
val editorDataContext = injector.executionContextManager.onEditor(editor, context) val editorDataContext = injector.executionContextManager.onEditor(editor, context)
val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext) val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext)
injector.actionExecutor.executeAction(action, dataContext)
val commandBuilder = editor.vimStateMachine.commandBuilder
for (i in 0 until commandBuilder.count.coerceAtLeast(1)) {
injector.actionExecutor.executeAction(action, dataContext)
}
commandBuilder.resetCount()
} }
public companion object { public companion object {

View File

@ -42,6 +42,7 @@ import javax.swing.KeyStroke
public interface Node<T> public interface Node<T>
/** Represents a complete command */ /** Represents a complete command */
// Todo make T LazyVimCommand
public class CommandNode<T>(public val actionHolder: T) : Node<T> public class CommandNode<T>(public val actionHolder: T) : Node<T>
/** Represents a part of the command */ /** Represents a part of the command */

View File

@ -77,7 +77,7 @@ public sealed class ShortcutOwnerInfo {
is Mode.VISUAL -> this.visual is Mode.VISUAL -> this.visual
is Mode.SELECT -> this.visual is Mode.SELECT -> this.visual
Mode.INSERT -> this.insert Mode.INSERT -> this.insert
Mode.CMD_LINE -> this.normal is Mode.CMD_LINE -> this.normal
is Mode.OP_PENDING -> this.normal is Mode.OP_PENDING -> this.normal
Mode.REPLACE -> this.insert Mode.REPLACE -> this.insert
} }

View File

@ -10,9 +10,9 @@ package com.maddyhome.idea.vim.options.helpers
import com.maddyhome.idea.vim.api.Options import com.maddyhome.idea.vim.api.Options
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.exExceptionMessage import com.maddyhome.idea.vim.ex.exExceptionMessage
import com.maddyhome.idea.vim.helper.enumSetOf import com.maddyhome.idea.vim.helper.enumSetOf
import com.maddyhome.idea.vim.state.mode.Mode
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
import java.util.* import java.util.*
@ -175,7 +175,7 @@ public enum class GuiCursorMode(public val token: String) {
is Mode.SELECT -> GuiCursorMode.INSERT is Mode.SELECT -> GuiCursorMode.INSERT
is Mode.VISUAL -> GuiCursorMode.VISUAL // TODO: VISUAL_EXCLUSIVE is Mode.VISUAL -> GuiCursorMode.VISUAL // TODO: VISUAL_EXCLUSIVE
// This doesn't handle ci and cr, but we don't care - our CMD_LINE will never call this // This doesn't handle ci and cr, but we don't care - our CMD_LINE will never call this
Mode.CMD_LINE -> GuiCursorMode.CMD_LINE is Mode.CMD_LINE -> GuiCursorMode.CMD_LINE
} }
} }
} }

View File

@ -27,14 +27,15 @@ import com.maddyhome.idea.vim.state.VimStateMachine
* Also read about how modes work in Vim: https://github.com/JetBrains/ideavim/wiki/how-many-modes-does-vim-have * Also read about how modes work in Vim: https://github.com/JetBrains/ideavim/wiki/how-many-modes-does-vim-have
*/ */
public sealed interface Mode { public sealed interface Mode {
public data class NORMAL(public val returnTo: ReturnTo? = null) : Mode public data class NORMAL(public val returnTo: ReturnTo? = null) : Mode, ReturnableFromCmd
public data class OP_PENDING(public val returnTo: ReturnTo? = null, public val forcedVisual: SelectionType? = null) : public data class OP_PENDING(public val returnTo: ReturnTo? = null, public val forcedVisual: SelectionType? = null) :
Mode Mode, ReturnableFromCmd
public data class VISUAL(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode public data class VISUAL(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode,
ReturnableFromCmd
public data class SELECT(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode public data class SELECT(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode
public object INSERT : Mode public object INSERT : Mode
public object REPLACE : Mode public object REPLACE : Mode
public object CMD_LINE : Mode public data class CMD_LINE(public val returnTo: ReturnableFromCmd) : Mode
} }
public sealed interface ReturnTo { public sealed interface ReturnTo {
@ -42,6 +43,9 @@ public sealed interface ReturnTo {
public object REPLACE : ReturnTo public object REPLACE : ReturnTo
} }
// Marks modes that can we return from CMD_LINE mode
public sealed interface ReturnableFromCmd
public enum class SelectionType { public enum class SelectionType {
LINE_WISE, LINE_WISE,
CHARACTER_WISE, CHARACTER_WISE,

View File

@ -117,7 +117,55 @@ public fun Mode.toVimNotation(): String {
} }
Mode.REPLACE -> "R" Mode.REPLACE -> "R"
Mode.CMD_LINE -> "c" is Mode.CMD_LINE -> "c"
is Mode.OP_PENDING -> "no" is Mode.OP_PENDING -> "no"
} }
} }
public fun Mode.returnTo(): Mode {
return when (this) {
is Mode.CMD_LINE -> {
val returnMode = returnTo as Mode
// We need to understand logic that doesn't exit visual if it's just visual,
// but exits visual if it's one-time visual
if (returnMode.returnTo != null) {
returnMode.returnTo()
} else {
returnMode
}
}
Mode.INSERT -> Mode.NORMAL()
is Mode.NORMAL -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
is Mode.OP_PENDING -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
Mode.REPLACE -> Mode.NORMAL()
is Mode.SELECT -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
is Mode.VISUAL -> when (returnTo) {
ReturnTo.INSERT -> Mode.INSERT
ReturnTo.REPLACE -> Mode.REPLACE
null -> Mode.NORMAL()
}
}
}
public val Mode.toReturnTo: ReturnTo
get() = when (this) {
Mode.INSERT -> ReturnTo.INSERT
Mode.REPLACE -> ReturnTo.REPLACE
else -> error("Cannot get return to from $this")
}

View File

@ -11,7 +11,13 @@ package com.maddyhome.idea.vim.vimscript.model
import java.lang.invoke.MethodHandles import java.lang.invoke.MethodHandles
import java.lang.invoke.MethodType import java.lang.invoke.MethodType
public open class LazyInstance<T>(private val className: String, private val classLoader: ClassLoader) { /**
* Abstract class representing a lazily loaded instance of a specified class. The class is dynamically
* loaded and instantiated at runtime, using the provided class name and class loader. This approach is
* useful for deferring the loading and instantiation of a class until it is actually needed, reducing
* initial memory footprint and startup time.
*/
public abstract class LazyInstance<T>(private val className: String, private val classLoader: ClassLoader) {
public open val instance: T by lazy { public open val instance: T by lazy {
val aClass = classLoader.loadClass(className) val aClass = classLoader.loadClass(className)
val lookup = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup()) val lookup = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup())