mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-18 19:24:55 +02:00
Compare commits
225 Commits
customized
...
customized
Author | SHA1 | Date | |
---|---|---|---|
dd8bfac3cf
|
|||
fa434074d7
|
|||
b488295b59
|
|||
990ce13d3e
|
|||
b8cf11257c
|
|||
755f05791a
|
|||
2d170dd15b
|
|||
943dffd43a
|
|||
f17e99dd46
|
|||
aa4caaa722
|
|||
4380b88cbd
|
|||
55ce038d51
|
|||
deec7eef2e
|
|||
2feffa9ff4
|
|||
f7f663f29a
|
|||
badbcd83d6
|
|||
d978901edf
|
|||
08940fdaba
|
|||
3a11fb9bd3
|
|||
fa9bb6adf4
|
|||
![]() |
fb75508258 | ||
![]() |
0e69168382 | ||
![]() |
9970ab8643 | ||
![]() |
7ff82010c3 | ||
![]() |
1da8cd53d2 | ||
![]() |
9337a89eac | ||
![]() |
510564dd91 | ||
![]() |
a9ededc997 | ||
![]() |
722cffbd48 | ||
![]() |
a787befd72 | ||
![]() |
8ddd71a65a | ||
![]() |
280e1ec16d | ||
![]() |
52cf10cb2e | ||
![]() |
c12082affc | ||
![]() |
c0d7d74dac | ||
![]() |
df72b24ad2 | ||
![]() |
26bdd15400 | ||
![]() |
e13310b4e0 | ||
![]() |
e9d4218705 | ||
![]() |
56b80e4e60 | ||
![]() |
679f6471e6 | ||
![]() |
984179695c | ||
![]() |
5cca484a82 | ||
![]() |
d91e2296b0 | ||
![]() |
59768c16e2 | ||
![]() |
580efeae1a | ||
![]() |
0a3b508c8a | ||
![]() |
5e2f590b76 | ||
![]() |
ee94396afa | ||
![]() |
98764b6356 | ||
![]() |
f01cc4d0d0 | ||
![]() |
4c0f17429b | ||
![]() |
6a2ae1c572 | ||
![]() |
a2681ce6cc | ||
![]() |
4e43606932 | ||
![]() |
28c0c3207a | ||
![]() |
ecfa0e2b49 | ||
![]() |
ec3122f320 | ||
![]() |
7e4b4c973c | ||
![]() |
64753df2dd | ||
![]() |
75b36ab886 | ||
![]() |
208a78c748 | ||
![]() |
027249c575 | ||
![]() |
5ceb960205 | ||
![]() |
1cea156c5a | ||
![]() |
e1efa1ecbc | ||
![]() |
517de5e179 | ||
![]() |
825b62a2a9 | ||
![]() |
5ec817776c | ||
![]() |
3ad0519add | ||
![]() |
9868522341 | ||
![]() |
5b8d8c617f | ||
![]() |
a1f66061e3 | ||
![]() |
d8811933c9 | ||
![]() |
c9864dde8d | ||
![]() |
ca849d6649 | ||
![]() |
95a2354a86 | ||
![]() |
538e0ac48c | ||
![]() |
1c17411f04 | ||
![]() |
cbe0f89548 | ||
![]() |
615b071dcb | ||
![]() |
2d74f121aa | ||
![]() |
f65c180b8f | ||
![]() |
eb389c472d | ||
![]() |
befdf08035 | ||
![]() |
7a43ac865e | ||
![]() |
c43fcf9fbf | ||
![]() |
472a633010 | ||
![]() |
fc46acb2e4 | ||
![]() |
7fde66eb40 | ||
![]() |
b3cea3997d | ||
![]() |
2f20193086 | ||
![]() |
601e207f04 | ||
![]() |
f0d3d8b276 | ||
![]() |
e02d34f023 | ||
![]() |
0504be84b6 | ||
![]() |
216f020b70 | ||
![]() |
66505eedfa | ||
![]() |
b307c7d88b | ||
![]() |
47d4445fa8 | ||
![]() |
7098d2633a | ||
![]() |
61b5393b54 | ||
![]() |
6fe2cf13b6 | ||
![]() |
cc971eb2df | ||
![]() |
a260987f5c | ||
![]() |
5eb8f44dfc | ||
![]() |
e36131b38b | ||
![]() |
b67868afde | ||
![]() |
328fdee281 | ||
![]() |
8ab43e98fe | ||
![]() |
4f407ccc03 | ||
![]() |
5f3fddd3e4 | ||
![]() |
392f3b536d | ||
![]() |
155de2b396 | ||
![]() |
6c9930ac2a | ||
![]() |
9dddf4f4bc | ||
![]() |
314506c15c | ||
673222da6c | |||
![]() |
58b97e6361 | ||
![]() |
8bc2032b07 | ||
![]() |
40d4354dfc | ||
![]() |
27f2f5bb2b | ||
![]() |
490b934269 | ||
![]() |
e2e2b4d176 | ||
![]() |
7a1763bbee | ||
![]() |
ca8904b6bb | ||
![]() |
6384b28689 | ||
![]() |
e661466558 | ||
![]() |
8faf2beba4 | ||
![]() |
fb29319ec6 | ||
![]() |
7779d7d193 | ||
![]() |
2c5246b62f | ||
![]() |
e43a3f4518 | ||
![]() |
b5716f7a6d | ||
![]() |
fac5a3cc6f | ||
![]() |
671793078a | ||
![]() |
4f5ea1696f | ||
![]() |
b3e47e3bac | ||
![]() |
d67e990065 | ||
![]() |
7fb6f4b47f | ||
![]() |
df3b435a1f | ||
![]() |
5b65f1b544 | ||
![]() |
e159866d3b | ||
![]() |
aa0ce71612 | ||
![]() |
522e547f99 | ||
![]() |
9430341d4e | ||
![]() |
95838d045d | ||
![]() |
20832f11b6 | ||
![]() |
258203f400 | ||
![]() |
3b1768fa4e | ||
![]() |
23a3085bad | ||
![]() |
78c12e0ea6 | ||
![]() |
997cb85663 | ||
![]() |
968d5eabfa | ||
![]() |
590ce1f7ed | ||
![]() |
416a8838e4 | ||
![]() |
f6c349ac31 | ||
![]() |
517c6b40b5 | ||
![]() |
1fa78935a6 | ||
![]() |
4ddcd56740 | ||
![]() |
e5a2f33584 | ||
![]() |
c17cf3256a | ||
![]() |
5415bda02d | ||
![]() |
07cbaeb7aa | ||
![]() |
9d5aa83786 | ||
![]() |
463164cb88 | ||
![]() |
4809742088 | ||
![]() |
9cf0e285b4 | ||
![]() |
a6ca6f1cf9 | ||
![]() |
bd7479e1c0 | ||
![]() |
b35b51c203 | ||
![]() |
5652774888 | ||
![]() |
836e9a2fbc | ||
![]() |
64538c255d | ||
![]() |
62a9293dcf | ||
![]() |
1faae92f33 | ||
![]() |
dee808752f | ||
![]() |
5590af6995 | ||
![]() |
5afd161fba | ||
![]() |
336efa1e8b | ||
![]() |
568d5ca4ff | ||
![]() |
a9991f2a50 | ||
![]() |
1c8096444a | ||
![]() |
f424de46e6 | ||
![]() |
8fcca05565 | ||
![]() |
ed1f3cec59 | ||
![]() |
c29a409f28 | ||
![]() |
1a46936ad6 | ||
![]() |
e82abfb948 | ||
![]() |
c3409be780 | ||
![]() |
1557ab3474 | ||
![]() |
75fdda4fbf | ||
![]() |
4d75ef2849 | ||
![]() |
a1da23d1ba | ||
![]() |
c4bc751df7 | ||
![]() |
972d89ec6e | ||
![]() |
70f040e104 | ||
![]() |
d4de0b49c8 | ||
![]() |
2a42d58361 | ||
![]() |
14308956d7 | ||
![]() |
71339a66d7 | ||
![]() |
85f0664b56 | ||
![]() |
2f86ac0dfa | ||
![]() |
79d7b7a08d | ||
![]() |
b550d1990e | ||
![]() |
22062f0c77 | ||
![]() |
515f613a53 | ||
![]() |
615ed6b713 | ||
![]() |
f6eab62c3c | ||
![]() |
7d1e00ff0d | ||
![]() |
692439953c | ||
![]() |
6960a34d02 | ||
![]() |
b3662d4e6e | ||
![]() |
50c9b7c352 | ||
![]() |
f395d3b2bf | ||
![]() |
4fbf6cbc50 | ||
![]() |
9916958d6c | ||
![]() |
184a069c7f | ||
![]() |
0b65346633 | ||
![]() |
11f23dcc9e | ||
![]() |
f80d1defcb | ||
![]() |
e95d6343cb | ||
![]() |
a9052a068f | ||
![]() |
b1323c0d67 | ||
![]() |
87ceb8fb58 |
24
.github/workflows/runUiTests.yml
vendored
24
.github/workflows/runUiTests.yml
vendored
@@ -8,18 +8,20 @@ jobs:
|
|||||||
if: github.repository == 'JetBrains/ideavim'
|
if: github.repository == 'JetBrains/ideavim'
|
||||||
runs-on: macos-latest
|
runs-on: macos-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v4
|
||||||
- name: Setup Java
|
- name: Setup Java
|
||||||
uses: actions/setup-java@v2.1.0
|
uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: zulu
|
distribution: zulu
|
||||||
java-version: 11
|
java-version: 11
|
||||||
- name: Setup FFmpeg
|
- name: Setup FFmpeg
|
||||||
uses: FedericoCarboni/setup-ffmpeg@v1
|
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||||
with:
|
with:
|
||||||
# Not strictly necessary, but it may prevent rate limit
|
# Not strictly necessary, but it may prevent rate limit
|
||||||
# errors especially on GitHub-hosted macos machines.
|
# errors especially on GitHub-hosted macos machines.
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Setup Gradle
|
||||||
|
uses: gradle/gradle-build-action@v2.4.2
|
||||||
- name: Build Plugin
|
- name: Build Plugin
|
||||||
run: gradle :buildPlugin
|
run: gradle :buildPlugin
|
||||||
- name: Run Idea
|
- name: Run Idea
|
||||||
@@ -27,7 +29,7 @@ jobs:
|
|||||||
mkdir -p build/reports
|
mkdir -p build/reports
|
||||||
gradle :runIdeForUiTests > build/reports/idea.log &
|
gradle :runIdeForUiTests > build/reports/idea.log &
|
||||||
- name: Wait for Idea started
|
- name: Wait for Idea started
|
||||||
uses: jtalk/url-health-check-action@1.5
|
uses: jtalk/url-health-check-action@v3
|
||||||
with:
|
with:
|
||||||
url: http://127.0.0.1:8082
|
url: http://127.0.0.1:8082
|
||||||
max-attempts: 20
|
max-attempts: 20
|
||||||
@@ -35,15 +37,19 @@ jobs:
|
|||||||
- name: Tests
|
- name: Tests
|
||||||
run: gradle :testUi
|
run: gradle :testUi
|
||||||
- name: Move video
|
- name: Move video
|
||||||
if: ${{ failure() }}
|
if: always()
|
||||||
run: mv video build/reports
|
run: mv video build/reports
|
||||||
- name: Save fails report
|
- name: Move sandbox logs
|
||||||
if: ${{ failure() }}
|
if: always()
|
||||||
uses: actions/upload-artifact@v2
|
run: mv build/idea-sandbox/system/log sandbox-idea-log
|
||||||
|
- name: Save report
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: ui-test-fails-report-mac
|
name: ui-test-fails-report-mac
|
||||||
path: |
|
path: |
|
||||||
build/reports
|
build/reports
|
||||||
|
sandbox-idea-log
|
||||||
# build-for-ui-test-linux:
|
# build-for-ui-test-linux:
|
||||||
# runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
# steps:
|
# steps:
|
||||||
|
2
.idea/copyright/IdeaVim.xml
generated
2
.idea/copyright/IdeaVim.xml
generated
@@ -1,6 +1,6 @@
|
|||||||
<component name="CopyrightManager">
|
<component name="CopyrightManager">
|
||||||
<copyright>
|
<copyright>
|
||||||
<option name="notice" value="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." />
|
<option name="notice" value="Copyright 2003-&#36;today.year 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." />
|
||||||
<option name="myName" value="IdeaVim" />
|
<option name="myName" value="IdeaVim" />
|
||||||
</copyright>
|
</copyright>
|
||||||
</component>
|
</component>
|
16
.teamcity/_Self/Constants.kt
vendored
16
.teamcity/_Self/Constants.kt
vendored
@@ -5,13 +5,13 @@ 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"
|
const val GITHUB_TESTS = "2023.3.2"
|
||||||
const val NVIM_TESTS = "2023.1.2"
|
const val NVIM_TESTS = "2023.3.2"
|
||||||
const val PROPERTY_TESTS = "2023.1.2"
|
const val PROPERTY_TESTS = "2023.3.2"
|
||||||
const val LONG_RUNNING_TESTS = "2023.1.2"
|
const val LONG_RUNNING_TESTS = "2023.3.2"
|
||||||
const val QODANA_TESTS = "2023.1.2"
|
const val QODANA_TESTS = "2023.3.2"
|
||||||
const val RELEASE = "2023.1.2"
|
const val RELEASE = "2023.3.2"
|
||||||
|
|
||||||
const val RELEASE_DEV = "2023.1.2"
|
const val RELEASE_DEV = "2023.3.2"
|
||||||
const val RELEASE_EAP = "2023.1.2"
|
const val RELEASE_EAP = "2023.3.2"
|
||||||
}
|
}
|
||||||
|
8
.teamcity/_Self/Project.kt
vendored
8
.teamcity/_Self/Project.kt
vendored
@@ -23,9 +23,8 @@ object Project : Project({
|
|||||||
vcsRoot(GitHubPullRequest)
|
vcsRoot(GitHubPullRequest)
|
||||||
|
|
||||||
// Active tests
|
// Active tests
|
||||||
buildType(TestingBuildType("2023.2", "<default>", version = "2023.2.3"))
|
|
||||||
buildType(TestingBuildType("2023.1", "<default>", version = "2023.1.5"))
|
|
||||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
|
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
|
||||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
|
|
||||||
buildType(PropertyBased)
|
buildType(PropertyBased)
|
||||||
@@ -40,6 +39,11 @@ object Project : Project({
|
|||||||
|
|
||||||
// Common build type for all configurations
|
// Common build type for all configurations
|
||||||
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
|
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
|
||||||
|
artifactRules = """
|
||||||
|
+:build/reports => build/reports
|
||||||
|
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
init()
|
init()
|
||||||
|
|
||||||
requirements {
|
requirements {
|
||||||
|
2
.teamcity/_Self/subprojects/OldTests.kt
vendored
2
.teamcity/_Self/subprojects/OldTests.kt
vendored
@@ -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))
|
||||||
})
|
})
|
||||||
|
29
.teamcity/patches/buildTypes/IdeaVimTests_Latest_EAP.kts
vendored
Normal file
29
.teamcity/patches/buildTypes/IdeaVimTests_Latest_EAP.kts
vendored
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package patches.buildTypes
|
||||||
|
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.RelativeId
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.GradleBuildStep
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.changeBuildType
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.expectSteps
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.update
|
||||||
|
|
||||||
|
/*
|
||||||
|
This patch script was generated by TeamCity on settings change in UI.
|
||||||
|
To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
|
||||||
|
accordingly, and delete the patch script.
|
||||||
|
*/
|
||||||
|
changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) {
|
||||||
|
expectSteps {
|
||||||
|
gradle {
|
||||||
|
tasks = "clean test"
|
||||||
|
buildFile = ""
|
||||||
|
enableStacktrace = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
steps {
|
||||||
|
update<GradleBuildStep>(0) {
|
||||||
|
clearConditions()
|
||||||
|
jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
.teamcity/patches/projects/_Self.kts
vendored
Normal file
17
.teamcity/patches/projects/_Self.kts
vendored
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package patches.projects
|
||||||
|
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
|
||||||
|
|
||||||
|
/*
|
||||||
|
This patch script was generated by TeamCity on settings change in UI.
|
||||||
|
To apply the patch, change the root project
|
||||||
|
accordingly, and delete the patch script.
|
||||||
|
*/
|
||||||
|
changeProject(DslContext.projectId) {
|
||||||
|
check(description == "Vim engine for IDEs based on the IntelliJ platform") {
|
||||||
|
"Unexpected description: '$description'"
|
||||||
|
}
|
||||||
|
description = "Vim engine for JetBrains IDEs"
|
||||||
|
}
|
@@ -487,6 +487,10 @@ Contributors:
|
|||||||
[![icon][github]](https://github.com/pWydmuch)
|
[![icon][github]](https://github.com/pWydmuch)
|
||||||
|
|
||||||
pWydmuch
|
pWydmuch
|
||||||
|
* [![icon][mail]](mailto:leonid989@gmail.com)
|
||||||
|
[![icon][github]](https://github.com/Infonautica)
|
||||||
|
|
||||||
|
Leonid Danilov
|
||||||
|
|
||||||
Previous contributors:
|
Previous contributors:
|
||||||
|
|
||||||
|
20
CHANGES.md
20
CHANGES.md
@@ -31,6 +31,26 @@ 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
|
||||||
|
* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
|
||||||
|
|
||||||
|
### 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…
|
||||||
|
* [772](https://github.com/JetBrains/ideavim/pull/772) by [chylex](https://github.com/chylex): Prevent code completion popup from appearing after running a macro
|
||||||
|
* [787](https://github.com/JetBrains/ideavim/pull/787) by [Leonid Danilov](https://github.com/Infonautica): Added "Which-Key" to Plugins
|
||||||
|
* [778](https://github.com/JetBrains/ideavim/pull/778) by [lippfi](https://github.com/lippfi): Showmode
|
||||||
|
* [788](https://github.com/JetBrains/ideavim/pull/788) by [Matt Ellis](https://github.com/citizenmatt): Refactor VimOptionGroupBase
|
||||||
|
|
||||||
## 2.7.0, 2023-11-07
|
## 2.7.0, 2023-11-07
|
||||||
|
|
||||||
|
@@ -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`.
|
||||||
|
@@ -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.22-1.0.16")
|
||||||
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")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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'),
|
||||||
|
|
||||||
|
@@ -49,14 +49,14 @@ buildscript {
|
|||||||
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||||
|
|
||||||
// This is needed for jgit to connect to ssh
|
// This is needed for jgit to connect to ssh
|
||||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
|
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
|
||||||
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.7")
|
||||||
classpath("io.ktor:ktor-client-cio:2.3.5")
|
classpath("io.ktor:ktor-client-cio:2.3.7")
|
||||||
classpath("io.ktor:ktor-client-auth:2.3.6")
|
classpath("io.ktor:ktor-client-auth:2.3.7")
|
||||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
|
classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
|
||||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
|
||||||
|
|
||||||
// This comes from the changelog plugin
|
// This comes from the changelog plugin
|
||||||
// classpath("org.jetbrains:markdown:0.3.1")
|
// classpath("org.jetbrains:markdown:0.3.1")
|
||||||
@@ -69,7 +69,7 @@ plugins {
|
|||||||
kotlin("jvm") version "1.8.21"
|
kotlin("jvm") version "1.8.21"
|
||||||
application
|
application
|
||||||
|
|
||||||
id("org.jetbrains.intellij") version "1.16.0"
|
id("org.jetbrains.intellij") version "1.16.1"
|
||||||
id("org.jetbrains.changelog") version "2.2.0"
|
id("org.jetbrains.changelog") version "2.2.0"
|
||||||
|
|
||||||
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
|
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
|
||||||
@@ -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")
|
||||||
@@ -126,11 +126,11 @@ dependencies {
|
|||||||
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
||||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
|
testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
|
||||||
|
|
||||||
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
|
testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
|
||||||
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
|
testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
|
||||||
testImplementation("com.automation-remarks:video-recorder-junit:2.0")
|
testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
|
||||||
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
|
runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
|
||||||
antlr("org.antlr:antlr4:$antlrVersion")
|
antlr("org.antlr:antlr4:$antlrVersion")
|
||||||
|
|
||||||
@@ -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")
|
||||||
}
|
}
|
||||||
@@ -184,6 +184,14 @@ tasks {
|
|||||||
include("**/*test.class")
|
include("**/*test.class")
|
||||||
include("**/*Tests.class")
|
include("**/*Tests.class")
|
||||||
exclude("**/ParserTest.class")
|
exclude("**/ParserTest.class")
|
||||||
|
|
||||||
|
// Set teamcity env variable locally to run additional tests for leaks.
|
||||||
|
// By default, this test runs on TC only, but this test doesn't take a lot of time,
|
||||||
|
// so we can turn it on for local development
|
||||||
|
if (environment["TEAMCITY_VERSION"] == null) {
|
||||||
|
println("Set env TEAMCITY_VERSION to X")
|
||||||
|
environment("TEAMCITY_VERSION" to "X")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val testWithNeovim by getting(Test::class) {
|
val testWithNeovim by getting(Test::class) {
|
||||||
@@ -294,6 +302,7 @@ tasks {
|
|||||||
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
|
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
|
||||||
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
||||||
systemProperty("jb.consents.confirmation.enabled", "false")
|
systemProperty("jb.consents.confirmation.enabled", "false")
|
||||||
|
systemProperty("ide.show.tips.on.startup.default.value", "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
runPluginVerifier {
|
runPluginVerifier {
|
||||||
@@ -344,8 +353,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 +531,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 {
|
||||||
|
@@ -396,3 +396,19 @@ Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
|
|||||||
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
|
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary><h2>Which-Key</h2></summary>
|
||||||
|
|
||||||
|
Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
|
||||||
|
|
||||||
|
### Setup:
|
||||||
|
- Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin.
|
||||||
|
- Add the following command to `~/.ideavimrc`: `set which-key`
|
||||||
|
|
||||||
|
### Instructions
|
||||||
|
|
||||||
|
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
|
||||||
|
|
||||||
|
</details>
|
||||||
|
@@ -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:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@@ -8,21 +8,26 @@
|
|||||||
|
|
||||||
# suppress inspection "UnusedProperty" for whole file
|
# suppress inspection "UnusedProperty" for whole file
|
||||||
|
|
||||||
ideaVersion=2023.2
|
ideaVersion=2023.3.2
|
||||||
downloadIdeaSources=true
|
downloadIdeaSources=true
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=chylex-20
|
version=chylex-26
|
||||||
javaVersion=17
|
javaVersion=17
|
||||||
remoteRobotVersion=0.11.17
|
remoteRobotVersion=0.11.21
|
||||||
antlrVersion=4.10.1
|
antlrVersion=4.10.1
|
||||||
|
|
||||||
kotlin.incremental.useClasspathSnapshot=false
|
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=
|
||||||
|
|
||||||
|
@@ -20,17 +20,17 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
|
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
|
||||||
|
|
||||||
implementation("io.ktor:ktor-client-core:2.3.6")
|
implementation("io.ktor:ktor-client-core:2.3.7")
|
||||||
implementation("io.ktor:ktor-client-cio:2.3.5")
|
implementation("io.ktor:ktor-client-cio:2.3.7")
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
|
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
|
||||||
implementation("io.ktor:ktor-client-auth:2.3.6")
|
implementation("io.ktor:ktor-client-auth:2.3.7")
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||||
|
|
||||||
// This is needed for jgit to connect to ssh
|
// This is needed for jgit to connect to ssh
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
|
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
|
||||||
implementation("com.vdurmont:semver4j:3.1.0")
|
implementation("com.vdurmont:semver4j:3.1.0")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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 }
|
||||||
|
@@ -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
|
||||||
|
@@ -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()
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
|
||||||
|
@@ -219,6 +219,10 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
return getInstance().enabled;
|
return getInstance().enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static boolean isNotEnabled() {
|
||||||
|
return !isEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
public static void setEnabled(final boolean enabled) {
|
public static void setEnabled(final boolean enabled) {
|
||||||
if (isEnabled() == enabled) return;
|
if (isEnabled() == enabled) return;
|
||||||
|
|
||||||
@@ -232,7 +236,13 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
getInstance().turnOnPlugin();
|
getInstance().turnOnPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusBarIconFactory.Companion.updateIcon();
|
if (enabled) {
|
||||||
|
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
|
||||||
|
} else {
|
||||||
|
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusBarIconFactory.Util.INSTANCE.updateIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getMessage() {
|
public static String getMessage() {
|
||||||
@@ -264,7 +274,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);
|
||||||
|
@@ -28,8 +28,11 @@ import javax.swing.KeyStroke
|
|||||||
* Accepts all regular keystrokes and passes them on to the Vim key handler.
|
* Accepts all regular keystrokes and passes them on to the Vim key handler.
|
||||||
*
|
*
|
||||||
* IDE shortcut keys used by Vim commands are handled by [com.maddyhome.idea.vim.action.VimShortcutKeyAction].
|
* IDE shortcut keys used by Vim commands are handled by [com.maddyhome.idea.vim.action.VimShortcutKeyAction].
|
||||||
|
*
|
||||||
|
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
|
||||||
|
* way to get ideavim keys for this plugin. See VIM-3085
|
||||||
*/
|
*/
|
||||||
internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
|
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
|
||||||
private val handler = KeyHandler.getInstance()
|
private val handler = KeyHandler.getInstance()
|
||||||
private val traceTime = injector.globalOptions().ideatracetime
|
private val traceTime = injector.globalOptions().ideatracetime
|
||||||
|
|
||||||
@@ -86,7 +89,7 @@ internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedAct
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
internal companion object {
|
||||||
private val LOG = logger<VimTypedActionHandler>()
|
private val LOG = logger<VimTypedActionHandler>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@@ -14,14 +14,10 @@ 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
|
||||||
@@ -58,9 +54,17 @@ 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.
|
||||||
|
*
|
||||||
|
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
|
||||||
|
* way to get ideavim keys for this plugin. See VIM-3085
|
||||||
*/
|
*/
|
||||||
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
public 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")
|
||||||
@@ -94,7 +98,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
|||||||
|
|
||||||
// There is a chance that we can use BGT, but we call for isCell inside the update.
|
// There is a chance that we can use BGT, but we call for isCell inside the update.
|
||||||
// Not sure if can can use BGT with this call. Let's use EDT for now.
|
// Not sure if can can use BGT with this call. Let's use EDT for now.
|
||||||
override fun getActionUpdateThread() = ActionUpdateThread.EDT
|
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
|
||||||
|
|
||||||
override fun update(e: AnActionEvent) {
|
override fun update(e: AnActionEvent) {
|
||||||
val start = if (traceTime) System.currentTimeMillis() else null
|
val start = if (traceTime) System.currentTimeMillis() else null
|
||||||
@@ -109,7 +113,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
|
private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
|
||||||
if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
|
if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
|
||||||
val editor = getEditor(e)
|
val editor = getEditor(e)
|
||||||
if (editor != null && keyStroke != null) {
|
if (editor != null && keyStroke != null) {
|
||||||
if (isOctopusEnabled(keyStroke, editor)) {
|
if (isOctopusEnabled(keyStroke, editor)) {
|
||||||
@@ -163,14 +167,6 @@ 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
|
||||||
@@ -236,9 +232,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
|||||||
/**
|
/**
|
||||||
* getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
|
* getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
|
||||||
* but we should cache the value because on the second call (isEnabled -> actionPerformed)
|
* but we should cache the value because on the second call (isEnabled -> actionPerformed)
|
||||||
* the event is already consumed
|
* the event is already consumed and getDefaultKeyStroke returns null
|
||||||
*/
|
*/
|
||||||
private var keyStrokeCache: Pair<KeyEvent?, KeyStroke?> = null to null
|
private var keyStrokeCache: Pair<Long?, KeyStroke?> = null to null
|
||||||
|
|
||||||
private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
|
private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
|
||||||
val inputEvent = e.inputEvent
|
val inputEvent = e.inputEvent
|
||||||
@@ -246,9 +242,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
|||||||
val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
|
val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
|
||||||
val strokeCache = keyStrokeCache
|
val strokeCache = keyStrokeCache
|
||||||
if (defaultKeyStroke != null) {
|
if (defaultKeyStroke != null) {
|
||||||
keyStrokeCache = inputEvent to defaultKeyStroke
|
keyStrokeCache = inputEvent.`when` to defaultKeyStroke
|
||||||
return defaultKeyStroke
|
return defaultKeyStroke
|
||||||
} else if (strokeCache.first === inputEvent) {
|
} else if (strokeCache.first == inputEvent.`when`) {
|
||||||
keyStrokeCache = null to null
|
keyStrokeCache = null to null
|
||||||
return strokeCache.second
|
return strokeCache.second
|
||||||
}
|
}
|
||||||
@@ -281,7 +277,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
|||||||
.toSet()
|
.toSet()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
internal companion object {
|
||||||
@JvmField
|
@JvmField
|
||||||
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
|
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
|
||||||
ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
|
ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
|
||||||
|
@@ -18,9 +18,7 @@ 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.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
|
||||||
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.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.common.argumentCaptured
|
import com.maddyhome.idea.vim.common.argumentCaptured
|
||||||
import com.maddyhome.idea.vim.group.MotionGroup
|
import com.maddyhome.idea.vim.group.MotionGroup
|
||||||
@@ -28,10 +26,9 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
|
|||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import java.util.*
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
|
||||||
// todo make it multicaret
|
// todo make it multicaret
|
||||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
|
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
|
||||||
@@ -104,8 +101,6 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
|
|||||||
internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
|
internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||||
|
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
|
||||||
|
|
||||||
override fun executeAction(
|
override fun executeAction(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
caret: VimCaret,
|
caret: VimCaret,
|
||||||
|
@@ -14,13 +14,10 @@ import com.maddyhome.idea.vim.api.VimCaret
|
|||||||
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.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
|
||||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author vlan
|
* @author vlan
|
||||||
@@ -29,8 +26,6 @@ import java.util.*
|
|||||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
override val type: Command.Type = Command.Type.DELETE
|
||||||
|
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
|
||||||
|
|
||||||
override fun executeForAllCarets(
|
override fun executeForAllCarets(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
|
@@ -14,13 +14,10 @@ import com.maddyhome.idea.vim.api.VimCaret
|
|||||||
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.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
|
||||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author vlan
|
* @author vlan
|
||||||
@@ -29,8 +26,6 @@ import java.util.*
|
|||||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.DELETE
|
override val type: Command.Type = Command.Type.DELETE
|
||||||
|
|
||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
|
||||||
|
|
||||||
override fun executeForAllCarets(
|
override fun executeForAllCarets(
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
|
@@ -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
|
||||||
|
@@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.extension
|
|||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.components.service
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
@@ -17,7 +18,6 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
|||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.MappingMode
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
|
||||||
import com.maddyhome.idea.vim.common.CommandAlias
|
import com.maddyhome.idea.vim.common.CommandAlias
|
||||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
||||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
|
import com.maddyhome.idea.vim.helper.CommandLineHelper
|
||||||
@@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.helper.vimStateMachine
|
|||||||
import com.maddyhome.idea.vim.key.MappingOwner
|
import com.maddyhome.idea.vim.key.MappingOwner
|
||||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
@@ -38,6 +39,9 @@ import javax.swing.KeyStroke
|
|||||||
* @author vlan
|
* @author vlan
|
||||||
*/
|
*/
|
||||||
public object VimExtensionFacade {
|
public object VimExtensionFacade {
|
||||||
|
|
||||||
|
private val LOG = logger<VimExtensionFacade>()
|
||||||
|
|
||||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun putExtensionHandlerMapping(
|
public fun putExtensionHandlerMapping(
|
||||||
@@ -140,10 +144,12 @@ public object VimExtensionFacade {
|
|||||||
public fun inputKeyStroke(editor: Editor): KeyStroke {
|
public fun inputKeyStroke(editor: Editor): KeyStroke {
|
||||||
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
|
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
|
||||||
val input = Extension.consumeKeystroke()
|
val input = Extension.consumeKeystroke()
|
||||||
|
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
|
||||||
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
|
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
|
||||||
}
|
}
|
||||||
|
|
||||||
val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
|
val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
LOG.trace("Unit test mode is active")
|
||||||
val mappingStack = KeyHandler.getInstance().keyStack
|
val mappingStack = KeyHandler.getInstance().keyStack
|
||||||
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
|
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
|
||||||
if (editor.vim.vimStateMachine.isRecording) {
|
if (editor.vim.vimStateMachine.isRecording) {
|
||||||
@@ -151,11 +157,13 @@ public object VimExtensionFacade {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
LOG.trace("Getting char from the modal entry...")
|
||||||
var ref: KeyStroke? = null
|
var ref: KeyStroke? = null
|
||||||
ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
|
ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
|
||||||
ref = stroke
|
ref = stroke
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
LOG.trace("Got char $ref")
|
||||||
ref
|
ref
|
||||||
}
|
}
|
||||||
val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
|
val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
|
||||||
|
@@ -156,11 +156,6 @@ internal class CommentaryExtension : VimExtension {
|
|||||||
private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
|
private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
|
||||||
override val isRepeatable = true
|
override val isRepeatable = true
|
||||||
|
|
||||||
// In this operator we process selection by ourselves. This is necessary for rider, VIM-1758
|
|
||||||
override fun postProcessSelection(): Boolean {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
setOperatorFunction(this)
|
setOperatorFunction(this)
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
||||||
|
@@ -217,6 +217,8 @@ private object FileTypePatterns {
|
|||||||
|
|
||||||
return if (fileTypeName in htmlLikeFileTypes) {
|
return if (fileTypeName in htmlLikeFileTypes) {
|
||||||
this.htmlPatterns
|
this.htmlPatterns
|
||||||
|
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
|
||||||
|
this.javaPatterns
|
||||||
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
|
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
|
||||||
this.rubyPatterns
|
this.rubyPatterns
|
||||||
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
|
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
|
||||||
@@ -242,6 +244,7 @@ private object FileTypePatterns {
|
|||||||
)
|
)
|
||||||
|
|
||||||
private val htmlPatterns = createHtmlPatterns()
|
private val htmlPatterns = createHtmlPatterns()
|
||||||
|
private val javaPatterns = createJavaPatterns()
|
||||||
private val rubyPatterns = createRubyPatterns()
|
private val rubyPatterns = createRubyPatterns()
|
||||||
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
|
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
|
||||||
private val phpPatterns = createPhpPatterns()
|
private val phpPatterns = createPhpPatterns()
|
||||||
@@ -271,6 +274,14 @@ private object FileTypePatterns {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun createJavaPatterns(): LanguagePatterns {
|
||||||
|
return (
|
||||||
|
LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
|
||||||
|
LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
|
||||||
|
LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
private fun createRubyPatterns(): LanguagePatterns {
|
private fun createRubyPatterns(): LanguagePatterns {
|
||||||
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
|
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
|
||||||
// We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.
|
// We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
package com.maddyhome.idea.vim.extension.surround
|
package com.maddyhome.idea.vim.extension.surround
|
||||||
|
|
||||||
import com.intellij.openapi.application.runWriteAction
|
import com.intellij.openapi.application.runWriteAction
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.editor.Editor
|
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
|
||||||
@@ -255,6 +256,7 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
// Deleting surround is just changing the surrounding to "nothing"
|
// Deleting surround is just changing the surrounding to "nothing"
|
||||||
val charFrom = getChar(editor.ij)
|
val charFrom = getChar(editor.ij)
|
||||||
|
LOG.debug("DSurroundHandler: charFrom = $charFrom")
|
||||||
if (charFrom.code == 0) return
|
if (charFrom.code == 0) return
|
||||||
|
|
||||||
runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
|
runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
|
||||||
@@ -307,96 +309,101 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
private val LOG = logger<VimSurroundExtension>()
|
||||||
private const val REGISTER = '"'
|
|
||||||
|
|
||||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
private const val REGISTER = '"'
|
||||||
|
|
||||||
private val SURROUND_PAIRS = mapOf(
|
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
||||||
'b' to ("(" to ")"),
|
|
||||||
'(' to ("( " to " )"),
|
|
||||||
')' to ("(" to ")"),
|
|
||||||
'B' to ("{" to "}"),
|
|
||||||
'{' to ("{ " to " }"),
|
|
||||||
'}' to ("{" to "}"),
|
|
||||||
'r' to ("[" to "]"),
|
|
||||||
'[' to ("[ " to " ]"),
|
|
||||||
']' to ("[" to "]"),
|
|
||||||
'a' to ("<" to ">"),
|
|
||||||
'>' to ("<" to ">"),
|
|
||||||
's' to (" " to ""),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
private val SURROUND_PAIRS = mapOf(
|
||||||
SURROUND_PAIRS[c]
|
'b' to ("(" to ")"),
|
||||||
} else if (!c.isLetter()) {
|
'(' to ("( " to " )"),
|
||||||
val s = c.toString()
|
')' to ("(" to ")"),
|
||||||
s to s
|
'B' to ("{" to "}"),
|
||||||
} else {
|
'{' to ("{ " to " }"),
|
||||||
null
|
'}' to ("{" to "}"),
|
||||||
}
|
'r' to ("[" to "]"),
|
||||||
|
'[' to ("[ " to " ]"),
|
||||||
|
']' to ("[" to "]"),
|
||||||
|
'a' to ("<" to ">"),
|
||||||
|
'>' to ("<" to ">"),
|
||||||
|
's' to (" " to ""),
|
||||||
|
)
|
||||||
|
|
||||||
private fun inputTagPair(editor: Editor): Pair<String, String>? {
|
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||||
val tagInput = inputString(editor, "<", '>')
|
SURROUND_PAIRS[c]
|
||||||
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
|
} else if (!c.isLetter()) {
|
||||||
return if (matcher.find()) {
|
val s = c.toString()
|
||||||
val tagName = matcher.group(1)
|
s to s
|
||||||
val tagAttributes = matcher.group(2)
|
} else {
|
||||||
"<$tagName$tagAttributes>" to "</$tagName>"
|
null
|
||||||
} else {
|
}
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun inputFunctionName(
|
private fun inputTagPair(editor: Editor): Pair<String, String>? {
|
||||||
editor: Editor,
|
val tagInput = inputString(editor, "<", '>')
|
||||||
withInternalSpaces: Boolean,
|
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
|
||||||
): Pair<String, String>? {
|
return if (matcher.find()) {
|
||||||
val functionNameInput = inputString(editor, "function: ", null)
|
val tagName = matcher.group(1)
|
||||||
if (functionNameInput.isEmpty()) return null
|
val tagAttributes = matcher.group(2)
|
||||||
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||||
}
|
} else {
|
||||||
|
null
|
||||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
|
}
|
||||||
'<', 't' -> inputTagPair(editor)
|
}
|
||||||
'f' -> inputFunctionName(editor, false)
|
|
||||||
'F' -> inputFunctionName(editor, true)
|
private fun inputFunctionName(
|
||||||
else -> getSurroundPair(c)
|
editor: Editor,
|
||||||
}
|
withInternalSpaces: Boolean,
|
||||||
|
): Pair<String, String>? {
|
||||||
private fun getChar(editor: Editor): Char {
|
val functionNameInput = inputString(editor, "function: ", null)
|
||||||
val key = inputKeyStroke(editor)
|
if (functionNameInput.isEmpty()) return null
|
||||||
val keyChar = key.keyChar
|
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
||||||
return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
|
}
|
||||||
0.toChar()
|
|
||||||
} else {
|
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
|
||||||
keyChar
|
'<', 't' -> inputTagPair(editor)
|
||||||
}
|
'f' -> inputFunctionName(editor, false)
|
||||||
}
|
'F' -> inputFunctionName(editor, true)
|
||||||
|
else -> getSurroundPair(c)
|
||||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
}
|
||||||
runWriteAction {
|
|
||||||
val editor = caret.editor
|
private fun getChar(editor: Editor): Char {
|
||||||
val change = VimPlugin.getChange()
|
val key = inputKeyStroke(editor)
|
||||||
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
val keyChar = key.keyChar
|
||||||
|
val res = if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
|
||||||
val isEOF = range.endOffset == editor.text().length
|
0.toChar()
|
||||||
val hasNewLine = editor.endsWithNewLine()
|
} else {
|
||||||
val rightSurround = (if (tagsOnNewLines) {
|
keyChar
|
||||||
if (isEOF && !hasNewLine) {
|
}
|
||||||
"\n" + pair.second
|
LOG.trace("getChar: $res")
|
||||||
} else {
|
return res
|
||||||
pair.second + "\n"
|
}
|
||||||
}
|
|
||||||
} else {
|
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||||
pair.second
|
runWriteAction {
|
||||||
}).let { RepeatedCharSequence.of(it, count) }
|
val editor = caret.editor
|
||||||
|
val change = VimPlugin.getChange()
|
||||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
|
||||||
injector.markService.setChangeMarks(caret, TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length))
|
val isEOF = range.endOffset == editor.text().length
|
||||||
}
|
val hasNewLine = editor.endsWithNewLine()
|
||||||
}
|
val rightSurround = (if (tagsOnNewLines) {
|
||||||
|
if (isEOF && !hasNewLine) {
|
||||||
|
"\n" + pair.second
|
||||||
|
} else {
|
||||||
|
pair.second + "\n"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
pair.second
|
||||||
|
}).let { RepeatedCharSequence.of(it, count) }
|
||||||
|
|
||||||
|
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||||
|
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||||
|
injector.markService.setChangeMarks(
|
||||||
|
caret,
|
||||||
|
TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -20,9 +20,6 @@ import com.intellij.openapi.editor.actions.EnterAction
|
|||||||
import com.intellij.openapi.editor.event.EditorMouseEvent
|
import com.intellij.openapi.editor.event.EditorMouseEvent
|
||||||
import com.intellij.openapi.editor.event.EditorMouseListener
|
import com.intellij.openapi.editor.event.EditorMouseListener
|
||||||
import com.intellij.openapi.editor.impl.TextRangeInterval
|
import com.intellij.openapi.editor.impl.TextRangeInterval
|
||||||
import com.intellij.openapi.ui.MessageType
|
|
||||||
import com.intellij.openapi.ui.popup.Balloon
|
|
||||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
|
||||||
import com.intellij.openapi.util.UserDataHolder
|
import com.intellij.openapi.util.UserDataHolder
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
import com.intellij.psi.codeStyle.CodeStyleManager
|
import com.intellij.psi.codeStyle.CodeStyleManager
|
||||||
@@ -65,7 +62,6 @@ import com.maddyhome.idea.vim.helper.endOffsetInclusive
|
|||||||
import com.maddyhome.idea.vim.helper.inInsertMode
|
import com.maddyhome.idea.vim.helper.inInsertMode
|
||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
|
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
|
||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||||
import com.maddyhome.idea.vim.icons.VimIcons
|
|
||||||
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
|
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
|
||||||
import com.maddyhome.idea.vim.listener.VimInsertListener
|
import com.maddyhome.idea.vim.listener.VimInsertListener
|
||||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||||
@@ -82,14 +78,12 @@ import java.math.BigInteger
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides all the insert/replace related functionality
|
* Provides all the insert/replace related functionality
|
||||||
*/
|
*/
|
||||||
public class ChangeGroup : VimChangeGroupBase() {
|
public class ChangeGroup : VimChangeGroupBase() {
|
||||||
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
|
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
|
||||||
private var lastShownTime = 0L
|
|
||||||
private val listener: EditorMouseListener = object : EditorMouseListener {
|
private val listener: EditorMouseListener = object : EditorMouseListener {
|
||||||
override fun mouseClicked(event: EditorMouseEvent) {
|
override fun mouseClicked(event: EditorMouseEvent) {
|
||||||
val editor = event.editor
|
val editor = event.editor
|
||||||
@@ -103,10 +97,6 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
|
EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
|
||||||
}
|
}
|
||||||
|
|
||||||
public fun editorReleased(editor: Editor?) {
|
|
||||||
EventFacade.getInstance().removeEditorMouseListener(editor!!, listener)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
|
override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
|
||||||
val editor = (vimEditor as IjVimEditor).editor
|
val editor = (vimEditor as IjVimEditor).editor
|
||||||
val ijContext = context.ij
|
val ijContext = context.ij
|
||||||
@@ -404,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
) {
|
) {
|
||||||
|
val startPos = editor.offsetToBufferPosition(caret.offset.point)
|
||||||
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
||||||
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
||||||
val ijEditor = (editor as IjVimEditor).editor
|
val ijEditor = (editor as IjVimEditor).editor
|
||||||
@@ -428,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val afterAction = {
|
val afterAction = {
|
||||||
val firstLine = editor.offsetToBufferPosition(
|
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
|
||||||
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
|
|
||||||
).line
|
|
||||||
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
|
|
||||||
caret.moveToOffset(newOffset)
|
|
||||||
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
||||||
}
|
}
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
@@ -645,25 +632,6 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
avalanche: Boolean,
|
avalanche: Boolean,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
|
|
||||||
// Just an easter egg
|
|
||||||
if (avalanche) {
|
|
||||||
val currentTime = System.currentTimeMillis()
|
|
||||||
if (currentTime - lastShownTime > 60000) {
|
|
||||||
lastShownTime = currentTime
|
|
||||||
ApplicationManager.getApplication().invokeLater {
|
|
||||||
val balloon = JBPopupFactory.getInstance()
|
|
||||||
.createHtmlTextBalloonBuilder(
|
|
||||||
"Wow, nice vim skills!", VimIcons.IDEAVIM,
|
|
||||||
MessageType.INFO.titleForeground, MessageType.INFO.popupBackground,
|
|
||||||
null
|
|
||||||
).createBalloon()
|
|
||||||
balloon.show(
|
|
||||||
JBPopupFactory.getInstance().guessBestPopupLocation((editor as IjVimEditor).editor),
|
|
||||||
Balloon.Position.below
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val nf: List<String> = injector.options(editor).nrformats
|
val nf: List<String> = injector.options(editor).nrformats
|
||||||
val alpha = nf.contains("alpha")
|
val alpha = nf.contains("alpha")
|
||||||
val hex = nf.contains("hex")
|
val hex = nf.contains("hex")
|
||||||
|
@@ -29,6 +29,8 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
|
|||||||
public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
|
public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
|
||||||
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
||||||
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
|
||||||
|
public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
|
||||||
|
public var colorfulmodewidget: Boolean by optionProperty(IjOptions.colorfulmodewidget)
|
||||||
|
|
||||||
// Temporary options to control work-in-progress behaviour
|
// Temporary options to control work-in-progress behaviour
|
||||||
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
||||||
|
@@ -86,6 +86,8 @@ public object IjOptions {
|
|||||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, 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))
|
||||||
|
public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
|
||||||
|
public val colorfulmodewidget: ToggleOption = addOption(ToggleOption("colorfulmodewidget", GLOBAL, "colorfulmodewidget", false, isTemporary = true))
|
||||||
|
|
||||||
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
|
// This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
|
||||||
// derives from Option<VimInt>
|
// derives from Option<VimInt>
|
||||||
|
@@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -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
|
||||||
@@ -61,22 +64,36 @@ internal class MacroGroup : VimMacroBase() {
|
|||||||
try {
|
try {
|
||||||
myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
|
myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
|
||||||
val runnable = runnable@{
|
val runnable = runnable@{
|
||||||
// Handle one keystroke then queue up the next key
|
try {
|
||||||
for (i in 0 until total) {
|
// Handle one keystroke then queue up the next key
|
||||||
myPotemkinProgress.fraction = (i + 1).toDouble() / total
|
for (i in 0 until total) {
|
||||||
while (keyStack.hasStroke()) {
|
|
||||||
val key = keyStack.feedStroke()
|
|
||||||
try {
|
try {
|
||||||
myPotemkinProgress.checkCanceled()
|
myPotemkinProgress.fraction = (i + 1).toDouble() / total
|
||||||
} catch (e: ProcessCanceledException) {
|
while (keyStack.hasStroke()) {
|
||||||
return@runnable
|
val key = keyStack.feedStroke()
|
||||||
|
try {
|
||||||
|
myPotemkinProgress.checkCanceled()
|
||||||
|
} catch (e: ProcessCanceledException) {
|
||||||
|
return@runnable
|
||||||
|
}
|
||||||
|
ProgressManager.getInstance().executeNonCancelableSection {
|
||||||
|
// Prevent autocompletion during macros.
|
||||||
|
// See https://github.com/JetBrains/ideavim/pull/772 for details
|
||||||
|
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
|
||||||
|
getInstance().handleKey(editor, key, context)
|
||||||
|
}
|
||||||
|
if (injector.messages.isError()) return@runnable
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
keyStack.resetFirst()
|
||||||
}
|
}
|
||||||
ProgressManager.getInstance().executeNonCancelableSection { getInstance().handleKey(editor, key, context) }
|
|
||||||
if (injector.messages.isError()) return@runnable
|
|
||||||
}
|
}
|
||||||
keyStack.resetFirst()
|
} finally {
|
||||||
|
keyStack.removeFirst()
|
||||||
|
}
|
||||||
|
if (!isInternalMacro) {
|
||||||
|
MacroAutoImport.run(editor.ij, context.ij)
|
||||||
}
|
}
|
||||||
keyStack.removeFirst()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternalMacro) {
|
if (isInternalMacro) {
|
||||||
|
@@ -48,9 +48,7 @@ import com.maddyhome.idea.vim.api.options
|
|||||||
import com.maddyhome.idea.vim.api.visualLineToBufferLine
|
import com.maddyhome.idea.vim.api.visualLineToBufferLine
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.command.MotionType
|
import com.maddyhome.idea.vim.command.MotionType
|
||||||
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.VimStateMachine
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.handler.Motion
|
import com.maddyhome.idea.vim.handler.Motion
|
||||||
@@ -74,6 +72,8 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret
|
|||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.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.ui.ex.ExEntryPanel
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||||
import org.jetbrains.annotations.Range
|
import org.jetbrains.annotations.Range
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@@ -461,11 +461,13 @@ internal class MotionGroup : VimMotionGroupBase() {
|
|||||||
val fileEditor = event.oldEditor
|
val fileEditor = event.oldEditor
|
||||||
if (fileEditor is TextEditor) {
|
if (fileEditor is TextEditor) {
|
||||||
val editor = fileEditor.editor
|
val editor = fileEditor.editor
|
||||||
ExOutputModel.getInstance(editor).clear()
|
if (!editor.isDisposed) {
|
||||||
editor.vim.let { vimEditor ->
|
ExOutputModel.getInstance(editor).clear()
|
||||||
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
|
editor.vim.let { vimEditor ->
|
||||||
vimEditor.exitVisualMode()
|
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
|
||||||
KeyHandler.getInstance().reset(vimEditor)
|
vimEditor.exitVisualMode()
|
||||||
|
KeyHandler.getInstance().reset(vimEditor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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)
|
||||||
|
@@ -40,10 +40,6 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
|||||||
override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
|
override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
|
||||||
override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
|
override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
|
||||||
|
|
||||||
private fun updateFallbackWindow(fallbackWindow: VimEditor, targetEditor: VimEditor) {
|
|
||||||
copyPerWindowGlobalValues(fallbackWindow, targetEditor)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||||
// Vim only has one window, and it's not possible to close it. This means that editing a new file will always
|
// Vim only has one window, and it's not possible to close it. This means that editing a new file will always
|
||||||
@@ -58,6 +54,8 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
|||||||
// Unfortunately, we can't reliably know if a closing editor is the selected editor. Instead, we rely on selection
|
// Unfortunately, we can't reliably know if a closing editor is the selected editor. Instead, we rely on selection
|
||||||
// change events. If an editor is losing selection and there is no new selection, we can assume this means that
|
// change events. If an editor is losing selection and there is no new selection, we can assume this means that
|
||||||
// the last editor has been closed, and use the closed editor to update the fallback window
|
// the last editor has been closed, and use the closed editor to update the fallback window
|
||||||
|
//
|
||||||
|
// XXX: event.oldEditor will must probably return a disposed editor. So, it should be treated with care
|
||||||
if (event.newEditor == null) {
|
if (event.newEditor == null) {
|
||||||
(event.oldEditor as? TextEditor)?.editor?.let {
|
(event.oldEditor as? TextEditor)?.editor?.let {
|
||||||
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
|
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
|
||||||
@@ -68,7 +66,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class IjOptionConstants {
|
internal class IjOptionConstants {
|
||||||
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
|
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
const val idearefactormode_keep = "keep"
|
const val idearefactormode_keep = "keep"
|
||||||
|
@@ -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());
|
|
||||||
}
|
|
281
src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt
Normal file
281
src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt
Normal 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>()
|
||||||
|
}
|
||||||
|
}
|
@@ -192,8 +192,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
|||||||
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
||||||
* @param direction The direction to search
|
* @param direction The direction to search
|
||||||
*/
|
*/
|
||||||
@TestOnly
|
@Override
|
||||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
|
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
|
||||||
@NotNull String patternOffset, Direction direction) {
|
@NotNull String patternOffset, Direction direction) {
|
||||||
setLastUsedPattern(pattern, RE_SEARCH, true);
|
setLastUsedPattern(pattern, RE_SEARCH, true);
|
||||||
lastIgnoreSmartCase = false;
|
lastIgnoreSmartCase = false;
|
||||||
|
@@ -205,7 +205,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
|||||||
* @param event The change event
|
* @param event The change event
|
||||||
*/
|
*/
|
||||||
override fun beforeDocumentChange(event: DocumentEvent) {
|
override fun beforeDocumentChange(event: DocumentEvent) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
|
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
|
||||||
if (event.oldLength == 0) return
|
if (event.oldLength == 0) return
|
||||||
val doc = event.document
|
val doc = event.document
|
||||||
@@ -221,7 +221,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
|||||||
* @param event The change event
|
* @param event The change event
|
||||||
*/
|
*/
|
||||||
override fun documentChanged(event: DocumentEvent) {
|
override fun documentChanged(event: DocumentEvent) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
|
if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
|
||||||
if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
|
if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
|
||||||
val doc = event.document
|
val doc = event.document
|
||||||
@@ -242,7 +242,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
|||||||
|
|
||||||
class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
|
class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
|
||||||
override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
|
override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
if (!injector.globalIjOptions().ideamarks) {
|
if (!injector.globalIjOptions().ideamarks) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
|
override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
if (!injector.globalIjOptions().ideamarks) {
|
if (!injector.globalIjOptions().ideamarks) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@@ -27,15 +27,12 @@ import com.maddyhome.idea.vim.api.getLineEndOffset
|
|||||||
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.api.setChangeMarks
|
import com.maddyhome.idea.vim.api.setChangeMarks
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
|
||||||
import com.maddyhome.idea.vim.state.mode.isBlock
|
|
||||||
import com.maddyhome.idea.vim.state.mode.isChar
|
|
||||||
import com.maddyhome.idea.vim.state.mode.isLine
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.diagnostic.debug
|
import com.maddyhome.idea.vim.diagnostic.debug
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.RWLockLabel
|
import com.maddyhome.idea.vim.helper.RWLockLabel
|
||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||||
|
import com.maddyhome.idea.vim.ide.isClionNova
|
||||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
|
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
@@ -48,6 +45,10 @@ import com.maddyhome.idea.vim.put.PutData
|
|||||||
import com.maddyhome.idea.vim.put.VimPasteProvider
|
import com.maddyhome.idea.vim.put.VimPasteProvider
|
||||||
import com.maddyhome.idea.vim.put.VimPutBase
|
import com.maddyhome.idea.vim.put.VimPutBase
|
||||||
import com.maddyhome.idea.vim.register.RegisterConstants
|
import com.maddyhome.idea.vim.register.RegisterConstants
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.state.mode.isBlock
|
||||||
|
import com.maddyhome.idea.vim.state.mode.isChar
|
||||||
|
import com.maddyhome.idea.vim.state.mode.isLine
|
||||||
import java.awt.datatransfer.DataFlavor
|
import java.awt.datatransfer.DataFlavor
|
||||||
|
|
||||||
internal class PutGroup : VimPutBase() {
|
internal class PutGroup : VimPutBase() {
|
||||||
@@ -189,7 +190,7 @@ internal class PutGroup : VimPutBase() {
|
|||||||
endOffset: Int,
|
endOffset: Int,
|
||||||
): Int {
|
): Int {
|
||||||
// Temp fix for VIM-2808. Should be removed after rider will fix it's issues
|
// Temp fix for VIM-2808. Should be removed after rider will fix it's issues
|
||||||
if (PlatformUtils.isRider()) return endOffset
|
if (PlatformUtils.isRider() || isClionNova()) return endOffset
|
||||||
|
|
||||||
val startLine = editor.offsetToBufferPosition(startOffset).line
|
val startLine = editor.offsetToBufferPosition(startOffset).line
|
||||||
val endLine = editor.offsetToBufferPosition(endOffset - 1).line
|
val endLine = editor.offsetToBufferPosition(endOffset - 1).line
|
||||||
|
@@ -40,9 +40,15 @@ internal object IdeaSelectionControl {
|
|||||||
* This method should be in sync with [predictMode]
|
* This method should be in sync with [predictMode]
|
||||||
*
|
*
|
||||||
* Control unexpected (non vim) selection change and adjust a mode to it. The new mode is not enabled immediately,
|
* Control unexpected (non vim) selection change and adjust a mode to it. The new mode is not enabled immediately,
|
||||||
* but with some delay (using [VimVisualTimer])
|
* but with some delay (using [VimVisualTimer]). The delay is used because some platform functionality
|
||||||
|
* makes features by using selection. E.g. PyCharm unindent firstly select the indenting then applies delete action.
|
||||||
|
* Such "quick" selection breaks IdeaVim behaviour.
|
||||||
*
|
*
|
||||||
* See [VimVisualTimer] to more info.
|
* See [VimVisualTimer] to more info.
|
||||||
|
*
|
||||||
|
* XXX: This method can be split into "change calculation" and "change apply". In this way, we would be able
|
||||||
|
* to calculate if we need to make a change or not and reduce the number of these calls.
|
||||||
|
* If this refactoring ever is applied, please add `assertNull(VimVisualTimer.timer)` to `tearDown` of VimTestCase.
|
||||||
*/
|
*/
|
||||||
fun controlNonVimSelectionChange(
|
fun controlNonVimSelectionChange(
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
@@ -50,6 +56,7 @@ internal object IdeaSelectionControl {
|
|||||||
) {
|
) {
|
||||||
VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
|
VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
|
||||||
|
|
||||||
|
if (VimPlugin.isNotEnabled()) return@singleTask
|
||||||
if (editor.isIdeaVimDisabledHere) return@singleTask
|
if (editor.isIdeaVimDisabledHere) return@singleTask
|
||||||
|
|
||||||
logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
|
logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
|
||||||
@@ -121,8 +128,9 @@ internal object IdeaSelectionControl {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun dontChangeMode(editor: Editor): Boolean =
|
private fun dontChangeMode(editor: Editor): Boolean {
|
||||||
editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
|
return editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
|
||||||
|
}
|
||||||
|
|
||||||
private fun chooseNonSelectionMode(editor: Editor): Mode {
|
private fun chooseNonSelectionMode(editor: Editor): Mode {
|
||||||
val templateActive = editor.isTemplateActive()
|
val templateActive = editor.isTemplateActive()
|
||||||
|
@@ -9,10 +9,10 @@
|
|||||||
package com.maddyhome.idea.vim.group.visual
|
package com.maddyhome.idea.vim.group.visual
|
||||||
|
|
||||||
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.group.visual.VimVisualTimer.mode
|
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
|
||||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
|
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import java.awt.event.ActionEvent
|
import java.awt.event.ActionEvent
|
||||||
import javax.swing.Timer
|
import javax.swing.Timer
|
||||||
|
|
||||||
@@ -79,6 +79,11 @@ internal object VimVisualTimer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun drop() {
|
||||||
|
swingTimer?.stop()
|
||||||
|
swingTimer = null
|
||||||
|
}
|
||||||
|
|
||||||
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
||||||
task(mode)
|
task(mode)
|
||||||
swingTimer = null
|
swingTimer = null
|
||||||
|
@@ -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}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -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 {
|
||||||
|
155
src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt
Normal file
155
src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* 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.components.Service
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
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.ProjectActivity
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.api.key
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.channels.BufferOverflow
|
||||||
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.debounce
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
|
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
|
||||||
|
internal val keyCheckRequests = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
|
||||||
|
*/
|
||||||
|
internal class KeymapChecker : ProjectActivity {
|
||||||
|
override suspend fun execute(project: Project) {
|
||||||
|
project.service<KeymapCheckerService>().start()
|
||||||
|
keyCheckRequests.emit(Unit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* At the moment of release 2023.3 there is a problem that starting a coroutine like this
|
||||||
|
* right in the project activity will block this project activity in tests.
|
||||||
|
* To avoid that, there is an intermediate service that will allow to avoid this issue.
|
||||||
|
*
|
||||||
|
* However, in general we should start this coroutine right in the [KeymapChecker]
|
||||||
|
*/
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
@Service(Service.Level.PROJECT)
|
||||||
|
internal class KeymapCheckerService(private val cs: CoroutineScope) {
|
||||||
|
fun start() {
|
||||||
|
cs.launch {
|
||||||
|
keyCheckRequests
|
||||||
|
.debounce(5_000)
|
||||||
|
.collectLatest { verifyKeymap() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
|
||||||
|
override fun activeKeymapChanged(keymap: Keymap?) {
|
||||||
|
check(keyCheckRequests.tryEmit(Unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
||||||
|
check(keyCheckRequests.tryEmit(Unit))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
||||||
|
check(keyCheckRequests.tryEmit(Unit))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
}
|
@@ -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,12 +116,16 @@ 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
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean {
|
private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean {
|
||||||
if (!VimPlugin.isEnabled()) return false
|
if (VimPlugin.isNotEnabled()) return false
|
||||||
if (!isHandlerEnabled(editor, dataContext)) return false
|
if (!isHandlerEnabled(editor, dataContext)) return false
|
||||||
if (isNotActualKeyPress(dataContext)) return false
|
if (isNotActualKeyPress(dataContext)) return false
|
||||||
return true
|
return 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -202,7 +229,7 @@ internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(n
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Rider uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
|
* Rider (and CLion Nova) uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
|
||||||
* designer to get all the esc presses, and if there is a completion close it and do not pass the execution further.
|
* designer to get all the esc presses, and if there is a completion close it and do not pass the execution further.
|
||||||
* This doesn't work the same as in IJ.
|
* This doesn't work the same as in IJ.
|
||||||
* In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
|
* In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
|
||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
@@ -81,7 +81,7 @@ private fun Editor.guicursorMode(): GuiCursorMode {
|
|||||||
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
|
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
|
||||||
|
|
||||||
private fun Editor.updatePrimaryCaretVisualAttributes() {
|
private fun Editor.updatePrimaryCaretVisualAttributes() {
|
||||||
if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
|
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
|
||||||
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
|
caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
|
||||||
|
|
||||||
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
|
// Make sure the caret is visible as soon as it's set. It might be invisible while blinking
|
||||||
@@ -89,7 +89,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun Editor.updateSecondaryCaretsVisualAttributes() {
|
private fun Editor.updateSecondaryCaretsVisualAttributes() {
|
||||||
if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
|
if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
|
||||||
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
|
// IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
|
||||||
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
|
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
|
||||||
this.caretModel.allCarets.forEach {
|
this.caretModel.allCarets.forEach {
|
||||||
|
@@ -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
|
||||||
|
@@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun vimEnabled(editor: Editor?): Boolean {
|
private fun vimEnabled(editor: Editor?): Boolean {
|
||||||
if (!VimPlugin.isEnabled()) return false
|
if (VimPlugin.isNotEnabled()) return false
|
||||||
if (editor != null && editor.isIdeaVimDisabledHere) return false
|
if (editor != null && editor.isIdeaVimDisabledHere) return false
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
@@ -139,7 +143,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
|||||||
manager.fireAfterActionPerformed(action, event, result!!)
|
manager.fireAfterActionPerformed(action, event, result!!)
|
||||||
}
|
}
|
||||||
if (indexError != null) {
|
if (indexError != null) {
|
||||||
ActionUtil.showDumbModeWarning(project, event)
|
ActionUtil.showDumbModeWarning(project, action, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -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,
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
package com.maddyhome.idea.vim.helper;
|
package com.maddyhome.idea.vim.helper;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
|
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
|
||||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
||||||
import com.intellij.lang.Commenter;
|
import com.intellij.lang.Commenter;
|
||||||
import com.intellij.lang.Language;
|
import com.intellij.lang.Language;
|
||||||
@@ -16,15 +17,15 @@ import com.intellij.lang.LanguageCommenters;
|
|||||||
import com.intellij.openapi.diagnostic.Logger;
|
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.project.Project;
|
||||||
import com.intellij.psi.PsiComment;
|
import com.intellij.psi.PsiComment;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
|
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
||||||
import com.maddyhome.idea.vim.api.VimEditor;
|
import com.maddyhome.idea.vim.api.VimEditor;
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
|
||||||
import com.maddyhome.idea.vim.common.CharacterPosition;
|
import com.maddyhome.idea.vim.common.CharacterPosition;
|
||||||
import com.maddyhome.idea.vim.common.Direction;
|
import com.maddyhome.idea.vim.common.Direction;
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
import com.maddyhome.idea.vim.common.TextRange;
|
||||||
@@ -32,6 +33,12 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret;
|
|||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||||
import com.maddyhome.idea.vim.regexp.CharPointer;
|
import com.maddyhome.idea.vim.regexp.CharPointer;
|
||||||
import com.maddyhome.idea.vim.regexp.RegExp;
|
import com.maddyhome.idea.vim.regexp.RegExp;
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine;
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntComparator;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntIterator;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntSortedSet;
|
||||||
import kotlin.Pair;
|
import kotlin.Pair;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
@@ -1523,6 +1530,42 @@ public class SearchHelper {
|
|||||||
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
|
return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int findMisspelledWords(@NotNull Editor editor,
|
||||||
|
int startOffset,
|
||||||
|
int endOffset,
|
||||||
|
int skipCount,
|
||||||
|
IntComparator offsetOrdering) {
|
||||||
|
Project project = editor.getProject();
|
||||||
|
if (project == null) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
|
||||||
|
DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
|
||||||
|
startOffset, endOffset, highlight -> {
|
||||||
|
if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
|
||||||
|
int offset = highlight.getStartOffset();
|
||||||
|
if (offset >= startOffset && offset <= endOffset) {
|
||||||
|
offsets.add(offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (offsets.isEmpty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skipCount >= offsets.size()) {
|
||||||
|
return offsets.lastInt();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
IntIterator offsetIterator = offsets.iterator();
|
||||||
|
offsetIterator.skip(skipCount);
|
||||||
|
return offsetIterator.nextInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
|
private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
|
||||||
List<String> pairs = options(injector, vimEditor).getMatchpairs();
|
List<String> pairs = options(injector, vimEditor).getMatchpairs();
|
||||||
StringBuilder res = new StringBuilder();
|
StringBuilder res = new StringBuilder();
|
||||||
|
@@ -18,6 +18,7 @@ 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
|
||||||
@@ -44,22 +45,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
restoreVisualMode(editor)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,12 +84,59 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
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) {
|
private fun restoreVisualMode(editor: VimEditor) {
|
||||||
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
|
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
|
||||||
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
|
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
|
||||||
|
@@ -124,10 +124,6 @@ internal var Editor.vimMorePanel: ExOutputPanel? by userData()
|
|||||||
internal var Editor.vimExOutput: ExOutputModel? by userData()
|
internal var Editor.vimExOutput: ExOutputModel? by userData()
|
||||||
internal var Editor.vimTestInputModel: TestInputModel? by userData()
|
internal var Editor.vimTestInputModel: TestInputModel? by userData()
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks whether a keeping visual mode visual operator action is performed on editor.
|
|
||||||
*/
|
|
||||||
internal var Editor.vimKeepingVisualOperatorAction: Boolean by userDataOr { false }
|
|
||||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
|
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 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.ide
|
||||||
|
|
||||||
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
|
|
||||||
|
internal val clionEP = ExtensionPointName.create<ClionNovaProvider>("IdeaVIM.clionNovaProvider")
|
||||||
|
|
||||||
|
internal interface ClionNovaProvider {
|
||||||
|
fun isClionNova(): Boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ClionNovaProviderImpl : ClionNovaProvider {
|
||||||
|
override fun isClionNova(): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun isClionNova(): Boolean {
|
||||||
|
return clionEP.extensions.any { it.isClionNova() }
|
||||||
|
}
|
@@ -40,7 +40,7 @@ internal object AppCodeTemplates {
|
|||||||
private var editor: Editor? = null
|
private var editor: Editor? = null
|
||||||
|
|
||||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||||
if (hostEditor != null) {
|
if (hostEditor != null) {
|
||||||
@@ -49,7 +49,7 @@ internal object AppCodeTemplates {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
if (ActionManager.getInstance().getId(action) == IdeActions.ACTION_CHOOSE_LOOKUP_ITEM) {
|
if (ActionManager.getInstance().getId(action) == IdeActions.ACTION_CHOOSE_LOOKUP_ITEM) {
|
||||||
val myEditor = editor
|
val myEditor = editor
|
||||||
|
@@ -56,14 +56,16 @@ internal object IdeaSpecifics {
|
|||||||
private val surrounderAction =
|
private val surrounderAction =
|
||||||
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
||||||
private var editor: Editor? = null
|
private var editor: Editor? = null
|
||||||
|
private var caretOffset = -1
|
||||||
private var completionPrevDocumentLength: Int? = null
|
private var completionPrevDocumentLength: Int? = null
|
||||||
private var completionPrevDocumentOffset: Int? = null
|
private var completionPrevDocumentOffset: Int? = null
|
||||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||||
if (hostEditor != null) {
|
if (hostEditor != null) {
|
||||||
editor = hostEditor
|
editor = hostEditor
|
||||||
|
caretOffset = hostEditor.caretModel.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
||||||
@@ -92,53 +94,58 @@ internal object IdeaSpecifics {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
val editor = editor
|
val editor = editor
|
||||||
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
if (editor != null) {
|
||||||
val prevDocumentLength = completionPrevDocumentLength
|
if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
||||||
val prevDocumentOffset = completionPrevDocumentOffset
|
val prevDocumentLength = completionPrevDocumentLength
|
||||||
|
val prevDocumentOffset = completionPrevDocumentOffset
|
||||||
|
|
||||||
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
||||||
val register = VimPlugin.getRegister()
|
val register = VimPlugin.getRegister()
|
||||||
val addedTextLength = editor.document.textLength - prevDocumentLength
|
val addedTextLength = editor.document.textLength - prevDocumentLength
|
||||||
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
||||||
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
||||||
|
|
||||||
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
||||||
repeat(caretShift.coerceAtLeast(0)) {
|
repeat(caretShift.coerceAtLeast(0)) {
|
||||||
register.recordKeyStroke(leftArrow)
|
register.recordKeyStroke(leftArrow)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.completionPrevDocumentLength = null
|
||||||
|
this.completionPrevDocumentOffset = null
|
||||||
}
|
}
|
||||||
|
|
||||||
this.completionPrevDocumentLength = null
|
//region Enter insert mode after surround with if
|
||||||
this.completionPrevDocumentOffset = null
|
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
||||||
}
|
action.templatePresentation.text.endsWith(
|
||||||
|
it,
|
||||||
//region Enter insert mode after surround with if
|
)
|
||||||
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
}
|
||||||
action.templatePresentation.text.endsWith(
|
) {
|
||||||
it,
|
val commandState = editor.vim.vimStateMachine
|
||||||
)
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
editor?.let {
|
|
||||||
val commandState = it.vim.vimStateMachine
|
|
||||||
commandState.mode = Mode.NORMAL()
|
commandState.mode = Mode.NORMAL()
|
||||||
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
|
||||||
KeyHandler.getInstance().reset(it.vim)
|
KeyHandler.getInstance().reset(editor.vim)
|
||||||
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
|
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
||||||
|
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//endregion
|
|
||||||
|
|
||||||
this.editor = null
|
this.editor = null
|
||||||
|
this.caretOffset = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//region Enter insert mode for surround templates without selection
|
//region Enter insert mode for surround templates without selection
|
||||||
class VimTemplateManagerListener : TemplateManagerListener {
|
class VimTemplateManagerListener : TemplateManagerListener {
|
||||||
override fun templateStarted(state: TemplateState) {
|
override fun templateStarted(state: TemplateState) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
val editor = state.editor ?: return
|
val editor = state.editor ?: return
|
||||||
|
|
||||||
state.addTemplateStateListener(object : TemplateEditingAdapter() {
|
state.addTemplateStateListener(object : TemplateEditingAdapter() {
|
||||||
@@ -176,7 +183,7 @@ internal object IdeaSpecifics {
|
|||||||
//region Register shortcuts for lookup and perform partial reset
|
//region Register shortcuts for lookup and perform partial reset
|
||||||
class LookupTopicListener : LookupManagerListener {
|
class LookupTopicListener : LookupManagerListener {
|
||||||
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
|
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
// Lookup opened
|
// Lookup opened
|
||||||
if (oldLookup == null && newLookup is LookupImpl) {
|
if (oldLookup == null && newLookup is LookupImpl) {
|
||||||
@@ -199,7 +206,7 @@ internal object IdeaSpecifics {
|
|||||||
//region Hide Vim search highlights when showing IntelliJ search results
|
//region Hide Vim search highlights when showing IntelliJ search results
|
||||||
class VimFindModelListener : FindModelListener {
|
class VimFindModelListener : FindModelListener {
|
||||||
override fun findNextModelChanged() {
|
override fun findNextModelChanged() {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
VimPlugin.getSearch().clearSearchHighlight()
|
VimPlugin.getSearch().clearSearchHighlight()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -27,7 +27,7 @@ internal class RiderActionListener : AnActionListener {
|
|||||||
|
|
||||||
private var editor: Editor? = null
|
private var editor: Editor? = null
|
||||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||||
if (hostEditor != null) {
|
if (hostEditor != null) {
|
||||||
@@ -36,7 +36,7 @@ internal class RiderActionListener : AnActionListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
//region Extend Selection for Rider
|
//region Extend Selection for Rider
|
||||||
when (ActionManager.getInstance().getId(action)) {
|
when (ActionManager.getInstance().getId(action)) {
|
||||||
|
@@ -9,6 +9,7 @@
|
|||||||
package com.maddyhome.idea.vim.listener
|
package com.maddyhome.idea.vim.listener
|
||||||
|
|
||||||
import com.intellij.ide.ui.UISettings
|
import com.intellij.ide.ui.UISettings
|
||||||
|
import com.intellij.openapi.Disposable
|
||||||
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.diagnostic.trace
|
import com.intellij.openapi.diagnostic.trace
|
||||||
@@ -28,8 +29,9 @@ import com.intellij.openapi.editor.event.EditorMouseMotionListener
|
|||||||
import com.intellij.openapi.editor.event.SelectionEvent
|
import com.intellij.openapi.editor.event.SelectionEvent
|
||||||
import com.intellij.openapi.editor.event.SelectionListener
|
import com.intellij.openapi.editor.event.SelectionListener
|
||||||
import com.intellij.openapi.editor.ex.DocumentEx
|
import com.intellij.openapi.editor.ex.DocumentEx
|
||||||
|
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
|
||||||
|
import com.intellij.openapi.editor.ex.FocusChangeListener
|
||||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
|
import com.intellij.openapi.editor.impl.EditorComponentImpl
|
||||||
import com.intellij.openapi.editor.impl.EditorImpl
|
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||||
@@ -40,14 +42,11 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
|
|||||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
|
import com.intellij.openapi.fileEditor.impl.EditorComposite
|
||||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
|
import com.intellij.openapi.fileEditor.impl.EditorWindow
|
||||||
import com.intellij.openapi.project.ProjectManager
|
import com.intellij.openapi.project.ProjectManager
|
||||||
import com.intellij.openapi.rd.createLifetime
|
|
||||||
import com.intellij.openapi.rd.createNestedDisposable
|
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.util.removeUserData
|
import com.intellij.openapi.util.removeUserData
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.util.ExceptionUtil
|
import com.intellij.util.ExceptionUtil
|
||||||
import com.jetbrains.rd.util.lifetime.Lifetime
|
|
||||||
import com.maddyhome.idea.vim.EventFacade
|
import com.maddyhome.idea.vim.EventFacade
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimKeyListener
|
import com.maddyhome.idea.vim.VimKeyListener
|
||||||
@@ -62,6 +61,7 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.group.EditorGroup
|
import com.maddyhome.idea.vim.group.EditorGroup
|
||||||
import com.maddyhome.idea.vim.group.FileGroup
|
import com.maddyhome.idea.vim.group.FileGroup
|
||||||
|
import com.maddyhome.idea.vim.group.IjOptions
|
||||||
import com.maddyhome.idea.vim.group.MotionGroup
|
import com.maddyhome.idea.vim.group.MotionGroup
|
||||||
import com.maddyhome.idea.vim.group.OptionGroup
|
import com.maddyhome.idea.vim.group.OptionGroup
|
||||||
import com.maddyhome.idea.vim.group.ScrollGroup
|
import com.maddyhome.idea.vim.group.ScrollGroup
|
||||||
@@ -70,6 +70,8 @@ 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.keyCheckRequests
|
||||||
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.exitSelectMode
|
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||||
@@ -95,6 +97,9 @@ 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.ui.ShowCmdOptionChangeListener
|
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
|
||||||
|
import com.maddyhome.idea.vim.vimDisposable
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
@@ -129,11 +134,14 @@ internal object VimListenerManager {
|
|||||||
fun turnOn() {
|
fun turnOn() {
|
||||||
GlobalListeners.enable()
|
GlobalListeners.enable()
|
||||||
EditorListeners.addAll()
|
EditorListeners.addAll()
|
||||||
|
correctorRequester.request()
|
||||||
|
check(keyCheckRequests.tryEmit(Unit))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun turnOff() {
|
fun turnOff() {
|
||||||
GlobalListeners.disable()
|
GlobalListeners.disable()
|
||||||
EditorListeners.removeAll()
|
EditorListeners.removeAll()
|
||||||
|
correctorRequester.request()
|
||||||
}
|
}
|
||||||
|
|
||||||
object GlobalListeners {
|
object GlobalListeners {
|
||||||
@@ -151,6 +159,13 @@ internal object VimListenerManager {
|
|||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||||
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||||
|
|
||||||
|
// This code is executed after ideavimrc execution, so we trigger onGlobalOptionChanged just in case
|
||||||
|
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
|
||||||
|
optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
|
||||||
|
modeWidgetOptionListener.onGlobalOptionChanged()
|
||||||
|
macroWidgetOptionListener.onGlobalOptionChanged()
|
||||||
|
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||||
|
|
||||||
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
|
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
|
||||||
@@ -158,6 +173,8 @@ internal object VimListenerManager {
|
|||||||
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
|
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
|
||||||
|
|
||||||
EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
|
EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
|
||||||
|
val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
|
||||||
|
eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun disable() {
|
fun disable() {
|
||||||
@@ -168,6 +185,8 @@ internal object VimListenerManager {
|
|||||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||||
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||||
|
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
|
||||||
|
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
|
||||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -210,49 +229,67 @@ internal object VimListenerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
|
fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
|
||||||
val pluginLifetime = VimPlugin.getInstance().createLifetime()
|
// As I understand, there is no need to pass a disposable that also disposes on editor close
|
||||||
val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
|
// because all editor resources will be garbage collected anyway on editor close
|
||||||
val disposable =
|
val disposable = editor.project?.vimDisposable ?: return
|
||||||
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
|
|
||||||
|
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||||
|
editor.putUserData(editorListenersDisposable, listenersDisposable)
|
||||||
|
|
||||||
|
Disposer.register(listenersDisposable) {
|
||||||
|
if (VimListenerTestObject.enabled) {
|
||||||
|
VimListenerTestObject.disposedCounter += 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
editor.contentComponent.addKeyListener(VimKeyListener)
|
editor.contentComponent.addKeyListener(VimKeyListener)
|
||||||
Disposer.register(disposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
|
Disposer.register(listenersDisposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
|
||||||
|
|
||||||
// Initialise the local options. We MUST do this before anything has the chance to query options
|
// Initialise the local options. We MUST do this before anything has the chance to query options
|
||||||
VimPlugin.getOptionGroup().initialiseLocalOptions(editor.vim, openingEditor, scenario)
|
val vimEditor = editor.vim
|
||||||
|
VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
|
||||||
|
|
||||||
val eventFacade = EventFacade.getInstance()
|
val eventFacade = EventFacade.getInstance()
|
||||||
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, disposable)
|
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, listenersDisposable)
|
||||||
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, disposable)
|
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, listenersDisposable)
|
||||||
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, disposable)
|
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, listenersDisposable)
|
||||||
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, disposable)
|
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, listenersDisposable)
|
||||||
eventFacade.addCaretListener(editor, EditorCaretHandler, disposable)
|
eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
|
||||||
|
|
||||||
VimPlugin.getEditor().editorCreated(editor)
|
VimPlugin.getEditor().editorCreated(editor)
|
||||||
|
|
||||||
VimPlugin.getChange().editorCreated(editor, disposable)
|
VimPlugin.getChange().editorCreated(editor, listenersDisposable)
|
||||||
|
|
||||||
Disposer.register(disposable) {
|
injector.listenersNotifier.notifyEditorCreated(vimEditor)
|
||||||
|
|
||||||
|
Disposer.register(listenersDisposable) {
|
||||||
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
|
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun remove(editor: Editor, isReleased: Boolean) {
|
fun remove(editor: Editor, isReleased: Boolean) {
|
||||||
editor.contentComponent.removeKeyListener(VimKeyListener)
|
val editorDisposable = editor.getUserData(editorListenersDisposable)
|
||||||
val eventFacade = EventFacade.getInstance()
|
if (editorDisposable != null) {
|
||||||
eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
|
Disposer.dispose(editorDisposable)
|
||||||
eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)
|
}
|
||||||
eventFacade.removeEditorSelectionListener(editor, EditorSelectionHandler)
|
else StrictMode.fail("Editor doesn't have disposable attached. $editor")
|
||||||
eventFacade.removeComponentMouseListener(editor.contentComponent, ComponentMouseListener)
|
|
||||||
eventFacade.removeCaretListener(editor, EditorCaretHandler)
|
|
||||||
|
|
||||||
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
|
VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
VimPlugin.getChange().editorReleased(editor)
|
private object VimFocusListener : FocusChangeListener {
|
||||||
|
override fun focusGained(editor: Editor) {
|
||||||
|
injector.listenersNotifier.notifyEditorFocusGained(editor.vim)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun focusLost(editor: Editor) {
|
||||||
|
injector.listenersNotifier.notifyEditorFocusLost(editor.vim)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val editorListenersDisposable = Key.create<Disposable>("IdeaVim listeners disposable")
|
||||||
|
|
||||||
object VimCaretListener : CaretListener {
|
object VimCaretListener : CaretListener {
|
||||||
override fun caretAdded(event: CaretEvent) {
|
override fun caretAdded(event: CaretEvent) {
|
||||||
if (vimDisabled(event.editor)) return
|
if (vimDisabled(event.editor)) return
|
||||||
@@ -267,7 +304,7 @@ 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.isNotEnabled()) return
|
||||||
|
|
||||||
val newEditor = event.newEditor
|
val newEditor = event.newEditor
|
||||||
if (newEditor is TextEditor) {
|
if (newEditor is TextEditor) {
|
||||||
@@ -345,13 +382,15 @@ internal object VimListenerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun editorReleased(event: EditorFactoryEvent) {
|
override fun editorReleased(event: EditorFactoryEvent) {
|
||||||
injector.markService.editorReleased(event.editor.vim)
|
val vimEditor = event.editor.vim
|
||||||
|
injector.listenersNotifier.notifyEditorReleased(vimEditor)
|
||||||
|
injector.markService.editorReleased(vimEditor)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fileOpenedSync(
|
override fun fileOpenedSync(
|
||||||
source: FileEditorManager,
|
source: FileEditorManager,
|
||||||
file: VirtualFile,
|
file: VirtualFile,
|
||||||
editorsWithProviders: List<FileEditorWithProvider>
|
editorsWithProviders: List<FileEditorWithProvider>,
|
||||||
) {
|
) {
|
||||||
// This callback is called once all editors are created for a file being opened. The EditorComposite has been
|
// This callback is called once all editors are created for a file being opened. The EditorComposite has been
|
||||||
// created (and the list of editors and providers is passed here) and added to an EditorWindow tab, inside a
|
// created (and the list of editors and providers is passed here) and added to an EditorWindow tab, inside a
|
||||||
@@ -409,6 +448,7 @@ internal object VimListenerManager {
|
|||||||
*/
|
*/
|
||||||
override fun selectionChanged(selectionEvent: SelectionEvent) {
|
override fun selectionChanged(selectionEvent: SelectionEvent) {
|
||||||
if (selectionEvent.editor.isIdeaVimDisabledHere) return
|
if (selectionEvent.editor.isIdeaVimDisabledHere) return
|
||||||
|
VimVisualTimer.drop()
|
||||||
val editor = selectionEvent.editor
|
val editor = selectionEvent.editor
|
||||||
val document = editor.document
|
val document = editor.document
|
||||||
val ijVimEditor = IjVimEditor(editor)
|
val ijVimEditor = IjVimEditor(editor)
|
||||||
@@ -702,6 +742,11 @@ internal object VimListenerManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal object VimListenerTestObject {
|
||||||
|
var enabled: Boolean = false
|
||||||
|
var disposedCounter = 0
|
||||||
|
}
|
||||||
|
|
||||||
private object MouseEventsDataHolder {
|
private object MouseEventsDataHolder {
|
||||||
const val skipEvents = 3
|
const val skipEvents = 3
|
||||||
var skipNDragEvents = skipEvents
|
var skipNDragEvents = skipEvents
|
||||||
|
@@ -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
|
||||||
|
@@ -54,7 +54,6 @@ import com.maddyhome.idea.vim.helper.isTemplateActive
|
|||||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
|
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
|
||||||
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
|
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
|
||||||
import com.maddyhome.idea.vim.helper.vimKeepingVisualOperatorAction
|
|
||||||
import com.maddyhome.idea.vim.helper.vimLastSelectionType
|
import com.maddyhome.idea.vim.helper.vimLastSelectionType
|
||||||
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
|
||||||
@@ -82,11 +81,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
|||||||
set(value) {
|
set(value) {
|
||||||
editor.vimChangeActionSwitchMode = value
|
editor.vimChangeActionSwitchMode = value
|
||||||
}
|
}
|
||||||
override var vimKeepingVisualOperatorAction: Boolean
|
|
||||||
get() = editor.vimKeepingVisualOperatorAction
|
|
||||||
set(value) {
|
|
||||||
editor.vimKeepingVisualOperatorAction = value
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fileSize(): Long = editor.fileSize.toLong()
|
override fun fileSize(): Long = editor.fileSize.toLong()
|
||||||
|
|
||||||
|
@@ -15,6 +15,8 @@ import com.maddyhome.idea.vim.api.VimSearchHelperBase
|
|||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.helper.SearchHelper
|
import com.maddyhome.idea.vim.helper.SearchHelper
|
||||||
import com.maddyhome.idea.vim.helper.SearchOptions
|
import com.maddyhome.idea.vim.helper.SearchOptions
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntComparator
|
||||||
|
import it.unimi.dsi.fastutil.ints.IntComparators
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -93,4 +95,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
|
|||||||
): TextRange? {
|
): TextRange? {
|
||||||
return SearchHelper.findBlockRange(editor.ij, caret.ij, type, count, isOuter)
|
return SearchHelper.findBlockRange(editor.ij, caret.ij, type, count, isOuter)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
|
||||||
|
val startOffset: Int
|
||||||
|
val endOffset: Int
|
||||||
|
val skipCount: Int
|
||||||
|
val offsetOrdering: IntComparator
|
||||||
|
|
||||||
|
if (count < 0) {
|
||||||
|
startOffset = 0
|
||||||
|
endOffset = caret.offset.point - 1
|
||||||
|
skipCount = -count - 1
|
||||||
|
offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
startOffset = caret.offset.point + 1
|
||||||
|
endOffset = editor.ij.document.textLength
|
||||||
|
skipCount = count - 1
|
||||||
|
offsetOrdering = IntComparators.NATURAL_COMPARATOR
|
||||||
|
}
|
||||||
|
|
||||||
|
return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -333,7 +333,7 @@
|
|||||||
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
|
* |[m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
|
||||||
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
||||||
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
* |[p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
||||||
* |[s| TO BE IMPLEMENTED
|
* |[s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction}
|
||||||
* |[z| TO BE IMPLEMENTED
|
* |[z| TO BE IMPLEMENTED
|
||||||
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
|
* |[{| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
|
||||||
* |]_CTRL-D| TO BE IMPLEMENTED
|
* |]_CTRL-D| TO BE IMPLEMENTED
|
||||||
@@ -358,7 +358,7 @@
|
|||||||
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
|
* |]m| {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
|
||||||
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
|
||||||
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
* |]p| {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
|
||||||
* |]s| TO BE IMPLEMENTED
|
* |]s| {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
|
||||||
* |]z| TO BE IMPLEMENTED
|
* |]z| TO BE IMPLEMENTED
|
||||||
* |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
|
* |]}| {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
|
||||||
*
|
*
|
||||||
|
@@ -18,6 +18,7 @@ import com.intellij.util.IJSwingUtilities;
|
|||||||
import com.maddyhome.idea.vim.KeyHandler;
|
import com.maddyhome.idea.vim.KeyHandler;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext;
|
import com.maddyhome.idea.vim.api.ExecutionContext;
|
||||||
|
import com.maddyhome.idea.vim.diagnostic.VimLogger;
|
||||||
import com.maddyhome.idea.vim.helper.HelperKt;
|
import com.maddyhome.idea.vim.helper.HelperKt;
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper;
|
import com.maddyhome.idea.vim.helper.MessageHelper;
|
||||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||||
@@ -59,6 +60,8 @@ public class ExOutputPanel extends JPanel {
|
|||||||
|
|
||||||
private boolean myActive = false;
|
private boolean myActive = false;
|
||||||
|
|
||||||
|
private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
|
||||||
|
|
||||||
private ExOutputPanel(@NotNull Editor editor) {
|
private ExOutputPanel(@NotNull Editor editor) {
|
||||||
myEditor = editor;
|
myEditor = editor;
|
||||||
|
|
||||||
@@ -299,6 +302,10 @@ public class ExOutputPanel extends JPanel {
|
|||||||
final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
|
final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
|
||||||
final List<KeyStroke> keys = new ArrayList<>(1);
|
final List<KeyStroke> keys = new ArrayList<>(1);
|
||||||
keys.add(key);
|
keys.add(key);
|
||||||
|
if (LOG.isTrace()) {
|
||||||
|
LOG.trace("Adding new keys to keyStack as part of playback. State before adding keys: " +
|
||||||
|
KeyHandler.getInstance().getKeyStack().dump());
|
||||||
|
}
|
||||||
KeyHandler.getInstance().getKeyStack().addKeys(keys);
|
KeyHandler.getInstance().getKeyStack().addKeys(keys);
|
||||||
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
|
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
|
||||||
VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
|
VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
|
||||||
@@ -358,7 +365,7 @@ public class ExOutputPanel extends JPanel {
|
|||||||
public static class LafListener implements LafManagerListener {
|
public static class LafListener implements LafManagerListener {
|
||||||
@Override
|
@Override
|
||||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||||
if (!VimPlugin.isEnabled()) return;
|
if (VimPlugin.isNotEnabled()) return;
|
||||||
// Calls updateUI on this and child components
|
// Calls updateUI on this and child components
|
||||||
for (Editor editor : HelperKt.localEditors()) {
|
for (Editor editor : HelperKt.localEditors()) {
|
||||||
if (!ExOutputPanel.isPanelActive(editor)) continue;
|
if (!ExOutputPanel.isPanelActive(editor)) continue;
|
||||||
|
@@ -8,6 +8,9 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.ui
|
package com.maddyhome.idea.vim.ui
|
||||||
|
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
|
import com.intellij.openapi.diagnostic.trace
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||||
@@ -22,13 +25,19 @@ import javax.swing.KeyStroke
|
|||||||
* @author dhleong
|
* @author dhleong
|
||||||
*/
|
*/
|
||||||
public object ModalEntry {
|
public object ModalEntry {
|
||||||
|
|
||||||
|
public val LOG: Logger = logger<ModalEntry>()
|
||||||
|
|
||||||
public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
|
public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
|
||||||
// Firstly we pull the unfinished keys of the current mapping
|
// Firstly we pull the unfinished keys of the current mapping
|
||||||
val mappingStack = KeyHandler.getInstance().keyStack
|
val mappingStack = KeyHandler.getInstance().keyStack
|
||||||
|
LOG.trace("Dumping key stack:")
|
||||||
|
LOG.trace { mappingStack.dump() }
|
||||||
var stroke = mappingStack.feedSomeStroke()
|
var stroke = mappingStack.feedSomeStroke()
|
||||||
while (stroke != null) {
|
while (stroke != null) {
|
||||||
val result = processor(stroke)
|
val result = processor(stroke)
|
||||||
if (!result) {
|
if (!result) {
|
||||||
|
LOG.trace("Got char from mapping stack")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stroke = mappingStack.feedSomeStroke()
|
stroke = mappingStack.feedSomeStroke()
|
||||||
@@ -55,6 +64,7 @@ public object ModalEntry {
|
|||||||
KeyHandler.getInstance().modalEntryKeys += stroke
|
KeyHandler.getInstance().modalEntryKeys += stroke
|
||||||
}
|
}
|
||||||
if (!processor(stroke)) {
|
if (!processor(stroke)) {
|
||||||
|
LOG.trace("Got char from keyboard input: $stroke. Event: $e")
|
||||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this)
|
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this)
|
||||||
loop.exit()
|
loop.exit()
|
||||||
}
|
}
|
||||||
|
@@ -19,7 +19,6 @@ import com.intellij.openapi.wm.StatusBarWidget
|
|||||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
||||||
import com.intellij.openapi.wm.WindowManager
|
import com.intellij.openapi.wm.WindowManager
|
||||||
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
|
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
|
||||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
|
|
||||||
import com.intellij.util.Consumer
|
import com.intellij.util.Consumer
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
@@ -68,13 +67,6 @@ internal object ShowCmd {
|
|||||||
internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
|
internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
|
||||||
override fun onGlobalOptionChanged() {
|
override fun onGlobalOptionChanged() {
|
||||||
ShowCmd.update()
|
ShowCmd.update()
|
||||||
|
|
||||||
val extension = StatusBarWidgetFactory.EP_NAME.findExtension(ShowCmdStatusBarWidgetFactory::class.java) ?: return
|
|
||||||
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
|
|
||||||
for (project in projectManager.openProjects) {
|
|
||||||
val statusBarWidgetsManager = project.getService(StatusBarWidgetsManager::class.java) ?: continue
|
|
||||||
statusBarWidgetsManager.updateWidget(extension)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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) {
|
||||||
|
@@ -453,7 +453,7 @@ public class ExEntryPanel extends JPanel {
|
|||||||
public static class LafListener implements LafManagerListener {
|
public static class LafListener implements LafManagerListener {
|
||||||
@Override
|
@Override
|
||||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||||
if (!VimPlugin.isEnabled()) return;
|
if (VimPlugin.isNotEnabled()) return;
|
||||||
// Calls updateUI on this and child components
|
// Calls updateUI on this and child components
|
||||||
if (ExEntryPanel.isInstanceWithShortcutsActive()) {
|
if (ExEntryPanel.isInstanceWithShortcutsActive()) {
|
||||||
IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstance());
|
IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstance());
|
||||||
|
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 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.ui.widgets
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.common.VimPluginListener
|
||||||
|
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
||||||
|
|
||||||
|
public class VimWidgetListener(private val updateWidget: Runnable) : GlobalOptionChangeListener, VimPluginListener {
|
||||||
|
init {
|
||||||
|
injector.listenersNotifier.vimPluginListeners.add(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onGlobalOptionChanged() {
|
||||||
|
updateWidget.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun turnedOn() {
|
||||||
|
updateWidget.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun turnedOff() {
|
||||||
|
updateWidget.run()
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.widgets.macro
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.project.ProjectManager
|
||||||
|
import com.intellij.openapi.wm.StatusBarWidget
|
||||||
|
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
||||||
|
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.common.MacroRecordingListener
|
||||||
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.mode.VimStatusBarWidget
|
||||||
|
import java.awt.Component
|
||||||
|
|
||||||
|
private const val ID = "IdeaVim::Macro"
|
||||||
|
|
||||||
|
internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget {
|
||||||
|
private var content: String = ""
|
||||||
|
|
||||||
|
private val macroRecordingListener = object : MacroRecordingListener {
|
||||||
|
override fun recordingStarted(editor: VimEditor, register: Char) {
|
||||||
|
content = "recording @$register"
|
||||||
|
updateWidgetInStatusBar(ID, editor.ij.project)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun recordingFinished(editor: VimEditor, register: Char) {
|
||||||
|
content = ""
|
||||||
|
updateWidgetInStatusBar(ID, editor.ij.project)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getId(): String {
|
||||||
|
return ID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayName(): String {
|
||||||
|
return "IdeaVim Macro Recording Widget"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createWidget(project: Project): StatusBarWidget {
|
||||||
|
injector.listenersNotifier.macroRecordingListeners.add(macroRecordingListener)
|
||||||
|
return VimMacroWidget()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isAvailable(project: Project): Boolean {
|
||||||
|
return VimPlugin.isEnabled() && injector.globalIjOptions().showmodewidget
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VimMacroWidget : StatusBarWidget {
|
||||||
|
override fun ID(): String {
|
||||||
|
return ID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPresentation(): StatusBarWidget.WidgetPresentation {
|
||||||
|
return VimModeWidgetPresentation()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class VimModeWidgetPresentation : StatusBarWidget.TextPresentation {
|
||||||
|
override fun getAlignment(): Float = Component.CENTER_ALIGNMENT
|
||||||
|
|
||||||
|
override fun getText(): String {
|
||||||
|
return content
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getTooltipText(): String {
|
||||||
|
return content.ifEmpty {
|
||||||
|
"No macro recording in progress"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun updateMacroWidget() {
|
||||||
|
val factory = StatusBarWidgetFactory.EP_NAME.findExtension(MacroWidgetFactory::class.java) ?: return
|
||||||
|
for (project in ProjectManager.getInstance().openProjects) {
|
||||||
|
val statusBarWidgetsManager = project.service<StatusBarWidgetsManager>()
|
||||||
|
statusBarWidgetsManager.updateWidget(factory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public val macroWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateMacroWidget() }
|
@@ -0,0 +1,45 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.widgets.mode
|
||||||
|
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.wm.StatusBarWidget
|
||||||
|
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
|
||||||
|
|
||||||
|
public class ModeWidgetFactory : StatusBarWidgetFactory {
|
||||||
|
public companion object {
|
||||||
|
public const val ID: String = "IdeaVim::Mode"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getId(): String {
|
||||||
|
return ID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getDisplayName(): String {
|
||||||
|
return "IdeaVim Mode Widget"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createWidget(project: Project): StatusBarWidget {
|
||||||
|
return VimModeWidget(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isAvailable(project: Project): Boolean {
|
||||||
|
return VimPlugin.isEnabled()
|
||||||
|
&& injector.globalIjOptions().showmodewidget
|
||||||
|
&& !project.isDisposed
|
||||||
|
&& FileEditorManager.getInstance(project).hasOpenFiles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public val modeWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateModeWidget() }
|
@@ -0,0 +1,368 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 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.ui.widgets.mode
|
||||||
|
|
||||||
|
import com.intellij.ide.ui.LafManager
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
|
import com.intellij.openapi.ui.DialogPanel
|
||||||
|
import com.intellij.openapi.ui.popup.JBPopup
|
||||||
|
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||||
|
import com.intellij.ui.components.JBCheckBox
|
||||||
|
import com.intellij.ui.components.JBScrollPane
|
||||||
|
import com.intellij.ui.components.JBTabbedPane
|
||||||
|
import com.intellij.ui.content.ContentFactory
|
||||||
|
import com.intellij.ui.dsl.builder.Cell
|
||||||
|
import com.intellij.ui.dsl.builder.RowLayout
|
||||||
|
import com.intellij.ui.dsl.builder.TopGap
|
||||||
|
import com.intellij.ui.dsl.builder.bindItem
|
||||||
|
import com.intellij.ui.dsl.builder.bindSelected
|
||||||
|
import com.intellij.ui.dsl.builder.bindText
|
||||||
|
import com.intellij.ui.dsl.builder.panel
|
||||||
|
import com.intellij.ui.dsl.builder.selected
|
||||||
|
import com.intellij.ui.dsl.builder.toNullableProperty
|
||||||
|
import com.intellij.ui.layout.not
|
||||||
|
import com.intellij.util.Alarm
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.asVimInt
|
||||||
|
import java.awt.BorderLayout
|
||||||
|
import java.awt.Dimension
|
||||||
|
import java.awt.FlowLayout
|
||||||
|
import javax.swing.BorderFactory
|
||||||
|
import javax.swing.JButton
|
||||||
|
import javax.swing.JComponent
|
||||||
|
import javax.swing.JLabel
|
||||||
|
import javax.swing.JPanel
|
||||||
|
import kotlin.properties.ReadWriteProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
|
public class ModeWidgetPopup : AnAction() {
|
||||||
|
public override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
val project = e.project ?: return
|
||||||
|
val popup = createPopup() ?: return
|
||||||
|
popup.showCenteredInCurrentWindow(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
@Volatile
|
||||||
|
private var currentPopup: JBPopup? = null
|
||||||
|
|
||||||
|
public fun createPopup(): JBPopup? {
|
||||||
|
synchronized(this) {
|
||||||
|
if (currentPopup?.isDisposed == false) return null
|
||||||
|
val mainPanel = JPanel(BorderLayout())
|
||||||
|
val buttonPanel = JPanel(FlowLayout(FlowLayout.RIGHT))
|
||||||
|
|
||||||
|
val applyButton = JButton("Apply").apply { isEnabled = false }
|
||||||
|
val cancelButton = JButton("Close")
|
||||||
|
buttonPanel.add(applyButton)
|
||||||
|
buttonPanel.add(cancelButton)
|
||||||
|
mainPanel.add(buttonPanel, BorderLayout.SOUTH)
|
||||||
|
|
||||||
|
val tabbedPane = JBTabbedPane()
|
||||||
|
val lightThemeSettings = createPanel(getWidgetThemeColors(true))
|
||||||
|
val darkThemeSettings = createPanel(getWidgetThemeColors(false))
|
||||||
|
tabbedPane.addTab(MessageHelper.getMessage("widget.mode.popup.tab.light"), lightThemeSettings.addScrollPane())
|
||||||
|
tabbedPane.addTab(MessageHelper.getMessage("widget.mode.popup.tab.dark"), darkThemeSettings.addScrollPane())
|
||||||
|
tabbedPane.preferredSize = Dimension(300, 300)
|
||||||
|
for (i in 0 until tabbedPane.tabCount) {
|
||||||
|
val label = JLabel(tabbedPane.getTitleAt(i), JLabel.CENTER)
|
||||||
|
label.preferredSize = Dimension(126, tabbedPane.getTabComponentAt(i).preferredSize.height)
|
||||||
|
tabbedPane.setTabComponentAt(i, label)
|
||||||
|
}
|
||||||
|
tabbedPane.selectedIndex = if (LafManager.getInstance().currentUIThemeLookAndFeel.isDark) 1 else 0
|
||||||
|
mainPanel.add(tabbedPane, BorderLayout.CENTER)
|
||||||
|
|
||||||
|
val popupContent = ContentFactory.getInstance().createContent(mainPanel, "", false).component
|
||||||
|
val popup = JBPopupFactory.getInstance()
|
||||||
|
.createComponentPopupBuilder(popupContent, popupContent)
|
||||||
|
.setTitle(MessageHelper.getMessage("widget.mode.popup.title"))
|
||||||
|
.setResizable(true)
|
||||||
|
.setMovable(true)
|
||||||
|
.setRequestFocus(true)
|
||||||
|
.setCancelOnClickOutside(false)
|
||||||
|
.setCancelKeyEnabled(false)
|
||||||
|
.createPopup()
|
||||||
|
|
||||||
|
applyButton.addActionListener {
|
||||||
|
lightThemeSettings.apply()
|
||||||
|
darkThemeSettings.apply()
|
||||||
|
repaintModeWidget()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelButton.addActionListener {
|
||||||
|
popup.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
val alarm = Alarm(popup)
|
||||||
|
fun updateApplyButtonVisibility() {
|
||||||
|
alarm.addRequest({
|
||||||
|
applyButton.isEnabled = lightThemeSettings.isModified() || darkThemeSettings.isModified()
|
||||||
|
updateApplyButtonVisibility()
|
||||||
|
}, 500L)
|
||||||
|
}
|
||||||
|
updateApplyButtonVisibility()
|
||||||
|
|
||||||
|
currentPopup = popup
|
||||||
|
return currentPopup
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getWidgetThemeColors(isLight: Boolean): ModeColors {
|
||||||
|
val keyPostfix = if (isLight) "_light" else "_dark"
|
||||||
|
return ModeColors(
|
||||||
|
"widget_mode_is_full_customization$keyPostfix",
|
||||||
|
"widget_mode_theme$keyPostfix",
|
||||||
|
"widget_mode_normal_background$keyPostfix",
|
||||||
|
"widget_mode_normal_foreground$keyPostfix",
|
||||||
|
"widget_mode_insert_background$keyPostfix",
|
||||||
|
"widget_mode_insert_foreground$keyPostfix",
|
||||||
|
"widget_mode_replace_background$keyPostfix",
|
||||||
|
"widget_mode_replace_foreground$keyPostfix",
|
||||||
|
"widget_mode_command_background$keyPostfix",
|
||||||
|
"widget_mode_command_foreground$keyPostfix",
|
||||||
|
"widget_mode_visual_background$keyPostfix",
|
||||||
|
"widget_mode_visual_foreground$keyPostfix",
|
||||||
|
"widget_mode_visual_line_background$keyPostfix",
|
||||||
|
"widget_mode_visual_line_foreground$keyPostfix",
|
||||||
|
"widget_mode_visual_block_background$keyPostfix",
|
||||||
|
"widget_mode_visual_block_foreground$keyPostfix",
|
||||||
|
"widget_mode_select_background$keyPostfix",
|
||||||
|
"widget_mode_select_foreground$keyPostfix",
|
||||||
|
"widget_mode_select_line_background$keyPostfix",
|
||||||
|
"widget_mode_select_line_foreground$keyPostfix",
|
||||||
|
"widget_mode_select_block_background$keyPostfix",
|
||||||
|
"widget_mode_select_block_foreground$keyPostfix",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createPanel(modeColors: ModeColors): DialogPanel {
|
||||||
|
val panel = panel {
|
||||||
|
lateinit var advancedSettings: Cell<JBCheckBox>
|
||||||
|
row {
|
||||||
|
advancedSettings = checkBox(MessageHelper.getMessage("widget.mode.popup.field.advanced.settings")).bindSelected(modeColors::isFullCustomization)
|
||||||
|
}
|
||||||
|
group {
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.theme"))
|
||||||
|
comboBox(ModeWidgetTheme.values().toList()).bindItem(modeColors::theme.toNullableProperty())
|
||||||
|
}
|
||||||
|
row { browserLink("Suggest your theme", "https://youtrack.jetbrains.com/issue/VIM-1377/Normal-mode-needs-to-be-more-obvious") }
|
||||||
|
}.topGap(TopGap.NONE).visibleIf(!advancedSettings.selected)
|
||||||
|
group(MessageHelper.getMessage("widget.mode.popup.group.title.full.customization")) {
|
||||||
|
row { text(MessageHelper.getMessage("widget.mode.popup.color.instruction")) }
|
||||||
|
|
||||||
|
group(MessageHelper.getMessage("widget.mode.popup.group.normal.title")) {
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::normalBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::normalFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
|
||||||
|
group(MessageHelper.getMessage("widget.mode.popup.group.insert.title")) {
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::insertBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::insertFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
|
||||||
|
group(MessageHelper.getMessage("widget.mode.popup.group.replace.title")) {
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::replaceBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::replaceFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
|
||||||
|
group(MessageHelper.getMessage("widget.mode.popup.group.command.title")) {
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::commandBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::commandFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
|
||||||
|
group(MessageHelper.getMessage("widget.mode.popup.group.visual.title")) {
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::visualBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::visualFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
|
||||||
|
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.line.title")) {
|
||||||
|
row { text(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.instruction")) }
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::visualLineBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::visualLineFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
|
||||||
|
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.block.title")) {
|
||||||
|
row { text(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.instruction")) }
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::visualBlockBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::visualBlockFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
group(MessageHelper.getMessage("widget.mode.popup.group.select.title")) {
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::selectBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::selectFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
|
||||||
|
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.line.title")) {
|
||||||
|
row { text(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.instruction")) }
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::selectLineBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::selectLineFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
|
||||||
|
collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.block.title")) {
|
||||||
|
row { text(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.instruction")) }
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.background"))
|
||||||
|
textField().bindText(modeColors::selectBlockBg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
row {
|
||||||
|
label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
|
||||||
|
textField().bindText(modeColors::selectBlockFg)
|
||||||
|
}.layout(RowLayout.PARENT_GRID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.topGap(TopGap.NONE).visibleIf(advancedSettings.selected)
|
||||||
|
}
|
||||||
|
return panel
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun JComponent.addScrollPane(): JComponent {
|
||||||
|
val scrollPane = JBScrollPane(this, JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JBScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
|
||||||
|
scrollPane.border = BorderFactory.createEmptyBorder()
|
||||||
|
return scrollPane
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ModeColors(
|
||||||
|
isFullCustomizationKey: String, themeKey: String,
|
||||||
|
normalBgKey: String, normalFgKey: String,
|
||||||
|
insertBgKey: String, insertFgKey: String,
|
||||||
|
replaceBgKey: String, replaceFgKey: String,
|
||||||
|
commandBgKey: String, commandFgKey: String,
|
||||||
|
visualBgKey: String, visualFgKey: String, visualLineBgKey: String, visualLineFgKey: String, visualBlockBgKey: String, visualBlockFgKey: String,
|
||||||
|
selectBgKey: String, selectFgKey: String, selectLineBgKey: String, selectLineFgKey: String, selectBlockBgKey: String, selectBlockFgKey: String
|
||||||
|
) {
|
||||||
|
var isFullCustomization: Boolean by VimScopeBooleanVariable(isFullCustomizationKey)
|
||||||
|
var theme: ModeWidgetTheme by VimScopeThemeVariable(themeKey)
|
||||||
|
var normalBg: String by VimScopeStringVariable(normalBgKey)
|
||||||
|
var normalFg: String by VimScopeStringVariable(normalFgKey)
|
||||||
|
var insertBg: String by VimScopeStringVariable(insertBgKey)
|
||||||
|
var insertFg: String by VimScopeStringVariable(insertFgKey)
|
||||||
|
var replaceBg: String by VimScopeStringVariable(replaceBgKey)
|
||||||
|
var replaceFg: String by VimScopeStringVariable(replaceFgKey)
|
||||||
|
var commandBg: String by VimScopeStringVariable(commandBgKey)
|
||||||
|
var commandFg: String by VimScopeStringVariable(commandFgKey)
|
||||||
|
var visualBg: String by VimScopeStringVariable(visualBgKey)
|
||||||
|
var visualFg: String by VimScopeStringVariable(visualFgKey)
|
||||||
|
var visualLineBg: String by VimScopeStringVariable(visualLineBgKey)
|
||||||
|
var visualLineFg: String by VimScopeStringVariable(visualLineFgKey)
|
||||||
|
var visualBlockBg: String by VimScopeStringVariable(visualBlockBgKey)
|
||||||
|
var visualBlockFg: String by VimScopeStringVariable(visualBlockFgKey)
|
||||||
|
var selectBg: String by VimScopeStringVariable(selectBgKey)
|
||||||
|
var selectFg: String by VimScopeStringVariable(selectFgKey)
|
||||||
|
var selectLineBg: String by VimScopeStringVariable(selectLineBgKey)
|
||||||
|
var selectLineFg: String by VimScopeStringVariable(selectLineFgKey)
|
||||||
|
var selectBlockBg: String by VimScopeStringVariable(selectBlockBgKey)
|
||||||
|
var selectBlockFg: String by VimScopeStringVariable(selectBlockFgKey)
|
||||||
|
|
||||||
|
private class VimScopeBooleanVariable(private var key: String): ReadWriteProperty<ModeColors, Boolean> {
|
||||||
|
override fun getValue(thisRef: ModeColors, property: KProperty<*>): Boolean {
|
||||||
|
return injector.variableService.getVimVariable(key)?.asBoolean() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: Boolean) {
|
||||||
|
injector.variableService.storeVimVariable(key, value.asVimInt())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VimScopeStringVariable(private var key: String): ReadWriteProperty<ModeColors, String> {
|
||||||
|
override fun getValue(thisRef: ModeColors, property: KProperty<*>): String {
|
||||||
|
return injector.variableService.getVimVariable(key)?.asString() ?: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: String) {
|
||||||
|
injector.variableService.storeVimVariable(key, VimString(value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class VimScopeThemeVariable(private var key: String): ReadWriteProperty<ModeColors, ModeWidgetTheme> {
|
||||||
|
override fun getValue(thisRef: ModeColors, property: KProperty<*>): ModeWidgetTheme {
|
||||||
|
val themeString = injector.variableService.getVimVariable(key)?.asString() ?: return ModeWidgetTheme.getDefaultTheme()
|
||||||
|
return ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: ModeWidgetTheme) {
|
||||||
|
injector.variableService.storeVimVariable(key, VimString(value.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum class ModeWidgetTheme(private var value: String) {
|
||||||
|
TEST("Nord-Aurora (testing, will be removed)"),
|
||||||
|
COLORLESS("Colorless");
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
public fun parseString(string: String): ModeWidgetTheme? {
|
||||||
|
return ModeWidgetTheme.values().firstOrNull { it.value == string }
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun getDefaultTheme(): ModeWidgetTheme = TEST
|
||||||
|
}
|
||||||
|
}
|
129
src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/Util.kt
Normal file
129
src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/Util.kt
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 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.ui.widgets.mode
|
||||||
|
|
||||||
|
import com.intellij.ide.ui.LafManager
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import java.awt.Color
|
||||||
|
|
||||||
|
public fun getModeBackground(mode: Mode?): Color {
|
||||||
|
val isLight = !LafManager.getInstance().currentUIThemeLookAndFeel.isDark
|
||||||
|
val keyPostfix = if (isLight) "_light" else "_dark"
|
||||||
|
if (injector.variableService.getVimVariable("widget_mode_is_full_customization$keyPostfix")?.asBoolean() != true) {
|
||||||
|
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
|
||||||
|
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
|
||||||
|
when (theme) {
|
||||||
|
ModeWidgetTheme.TEST -> {
|
||||||
|
return when (mode) {
|
||||||
|
Mode.INSERT -> Color.decode("#D08770")
|
||||||
|
Mode.REPLACE -> Color.decode("#EBCB8B")
|
||||||
|
is Mode.NORMAL -> Color.decode("#BF616A")
|
||||||
|
is Mode.CMD_LINE -> Color.decode("#A3BE8C")
|
||||||
|
is Mode.VISUAL -> Color.decode("#B48EAD")
|
||||||
|
is Mode.SELECT -> Color.decode("#B48EAD")
|
||||||
|
is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ModeWidgetTheme.COLORLESS -> {
|
||||||
|
return UIUtil.getPanelBackground()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val colorString = when (mode) {
|
||||||
|
Mode.INSERT -> injector.variableService.getVimVariable("widget_mode_insert_background$keyPostfix")
|
||||||
|
Mode.REPLACE -> injector.variableService.getVimVariable("widget_mode_replace_background$keyPostfix")
|
||||||
|
is Mode.NORMAL -> injector.variableService.getVimVariable("widget_mode_normal_background$keyPostfix")
|
||||||
|
is Mode.CMD_LINE -> injector.variableService.getVimVariable("widget_mode_command_background$keyPostfix")
|
||||||
|
is Mode.VISUAL -> {
|
||||||
|
val visualModeBackground = injector.variableService.getVimVariable("widget_mode_visual_background$keyPostfix")
|
||||||
|
when (mode.selectionType) {
|
||||||
|
SelectionType.CHARACTER_WISE -> visualModeBackground
|
||||||
|
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_visual_line_background$keyPostfix") ?: visualModeBackground
|
||||||
|
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_visual_block_background$keyPostfix") ?: visualModeBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Mode.SELECT -> {
|
||||||
|
val selectModeBackground = injector.variableService.getVimVariable("widget_mode_select_background$keyPostfix")
|
||||||
|
when (mode.selectionType) {
|
||||||
|
SelectionType.CHARACTER_WISE -> selectModeBackground
|
||||||
|
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_select_line_background$keyPostfix") ?: selectModeBackground
|
||||||
|
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_select_block_background$keyPostfix") ?: selectModeBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Mode.OP_PENDING, null -> null
|
||||||
|
}?.asString()
|
||||||
|
val defaultColor = UIUtil.getPanelBackground()
|
||||||
|
val color = when (colorString) {
|
||||||
|
"v:status_bar_bg" -> UIUtil.getPanelBackground()
|
||||||
|
"v:status_bar_fg" -> UIUtil.getLabelForeground()
|
||||||
|
else -> {
|
||||||
|
if (colorString == null) {
|
||||||
|
defaultColor
|
||||||
|
} else {
|
||||||
|
try { Color.decode(colorString) } catch (e: Exception) { defaultColor }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun getModeForeground(mode: Mode?): Color {
|
||||||
|
val isLight = !LafManager.getInstance().currentUIThemeLookAndFeel.isDark
|
||||||
|
val keyPostfix = if (isLight) "_light" else "_dark"
|
||||||
|
if (injector.variableService.getVimVariable("widget_mode_is_full_customization$keyPostfix")?.asBoolean() != true) {
|
||||||
|
val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
|
||||||
|
val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
|
||||||
|
return when (theme) {
|
||||||
|
ModeWidgetTheme.TEST -> Color.decode("#2E3440")
|
||||||
|
ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val colorString = when (mode) {
|
||||||
|
Mode.INSERT -> injector.variableService.getVimVariable("widget_mode_insert_foreground$keyPostfix")
|
||||||
|
Mode.REPLACE -> injector.variableService.getVimVariable("widget_mode_replace_foreground$keyPostfix")
|
||||||
|
is Mode.NORMAL -> injector.variableService.getVimVariable("widget_mode_normal_foreground$keyPostfix")
|
||||||
|
is Mode.CMD_LINE -> injector.variableService.getVimVariable("widget_mode_command_foreground$keyPostfix")
|
||||||
|
is Mode.VISUAL -> {
|
||||||
|
val visualModeBackground = injector.variableService.getVimVariable("widget_mode_visual_foreground$keyPostfix")
|
||||||
|
when (mode.selectionType) {
|
||||||
|
SelectionType.CHARACTER_WISE -> visualModeBackground
|
||||||
|
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_visual_line_foreground$keyPostfix") ?: visualModeBackground
|
||||||
|
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_visual_block_foreground$keyPostfix") ?: visualModeBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Mode.SELECT -> {
|
||||||
|
val selectModeBackground = injector.variableService.getVimVariable("widget_mode_select_foreground$keyPostfix")
|
||||||
|
when (mode.selectionType) {
|
||||||
|
SelectionType.CHARACTER_WISE -> selectModeBackground
|
||||||
|
SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_select_line_foreground$keyPostfix") ?: selectModeBackground
|
||||||
|
SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_select_block_foreground$keyPostfix") ?: selectModeBackground
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is Mode.OP_PENDING, null -> null
|
||||||
|
}?.asString()
|
||||||
|
val defaultColor = UIUtil.getLabelForeground()
|
||||||
|
val color = when (colorString) {
|
||||||
|
"v:status_bar_bg" -> UIUtil.getPanelBackground()
|
||||||
|
"v:status_bar_fg" -> UIUtil.getLabelForeground()
|
||||||
|
else -> {
|
||||||
|
if (colorString == null) {
|
||||||
|
defaultColor
|
||||||
|
} else {
|
||||||
|
try { Color.decode(colorString) } catch (e: Exception) { defaultColor }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return color
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -0,0 +1,176 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.widgets.mode
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.service
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.project.ProjectManager
|
||||||
|
import com.intellij.openapi.wm.CustomStatusBarWidget
|
||||||
|
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
||||||
|
import com.intellij.openapi.wm.WindowManager
|
||||||
|
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
|
||||||
|
import com.intellij.ui.awt.RelativePoint
|
||||||
|
import com.intellij.ui.components.JBLabel
|
||||||
|
import com.intellij.ui.util.width
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
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.ui.widgets.mode.listeners.ModeWidgetFocusListener
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetModeListener
|
||||||
|
import java.awt.Dimension
|
||||||
|
import java.awt.Point
|
||||||
|
import java.awt.event.MouseAdapter
|
||||||
|
import java.awt.event.MouseEvent
|
||||||
|
import javax.swing.JComponent
|
||||||
|
import kotlin.math.max
|
||||||
|
|
||||||
|
public class VimModeWidget(public val project: Project) : CustomStatusBarWidget, VimStatusBarWidget {
|
||||||
|
private companion object {
|
||||||
|
private const val INSERT = "INSERT"
|
||||||
|
private const val NORMAL = "NORMAL"
|
||||||
|
private const val REPLACE = "REPLACE"
|
||||||
|
private const val COMMAND = "COMMAND"
|
||||||
|
private const val VISUAL = "VISUAL"
|
||||||
|
private const val VISUAL_LINE = "V-LINE"
|
||||||
|
private const val VISUAL_BLOCK = "V-BLOCK"
|
||||||
|
private const val SELECT = "SELECT"
|
||||||
|
private const val SELECT_LINE = "S-LINE"
|
||||||
|
private const val SELECT_BLOCK = "S-BLOCK"
|
||||||
|
}
|
||||||
|
private val useColors = injector.globalIjOptions().colorfulmodewidget
|
||||||
|
private val label = JBLabelWiderThan(setOf(REPLACE)).apply {
|
||||||
|
isOpaque = useColors
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val mode = getFocusedEditor(project)?.vim?.mode
|
||||||
|
updateLabel(mode)
|
||||||
|
|
||||||
|
injector.listenersNotifier.apply {
|
||||||
|
modeChangeListeners.add(ModeWidgetModeListener(this@VimModeWidget))
|
||||||
|
myEditorListeners.add(ModeWidgetFocusListener(this@VimModeWidget))
|
||||||
|
}
|
||||||
|
|
||||||
|
label.addMouseListener(object : MouseAdapter() {
|
||||||
|
override fun mouseClicked(e: MouseEvent) {
|
||||||
|
val popup = ModeWidgetPopup.createPopup() ?: return
|
||||||
|
val dimension = popup.content.preferredSize
|
||||||
|
|
||||||
|
val widgetLocation = e.component.locationOnScreen
|
||||||
|
popup.show(RelativePoint(Point(
|
||||||
|
widgetLocation.x + e.component.width - dimension.width,
|
||||||
|
widgetLocation.y - dimension.height,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun ID(): String {
|
||||||
|
return ModeWidgetFactory.ID
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getComponent(): JComponent {
|
||||||
|
return label
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun updateWidget() {
|
||||||
|
val mode = getFocusedEditor(project)?.vim?.mode
|
||||||
|
updateWidget(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun updateWidget(mode: Mode?) {
|
||||||
|
updateLabel(mode)
|
||||||
|
updateWidgetInStatusBar(ModeWidgetFactory.ID, project)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun updateLabel(mode: Mode?) {
|
||||||
|
label.text = getModeText(mode)
|
||||||
|
if (useColors) {
|
||||||
|
label.foreground = getModeForeground(mode)
|
||||||
|
label.background = getModeBackground(mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFocusedEditor(project: Project): Editor? {
|
||||||
|
val fileEditorManager = FileEditorManager.getInstance(project)
|
||||||
|
return fileEditorManager.selectedTextEditor
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getModeText(mode: Mode?): String? {
|
||||||
|
return when (mode) {
|
||||||
|
Mode.INSERT -> INSERT
|
||||||
|
Mode.REPLACE -> REPLACE
|
||||||
|
is Mode.NORMAL -> NORMAL
|
||||||
|
is Mode.CMD_LINE -> COMMAND
|
||||||
|
is Mode.VISUAL -> getVisualModeText(mode)
|
||||||
|
is Mode.SELECT -> getSelectModeText(mode)
|
||||||
|
is Mode.OP_PENDING, null -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getVisualModeText(mode: Mode.VISUAL) = when (mode.selectionType) {
|
||||||
|
SelectionType.CHARACTER_WISE -> VISUAL
|
||||||
|
SelectionType.LINE_WISE -> VISUAL_LINE
|
||||||
|
SelectionType.BLOCK_WISE -> VISUAL_BLOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSelectModeText(mode: Mode.SELECT) = when (mode.selectionType) {
|
||||||
|
SelectionType.CHARACTER_WISE -> SELECT
|
||||||
|
SelectionType.LINE_WISE -> SELECT_LINE
|
||||||
|
SelectionType.BLOCK_WISE -> SELECT_BLOCK
|
||||||
|
}
|
||||||
|
|
||||||
|
private class JBLabelWiderThan(private val words: Collection<String>): JBLabel("", CENTER) {
|
||||||
|
private val wordWidth: Int
|
||||||
|
get() {
|
||||||
|
val fontMetrics = getFontMetrics(font)
|
||||||
|
return words.maxOfOrNull { fontMetrics.stringWidth(it) } ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMinimumSize(): Dimension {
|
||||||
|
val minimumSize = super.getMinimumSize()
|
||||||
|
return Dimension(max(minimumSize.width, wordWidth + insets.width), minimumSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getPreferredSize(): Dimension {
|
||||||
|
val preferredSize = super.getPreferredSize()
|
||||||
|
return Dimension(max(preferredSize.width, wordWidth + insets.width), preferredSize.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMaximumSize(): Dimension {
|
||||||
|
val maximumSize = super.getMaximumSize()
|
||||||
|
return Dimension(max(maximumSize.width, wordWidth + insets.width), maximumSize.height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun updateModeWidget() {
|
||||||
|
val factory = StatusBarWidgetFactory.EP_NAME.findExtension(ModeWidgetFactory::class.java) ?: return
|
||||||
|
for (project in ProjectManager.getInstance().openProjects) {
|
||||||
|
val statusBarWidgetsManager = project.service<StatusBarWidgetsManager>()
|
||||||
|
statusBarWidgetsManager.updateWidget(factory)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun repaintModeWidget() {
|
||||||
|
for (project in ProjectManager.getInstance().openProjects) {
|
||||||
|
val widgets = WindowManager.getInstance()?.getStatusBar(project)?.allWidgets ?: continue
|
||||||
|
|
||||||
|
for (widget in widgets) {
|
||||||
|
if (widget is VimModeWidget) {
|
||||||
|
widget.updateWidget()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 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.ui.widgets.mode
|
||||||
|
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.wm.WindowManager
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
public interface VimStatusBarWidget {
|
||||||
|
public fun updateWidgetInStatusBar(widgetID: String, project: Project?) {
|
||||||
|
if (project == null) return
|
||||||
|
val windowManager = WindowManager.getInstance()
|
||||||
|
windowManager.getStatusBar(project)?.updateWidget(widgetID) ?: run {
|
||||||
|
TimerWithRetriesTask(500L, 50) {
|
||||||
|
val statusBar = windowManager.getStatusBar(project) ?: return@TimerWithRetriesTask false
|
||||||
|
statusBar.updateWidget(widgetID)
|
||||||
|
return@TimerWithRetriesTask true
|
||||||
|
}.execute()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A task that may be used to address issues with initialization in the Platform, executing code with a reasonable number of retries and a reasonable period.
|
||||||
|
* Clearly, this is a workaround and its use should be avoided when possible.
|
||||||
|
*
|
||||||
|
* Why is it needed for widgets?
|
||||||
|
* In a single project environment, it is not necessary since the status bar is initialized before the editor opens.
|
||||||
|
* However, in multi-project setups, the editor window is opened before the status bar initialization.
|
||||||
|
* And this tasks tries to loops until status bar creation in order to notify it about opened editor.
|
||||||
|
*/
|
||||||
|
private class TimerWithRetriesTask(
|
||||||
|
private val period: Long,
|
||||||
|
private val retriesLimit: Int,
|
||||||
|
private val block: () -> Boolean,
|
||||||
|
) {
|
||||||
|
private val timer = Timer()
|
||||||
|
|
||||||
|
fun execute() {
|
||||||
|
timer.schedule(object : TimerTask() {
|
||||||
|
private var counter = 0
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
if (counter >= retriesLimit) {
|
||||||
|
timer.cancel()
|
||||||
|
} else {
|
||||||
|
if (this@TimerWithRetriesTask.block()) timer.cancel()
|
||||||
|
counter++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 0L, period)
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.widgets.mode.listeners
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.common.EditorListener
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.mode.updateModeWidget
|
||||||
|
|
||||||
|
internal class ModeWidgetFocusListener(private val modeWidget: VimModeWidget): EditorListener {
|
||||||
|
override fun created(editor: VimEditor) {
|
||||||
|
updateModeWidget()
|
||||||
|
val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
|
||||||
|
modeWidget.updateWidget(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun released(editor: VimEditor) {
|
||||||
|
updateModeWidget()
|
||||||
|
val focusedEditor = getFocusedEditorForProject(editor.ij.project)
|
||||||
|
if (focusedEditor == null || focusedEditor == editor.ij) {
|
||||||
|
modeWidget.updateWidget(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun focusGained(editor: VimEditor) {
|
||||||
|
if (editor.ij.project != modeWidget.project) return
|
||||||
|
val mode = editor.mode
|
||||||
|
modeWidget.updateWidget(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun focusLost(editor: VimEditor) {
|
||||||
|
val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
|
||||||
|
modeWidget.updateWidget(mode)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFocusedEditorForProject(editorProject: Project?): Editor? {
|
||||||
|
if (editorProject != modeWidget.project) return null
|
||||||
|
val fileEditorManager = FileEditorManager.getInstance(editorProject)
|
||||||
|
return fileEditorManager.selectedTextEditor
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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.ui.widgets.mode.listeners
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
|
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
|
||||||
|
|
||||||
|
internal class ModeWidgetModeListener(private val modeWidget: VimModeWidget): ModeChangeListener {
|
||||||
|
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
|
||||||
|
val editorMode = editor.mode
|
||||||
|
if (editorMode !is Mode.OP_PENDING && editor.ij.project == modeWidget.project) {
|
||||||
|
modeWidget.updateWidget(editorMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -36,31 +36,28 @@ public val VimEditor.isIdeaRefactorModeSelect: Boolean
|
|||||||
|
|
||||||
internal object IdeaRefactorModeHelper {
|
internal object IdeaRefactorModeHelper {
|
||||||
|
|
||||||
fun correctSelection(editor: Editor) {
|
sealed interface Action {
|
||||||
val action: () -> Unit = {
|
object RemoveSelection : Action
|
||||||
val mode = editor.vim.mode
|
class SetMode(val newMode: Mode) : Action
|
||||||
if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
|
class MoveToOffset(val newOffset: Int) : Action
|
||||||
SelectionVimListenerSuppressor.lock().use {
|
}
|
||||||
editor.selectionModel.removeSelection()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
|
|
||||||
val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim)
|
|
||||||
if (mode.selectionType != autodetectedSubmode) {
|
|
||||||
// Update the submode
|
|
||||||
val newMode = when (mode) {
|
|
||||||
is Mode.SELECT -> mode.copy(selectionType = autodetectedSubmode)
|
|
||||||
is Mode.VISUAL -> mode.copy(selectionType = autodetectedSubmode)
|
|
||||||
else -> error("IdeaVim should be either in visual or select modes")
|
|
||||||
}
|
|
||||||
editor.vim.vimStateMachine.mode = newMode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (editor.hasBlockOrUnderscoreCaret()) {
|
fun applyCorrections(corrections: List<Action>, editor: Editor) {
|
||||||
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
|
val correctionsApplier = {
|
||||||
if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
|
corrections.forEach { correction ->
|
||||||
editor.caretModel.moveToOffset(editor.caretModel.offset - 1)
|
when (correction) {
|
||||||
|
is Action.MoveToOffset -> {
|
||||||
|
editor.caretModel.moveToOffset(correction.newOffset)
|
||||||
|
}
|
||||||
|
|
||||||
|
Action.RemoveSelection -> {
|
||||||
|
SelectionVimListenerSuppressor.lock().use {
|
||||||
|
editor.selectionModel.removeSelection()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is Action.SetMode -> {
|
||||||
|
editor.vim.vimStateMachine.mode = correction.newMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,7 +67,9 @@ internal object IdeaRefactorModeHelper {
|
|||||||
if (lookup != null) {
|
if (lookup != null) {
|
||||||
val selStart = editor.selectionModel.selectionStart
|
val selStart = editor.selectionModel.selectionStart
|
||||||
val selEnd = editor.selectionModel.selectionEnd
|
val selEnd = editor.selectionModel.selectionEnd
|
||||||
lookup.performGuardedChange(action)
|
lookup.performGuardedChange {
|
||||||
|
correctionsApplier()
|
||||||
|
}
|
||||||
lookup.addLookupListener(object : LookupListener {
|
lookup.addLookupListener(object : LookupListener {
|
||||||
override fun beforeItemSelected(event: LookupEvent): Boolean {
|
override fun beforeItemSelected(event: LookupEvent): Boolean {
|
||||||
// FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
|
// FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
|
||||||
@@ -82,7 +81,41 @@ internal object IdeaRefactorModeHelper {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
action()
|
correctionsApplier()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun calculateCorrections(editor: Editor): List<Action> {
|
||||||
|
val corrections = mutableListOf<Action>()
|
||||||
|
val mode = editor.vim.mode
|
||||||
|
if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
|
||||||
|
corrections.add(Action.RemoveSelection)
|
||||||
|
}
|
||||||
|
if (mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
|
||||||
|
val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim)
|
||||||
|
if (mode.selectionType != autodetectedSubmode) {
|
||||||
|
// Update the submode
|
||||||
|
val newMode = when (mode) {
|
||||||
|
is Mode.SELECT -> mode.copy(selectionType = autodetectedSubmode)
|
||||||
|
is Mode.VISUAL -> mode.copy(selectionType = autodetectedSubmode)
|
||||||
|
else -> error("IdeaVim should be either in visual or select modes")
|
||||||
|
}
|
||||||
|
corrections.add(Action.SetMode(newMode))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor.hasBlockOrUnderscoreCaret()) {
|
||||||
|
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
|
||||||
|
if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
|
||||||
|
corrections.add(Action.MoveToOffset(editor.caretModel.offset - 1))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return corrections
|
||||||
|
}
|
||||||
|
|
||||||
|
fun correctSelection(editor: Editor) {
|
||||||
|
val corrections = calculateCorrections(editor)
|
||||||
|
applyCorrections(corrections, editor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -8,6 +8,10 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.vimscript.services
|
package com.maddyhome.idea.vim.vimscript.services
|
||||||
|
|
||||||
|
import com.intellij.openapi.components.PersistentStateComponent
|
||||||
|
import com.intellij.openapi.components.RoamingType
|
||||||
|
import com.intellij.openapi.components.State
|
||||||
|
import com.intellij.openapi.components.Storage
|
||||||
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.ex.vimscript.VimScriptGlobalEnvironment
|
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
|
||||||
@@ -22,8 +26,10 @@ import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
|
|||||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
|
||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
|
||||||
|
import org.jdom.Element
|
||||||
|
|
||||||
internal class IjVariableService : VimVariableServiceBase() {
|
@State(name = "VimVariables", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
|
||||||
|
internal class IjVariableService : VimVariableServiceBase(), PersistentStateComponent<Element?> {
|
||||||
override fun storeVariable(variable: Variable, value: VimDataType, editor: VimEditor, context: ExecutionContext, vimContext: VimLContext) {
|
override fun storeVariable(variable: Variable, value: VimDataType, editor: VimEditor, context: ExecutionContext, vimContext: VimLContext) {
|
||||||
super.storeVariable(variable, value, editor, context, vimContext)
|
super.storeVariable(variable, value, editor, context, vimContext)
|
||||||
|
|
||||||
@@ -47,4 +53,49 @@ internal class IjVariableService : VimVariableServiceBase() {
|
|||||||
else -> error("Unexpected")
|
else -> error("Unexpected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getState(): Element {
|
||||||
|
val element = Element("variables")
|
||||||
|
saveData(element)
|
||||||
|
return element
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun loadState(state: Element) {
|
||||||
|
readData(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun saveData(element: Element) {
|
||||||
|
val vimVariablesElement = Element("vim-variables")
|
||||||
|
for ((key, value) in vimVariables.entries) {
|
||||||
|
if (value is VimString) {
|
||||||
|
val variableElement = Element("variable")
|
||||||
|
variableElement.setAttribute("key", key)
|
||||||
|
variableElement.setAttribute("value", value.value)
|
||||||
|
variableElement.setAttribute("type", "string")
|
||||||
|
vimVariablesElement.addContent(variableElement)
|
||||||
|
} else if (value is VimInt) {
|
||||||
|
val variableElement = Element("variable")
|
||||||
|
variableElement.setAttribute("key", key)
|
||||||
|
variableElement.setAttribute("value", value.value.toString())
|
||||||
|
variableElement.setAttribute("type", "int")
|
||||||
|
vimVariablesElement.addContent(variableElement)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
element.addContent(vimVariablesElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readData(element: Element) {
|
||||||
|
val vimVariablesElement = element.getChild("vim-variables")
|
||||||
|
val variableElements = vimVariablesElement.getChildren("variable")
|
||||||
|
for (variableElement in variableElements) {
|
||||||
|
when (variableElement.getAttributeValue("type")) {
|
||||||
|
"string" -> {
|
||||||
|
vimVariables[variableElement.getAttributeValue("key")] = VimString(variableElement.getAttributeValue("value"))
|
||||||
|
}
|
||||||
|
"int" -> {
|
||||||
|
vimVariables[variableElement.getAttributeValue("key")] = VimInt(variableElement.getAttributeValue("value"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
19
src/main/resources/META-INF/ides/ideavim-withClionNova.xml
Normal file
19
src/main/resources/META-INF/ides/ideavim-withClionNova.xml
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!--
|
||||||
|
~ Copyright 2003-2024 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>
|
||||||
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
|
<editorActionHandler action="EditorEscape"
|
||||||
|
implementationClass="com.maddyhome.idea.vim.handler.VimEscForRiderHandler"
|
||||||
|
id="ideavim-clion-nova-esc"
|
||||||
|
order="first, before idea.only.escape"/>
|
||||||
|
</extensions>
|
||||||
|
<extensions defaultExtensionNs="IdeaVIM">
|
||||||
|
<clionNovaProvider implementation="com.maddyhome.idea.vim.ide.ClionNovaProviderImpl"/>
|
||||||
|
</extensions>
|
||||||
|
</idea-plugin>
|
@@ -94,6 +94,8 @@
|
|||||||
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction" mappingModes="NXO" keys="[m"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction" mappingModes="NXO" keys="[m"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction" mappingModes="NXO" keys="]M"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction" mappingModes="NXO" keys="]M"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction" mappingModes="NXO" keys="]m"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction" mappingModes="NXO" keys="]m"/>
|
||||||
|
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction" mappingModes="NXO" keys="[s"/>
|
||||||
|
<vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction" mappingModes="NXO" keys="]s"/>
|
||||||
<!-- Text Objects -->
|
<!-- Text Objects -->
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterWordAction" mappingModes="XO" keys="aw"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterWordAction" mappingModes="XO" keys="aw"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterBigWordAction" mappingModes="XO" keys="aW"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterBigWordAction" mappingModes="XO" keys="aW"/>
|
||||||
|
@@ -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"
|
||||||
|
@@ -29,6 +29,8 @@
|
|||||||
<!--suppress PluginXmlValidity -->
|
<!--suppress PluginXmlValidity -->
|
||||||
<depends optional="true" config-file="ides/ideavim-withRider.xml">com.intellij.modules.rider</depends>
|
<depends optional="true" config-file="ides/ideavim-withRider.xml">com.intellij.modules.rider</depends>
|
||||||
<!--suppress PluginXmlValidity -->
|
<!--suppress PluginXmlValidity -->
|
||||||
|
<depends optional="true" config-file="ides/ideavim-withClionNova.xml">org.jetbrains.plugins.clion.radler</depends>
|
||||||
|
<!--suppress PluginXmlValidity -->
|
||||||
<depends optional="true" config-file="ides/ideavim-withAppCode.xml">com.intellij.modules.appcode</depends>
|
<depends optional="true" config-file="ides/ideavim-withAppCode.xml">com.intellij.modules.appcode</depends>
|
||||||
<depends optional="true" config-file="ideavim-withAceJump.xml">AceJump</depends>
|
<depends optional="true" config-file="ideavim-withAceJump.xml">AceJump</depends>
|
||||||
|
|
||||||
@@ -55,6 +57,8 @@
|
|||||||
<extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
|
<extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
|
||||||
<with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
|
<with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
|
||||||
</extensionPoint>
|
</extensionPoint>
|
||||||
|
|
||||||
|
<extensionPoint interface="com.maddyhome.idea.vim.ide.ClionNovaProvider" dynamic="true" name="clionNovaProvider"/>
|
||||||
</extensionPoints>
|
</extensionPoints>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
@@ -62,7 +66,9 @@
|
|||||||
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
|
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
|
||||||
<projectService serviceImplementation="com.maddyhome.idea.vim.group.LastTabService"/>
|
<projectService serviceImplementation="com.maddyhome.idea.vim.group.LastTabService"/>
|
||||||
<statusBarWidgetFactory id="IdeaVim-Icon" implementation="com.maddyhome.idea.vim.ui.StatusBarIconFactory"/>
|
<statusBarWidgetFactory id="IdeaVim-Icon" implementation="com.maddyhome.idea.vim.ui.StatusBarIconFactory"/>
|
||||||
|
<statusBarWidgetFactory id="IdeaVim::Mode" implementation="com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory" order="last"/>
|
||||||
<statusBarWidgetFactory id="IdeaVim::ShowCmd" implementation="com.maddyhome.idea.vim.ui.ShowCmdStatusBarWidgetFactory" order="first"/>
|
<statusBarWidgetFactory id="IdeaVim::ShowCmd" implementation="com.maddyhome.idea.vim.ui.ShowCmdStatusBarWidgetFactory" order="first"/>
|
||||||
|
<statusBarWidgetFactory id="IdeaVim::Macro" implementation="com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetFactory"/>
|
||||||
|
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
|
||||||
|
|
||||||
@@ -71,11 +77,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"/>
|
||||||
|
|
||||||
@@ -117,8 +124,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>
|
||||||
|
|
||||||
@@ -141,6 +152,7 @@
|
|||||||
|
|
||||||
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction"/>
|
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction"/>
|
||||||
<action id="VimActions" class="com.maddyhome.idea.vim.ui.VimActions"/>
|
<action id="VimActions" class="com.maddyhome.idea.vim.ui.VimActions"/>
|
||||||
|
<action id="CustomizeModeWidget" class="com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetPopup"/>
|
||||||
|
|
||||||
<group id="IdeaVim.ReloadVimRc.group" class="com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup">
|
<group id="IdeaVim.ReloadVimRc.group" class="com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup">
|
||||||
<action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc"
|
<action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc"
|
||||||
|
11
src/main/resources/icons/ideavim_outline.svg
Normal file
11
src/main/resources/icons/ideavim_outline.svg
Normal 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 |
@@ -84,6 +84,8 @@ action.VimShortcutKeyAction.text=Shortcuts
|
|||||||
action.VimActions.text=Vim Actions
|
action.VimActions.text=Vim Actions
|
||||||
action.not.found.0=Action not found: {0}
|
action.not.found.0=Action not found: {0}
|
||||||
|
|
||||||
|
action.CustomizeModeWidget.text=Mode Widget Settings
|
||||||
|
|
||||||
action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
|
action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
|
||||||
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
|
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
|
||||||
|
|
||||||
@@ -129,6 +131,28 @@ action.finish.eap.text=Finish EAP
|
|||||||
# Don't forget to update README if you modify this entry
|
# Don't forget to update README if you modify this entry
|
||||||
action.subscribe.to.eap.text=Subscribe to EAP
|
action.subscribe.to.eap.text=Subscribe to EAP
|
||||||
|
|
||||||
|
widget.mode.popup.title=Mode Widget Colors
|
||||||
|
widget.mode.popup.tab.light=Light Theme
|
||||||
|
widget.mode.popup.tab.dark=Dark Theme
|
||||||
|
widget.mode.popup.color.instruction=Use HEX color values for exact colors; use v:status_bar_bg to use your IDE's status bar background color and v:status_bar_fg for the foreground
|
||||||
|
widget.mode.popup.field.theme=Widget theme:
|
||||||
|
widget.mode.popup.field.advanced.settings=Full color customization (advanced)
|
||||||
|
widget.mode.popup.group.title.full.customization=Full customization
|
||||||
|
widget.mode.popup.group.normal.title=Normal Mode
|
||||||
|
widget.mode.popup.group.insert.title=Insert Mode
|
||||||
|
widget.mode.popup.group.replace.title=Replace Mode
|
||||||
|
widget.mode.popup.group.command.title=Command Mode
|
||||||
|
widget.mode.popup.group.visual.title=Visual Mode
|
||||||
|
widget.mode.popup.group.visual.subgroup.instruction=Leave fields empty to inherit colors from Visual Mode
|
||||||
|
widget.mode.popup.group.visual.subgroup.line.title=Visual Line
|
||||||
|
widget.mode.popup.group.visual.subgroup.block.title=Visual Block
|
||||||
|
widget.mode.popup.group.select.title=Select Mode
|
||||||
|
widget.mode.popup.group.select.subgroup.instruction=Leave fields empty to inherit colors from Select Mode
|
||||||
|
widget.mode.popup.group.select.subgroup.line.title=Select Line
|
||||||
|
widget.mode.popup.group.select.subgroup.block.title=Select Block
|
||||||
|
widget.mode.popup.field.background=Background:
|
||||||
|
widget.mode.popup.field.foreground=Text:
|
||||||
|
|
||||||
configurable.name.vim.emulation=Vim
|
configurable.name.vim.emulation=Vim
|
||||||
configurable.keyhandler.link=<html>Use <a>sethandler</a> command to configure handlers from the .ideavimrc file</html>
|
configurable.keyhandler.link=<html>Use <a>sethandler</a> command to configure handlers from the .ideavimrc file</html>
|
||||||
configurable.noneditablehandler.helper.text.with.example=Non-editable handlers are defined in .ideavimrc file. E.g. ''{0}'' for {1}.
|
configurable.noneditablehandler.helper.text.with.example=Non-editable handlers are defined in .ideavimrc file. E.g. ''{0}'' for {1}.
|
||||||
|
@@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package org.jetbrains.plugins.ideavim
|
||||||
|
|
||||||
|
import com.intellij.testFramework.LoggedErrorProcessor
|
||||||
|
import com.intellij.testFramework.TestLoggerFactory.TestLoggerAssertionError
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import org.junit.jupiter.api.fail
|
||||||
|
|
||||||
|
/**
|
||||||
|
* By default, LOG.error does three things in tests:
|
||||||
|
* - rethrows the exception
|
||||||
|
* - logs error
|
||||||
|
* - prints to stderr
|
||||||
|
*
|
||||||
|
* The problem is that if we catch exception in tests, such an approach will print the exception to stderr and it will
|
||||||
|
* look like the exception is not processed.
|
||||||
|
* I don't see a need for printing these caught exceptions, so we can use this processor to only rethrow them.
|
||||||
|
*/
|
||||||
|
internal object OnlyThrowLoggedErrorProcessor : LoggedErrorProcessor() {
|
||||||
|
override fun processError(category: String, message: String, details: Array<out String>, t: Throwable?): Set<Action> {
|
||||||
|
return setOf(Action.RETHROW)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Asserts that [T] was thrown via `LOG.error("message", e)` call where `e` has a type of [T].
|
||||||
|
*/
|
||||||
|
internal inline fun <reified T: Throwable> assertThrowsLogError(crossinline action: () -> Unit): T {
|
||||||
|
val exception = assertThrows<TestLoggerAssertionError> {
|
||||||
|
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val cause = exception.cause
|
||||||
|
if (cause !is T) fail("Expected ${T::class.java} exception in LOG.error, but got $cause")
|
||||||
|
return cause
|
||||||
|
}
|
@@ -14,12 +14,17 @@ import com.intellij.openapi.editor.LogicalPosition
|
|||||||
import com.intellij.testFramework.EditorTestUtil
|
import com.intellij.testFramework.EditorTestUtil
|
||||||
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
|
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
|
||||||
import com.intellij.util.containers.toArray
|
import com.intellij.util.containers.toArray
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.state.mode.mode
|
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||||
|
import com.maddyhome.idea.vim.key.MappingOwner
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
import org.junit.jupiter.params.provider.Arguments
|
import org.junit.jupiter.params.provider.Arguments
|
||||||
import kotlin.test.fail
|
import kotlin.test.fail
|
||||||
|
|
||||||
@@ -129,3 +134,15 @@ internal fun <T> product(vararg elements: List<T>): List<List<T>> {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal class ExceptionHandler : ExtensionHandler {
|
||||||
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
|
error(exceptionMessage)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
internal const val exceptionMessage = "Exception here"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val exceptionMappingOwner = MappingOwner.Plugin.get("Exception mapping owner")
|
||||||
|
@@ -123,7 +123,7 @@ abstract class VimTestCase {
|
|||||||
VimPlugin.getOptionGroup().resetAllOptionsForTesting()
|
VimPlugin.getOptionGroup().resetAllOptionsForTesting()
|
||||||
VimPlugin.getKey().resetKeyMappings()
|
VimPlugin.getKey().resetKeyMappings()
|
||||||
VimPlugin.getSearch().resetState()
|
VimPlugin.getSearch().resetState()
|
||||||
if (!VimPlugin.isEnabled()) VimPlugin.setEnabled(true)
|
if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true)
|
||||||
injector.globalOptions().ideastrictmode = true
|
injector.globalOptions().ideastrictmode = true
|
||||||
Checks.reset()
|
Checks.reset()
|
||||||
clearClipboard()
|
clearClipboard()
|
||||||
@@ -152,8 +152,8 @@ abstract class VimTestCase {
|
|||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
open fun tearDown(testInfo: TestInfo) {
|
open fun tearDown(testInfo: TestInfo) {
|
||||||
val swingTimer = swingTimer
|
|
||||||
swingTimer?.stop()
|
swingTimer?.stop()
|
||||||
|
swingTimer = null
|
||||||
val bookmarksManager = BookmarksManager.getInstance(fixture.project)
|
val bookmarksManager = BookmarksManager.getInstance(fixture.project)
|
||||||
bookmarksManager?.bookmarks?.forEach { bookmark ->
|
bookmarksManager?.bookmarks?.forEach { bookmark ->
|
||||||
bookmarksManager.remove(bookmark)
|
bookmarksManager.remove(bookmark)
|
||||||
@@ -170,6 +170,7 @@ abstract class VimTestCase {
|
|||||||
injector.jumpService.resetJumps()
|
injector.jumpService.resetJumps()
|
||||||
VimPlugin.getChange().resetRepeat()
|
VimPlugin.getChange().resetRepeat()
|
||||||
VimPlugin.getKey().savedShortcutConflicts.clear()
|
VimPlugin.getKey().savedShortcutConflicts.clear()
|
||||||
|
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
|
||||||
|
|
||||||
// Tear down neovim
|
// Tear down neovim
|
||||||
NeovimTesting.tearDown(testInfo)
|
NeovimTesting.tearDown(testInfo)
|
||||||
@@ -447,6 +448,11 @@ abstract class VimTestCase {
|
|||||||
return NeovimTesting.getMark(char)
|
return NeovimTesting.getMark(char)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected fun assertRegister(char: Char, expected: String?) {
|
||||||
|
val actual = injector.registerGroup.getRegister(char)?.keys?.let(injector.parser::toKeyNotation)
|
||||||
|
assertEquals(expected, actual, "Wrong register contents")
|
||||||
|
}
|
||||||
|
|
||||||
protected fun assertState(modeAfter: Mode) {
|
protected fun assertState(modeAfter: Mode) {
|
||||||
assertMode(modeAfter)
|
assertMode(modeAfter)
|
||||||
assertCaretsVisualAttributes()
|
assertCaretsVisualAttributes()
|
||||||
@@ -771,7 +777,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
|
||||||
|
@@ -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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
101
src/test/java/org/jetbrains/plugins/ideavim/action/EscapeTest.kt
Normal file
101
src/test/java/org/jetbrains/plugins/ideavim/action/EscapeTest.kt
Normal 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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@@ -7,32 +7,47 @@
|
|||||||
*/
|
*/
|
||||||
package org.jetbrains.plugins.ideavim.action
|
package org.jetbrains.plugins.ideavim.action
|
||||||
|
|
||||||
|
import com.intellij.idea.TestFor
|
||||||
|
import com.intellij.testFramework.LoggedErrorProcessor
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.api.keys
|
||||||
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import org.jetbrains.plugins.ideavim.ExceptionHandler
|
||||||
|
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
|
||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
|
||||||
import org.jetbrains.plugins.ideavim.rangeOf
|
import org.jetbrains.plugins.ideavim.rangeOf
|
||||||
import org.jetbrains.plugins.ideavim.waitAndAssert
|
import org.jetbrains.plugins.ideavim.waitAndAssert
|
||||||
|
import org.junit.jupiter.api.AfterEach
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import org.junit.jupiter.api.assertThrows
|
||||||
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNotNull
|
import kotlin.test.assertNotNull
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author vlan
|
* @author vlan
|
||||||
*/
|
*/
|
||||||
class MacroActionTest : VimTestCase() {
|
class MacroActionTest : VimTestCase() {
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
fun tearDown() {
|
||||||
|
injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
|
||||||
|
}
|
||||||
|
|
||||||
// |q|
|
// |q|
|
||||||
@Test
|
@Test
|
||||||
fun testRecordMacro() {
|
fun testRecordMacro() {
|
||||||
val editor = typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "on<caret>e two three\n")
|
val editor = typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "on<caret>e two three\n")
|
||||||
val commandState = editor.vim.vimStateMachine
|
val commandState = editor.vim.vimStateMachine
|
||||||
kotlin.test.assertFalse(commandState.isRecording)
|
kotlin.test.assertFalse(commandState.isRecording)
|
||||||
val registerGroup = VimPlugin.getRegister()
|
assertRegister('a', "3l")
|
||||||
val register = registerGroup.getRegister('a')
|
|
||||||
assertNotNull<Any>(register)
|
|
||||||
kotlin.test.assertEquals("3l", register.text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -40,9 +55,7 @@ class MacroActionTest : VimTestCase() {
|
|||||||
configureByText("")
|
configureByText("")
|
||||||
enterCommand("imap pp hello")
|
enterCommand("imap pp hello")
|
||||||
typeText(injector.parser.parseKeys("qa" + "i" + "pp<Esc>" + "q"))
|
typeText(injector.parser.parseKeys("qa" + "i" + "pp<Esc>" + "q"))
|
||||||
val register = VimPlugin.getRegister().getRegister('a')
|
assertRegister('a', "ipp<Esc>")
|
||||||
assertNotNull<Any>(register)
|
|
||||||
kotlin.test.assertEquals("ipp<Esc>", injector.parser.toKeyNotation(register.keys))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -50,7 +63,7 @@ class MacroActionTest : VimTestCase() {
|
|||||||
typeTextInFile(injector.parser.parseKeys("qa" + "i" + "<C-K>OK<Esc>" + "q"), "")
|
typeTextInFile(injector.parser.parseKeys("qa" + "i" + "<C-K>OK<Esc>" + "q"), "")
|
||||||
val register = VimPlugin.getRegister().getRegister('a')
|
val register = VimPlugin.getRegister().getRegister('a')
|
||||||
assertNotNull<Any>(register)
|
assertNotNull<Any>(register)
|
||||||
kotlin.test.assertEquals("i<C-K>OK<Esc>", injector.parser.toKeyNotation(register.keys))
|
assertRegister('a', "i<C-K>OK<Esc>")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -123,8 +136,8 @@ class MacroActionTest : VimTestCase() {
|
|||||||
assertState("4\n5\n")
|
assertState("4\n5\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Broken, see the resulting text
|
@Test
|
||||||
fun `ignore test macro with macro`() {
|
fun `test macro with macro`() {
|
||||||
val content = """
|
val content = """
|
||||||
Lorem Ipsum
|
Lorem Ipsum
|
||||||
|
|
||||||
@@ -134,16 +147,55 @@ class MacroActionTest : VimTestCase() {
|
|||||||
Cras id tellus in ex imperdiet egestas.
|
Cras id tellus in ex imperdiet egestas.
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
configureByText(content)
|
configureByText(content)
|
||||||
typeText(injector.parser.parseKeys("qa" + "l" + "q" + "qb" + "10@a" + "q" + "2@b"))
|
typeText(
|
||||||
|
injector.parser.parseKeys(
|
||||||
|
"qa" + "l" + "q" +
|
||||||
|
"qb" + "6@a" + "q" +
|
||||||
|
"^" + "3@b"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
val startOffset = content.rangeOf("rocks").startOffset
|
assertRegister('b', "6@a")
|
||||||
|
assertState("""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
waitAndAssert {
|
Lorem ipsum dolor ${c}sit amet,
|
||||||
println(fixture.editor.caretModel.offset)
|
consectetur adipiscing elit
|
||||||
println(startOffset)
|
Sed in orci mauris.
|
||||||
println()
|
Cras id tellus in ex imperdiet egestas.
|
||||||
startOffset == fixture.editor.caretModel.offset
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test macro with macro with macro`() {
|
||||||
|
val content = """
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
${c}Lorem ipsum dolor sit amet,
|
||||||
|
consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
configureByText(content)
|
||||||
|
typeText(
|
||||||
|
injector.parser.parseKeys(
|
||||||
|
"qa" + "l" + "q" +
|
||||||
|
"qb" + "3@a" + "q" +
|
||||||
|
"qc" + "2@b" + "q" +
|
||||||
|
"^" + "3@c"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
assertRegister('b', "3@a")
|
||||||
|
assertRegister('c', "2@b")
|
||||||
|
assertState("""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor ${c}sit amet,
|
||||||
|
consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent())
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -178,4 +230,33 @@ class MacroActionTest : VimTestCase() {
|
|||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestFor(issues = ["VIM-2929"])
|
||||||
|
@TestWithoutNeovim(reason = SkipNeovimReason.ACTION_COMMAND)
|
||||||
|
@Test
|
||||||
|
fun `macro to handler with exception`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
${c}consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
injector.keyGroup.putKeyMapping(MappingMode.NXO, keys("abc"), exceptionMappingOwner, ExceptionHandler(), false)
|
||||||
|
|
||||||
|
injector.registerGroup.storeText('k', "abc")
|
||||||
|
injector.registerGroup.storeText('q', "x@ky")
|
||||||
|
|
||||||
|
val exception = assertThrows<Throwable> {
|
||||||
|
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
|
||||||
|
typeText("@q")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assertEquals(ExceptionHandler.exceptionMessage, exception.cause!!.cause!!.message)
|
||||||
|
|
||||||
|
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -1025,10 +1025,10 @@ $c tw${c}o
|
|||||||
)
|
)
|
||||||
assertState(
|
assertState(
|
||||||
"""
|
"""
|
||||||
<selection>one two
|
${s}one two
|
||||||
three four
|
three four
|
||||||
five six
|
five six
|
||||||
</selection>
|
$se
|
||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@@ -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"))
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user