mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-25 07:42:59 +01:00
Compare commits
160 Commits
fd1a706e4a
...
0612367c59
Author | SHA1 | Date | |
---|---|---|---|
0612367c59 | |||
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 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
24
.github/workflows/runUiTests.yml
vendored
24
.github/workflows/runUiTests.yml
vendored
@ -8,18 +8,20 @@ jobs:
|
||||
if: github.repository == 'JetBrains/ideavim'
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions/checkout@v4
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v2.1.0
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
distribution: zulu
|
||||
java-version: 11
|
||||
- name: Setup FFmpeg
|
||||
uses: FedericoCarboni/setup-ffmpeg@v1
|
||||
uses: FedericoCarboni/setup-ffmpeg@v3
|
||||
with:
|
||||
# Not strictly necessary, but it may prevent rate limit
|
||||
# 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
|
||||
run: gradle :buildPlugin
|
||||
- name: Run Idea
|
||||
@ -27,7 +29,7 @@ jobs:
|
||||
mkdir -p build/reports
|
||||
gradle :runIdeForUiTests > build/reports/idea.log &
|
||||
- name: Wait for Idea started
|
||||
uses: jtalk/url-health-check-action@1.5
|
||||
uses: jtalk/url-health-check-action@v3
|
||||
with:
|
||||
url: http://127.0.0.1:8082
|
||||
max-attempts: 20
|
||||
@ -35,15 +37,19 @@ jobs:
|
||||
- name: Tests
|
||||
run: gradle :testUi
|
||||
- name: Move video
|
||||
if: ${{ failure() }}
|
||||
if: always()
|
||||
run: mv video build/reports
|
||||
- name: Save fails report
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@v2
|
||||
- name: Move sandbox logs
|
||||
if: always()
|
||||
run: mv build/idea-sandbox/system/log sandbox-idea-log
|
||||
- name: Save report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ui-test-fails-report-mac
|
||||
path: |
|
||||
build/reports
|
||||
sandbox-idea-log
|
||||
# build-for-ui-test-linux:
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
|
@ -6,6 +6,7 @@
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="LINE_SEPARATOR" value=" " />
|
||||
<JavaCodeStyleSettings>
|
||||
<option name="FIELD_NAME_PREFIX" value="my" />
|
||||
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />
|
||||
|
@ -1,6 +1,6 @@
|
||||
<component name="CopyrightManager">
|
||||
<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" />
|
||||
</copyright>
|
||||
</component>
|
17
.teamcity/_Self/Constants.kt
vendored
17
.teamcity/_Self/Constants.kt
vendored
@ -5,14 +5,13 @@ object Constants {
|
||||
const val EAP_CHANNEL = "eap"
|
||||
const val DEV_CHANNEL = "Dev"
|
||||
|
||||
// TODO it should be 2023.3 as soon as it releases
|
||||
const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||
const val RELEASE = "LATEST-EAP-SNAPSHOT"
|
||||
const val GITHUB_TESTS = "2023.3.2"
|
||||
const val NVIM_TESTS = "2023.3.2"
|
||||
const val PROPERTY_TESTS = "2023.3.2"
|
||||
const val LONG_RUNNING_TESTS = "2023.3.2"
|
||||
const val QODANA_TESTS = "2023.3.2"
|
||||
const val RELEASE = "2023.3.2"
|
||||
|
||||
const val RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
|
||||
const val RELEASE_EAP = "LATEST-EAP-SNAPSHOT"
|
||||
const val RELEASE_DEV = "2023.3.2"
|
||||
const val RELEASE_EAP = "2023.3.2"
|
||||
}
|
||||
|
6
.teamcity/_Self/Project.kt
vendored
6
.teamcity/_Self/Project.kt
vendored
@ -24,6 +24,7 @@ object Project : Project({
|
||||
|
||||
// Active tests
|
||||
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(PropertyBased)
|
||||
@ -38,6 +39,11 @@ object Project : Project({
|
||||
|
||||
// Common build type for all configurations
|
||||
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
|
||||
artifactRules = """
|
||||
+:build/reports => build/reports
|
||||
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
|
||||
""".trimIndent()
|
||||
|
||||
init()
|
||||
|
||||
requirements {
|
||||
|
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)
|
||||
|
||||
pWydmuch
|
||||
* [![icon][mail]](mailto:leonid989@gmail.com)
|
||||
[![icon][github]](https://github.com/Infonautica)
|
||||
|
||||
Leonid Danilov
|
||||
|
||||
Previous contributors:
|
||||
|
||||
|
@ -43,9 +43,14 @@ usual beta standards.
|
||||
* [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
|
||||
|
||||
|
@ -21,7 +21,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16")
|
||||
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")
|
||||
|
@ -49,14 +49,14 @@ buildscript {
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||
|
||||
// This is needed for jgit to connect to ssh
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache: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("io.ktor:ktor-client-core:2.3.6")
|
||||
classpath("io.ktor:ktor-client-cio:2.3.6")
|
||||
classpath("io.ktor:ktor-client-auth:2.3.6")
|
||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
|
||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
||||
classpath("io.ktor:ktor-client-core:2.3.7")
|
||||
classpath("io.ktor:ktor-client-cio:2.3.7")
|
||||
classpath("io.ktor:ktor-client-auth:2.3.7")
|
||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
|
||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
|
||||
|
||||
// This comes from the changelog plugin
|
||||
// classpath("org.jetbrains:markdown:0.3.1")
|
||||
@ -69,7 +69,7 @@ plugins {
|
||||
kotlin("jvm") version "1.8.21"
|
||||
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"
|
||||
|
||||
// ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
|
||||
@ -126,11 +126,11 @@ dependencies {
|
||||
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
||||
|
||||
// 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-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")
|
||||
antlr("org.antlr:antlr4:$antlrVersion")
|
||||
|
||||
@ -184,6 +184,14 @@ tasks {
|
||||
include("**/*test.class")
|
||||
include("**/*Tests.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) {
|
||||
@ -294,6 +302,7 @@ tasks {
|
||||
systemProperty("ide.mac.message.dialogs.as.sheets", "false")
|
||||
systemProperty("jb.privacy.policy.text", "<!--999.999-->")
|
||||
systemProperty("jb.consents.confirmation.enabled", "false")
|
||||
systemProperty("ide.show.tips.on.startup.default.value", "false")
|
||||
}
|
||||
|
||||
runPluginVerifier {
|
||||
@ -344,8 +353,6 @@ tasks {
|
||||
val pluginVersion = version
|
||||
// Don't forget to update plugin.xml
|
||||
patchPluginXml {
|
||||
sinceBuild.set("233.11799.30")
|
||||
|
||||
// Get the latest available change notes from the changelog file
|
||||
changeNotes.set(
|
||||
provider {
|
||||
|
@ -396,3 +396,19 @@ Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
|
||||
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
|
||||
|
||||
</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>
|
||||
|
@ -8,14 +8,15 @@
|
||||
|
||||
# suppress inspection "UnusedProperty" for whole file
|
||||
|
||||
ideaVersion=LATEST-EAP-SNAPSHOT
|
||||
ideaVersion=2023.3.2
|
||||
downloadIdeaSources=true
|
||||
instrumentPluginCode=true
|
||||
version=SNAPSHOT
|
||||
version=chylex-23
|
||||
javaVersion=17
|
||||
remoteRobotVersion=0.11.17
|
||||
remoteRobotVersion=0.11.21
|
||||
antlrVersion=4.10.1
|
||||
|
||||
kotlin.incremental.useClasspathSnapshot=false
|
||||
|
||||
# Please don't forget to update kotlin version in buildscript section
|
||||
# Also update kotlinxSerializationVersion version
|
||||
@ -37,4 +38,4 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
kotlin.stdlib.default.dependency=false
|
||||
|
||||
# Disable incremental annotation processing
|
||||
ksp.incremental=false
|
||||
ksp.incremental=false
|
||||
|
@ -20,17 +20,17 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.21")
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
|
||||
|
||||
implementation("io.ktor:ktor-client-core:2.3.6")
|
||||
implementation("io.ktor:ktor-client-cio:2.3.6")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
||||
implementation("io.ktor:ktor-client-auth:2.3.6")
|
||||
implementation("io.ktor:ktor-client-core:2.3.7")
|
||||
implementation("io.ktor:ktor-client-cio:2.3.7")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
|
||||
implementation("io.ktor:ktor-client-auth:2.3.7")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||
|
||||
// This is needed for jgit to connect to ssh
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache: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")
|
||||
}
|
||||
|
||||
|
@ -219,6 +219,10 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
return getInstance().enabled;
|
||||
}
|
||||
|
||||
public static boolean isNotEnabled() {
|
||||
return !isEnabled();
|
||||
}
|
||||
|
||||
public static void setEnabled(final boolean enabled) {
|
||||
if (isEnabled() == enabled) return;
|
||||
|
||||
@ -232,6 +236,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
getInstance().turnOnPlugin();
|
||||
}
|
||||
|
||||
if (enabled) {
|
||||
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
|
||||
} else {
|
||||
VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
|
||||
}
|
||||
|
||||
StatusBarIconFactory.Util.INSTANCE.updateIcon();
|
||||
}
|
||||
|
||||
|
@ -28,8 +28,11 @@ import javax.swing.KeyStroke
|
||||
* 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].
|
||||
*
|
||||
* 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 traceTime = injector.globalOptions().ideatracetime
|
||||
|
||||
@ -86,7 +89,7 @@ internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedAct
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal companion object {
|
||||
private val LOG = logger<VimTypedActionHandler>()
|
||||
}
|
||||
}
|
||||
|
@ -54,8 +54,11 @@ import javax.swing.KeyStroke
|
||||
*
|
||||
*
|
||||
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
|
||||
*
|
||||
* This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
|
||||
* way to get ideavim keys for this plugin. See VIM-3085
|
||||
*/
|
||||
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||
private val traceTime: Boolean
|
||||
get() {
|
||||
// Make sure the injector is initialized
|
||||
@ -95,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.
|
||||
// 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) {
|
||||
val start = if (traceTime) System.currentTimeMillis() else null
|
||||
@ -110,7 +113,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
||||
}
|
||||
|
||||
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)
|
||||
if (editor != null && keyStroke != null) {
|
||||
if (isOctopusEnabled(keyStroke, editor)) {
|
||||
@ -229,9 +232,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
||||
/**
|
||||
* getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
|
||||
* 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? {
|
||||
val inputEvent = e.inputEvent
|
||||
@ -239,9 +242,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
||||
val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
|
||||
val strokeCache = keyStrokeCache
|
||||
if (defaultKeyStroke != null) {
|
||||
keyStrokeCache = inputEvent to defaultKeyStroke
|
||||
keyStrokeCache = inputEvent.`when` to defaultKeyStroke
|
||||
return defaultKeyStroke
|
||||
} else if (strokeCache.first === inputEvent) {
|
||||
} else if (strokeCache.first == inputEvent.`when`) {
|
||||
keyStrokeCache = null to null
|
||||
return strokeCache.second
|
||||
}
|
||||
@ -274,7 +277,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
||||
.toSet()
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal companion object {
|
||||
@JvmField
|
||||
val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
|
||||
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.command.Argument
|
||||
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.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.common.argumentCaptured
|
||||
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.VisualOperatorActionHandler
|
||||
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.newapi.ij
|
||||
import java.util.*
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
|
||||
// todo make it multicaret
|
||||
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() {
|
||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeAction(
|
||||
editor: VimEditor,
|
||||
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.injector
|
||||
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.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@ -29,8 +26,6 @@ import java.util.*
|
||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeForAllCarets(
|
||||
editor: VimEditor,
|
||||
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.injector
|
||||
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.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@ -29,8 +26,6 @@ import java.util.*
|
||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.DELETE
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeForAllCarets(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
|
@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.extension
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
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.injector
|
||||
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.CommandAliasHandler
|
||||
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.OperatorFunction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
@ -38,6 +39,9 @@ import javax.swing.KeyStroke
|
||||
* @author vlan
|
||||
*/
|
||||
public object VimExtensionFacade {
|
||||
|
||||
private val LOG = logger<VimExtensionFacade>()
|
||||
|
||||
/** The 'map' command for mapping keys to handlers defined in extensions. */
|
||||
@JvmStatic
|
||||
public fun putExtensionHandlerMapping(
|
||||
@ -140,10 +144,12 @@ public object VimExtensionFacade {
|
||||
public fun inputKeyStroke(editor: Editor): KeyStroke {
|
||||
if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
|
||||
val input = Extension.consumeKeystroke()
|
||||
LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
|
||||
return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
|
||||
}
|
||||
|
||||
val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
|
||||
LOG.trace("Unit test mode is active")
|
||||
val mappingStack = KeyHandler.getInstance().keyStack
|
||||
mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
|
||||
if (editor.vim.vimStateMachine.isRecording) {
|
||||
@ -151,11 +157,13 @@ public object VimExtensionFacade {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LOG.trace("Getting char from the modal entry...")
|
||||
var ref: KeyStroke? = null
|
||||
ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
|
||||
ref = stroke
|
||||
false
|
||||
}
|
||||
LOG.trace("Got char $ref")
|
||||
ref
|
||||
}
|
||||
val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
|
||||
|
@ -156,11 +156,6 @@ internal class CommentaryExtension : VimExtension {
|
||||
private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
|
||||
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) {
|
||||
setOperatorFunction(this)
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
||||
|
@ -217,6 +217,8 @@ private object FileTypePatterns {
|
||||
|
||||
return if (fileTypeName in htmlLikeFileTypes) {
|
||||
this.htmlPatterns
|
||||
} else if (fileTypeName == "JAVA" || fileExtension == "java") {
|
||||
this.javaPatterns
|
||||
} else if (fileTypeName == "Ruby" || fileExtension == "rb") {
|
||||
this.rubyPatterns
|
||||
} else if (fileTypeName == "RHTML" || fileExtension == "erb") {
|
||||
@ -231,7 +233,7 @@ private object FileTypePatterns {
|
||||
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
||||
this.cMakePatterns
|
||||
} else {
|
||||
return null
|
||||
this.htmlPatterns
|
||||
}
|
||||
}
|
||||
|
||||
@ -242,6 +244,7 @@ private object FileTypePatterns {
|
||||
)
|
||||
|
||||
private val htmlPatterns = createHtmlPatterns()
|
||||
private val javaPatterns = createJavaPatterns()
|
||||
private val rubyPatterns = createRubyPatterns()
|
||||
private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
|
||||
private val phpPatterns = createPhpPatterns()
|
||||
@ -270,6 +273,14 @@ private object FileTypePatterns {
|
||||
LanguagePatterns(linkedMapOf(openingTagPattern to htmlSearchPair), linkedMapOf(closingTagPattern to htmlSearchPair))
|
||||
)
|
||||
}
|
||||
|
||||
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 {
|
||||
// Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
|
||||
|
@ -0,0 +1,30 @@
|
||||
package com.maddyhome.idea.vim.extension.surround
|
||||
|
||||
import com.intellij.util.text.CharSequenceSubSequence
|
||||
|
||||
internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
|
||||
override val length = text.length * count
|
||||
|
||||
override fun get(index: Int): Char {
|
||||
if (index < 0 || index >= length) throw IndexOutOfBoundsException()
|
||||
return text[index % text.length]
|
||||
}
|
||||
|
||||
override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
|
||||
return CharSequenceSubSequence(this, startIndex, endIndex)
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return text.repeat(count)
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun of(text: CharSequence, count: Int): CharSequence {
|
||||
return when (count) {
|
||||
0 -> ""
|
||||
1 -> text
|
||||
else -> RepeatedCharSequence(text, count)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -8,20 +8,19 @@
|
||||
package com.maddyhome.idea.vim.extension.surround
|
||||
|
||||
import com.intellij.openapi.application.runWriteAction
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.endsWithNewLine
|
||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.setChangeMarks
|
||||
import com.maddyhome.idea.vim.command.MappingMode
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||
import com.maddyhome.idea.vim.extension.VimExtension
|
||||
@ -33,12 +32,18 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
||||
import com.maddyhome.idea.vim.put.PutData
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
@ -79,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
override val isRepeatable = true
|
||||
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
setOperatorFunction(Operator())
|
||||
setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
||||
}
|
||||
}
|
||||
@ -100,7 +105,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
||||
if (lastNonWhiteSpaceOffset != null) {
|
||||
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
||||
performSurround(pair, range, it)
|
||||
performSurround(pair, range, it, count = operatorArguments.count1)
|
||||
}
|
||||
// it.moveToOffset(lineStartOffset)
|
||||
}
|
||||
@ -120,15 +125,13 @@ internal class VimSurroundExtension : VimExtension {
|
||||
|
||||
private class VSurroundHandler : ExtensionHandler {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
|
||||
// NB: Operator ignores SelectionType anyway
|
||||
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
|
||||
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
|
||||
return
|
||||
}
|
||||
runWriteAction {
|
||||
// Leave visual mode
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
||||
editor.ij.caretModel.moveToOffset(selectionStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -149,6 +152,10 @@ internal class VimSurroundExtension : VimExtension {
|
||||
|
||||
companion object {
|
||||
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
|
||||
}
|
||||
|
||||
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||
// Save old register values for carets
|
||||
val surroundings = editor.sortedCarets()
|
||||
.map {
|
||||
@ -249,26 +256,48 @@ internal class VimSurroundExtension : VimExtension {
|
||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||
// Deleting surround is just changing the surrounding to "nothing"
|
||||
val charFrom = getChar(editor.ij)
|
||||
LOG.debug("DSurroundHandler: charFrom = $charFrom")
|
||||
if (charFrom.code == 0) return
|
||||
|
||||
runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
|
||||
}
|
||||
}
|
||||
|
||||
private class Operator : OperatorFunction {
|
||||
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||
val ijEditor = editor.ij
|
||||
val c = getChar(ijEditor)
|
||||
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
|
||||
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||
val editor = vimEditor.ij
|
||||
val c = getChar(editor)
|
||||
if (c.code == 0) return true
|
||||
|
||||
val pair = getOrInputPair(c, ijEditor) ?: return false
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
val range = getSurroundRange(editor.currentCaret()) ?: return false
|
||||
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
|
||||
// Jump back to start
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
||||
val pair = getOrInputPair(c, editor) ?: return false
|
||||
|
||||
runWriteAction {
|
||||
val change = VimPlugin.getChange()
|
||||
if (supportsMultipleCursors) {
|
||||
editor.runWithEveryCaretAndRestore {
|
||||
applyOnce(editor, change, pair, count)
|
||||
}
|
||||
}
|
||||
else {
|
||||
applyOnce(editor, change, pair, count)
|
||||
// Jump back to start
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
val primaryCaret = editor.caretModel.primaryCaret
|
||||
val range = getSurroundRange(primaryCaret.vim)
|
||||
if (range != null) {
|
||||
val start = RepeatedCharSequence.of(pair.first, count)
|
||||
val end = RepeatedCharSequence.of(pair.second, count)
|
||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
|
||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
||||
val editor = caret.editor
|
||||
@ -280,96 +309,101 @@ internal class VimSurroundExtension : VimExtension {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val REGISTER = '"'
|
||||
private val LOG = logger<VimSurroundExtension>()
|
||||
|
||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
||||
private const val REGISTER = '"'
|
||||
|
||||
private val SURROUND_PAIRS = mapOf(
|
||||
'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 val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
||||
|
||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||
SURROUND_PAIRS[c]
|
||||
} else if (!c.isLetter()) {
|
||||
val s = c.toString()
|
||||
s to s
|
||||
} else {
|
||||
null
|
||||
}
|
||||
private val SURROUND_PAIRS = mapOf(
|
||||
'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 inputTagPair(editor: Editor): Pair<String, String>? {
|
||||
val tagInput = inputString(editor, "<", '>')
|
||||
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
|
||||
return if (matcher.find()) {
|
||||
val tagName = matcher.group(1)
|
||||
val tagAttributes = matcher.group(2)
|
||||
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||
SURROUND_PAIRS[c]
|
||||
} else if (!c.isLetter()) {
|
||||
val s = c.toString()
|
||||
s to s
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private fun inputFunctionName(
|
||||
editor: Editor,
|
||||
withInternalSpaces: Boolean,
|
||||
): Pair<String, String>? {
|
||||
val functionNameInput = inputString(editor, "function: ", null)
|
||||
if (functionNameInput.isEmpty()) return null
|
||||
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
||||
}
|
||||
|
||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor)
|
||||
'f' -> inputFunctionName(editor, false)
|
||||
'F' -> inputFunctionName(editor, true)
|
||||
else -> getSurroundPair(c)
|
||||
}
|
||||
|
||||
private fun getChar(editor: Editor): Char {
|
||||
val key = inputKeyStroke(editor)
|
||||
val keyChar = key.keyChar
|
||||
return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
|
||||
0.toChar()
|
||||
} else {
|
||||
keyChar
|
||||
}
|
||||
}
|
||||
|
||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
|
||||
runWriteAction {
|
||||
val editor = caret.editor
|
||||
val change = VimPlugin.getChange()
|
||||
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
private fun inputTagPair(editor: Editor): Pair<String, String>? {
|
||||
val tagInput = inputString(editor, "<", '>')
|
||||
val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
|
||||
return if (matcher.find()) {
|
||||
val tagName = matcher.group(1)
|
||||
val tagAttributes = matcher.group(2)
|
||||
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun inputFunctionName(
|
||||
editor: Editor,
|
||||
withInternalSpaces: Boolean,
|
||||
): Pair<String, String>? {
|
||||
val functionNameInput = inputString(editor, "function: ", null)
|
||||
if (functionNameInput.isEmpty()) return null
|
||||
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
||||
}
|
||||
|
||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor)
|
||||
'f' -> inputFunctionName(editor, false)
|
||||
'F' -> inputFunctionName(editor, true)
|
||||
else -> getSurroundPair(c)
|
||||
}
|
||||
|
||||
private fun getChar(editor: Editor): Char {
|
||||
val key = inputKeyStroke(editor)
|
||||
val keyChar = key.keyChar
|
||||
val res = if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
|
||||
0.toChar()
|
||||
} else {
|
||||
keyChar
|
||||
}
|
||||
LOG.trace("getChar: $res")
|
||||
return res
|
||||
}
|
||||
|
||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||
runWriteAction {
|
||||
val editor = caret.editor
|
||||
val change = VimPlugin.getChange()
|
||||
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||
|
||||
val isEOF = range.endOffset == editor.text().length
|
||||
val hasNewLine = editor.endsWithNewLine()
|
||||
val rightSurround = (if (tagsOnNewLines) {
|
||||
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.EditorMouseListener
|
||||
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.text.StringUtil
|
||||
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.moveToInlayAwareLogicalPosition
|
||||
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.listener.VimInsertListener
|
||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||
@ -89,7 +85,6 @@ import kotlin.math.min
|
||||
*/
|
||||
public class ChangeGroup : VimChangeGroupBase() {
|
||||
private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
|
||||
private var lastShownTime = 0L
|
||||
private val listener: EditorMouseListener = object : EditorMouseListener {
|
||||
override fun mouseClicked(event: EditorMouseEvent) {
|
||||
val editor = event.editor
|
||||
@ -103,10 +98,6 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
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) {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val ijContext = context.ij
|
||||
@ -645,25 +636,6 @@ public class ChangeGroup : VimChangeGroupBase() {
|
||||
avalanche: 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 alpha = nf.contains("alpha")
|
||||
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 var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
|
||||
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
|
||||
public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
|
||||
|
@ -83,11 +83,13 @@ public object IjOptions {
|
||||
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
||||
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
|
||||
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true))
|
||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
|
||||
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
|
||||
public val 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
|
||||
// derives from Option<VimInt>
|
||||
private fun <T : Option<out VimDataType>> addOption(option: T) = option.also { Options.addOption(option) }
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionPhase
|
||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
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.macro.VimMacroBase
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
|
||||
/**
|
||||
* Used to handle playback of macros
|
||||
@ -61,22 +64,36 @@ internal class MacroGroup : VimMacroBase() {
|
||||
try {
|
||||
myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
|
||||
val runnable = runnable@{
|
||||
// Handle one keystroke then queue up the next key
|
||||
for (i in 0 until total) {
|
||||
myPotemkinProgress.fraction = (i + 1).toDouble() / total
|
||||
while (keyStack.hasStroke()) {
|
||||
val key = keyStack.feedStroke()
|
||||
try {
|
||||
// Handle one keystroke then queue up the next key
|
||||
for (i in 0 until total) {
|
||||
try {
|
||||
myPotemkinProgress.checkCanceled()
|
||||
} catch (e: ProcessCanceledException) {
|
||||
return@runnable
|
||||
myPotemkinProgress.fraction = (i + 1).toDouble() / total
|
||||
while (keyStack.hasStroke()) {
|
||||
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) {
|
||||
|
@ -48,9 +48,7 @@ import com.maddyhome.idea.vim.api.options
|
||||
import com.maddyhome.idea.vim.api.visualLineToBufferLine
|
||||
import com.maddyhome.idea.vim.command.Argument
|
||||
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.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
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.ij
|
||||
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 org.jetbrains.annotations.Range
|
||||
import java.io.File
|
||||
@ -461,11 +461,13 @@ internal class MotionGroup : VimMotionGroupBase() {
|
||||
val fileEditor = event.oldEditor
|
||||
if (fileEditor is TextEditor) {
|
||||
val editor = fileEditor.editor
|
||||
ExOutputModel.getInstance(editor).clear()
|
||||
editor.vim.let { vimEditor ->
|
||||
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
|
||||
vimEditor.exitVisualMode()
|
||||
KeyHandler.getInstance().reset(vimEditor)
|
||||
if (!editor.isDisposed) {
|
||||
ExOutputModel.getInstance(editor).clear()
|
||||
editor.vim.let { vimEditor ->
|
||||
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
|
||||
vimEditor.exitVisualMode()
|
||||
KeyHandler.getInstance().reset(vimEditor)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,10 +40,6 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
||||
override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
|
||||
override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
|
||||
|
||||
private fun updateFallbackWindow(fallbackWindow: VimEditor, targetEditor: VimEditor) {
|
||||
copyPerWindowGlobalValues(fallbackWindow, targetEditor)
|
||||
}
|
||||
|
||||
companion object {
|
||||
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
|
||||
@ -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
|
||||
// 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
|
||||
//
|
||||
// XXX: event.oldEditor will must probably return a disposed editor. So, it should be treated with care
|
||||
if (event.newEditor == null) {
|
||||
(event.oldEditor as? TextEditor)?.editor?.let {
|
||||
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
|
||||
@ -68,7 +66,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
|
||||
}
|
||||
|
||||
internal class IjOptionConstants {
|
||||
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
|
||||
@Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
|
||||
companion object {
|
||||
|
||||
const val idearefactormode_keep = "keep"
|
||||
|
@ -192,8 +192,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
|
||||
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
||||
* @param direction The direction to search
|
||||
*/
|
||||
@TestOnly
|
||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
|
||||
@Override
|
||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
|
||||
@NotNull String patternOffset, Direction direction) {
|
||||
setLastUsedPattern(pattern, RE_SEARCH, true);
|
||||
lastIgnoreSmartCase = false;
|
||||
|
@ -205,7 +205,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
* @param event The change event
|
||||
*/
|
||||
override fun beforeDocumentChange(event: DocumentEvent) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
|
||||
if (event.oldLength == 0) return
|
||||
val doc = event.document
|
||||
@ -221,7 +221,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
* @param event The change event
|
||||
*/
|
||||
override fun documentChanged(event: DocumentEvent) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
|
||||
if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
|
||||
val doc = event.document
|
||||
@ -242,7 +242,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
|
||||
class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
|
||||
override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!injector.globalIjOptions().ideamarks) {
|
||||
return
|
||||
}
|
||||
@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
|
||||
}
|
||||
|
||||
override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
if (!injector.globalIjOptions().ideamarks) {
|
||||
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.injector
|
||||
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.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.RWLockLabel
|
||||
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.newapi.IjVimCaret
|
||||
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.VimPutBase
|
||||
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
|
||||
|
||||
internal class PutGroup : VimPutBase() {
|
||||
@ -189,7 +190,7 @@ internal class PutGroup : VimPutBase() {
|
||||
endOffset: Int,
|
||||
): Int {
|
||||
// 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 endLine = editor.offsetToBufferPosition(endOffset - 1).line
|
||||
|
@ -40,9 +40,15 @@ internal object IdeaSelectionControl {
|
||||
* 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,
|
||||
* 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.
|
||||
*
|
||||
* 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(
|
||||
editor: Editor,
|
||||
@ -50,6 +56,7 @@ internal object IdeaSelectionControl {
|
||||
) {
|
||||
VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
|
||||
|
||||
if (VimPlugin.isNotEnabled()) return@singleTask
|
||||
if (editor.isIdeaVimDisabledHere) return@singleTask
|
||||
|
||||
logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
|
||||
@ -121,8 +128,9 @@ internal object IdeaSelectionControl {
|
||||
}
|
||||
}
|
||||
|
||||
private fun dontChangeMode(editor: Editor): Boolean =
|
||||
editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
|
||||
private fun dontChangeMode(editor: Editor): Boolean {
|
||||
return editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
|
||||
}
|
||||
|
||||
private fun chooseNonSelectionMode(editor: Editor): Mode {
|
||||
val templateActive = editor.isTemplateActive()
|
||||
|
@ -9,10 +9,10 @@
|
||||
package com.maddyhome.idea.vim.group.visual
|
||||
|
||||
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.singleTask
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import java.awt.event.ActionEvent
|
||||
import javax.swing.Timer
|
||||
|
||||
@ -79,6 +79,11 @@ internal object VimVisualTimer {
|
||||
}
|
||||
}
|
||||
|
||||
fun drop() {
|
||||
swingTimer?.stop()
|
||||
swingTimer = null
|
||||
}
|
||||
|
||||
inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
|
||||
task(mode)
|
||||
swingTimer = null
|
||||
|
@ -11,41 +11,68 @@ 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.StartupActivity
|
||||
import com.intellij.util.SingleAlarm
|
||||
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
|
||||
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
|
||||
internal val keymapCheckRequester = SingleAlarm({ verifyKeymap() }, 5_000)
|
||||
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 : StartupActivity {
|
||||
override fun runActivity(project: Project) {
|
||||
keymapCheckRequester.request()
|
||||
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?) {
|
||||
keymapCheckRequester.request()
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
|
||||
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
||||
keymapCheckRequester.request()
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
|
||||
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
||||
keymapCheckRequester.request()
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
|
||||
}
|
||||
|
||||
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 (isNotActualKeyPress(dataContext)) return false
|
||||
return true
|
||||
@ -229,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.
|
||||
* 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
|
||||
|
@ -81,7 +81,7 @@ private fun Editor.guicursorMode(): GuiCursorMode {
|
||||
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
|
||||
|
||||
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)
|
||||
|
||||
// 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() {
|
||||
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
|
||||
val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
|
||||
this.caretModel.allCarets.forEach {
|
||||
|
@ -335,7 +335,7 @@ public class EditorHelper {
|
||||
|
||||
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
|
||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
|
||||
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
||||
|
||||
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
||||
|
@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.codeWithMe.ClientId
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.CaretState
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||
import java.awt.Component
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JTable
|
||||
@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
|
||||
*/
|
||||
internal val Editor.vimLine: Int
|
||||
get() = this.caretModel.currentCaret.vimLine
|
||||
|
||||
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
|
||||
val caretModel = this.caretModel
|
||||
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
|
||||
if (carets == null || carets.size == 1) {
|
||||
action()
|
||||
}
|
||||
else {
|
||||
var initialDocumentSize = this.document.textLength
|
||||
var documentSizeDifference = 0
|
||||
|
||||
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
|
||||
val restoredCarets = mutableListOf<CaretState>()
|
||||
|
||||
caretModel.removeSecondaryCarets()
|
||||
|
||||
for ((selectionStart, selectionEnd) in caretOffsets) {
|
||||
if (selectionStart == selectionEnd) {
|
||||
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
|
||||
}
|
||||
else {
|
||||
caretModel.primaryCaret.setSelection(
|
||||
selectionStart + documentSizeDifference,
|
||||
selectionEnd + documentSizeDifference
|
||||
)
|
||||
}
|
||||
|
||||
action()
|
||||
restoredCarets.add(caretModel.caretsAndSelections.single())
|
||||
|
||||
val documentLength = this.document.textLength
|
||||
documentSizeDifference += documentLength - initialDocumentSize
|
||||
initialDocumentSize = documentLength
|
||||
}
|
||||
|
||||
caretModel.caretsAndSelections = restoredCarets
|
||||
}
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
|
||||
}
|
||||
|
||||
private fun vimEnabled(editor: Editor?): Boolean {
|
||||
if (!VimPlugin.isEnabled()) return false
|
||||
if (VimPlugin.isNotEnabled()) return false
|
||||
if (editor != null && editor.isIdeaVimDisabledHere) return false
|
||||
return true
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
||||
import com.maddyhome.idea.vim.api.options
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
|
||||
@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import kotlin.math.roundToInt
|
||||
@ -56,7 +56,7 @@ internal object ScrollViewHelper {
|
||||
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
||||
val topLine = getVisualLineAtTopOfScreen(editor)
|
||||
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
||||
val lastLine = vimEditor.getVisualLineCount() - 1
|
||||
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
|
||||
|
||||
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
||||
val scrollOffset = injector.options(vimEditor).scrolloff
|
||||
|
@ -14,6 +14,7 @@ import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.undo.UndoManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
|
||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||
|
||||
/**
|
||||
@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
|
||||
if (injector.globalIjOptions().oldundo) {
|
||||
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
||||
restoreVisualMode(editor)
|
||||
} else {
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
if (undoManager.isRedoAvailable(fileEditor)) {
|
||||
if (injector.globalIjOptions().oldundo) {
|
||||
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
||||
restoreVisualMode(editor)
|
||||
} else {
|
||||
undoManager.redo(fileEditor)
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
val hasChanges: Boolean
|
||||
get() = changeListener.hasChanged || initialPath != editor.getPath()
|
||||
}
|
||||
|
||||
private fun restoreVisualMode(editor: VimEditor) {
|
||||
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
|
||||
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
|
||||
|
||||
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
|
||||
// identified as visual block mode, leading to false positives.
|
||||
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
|
||||
// visual block mode.
|
||||
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
|
||||
SelectionType.CHARACTER_WISE
|
||||
else
|
||||
detectedMode
|
||||
|
||||
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -124,10 +124,6 @@ internal var Editor.vimMorePanel: ExOutputPanel? by userData()
|
||||
internal var Editor.vimExOutput: ExOutputModel? 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()
|
||||
|
||||
/**
|
||||
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.group.NotificationService
|
||||
import com.maddyhome.idea.vim.icons.VimIcons
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
|
||||
VimPlugin.getPluginId(),
|
||||
updateTimestampProperty = PROPERTY_NAME,
|
||||
NotificationService.IDEAVIM_STICKY_GROUP,
|
||||
VimIcons.IDEAVIM,
|
||||
) {
|
||||
|
||||
override fun skipUpdateCheck(): Boolean = !VimPlugin.isEnabled() || "dev" in VimPlugin.getVersion()
|
||||
|
||||
companion object {
|
||||
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
|
||||
val instance: VimStandalonePluginUpdateChecker = service()
|
||||
}
|
||||
}
|
@ -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
|
||||
|
||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
@ -49,7 +49,7 @@ internal object AppCodeTemplates {
|
||||
}
|
||||
|
||||
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) {
|
||||
val myEditor = editor
|
||||
|
@ -59,7 +59,7 @@ internal object IdeaSpecifics {
|
||||
private var completionPrevDocumentLength: Int? = null
|
||||
private var completionPrevDocumentOffset: Int? = null
|
||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
@ -92,7 +92,7 @@ internal object IdeaSpecifics {
|
||||
}
|
||||
|
||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val editor = editor
|
||||
if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
|
||||
@ -138,7 +138,7 @@ internal object IdeaSpecifics {
|
||||
//region Enter insert mode for surround templates without selection
|
||||
class VimTemplateManagerListener : TemplateManagerListener {
|
||||
override fun templateStarted(state: TemplateState) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
val editor = state.editor ?: return
|
||||
|
||||
state.addTemplateStateListener(object : TemplateEditingAdapter() {
|
||||
@ -176,7 +176,7 @@ internal object IdeaSpecifics {
|
||||
//region Register shortcuts for lookup and perform partial reset
|
||||
class LookupTopicListener : LookupManagerListener {
|
||||
override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
// Lookup opened
|
||||
if (oldLookup == null && newLookup is LookupImpl) {
|
||||
@ -199,7 +199,7 @@ internal object IdeaSpecifics {
|
||||
//region Hide Vim search highlights when showing IntelliJ search results
|
||||
class VimFindModelListener : FindModelListener {
|
||||
override fun findNextModelChanged() {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
VimPlugin.getSearch().clearSearchHighlight()
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ internal class RiderActionListener : AnActionListener {
|
||||
|
||||
private var editor: Editor? = null
|
||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
@ -36,7 +36,7 @@ internal class RiderActionListener : AnActionListener {
|
||||
}
|
||||
|
||||
override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
//region Extend Selection for Rider
|
||||
when (ActionManager.getInstance().getId(action)) {
|
||||
|
@ -9,6 +9,7 @@
|
||||
package com.maddyhome.idea.vim.listener
|
||||
|
||||
import com.intellij.ide.ui.UISettings
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
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.SelectionListener
|
||||
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.EditorImpl
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
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.EditorWindow
|
||||
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.Key
|
||||
import com.intellij.openapi.util.removeUserData
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.util.ExceptionUtil
|
||||
import com.jetbrains.rd.util.lifetime.Lifetime
|
||||
import com.maddyhome.idea.vim.EventFacade
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
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.group.EditorGroup
|
||||
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.OptionGroup
|
||||
import com.maddyhome.idea.vim.group.ScrollGroup
|
||||
@ -71,10 +71,9 @@ import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
|
||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
|
||||
import com.maddyhome.idea.vim.handler.correctorRequester
|
||||
import com.maddyhome.idea.vim.handler.keymapCheckRequester
|
||||
import com.maddyhome.idea.vim.handler.keyCheckRequests
|
||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
|
||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.forceBarCursor
|
||||
@ -91,11 +90,16 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
|
||||
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||
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.MouseEvent
|
||||
import javax.swing.SwingUtilities
|
||||
@ -131,7 +135,7 @@ internal object VimListenerManager {
|
||||
GlobalListeners.enable()
|
||||
EditorListeners.addAll()
|
||||
correctorRequester.request()
|
||||
keymapCheckRequester.request()
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
}
|
||||
|
||||
fun turnOff() {
|
||||
@ -155,6 +159,13 @@ internal object VimListenerManager {
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||
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)
|
||||
|
||||
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
|
||||
@ -162,6 +173,8 @@ internal object VimListenerManager {
|
||||
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
|
||||
|
||||
EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
|
||||
val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
|
||||
eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
|
||||
}
|
||||
|
||||
fun disable() {
|
||||
@ -172,10 +185,12 @@ internal object VimListenerManager {
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
|
||||
optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
|
||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
object EditorListeners {
|
||||
fun addAll() {
|
||||
val initialisedEditors = mutableSetOf<Editor>()
|
||||
@ -214,49 +229,67 @@ internal object VimListenerManager {
|
||||
}
|
||||
|
||||
fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
|
||||
val pluginLifetime = VimPlugin.getInstance().createLifetime()
|
||||
val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
|
||||
val disposable =
|
||||
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
|
||||
// As I understand, there is no need to pass a disposable that also disposes on editor close
|
||||
// because all editor resources will be garbage collected anyway on editor close
|
||||
val disposable = editor.project?.vimDisposable ?: return
|
||||
|
||||
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||
editor.putUserData(editorListenersDisposable, listenersDisposable)
|
||||
|
||||
Disposer.register(listenersDisposable) {
|
||||
if (VimListenerTestObject.enabled) {
|
||||
VimListenerTestObject.disposedCounter += 1
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
VimPlugin.getOptionGroup().initialiseLocalOptions(editor.vim, openingEditor, scenario)
|
||||
val vimEditor = editor.vim
|
||||
VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
|
||||
|
||||
val eventFacade = EventFacade.getInstance()
|
||||
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, disposable)
|
||||
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, disposable)
|
||||
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, disposable)
|
||||
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, disposable)
|
||||
eventFacade.addCaretListener(editor, EditorCaretHandler, disposable)
|
||||
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, listenersDisposable)
|
||||
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, listenersDisposable)
|
||||
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, listenersDisposable)
|
||||
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, listenersDisposable)
|
||||
eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
fun remove(editor: Editor, isReleased: Boolean) {
|
||||
editor.contentComponent.removeKeyListener(VimKeyListener)
|
||||
val eventFacade = EventFacade.getInstance()
|
||||
eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
|
||||
eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)
|
||||
eventFacade.removeEditorSelectionListener(editor, EditorSelectionHandler)
|
||||
eventFacade.removeComponentMouseListener(editor.contentComponent, ComponentMouseListener)
|
||||
eventFacade.removeCaretListener(editor, EditorCaretHandler)
|
||||
val editorDisposable = editor.getUserData(editorListenersDisposable)
|
||||
if (editorDisposable != null) {
|
||||
Disposer.dispose(editorDisposable)
|
||||
}
|
||||
else StrictMode.fail("Editor doesn't have disposable attached. $editor")
|
||||
|
||||
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 {
|
||||
override fun caretAdded(event: CaretEvent) {
|
||||
if (vimDisabled(event.editor)) return
|
||||
@ -271,7 +304,17 @@ internal object VimListenerManager {
|
||||
|
||||
class VimFileEditorManagerListener : FileEditorManagerListener {
|
||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||
if (!VimPlugin.isEnabled()) return
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val newEditor = event.newEditor
|
||||
if (newEditor is TextEditor) {
|
||||
val editor = newEditor.editor
|
||||
if (editor.isInsertMode) {
|
||||
VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
}
|
||||
|
||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
SearchGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
@ -336,18 +379,18 @@ internal object VimListenerManager {
|
||||
|
||||
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
||||
}
|
||||
|
||||
VimStandalonePluginUpdateChecker.instance.pluginUsed()
|
||||
}
|
||||
|
||||
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(
|
||||
source: FileEditorManager,
|
||||
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
|
||||
// created (and the list of editors and providers is passed here) and added to an EditorWindow tab, inside a
|
||||
@ -405,6 +448,7 @@ internal object VimListenerManager {
|
||||
*/
|
||||
override fun selectionChanged(selectionEvent: SelectionEvent) {
|
||||
if (selectionEvent.editor.isIdeaVimDisabledHere) return
|
||||
VimVisualTimer.drop()
|
||||
val editor = selectionEvent.editor
|
||||
val document = editor.document
|
||||
val ijVimEditor = IjVimEditor(editor)
|
||||
@ -698,6 +742,11 @@ internal object VimListenerManager {
|
||||
}
|
||||
}
|
||||
|
||||
internal object VimListenerTestObject {
|
||||
var enabled: Boolean = false
|
||||
var disposedCounter = 0
|
||||
}
|
||||
|
||||
private object MouseEventsDataHolder {
|
||||
const val skipEvents = 3
|
||||
var skipNDragEvents = skipEvents
|
||||
|
@ -54,7 +54,6 @@ import com.maddyhome.idea.vim.helper.isTemplateActive
|
||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
|
||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
|
||||
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.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
@ -67,7 +66,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
companion object {
|
||||
// For cases where Editor does not have a project (for some reason)
|
||||
// It's something IJ Platform related and stored here because of this reason
|
||||
const val DEFAULT_PROJECT_ID = "no project"
|
||||
const val DEFAULT_PROJECT_ID = "no project"
|
||||
}
|
||||
|
||||
// All the editor actions should be performed with top level editor!!!
|
||||
@ -82,11 +81,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
|
||||
set(value) {
|
||||
editor.vimChangeActionSwitchMode = value
|
||||
}
|
||||
override var vimKeepingVisualOperatorAction: Boolean
|
||||
get() = editor.vimKeepingVisualOperatorAction
|
||||
set(value) {
|
||||
editor.vimKeepingVisualOperatorAction = value
|
||||
}
|
||||
|
||||
override fun fileSize(): Long = editor.fileSize.toLong()
|
||||
|
||||
|
@ -18,6 +18,7 @@ import com.intellij.util.IJSwingUtilities;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
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.MessageHelper;
|
||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||
@ -59,6 +60,8 @@ public class ExOutputPanel extends JPanel {
|
||||
|
||||
private boolean myActive = false;
|
||||
|
||||
private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
|
||||
|
||||
private ExOutputPanel(@NotNull Editor editor) {
|
||||
myEditor = editor;
|
||||
|
||||
@ -299,6 +302,10 @@ public class ExOutputPanel extends JPanel {
|
||||
final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
|
||||
final List<KeyStroke> keys = new ArrayList<>(1);
|
||||
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);
|
||||
ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
|
||||
VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
|
||||
@ -358,7 +365,7 @@ public class ExOutputPanel extends JPanel {
|
||||
public static class LafListener implements LafManagerListener {
|
||||
@Override
|
||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||
if (!VimPlugin.isEnabled()) return;
|
||||
if (VimPlugin.isNotEnabled()) return;
|
||||
// Calls updateUI on this and child components
|
||||
for (Editor editor : HelperKt.localEditors()) {
|
||||
if (!ExOutputPanel.isPanelActive(editor)) continue;
|
||||
|
@ -8,6 +8,9 @@
|
||||
|
||||
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.api.VimEditor
|
||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||
@ -22,13 +25,19 @@ import javax.swing.KeyStroke
|
||||
* @author dhleong
|
||||
*/
|
||||
public object ModalEntry {
|
||||
|
||||
public val LOG: Logger = logger<ModalEntry>()
|
||||
|
||||
public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
|
||||
// Firstly we pull the unfinished keys of the current mapping
|
||||
val mappingStack = KeyHandler.getInstance().keyStack
|
||||
LOG.trace("Dumping key stack:")
|
||||
LOG.trace { mappingStack.dump() }
|
||||
var stroke = mappingStack.feedSomeStroke()
|
||||
while (stroke != null) {
|
||||
val result = processor(stroke)
|
||||
if (!result) {
|
||||
LOG.trace("Got char from mapping stack")
|
||||
return
|
||||
}
|
||||
stroke = mappingStack.feedSomeStroke()
|
||||
@ -55,6 +64,7 @@ public object ModalEntry {
|
||||
KeyHandler.getInstance().modalEntryKeys += stroke
|
||||
}
|
||||
if (!processor(stroke)) {
|
||||
LOG.trace("Got char from keyboard input: $stroke. Event: $e")
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this)
|
||||
loop.exit()
|
||||
}
|
||||
|
@ -19,7 +19,6 @@ import com.intellij.openapi.wm.StatusBarWidget
|
||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
|
||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
|
||||
import com.intellij.util.Consumer
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
@ -68,13 +67,6 @@ internal object ShowCmd {
|
||||
internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
|
||||
override fun onGlobalOptionChanged() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -453,7 +453,7 @@ public class ExEntryPanel extends JPanel {
|
||||
public static class LafListener implements LafManagerListener {
|
||||
@Override
|
||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||
if (!VimPlugin.isEnabled()) return;
|
||||
if (VimPlugin.isNotEnabled()) return;
|
||||
// Calls updateUI on this and child components
|
||||
if (ExEntryPanel.isInstanceWithShortcutsActive()) {
|
||||
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 {
|
||||
|
||||
fun correctSelection(editor: Editor) {
|
||||
val action: () -> Unit = {
|
||||
val mode = editor.vim.mode
|
||||
if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
|
||||
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
|
||||
}
|
||||
}
|
||||
sealed interface Action {
|
||||
object RemoveSelection : Action
|
||||
class SetMode(val newMode: Mode) : Action
|
||||
class MoveToOffset(val newOffset: Int) : Action
|
||||
}
|
||||
|
||||
if (editor.hasBlockOrUnderscoreCaret()) {
|
||||
TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
|
||||
if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
|
||||
editor.caretModel.moveToOffset(editor.caretModel.offset - 1)
|
||||
fun applyCorrections(corrections: List<Action>, editor: Editor) {
|
||||
val correctionsApplier = {
|
||||
corrections.forEach { correction ->
|
||||
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) {
|
||||
val selStart = editor.selectionModel.selectionStart
|
||||
val selEnd = editor.selectionModel.selectionEnd
|
||||
lookup.performGuardedChange(action)
|
||||
lookup.performGuardedChange {
|
||||
correctionsApplier()
|
||||
}
|
||||
lookup.addLookupListener(object : LookupListener {
|
||||
override fun beforeItemSelected(event: LookupEvent): Boolean {
|
||||
// FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
|
||||
@ -82,7 +81,41 @@ internal object IdeaRefactorModeHelper {
|
||||
}
|
||||
})
|
||||
} 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
|
||||
|
||||
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.VimEditor
|
||||
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.expressions.Scope
|
||||
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) {
|
||||
super.storeVariable(variable, value, editor, context, vimContext)
|
||||
|
||||
@ -47,4 +53,49 @@ internal class IjVariableService : VimVariableServiceBase() {
|
||||
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>
|
@ -226,12 +226,12 @@
|
||||
|
||||
<!-- Change -->
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>
|
||||
<!-- <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>-->
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>
|
||||
<!-- <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>-->
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/>
|
||||
@ -329,8 +329,8 @@
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="N" keys="«C-R»"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="N"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="NX" keys="U,«C-R»"/>
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="NX"/>
|
||||
|
||||
<!-- Keys -->
|
||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>
|
||||
|
@ -1,12 +1,4 @@
|
||||
<!--
|
||||
~ Copyright 2003-2023 The IdeaVim authors
|
||||
~
|
||||
~ Use of this source code is governed by an MIT-style
|
||||
~ license that can be found in the LICENSE.txt file or at
|
||||
~ https://opensource.org/licenses/MIT.
|
||||
-->
|
||||
|
||||
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<name>IdeaVim</name>
|
||||
<id>IdeaVIM</id>
|
||||
<description><![CDATA[
|
||||
@ -21,13 +13,13 @@
|
||||
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
|
||||
</ul>
|
||||
]]></description>
|
||||
<version>SNAPSHOT</version>
|
||||
<version>chylex</version>
|
||||
<vendor>JetBrains</vendor>
|
||||
|
||||
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
|
||||
<!-- Check for [Version Update] tag in YouTrack as well -->
|
||||
<!-- Also, please update the value in build.gradle.kts file-->
|
||||
<idea-version since-build="233.11799.30"/>
|
||||
<idea-version since-build="232"/>
|
||||
|
||||
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
|
||||
<depends>com.intellij.modules.platform</depends>
|
||||
@ -37,6 +29,8 @@
|
||||
<!--suppress PluginXmlValidity -->
|
||||
<depends optional="true" config-file="ides/ideavim-withRider.xml">com.intellij.modules.rider</depends>
|
||||
<!--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="ideavim-withAceJump.xml">AceJump</depends>
|
||||
|
||||
@ -63,6 +57,8 @@
|
||||
<extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
|
||||
<with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
|
||||
</extensionPoint>
|
||||
|
||||
<extensionPoint interface="com.maddyhome.idea.vim.ide.ClionNovaProvider" dynamic="true" name="clionNovaProvider"/>
|
||||
</extensionPoints>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij">
|
||||
@ -70,7 +66,9 @@
|
||||
<projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
|
||||
<projectService serviceImplementation="com.maddyhome.idea.vim.group.LastTabService"/>
|
||||
<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::Macro" implementation="com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetFactory"/>
|
||||
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
|
||||
|
||||
@ -154,6 +152,7 @@
|
||||
|
||||
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction"/>
|
||||
<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">
|
||||
<action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc"
|
||||
@ -162,5 +161,6 @@
|
||||
</group>
|
||||
|
||||
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
|
||||
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
|
||||
</actions>
|
||||
</idea-plugin>
|
||||
|
@ -84,6 +84,8 @@ action.VimShortcutKeyAction.text=Shortcuts
|
||||
action.VimActions.text=Vim Actions
|
||||
action.not.found.0=Action not found: {0}
|
||||
|
||||
action.CustomizeModeWidget.text=Mode Widget Settings
|
||||
|
||||
action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
|
||||
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
|
||||
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.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}.
|
||||
|
@ -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.fixtures.CodeInsightTestFixture
|
||||
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.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
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.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 kotlin.test.fail
|
||||
|
||||
@ -129,3 +134,15 @@ internal fun <T> product(vararg elements: List<T>): List<List<T>> {
|
||||
}
|
||||
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")
|
||||
|
@ -102,7 +102,7 @@ import kotlin.test.assertTrue
|
||||
* This is done as we have no mechanism to guarantee compatibility as we update this test case.
|
||||
* Feel free to copy this class into your plugin, or copy just needed functions.
|
||||
*/
|
||||
@RunInEdt(writeIntent = true)
|
||||
@RunInEdt
|
||||
@ApiStatus.Internal
|
||||
abstract class VimTestCase {
|
||||
protected lateinit var fixture: CodeInsightTestFixture
|
||||
@ -123,7 +123,7 @@ abstract class VimTestCase {
|
||||
VimPlugin.getOptionGroup().resetAllOptionsForTesting()
|
||||
VimPlugin.getKey().resetKeyMappings()
|
||||
VimPlugin.getSearch().resetState()
|
||||
if (!VimPlugin.isEnabled()) VimPlugin.setEnabled(true)
|
||||
if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true)
|
||||
injector.globalOptions().ideastrictmode = true
|
||||
Checks.reset()
|
||||
clearClipboard()
|
||||
@ -152,8 +152,8 @@ abstract class VimTestCase {
|
||||
|
||||
@AfterEach
|
||||
open fun tearDown(testInfo: TestInfo) {
|
||||
val swingTimer = swingTimer
|
||||
swingTimer?.stop()
|
||||
swingTimer = null
|
||||
val bookmarksManager = BookmarksManager.getInstance(fixture.project)
|
||||
bookmarksManager?.bookmarks?.forEach { bookmark ->
|
||||
bookmarksManager.remove(bookmark)
|
||||
@ -170,6 +170,7 @@ abstract class VimTestCase {
|
||||
injector.jumpService.resetJumps()
|
||||
VimPlugin.getChange().resetRepeat()
|
||||
VimPlugin.getKey().savedShortcutConflicts.clear()
|
||||
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
|
||||
|
||||
// Tear down neovim
|
||||
NeovimTesting.tearDown(testInfo)
|
||||
|
@ -7,22 +7,40 @@
|
||||
*/
|
||||
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.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.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.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
|
||||
import org.jetbrains.plugins.ideavim.rangeOf
|
||||
import org.jetbrains.plugins.ideavim.waitAndAssert
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertNotNull
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
class MacroActionTest : VimTestCase() {
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
|
||||
}
|
||||
|
||||
// |q|
|
||||
@Test
|
||||
fun testRecordMacro() {
|
||||
@ -178,4 +196,33 @@ class MacroActionTest : VimTestCase() {
|
||||
""".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(
|
||||
"""
|
||||
<selection>one two
|
||||
${s}one two
|
||||
three four
|
||||
five six
|
||||
</selection>
|
||||
$se
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
|
@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
|
||||
|
||||
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
||||
configureByText(before)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(keys)
|
||||
assertState(after)
|
||||
assertState(Mode.NORMAL())
|
||||
|
@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
|
||||
|
||||
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
||||
configureByText(before)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(keys)
|
||||
assertState(after)
|
||||
assertState(Mode.NORMAL())
|
||||
|
@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
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.common.Direction
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
|
||||
@Test
|
||||
fun testWithoutSpaces() {
|
||||
configureByText("test<caret>test")
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(injector.parser.parseKeys("gn"))
|
||||
assertOffset(7)
|
||||
assertSelection("test")
|
||||
|
@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
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.common.Direction
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
|
||||
@Test
|
||||
fun testWithoutSpaces() {
|
||||
configureByText("tes<caret>ttest")
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||
typeText(injector.parser.parseKeys("gN"))
|
||||
assertOffset(0)
|
||||
assertSelection("test")
|
||||
|
@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action.motion.search
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() {
|
||||
|
||||
private fun doTestWithSearch(keys: String, before: String, after: String) {
|
||||
doTest(keys, before, after) {
|
||||
VimPlugin.getSearch().setLastSearchState(it, "all", "", Direction.FORWARDS)
|
||||
VimPlugin.getSearch().setLastSearchState(it.vim, "all", "", Direction.FORWARDS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,26 +10,45 @@ package org.jetbrains.plugins.ideavim.ex.implementation.commands
|
||||
import com.intellij.idea.TestFor
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.testFramework.LoggedErrorProcessor
|
||||
import com.intellij.testFramework.TestLoggerFactory.TestLoggerAssertionError
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
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.ex.ExException
|
||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
||||
import com.maddyhome.idea.vim.history.HistoryConstants
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import org.jetbrains.plugins.ideavim.ExceptionHandler
|
||||
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.jetbrains.plugins.ideavim.assertThrowsLogError
|
||||
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
|
||||
import org.jetbrains.plugins.ideavim.waitAndAssert
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.assertThrows
|
||||
import javax.swing.JTextArea
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertIs
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
class MapCommandTest : VimTestCase() {
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
|
||||
}
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.UNCLEAR)
|
||||
@Test
|
||||
fun testMapKtoJ() {
|
||||
@ -876,13 +895,14 @@ n ,f <Plug>Foo
|
||||
indicateErrors = true,
|
||||
null,
|
||||
)
|
||||
val exception = assertThrows<Throwable> {
|
||||
val exception = assertThrowsLogError<TestLoggerAssertionError> {
|
||||
typeText(injector.parser.parseKeys("t"))
|
||||
}
|
||||
assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
|
||||
assertIs<ExException>(exception.cause) // Exception is wrapped into LOG.error twice
|
||||
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E121: Undefined variable: s:mapping")
|
||||
editor.caretModel.allCarets.forEach { Disposer.dispose(it) }
|
||||
}
|
||||
|
||||
// todo keyPresses invoked inside a script should have access to the script context
|
||||
@ -923,11 +943,10 @@ n ,f <Plug>Foo
|
||||
""".trimIndent()
|
||||
configureByJavaText(text)
|
||||
|
||||
val exception = assertThrows<Throwable> {
|
||||
assertThrowsLogError<ExException> {
|
||||
typeText(commandToKeys("inoremap <expr> <cr> unknownFunction() ? '\\<C-y>' : '\\<C-g>u\\<CR>'"))
|
||||
typeText(injector.parser.parseKeys("i<CR>"))
|
||||
}
|
||||
assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
|
||||
|
||||
assertPluginError(true)
|
||||
assertPluginErrorMessageContains("E117: Unknown function: unknownFunction")
|
||||
@ -1097,4 +1116,32 @@ n ,i <Action>(Back)
|
||||
Cras id tellus in ex imperdiet egestas.
|
||||
""".trimIndent())
|
||||
}
|
||||
|
||||
@TestFor(issues = ["VIM-2929"])
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.ACTION_COMMAND)
|
||||
@Test
|
||||
fun `mapping 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)
|
||||
|
||||
typeText(commandToKeys("map k abcx"))
|
||||
|
||||
val exception = assertThrows<Throwable> {
|
||||
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
|
||||
typeText("k")
|
||||
}
|
||||
}
|
||||
assertEquals(ExceptionHandler.exceptionMessage, exception.cause!!.cause!!.message)
|
||||
|
||||
assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,9 @@
|
||||
package org.jetbrains.plugins.ideavim.extension
|
||||
|
||||
import com.intellij.ide.plugins.PluginManagerCore
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.invokeLater
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.testFramework.PlatformTestUtil
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
@ -41,28 +43,23 @@ import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class OpMappingTest : VimTestCase() {
|
||||
private var initialized = false
|
||||
|
||||
private lateinit var extension: ExtensionBeanClass
|
||||
|
||||
private var disposable: Disposable = Disposer.newDisposable()
|
||||
|
||||
@BeforeEach
|
||||
override fun setUp(testInfo: TestInfo) {
|
||||
super.setUp(testInfo)
|
||||
if (!initialized) {
|
||||
initialized = true
|
||||
extension = TestExtension.createBean()
|
||||
|
||||
extension = TestExtension.createBean()
|
||||
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
|
||||
enableExtensions("TestExtension")
|
||||
}
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
|
||||
enableExtensions("TestExtension")
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
override fun tearDown(testInfo: TestInfo) {
|
||||
@Suppress("DEPRECATION")
|
||||
VimExtension.EP_NAME.point.unregisterExtension(extension)
|
||||
super.tearDown(super.testInfo)
|
||||
Disposer.dispose(disposable)
|
||||
super.tearDown(testInfo)
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -138,13 +135,13 @@ class OpMappingTest : VimTestCase() {
|
||||
typeText(injector.parser.parseKeys("Q"))
|
||||
assertState("I$c found it in a legendary land")
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
VimExtension.EP_NAME.point.unregisterExtension(extension)
|
||||
Disposer.dispose(disposable)
|
||||
disposable = Disposer.newDisposable()
|
||||
assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
|
||||
typeText(injector.parser.parseKeys("Q"))
|
||||
assertState("I$c found it in a legendary land")
|
||||
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
|
||||
assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
|
||||
enableExtensions("TestExtension")
|
||||
typeText(injector.parser.parseKeys("Q"))
|
||||
@ -158,12 +155,12 @@ class OpMappingTest : VimTestCase() {
|
||||
assertState("I$c found it in a legendary land")
|
||||
|
||||
enterCommand("set noTestExtension")
|
||||
@Suppress("DEPRECATION")
|
||||
VimExtension.EP_NAME.point.unregisterExtension(extension)
|
||||
Disposer.dispose(disposable)
|
||||
disposable = Disposer.newDisposable()
|
||||
typeText(injector.parser.parseKeys("Q"))
|
||||
assertState("I$c found it in a legendary land")
|
||||
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
|
||||
enableExtensions("TestExtension")
|
||||
typeText(injector.parser.parseKeys("Q"))
|
||||
assertState("I ${c}found it in a legendary land")
|
||||
@ -201,19 +198,20 @@ class PlugExtensionsTest : VimTestCase() {
|
||||
|
||||
private lateinit var extension: ExtensionBeanClass
|
||||
|
||||
private var disposable: Disposable = Disposer.newDisposable()
|
||||
|
||||
@BeforeEach
|
||||
override fun setUp(testInfo: TestInfo) {
|
||||
super.setUp(testInfo)
|
||||
configureByText("\n")
|
||||
|
||||
extension = TestExtension.createBean()
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
override fun tearDown(testInfo: TestInfo) {
|
||||
@Suppress("DEPRECATION")
|
||||
VimExtension.EP_NAME.point.unregisterExtension(extension)
|
||||
Disposer.dispose(disposable)
|
||||
super.tearDown(super.testInfo)
|
||||
}
|
||||
|
||||
@ -244,19 +242,20 @@ class PlugMissingKeysTest : VimTestCase() {
|
||||
|
||||
private lateinit var extension: ExtensionBeanClass
|
||||
|
||||
private var disposable: Disposable = Disposer.newDisposable()
|
||||
|
||||
@BeforeEach
|
||||
override fun setUp(testInfo: TestInfo) {
|
||||
super.setUp(testInfo)
|
||||
configureByText("\n")
|
||||
|
||||
extension = TestExtension.createBean()
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
|
||||
VimExtension.EP_NAME.point.registerExtension(extension, disposable)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
override fun tearDown(testInfo: TestInfo) {
|
||||
@Suppress("DEPRECATION")
|
||||
VimExtension.EP_NAME.point.unregisterExtension(extension)
|
||||
Disposer.dispose(disposable)
|
||||
super.tearDown(super.testInfo)
|
||||
}
|
||||
|
||||
|
@ -14,16 +14,16 @@ import com.intellij.codeInsight.template.TemplateManager
|
||||
import com.intellij.codeInsight.template.impl.ConstantNode
|
||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
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.group.visual.IdeaSelectionControl
|
||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
||||
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.listener.VimListenerManager
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.OptionConstants
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.mode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestOptionConstants
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
@ -33,6 +33,7 @@ import org.jetbrains.plugins.ideavim.impl.TraceOptions
|
||||
import org.jetbrains.plugins.ideavim.impl.VimOption
|
||||
import org.jetbrains.plugins.ideavim.waitAndAssert
|
||||
import org.jetbrains.plugins.ideavim.waitAndAssertMode
|
||||
import kotlin.test.assertNull
|
||||
|
||||
@TraceOptions(TestOptionConstants.selectmode)
|
||||
class IdeaVisualControlTest : VimTestCase() {
|
||||
@ -764,6 +765,23 @@ class IdeaVisualControlTest : VimTestCase() {
|
||||
assertCaretsVisualAttributes()
|
||||
}
|
||||
|
||||
@OptionTest(VimOption(TestOptionConstants.selectmode, limitedValues = [""]))
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||
fun `test control selection interruption`() {
|
||||
configureByText(
|
||||
"""
|
||||
Lorem Ipsum
|
||||
|
||||
I $s${c}found$se it in a legendary land
|
||||
consectetur adipiscing elit
|
||||
""".trimIndent(),
|
||||
)
|
||||
|
||||
IdeaSelectionControl.controlNonVimSelectionChange(fixture.editor)
|
||||
typeText(injector.parser.parseKeys("V"))
|
||||
assertNull(VimVisualTimer.swingTimer)
|
||||
}
|
||||
|
||||
private fun startDummyTemplate() {
|
||||
TemplateManagerImpl.setTemplateTesting(fixture.testRootDisposable)
|
||||
val templateManager = TemplateManager.getInstance(fixture.project)
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* 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.listener
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.listener.VimListenerTestObject
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
class VimListenersTest : VimTestCase() {
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
VimListenerTestObject.disposedCounter = 0
|
||||
VimListenerTestObject.enabled = false
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `disposable is called on plugin disable`() {
|
||||
configureByText("XYZ")
|
||||
VimListenerTestObject.disposedCounter = 0
|
||||
VimListenerTestObject.enabled = true
|
||||
|
||||
VimPlugin.setEnabled(false)
|
||||
|
||||
assertEquals(1, VimListenerTestObject.disposedCounter)
|
||||
|
||||
VimPlugin.setEnabled(true)
|
||||
}
|
||||
}
|
@ -12,11 +12,14 @@ import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.DataContext
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.testFramework.junit5.TestDisposable
|
||||
import com.intellij.testFramework.replaceService
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.junit.jupiter.api.AfterEach
|
||||
import org.mockito.Mockito
|
||||
import javax.swing.JTextArea
|
||||
|
||||
@ -28,6 +31,11 @@ open class MockTestCase : VimTestCase() {
|
||||
val editorStub = TextComponentEditorImpl(null, JTextArea()).vim
|
||||
val contextStub: ExecutionContext = DataContext.EMPTY_CONTEXT.vim
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
editorStub.carets().forEach { Disposer.dispose(it.ij) }
|
||||
}
|
||||
|
||||
fun <T : Any> mockService(service: Class<T>): T {
|
||||
val mock = Mockito.mock(service)
|
||||
val applicationManager = ApplicationManager.getApplication()
|
||||
|
@ -22,9 +22,11 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
|
||||
import com.maddyhome.idea.vim.options.NumberOption
|
||||
import com.maddyhome.idea.vim.options.OptionAccessScope
|
||||
import com.maddyhome.idea.vim.options.OptionDeclaredScope
|
||||
import com.maddyhome.idea.vim.options.StringOption
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
@ -34,9 +36,10 @@ import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInfo
|
||||
import javax.swing.SwingConstants
|
||||
import kotlin.test.assertContentEquals
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
private const val defaultValue = "defaultValue"
|
||||
private const val defaultNumberValue = 10
|
||||
|
||||
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
|
||||
class EffectiveOptionChangeListenerTest : VimTestCase() {
|
||||
@ -109,10 +112,17 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
|
||||
return option
|
||||
}
|
||||
|
||||
private fun addNumberOption(scope: OptionDeclaredScope): NumberOption {
|
||||
val option = NumberOption(optionName, scope, optionName, defaultNumberValue)
|
||||
injector.optionGroup.addOption(option)
|
||||
injector.optionGroup.addEffectiveOptionValueChangeListener(option, Listener)
|
||||
return option
|
||||
}
|
||||
|
||||
private fun assertNotifiedEditors(vararg editors: Editor) {
|
||||
val sortedExpected = editors.sortedBy { it.virtualFile!!.path }.toTypedArray()
|
||||
val sortedActual = Listener.notifiedEditors.sortedBy { it.virtualFile!!.path }.toTypedArray()
|
||||
assertContentEquals(sortedExpected, sortedActual)
|
||||
val expected = editors.toSet()
|
||||
val actual = Listener.notifiedEditors.toSet()
|
||||
assertEquals(expected, actual)
|
||||
}
|
||||
|
||||
private fun assertNoNotifications() = assertNotifiedEditors()
|
||||
@ -272,10 +282,23 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
|
||||
@Test
|
||||
fun `test listener called for all editors when locally modified global-local local-to-buffer option changes at effective scope`() {
|
||||
val option = addOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER)
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(otherBufferWindow.vim), VimString("localValue"))
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimString("localValue"))
|
||||
Listener.notifiedEditors.clear()
|
||||
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(otherBufferWindow.vim), VimString("newValue"))
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimString("newValue"))
|
||||
|
||||
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test listener called for all editors when locally modified number global-local local-to-buffer option changes at effective scope`() {
|
||||
// When a number (and therefore also toggle) global-local option is set at effective scope, the local value is not
|
||||
// reset to [Option.unsetValue] but to a copy of the new value. The local editor(s) should still be notified.
|
||||
val option = addNumberOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER)
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimInt(100))
|
||||
Listener.notifiedEditors.clear()
|
||||
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimInt(200))
|
||||
|
||||
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
|
||||
}
|
||||
@ -327,6 +350,19 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
|
||||
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test listener called for all editors when locally modified number global-local local-to-window option changes at effective scope`() {
|
||||
// When a number (and therefore also toggle) global-local option is set at effective scope, the local value is not
|
||||
// reset to [Option.unsetValue] but to a copy of the new value. The local editor(s) should still be notified.
|
||||
val option = addNumberOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_WINDOW)
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimInt(100))
|
||||
Listener.notifiedEditors.clear()
|
||||
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimInt(200))
|
||||
|
||||
assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test listener not called for locally modified editor when global-local local-to-window option changes at global scope`() {
|
||||
val option = addOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_WINDOW)
|
||||
|
@ -129,6 +129,23 @@ class OptionAccessScopeTest: VimTestCase() {
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test set local-to-buffer option at effective scope to current value changes global value`() {
|
||||
val defaultValue = VimInt(10)
|
||||
val option = NumberOption(OPTION_NAME, OptionDeclaredScope.LOCAL_TO_BUFFER, OPTION_NAME, defaultValue)
|
||||
injector.optionGroup.addOption(option)
|
||||
|
||||
val effectiveValue = VimInt(100)
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim), effectiveValue)
|
||||
assertEquals(defaultValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
|
||||
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim), effectiveValue)
|
||||
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim)))
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
|
||||
}
|
||||
|
||||
|
||||
// LOCAL_TO_WINDOW
|
||||
@Test
|
||||
@ -173,6 +190,23 @@ class OptionAccessScopeTest: VimTestCase() {
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test set local-to-window option at effective scope to current value changes global value`() {
|
||||
val defaultValue = VimInt(10)
|
||||
val option = NumberOption(OPTION_NAME, OptionDeclaredScope.LOCAL_TO_WINDOW, OPTION_NAME, defaultValue)
|
||||
|
||||
injector.optionGroup.addOption(option)
|
||||
val effectiveValue = VimInt(100)
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim), effectiveValue)
|
||||
assertEquals(defaultValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
|
||||
|
||||
injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim), effectiveValue)
|
||||
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim)))
|
||||
assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
|
||||
}
|
||||
|
||||
|
||||
// Global-local is tricky. The local value is initially not set, and the global value is used until it is. For string
|
||||
// options, this is represented by the local value showing as an empty string. Number options usually use the value -1
|
||||
|
@ -8,14 +8,13 @@
|
||||
|
||||
package ui
|
||||
|
||||
import com.automation.remarks.junit.VideoRule
|
||||
import com.automation.remarks.video.annotations.Video
|
||||
import com.automation.remarks.junit5.Video
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture
|
||||
import com.intellij.remoterobot.steps.CommonSteps
|
||||
import com.intellij.remoterobot.stepsProcessing.step
|
||||
import com.intellij.remoterobot.utils.keyboard
|
||||
import org.assertj.swing.core.MouseButton
|
||||
import org.junit.Rule
|
||||
import org.junit.jupiter.api.Test
|
||||
import ui.pages.Editor
|
||||
import ui.pages.IdeaFrame
|
||||
@ -25,6 +24,7 @@ import ui.pages.dialog
|
||||
import ui.pages.editor
|
||||
import ui.pages.gutter
|
||||
import ui.pages.idea
|
||||
import ui.pages.searchEverywhere
|
||||
import ui.pages.welcomeFrame
|
||||
import ui.utils.JavaExampleSteps
|
||||
import ui.utils.StepsLogger
|
||||
@ -38,6 +38,7 @@ import ui.utils.tripleClickOnRight
|
||||
import ui.utils.uiTest
|
||||
import ui.utils.vimExit
|
||||
import java.awt.Point
|
||||
import java.awt.event.KeyEvent
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
@ -47,14 +48,19 @@ class UiTests {
|
||||
StepsLogger.init()
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
var videoRule = VideoRule()
|
||||
private lateinit var commonSteps: CommonSteps
|
||||
|
||||
private val testTextForEditor = """
|
||||
|One Two
|
||||
|Three Four
|
||||
|Five
|
||||
""".trimMargin()
|
||||
|
||||
@Test
|
||||
@Video
|
||||
fun ideaVimTest() = uiTest("ideaVimTest") {
|
||||
val sharedSteps = JavaExampleSteps(this)
|
||||
commonSteps = CommonSteps(this)
|
||||
|
||||
startNewProject()
|
||||
Thread.sleep(1000)
|
||||
@ -63,16 +69,11 @@ class UiTests {
|
||||
Thread.sleep(1000)
|
||||
|
||||
idea {
|
||||
waitSmartMode()
|
||||
createFile("MyDoc.txt", this@uiTest)
|
||||
val editor = editor("MyDoc.txt") {
|
||||
step("Write a text") {
|
||||
injectText(
|
||||
"""
|
||||
|One Two
|
||||
|Three Four
|
||||
|Five
|
||||
""".trimMargin(),
|
||||
)
|
||||
injectText(testTextForEditor)
|
||||
}
|
||||
}
|
||||
testSelectTextWithDelay(editor)
|
||||
@ -89,6 +90,11 @@ class UiTests {
|
||||
testClickRightFromLineEnd(editor)
|
||||
testClickOnWord(editor)
|
||||
testGutterClick(editor)
|
||||
testAddNewLineInNormalMode(editor)
|
||||
testMappingToCtrlOrAltEnter(editor)
|
||||
`simple enter in insert mode`(editor)
|
||||
testMilticaretEnter(editor)
|
||||
`simple enter in select mode`(editor)
|
||||
reenableIdeaVim(editor)
|
||||
|
||||
createFile("MyTest.java", this@uiTest)
|
||||
@ -96,7 +102,7 @@ class UiTests {
|
||||
step("Write a text") {
|
||||
injectText(
|
||||
"""
|
||||
|class Main {
|
||||
|class MyTest {
|
||||
| public static void main() {
|
||||
| System.out.println("Hello");
|
||||
| }
|
||||
@ -115,7 +121,6 @@ class UiTests {
|
||||
|
||||
private fun closeUnrelated(sharedSteps: JavaExampleSteps) {
|
||||
with(sharedSteps) {
|
||||
closeIdeaVimDialog()
|
||||
closeTipOfTheDay()
|
||||
closeAllTabs()
|
||||
}
|
||||
@ -126,7 +131,7 @@ class UiTests {
|
||||
createNewProjectLink.click()
|
||||
dialog("New Project") {
|
||||
findText("Java").click()
|
||||
checkBox("Add sample code").select()
|
||||
checkBox("Add sample code").unselect()
|
||||
button("Create").click()
|
||||
}
|
||||
}
|
||||
@ -195,9 +200,11 @@ class UiTests {
|
||||
|
||||
private fun IdeaFrame.testTrackActionId(editor: Editor) {
|
||||
remoteRobot.invokeActionJs("GotoAction")
|
||||
editor.keyboard {
|
||||
enterText("IdeaVim: Track Action Ids")
|
||||
enter()
|
||||
|
||||
val searchEverywhere = this@testTrackActionId.searchEverywhere()
|
||||
|
||||
commonSteps.invokeAction("VimFindActionIdAction")
|
||||
keyboard {
|
||||
escape()
|
||||
}
|
||||
|
||||
@ -211,7 +218,7 @@ class UiTests {
|
||||
|
||||
assertEquals(
|
||||
"""
|
||||
|EditorEscapeclass Main {
|
||||
|EditorEscapeclass MyTest {
|
||||
| public static void main() {
|
||||
| if (true) {
|
||||
| System.out.println("Hello");
|
||||
@ -231,7 +238,7 @@ class UiTests {
|
||||
private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) {
|
||||
step("Create $fileName file") {
|
||||
with(projectViewTree) {
|
||||
setExpandTimeout(15_000)
|
||||
setExpandTimeout(30_000)
|
||||
expand(projectName, "src")
|
||||
findText("src").click(MouseButton.RIGHT_BUTTON)
|
||||
}
|
||||
@ -510,4 +517,187 @@ class UiTests {
|
||||
|
||||
vimExit()
|
||||
}
|
||||
|
||||
// For VIM-3159
|
||||
private fun ContainerFixture.testAddNewLineInNormalMode(editor: Editor) {
|
||||
println("Run testAddNewLineInNormalMode...")
|
||||
|
||||
commonSteps.invokeAction("EditorStartNewLineBefore")
|
||||
|
||||
assertEquals(
|
||||
"""
|
||||
|
|
||||
|One Two
|
||||
|Three Four
|
||||
|Five
|
||||
""".trimMargin(), editor.text
|
||||
)
|
||||
|
||||
editor.injectText(testTextForEditor)
|
||||
|
||||
commonSteps.invokeAction("EditorStartNewLine")
|
||||
|
||||
assertEquals(
|
||||
"""
|
||||
|One Two
|
||||
|
|
||||
|Three Four
|
||||
|Five
|
||||
""".trimMargin(), editor.text
|
||||
)
|
||||
|
||||
editor.injectText(testTextForEditor)
|
||||
|
||||
vimExit()
|
||||
}
|
||||
|
||||
// For VIM-3190
|
||||
private fun ContainerFixture.testMappingToCtrlOrAltEnter(editor: Editor) {
|
||||
println("Run testMappingToCtrlOrAltEnter...")
|
||||
|
||||
keyboard {
|
||||
enterText(":nmap <C-Enter> k")
|
||||
enter()
|
||||
enterText(":nmap <A-Enter> G")
|
||||
enter()
|
||||
}
|
||||
|
||||
// Set up initial position
|
||||
keyboard {
|
||||
enterText("jll")
|
||||
}
|
||||
assertEquals(10, editor.caretOffset)
|
||||
|
||||
// Checking C-ENTER
|
||||
keyboard {
|
||||
pressing(KeyEvent.VK_CONTROL) { enter() }
|
||||
}
|
||||
assertEquals(2, editor.caretOffset)
|
||||
|
||||
// Checking A-ENTER
|
||||
keyboard {
|
||||
pressing(KeyEvent.VK_ALT) { enter() }
|
||||
}
|
||||
assertEquals(19, editor.caretOffset)
|
||||
|
||||
vimExit()
|
||||
}
|
||||
|
||||
// For VIM-3186
|
||||
private fun ContainerFixture.testMilticaretEnter(editor: Editor) {
|
||||
println("Run testMilticaretEnter...")
|
||||
|
||||
keyboard {
|
||||
pressing(KeyEvent.VK_ALT) {
|
||||
pressing(KeyEvent.VK_SHIFT) {
|
||||
findText("One").click()
|
||||
findText("Three").click()
|
||||
findText("Five").click()
|
||||
}
|
||||
}
|
||||
|
||||
enterText("A")
|
||||
enter()
|
||||
}
|
||||
|
||||
assertEquals(3, editor.caretCount)
|
||||
|
||||
assertEquals(
|
||||
"""
|
||||
|One Two
|
||||
|
|
||||
|Three Four
|
||||
|
|
||||
|Five
|
||||
|
|
||||
""".trimMargin(), editor.text
|
||||
)
|
||||
|
||||
// Reset state
|
||||
keyboard {
|
||||
escape()
|
||||
escape()
|
||||
}
|
||||
assertEquals(1, editor.caretCount)
|
||||
editor.injectText(testTextForEditor)
|
||||
vimExit()
|
||||
}
|
||||
|
||||
private fun ContainerFixture.`simple enter in insert mode`(editor: Editor) {
|
||||
println("Run test 'simple enter in insert mode'...")
|
||||
|
||||
// Start of file
|
||||
keyboard {
|
||||
enterText("i")
|
||||
enter()
|
||||
}
|
||||
assertEquals(
|
||||
"""
|
||||
|
|
||||
|One Two
|
||||
|Three Four
|
||||
|Five
|
||||
""".trimMargin(),
|
||||
editor.text
|
||||
)
|
||||
|
||||
// Middle of file
|
||||
findText("Four").click()
|
||||
keyboard { enter() }
|
||||
assertEquals(
|
||||
"""
|
||||
|
|
||||
|One Two
|
||||
|Three
|
||||
|Four
|
||||
|Five
|
||||
""".trimMargin(),
|
||||
editor.text
|
||||
)
|
||||
|
||||
// End of file
|
||||
val fivePoint = findText("Five").point
|
||||
val endOfLine = Point(fivePoint.x + 50, fivePoint.y)
|
||||
click(endOfLine)
|
||||
keyboard { enter() }
|
||||
assertEquals(
|
||||
"""
|
||||
|
|
||||
|One Two
|
||||
|Three
|
||||
|Four
|
||||
|Five
|
||||
|
|
||||
""".trimMargin(),
|
||||
editor.text
|
||||
)
|
||||
|
||||
editor.injectText(testTextForEditor)
|
||||
vimExit()
|
||||
}
|
||||
|
||||
|
||||
private fun ContainerFixture.`simple enter in select mode`(editor: Editor) {
|
||||
println("Run test 'simple enter in select mode'...")
|
||||
|
||||
findText("Four").doubleClick()
|
||||
|
||||
keyboard {
|
||||
pressing(KeyEvent.VK_CONTROL) { enterText("g") }
|
||||
enter()
|
||||
}
|
||||
|
||||
assertEquals(
|
||||
"""
|
||||
|One Two
|
||||
|Three
|
||||
|
|
||||
|Five
|
||||
""".trimMargin(),
|
||||
editor.text
|
||||
)
|
||||
|
||||
editor.injectText(testTextForEditor)
|
||||
vimExit()
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,9 @@ class Editor(
|
||||
val caretOffset: Int
|
||||
get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
|
||||
|
||||
val caretCount: Int
|
||||
get() = callJs("component.getEditor().getCaretModel().getCaretCount()", runInEdt = true)
|
||||
|
||||
val isBlockCursor: Boolean
|
||||
// get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
|
||||
// Doesn't work at the moment because remote robot can't resolve classes from a plugin classloader
|
||||
|
@ -51,6 +51,14 @@ class IdeaFrame(
|
||||
}
|
||||
}
|
||||
|
||||
fun waitSmartMode(timeout: Duration = Duration.ofMinutes(5)) {
|
||||
step("Wait for smart mode") {
|
||||
waitFor(duration = timeout, interval = Duration.ofSeconds(5)) {
|
||||
isDumbMode().not()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isDumbMode(): Boolean {
|
||||
return callJs("com.intellij.openapi. project.DumbService.isDumb(component.project);", true)
|
||||
}
|
||||
|
31
src/test/java/ui/pages/SearchEverywhere.kt
Normal file
31
src/test/java/ui/pages/SearchEverywhere.kt
Normal file
@ -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 ui.pages
|
||||
|
||||
import com.intellij.remoterobot.RemoteRobot
|
||||
import com.intellij.remoterobot.data.RemoteComponent
|
||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.ContainerFixture
|
||||
import com.intellij.remoterobot.fixtures.FixtureName
|
||||
import com.intellij.remoterobot.search.locators.byXpath
|
||||
|
||||
@JvmOverloads
|
||||
fun ContainerFixture.searchEverywhere(function: SearchEverywhere.() -> Unit = {}): SearchEverywhere {
|
||||
return find<SearchEverywhere>(
|
||||
byXpath("Search Everywhere", "//div[@accessiblename='Search everywhere' and @class='SearchEverywhereUI']"),
|
||||
)
|
||||
.apply { runJs("robot.moveMouse(component);") }
|
||||
.apply(function)
|
||||
}
|
||||
|
||||
@FixtureName("SearchEverywhere")
|
||||
class SearchEverywhere(
|
||||
remoteRobot: RemoteRobot,
|
||||
remoteComponent: RemoteComponent,
|
||||
) : CommonContainerFixture(remoteRobot, remoteComponent)
|
@ -17,19 +17,11 @@ import com.intellij.remoterobot.utils.Keyboard
|
||||
import ui.pages.DialogFixture
|
||||
import ui.pages.DialogFixture.Companion.byTitle
|
||||
import ui.pages.IdeaFrame
|
||||
import ui.pages.dialog
|
||||
import ui.pages.idea
|
||||
|
||||
class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
|
||||
@Suppress("unused")
|
||||
private val keyboard: Keyboard = Keyboard(remoteRobot)
|
||||
|
||||
fun closeIdeaVimDialog() = optionalStep("Close Idea Vim dialog if it appears") {
|
||||
remoteRobot.idea {
|
||||
dialog("IdeaVim") { button("Yes").click() }
|
||||
}
|
||||
}
|
||||
|
||||
fun closeTipOfTheDay() = optionalStep("Close Tip of the Day if it appears") {
|
||||
val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
|
||||
idea.dumbAware {
|
||||
|
@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
|
||||
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
|
||||
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
public class RedoAction : VimActionHandler.SingleExecution() {
|
||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||
|
||||
|
@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
|
||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||
public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
|
||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
||||
injector.parser.parseKeys("u"),
|
||||
|
@ -28,7 +28,7 @@ import java.util.*
|
||||
public class AutoIndentLinesVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.CHANGE
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE, CommandFlags.FLAG_EXIT_VISUAL)
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
|
||||
|
||||
override fun executeAction(
|
||||
editor: VimEditor,
|
||||
|
@ -8,29 +8,23 @@
|
||||
package com.maddyhome.idea.vim.action.change.change
|
||||
|
||||
import com.intellij.vim.annotations.CommandOrMotion
|
||||
import com.intellij.vim.annotations.Mode
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.Command
|
||||
import com.maddyhome.idea.vim.command.CommandFlags
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL])
|
||||
@CommandOrMotion(keys = [], modes = [])
|
||||
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.CHANGE
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeAction(
|
||||
editor: VimEditor,
|
||||
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.injector
|
||||
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.group.visual.VimSelection
|
||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
|
||||
import com.maddyhome.idea.vim.helper.CharacterHelper
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
@ -29,8 +26,6 @@ import java.util.*
|
||||
public class ChangeCaseToggleVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||
override val type: Command.Type = Command.Type.CHANGE
|
||||
|
||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
|
||||
|
||||
override fun executeAction(
|
||||
editor: VimEditor,
|
||||
caret: VimCaret,
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user