mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-01-30 20:45:59 +01:00
Compare commits
178 Commits
822dad52f3
...
f33589fbce
Author | SHA1 | Date | |
---|---|---|---|
f33589fbce | |||
35445cb730 | |||
9cba953bf7 | |||
eb7c1953ae | |||
8d4a1cd7d5 | |||
fd33e4254b | |||
f242cf18d7 | |||
ac46a45f26 | |||
9d1e29d4bc | |||
8c30f802ca | |||
766f3f498d | |||
60d9c59a3e | |||
025bfe5aba | |||
2edb650830 | |||
3d75e14dc4 | |||
db0290d218 | |||
36716fe849 | |||
7cfe42688b | |||
1d9fb7f6a7 | |||
8c4828a6fd | |||
86f11a9554 | |||
|
4614e2ad54 | ||
|
077de91e01 | ||
|
1ce7a97f2a | ||
|
02a42843a6 | ||
|
eb72762073 | ||
|
43a94b3d71 | ||
|
1984873b1c | ||
|
4962baabad | ||
|
2e550a0960 | ||
|
bc48a45cd1 | ||
|
a005eb0612 | ||
|
63297e685c | ||
|
6c9a5e1f2d | ||
|
34a5ba0ba7 | ||
|
2354dc9af9 | ||
|
be31963a4f | ||
|
277c5ae7a5 | ||
|
49d03cf7f1 | ||
|
f5e7459b37 | ||
|
df8144fc00 | ||
|
05f1d7abd7 | ||
|
6ca17ae09d | ||
|
f9d8b3a59e | ||
|
3279325694 | ||
|
b2a6d0d5c7 | ||
|
e32fa6dd11 | ||
|
ec3db81c6d | ||
|
7062dc4860 | ||
|
46619040b1 | ||
|
035804860c | ||
|
0f1a4d523f | ||
|
5f5a97a7e1 | ||
|
03996dce59 | ||
|
0dd63c7b57 | ||
|
94d7902ef2 | ||
|
3d08170d54 | ||
|
cccb23d9ee | ||
|
eae3fb3ebe | ||
|
7f5dce4051 | ||
|
25e3988dcf | ||
|
36589c5250 | ||
|
f1f86b5cd2 | ||
|
628b3ca89f | ||
|
d2b929ddd0 | ||
|
43d770ff5a | ||
|
cde9bc94e6 | ||
|
63f6e73223 | ||
|
ad28e09fec | ||
|
f616f2c3c1 | ||
|
66aec26091 | ||
|
1d59c49b95 | ||
|
8da15b8c08 | ||
|
e22c2b6473 | ||
|
e293c1b786 | ||
|
468ca840ed | ||
|
c75e6756c0 | ||
|
52737c60cf | ||
|
e99c191d49 | ||
|
5db2984fdd | ||
|
365b58eb56 | ||
|
b026144254 | ||
|
0e3cda827c | ||
|
ed2fe3dcf0 | ||
|
791edbd29b | ||
|
4f9d76ef66 | ||
|
4b7381901d | ||
|
6e2cb9ba11 | ||
|
91cd4ab01f | ||
|
34d23180bd | ||
|
fc5aaa50d8 | ||
|
c7bbfdcaf5 | ||
|
562906fe6d | ||
|
976771d11a | ||
|
5fc4462b03 | ||
|
5f263e7014 | ||
|
4c899fcc93 | ||
|
2f8fe392af | ||
|
84c7e1159b | ||
|
18d6f79796 | ||
|
a745da9761 | ||
|
5eb36ce428 | ||
|
b0951f4a38 | ||
|
bcf519027e | ||
|
9aece44a00 | ||
|
61912e2c9b | ||
|
92ee271f1e | ||
|
babc5daf3b | ||
|
c0a10c65e1 | ||
|
5145bb4193 | ||
|
8825f790d4 | ||
|
a0f0f71b6a | ||
|
6a73f9e65a | ||
|
4ee858877d | ||
|
82d18cfbb9 | ||
|
b0dd75f77c | ||
|
727cfb36ba | ||
|
a7d5c5f2d4 | ||
|
1e02848a66 | ||
|
9d8afc5fcb | ||
|
15b2a17ae6 | ||
|
39a2cdcf50 | ||
|
2fd488531b | ||
|
e69b30796c | ||
|
790a0afdf3 | ||
|
66b01b0b0d | ||
|
71ea6184a0 | ||
|
0b1c79d961 | ||
|
6b751fae6e | ||
|
152066b73c | ||
|
dfe645c4eb | ||
|
5024fd94da | ||
|
4d3a231a35 | ||
|
e1a803d310 | ||
|
96b224c0d8 | ||
|
8b6de3f5c6 | ||
|
9014d99cde | ||
|
8dfd41a891 | ||
|
7e62e816a5 | ||
|
9c4965581a | ||
|
d1abc0c9f9 | ||
|
43555ad581 | ||
|
640e5ac552 | ||
|
40b706bcf4 | ||
|
5b355a8e95 | ||
|
8ed53284e4 | ||
|
1f6e124f9d | ||
|
5198864f34 | ||
|
ecfff61aad | ||
|
26909af5de | ||
|
ad762379fb | ||
|
dd4f2de368 | ||
|
879d191800 | ||
|
9d9e38843d | ||
|
f7aded99e8 | ||
|
4e1de61d77 | ||
|
34fe09c8f9 | ||
|
10283ce2f8 | ||
|
cc53b59658 | ||
|
c758a79f79 | ||
|
01d00d45d8 | ||
|
5c7edc498f | ||
|
9c403d2862 | ||
|
ebbd733bba | ||
|
88d1e1d24a | ||
|
c19f2aeee4 | ||
|
86202c846a | ||
|
9a7ff442f3 | ||
|
3752a97d74 | ||
|
5572dfc80d | ||
|
100f420d46 | ||
|
3fcc4746d8 | ||
|
5c5ac192da | ||
|
a0a2163ba0 | ||
|
02724cadce | ||
|
b205f87902 | ||
|
314304246a | ||
|
785688b1ca |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
* text=auto eol=lf
|
3
.github/workflows/closeYoutrackOnCommit.yml
vendored
3
.github/workflows/closeYoutrackOnCommit.yml
vendored
@ -8,6 +8,9 @@ on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
|
5
.github/workflows/codeql-analysis.yml
vendored
5
.github/workflows/codeql-analysis.yml
vendored
@ -20,6 +20,11 @@ on:
|
||||
schedule:
|
||||
- cron: '44 12 * * 4'
|
||||
|
||||
permissions:
|
||||
actions: read
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
|
14
.github/workflows/syncDoc.yml
vendored
14
.github/workflows/syncDoc.yml
vendored
@ -10,6 +10,9 @@ on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
@ -34,6 +37,17 @@ jobs:
|
||||
id: update_authors
|
||||
run: cp -a origin/doc/. docs
|
||||
|
||||
# The Wiki for github should have no `.md` in references
|
||||
# Otherwise, such links will lead to the raw text.
|
||||
# However, the `.md` should exist to have a navigation in GitHub code.
|
||||
- name: Replace `.md)` with `)`
|
||||
run: |
|
||||
# Define the directory you want to process
|
||||
DIRECTORY="docs"
|
||||
|
||||
# Find all files in the directory and perform the replacement
|
||||
find $DIRECTORY -type f -exec sed -i 's/\.md)/)/g' {} +
|
||||
|
||||
- name: Commit changes
|
||||
uses: stefanzweifel/git-auto-commit-action@v4
|
||||
with:
|
||||
|
15
.teamcity/_Self/Constants.kt
vendored
15
.teamcity/_Self/Constants.kt
vendored
@ -5,13 +5,12 @@ object Constants {
|
||||
const val EAP_CHANNEL = "eap"
|
||||
const val DEV_CHANNEL = "Dev"
|
||||
|
||||
const val GITHUB_TESTS = "2024.1.1"
|
||||
const val NVIM_TESTS = "2024.1.1"
|
||||
const val PROPERTY_TESTS = "2024.1.1"
|
||||
const val LONG_RUNNING_TESTS = "2024.1.1"
|
||||
const val QODANA_TESTS = "2024.1.1"
|
||||
const val RELEASE = "2024.1.1"
|
||||
const val NVIM_TESTS = "2024.2.1"
|
||||
const val PROPERTY_TESTS = "2024.2.1"
|
||||
const val LONG_RUNNING_TESTS = "2024.2.1"
|
||||
const val QODANA_TESTS = "2024.2.1"
|
||||
const val RELEASE = "2024.2.1"
|
||||
|
||||
const val RELEASE_DEV = "2024.1.1"
|
||||
const val RELEASE_EAP = "2024.1.1"
|
||||
const val RELEASE_DEV = "2024.2.1"
|
||||
const val RELEASE_EAP = "2024.2.1"
|
||||
}
|
||||
|
11
.teamcity/_Self/Project.kt
vendored
11
.teamcity/_Self/Project.kt
vendored
@ -8,7 +8,6 @@ import _Self.buildTypes.PropertyBased
|
||||
import _Self.buildTypes.Qodana
|
||||
import _Self.buildTypes.TestingBuildType
|
||||
import _Self.subprojects.GitHub
|
||||
import _Self.subprojects.OldTests
|
||||
import _Self.subprojects.Releases
|
||||
import _Self.vcsRoots.GitHubPullRequest
|
||||
import _Self.vcsRoots.ReleasesVcsRoot
|
||||
@ -18,7 +17,7 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
||||
object Project : Project({
|
||||
description = "Vim engine for JetBrains IDEs"
|
||||
|
||||
subProjects(Releases, OldTests, GitHub)
|
||||
subProjects(Releases, GitHub)
|
||||
|
||||
// VCS roots
|
||||
vcsRoot(GitHubPullRequest)
|
||||
@ -26,8 +25,7 @@ object Project : Project({
|
||||
|
||||
// Active tests
|
||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
buildType(TestingBuildType("2024.1.1", "<default>"))
|
||||
buildType(TestingBuildType("2024.2", "<default>"))
|
||||
buildType(TestingBuildType("2024.2.1", "<default>"))
|
||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||
|
||||
buildType(PropertyBased)
|
||||
@ -44,6 +42,9 @@ object Project : Project({
|
||||
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
|
||||
artifactRules = """
|
||||
+:build/reports => build/reports
|
||||
+:tests/java-tests/build/reports => java-tests/build/reports
|
||||
+:tests/long-running-tests/build/reports => long-running-tests/build/reports
|
||||
+:tests/property-tests/build/reports => property-tests/build/reports
|
||||
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
|
||||
""".trimIndent()
|
||||
|
||||
@ -53,7 +54,7 @@ abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
|
||||
// These requirements define Linux-Medium configuration.
|
||||
// Unfortunately, requirement by name (teamcity.agent.name) doesn't work
|
||||
// IDK the reason for it, but on our agents this property is empty
|
||||
equals("teamcity.agent.hardware.cpuCount", "4")
|
||||
equals("teamcity.agent.hardware.cpuCount", "16")
|
||||
equals("teamcity.agent.os.family", "Linux")
|
||||
}
|
||||
|
||||
|
3
.teamcity/_Self/buildTypes/Compatibility.kt
vendored
3
.teamcity/_Self/buildTypes/Compatibility.kt
vendored
@ -40,6 +40,9 @@ object Compatibility : IdeaVimBuildType({
|
||||
# java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.copilot' [latest-IU] -team-city
|
||||
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.github.dankinsoid.multicursor' [latest-IU] -team-city
|
||||
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.joshestein.ideavim-quickscope' [latest-IU] -team-city
|
||||
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.peekaboo' [latest-IU] -team-city
|
||||
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.switch' [latest-IU] -team-city
|
||||
java -jar verifier1/verifier-cli-dev-all-1.jar check-plugin '${'$'}com.julienphalip.ideavim.functiontextobj' [latest-IU] -team-city
|
||||
""".trimIndent()
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ object ReleaseEapFromBranch : IdeaVimBuildType({
|
||||
vcs {
|
||||
root(ReleasesVcsRoot)
|
||||
branchFilter = """
|
||||
+:heads/(releases/*)
|
||||
+:heads/releases/*
|
||||
""".trimIndent()
|
||||
|
||||
checkoutMode = CheckoutMode.AUTO
|
||||
|
@ -39,9 +39,11 @@ open class TestingBuildType(
|
||||
|
||||
steps {
|
||||
gradle {
|
||||
clearConditions()
|
||||
tasks = "clean test"
|
||||
buildFile = ""
|
||||
enableStacktrace = true
|
||||
jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
|
||||
}
|
||||
}
|
||||
|
||||
|
2
.teamcity/_Self/subprojects/GitHub.kt
vendored
2
.teamcity/_Self/subprojects/GitHub.kt
vendored
@ -1,6 +1,5 @@
|
||||
package _Self.subprojects
|
||||
|
||||
import _Self.Constants
|
||||
import _Self.IdeaVimBuildType
|
||||
import _Self.vcsRoots.GitHubPullRequest
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
|
||||
@ -25,7 +24,6 @@ class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({
|
||||
|
||||
params {
|
||||
param("env.ORG_GRADLE_PROJECT_downloadIdeaSources", "false")
|
||||
param("env.ORG_GRADLE_PROJECT_ideaVersion", Constants.GITHUB_TESTS)
|
||||
param("env.ORG_GRADLE_PROJECT_instrumentPluginCode", "false")
|
||||
}
|
||||
|
||||
|
25
.teamcity/_Self/subprojects/OldTests.kt
vendored
25
.teamcity/_Self/subprojects/OldTests.kt
vendored
@ -1,25 +0,0 @@
|
||||
package _Self.subprojects
|
||||
|
||||
import _Self.buildTypes.TestingBuildType
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
||||
|
||||
object OldTests : Project({
|
||||
name = "Old IdeaVim tests"
|
||||
description = "Tests for older versions of IJ"
|
||||
|
||||
buildType(TestingBuildType("IC-2018.1", "181-182", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2018.2", "181-182", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2018.3", "183", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2019.1", "191-193", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2019.2", "191-193", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2019.3", "191-193", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2020.1", "201", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2020.2", "202", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2020.3", "203-212", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2021.1", "203-212", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
|
||||
buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
|
||||
})
|
@ -1,39 +0,0 @@
|
||||
package patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
||||
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.*
|
||||
|
||||
/*
|
||||
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")) {
|
||||
check(artifactRules == """
|
||||
+:build/reports => build/reports
|
||||
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
|
||||
""".trimIndent()) {
|
||||
"Unexpected option value: artifactRules = $artifactRules"
|
||||
}
|
||||
artifactRules = """
|
||||
+:build/reports => build/reports
|
||||
+:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
|
||||
+:tests/java-tests/build/reports => tests/java-tests/build/reports
|
||||
""".trimIndent()
|
||||
|
||||
expectSteps {
|
||||
gradle {
|
||||
tasks = "clean test"
|
||||
buildFile = ""
|
||||
enableStacktrace = true
|
||||
}
|
||||
}
|
||||
steps {
|
||||
update<GradleBuildStep>(0) {
|
||||
clearConditions()
|
||||
jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
package patches.buildTypes
|
||||
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
|
||||
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
|
||||
|
||||
/*
|
||||
This patch script was generated by TeamCity on settings change in UI.
|
||||
To apply the patch, change the buildType with id = 'ReleaseEapFromBranch'
|
||||
accordingly, and delete the patch script.
|
||||
*/
|
||||
changeBuildType(RelativeId("ReleaseEapFromBranch")) {
|
||||
vcs {
|
||||
|
||||
check(branchFilter == "+:heads/(releases/*)") {
|
||||
"Unexpected option value: branchFilter = $branchFilter"
|
||||
}
|
||||
branchFilter = "heads/releases/*"
|
||||
}
|
||||
}
|
28
AUTHORS.md
28
AUTHORS.md
@ -539,6 +539,30 @@ Contributors:
|
||||
[![icon][github]](https://github.com/felixwiemuth)
|
||||
|
||||
Felix Wiemuth
|
||||
* [![icon][mail]](mailto:kirill.karnaukhov@jetbrains.com)
|
||||
[![icon][github]](https://github.com/kkarnauk)
|
||||
|
||||
Kirill Karnaukhov,
|
||||
* [![icon][mail]](mailto:sander.hestvik@gmail.com)
|
||||
[![icon][github]](https://github.com/SanderHestvik)
|
||||
|
||||
SanderHestvik
|
||||
* [![icon][mail]](mailto:gregory.shrago@jetbrains.com)
|
||||
[![icon][github]](https://github.com/gregsh)
|
||||
|
||||
Greg Shrago
|
||||
* [![icon][mail]](mailto:jphalip@gmail.com)
|
||||
[![icon][github]](https://github.com/jphalip)
|
||||
|
||||
Julien Phalip
|
||||
* [![icon][mail]](mailto:j.trimailovas@gmail.com)
|
||||
[![icon][github]](https://github.com/trimailov)
|
||||
|
||||
Justas Trimailovas,
|
||||
* [![icon][mail]](mailto:justast@wix.com)
|
||||
[![icon][github]](https://github.com/justast-wix)
|
||||
|
||||
Justas Trimailovas
|
||||
|
||||
Previous contributors:
|
||||
|
||||
@ -550,6 +574,10 @@ Previous contributors:
|
||||
[![icon][github]](https://github.com/kevin70)
|
||||
|
||||
kk
|
||||
* [![icon][mail]](mailto:gregory.shrago@jetbrains.com)
|
||||
[![icon][github]](https://github.com/gregsh)
|
||||
|
||||
Greg Shrago
|
||||
|
||||
|
||||
If you are a contributor and your name is not listed here, feel free to
|
||||
|
@ -21,7 +21,7 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.0.0-1.0.24")
|
||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:2.1.0-1.0.29")
|
||||
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")
|
||||
|
@ -50,14 +50,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.10.0.202406032230-r")
|
||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
|
||||
classpath("org.kohsuke:github-api:1.305")
|
||||
|
||||
classpath("io.ktor:ktor-client-core:2.3.12")
|
||||
classpath("io.ktor:ktor-client-cio:2.3.10")
|
||||
classpath("io.ktor:ktor-client-auth:2.3.12")
|
||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
|
||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
|
||||
classpath("io.ktor:ktor-client-core:3.0.2")
|
||||
classpath("io.ktor:ktor-client-cio:3.0.2")
|
||||
classpath("io.ktor:ktor-client-auth:3.0.2")
|
||||
classpath("io.ktor:ktor-client-content-negotiation:3.0.2")
|
||||
classpath("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
|
||||
|
||||
// This comes from the changelog plugin
|
||||
// classpath("org.jetbrains:markdown:0.3.1")
|
||||
@ -69,7 +69,7 @@ plugins {
|
||||
kotlin("jvm") version "2.0.0"
|
||||
application
|
||||
id("java-test-fixtures")
|
||||
id("org.jetbrains.intellij.platform") version "2.0.0-rc2"
|
||||
id("org.jetbrains.intellij.platform") version "2.2.0"
|
||||
id("org.jetbrains.changelog") version "2.2.1"
|
||||
id("org.jetbrains.kotlinx.kover") version "0.6.1"
|
||||
id("com.dorongold.task-tree") version "4.0.0"
|
||||
@ -85,7 +85,6 @@ val ideaVersion: String by project
|
||||
val ideaType: String by project
|
||||
val instrumentPluginCode: String by project
|
||||
val remoteRobotVersion: String by project
|
||||
val splitModeVersion: String by project
|
||||
|
||||
val publishChannels: String by project
|
||||
val publishToken: String by project
|
||||
@ -108,13 +107,17 @@ dependencies {
|
||||
compileOnly(project(":annotation-processors"))
|
||||
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||
compileOnly("org.jetbrains:annotations:26.0.1")
|
||||
|
||||
intellijPlatform {
|
||||
// Snapshots don't use installers
|
||||
// https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html#target-versions-installers
|
||||
val useInstaller = "EAP-SNAPSHOT" !in ideaVersion
|
||||
|
||||
// Note that it is also possible to use local("...") to compile against a locally installed IDE
|
||||
// E.g. local("/Users/{user}/Applications/IntelliJ IDEA Ultimate.app")
|
||||
// Or something like: intellijIdeaUltimate(ideaVersion)
|
||||
create(ideaType, ideaVersion)
|
||||
create(ideaType, ideaVersion, useInstaller)
|
||||
|
||||
pluginVerifier()
|
||||
zipSigner()
|
||||
@ -125,6 +128,7 @@ dependencies {
|
||||
|
||||
// AceJump is an optional dependency. We use their SessionManager class to check if it's active
|
||||
plugin("AceJump", "3.8.19")
|
||||
plugin("com.intellij.classic.ui", "242.20224.159")
|
||||
}
|
||||
|
||||
moduleSources(project(":vim-engine", "sourcesJarArtifacts"))
|
||||
@ -146,17 +150,17 @@ dependencies {
|
||||
// https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
|
||||
testImplementation("org.mockito.kotlin:mockito-kotlin:5.4.0")
|
||||
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.3")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.3")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.3")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.5")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
|
||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.5")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.5")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.5")
|
||||
testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.5")
|
||||
|
||||
// Temp workaround suggested in https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-faq.html#junit5-test-framework-refers-to-junit4
|
||||
// Can be removed when IJPL-159134 is fixed
|
||||
// testRuntimeOnly("junit:junit:4.13.2")
|
||||
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
|
||||
testImplementation("org.junit.vintage:junit-vintage-engine:5.10.5")
|
||||
// testFixturesImplementation("org.junit.vintage:junit-vintage-engine:5.10.3")
|
||||
}
|
||||
|
||||
@ -210,6 +214,8 @@ tasks {
|
||||
}
|
||||
|
||||
compileTestKotlin {
|
||||
enabled = false
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = javaVersion
|
||||
apiVersion = "1.9"
|
||||
@ -229,6 +235,7 @@ tasks {
|
||||
|
||||
// Uncomment to run the plugin in a custom IDE, rather than the IDE specified as a compile target in dependencies
|
||||
// Note that the version must be greater than the plugin's target version, for obvious reasons
|
||||
// You can also set splitMode and splitModeTarget here to test split mode in a custom IDE
|
||||
// val runIdeCustom by intellijPlatformTesting.runIde.registering {
|
||||
// type = IntelliJPlatformType.Rider
|
||||
// version = "2024.1.2"
|
||||
@ -261,10 +268,6 @@ tasks {
|
||||
val runIdeSplitMode by intellijPlatformTesting.runIde.registering {
|
||||
splitMode = true
|
||||
splitModeTarget = SplitModeAware.SplitModeTarget.FRONTEND
|
||||
|
||||
// Frontend split mode support requires 242+
|
||||
// TODO: Remove this once IdeaVim targets 242, as the task will naturally use the target version to run
|
||||
version.set(splitModeVersion)
|
||||
}
|
||||
|
||||
// Add plugin open API sources to the plugin ZIP
|
||||
|
@ -1,6 +1,6 @@
|
||||
Welcome to the IdeaVim wiki!
|
||||
|
||||
- List of IdeaVim plugins: [plugins](IdeaVim%20Plugins)
|
||||
- Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples)
|
||||
- List of "set" commands: ["set" commands](set-commands)
|
||||
- Docs about "select" mode in vim: [select mode](Select-mode)
|
||||
- List of IdeaVim plugins: [plugins](IdeaVim%20Plugins.md)
|
||||
- Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples.md)
|
||||
- List of "set" commands: ["set" commands](set-commands.md)
|
||||
- Docs about "select" mode in vim: [select mode](Select-mode.md)
|
||||
|
@ -82,7 +82,7 @@ Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
|
||||
|
||||
### Instructions
|
||||
|
||||
[See here](NERDTree-support).
|
||||
[See here](NERDTree-support.md).
|
||||
|
||||
</details>
|
||||
|
||||
@ -322,6 +322,9 @@ If you want to optimize highlight duration, assign a time in milliseconds:
|
||||
If you want to change background color of highlight you can provide the rgba of the color you want e.g.
|
||||
`let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"`
|
||||
|
||||
If you want to change text color of highlight you can provide the rgba of the color you want e.g.
|
||||
`let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"`
|
||||
|
||||
https://github.com/machakann/vim-highlightedyank/blob/master/doc/highlightedyank.txt
|
||||
|
||||
</details>
|
||||
@ -435,3 +438,50 @@ Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
|
||||
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
|
||||
|
||||
</details>
|
||||
<details>
|
||||
<summary><h2>Vim Peekaboo</h2></summary>
|
||||
|
||||
By Julien Phalip
|
||||
Original plugin: [vim-peekaboo](https://github.com/junegunn/vim-peekaboo).
|
||||
|
||||
### Setup
|
||||
|
||||
Add `set peekaboo` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
|
||||
or restart the IDE.
|
||||
|
||||
### Instructions
|
||||
|
||||
https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><h2>FunctionTextObj</h2></summary>
|
||||
|
||||
By Julien Phalip
|
||||
|
||||
### Setup
|
||||
|
||||
Add `set functiontextobj` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
|
||||
or restart the IDE.
|
||||
|
||||
### Instructions
|
||||
|
||||
https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary><h2>Switch</h2></summary>
|
||||
|
||||
By Julien Phalip
|
||||
Original plugin: [switch.vim](https://github.com/AndrewRadev/switch.vim).
|
||||
|
||||
### Setup
|
||||
|
||||
Add `set switch` to your `~/.ideavimrc` file, then run `:source ~/.ideavimrc`
|
||||
or restart the IDE.
|
||||
|
||||
### Instructions
|
||||
|
||||
https://plugins.jetbrains.com/plugin/25899-vim-switch
|
||||
|
||||
</details>
|
||||
|
@ -25,7 +25,7 @@ It's not intended to be read by the users as it brings no value to them.
|
||||
IdeaVim has multiple YouTrack statuses, main are:
|
||||
|
||||
- Submitted: issue is created by user, but not processed by our team. This is the default status for new tickets.
|
||||
- Open: issues is processed by out team, what means that the issues is reproduced and accepted
|
||||
- Open: issues is processed by our team, what means that the issues is reproduced and accepted
|
||||
- Waiting For Reply: Waiting for further information from the user. These issues are automatically closed if the
|
||||
user doesn't reply in 30 days.
|
||||
- Ready To Release: Bug is fixed, but not yet released
|
||||
|
@ -16,21 +16,15 @@
|
||||
# https://data.services.jetbrains.com/products?code=IC
|
||||
# Maven releases are here: https://www.jetbrains.com/intellij-repository/releases
|
||||
# And snapshots: https://www.jetbrains.com/intellij-repository/snapshots
|
||||
ideaVersion=2024.1.1
|
||||
ideaVersion=2024.2
|
||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||
ideaType=IC
|
||||
instrumentPluginCode=true
|
||||
version=SNAPSHOT
|
||||
version=chylex-42
|
||||
javaVersion=17
|
||||
remoteRobotVersion=0.11.23
|
||||
antlrVersion=4.10.1
|
||||
|
||||
# [VERSION UPDATE] 2024.2 - remove when IdeaVim targets 2024.2
|
||||
# Running IdeaVim in split mode requires 242. Update this version once 242 has been released, and remove it completely
|
||||
# when IdeaVim targets 242, at which point runIdeSplitMode will run correctly with the target version.
|
||||
# See also runIdeSplitMode
|
||||
splitModeVersion=242-EAP-SNAPSHOT
|
||||
|
||||
|
||||
# Please don't forget to update kotlin version in buildscript section
|
||||
# Also update kotlinxSerializationVersion version
|
||||
@ -47,7 +41,6 @@ youtrackToken=
|
||||
|
||||
# Gradle settings
|
||||
org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
||||
org.gradle.configuration-cache=true
|
||||
org.gradle.caching=true
|
||||
|
||||
# Disable warning from gradle-intellij-plugin. Kotlin stdlib is included as compileOnly, so the warning is unnecessary
|
||||
|
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
22
gradlew
vendored
22
gradlew
vendored
@ -15,6 +15,8 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
@ -55,7 +57,7 @@
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
@ -83,7 +85,9 @@ done
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s
|
||||
' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
@ -144,7 +148,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
@ -152,7 +156,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC3045
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
@ -201,11 +205,11 @@ fi
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command;
|
||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||
# shell script including quotes and variable substitutions, so put them in
|
||||
# double quotes to make sure that they get re-expanded; and
|
||||
# * put everything else in single quotes, so that it's not re-expanded.
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
|
22
gradlew.bat
vendored
22
gradlew.bat
vendored
@ -13,6 +13,8 @@
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@ -43,11 +45,11 @@ set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
@ -57,11 +59,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
|
@ -20,17 +20,17 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.25")
|
||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:2.1.0")
|
||||
|
||||
implementation("io.ktor:ktor-client-core:2.3.12")
|
||||
implementation("io.ktor:ktor-client-cio:2.3.10")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.12")
|
||||
implementation("io.ktor:ktor-client-auth:2.3.12")
|
||||
implementation("io.ktor:ktor-client-core:3.0.2")
|
||||
implementation("io.ktor:ktor-client-cio:3.0.2")
|
||||
implementation("io.ktor:ktor-client-content-negotiation:3.0.2")
|
||||
implementation("io.ktor:ktor-serialization-kotlinx-json:3.0.2")
|
||||
implementation("io.ktor:ktor-client-auth:3.0.2")
|
||||
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.10.0.202406032230-r")
|
||||
implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:7.1.0.202411261347-r")
|
||||
implementation("com.vdurmont:semver4j:3.1.0")
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,9 @@ val knownPlugins = setOf(
|
||||
"com.protoseo.input-source-auto-converter",
|
||||
|
||||
// "cc.implicated.intellij.plugins.bunny", // I don't want to include this plugin in the list of IdeaVim plugins as I don't understand what this is for
|
||||
"com.julienphalip.ideavim.peekaboo", // https://plugins.jetbrains.com/plugin/25776-vim-peekaboo
|
||||
"com.julienphalip.ideavim.switch", // https://plugins.jetbrains.com/plugin/25899-vim-switch
|
||||
"com.julienphalip.ideavim.functiontextobj", // https://plugins.jetbrains.com/plugin/25897-vim-functiontextobj
|
||||
)
|
||||
|
||||
suspend fun main() {
|
||||
|
@ -33,6 +33,9 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
|
||||
|
||||
private var firstInitializationOccurred = false
|
||||
|
||||
// TODO
|
||||
// We should migrate to some solution from https://plugins.jetbrains.com/docs/intellij/plugin-components.html#application-startup
|
||||
// If you'd like to add a new code here, please consider using one of the things described there.
|
||||
override suspend fun execute(project: Project) {
|
||||
if (firstInitializationOccurred) return
|
||||
firstInitializationOccurred = true
|
||||
|
@ -10,6 +10,7 @@ package com.maddyhome.idea.vim;
|
||||
import com.intellij.ide.plugins.IdeaPluginDescriptor;
|
||||
import com.intellij.ide.plugins.PluginManagerCore;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.AccessToken;
|
||||
import com.intellij.openapi.application.Application;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
@ -24,10 +25,8 @@ import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.maddyhome.idea.vim.api.VimEditor;
|
||||
import com.maddyhome.idea.vim.api.VimInjectorKt;
|
||||
import com.maddyhome.idea.vim.api.VimKeyGroup;
|
||||
import com.maddyhome.idea.vim.api.VimOptionGroup;
|
||||
import com.intellij.util.SlowOperations;
|
||||
import com.maddyhome.idea.vim.api.*;
|
||||
import com.maddyhome.idea.vim.config.VimState;
|
||||
import com.maddyhome.idea.vim.config.migration.ApplicationConfigurationMigrator;
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionRegistrar;
|
||||
@ -36,7 +35,6 @@ import com.maddyhome.idea.vim.group.copy.PutGroup;
|
||||
import com.maddyhome.idea.vim.group.visual.VisualMotionGroup;
|
||||
import com.maddyhome.idea.vim.helper.MacKeyRepeat;
|
||||
import com.maddyhome.idea.vim.listener.VimListenerManager;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimInjector;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimSearchGroup;
|
||||
import com.maddyhome.idea.vim.ui.StatusBarIconFactory;
|
||||
@ -141,8 +139,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
return (MacroGroup)VimInjectorKt.getInjector().getMacro();
|
||||
}
|
||||
|
||||
public static @NotNull DigraphGroup getDigraph() {
|
||||
return (DigraphGroup)VimInjectorKt.getInjector().getDigraphGroup();
|
||||
public static @NotNull VimDigraphGroup getDigraph() {
|
||||
return VimInjectorKt.getInjector().getDigraphGroup();
|
||||
}
|
||||
|
||||
public static @NotNull HistoryGroup getHistory() {
|
||||
@ -339,7 +337,9 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
||||
|
||||
// 4) ~/.ideavimrc execution
|
||||
// Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window
|
||||
try (AccessToken ignore = SlowOperations.knownIssue("VIM-3661")) {
|
||||
registerIdeavimrc(VimInjectorKt.getInjector().getFallbackWindow());
|
||||
}
|
||||
|
||||
// Turing on should be performed after all commands registration
|
||||
getSearch().turnOn();
|
||||
|
@ -0,0 +1,52 @@
|
||||
package com.maddyhome.idea.vim.action
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||
import com.intellij.openapi.command.WriteCommandAction
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
import com.intellij.openapi.project.DumbAwareAction
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
|
||||
class VimRunLastMacroInOpenFiles : DumbAwareAction() {
|
||||
override fun update(e: AnActionEvent) {
|
||||
val lastRegister = injector.macro.lastRegister
|
||||
val isEnabled = lastRegister != 0.toChar()
|
||||
|
||||
e.presentation.isEnabled = isEnabled
|
||||
e.presentation.text = if (isEnabled) "Run Macro '${lastRegister}' in Open Files" else "Run Last Macro in Open Files"
|
||||
}
|
||||
|
||||
override fun getActionUpdateThread(): ActionUpdateThread {
|
||||
return ActionUpdateThread.EDT
|
||||
}
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
val project = e.project ?: return
|
||||
val fileEditorManager = FileEditorManagerEx.getInstanceExIfCreated(project) ?: return
|
||||
val editors = fileEditorManager.allEditors.filterIsInstance<TextEditor>()
|
||||
|
||||
WriteCommandAction.writeCommandAction(project)
|
||||
.withName(e.presentation.text)
|
||||
.withGlobalUndo()
|
||||
.withUndoConfirmationPolicy(UndoConfirmationPolicy.REQUEST_CONFIRMATION)
|
||||
.run<RuntimeException> {
|
||||
val reg = injector.macro.lastRegister
|
||||
|
||||
for (editor in editors) {
|
||||
fileEditorManager.openFile(editor.file, true)
|
||||
|
||||
val vimEditor = editor.editor.vim
|
||||
vimEditor.mode = Mode.NORMAL()
|
||||
KeyHandler.getInstance().reset(vimEditor)
|
||||
|
||||
injector.macro.playbackRegister(vimEditor, IjEditorExecutionContext(e.dataContext), reg, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -298,26 +298,10 @@ class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_BACK_SPACE, 0, InputEvent.CTRL_DOWN_MASK))
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_INSERT, 0))
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_DELETE, 0, InputEvent.CTRL_DOWN_MASK))
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_UP, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK))
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0, InputEvent.CTRL_DOWN_MASK, InputEvent.SHIFT_DOWN_MASK))
|
||||
.addAll(
|
||||
getKeyStrokes(
|
||||
KeyEvent.VK_LEFT,
|
||||
0,
|
||||
InputEvent.CTRL_DOWN_MASK,
|
||||
InputEvent.SHIFT_DOWN_MASK,
|
||||
InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK,
|
||||
),
|
||||
)
|
||||
.addAll(
|
||||
getKeyStrokes(
|
||||
KeyEvent.VK_RIGHT,
|
||||
0,
|
||||
InputEvent.CTRL_DOWN_MASK,
|
||||
InputEvent.SHIFT_DOWN_MASK,
|
||||
InputEvent.CTRL_DOWN_MASK or InputEvent.SHIFT_DOWN_MASK,
|
||||
),
|
||||
)
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_UP, 0))
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_DOWN, 0))
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_LEFT, 0))
|
||||
.addAll(getKeyStrokes(KeyEvent.VK_RIGHT, 0))
|
||||
.addAll(
|
||||
getKeyStrokes(
|
||||
KeyEvent.VK_HOME,
|
||||
|
@ -40,7 +40,7 @@ class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecuti
|
||||
): Boolean {
|
||||
injector.editorGroup.notifyIdeaJoin(editor)
|
||||
|
||||
return injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, false)
|
||||
return injector.changeGroup.deleteJoinLines(editor, context, caret, operatorArguments.count1, false)
|
||||
}
|
||||
|
||||
override fun execute(
|
||||
|
@ -35,7 +35,7 @@ class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution()
|
||||
injector.editorGroup.notifyIdeaJoin(editor)
|
||||
var res = true
|
||||
editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
|
||||
if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true)) {
|
||||
if (!injector.changeGroup.deleteJoinLines(editor, context, caret, operatorArguments.count1, true)) {
|
||||
res = false
|
||||
}
|
||||
}
|
||||
|
@ -44,6 +44,7 @@ class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution(
|
||||
val range = caretsAndSelections[caret] ?: return@forEach
|
||||
if (!injector.changeGroup.deleteJoinRange(
|
||||
editor,
|
||||
context,
|
||||
caret,
|
||||
range.toVimTextRange(true).normalize(),
|
||||
false,
|
||||
|
@ -44,6 +44,7 @@ class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExec
|
||||
val range = caretsAndSelections[caret] ?: return@forEach
|
||||
if (!injector.changeGroup.deleteJoinRange(
|
||||
editor,
|
||||
context,
|
||||
caret,
|
||||
range.toVimTextRange(true).normalize(),
|
||||
true,
|
||||
|
@ -20,6 +20,8 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.handler.IdeActionHandler
|
||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
|
||||
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
|
||||
import java.util.*
|
||||
|
||||
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
|
||||
@ -39,8 +41,13 @@ internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CA
|
||||
operatorArguments: OperatorArguments
|
||||
): Boolean {
|
||||
val undo = injector.undo
|
||||
when (undo) {
|
||||
is VimKeyBasedUndoService -> undo.setMergeUndoKey()
|
||||
is VimTimestampBasedUndoService -> {
|
||||
val nanoTime = System.nanoTime()
|
||||
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
|
||||
}
|
||||
}
|
||||
return super.execute(editor, context, cmd, operatorArguments)
|
||||
}
|
||||
}
|
||||
@ -63,8 +70,13 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
|
||||
operatorArguments: OperatorArguments
|
||||
): Boolean {
|
||||
val undo = injector.undo
|
||||
when (undo) {
|
||||
is VimKeyBasedUndoService -> undo.setMergeUndoKey()
|
||||
is VimTimestampBasedUndoService -> {
|
||||
val nanoTime = System.nanoTime()
|
||||
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
|
||||
}
|
||||
}
|
||||
return super.execute(editor, context, cmd, operatorArguments)
|
||||
}
|
||||
}
|
||||
|
@ -27,6 +27,30 @@ public interface VimExtension {
|
||||
return MappingOwner.Plugin.Companion.get(getName());
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is always called AFTER the full execution of the `.ideavimrc` file.
|
||||
* <p>
|
||||
* During vim initialization process, it firstly loads the .vimrc file, then executes scripts from the plugins folder.
|
||||
* This practically means that the .vimrc file is initialized first; then the plugins are loaded.
|
||||
* See `:h initialization`
|
||||
* <p>
|
||||
* Why does this matter? Because this affects the order of commands are executed. For example,
|
||||
* ```
|
||||
* plug 'tommcdo/vim-exchange'
|
||||
* let g:exchange_no_mappings=1
|
||||
* ```
|
||||
* Here the user will expect that the exchange plugin won't have default mappings. However, if we load vim-exchange
|
||||
* immediately, this variable won't be initialized at the moment of plugin initialization.
|
||||
* <p>
|
||||
* There is also a tricky case for mappings override:
|
||||
* ```
|
||||
* plug 'tommcdo/vim-exchange'
|
||||
* map X <Plug>(ExchangeLine)
|
||||
* ```
|
||||
* For this case, a plugin with a good implementation detects that there is already a defined mapping for
|
||||
* `<Plug>(ExchangeLine)` and doesn't register the default cxx mapping. However, such detection requires the mapping
|
||||
* to be defined before the plugin initialization.
|
||||
*/
|
||||
void init();
|
||||
|
||||
default void dispose() {
|
||||
|
@ -188,11 +188,19 @@ object VimExtensionFacade {
|
||||
|
||||
/** Get the current contents of the given register similar to 'getreg()'. */
|
||||
@JvmStatic
|
||||
@Deprecated("Please use com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister(com.maddyhome.idea.vim.api.VimEditor, char)")
|
||||
fun getRegister(register: Char): List<KeyStroke>? {
|
||||
val reg = VimPlugin.getRegister().getRegister(register) ?: return null
|
||||
return reg.keys
|
||||
}
|
||||
|
||||
/** Get the current contents of the given register similar to 'getreg()'. */
|
||||
@JvmStatic
|
||||
fun getRegister(editor: VimEditor, register: Char): List<KeyStroke>? {
|
||||
val reg = VimPlugin.getRegister().getRegister(editor, injector.executionContextManager.getEditorExecutionContext(editor), register) ?: return null
|
||||
return reg.keys
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getRegisterForCaret(register: Char, caret: VimCaret): List<KeyStroke>? {
|
||||
val reg = caret.registerStorage.getRegister(register) ?: return null
|
||||
|
@ -88,29 +88,10 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
|
||||
}
|
||||
|
||||
/**
|
||||
* During vim initialization process, it firstly loads the .vimrc file, then executes scripts from the plugins folder.
|
||||
* This practically means that the .vimrc file is initialized first, then the plugins are loaded.
|
||||
* See `:h initialization`
|
||||
* See the docs for [VimExtension.init]
|
||||
*
|
||||
* In IdeaVim we don't have a separate plugins folder to load it after .ideavimrc load. However, we can collect
|
||||
* the list of plugins mentioned in the .ideavimrc and load them after .ideavimrc execution is finished.
|
||||
*
|
||||
* Why this matters? Because this affects the order of commands are executed. For example:
|
||||
* ```
|
||||
* plug 'tommcdo/vim-exchange'
|
||||
* let g:exchange_no_mappings=1
|
||||
* ```
|
||||
* Here the user will expect that the exchange plugin won't have default mappings. However, if we load vim-exchange
|
||||
* immediately, this variable won't be initialized at the moment of plugin initialization.
|
||||
*
|
||||
* There is also a tricky case for mappings override:
|
||||
* ```
|
||||
* plug 'tommcdo/vim-exchange'
|
||||
* map X <Plug>(ExchangeLine)
|
||||
* ```
|
||||
* For this case, a plugin with a good implementation detects that there is already a defined mapping for
|
||||
* `<Plug>(ExchangeLine)` and doesn't register the default cxx mapping. However, such detection requires the mapping
|
||||
* to be defined before the plugin initialization.
|
||||
*/
|
||||
@JvmStatic
|
||||
fun enableDelayedExtensions() {
|
||||
|
@ -222,10 +222,10 @@ internal class VimExchangeExtension : VimExtension {
|
||||
}
|
||||
}
|
||||
|
||||
val zRegText = getRegister('z')
|
||||
val unnRegText = getRegister('"')
|
||||
val startRegText = getRegister('*')
|
||||
val plusRegText = getRegister('+')
|
||||
val zRegText = getRegister(editor.vim, 'z')
|
||||
val unnRegText = getRegister(editor.vim, '"')
|
||||
val startRegText = getRegister(editor.vim, '*')
|
||||
val plusRegText = getRegister(editor.vim, '+')
|
||||
runWriteAction {
|
||||
// TODO handle:
|
||||
// " Compare using =~ because "'==' != 0" returns 0
|
||||
@ -299,7 +299,7 @@ internal class VimExchangeExtension : VimExtension {
|
||||
|
||||
private fun getExchange(editor: Editor, isVisual: Boolean, selectionType: SelectionType): Exchange {
|
||||
// TODO: improve KeyStroke list to sting conversion
|
||||
fun getRegisterText(reg: Char): String = getRegister(reg)?.map { it.keyChar }?.joinToString("") ?: ""
|
||||
fun getRegisterText(reg: Char): String = getRegister(editor.vim, reg)?.map { it.keyChar }?.joinToString("") ?: ""
|
||||
fun getMarks(isVisual: Boolean): Pair<Mark, Mark> {
|
||||
val (startMark, endMark) =
|
||||
if (isVisual) {
|
||||
@ -313,9 +313,9 @@ internal class VimExchangeExtension : VimExtension {
|
||||
return Pair(marks.getMark(vimEditor.primaryCaret(), startMark)!!, marks.getMark(vimEditor.primaryCaret(), endMark)!!)
|
||||
}
|
||||
|
||||
val unnRegText = getRegister('"')
|
||||
val starRegText = getRegister('*')
|
||||
val plusRegText = getRegister('+')
|
||||
val unnRegText = getRegister(editor.vim, '"')
|
||||
val starRegText = getRegister(editor.vim, '*')
|
||||
val plusRegText = getRegister(editor.vim, '+')
|
||||
|
||||
val (selectionStart, selectionEnd) = getMarks(isVisual)
|
||||
if (isVisual) {
|
||||
|
@ -21,10 +21,7 @@ import com.intellij.openapi.editor.markup.TextAttributes
|
||||
import com.intellij.openapi.util.Disposer
|
||||
import com.intellij.util.Alarm
|
||||
import com.intellij.util.Alarm.ThreadToUse
|
||||
import com.jetbrains.rd.util.first
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.ModeChangeListener
|
||||
@ -48,6 +45,10 @@ private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_durati
|
||||
|
||||
@NonNls
|
||||
private val HIGHLIGHT_COLOR_VARIABLE_NAME = "highlightedyank_highlight_color"
|
||||
|
||||
@NonNls
|
||||
private val HIGHLIGHT_FOREGROUND_COLOR_VARIABLE_NAME = "highlightedyank_highlight_foreground_color"
|
||||
|
||||
private var defaultHighlightTextColor: Color? = null
|
||||
|
||||
private fun getDefaultHighlightTextColor(): Color {
|
||||
@ -78,6 +79,9 @@ internal class HighlightColorResetter : LafManagerListener {
|
||||
* if you want to change background color of highlight you can provide the rgba of the color you want e.g.
|
||||
* let g:highlightedyank_highlight_color = "rgba(160, 160, 160, 155)"
|
||||
*
|
||||
* if you want to change text color of highlight you can provide the rgba of the color you want e.g.
|
||||
* let g:highlightedyank_highlight_foreground_color = "rgba(0, 0, 0, 255)"
|
||||
*
|
||||
* When a new text is yanked or user starts editing, the old highlighting would be deleted.
|
||||
*/
|
||||
internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeListener {
|
||||
@ -117,9 +121,9 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
|
||||
initialised = false
|
||||
}
|
||||
|
||||
override fun yankPerformed(caretToRange: Map<ImmutableVimCaret, TextRange>) {
|
||||
override fun yankPerformed(editor: VimEditor, range: TextRange) {
|
||||
ensureInitialised()
|
||||
highlightHandler.highlightYankRange(caretToRange)
|
||||
highlightHandler.highlightYankRange(editor.ij, range)
|
||||
}
|
||||
|
||||
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
|
||||
@ -140,15 +144,13 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
|
||||
private var lastEditor: Editor? = null
|
||||
private val highlighters = mutableSetOf<RangeHighlighter>()
|
||||
|
||||
fun highlightYankRange(caretToRange: Map<ImmutableVimCaret, TextRange>) {
|
||||
fun highlightYankRange(editor: Editor, range: TextRange) {
|
||||
// from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
|
||||
clearYankHighlighters()
|
||||
|
||||
val editor = caretToRange.first().key.editor.ij
|
||||
lastEditor = editor
|
||||
|
||||
val attributes = getHighlightTextAttributes(editor)
|
||||
for (range in caretToRange.values) {
|
||||
for (i in 0 until range.size()) {
|
||||
val highlighter = editor.markupModel.addRangeHighlighter(
|
||||
range.startOffsets[i],
|
||||
@ -159,7 +161,6 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
|
||||
)
|
||||
highlighters.add(highlighter)
|
||||
}
|
||||
}
|
||||
|
||||
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
|
||||
val timeout = extractUsersHighlightDuration()
|
||||
@ -187,13 +188,15 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
|
||||
highlighters.clear()
|
||||
}
|
||||
|
||||
private fun getHighlightTextAttributes(editor: Editor) = TextAttributes(
|
||||
null,
|
||||
private fun getHighlightTextAttributes(editor: Editor): TextAttributes {
|
||||
return TextAttributes(
|
||||
extractUserHighlightForegroundColor(),
|
||||
extractUsersHighlightColor(),
|
||||
editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
|
||||
EffectType.SEARCH_MATCH,
|
||||
Font.PLAIN,
|
||||
)
|
||||
}
|
||||
|
||||
private fun extractUsersHighlightDuration(): Int {
|
||||
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
|
||||
@ -206,15 +209,52 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, ModeChangeLis
|
||||
}
|
||||
|
||||
private fun extractUsersHighlightColor(): Color {
|
||||
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value ->
|
||||
val rgba = value.asString()
|
||||
val value = VimPlugin.getVariableService().getGlobalVariableValue(HIGHLIGHT_COLOR_VARIABLE_NAME)
|
||||
if (value != null) {
|
||||
return try {
|
||||
parseRgbaColor(value.asString())
|
||||
} catch (e: Exception) {
|
||||
@VimNlsSafe val message = MessageHelper.message(
|
||||
"highlightedyank.invalid.value.of.0.1",
|
||||
"g:$HIGHLIGHT_COLOR_VARIABLE_NAME",
|
||||
e.message ?: "",
|
||||
)
|
||||
VimPlugin.showMessage(message)
|
||||
getDefaultHighlightTextColor()
|
||||
}
|
||||
}
|
||||
return getDefaultHighlightTextColor()
|
||||
}
|
||||
|
||||
private fun extractUserHighlightForegroundColor(): Color? {
|
||||
val value = VimPlugin.getVariableService().getGlobalVariableValue(HIGHLIGHT_FOREGROUND_COLOR_VARIABLE_NAME)
|
||||
?: return null
|
||||
|
||||
return try {
|
||||
parseRgbaColor(value.asString())
|
||||
} catch (e: Exception) {
|
||||
@VimNlsSafe val message = MessageHelper.message(
|
||||
"highlightedyank.invalid.value.of.0.1",
|
||||
"g:$HIGHLIGHT_FOREGROUND_COLOR_VARIABLE_NAME",
|
||||
e.message ?: "",
|
||||
)
|
||||
VimPlugin.showMessage(message)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseRgbaColor(colorString: String): Color {
|
||||
val rgba = colorString
|
||||
.substring(4)
|
||||
.filter { it != '(' && it != ')' && !it.isWhitespace() }
|
||||
.split(',')
|
||||
.map { it.toInt() }
|
||||
|
||||
Color(rgba[0], rgba[1], rgba[2], rgba[3])
|
||||
if (rgba.size != 4 || rgba.any { it < 0 || it > 255 }) {
|
||||
throw IllegalArgumentException("Invalid RGBA values. Each component must be between 0 and 255")
|
||||
}
|
||||
|
||||
return Color(rgba[0], rgba[1], rgba[2], rgba[3])
|
||||
}
|
||||
|
||||
private fun <T> extractVariable(variable: String, default: T, extractFun: (value: VimDataType) -> T): T {
|
||||
|
@ -230,7 +230,7 @@ private object FileTypePatterns {
|
||||
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
||||
this.cMakePatterns
|
||||
} else {
|
||||
return null
|
||||
this.htmlPatterns
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -42,13 +42,10 @@ import com.maddyhome.idea.vim.extension.VimExtension
|
||||
import com.maddyhome.idea.vim.group.KeyGroup
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
import com.maddyhome.idea.vim.helper.runAfterGotFocus
|
||||
import com.maddyhome.idea.vim.key.CommandNode
|
||||
import com.maddyhome.idea.vim.key.CommandPartNode
|
||||
import com.maddyhome.idea.vim.key.KeyStrokeTrie
|
||||
import com.maddyhome.idea.vim.key.MappingOwner
|
||||
import com.maddyhome.idea.vim.key.Node
|
||||
import com.maddyhome.idea.vim.key.RequiredShortcut
|
||||
import com.maddyhome.idea.vim.key.RootNode
|
||||
import com.maddyhome.idea.vim.key.addLeafs
|
||||
import com.maddyhome.idea.vim.key.add
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||
@ -198,6 +195,8 @@ internal class NerdTree : VimExtension {
|
||||
internal var waitForSearch = false
|
||||
internal var speedSearchListenerInstalled = false
|
||||
|
||||
private val keys = mutableListOf<KeyStroke>()
|
||||
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
var keyStroke = getKeyStroke(e) ?: return
|
||||
val keyChar = keyStroke.keyChar
|
||||
@ -205,20 +204,14 @@ internal class NerdTree : VimExtension {
|
||||
keyStroke = KeyStroke.getKeyStroke(keyChar)
|
||||
}
|
||||
|
||||
val nextNode = currentNode[keyStroke]
|
||||
|
||||
when (nextNode) {
|
||||
null -> currentNode = actionsRoot
|
||||
is CommandNode<NerdAction> -> {
|
||||
currentNode = actionsRoot
|
||||
|
||||
val action = nextNode.actionHolder
|
||||
keys.add(keyStroke)
|
||||
actionsRoot.getData(keys)?.let { action ->
|
||||
when (action) {
|
||||
is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim)
|
||||
is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
|
||||
}
|
||||
}
|
||||
is CommandPartNode<NerdAction> -> currentNode = nextNode
|
||||
|
||||
keys.clear()
|
||||
}
|
||||
}
|
||||
|
||||
@ -540,38 +533,29 @@ private fun addCommand(alias: String, handler: CommandAliasHandler) {
|
||||
VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
|
||||
}
|
||||
|
||||
private fun registerCommand(variable: String, default: String, action: NerdAction) {
|
||||
private fun registerCommand(variable: String, defaultMapping: String, action: NerdAction) {
|
||||
val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
|
||||
val mappings = if (variableValue is VimString) {
|
||||
val mapping = if (variableValue is VimString) {
|
||||
variableValue.value
|
||||
} else {
|
||||
default
|
||||
defaultMapping
|
||||
}
|
||||
actionsRoot.addLeafs(mappings, action)
|
||||
registerCommand(mapping, action)
|
||||
}
|
||||
|
||||
private fun registerCommand(default: String, action: NerdAction) {
|
||||
actionsRoot.addLeafs(default, action)
|
||||
}
|
||||
|
||||
|
||||
private val actionsRoot: RootNode<NerdAction> = RootNode("NERDTree")
|
||||
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
|
||||
|
||||
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
|
||||
return if (node is CommandPartNode<NerdAction>) {
|
||||
val res = node.children.keys.toMutableSet()
|
||||
res += node.children.values.map { collectShortcuts(it) }.flatten()
|
||||
res
|
||||
} else {
|
||||
emptySet()
|
||||
private fun registerCommand(mapping: String, action: NerdAction) {
|
||||
actionsRoot.add(mapping, action)
|
||||
injector.parser.parseKeys(mapping).forEach {
|
||||
distinctShortcuts.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
private val actionsRoot: KeyStrokeTrie<NerdAction> = KeyStrokeTrie<NerdAction>("NERDTree")
|
||||
private val distinctShortcuts = mutableSetOf<KeyStroke>()
|
||||
|
||||
private fun installDispatcher(project: Project) {
|
||||
val dispatcher = NerdTree.NerdDispatcher.getInstance(project)
|
||||
val shortcuts =
|
||||
collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
|
||||
val shortcuts = distinctShortcuts.map { RequiredShortcut(it, MappingOwner.Plugin.get(NerdTree.pluginName)) }
|
||||
dispatcher.registerCustomShortcutSet(
|
||||
KeyGroup.toShortcutSet(shortcuts),
|
||||
(ProjectView.getInstance(project) as ProjectViewImpl).component,
|
||||
|
@ -62,7 +62,7 @@ internal class ParagraphMotion : VimExtension {
|
||||
toKeys: List<KeyStroke>,
|
||||
recursive: Boolean,
|
||||
) {
|
||||
val filteredModes = modes.filterNotTo(HashSet()) { VimPlugin.getKey().hasmapfrom(it, fromKeys) }
|
||||
val filteredModes = modes.filterNotTo(HashSet()) { VimPlugin.getKey().getKeyMapping(it).getLayer(fromKeys) != null }
|
||||
putKeyMappingIfMissing(filteredModes, fromKeys, pluginOwner, toKeys, recursive)
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
|
||||
import com.maddyhome.idea.vim.extension.VimExtensionHandler
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.awt.Font
|
||||
import java.awt.event.KeyEvent
|
||||
import java.util.*
|
||||
@ -45,15 +46,26 @@ private const val DEFAULT_HIGHLIGHT_DURATION_SNEAK = 300
|
||||
// By [Mikhail Levchenko](https://github.com/Mishkun)
|
||||
// Original repository with the plugin: https://github.com/Mishkun/ideavim-sneak
|
||||
internal class IdeaVimSneakExtension : VimExtension {
|
||||
@Suppress("CompanionObjectInExtension")
|
||||
companion object {
|
||||
private var highlightHandler: HighlightHandler? = null
|
||||
|
||||
@TestOnly
|
||||
internal fun stopTimer() {
|
||||
highlightHandler?.stopExistingTimer()
|
||||
}
|
||||
}
|
||||
|
||||
override fun getName(): String = "sneak"
|
||||
|
||||
override fun init() {
|
||||
val highlightHandler = HighlightHandler()
|
||||
mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD), MappingMode.NXO)
|
||||
val _highlightHandler = HighlightHandler()
|
||||
highlightHandler = _highlightHandler
|
||||
mapToFunctionAndProvideKeys("s", SneakHandler(_highlightHandler, Direction.FORWARD), MappingMode.NXO)
|
||||
|
||||
// vim-sneak uses `Z` for visual mode because `S` conflict with vim-sneak plugin VIM-3330
|
||||
mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.NO)
|
||||
mapToFunctionAndProvideKeys("Z", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.X)
|
||||
mapToFunctionAndProvideKeys("S", SneakHandler(_highlightHandler, Direction.BACKWARD), MappingMode.NO)
|
||||
mapToFunctionAndProvideKeys("Z", SneakHandler(_highlightHandler, Direction.BACKWARD), MappingMode.X)
|
||||
|
||||
// workaround to support ; and , commands
|
||||
mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"), MappingMode.NXO)
|
||||
@ -61,8 +73,8 @@ internal class IdeaVimSneakExtension : VimExtension {
|
||||
mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"), MappingMode.NXO)
|
||||
mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"), MappingMode.NXO)
|
||||
|
||||
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO)
|
||||
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO)
|
||||
mapToFunctionAndProvideKeys(";", SneakRepeatHandler(_highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO)
|
||||
mapToFunctionAndProvideKeys(",", SneakRepeatHandler(_highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO)
|
||||
}
|
||||
|
||||
private class SneakHandler(
|
||||
@ -209,6 +221,7 @@ internal class IdeaVimSneakExtension : VimExtension {
|
||||
private class HighlightHandler {
|
||||
private var editor: Editor? = null
|
||||
private val sneakHighlighters: MutableSet<RangeHighlighter> = mutableSetOf()
|
||||
private var timer: Timer? = null
|
||||
|
||||
fun highlightSneakRange(editor: Editor, range: TextRange) {
|
||||
clearAllSneakHighlighters()
|
||||
@ -254,13 +267,19 @@ internal class IdeaVimSneakExtension : VimExtension {
|
||||
}
|
||||
|
||||
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
|
||||
val timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) {
|
||||
stopExistingTimer()
|
||||
timer = Timer(DEFAULT_HIGHLIGHT_DURATION_SNEAK) {
|
||||
if (editor?.isDisposed != true) {
|
||||
editor?.markupModel?.removeHighlighter(highlighter)
|
||||
}
|
||||
}
|
||||
timer.isRepeats = false
|
||||
timer.start()
|
||||
timer?.isRepeats = false
|
||||
timer?.start()
|
||||
}
|
||||
|
||||
fun stopExistingTimer() {
|
||||
timer?.stop()
|
||||
timer?.actionListeners?.forEach { it.actionPerformed(null) }
|
||||
}
|
||||
|
||||
private fun getHighlightTextAttributes() = TextAttributes(
|
||||
@ -307,7 +326,7 @@ private fun VimExtension.mapToFunctionAndProvideKeys(
|
||||
VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
|
||||
}
|
||||
val filteredFromModes = mappingModes.filterNotTo(HashSet()) {
|
||||
injector.keyGroup.hasmapfrom(it, fromKeys)
|
||||
injector.keyGroup.getKeyMapping(it).getLayer(fromKeys) != null
|
||||
}
|
||||
|
||||
val doubleFiltered = mappingModes
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -14,6 +14,7 @@ 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
|
||||
@ -36,18 +37,21 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
|
||||
import com.maddyhome.idea.vim.group.findBlockRange
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
|
||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
||||
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.returnTo
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.KeyStroke
|
||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||
|
||||
/**
|
||||
* Port of vim-surround.
|
||||
@ -80,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
|
||||
}
|
||||
|
||||
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
|
||||
VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
|
||||
}
|
||||
|
||||
private class YSurroundHandler : ExtensionHandler {
|
||||
@ -108,7 +112,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
||||
if (lastNonWhiteSpaceOffset != null) {
|
||||
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
||||
performSurround(pair, range, it)
|
||||
performSurround(pair, range, it, count = operatorArguments.count1)
|
||||
}
|
||||
// it.moveToOffset(lineStartOffset)
|
||||
}
|
||||
@ -131,15 +135,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
|
||||
editor.exitVisualMode()
|
||||
editor.ij.caretModel.moveToOffset(selectionStart)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -159,13 +161,17 @@ internal class VimSurroundExtension : VimExtension {
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
|
||||
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
|
||||
}
|
||||
|
||||
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: SurroundPair?) {
|
||||
// Save old register values for carets
|
||||
val surroundings = editor.sortedCarets()
|
||||
.map {
|
||||
val oldValue: List<KeyStroke>? = getRegisterForCaret(REGISTER, it)
|
||||
setRegisterForCaret(REGISTER, it, null)
|
||||
SurroundingInfo(it, null, oldValue)
|
||||
SurroundingInfo(it, null, oldValue, false)
|
||||
}
|
||||
|
||||
// Delete surrounding's content
|
||||
@ -181,21 +187,25 @@ internal class VimSurroundExtension : VimExtension {
|
||||
}
|
||||
|
||||
val registerValue = getRegisterForCaret(REGISTER, it.caret)
|
||||
val innerValue = if (registerValue.isNullOrEmpty()) null else registerValue
|
||||
val innerValue = if (registerValue.isNullOrEmpty()) emptyList() else registerValue
|
||||
it.innerText = innerValue
|
||||
}
|
||||
|
||||
surroundings.forEach {
|
||||
if (it.innerText == null && getRegisterForCaret(REGISTER, it.caret)?.isNotEmpty() == true) {
|
||||
it.innerText = emptyList()
|
||||
// Valid surroundings are only those that:
|
||||
// - are validly wrapping with surround characters (i.e. parenthesis, brackets, tags, quotes, etc.);
|
||||
// - or have non-empty inner text (e.g. when we are surrounding words: `csw"`)
|
||||
if (currentSurrounding != null || innerValue.isNotEmpty()) {
|
||||
it.isValidSurrounding = true
|
||||
}
|
||||
}
|
||||
|
||||
surroundings
|
||||
.filter { it.innerText != null } // we do nothing with carets that are not inside the surrounding
|
||||
.filter { it.isValidSurrounding } // we do nothing with carets that are not inside the surrounding
|
||||
.map { surrounding ->
|
||||
val innerValue = injector.parser.toPrintableString(surrounding.innerText!!)
|
||||
val text = newSurround?.let { it.first + innerValue + it.second } ?: innerValue
|
||||
val text = newSurround?.let {
|
||||
val trimmedValue = if (newSurround.shouldTrim) innerValue.trim() else innerValue
|
||||
it.first + trimmedValue + it.second
|
||||
} ?: innerValue
|
||||
val textData = PutData.TextData(text, SelectionType.CHARACTER_WISE, emptyList(), null)
|
||||
val putData = PutData(textData, null, 1, insertTextBeforeCaret = true, rawIndent = true, caretAfterInsertedText = false)
|
||||
|
||||
@ -248,7 +258,7 @@ internal class VimSurroundExtension : VimExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private data class SurroundingInfo(val caret: VimCaret, var innerText: List<KeyStroke>?, val oldRegisterContent: List<KeyStroke>?) {
|
||||
private data class SurroundingInfo(val caret: VimCaret, var innerText: List<KeyStroke>?, val oldRegisterContent: List<KeyStroke>?, var isValidSurrounding: Boolean) {
|
||||
fun restoreRegister() {
|
||||
setRegisterForCaret(REGISTER, caret, oldRegisterContent)
|
||||
}
|
||||
@ -267,21 +277,42 @@ internal class VimSurroundExtension : VimExtension {
|
||||
}
|
||||
}
|
||||
|
||||
private class Operator : OperatorFunction {
|
||||
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||
val ijEditor = editor.ij
|
||||
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
|
||||
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||
val ijEditor = vimEditor.ij
|
||||
val c = getChar(ijEditor)
|
||||
if (c.code == 0) return true
|
||||
|
||||
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
val range = getSurroundRange(editor.currentCaret()) ?: return false
|
||||
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
|
||||
|
||||
runWriteAction {
|
||||
val change = VimPlugin.getChange()
|
||||
if (supportsMultipleCursors) {
|
||||
ijEditor.runWithEveryCaretAndRestore {
|
||||
applyOnce(ijEditor, change, pair, count)
|
||||
}
|
||||
}
|
||||
else {
|
||||
applyOnce(ijEditor, change, pair, count)
|
||||
// Jump back to start
|
||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: SurroundPair, count: Int) {
|
||||
// XXX: Will it work with line-wise or block-wise selections?
|
||||
val primaryCaret = editor.caretModel.primaryCaret
|
||||
val range = getSurroundRange(primaryCaret.vim)
|
||||
if (range != null) {
|
||||
val start = RepeatedCharSequence.of(pair.first, count)
|
||||
val end = RepeatedCharSequence.of(pair.second, count)
|
||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
|
||||
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
||||
val editor = caret.editor
|
||||
if (editor.mode is Mode.CMD_LINE) {
|
||||
@ -302,33 +333,35 @@ private const val REGISTER = '"'
|
||||
|
||||
private const val OPERATOR_FUNC = "SurroundOperatorFunc"
|
||||
|
||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
|
||||
|
||||
private data class SurroundPair(val first: String, val second: String, val shouldTrim: Boolean)
|
||||
|
||||
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 ""),
|
||||
'b' to SurroundPair("(", ")", false),
|
||||
'(' to SurroundPair("( ", " )", false),
|
||||
')' to SurroundPair("(", ")", true),
|
||||
'B' to SurroundPair("{", "}", false),
|
||||
'{' to SurroundPair("{ ", " }", false),
|
||||
'}' to SurroundPair("{", "}", true),
|
||||
'r' to SurroundPair("[", "]", false),
|
||||
'[' to SurroundPair("[ ", " ]", false),
|
||||
']' to SurroundPair("[", "]", true),
|
||||
'a' to SurroundPair("<", ">", false),
|
||||
'>' to SurroundPair("<", ">", false),
|
||||
's' to SurroundPair(" ", "", false),
|
||||
)
|
||||
|
||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
|
||||
private fun getSurroundPair(c: Char): SurroundPair? = if (c in SURROUND_PAIRS) {
|
||||
SURROUND_PAIRS[c]
|
||||
} else if (!c.isLetter()) {
|
||||
val s = c.toString()
|
||||
s to s
|
||||
SurroundPair(s, s, false)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
|
||||
private fun inputTagPair(editor: Editor, context: DataContext): SurroundPair? {
|
||||
val tagInput = inputString(editor, context, "<", '>')
|
||||
if (editor.vim.mode is Mode.CMD_LINE) {
|
||||
editor.vim.mode = editor.vim.mode.returnTo()
|
||||
@ -337,7 +370,7 @@ private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, Str
|
||||
return if (matcher.find()) {
|
||||
val tagName = matcher.group(1)
|
||||
val tagAttributes = matcher.group(2)
|
||||
"<$tagName$tagAttributes>" to "</$tagName>"
|
||||
SurroundPair("<$tagName$tagAttributes>", "</$tagName>", false)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
@ -347,16 +380,20 @@ private fun inputFunctionName(
|
||||
editor: Editor,
|
||||
context: DataContext,
|
||||
withInternalSpaces: Boolean,
|
||||
): Pair<String, String>? {
|
||||
): SurroundPair? {
|
||||
val functionNameInput = inputString(editor, context, "function: ", null)
|
||||
if (editor.vim.mode is Mode.CMD_LINE) {
|
||||
editor.vim.mode = editor.vim.mode.returnTo()
|
||||
}
|
||||
if (functionNameInput.isEmpty()) return null
|
||||
return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
|
||||
return if (withInternalSpaces) {
|
||||
SurroundPair("$functionNameInput( ", " )", false)
|
||||
} else {
|
||||
SurroundPair("$functionNameInput(", ")", false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) {
|
||||
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): SurroundPair? = when (c) {
|
||||
'<', 't' -> inputTagPair(editor, context)
|
||||
'f' -> inputFunctionName(editor, context, false)
|
||||
'F' -> inputFunctionName(editor, context, true)
|
||||
@ -375,15 +412,15 @@ private fun getChar(editor: Editor): Char {
|
||||
return res
|
||||
}
|
||||
|
||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
|
||||
private fun performSurround(pair: SurroundPair, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||
runWriteAction {
|
||||
val editor = caret.editor
|
||||
val change = VimPlugin.getChange()
|
||||
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
|
||||
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||
|
||||
val isEOF = range.endOffset == editor.text().length
|
||||
val hasNewLine = editor.endsWithNewLine()
|
||||
val rightSurround = if (tagsOnNewLines) {
|
||||
val rightSurround = (if (tagsOnNewLines) {
|
||||
if (isEOF && !hasNewLine) {
|
||||
"\n" + pair.second
|
||||
} else {
|
||||
@ -391,7 +428,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
|
||||
}
|
||||
} else {
|
||||
pair.second
|
||||
}
|
||||
}).let { RepeatedCharSequence.of(it, count) }
|
||||
|
||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||
|
@ -36,10 +36,12 @@ import com.maddyhome.idea.vim.helper.inInsertMode
|
||||
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.listener.VimInsertListener
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCopiedText
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import kotlin.math.min
|
||||
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
|
||||
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
|
||||
|
||||
/**
|
||||
* Provides all the insert/replace related functionality
|
||||
@ -62,9 +64,15 @@ class ChangeGroup : VimChangeGroupBase() {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val ijContext = context.ij
|
||||
val doc = vimEditor.editor.document
|
||||
|
||||
val undo = injector.undo
|
||||
when (undo) {
|
||||
is VimKeyBasedUndoService -> undo.setInsertNonMergeUndoKey()
|
||||
is VimTimestampBasedUndoService -> {
|
||||
val nanoTime = System.nanoTime()
|
||||
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
|
||||
}
|
||||
}
|
||||
CommandProcessor.getInstance().executeCommand(
|
||||
editor.project, {
|
||||
ApplicationManager.getApplication()
|
||||
@ -130,16 +138,17 @@ class ChangeGroup : VimChangeGroupBase() {
|
||||
context: ExecutionContext,
|
||||
range: TextRange,
|
||||
) {
|
||||
val startPos = editor.offsetToBufferPosition(caret.offset)
|
||||
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
||||
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
||||
val ijEditor = (editor as IjVimEditor).editor
|
||||
|
||||
// FIXME: Here we do selection, and it is not a good idea, because it updates primary selection in Linux
|
||||
// FIXME: I'll leave here a dirty fix that restores primary selection, but it would be better to rewrite this method
|
||||
var primaryTextAndTransferableData: Pair<String, List<Any>?>? = null
|
||||
var copiedText: IjVimCopiedText? = null
|
||||
try {
|
||||
if (injector.registerGroup.isPrimaryRegisterSupported()) {
|
||||
primaryTextAndTransferableData = injector.clipboardManager.getPrimaryTextAndTransferableData()
|
||||
copiedText = injector.clipboardManager.getPrimaryContent(editor, context) as IjVimCopiedText
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
|
||||
@ -154,11 +163,7 @@ class ChangeGroup : VimChangeGroupBase() {
|
||||
}
|
||||
}
|
||||
val afterAction = {
|
||||
val firstLine = editor.offsetToBufferPosition(
|
||||
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
|
||||
).line
|
||||
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
|
||||
caret.moveToOffset(newOffset)
|
||||
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
|
||||
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
||||
}
|
||||
if (project != null) {
|
||||
@ -169,12 +174,8 @@ class ChangeGroup : VimChangeGroupBase() {
|
||||
afterAction.invoke()
|
||||
}
|
||||
try {
|
||||
if (primaryTextAndTransferableData != null) {
|
||||
injector.clipboardManager.setPrimaryText(
|
||||
primaryTextAndTransferableData.first,
|
||||
primaryTextAndTransferableData.first,
|
||||
primaryTextAndTransferableData.second ?: emptyList()
|
||||
)
|
||||
if (copiedText != null) {
|
||||
injector.clipboardManager.setPrimaryContent(editor, context, copiedText)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
// FIXME: [isPrimaryRegisterSupported()] is not implemented perfectly, so there might be thrown an exception after trying to access the primary selection
|
||||
|
@ -1,83 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.group;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.maddyhome.idea.vim.api.VimDigraphGroupBase;
|
||||
import com.maddyhome.idea.vim.api.VimEditor;
|
||||
import com.maddyhome.idea.vim.api.VimOutputPanel;
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||
|
||||
public class DigraphGroup extends VimDigraphGroupBase {
|
||||
|
||||
public void showDigraphs(@NotNull VimEditor editor) {
|
||||
int width = EditorHelper.getApproximateScreenWidth(((IjVimEditor) editor).getEditor());
|
||||
if (width < 10) {
|
||||
width = 80;
|
||||
}
|
||||
int colCount = width / 12;
|
||||
int height = (int)Math.ceil((double) getDigraphs().size() / (double)colCount);
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("width=" + width);
|
||||
logger.debug("colCount=" + colCount);
|
||||
logger.debug("height=" + height);
|
||||
}
|
||||
|
||||
StringBuilder res = new StringBuilder();
|
||||
int cnt = 0;
|
||||
for (Character code : getKeys().keySet()) {
|
||||
String key = getKeys().get(code);
|
||||
|
||||
res.append(key);
|
||||
res.append(' ');
|
||||
if (code < 32) {
|
||||
res.append('^');
|
||||
res.append((char)(code + '@'));
|
||||
}
|
||||
else if (code >= 128 && code <= 159) {
|
||||
res.append('~');
|
||||
res.append((char)(code - 128 + '@'));
|
||||
}
|
||||
else {
|
||||
res.append(code);
|
||||
res.append(' ');
|
||||
}
|
||||
res.append(' ');
|
||||
if (code < 0x1000) {
|
||||
res.append('0');
|
||||
}
|
||||
if (code < 0x100) {
|
||||
res.append('0');
|
||||
}
|
||||
if (code < 0x10) {
|
||||
res.append('0');
|
||||
}
|
||||
res.append(Integer.toHexString(code));
|
||||
res.append(" ");
|
||||
|
||||
cnt++;
|
||||
if (cnt == colCount) {
|
||||
res.append('\n');
|
||||
cnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
VimOutputPanel output = injector.getOutputPanel().getOrCreate(editor, injector.getExecutionContextManager().getEditorExecutionContext(editor));
|
||||
output.addText(res.toString(), true );
|
||||
output.show();
|
||||
}
|
||||
|
||||
private static final Logger logger = Logger.getInstance(DigraphGroup.class.getName());
|
||||
}
|
@ -42,7 +42,6 @@ import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||
import com.maddyhome.idea.vim.newapi.execute
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
|
@ -139,7 +139,7 @@ object IjOptions {
|
||||
// Temporary feature flags during development, not really intended for external use
|
||||
val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
|
||||
val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
|
||||
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true))
|
||||
val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
|
||||
val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
|
||||
val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
|
||||
|
||||
|
@ -8,7 +8,6 @@
|
||||
|
||||
package com.maddyhome.idea.vim.group;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.intellij.codeInsight.lookup.impl.LookupImpl;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
|
||||
@ -17,18 +16,16 @@ import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.keymap.Keymap;
|
||||
import com.intellij.openapi.keymap.KeymapManager;
|
||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import com.maddyhome.idea.vim.EventFacade;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
|
||||
import com.maddyhome.idea.vim.action.change.LazyVimCommand;
|
||||
import com.maddyhome.idea.vim.api.*;
|
||||
import com.maddyhome.idea.vim.command.MappingMode;
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel;
|
||||
import com.maddyhome.idea.vim.key.*;
|
||||
import com.maddyhome.idea.vim.newapi.IjNativeAction;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
||||
@ -58,8 +55,6 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
|
||||
private static final @NonNls String OWNER_ATTRIBUTE = "owner";
|
||||
private static final String TEXT_ELEMENT = "text";
|
||||
|
||||
private static final Logger logger = Logger.getInstance(KeyGroup.class);
|
||||
|
||||
public void registerRequiredShortcutKeys(@NotNull VimEditor editor) {
|
||||
EventFacade.getInstance()
|
||||
.registerCustomShortcutSet(VimShortcutKeyAction.getInstance(), toShortcutSet(getRequiredShortcutKeys()),
|
||||
@ -199,8 +194,7 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
|
||||
registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE);
|
||||
|
||||
for (MappingMode mappingMode : command.getModes()) {
|
||||
Node<LazyVimCommand> node = getKeyRoot(mappingMode);
|
||||
NodesKt.addLeafs(node, keyStrokes, command);
|
||||
getBuiltinCommandsTrie(mappingMode).add(keyStrokes, command);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -225,53 +219,79 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
|
||||
return new CustomShortcutSet(shortcuts.toArray(new Shortcut[0]));
|
||||
}
|
||||
|
||||
private static @NotNull List<Pair<EnumSet<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<? extends MappingMode> modes) {
|
||||
final Map<ImmutableList<KeyStroke>, EnumSet<MappingMode>> actualModes = new HashMap<>();
|
||||
private static @NotNull List<Pair<Set<MappingMode>, MappingInfo>> getKeyMappingRows(@NotNull Set<? extends MappingMode> modes,
|
||||
@NotNull List<? extends KeyStroke> prefix) {
|
||||
// Some map commands set a mapping for more than one mode (e.g. `map` sets for Normal, Visual, Select and
|
||||
// Op-pending). Vim treats this as a single mapping, and when listing all maps only lists it once, with the
|
||||
// appropriate mode indicator(s) in the first column (NVO is a space char). If the lhs mapping is changed or cleared
|
||||
// for one of the modes, the original mapping is still a single map for the remaining modes, and the indicator
|
||||
// changes. E.g. `map foo bar` followed by `sunmap foo` would result in `nox foo bar` in the output to `map`.
|
||||
// Vim doesn't do automatic grouping - `nmap foo bar` followed by `omap foo bar` and `vmap foo bar` would result in
|
||||
// 3 lines in the output to `map` - one for `n`, one for `o` and one for `v`.
|
||||
// We store mappings separately per mode (to simplify lookup, especially when matching prefixes), but want to have
|
||||
// the same behaviour as Vim in map output. So we store the original modes with the mapping and check they're still
|
||||
// valid as we collect output
|
||||
final List<Pair<Set<MappingMode>, MappingInfo>> rows = new ArrayList<>();
|
||||
final MultiMap<List<? extends KeyStroke>, Set<MappingMode>> multiModeMappings = MultiMap.create();
|
||||
final List<KeyStroke> fromKeys = new ArrayList<>();
|
||||
|
||||
for (MappingMode mode : modes) {
|
||||
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
|
||||
for (List<? extends KeyStroke> fromKeys : mapping) {
|
||||
final ImmutableList<KeyStroke> key = ImmutableList.copyOf(fromKeys);
|
||||
final EnumSet<MappingMode> value = actualModes.get(key);
|
||||
final EnumSet<MappingMode> newValue;
|
||||
if (value != null) {
|
||||
newValue = value.clone();
|
||||
newValue.add(mode);
|
||||
|
||||
final Iterator<KeyMappingEntry> iterator = mapping.getAll(prefix).iterator();
|
||||
while (iterator.hasNext()) {
|
||||
final KeyMappingEntry entry = iterator.next();
|
||||
final MappingInfo mappingInfo = entry.getMappingInfo();
|
||||
|
||||
final Set<@NotNull MappingMode> originalModes = mappingInfo.getOriginalModes();
|
||||
if (originalModes.size() == 1) {
|
||||
rows.add(new Pair<>(originalModes, mappingInfo));
|
||||
}
|
||||
else {
|
||||
newValue = EnumSet.of(mode);
|
||||
}
|
||||
actualModes.put(key, newValue);
|
||||
}
|
||||
}
|
||||
final List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = new ArrayList<>();
|
||||
for (Map.Entry<ImmutableList<KeyStroke>, EnumSet<MappingMode>> entry : actualModes.entrySet()) {
|
||||
final ArrayList<KeyStroke> fromKeys = new ArrayList<>(entry.getKey());
|
||||
final EnumSet<MappingMode> mappingModes = entry.getValue();
|
||||
if (!mappingModes.isEmpty()) {
|
||||
final MappingMode mode = mappingModes.iterator().next();
|
||||
final KeyMapping mapping = VimPlugin.getKey().getKeyMapping(mode);
|
||||
final MappingInfo mappingInfo = mapping.get(fromKeys);
|
||||
if (mappingInfo != null) {
|
||||
rows.add(new Pair<>(mappingModes, mappingInfo));
|
||||
entry.collectPath(fromKeys);
|
||||
if (!multiModeMappings.get(fromKeys).contains(originalModes)) {
|
||||
multiModeMappings.putValue(new ArrayList<>(fromKeys), originalModes);
|
||||
rows.add(new Pair<>(getModesForMapping(fromKeys, originalModes), mappingInfo));
|
||||
}
|
||||
}
|
||||
}
|
||||
rows.sort(Comparator.comparing(Pair<EnumSet<MappingMode>, MappingInfo>::getSecond));
|
||||
}
|
||||
rows.sort(Comparator.comparing(Pair<Set<MappingMode>, MappingInfo>::getSecond));
|
||||
return rows;
|
||||
}
|
||||
|
||||
private static @NotNull Set<MappingMode> getModesForMapping(@NotNull List<? extends KeyStroke> keyStrokes,
|
||||
@NotNull Set<MappingMode> originalMappingModes) {
|
||||
final Set<MappingMode> actualModes = EnumSet.noneOf(MappingMode.class);
|
||||
for (MappingMode mode : originalMappingModes) {
|
||||
final MappingInfo mappingInfo = VimPlugin.getKey().getKeyMapping(mode).get(keyStrokes);
|
||||
if (mappingInfo != null && mappingInfo.getOriginalModes() == originalMappingModes) {
|
||||
actualModes.add(mode);
|
||||
}
|
||||
}
|
||||
return actualModes;
|
||||
}
|
||||
|
||||
private static @NotNull @NonNls String getModesStringCode(@NotNull Set<MappingMode> modes) {
|
||||
if (modes.equals(MappingMode.NVO)) {
|
||||
return "";
|
||||
if (modes.equals(MappingMode.IC)) return "!";
|
||||
if (modes.equals(MappingMode.NVO)) return " ";
|
||||
if (modes.equals(MappingMode.C)) return "c";
|
||||
if (modes.equals(MappingMode.I)) return "i";
|
||||
//if (modes.equals(MappingMode.L)) return "l";
|
||||
|
||||
// The following modes are concatenated
|
||||
String mode = "";
|
||||
if (modes.containsAll(MappingMode.N)) mode += "n";
|
||||
if (modes.containsAll(MappingMode.O)) mode += "o";
|
||||
|
||||
if (modes.containsAll(MappingMode.V)) {
|
||||
mode += "v";
|
||||
}
|
||||
else if (modes.contains(MappingMode.INSERT)) {
|
||||
return "i";
|
||||
else {
|
||||
if (modes.containsAll(MappingMode.X)) mode += "x";
|
||||
if (modes.containsAll(MappingMode.S)) mode += "s";
|
||||
}
|
||||
else if (modes.contains(MappingMode.NORMAL)) {
|
||||
return "n";
|
||||
}
|
||||
// TODO: Add more codes
|
||||
return "";
|
||||
return mode;
|
||||
}
|
||||
|
||||
private @NotNull List<AnAction> getActions(@NotNull Component component, @NotNull KeyStroke keyStroke) {
|
||||
@ -335,20 +355,20 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull VimEditor editor) {
|
||||
List<Pair<EnumSet<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes);
|
||||
public boolean showKeyMappings(@NotNull Set<? extends MappingMode> modes, @NotNull List<? extends KeyStroke> prefix, @NotNull VimEditor editor) {
|
||||
List<Pair<Set<MappingMode>, MappingInfo>> rows = getKeyMappingRows(modes, prefix);
|
||||
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
for (Pair<EnumSet<MappingMode>, MappingInfo> row : rows) {
|
||||
for (Pair<Set<MappingMode>, MappingInfo> row : rows) {
|
||||
MappingInfo mappingInfo = row.getSecond();
|
||||
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 2, ' '));
|
||||
builder.append(" ");
|
||||
builder.append(StringsKt.padEnd(VimInjectorKt.getInjector().getParser().toKeyNotation(mappingInfo.getFromKeys()), 11, ' '));
|
||||
builder.append(" ");
|
||||
builder.append(mappingInfo.isRecursive() ? " " : "*");
|
||||
builder.append(" ");
|
||||
builder.append(StringsKt.padEnd(getModesStringCode(row.getFirst()), 3, ' '));
|
||||
builder.append(StringsKt.padEnd(VimInjectorKt.getInjector().getParser().toKeyNotation(mappingInfo.getFromKeys()) + " ", 12, ' '));
|
||||
builder.append(mappingInfo.isRecursive() ? " " : "*"); // Or `&` if script-local mappings being recursive
|
||||
builder.append(" "); // Should be `@` if it's a buffer-local mapping
|
||||
builder.append(mappingInfo.getPresentableString());
|
||||
builder.append("\n");
|
||||
}
|
||||
|
||||
VimOutputPanel outputPanel = injector.getOutputPanel().getOrCreate(editor, injector.getExecutionContextManager().getEditorExecutionContext(editor));
|
||||
outputPanel.addText(builder.toString(), true);
|
||||
outputPanel.show();
|
||||
|
@ -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 }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -22,6 +22,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
|
||||
@ -93,6 +94,9 @@ internal class MacroGroup : VimMacroBase() {
|
||||
} finally {
|
||||
keyStack.removeFirst()
|
||||
}
|
||||
if (!isInternalMacro) {
|
||||
MacroAutoImport.run(editor.ij, context.ij)
|
||||
}
|
||||
}
|
||||
|
||||
if (isInternalMacro) {
|
||||
|
@ -12,6 +12,7 @@ import com.intellij.application.options.CodeStyle
|
||||
import com.intellij.codeStyle.AbstractConvertLineSeparatorsAction
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorKind
|
||||
import com.intellij.openapi.editor.EditorSettings.LineNumerationType
|
||||
import com.intellij.openapi.editor.ScrollPositionCalculator
|
||||
@ -19,8 +20,6 @@ import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
|
||||
import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
import com.intellij.openapi.fileEditor.impl.LoadTextUtil
|
||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl
|
||||
import com.intellij.openapi.project.Project
|
||||
@ -158,25 +157,24 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup, InternalOpt
|
||||
}
|
||||
|
||||
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
|
||||
// reuse an existing window (opening a new window will always open from an existing window). More importantly,
|
||||
// this means that any newly edited file will always get up-to-date local-to-window options. A new window is based
|
||||
// on the opening window (treated as split then edit, so copy local + per-window "global" window values, then
|
||||
// apply the per-window "global" values) and an edit reapplies the per-window "global" values.
|
||||
// If we close all windows, and open a new one, we can only use the per-window "global" values from the fallback
|
||||
// window, but this is only initialised when we first read `~/.ideavimrc` during startup. Vim would use the values
|
||||
// from the current window, so to simulate this, we should update the fallback window with the values from the
|
||||
// window that was selected at the time that the last window was closed.
|
||||
// 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)
|
||||
}
|
||||
fun editorReleased(editor: Editor) {
|
||||
// Vim always has at least one window; it's not possible to close it. Editing a new file will open a new buffer in
|
||||
// the current window, or it's possible to split the current buffer into a new window, or open a new buffer in a
|
||||
// new window. This is important for us because when Vim opens a new window, the new window's local options are
|
||||
// copied from the current window.
|
||||
// In detail: splitting the current window gets a complete copy of local and per-window global option values.
|
||||
// Editing a new file will split the current window and then edit the new buffer in-place.
|
||||
// IntelliJ does not always have an open window. It would be weird to close the last editor tab, and then open
|
||||
// the next tab with different options - the user would expect the editor to look like the last one did.
|
||||
// Therefore, we have a dummy "fallback" window that captures the options of the last closed editor. When opening
|
||||
// an editor and there are no currently open editors, we use the fallback window to initialise the new window.
|
||||
// This callback tracks when editors are closed, and if the last editor in a project is being closed, updates the
|
||||
// fallback window's options.
|
||||
val project = editor.project ?: return
|
||||
if (!injector.editorGroup.getEditorsRaw()
|
||||
.any { it.ij != editor && it.ij.project === project && it.ij.editorKind == EditorKind.MAIN_EDITOR }
|
||||
) {
|
||||
(VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, editor.vim)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.api.VimInjectorKt;
|
||||
import com.maddyhome.idea.vim.newapi.IjVimInjectorKt;
|
||||
import com.maddyhome.idea.vim.register.Register;
|
||||
import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.group
|
||||
|
||||
import com.intellij.codeWithMe.ClientId
|
||||
import com.intellij.ide.bookmark.Bookmark
|
||||
import com.intellij.ide.bookmark.BookmarkGroup
|
||||
import com.intellij.ide.bookmark.BookmarksListener
|
||||
|
@ -51,6 +51,8 @@ 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.undo.VimKeyBasedUndoService
|
||||
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
|
||||
import java.awt.datatransfer.DataFlavor
|
||||
|
||||
@Service
|
||||
@ -85,10 +87,16 @@ internal class PutGroup : VimPutBase() {
|
||||
val context = vimContext.context as DataContext
|
||||
val carets: MutableMap<Caret, RangeMarker> = mutableMapOf()
|
||||
if (injector.vimState.mode is Mode.INSERT) {
|
||||
val undo = injector.undo
|
||||
val nanoTime = System.nanoTime()
|
||||
|
||||
val undo = injector.undo
|
||||
when (undo) {
|
||||
is VimKeyBasedUndoService -> undo.setInsertNonMergeUndoKey()
|
||||
is VimTimestampBasedUndoService -> {
|
||||
vimEditor.forEachCaret { undo.startInsertSequence(it, it.offset, nanoTime) }
|
||||
}
|
||||
}
|
||||
}
|
||||
EditorHelper.getOrderedCaretsList(editor).forEach { caret ->
|
||||
val startOffset =
|
||||
prepareDocumentAndGetStartOffsets(
|
||||
|
@ -8,6 +8,7 @@
|
||||
|
||||
package com.maddyhome.idea.vim.group.visual
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.trace
|
||||
import com.intellij.openapi.editor.Editor
|
||||
@ -15,6 +16,8 @@ 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.options
|
||||
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.controlNonVimSelectionChange
|
||||
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl.predictMode
|
||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.hasVisualSelection
|
||||
@ -63,12 +66,15 @@ internal object IdeaSelectionControl {
|
||||
// - There was no selection and now it is
|
||||
// - There was a selection and now it doesn't exist
|
||||
// - There was a selection and now it exists as well (transforming char selection to line selection, for example)
|
||||
if (initialMode?.hasVisualSelection == false && !editor.selectionModel.hasSelection(true)) {
|
||||
val hasSelection = ApplicationManager.getApplication().runReadAction<Boolean> {
|
||||
editor.selectionModel.hasSelection(true)
|
||||
}
|
||||
if (initialMode?.hasVisualSelection == false && !hasSelection) {
|
||||
logger.trace { "Exiting without selection adjusting" }
|
||||
return@singleTask
|
||||
}
|
||||
|
||||
if (editor.selectionModel.hasSelection(true)) {
|
||||
if (hasSelection) {
|
||||
if (editor.vim.inCommandLineMode && editor.vim.mode.returnTo().hasVisualSelection) {
|
||||
logger.trace { "Modifying selection while in Command-line mode, most likely incsearch" }
|
||||
return@singleTask
|
||||
|
@ -9,8 +9,6 @@
|
||||
package com.maddyhome.idea.vim.group.visual
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
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
|
||||
|
@ -183,6 +183,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
|
||||
* - App code - set handler after
|
||||
* - Template - doesn't intersect with enter anymore
|
||||
* - rd.client.editor.enter - set handler before. Otherwise, rider will add new line on enter even in normal mode
|
||||
* - inline.completion.enter - set handler before. Otherwise, AI completion is not invoked on enter.
|
||||
*
|
||||
* This rule is disabled due to VIM-3124
|
||||
* - before terminalEnter - not necessary, but terminalEnter causes "file is read-only" tooltip for readonly files VIM-3122
|
||||
|
@ -157,6 +157,19 @@ public class EditorHelper {
|
||||
return (int)(getVisibleArea(editor).width / getPlainSpaceWidthFloat(editor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of characters that can be fit inside the output panel for an editor.
|
||||
* <p>
|
||||
* This will be greater than the approximate screen width as it also includes any gutter components in the editor.
|
||||
* </p>
|
||||
*
|
||||
* @param editor The editor
|
||||
* @return The approximate number of columns that can fit in the output panel
|
||||
*/
|
||||
public static int getApproximateOutputPanelWidth(final @NotNull Editor editor) {
|
||||
return (int)(editor.getComponent().getWidth() / getPlainSpaceWidthFloat(editor));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the width of the space character in the editor's plain font as a float.
|
||||
* <p>
|
||||
@ -273,7 +286,7 @@ public class EditorHelper {
|
||||
|
||||
// Scroll the given visual line to the caret location, but do not scroll down passed the end of file, or the current
|
||||
// virtual space at the bottom of the screen
|
||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
||||
final int yBottomLineOffset = max(getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine), visibleArea.y);
|
||||
scrollVertically(editor, min(yVisualLine - caretScreenOffset - inlayOffset, yBottomLineOffset));
|
||||
@ -325,8 +338,8 @@ public class EditorHelper {
|
||||
final int lineHeight = editor.getLineHeight();
|
||||
|
||||
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
|
||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
||||
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
|
||||
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.
|
||||
@ -379,7 +392,7 @@ public class EditorHelper {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int getHorizontalScrollbarHeight(@NotNull final Editor editor) {
|
||||
private static int getHorizontalScrollbarHeight(final @NotNull Editor editor) {
|
||||
// Horizontal scrollbars on macOS are either transparent AND auto-hide, so we don't need to worry about obscured
|
||||
// text, or always visible, opaque and outside the content area, so we don't need to adjust for them
|
||||
// Transparent scrollbars on Windows and Linux are overlays on the editor content area, and always visible. That
|
||||
@ -462,7 +475,7 @@ public class EditorHelper {
|
||||
*/
|
||||
public static Pair<Boolean, Integer> scrollFullPageDown(final @NotNull Editor editor, int pages) {
|
||||
final Rectangle visibleArea = getVisibleArea(editor);
|
||||
@NotNull final VimEditor editor2 = new IjVimEditor(editor);
|
||||
final @NotNull VimEditor editor2 = new IjVimEditor(editor);
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor2) - 1;
|
||||
|
||||
int y = visibleArea.y + visibleArea.height;
|
||||
@ -480,7 +493,7 @@ public class EditorHelper {
|
||||
caretVisualLine = lastVisualLine;
|
||||
}
|
||||
else {
|
||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
|
||||
caretVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
||||
completed = false;
|
||||
}
|
||||
@ -515,7 +528,7 @@ public class EditorHelper {
|
||||
public static Pair<Boolean, Integer> scrollFullPageUp(final @NotNull Editor editor, int pages) {
|
||||
final Rectangle visibleArea = getVisibleArea(editor);
|
||||
final int lineHeight = editor.getLineHeight();
|
||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||
final @NotNull VimEditor editor1 = new IjVimEditor(editor);
|
||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
||||
|
||||
int y = visibleArea.y;
|
||||
|
@ -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
|
||||
@ -20,6 +21,8 @@ import com.maddyhome.idea.vim.api.StringListOptionValue
|
||||
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
|
||||
@ -96,3 +99,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
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||
import com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks
|
||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||
import com.intellij.openapi.actionSystem.impl.Utils
|
||||
import com.intellij.openapi.application.ex.ApplicationManagerEx
|
||||
import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||
@ -74,7 +75,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
val applicationEx = ApplicationManagerEx.getApplicationEx()
|
||||
if (ProgressIndicatorUtils.isWriteActionRunningOrPending(applicationEx)) {
|
||||
// This is needed for VIM-3376 and it should turn into error at soeme moment
|
||||
thisLogger().warn(RuntimeException("Actions cannot be updated when write-action is running or pending", ))
|
||||
thisLogger().warn("Actions cannot be updated when write-action is running or pending")
|
||||
}
|
||||
|
||||
val ijAction = (action as IjNativeAction).action
|
||||
@ -95,6 +96,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
||||
ActionManager.getInstance(),
|
||||
0,
|
||||
)
|
||||
Utils.initUpdateSession(event)
|
||||
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
|
||||
// because rider use async update method. See VIM-1819.
|
||||
// This method executes inside of lastUpdateAndCheckDumb
|
||||
|
@ -13,7 +13,6 @@ import com.intellij.openapi.editor.ReadOnlyFragmentModificationException
|
||||
import com.intellij.openapi.editor.VisualPosition
|
||||
import com.intellij.openapi.editor.actionSystem.EditorActionManager
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.maddyhome.idea.vim.api.EngineEditorHelper
|
||||
import com.maddyhome.idea.vim.api.EngineEditorHelperBase
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimRangeMarker
|
||||
@ -42,6 +41,10 @@ internal class IjEditorHelper : EngineEditorHelperBase() {
|
||||
return EditorHelper.getApproximateScreenWidth(editor.ij)
|
||||
}
|
||||
|
||||
override fun getApproximateOutputPanelWidth(editor: VimEditor): Int {
|
||||
return EditorHelper.getApproximateOutputPanelWidth(editor.ij)
|
||||
}
|
||||
|
||||
override fun handleWithReadonlyFragmentModificationHandler(editor: VimEditor, exception: Exception) {
|
||||
return EditorActionManager.getInstance()
|
||||
.getReadonlyFragmentModificationHandler(editor.ij.document)
|
||||
|
@ -29,7 +29,6 @@ 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
|
||||
@ -60,7 +59,7 @@ internal object ScrollViewHelper {
|
||||
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
||||
val topLine = getVisualLineAtTopOfScreen(editor)
|
||||
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
||||
val lastLine = vimEditor.getVisualLineCount() - 1
|
||||
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
|
||||
|
||||
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
||||
val scrollOffset = injector.options(vimEditor).scrolloff
|
||||
|
@ -102,7 +102,8 @@ private fun updateSearchHighlights(
|
||||
// Update highlights in all visible editors. We update non-visible editors when they get focus.
|
||||
// Note that this now includes all editors - main, diff windows, even toolwindows like the Commit editor and consoles
|
||||
val editors = injector.editorGroup.getEditors().filter {
|
||||
injector.application.isUnitTest() || it.ij.component.isShowing
|
||||
(injector.application.isUnitTest() || it.ij.component.isShowing)
|
||||
&& (currentEditor == null || it.projectId == currentEditor.projectId)
|
||||
}
|
||||
|
||||
editors.forEach {
|
||||
|
@ -14,7 +14,9 @@ import com.intellij.openapi.command.CommandProcessor
|
||||
import com.intellij.openapi.command.undo.UndoManager
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.fileEditor.TextEditor
|
||||
import com.intellij.openapi.fileEditor.TextEditorWithPreview
|
||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
@ -28,13 +30,15 @@ import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
|
||||
|
||||
/**
|
||||
* @author oleg
|
||||
*/
|
||||
@Service
|
||||
internal class UndoRedoHelper : UndoRedoBase() {
|
||||
internal class UndoRedoHelper : VimTimestampBasedUndoService {
|
||||
companion object {
|
||||
private val logger = logger<UndoRedoHelper>()
|
||||
}
|
||||
@ -42,13 +46,13 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||
val ijContext = context.context as DataContext
|
||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
|
||||
val textEditor = getTextEditor(editor.ij)
|
||||
val undoManager = UndoManager.getInstance(project)
|
||||
if (undoManager.isUndoAvailable(fileEditor)) {
|
||||
if (undoManager.isUndoAvailable(textEditor)) {
|
||||
val scrollingModel = editor.getScrollingModel()
|
||||
scrollingModel.accumulateViewportChanges()
|
||||
|
||||
performUndo(editor, undoManager, fileEditor)
|
||||
performUndo(editor, undoManager, textEditor)
|
||||
|
||||
scrollingModel.flushViewportChanges()
|
||||
|
||||
@ -57,6 +61,15 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getTextEditor(editor: Editor): TextEditor {
|
||||
// If the Editor is hosted in a TextEditor with a preview, then TextEditorProvider will return a TextEditor for the
|
||||
// hosted instance, not for the main editor that also contains the preview. If we pass the inner TextEditor to the
|
||||
// UndoManager, it doesn't correctly restore state. Specifically, the change is undone/redone, but the caret is not
|
||||
// moved. See VIM-3671.
|
||||
val currentTextEditor = TextEditorProvider.getInstance().getTextEditor(editor)
|
||||
return TextEditorWithPreview.getParentSplitEditor(currentTextEditor) as? TextEditor ?: currentTextEditor
|
||||
}
|
||||
|
||||
private fun performUndo(
|
||||
editor: VimEditor,
|
||||
undoManager: UndoManager,
|
||||
@ -66,15 +79,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||
editor.runWithChangeTracking {
|
||||
undoManager.undo(fileEditor)
|
||||
|
||||
// We execute undo one more time if the previous one just restored selection
|
||||
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
|
||||
undoManager.undo(fileEditor)
|
||||
}
|
||||
}
|
||||
|
||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||
removeSelections(editor)
|
||||
restoreVisualMode(editor)
|
||||
}
|
||||
} else {
|
||||
notifyAboutNewUndo(editor.ij.project)
|
||||
@ -112,10 +117,10 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
||||
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||
val ijContext = context.context as DataContext
|
||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
|
||||
val textEditor = getTextEditor(editor.ij)
|
||||
val undoManager = UndoManager.getInstance(project)
|
||||
if (undoManager.isRedoAvailable(fileEditor)) {
|
||||
performRedo(undoManager, fileEditor, editor)
|
||||
if (undoManager.isRedoAvailable(textEditor)) {
|
||||
performRedo(undoManager, textEditor, editor)
|
||||
|
||||
return true
|
||||
}
|
||||
@ -229,4 +234,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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,16 +18,14 @@ import com.intellij.openapi.editor.VisualPosition
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.util.Key
|
||||
import com.intellij.openapi.util.UserDataHolder
|
||||
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
|
||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
||||
import com.maddyhome.idea.vim.api.SelectionInfo
|
||||
import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
|
||||
import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.common.VimEditorReplaceMask
|
||||
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.SelectionType
|
||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
||||
@ -97,7 +95,6 @@ internal var Caret.vimInsertStart: RangeMarker by userDataOr {
|
||||
}
|
||||
|
||||
// TODO: Data could be lost during visual block motion
|
||||
internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretToEditor()
|
||||
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
|
||||
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()
|
||||
|
||||
|
@ -1,32 +0,0 @@
|
||||
/*
|
||||
* Copyright 2003-2023 The IdeaVim authors
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style
|
||||
* license that can be found in the LICENSE.txt file or at
|
||||
* https://opensource.org/licenses/MIT.
|
||||
*/
|
||||
|
||||
package com.maddyhome.idea.vim.helper
|
||||
|
||||
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
|
||||
import com.intellij.openapi.components.Service
|
||||
import com.intellij.openapi.components.service
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.group.NotificationService
|
||||
import com.maddyhome.idea.vim.icons.VimIcons
|
||||
|
||||
@Service(Service.Level.APP)
|
||||
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
|
||||
VimPlugin.getPluginId(),
|
||||
updateTimestampProperty = PROPERTY_NAME,
|
||||
NotificationService.IDEAVIM_STICKY_GROUP,
|
||||
VimIcons.IDEAVIM,
|
||||
) {
|
||||
|
||||
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
|
||||
|
||||
companion object {
|
||||
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
|
||||
fun getInstance(): VimStandalonePluginUpdateChecker = service()
|
||||
}
|
||||
}
|
@ -1,15 +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.key
|
||||
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
|
||||
internal fun <T> Node<T>.addLeafs(keys: String, actionHolder: T) {
|
||||
addLeafs(injector.parser.parseKeys(keys), actionHolder)
|
||||
}
|
@ -10,15 +10,12 @@ package com.maddyhome.idea.vim.listener
|
||||
|
||||
import com.intellij.execution.impl.ConsoleViewImpl
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorKind
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.EditorListener
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
import com.maddyhome.idea.vim.helper.inInsertMode
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
@ -31,8 +28,8 @@ import com.maddyhome.idea.vim.state.mode.Mode
|
||||
*/
|
||||
class IJEditorFocusListener : EditorListener {
|
||||
override fun focusGained(editor: VimEditor) {
|
||||
val oldEditor = KeyHandler.getInstance().editorInFocus
|
||||
if (oldEditor != null && oldEditor.ij == editor.ij) return
|
||||
val editorInFocus = KeyHandler.getInstance().editorInFocus
|
||||
if (editorInFocus != null && editorInFocus.ij == editor.ij) return
|
||||
|
||||
KeyHandler.getInstance().editorInFocus = editor
|
||||
|
||||
@ -63,9 +60,7 @@ class IJEditorFocusListener : EditorListener {
|
||||
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
|
||||
VimPlugin.getChange().insertBeforeCursor(editor, context)
|
||||
}
|
||||
if (isTerminal(ijEditor) && !ijEditor.inInsertMode) {
|
||||
switchToInsertMode.run()
|
||||
} else if (ijEditor.isInsertMode && ((oldEditor != null && isTerminal(oldEditor.ij)) || !ijEditor.document.isWritable)) {
|
||||
if (!ijEditor.document.isWritable) {
|
||||
val context: ExecutionContext = injector.executionContextManager.getEditorExecutionContext(editor)
|
||||
val mode = injector.vimState.mode
|
||||
when (mode) {
|
||||
@ -82,12 +77,5 @@ class IJEditorFocusListener : EditorListener {
|
||||
}
|
||||
KeyHandler.getInstance().reset(editor)
|
||||
}
|
||||
|
||||
// By "terminal" we refer to some editor that should switch to INSERT mode on focus
|
||||
private fun isTerminal(ijEditor: Editor): Boolean {
|
||||
return !ijEditor.isViewer &&
|
||||
!EditorHelper.isFileEditor(ijEditor) &&
|
||||
ijEditor.document.isWritable &&
|
||||
ijEditor.editorKind != EditorKind.DIFF
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,9 @@ import com.intellij.codeInsight.lookup.impl.actions.ChooseItemAction
|
||||
import com.intellij.codeInsight.template.Template
|
||||
import com.intellij.codeInsight.template.TemplateEditingAdapter
|
||||
import com.intellij.codeInsight.template.TemplateManagerListener
|
||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
|
||||
import com.intellij.codeInsight.template.impl.TemplateState
|
||||
import com.intellij.codeInsight.template.impl.actions.NextVariableAction
|
||||
import com.intellij.find.FindModelListener
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
||||
@ -28,6 +30,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.impl.ScrollingModelImpl
|
||||
import com.intellij.openapi.keymap.KeymapManager
|
||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||
import com.intellij.openapi.util.TextRange
|
||||
@ -58,6 +61,7 @@ internal object IdeaSpecifics {
|
||||
private val surrounderAction =
|
||||
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
||||
private var editor: Editor? = null
|
||||
private var caretOffset = -1
|
||||
private var completionPrevDocumentLength: Int? = null
|
||||
private var completionPrevDocumentOffset: Int? = null
|
||||
|
||||
@ -67,6 +71,7 @@ internal object IdeaSpecifics {
|
||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||
if (hostEditor != null) {
|
||||
editor = hostEditor
|
||||
caretOffset = hostEditor.caretModel.offset
|
||||
}
|
||||
|
||||
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
||||
@ -115,7 +120,8 @@ internal object IdeaSpecifics {
|
||||
if (VimPlugin.isNotEnabled()) return
|
||||
|
||||
val editor = editor
|
||||
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
|
||||
if (editor != null) {
|
||||
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
|
||||
val prevDocumentLength = completionPrevDocumentLength
|
||||
val prevDocumentOffset = completionPrevDocumentOffset
|
||||
|
||||
@ -148,9 +154,27 @@ internal object IdeaSpecifics {
|
||||
KeyHandler.getInstance().reset(it.vim)
|
||||
}
|
||||
}
|
||||
else if (action is NextVariableAction && TemplateManagerImpl.getTemplateState(editor) == null) {
|
||||
editor.vim.exitInsertMode(event.dataContext.vim)
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
//endregion
|
||||
|
||||
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
||||
val scrollModel = editor.scrollingModel as ScrollingModelImpl
|
||||
if (scrollModel.isScrollingNow) {
|
||||
val v = scrollModel.verticalScrollOffset
|
||||
val h = scrollModel.horizontalScrollOffset
|
||||
scrollModel.finishAnimation()
|
||||
scrollModel.scroll(h, v)
|
||||
scrollModel.finishAnimation()
|
||||
}
|
||||
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||
}
|
||||
}
|
||||
|
||||
this.editor = null
|
||||
this.caretOffset = -1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,7 +16,6 @@ import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.diagnostic.trace
|
||||
import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.Editor
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.EditorKind
|
||||
import com.intellij.openapi.editor.actionSystem.TypedAction
|
||||
import com.intellij.openapi.editor.event.CaretEvent
|
||||
@ -32,9 +31,10 @@ 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.EditorEx
|
||||
import com.intellij.openapi.editor.ex.FocusChangeListener
|
||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
|
||||
import com.intellij.openapi.fileEditor.FileEditor
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||
@ -44,12 +44,15 @@ import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||
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.observable.util.addKeyListener
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.ProjectManager
|
||||
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.intellij.util.SlowOperations
|
||||
import com.maddyhome.idea.vim.EventFacade
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimKeyListener
|
||||
@ -62,7 +65,6 @@ import com.maddyhome.idea.vim.api.coerceOffset
|
||||
import com.maddyhome.idea.vim.api.getLineEndForOffset
|
||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||
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
|
||||
@ -79,7 +81,6 @@ import com.maddyhome.idea.vim.handler.keyCheckRequests
|
||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
|
||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||
import com.maddyhome.idea.vim.helper.StrictMode
|
||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
|
||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||
import com.maddyhome.idea.vim.helper.forceBarCursor
|
||||
@ -95,6 +96,7 @@ import com.maddyhome.idea.vim.newapi.IjVimSearchGroup
|
||||
import com.maddyhome.idea.vim.newapi.InsertTimeRecorder
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||
@ -103,9 +105,11 @@ import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
|
||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
|
||||
import com.maddyhome.idea.vim.vimDisposable
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.*
|
||||
import javax.swing.SwingUtilities
|
||||
|
||||
/**
|
||||
@ -127,23 +131,15 @@ import javax.swing.SwingUtilities
|
||||
* Make sure the selected editor isn't the new editor, which can happen if there are no other editors open.
|
||||
*/
|
||||
private fun getOpeningEditor(newEditor: Editor) = newEditor.project?.let { project ->
|
||||
// Some TextEditor implementations create a dummy Editor instance on demand, e.g., while downloading a file to edit
|
||||
// (see BaseRemoteFileEditor). This can cause recursion if the newly opened/created TextEditor is also the currently
|
||||
// selected TextEditor, because we will be notified of the new dummy Editor before it has finished initialisation, and
|
||||
// try to get its opening editor, causing a new dummy Editor to be created and notifications sent, and so on.
|
||||
// This was reported for 232 and 233 (see VIM-3066), but I can't recreate in 241. The callstack looks different, now
|
||||
// using coroutines, so it's possible the deadlock has been broken. However, it's sensible to leave the recursion
|
||||
// guard in.
|
||||
if (openingEditorRecursionGuard) return null
|
||||
openingEditorRecursionGuard = true
|
||||
try {
|
||||
FileEditorManager.getInstance(project).selectedTextEditor?.takeUnless { it == newEditor }
|
||||
}
|
||||
finally {
|
||||
openingEditorRecursionGuard = false
|
||||
}
|
||||
// We can't rely on FileEditorManager.selectedTextEditor because we're trying to retrieve the selected text editor
|
||||
// while creating a text editor that is about to become the selected text editor.
|
||||
// This worked fine for 2024.2, but internal changes for 2024.3 broke things. It appears that the currently selected
|
||||
// text editor is reset to null while the soon-to-be-selected text editor is being created. We therefore track the
|
||||
// last selected editor manually.
|
||||
// Note that if we ever switch back to FileEditorManager.selectedTextEditor, be careful of recursion, because the
|
||||
// actual editor might be created on-demand, which would notify our initialisation method, which would call us...
|
||||
VimListenerManager.VimLastSelectedEditorTracker.getLastSelectedEditor(project)?.takeUnless { it == newEditor }
|
||||
}
|
||||
private var openingEditorRecursionGuard = false
|
||||
|
||||
internal object VimListenerManager {
|
||||
|
||||
@ -153,7 +149,9 @@ internal object VimListenerManager {
|
||||
|
||||
fun turnOn() {
|
||||
GlobalListeners.enable()
|
||||
SlowOperations.knownIssue("VIM-3648, VIM-3649").use {
|
||||
EditorListeners.addAll()
|
||||
}
|
||||
check(correctorRequester.tryEmit(Unit))
|
||||
check(keyCheckRequests.tryEmit(Unit))
|
||||
|
||||
@ -214,16 +212,6 @@ internal object VimListenerManager {
|
||||
EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
|
||||
val busConnection = ApplicationManager.getApplication().messageBus.connect(VimPlugin.getInstance().onOffDisposable)
|
||||
busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
|
||||
|
||||
// Listen for focus change to update various features such as mode widget
|
||||
val eventMulticaster = EditorFactory.getInstance().eventMulticaster
|
||||
(eventMulticaster as? EditorEventMulticasterEx)?.addFocusChangeListener(
|
||||
VimFocusListener,
|
||||
VimPlugin.getInstance().onOffDisposable
|
||||
)
|
||||
|
||||
// Listen for document changes to update document state such as marks
|
||||
eventMulticaster.addDocumentListener(VimDocumentListener, VimPlugin.getInstance().onOffDisposable)
|
||||
}
|
||||
|
||||
fun disable() {
|
||||
@ -285,47 +273,52 @@ internal object VimListenerManager {
|
||||
// TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
|
||||
if (vimDisabled(editor)) return
|
||||
|
||||
// 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
|
||||
// Note that this uses the plugin's main disposable, rather than VimPlugin.onOffDisposable, because we don't need
|
||||
// to - we explicitly call VimListenerManager.removeAll from VimPlugin.turnOffPlugin, and this disposes each
|
||||
// editor's disposable individually.
|
||||
val disposable = editor.project?.vimDisposable ?: return
|
||||
|
||||
// Protect against double initialisation
|
||||
if (editor.getUserData(editorListenersDisposableKey) != null) {
|
||||
return
|
||||
}
|
||||
|
||||
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
|
||||
// Make sure we explicitly dispose this per-editor disposable!
|
||||
// Because the listeners are registered with a parent disposable, they add child disposables that have to call a
|
||||
// method on the editor to remove the listener. This means the disposable contains a reference to the editor (even
|
||||
// if the listener handler is a singleton that doesn't hold a reference).
|
||||
// Unless the per-editor disposable is disposed, all of these disposables sit in the disposer tree until the
|
||||
// parent disposable is disposed, which will mean we leak editor instances.
|
||||
// The per-editor disposable is explicitly disposed when the editor is released, and disposed via its parent when
|
||||
// the plugin's on/off functionality is toggled, and so also when the plugin is disabled/unloaded by the platform.
|
||||
// It doesn't matter if we explicitly remove all listeners before disposing onOffDisposable, as that will remove
|
||||
// the per-editor disposable from the disposer tree.
|
||||
val perEditorDisposable = Disposer.newDisposable(VimPlugin.getInstance().onOffDisposable)
|
||||
editor.putUserData(editorListenersDisposableKey, perEditorDisposable)
|
||||
|
||||
Disposer.register(listenersDisposable) {
|
||||
Disposer.register(perEditorDisposable) {
|
||||
if (VimListenerTestObject.enabled) {
|
||||
VimListenerTestObject.disposedCounter += 1
|
||||
}
|
||||
}
|
||||
|
||||
editor.contentComponent.addKeyListener(VimKeyListener)
|
||||
Disposer.register(listenersDisposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
|
||||
// This listener and several below add a reference to the editor to the disposer tree
|
||||
editor.contentComponent.addKeyListener(perEditorDisposable, VimKeyListener)
|
||||
|
||||
// Initialise the local options. We MUST do this before anything has the chance to query options
|
||||
val vimEditor = editor.vim
|
||||
VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
|
||||
|
||||
val eventFacade = EventFacade.getInstance()
|
||||
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)
|
||||
eventFacade.addEditorMouseListener(editor, EditorMouseHandler, perEditorDisposable)
|
||||
eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, perEditorDisposable)
|
||||
eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, perEditorDisposable)
|
||||
eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, perEditorDisposable)
|
||||
eventFacade.addCaretListener(editor, EditorCaretHandler, perEditorDisposable)
|
||||
|
||||
VimPlugin.getEditor().editorCreated(editor)
|
||||
VimPlugin.getChange().editorCreated(editor, listenersDisposable)
|
||||
VimPlugin.getChange().editorCreated(editor, perEditorDisposable)
|
||||
|
||||
(editor as EditorEx).addFocusListener(VimFocusListener, perEditorDisposable)
|
||||
|
||||
injector.listenersNotifier.notifyEditorCreated(vimEditor)
|
||||
|
||||
Disposer.register(listenersDisposable) {
|
||||
Disposer.register(perEditorDisposable) {
|
||||
VimPlugin.getEditor().editorDeinit(editor)
|
||||
}
|
||||
}
|
||||
@ -363,7 +356,7 @@ internal object VimListenerManager {
|
||||
* open in non-local Code With Me guest editors, which we still want to process (e.g. to update marks when a guest
|
||||
* edits a file. Updating search highlights will be a no-op if there are no open local editors)
|
||||
*/
|
||||
private object VimDocumentListener : DocumentListener {
|
||||
class VimDocumentListener : DocumentListener {
|
||||
override fun beforeDocumentChange(event: DocumentEvent) {
|
||||
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
|
||||
IjVimSearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
|
||||
@ -377,6 +370,26 @@ internal object VimListenerManager {
|
||||
}
|
||||
}
|
||||
|
||||
internal object VimLastSelectedEditorTracker {
|
||||
// This stores a weak reference to an editor against a weak reference to a project, which means there is nothing
|
||||
// keeping the project or editor from being garbage collected at any time. Stale keys are automatically expunged
|
||||
// whenever the map is used.
|
||||
private val selectedEditors = WeakHashMap<Project, WeakReference<Editor>>()
|
||||
|
||||
fun getLastSelectedEditor(project: Project): Editor? = selectedEditors[project]?.get()
|
||||
|
||||
internal fun setLastSelectedEditor(fileEditor: FileEditor?) {
|
||||
(fileEditor as? TextEditor)?.editor?.let { editor ->
|
||||
editor.project?.let { project -> selectedEditors[project] = WeakReference(editor) }
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
internal fun resetLastSelectedEditor(project: Project) {
|
||||
selectedEditors.remove(project)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the selected file editor changes. In other words, when the user selects a new tab. Used to remember the
|
||||
* last selected file, update search highlights in the new tab, etc. This will be called with non-local Code With Me
|
||||
@ -387,11 +400,22 @@ internal object VimListenerManager {
|
||||
// We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
|
||||
if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return
|
||||
|
||||
val newEditor = event.newEditor
|
||||
if (newEditor is TextEditor) {
|
||||
val editor = newEditor.editor
|
||||
if (editor.isInsertMode) {
|
||||
editor.vim.mode = Mode.NORMAL()
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
// Breaks relativenumber for some reason
|
||||
// injector.scroll.scrollCaretIntoView(editor.vim)
|
||||
}
|
||||
|
||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
|
||||
OptionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||
IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event)
|
||||
VimLastSelectedEditorTracker.setLastSelectedEditor(event.newEditor)
|
||||
}
|
||||
}
|
||||
|
||||
@ -458,15 +482,26 @@ internal object VimListenerManager {
|
||||
|
||||
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
||||
}
|
||||
|
||||
VimStandalonePluginUpdateChecker.getInstance().pluginUsed()
|
||||
}
|
||||
|
||||
override fun editorReleased(event: EditorFactoryEvent) {
|
||||
if (vimDisabled(event.editor)) return
|
||||
val vimEditor = event.editor.vim
|
||||
EditorListeners.remove(event.editor)
|
||||
injector.listenersNotifier.notifyEditorReleased(vimEditor)
|
||||
injector.markService.editorReleased(vimEditor)
|
||||
|
||||
// This ticket will have a different stack trace, but it's the same problem. Originally, we tracked the last
|
||||
// editor closing based on file selection (closing an editor would select the next editor - so a null selection
|
||||
// was taken to mean that there were no more editors to select). This assumption broke in 242, so it's changed to
|
||||
// check when the editor is released.
|
||||
// However, the actions taken when the last editor closes can still be expensive/slow because we copy options, and
|
||||
// some options are backed by PSI options. E.g. 'textwidth' is mapped to
|
||||
// CodeStyle.getSettings(ijEditor).isWrapOnTyping(language)), and getting the document's PSI language is a slow
|
||||
// operation. This underlying issue still needs to be addressed, even though the method has moved
|
||||
SlowOperations.knownIssue("VIM-3658").use {
|
||||
OptionGroup.editorReleased(event.editor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun fileOpenedSync(
|
||||
@ -754,6 +789,10 @@ internal object VimListenerManager {
|
||||
// https://youtrack.jetbrains.com/issue/IDEA-277716
|
||||
// https://youtrack.jetbrains.com/issue/VIM-2368
|
||||
if (event.mouseEvent.clickCount == 1 && !SwingUtilities.isRightMouseButton(event.mouseEvent)) {
|
||||
val hasSelection = ApplicationManager.getApplication().runReadAction<Boolean> {
|
||||
editor.selectionModel.hasSelection(true)
|
||||
}
|
||||
if (!hasSelection) {
|
||||
if (editor.inVisualMode) {
|
||||
editor.vim.exitVisualMode()
|
||||
} else if (editor.vim.inSelectMode) {
|
||||
@ -761,6 +800,7 @@ internal object VimListenerManager {
|
||||
KeyHandler.getInstance().reset(editor.vim)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (event.area != EditorMouseEventArea.ANNOTATIONS_AREA &&
|
||||
event.area != EditorMouseEventArea.FOLDING_OUTLINE_AREA &&
|
||||
event.mouseEvent.button != MouseEvent.BUTTON3
|
||||
|
@ -22,10 +22,13 @@ import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.project.IndexNotReadyException
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.util.ui.EmptyClipboardOwner
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimClipboardManager
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.getText
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.TextRange
|
||||
import com.maddyhome.idea.vim.common.VimCopiedText
|
||||
import com.maddyhome.idea.vim.diagnostic.debug
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import java.awt.HeadlessException
|
||||
@ -37,18 +40,52 @@ import java.io.IOException
|
||||
|
||||
@Service
|
||||
internal class IjClipboardManager : VimClipboardManager {
|
||||
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getPrimaryTextAndTransferableData")
|
||||
override fun getPrimaryTextAndTransferableData(): Pair<String, List<Any>?>? {
|
||||
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return null
|
||||
val contents = clipboard.getContents(null) ?: return null
|
||||
return getTextAndTransferableData(contents)
|
||||
}
|
||||
|
||||
override fun getPrimaryContent(editor: VimEditor, context: ExecutionContext): IjVimCopiedText? {
|
||||
val (text, transferableData) = getPrimaryTextAndTransferableData() ?: return null
|
||||
return IjVimCopiedText(text, transferableData ?: emptyList())
|
||||
}
|
||||
|
||||
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#getClipboardTextAndTransferableData")
|
||||
override fun getClipboardTextAndTransferableData(): Pair<String, List<Any>?>? {
|
||||
val contents = getContents() ?: return null
|
||||
return getTextAndTransferableData(contents)
|
||||
}
|
||||
|
||||
private fun getTextAndTransferableData(trans: Transferable): Pair<String, List<Any>?>? {
|
||||
override fun getClipboardContent(editor: VimEditor, context: ExecutionContext): VimCopiedText? {
|
||||
val (text, transferableData) = getClipboardTextAndTransferableData() ?: return null
|
||||
return IjVimCopiedText(text, transferableData ?: emptyList())
|
||||
}
|
||||
|
||||
override fun setClipboardContent(editor: VimEditor, context: ExecutionContext, textData: VimCopiedText): Boolean {
|
||||
require(textData is IjVimCopiedText)
|
||||
return handleTextSetting(textData.text, textData.text, textData.transferableData) { content -> setContents(content) } != null
|
||||
}
|
||||
|
||||
// TODO prefer methods with ranges, because they collect and preprocess for us
|
||||
// override fun createClipboardEntry(
|
||||
// editor: VimEditor,
|
||||
// context: ExecutionContext,
|
||||
// text: String,
|
||||
// range: TextRange,
|
||||
// ): ClipboardEntry {
|
||||
// val transferableData = getTransferableData(editor, range, text)
|
||||
// val preprocessedText = preprocessText(editor, range, text, transferableData)
|
||||
// return IJClipboardEntry(preprocessedText, text, transferableData)
|
||||
// }
|
||||
|
||||
// override fun setClipboardText(editor: VimEditor, context: ExecutionContext, entry: ClipboardEntry): Boolean {
|
||||
// require(entry is IJClipboardEntry)
|
||||
// return setClipboardText(entry.text, entry.rawText, entry.transferableData) != null
|
||||
// }
|
||||
|
||||
private fun getTextAndTransferableData(trans: Transferable): Pair<String, List<TextBlockTransferableData>?>? {
|
||||
var res: String? = null
|
||||
var transferableData: List<TextBlockTransferableData> = ArrayList()
|
||||
try {
|
||||
@ -63,10 +100,29 @@ internal class IjClipboardManager : VimClipboardManager {
|
||||
return Pair(res, transferableData)
|
||||
}
|
||||
|
||||
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#setClipboardText")
|
||||
override fun setClipboardText(text: String, rawText: String, transferableData: List<Any>): Transferable? {
|
||||
return handleTextSetting(text, rawText, transferableData) { content -> setContents(content) }
|
||||
}
|
||||
|
||||
override fun setPrimaryContent(
|
||||
editor: VimEditor,
|
||||
context: ExecutionContext,
|
||||
textData: VimCopiedText,
|
||||
): Boolean {
|
||||
require(textData is IjVimCopiedText)
|
||||
return handleTextSetting(textData.text, textData.text, textData.transferableData) { content ->
|
||||
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return@handleTextSetting null
|
||||
clipboard.setContents(content, EmptyClipboardOwner.INSTANCE)
|
||||
} != null
|
||||
}
|
||||
|
||||
// override fun setPrimaryText(editor: VimEditor, context: ExecutionContext, entry: ClipboardEntry): Boolean {
|
||||
// require(entry is IJClipboardEntry)
|
||||
// return setPrimaryText(entry.text, entry.rawText, entry.transferableData) != null
|
||||
// }
|
||||
|
||||
@Deprecated("Please use com.maddyhome.idea.vim.api.VimClipboardManager#setPrimaryText")
|
||||
override fun setPrimaryText(text: String, rawText: String, transferableData: List<Any>): Transferable? {
|
||||
return handleTextSetting(text, rawText, transferableData) { content ->
|
||||
val clipboard = Toolkit.getDefaultToolkit()?.systemSelection ?: return@handleTextSetting null
|
||||
@ -74,6 +130,16 @@ internal class IjClipboardManager : VimClipboardManager {
|
||||
}
|
||||
}
|
||||
|
||||
override fun collectCopiedText(editor: VimEditor, context: ExecutionContext, range: TextRange, text: String): VimCopiedText {
|
||||
val transferableData = getTransferableData(editor, range)
|
||||
val preprocessedText = preprocessText(editor, range, text, transferableData)
|
||||
return IjVimCopiedText(preprocessedText, transferableData)
|
||||
}
|
||||
|
||||
override fun dumbCopiedText(text: String): VimCopiedText {
|
||||
return IjVimCopiedText(text, emptyList())
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun handleTextSetting(text: String, rawText: String, transferableData: List<Any>, setContent: (TextBlockTransferable) -> Unit?): Transferable? {
|
||||
val mutableTransferableData = (transferableData as List<TextBlockTransferableData>).toMutableList()
|
||||
@ -92,7 +158,7 @@ internal class IjClipboardManager : VimClipboardManager {
|
||||
return null
|
||||
}
|
||||
|
||||
override fun getTransferableData(vimEditor: VimEditor, textRange: TextRange, text: String): List<Any> {
|
||||
override fun getTransferableData(vimEditor: VimEditor, textRange: TextRange): List<TextBlockTransferableData> {
|
||||
val editor = (vimEditor as IjVimEditor).editor
|
||||
val transferableData: MutableList<TextBlockTransferableData> = ArrayList()
|
||||
val project = editor.project ?: return emptyList()
|
||||
@ -117,7 +183,7 @@ internal class IjClipboardManager : VimClipboardManager {
|
||||
}
|
||||
}
|
||||
}
|
||||
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
|
||||
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(textRange.endOffset - textRange.startOffset)))
|
||||
|
||||
// These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with
|
||||
// IdeaVim and I don't see a way to fix it
|
||||
@ -175,3 +241,7 @@ internal class IjClipboardManager : VimClipboardManager {
|
||||
val logger = vimLogger<IjClipboardManager>()
|
||||
}
|
||||
}
|
||||
|
||||
data class IjVimCopiedText(override val text: String, val transferableData: List<Any>): VimCopiedText {
|
||||
override fun updateText(newText: String): VimCopiedText = IjVimCopiedText(newText, transferableData)
|
||||
}
|
||||
|
@ -12,8 +12,6 @@ import com.intellij.openapi.editor.Caret
|
||||
import com.intellij.openapi.editor.LogicalPosition
|
||||
import com.intellij.openapi.editor.VisualPosition
|
||||
import com.maddyhome.idea.vim.api.BufferPosition
|
||||
import com.maddyhome.idea.vim.api.CaretRegisterStorage
|
||||
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
|
||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
|
||||
import com.maddyhome.idea.vim.api.SelectionInfo
|
||||
@ -21,6 +19,7 @@ import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimCaretBase
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimVisualPosition
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.InsertSequence
|
||||
import com.maddyhome.idea.vim.common.LiveRange
|
||||
import com.maddyhome.idea.vim.group.visual.VisualChange
|
||||
@ -29,7 +28,6 @@ import com.maddyhome.idea.vim.helper.insertHistory
|
||||
import com.maddyhome.idea.vim.helper.lastSelectionInfo
|
||||
import com.maddyhome.idea.vim.helper.markStorage
|
||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
|
||||
import com.maddyhome.idea.vim.helper.registerStorage
|
||||
import com.maddyhome.idea.vim.helper.resetVimLastColumn
|
||||
import com.maddyhome.idea.vim.helper.vimInsertStart
|
||||
import com.maddyhome.idea.vim.helper.vimLastColumn
|
||||
@ -37,22 +35,14 @@ import com.maddyhome.idea.vim.helper.vimLastVisualOperatorRange
|
||||
import com.maddyhome.idea.vim.helper.vimLine
|
||||
import com.maddyhome.idea.vim.helper.vimSelectionStart
|
||||
import com.maddyhome.idea.vim.helper.vimSelectionStartClear
|
||||
import com.maddyhome.idea.vim.register.VimRegisterGroup
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
|
||||
internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
|
||||
|
||||
override val registerStorage: CaretRegisterStorage
|
||||
get() {
|
||||
var storage = this.caret.registerStorage
|
||||
if (storage == null) {
|
||||
initInjector() // To initialize injector used in CaretRegisterStorageBase
|
||||
storage = CaretRegisterStorageBase(this)
|
||||
this.caret.registerStorage = storage
|
||||
} else if (storage.caret != this) {
|
||||
storage.caret = this
|
||||
}
|
||||
return storage
|
||||
}
|
||||
override val registerStorage: VimRegisterGroup
|
||||
get() = injector.registerGroup
|
||||
|
||||
override val markStorage: LocalMarkStorage
|
||||
get() {
|
||||
var storage = this.caret.markStorage
|
||||
|
@ -60,6 +60,8 @@ import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
|
||||
import com.maddyhome.idea.vim.state.mode.Mode
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||
import com.maddyhome.idea.vim.undo.VimKeyBasedUndoService
|
||||
import com.maddyhome.idea.vim.undo.VimTimestampBasedUndoService
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import java.lang.System.identityHashCode
|
||||
|
||||
@ -140,7 +142,13 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
||||
|
||||
override fun insertText(caret: VimCaret, atPosition: Int, text: CharSequence) {
|
||||
if (injector.vimState.mode is Mode.INSERT) {
|
||||
injector.undo.startInsertSequence(caret, atPosition, System.nanoTime())
|
||||
val undo = injector.undo
|
||||
when (undo) {
|
||||
is VimKeyBasedUndoService -> undo.setInsertNonMergeUndoKey()
|
||||
is VimTimestampBasedUndoService -> {
|
||||
undo.startInsertSequence(caret, atPosition, System.nanoTime())
|
||||
}
|
||||
}
|
||||
}
|
||||
editor.document.insertString(atPosition, text)
|
||||
}
|
||||
@ -171,21 +179,38 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor, VimEditorBase(
|
||||
return editor.caretModel.allCarets.map { IjVimCaret(it) }
|
||||
}
|
||||
|
||||
override var isFirstCaret = true
|
||||
override var isReversingCarets = false
|
||||
|
||||
@Suppress("ideavimRunForEachCaret")
|
||||
override fun forEachCaret(action: (VimCaret) -> Unit) {
|
||||
if (editor.vim.inBlockSelection) {
|
||||
action(IjVimCaret(editor.caretModel.primaryCaret))
|
||||
} else {
|
||||
try {
|
||||
editor.caretModel.runForEachCaret({
|
||||
if (it.isValid) {
|
||||
action(IjVimCaret(it))
|
||||
isFirstCaret = false
|
||||
}
|
||||
}, false)
|
||||
} finally {
|
||||
isFirstCaret = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun forEachNativeCaret(action: (VimCaret) -> Unit, reverse: Boolean) {
|
||||
editor.caretModel.runForEachCaret({ action(IjVimCaret(it)) }, reverse)
|
||||
isReversingCarets = reverse
|
||||
try {
|
||||
editor.caretModel.runForEachCaret({
|
||||
action(IjVimCaret(it))
|
||||
isFirstCaret = false
|
||||
}, reverse)
|
||||
} finally {
|
||||
isFirstCaret = true
|
||||
isReversingCarets = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInForEachCaretScope(): Boolean {
|
||||
@ -532,7 +557,7 @@ internal class InsertTimeRecorder: ModeChangeListener {
|
||||
override fun modeChanged(editor: VimEditor, oldMode: Mode) {
|
||||
editor as IjVimEditor
|
||||
if (oldMode == Mode.INSERT) {
|
||||
val undo = injector.undo
|
||||
val undo = injector.undo as? VimTimestampBasedUndoService ?: return
|
||||
val nanoTime = System.nanoTime()
|
||||
editor.forEachCaret { undo.endInsertSequence(it, it.offset, nanoTime) }
|
||||
}
|
||||
|
@ -22,7 +22,6 @@ import com.maddyhome.idea.vim.api.VimApplication
|
||||
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||
import com.maddyhome.idea.vim.api.VimClipboardManager
|
||||
import com.maddyhome.idea.vim.api.VimCommandGroup
|
||||
import com.maddyhome.idea.vim.api.VimCommandLine
|
||||
import com.maddyhome.idea.vim.api.VimCommandLineService
|
||||
import com.maddyhome.idea.vim.api.VimDigraphGroup
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
|
@ -18,11 +18,8 @@ import com.intellij.openapi.editor.event.DocumentEvent
|
||||
import com.intellij.openapi.editor.event.DocumentListener
|
||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.Options
|
||||
import com.maddyhome.idea.vim.api.VimCaret
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.VimSearchGroupBase
|
||||
import com.maddyhome.idea.vim.api.globalOptions
|
||||
@ -30,21 +27,16 @@ import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.common.Direction
|
||||
import com.maddyhome.idea.vim.common.Direction.Companion.fromInt
|
||||
import com.maddyhome.idea.vim.diagnostic.vimLogger
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
import com.maddyhome.idea.vim.helper.TestInputModel.Companion.getInstance
|
||||
import com.maddyhome.idea.vim.helper.addSubstitutionConfirmationHighlight
|
||||
import com.maddyhome.idea.vim.helper.highlightSearchResults
|
||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||
import com.maddyhome.idea.vim.helper.shouldIgnoreCase
|
||||
import com.maddyhome.idea.vim.helper.updateSearchHighlights
|
||||
import com.maddyhome.idea.vim.helper.vimLastHighlighters
|
||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
|
||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||
import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler
|
||||
import org.jdom.Element
|
||||
import org.jetbrains.annotations.Contract
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
@State(
|
||||
name = "VimSearchSettings",
|
||||
@ -52,7 +44,7 @@ import javax.swing.KeyStroke
|
||||
)
|
||||
open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Element> {
|
||||
companion object {
|
||||
private val logger = vimLogger<IjVimSearchGroup>()
|
||||
private val logger by lazy { vimLogger<IjVimSearchGroup>() }
|
||||
}
|
||||
|
||||
init {
|
||||
@ -112,27 +104,25 @@ open class IjVimSearchGroup : VimSearchGroupBase(), PersistentStateComponent<Ele
|
||||
return IjSearchHighlight(ijEditor, highlighter)
|
||||
}
|
||||
|
||||
override fun setLatestMatch(match: String) {
|
||||
SubmatchFunctionHandler.getInstance().latestMatch = match
|
||||
}
|
||||
|
||||
override fun replaceString(
|
||||
editor: VimEditor,
|
||||
startOffset: Int,
|
||||
endOffset: Int,
|
||||
newString: String,
|
||||
) {
|
||||
ApplicationManager.getApplication().runWriteAction {
|
||||
(editor as IjVimEditor).editor.document.replaceString(startOffset, endOffset, newString)
|
||||
}
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
override fun resetState() {
|
||||
super.resetState()
|
||||
showSearchHighlight = injector.globalOptions().hlsearch
|
||||
}
|
||||
|
||||
override fun isSomeTextHighlighted(): Boolean {
|
||||
val vimEditors = injector.editorGroup.getEditors().filter {
|
||||
(injector.application.isUnitTest() || it.ij.component.isShowing)
|
||||
}
|
||||
for (vimEditor in vimEditors) {
|
||||
val editor = vimEditor.ij
|
||||
if (editor.vimLastHighlighters != null) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun setShouldShowSearchHighlights() {
|
||||
showSearchHighlight = injector.globalOptions().hlsearch
|
||||
}
|
||||
|
@ -32,9 +32,9 @@ internal class Troubleshooter {
|
||||
fun findIncorrectMappings(): List<Problem> {
|
||||
val problems = ArrayList<Problem>()
|
||||
MappingMode.entries.forEach { mode ->
|
||||
injector.keyGroup.getKeyMapping(mode).getByOwner(MappingOwner.IdeaVim.InitScript).forEach { (_, to) ->
|
||||
if (to is ToKeysMappingInfo) {
|
||||
if (":action" in to.toKeys.joinToString { it.keyChar.toString() }) {
|
||||
injector.keyGroup.getKeyMapping(mode).getAllByOwner(MappingOwner.IdeaVim.InitScript).forEach { entry ->
|
||||
(entry.mappingInfo as? ToKeysMappingInfo)?.let { mappingInfo ->
|
||||
if (":action" in mappingInfo.toKeys.joinToString { it.keyChar.toString() }) {
|
||||
problems += Problem("Mappings contain `:action` call")
|
||||
}
|
||||
}
|
||||
|
@ -7,7 +7,6 @@
|
||||
*/
|
||||
package com.maddyhome.idea.vim.ui.ex
|
||||
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
|
@ -23,7 +23,10 @@ import com.maddyhome.idea.vim.EventFacade;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
|
||||
import com.maddyhome.idea.vim.api.*;
|
||||
import com.maddyhome.idea.vim.api.VimCommandLine;
|
||||
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
|
||||
import com.maddyhome.idea.vim.api.VimEditor;
|
||||
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
|
||||
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
|
||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||
@ -348,7 +351,7 @@ public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||
int count1 = Math.max(1, KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder()
|
||||
.calculateCount0Snapshot());
|
||||
|
||||
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
|
||||
if ((labelText.equals("/") || labelText.equals("?") || searchCommand) && !injector.getMacro().isExecutingMacro()) {
|
||||
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
||||
int pattenEnd = injector.getSearchGroup().findEndOfPattern(searchText, separator, 0);
|
||||
final String pattern = searchText.substring(0, pattenEnd);
|
||||
|
@ -11,6 +11,7 @@ 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.intellij.openapi.util.RecursionManager
|
||||
import com.intellij.openapi.wm.WindowManager
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.common.EditorListener
|
||||
@ -73,7 +74,14 @@ internal class ModeWidgetListener: ModeChangeListener, EditorListener, VimWidget
|
||||
|
||||
private fun getFocusedEditorForProject(editorProject: Project?): Editor? {
|
||||
if (editorProject == null) return null
|
||||
return recursionGuard.doPreventingRecursion(recursionKey, false) {
|
||||
val fileEditorManager = FileEditorManager.getInstance(editorProject)
|
||||
return fileEditorManager.selectedTextEditor
|
||||
fileEditorManager.selectedTextEditor
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val recursionGuard = RecursionManager.createGuard<Any>("IdeaVim.modeWidgetListener")
|
||||
private val recursionKey = Any()
|
||||
}
|
||||
}
|
@ -15,10 +15,8 @@ import com.maddyhome.idea.vim.api.ExecutionContext
|
||||
import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||
import com.maddyhome.idea.vim.newapi.ij
|
||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||
import java.util.*
|
||||
|
||||
@ -26,7 +24,9 @@ import java.util.*
|
||||
* @author smartbomb
|
||||
*/
|
||||
@ExCommand(command = "actionl[ist]")
|
||||
internal data class ActionListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||
internal data class ActionListCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
|
||||
Command.SingleExecution(range, modifier) {
|
||||
|
||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||
|
||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||
|
@ -27,13 +27,14 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||
* @author John Weigel
|
||||
*/
|
||||
@ExCommand(command = "b[uffer]")
|
||||
internal data class BufferCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||
internal data class BufferCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
|
||||
Command.SingleExecution(range, modifier) {
|
||||
|
||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||
|
||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||
val arg = argument.trim()
|
||||
val overrideModified = arg.startsWith('!')
|
||||
val buffer = if (overrideModified) arg.replace(Regex("^!\\s*"), "") else arg
|
||||
val overrideModified = modifier == CommandModifier.BANG
|
||||
val buffer = argument.trim()
|
||||
var result = true
|
||||
|
||||
if (buffer.isNotEmpty()) {
|
||||
|
@ -32,7 +32,9 @@ import org.jetbrains.annotations.NonNls
|
||||
* @author John Weigel
|
||||
*/
|
||||
@ExCommand(command = "ls,files,buffers")
|
||||
internal data class BufferListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||
internal data class BufferListCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
|
||||
Command.SingleExecution(range, modifier) {
|
||||
|
||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||
|
||||
companion object {
|
||||
|
@ -18,7 +18,6 @@ import com.maddyhome.idea.vim.api.VimEditor
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||
import com.maddyhome.idea.vim.ex.ExException
|
||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||
import com.maddyhome.idea.vim.ex.ranges.toTextRange
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||
@ -30,7 +29,9 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||
* see "h :!"
|
||||
*/
|
||||
@ExCommand(command = "!")
|
||||
internal data class CmdFilterCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||
internal data class CmdFilterCommand(val range: Range, val modifier: CommandModifier, val argument: String)
|
||||
: Command.SingleExecution(range, modifier) {
|
||||
|
||||
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
|
||||
|
||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||
@ -99,7 +100,7 @@ internal data class CmdFilterCommand(val range: Range, val argument: String) : C
|
||||
lastCommand = command
|
||||
ExecutionResult.Success
|
||||
}
|
||||
} catch (e: ProcessCanceledException) {
|
||||
} catch (_: ProcessCanceledException) {
|
||||
throw ExException("Command terminated")
|
||||
} catch (e: Exception) {
|
||||
throw ExException(e.message)
|
||||
|
@ -24,8 +24,11 @@ import java.net.URLEncoder
|
||||
* see "h :help"
|
||||
*/
|
||||
@ExCommand(command = "h[elp]")
|
||||
internal data class HelpCommand(val range: Range, val argument: String) : Command.SingleExecution(range, argument) {
|
||||
internal data class HelpCommand(val range: Range, val modifier: CommandModifier, val argument: String) :
|
||||
Command.SingleExecution(range, modifier, argument) {
|
||||
|
||||
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||
|
||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||
BrowserUtil.browse(helpTopicUrl(argument))
|
||||
return ExecutionResult.Success
|
||||
@ -37,7 +40,7 @@ internal data class HelpCommand(val range: Range, val argument: String) : Comman
|
||||
|
||||
return try {
|
||||
String.format("%s?docs=help&search=%s", HELP_QUERY_URL, URLEncoder.encode(topic, "UTF-8"))
|
||||
} catch (e: UnsupportedEncodingException) {
|
||||
} catch (_: UnsupportedEncodingException) {
|
||||
HELP_ROOT_URL
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@
|
||||
serviceInterface="com.maddyhome.idea.vim.api.VimCommandLineService"/>
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.IjOutputPanelService"
|
||||
serviceInterface="com.maddyhome.idea.vim.api.VimOutputPanelService"/>
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup"
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.api.VimDigraphGroupBase"
|
||||
serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/>
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/>
|
||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.KeyGroup"
|
||||
|
@ -1,12 +1,4 @@
|
||||
<!--
|
||||
~ Copyright 2003-2023 The IdeaVim authors
|
||||
~
|
||||
~ Use of this source code is governed by an MIT-style
|
||||
~ license that can be found in the LICENSE.txt file or at
|
||||
~ https://opensource.org/licenses/MIT.
|
||||
-->
|
||||
|
||||
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||
<name>IdeaVim</name>
|
||||
<id>IdeaVIM</id>
|
||||
<description><![CDATA[
|
||||
@ -21,7 +13,7 @@
|
||||
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
|
||||
</ul>
|
||||
]]></description>
|
||||
<version>SNAPSHOT</version>
|
||||
<version>chylex</version>
|
||||
<vendor>JetBrains</vendor>
|
||||
|
||||
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
|
||||
@ -103,7 +95,7 @@
|
||||
<!-- Do not care about red handlers in order. They are necessary for proper ordering, and they'll be resolved when needed -->
|
||||
<editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.VimEnterHandler"
|
||||
id="ideavim-enter"
|
||||
order="before editorEnter, before rd.client.editor.enter, after smart-step-into-enter, after AceHandlerEnter, after jupyterCommandModeEnterKeyHandler, after swift.placeholder.enter"/>
|
||||
order="before editorEnter, before inline.completion.enter, before rd.client.editor.enter, after smart-step-into-enter, after AceHandlerEnter, after jupyterCommandModeEnterKeyHandler, after swift.placeholder.enter"/>
|
||||
<editorActionHandler action="EditorEnter" implementationClass="com.maddyhome.idea.vim.handler.CaretShapeEnterEditorHandler"
|
||||
id="ideavim-enter-shape"
|
||||
order="before jupyterCommandModeEnterKeyHandler"/>
|
||||
@ -126,6 +118,8 @@
|
||||
implementationClass="com.maddyhome.idea.vim.handler.StartNewLineBeforeCurrentDetector"
|
||||
id="ideavim-start-new-line-before-current-detector"
|
||||
order="first"/>
|
||||
<editorFactoryDocumentListener
|
||||
implementation="com.maddyhome.idea.vim.listener.VimListenerManager$VimDocumentListener"/>
|
||||
</extensions>
|
||||
|
||||
<xi:include href="/META-INF/includes/ApplicationServices.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
@ -133,9 +127,11 @@
|
||||
<xi:include href="/META-INF/includes/VimListeners.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||
|
||||
<actions resource-bundle="messages.IdeaVimBundle">
|
||||
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction">
|
||||
<group id="com.chylex.intellij.vim" text="Vim" popup="true">
|
||||
<add-to-group group-id="ToolsMenu" anchor="last"/>
|
||||
</action>
|
||||
<action id="VimPluginToggle" class="com.maddyhome.idea.vim.action.VimPluginToggleAction"/>
|
||||
<action id="VimRunLastMacroInOpenFiles" class="com.maddyhome.idea.vim.action.VimRunLastMacroInOpenFiles"/>
|
||||
</group>
|
||||
|
||||
<!-- Internal -->
|
||||
<!--suppress PluginXmlI18n -->
|
||||
@ -154,5 +150,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>
|
||||
|
@ -1,6 +1,3 @@
|
||||
{
|
||||
"col": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.ColFunctionHandler",
|
||||
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler",
|
||||
"line": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.LineFunctionHandler",
|
||||
"submatch": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctionHandler"
|
||||
"has": "com.maddyhome.idea.vim.vimscript.model.functions.handlers.HasFunctionHandler"
|
||||
}
|
@ -28,6 +28,7 @@ E20=E20: Mark not set
|
||||
e_nopresub=E33: No previous substitute regular expression
|
||||
e_noprev=E34: No previous command
|
||||
e_noprevre=E35: No previous regular expression
|
||||
E39=E39: Number expected
|
||||
e_re_damg=E43: Damaged match string
|
||||
e_re_corr=E44: Currupted regexp program
|
||||
E50=E50: Too many \\z(
|
||||
@ -69,6 +70,7 @@ E385=E385: Search hit BOTTOM without match for: {0}
|
||||
E471=E471: Argument required
|
||||
E474=E474: Invalid argument: {0}
|
||||
E475=E475: Invalid argument: {0}
|
||||
E477=E477: No ! allowed
|
||||
E481=E481: No range allowed
|
||||
E486=E486: Pattern not found: {0}
|
||||
E488=E488: Trailing characters: {0}
|
||||
@ -84,6 +86,7 @@ E549=E549: Illegal percentage: {0}
|
||||
E774=E774: 'operatorfunc' is empty
|
||||
e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command
|
||||
E939=E939: Positive count required
|
||||
E1214=E1214: Digraph must be just two characters: {0}
|
||||
|
||||
message.search.hit.bottom=search hit BOTTOM, continuing at TOP
|
||||
message.search.hit.top=search hit TOP, continuing at BOTTOM
|
||||
|
@ -69,6 +69,7 @@ class ChangeActionTest : VimTestCase() {
|
||||
|
||||
// VIM-620 |i_CTRL-O|
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.UNCLEAR)
|
||||
fun testInsertSingleCommandAndNewLineInserting4() {
|
||||
doTest(
|
||||
listOf("i", "<C-O>", "v", "d"),
|
||||
@ -129,6 +130,7 @@ class ChangeActionTest : VimTestCase() {
|
||||
|
||||
// VIM-311 |i_CTRL-O|
|
||||
@Test
|
||||
@TestWithoutNeovim(SkipNeovimReason.UNCLEAR)
|
||||
fun testInsertSingleCommand() {
|
||||
doTest(
|
||||
listOf("i", "def", "<C-O>", "d2h", "x"),
|
||||
@ -1072,6 +1074,7 @@ foobaz
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["VIM-2074"])
|
||||
@TestWithoutNeovim(SkipNeovimReason.UNCLEAR)
|
||||
fun `backspace with replace mode`() {
|
||||
configureByText("${c}Hello world")
|
||||
typeText("R1111")
|
||||
|
@ -11,6 +11,7 @@ import com.intellij.idea.TestFor
|
||||
import com.maddyhome.idea.vim.KeyHandler
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
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
|
||||
@ -250,7 +251,10 @@ class CopyActionTest : VimTestCase() {
|
||||
enterCommand("set clipboard=unnamed")
|
||||
kotlin.test.assertEquals('*', VimPlugin.getRegister().defaultRegister)
|
||||
typeText("yy")
|
||||
val starRegister = VimPlugin.getRegister().getRegister('*')
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val starRegister = registerService.getRegister(vimEditor, context, '*')
|
||||
assertNotNull<Any>(starRegister)
|
||||
kotlin.test.assertEquals("bar\n", starRegister.text)
|
||||
}
|
||||
@ -262,7 +266,10 @@ class CopyActionTest : VimTestCase() {
|
||||
fun testLineWiseClipboardYankPaste() {
|
||||
configureByText("<caret>foo\n")
|
||||
typeText("\"*yy" + "\"*p")
|
||||
val register = VimPlugin.getRegister().getRegister('*')
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val register = registerService.getRegister(vimEditor, context, '*')
|
||||
assertNotNull<Any>(register)
|
||||
kotlin.test.assertEquals("foo\n", register.text)
|
||||
val editor = fixture.editor
|
||||
@ -290,7 +297,10 @@ class CopyActionTest : VimTestCase() {
|
||||
""".trimIndent(),
|
||||
)
|
||||
typeText("<C-V>j" + "\"*y" + "\"*p")
|
||||
val register = VimPlugin.getRegister().getRegister('*')
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val register = registerService.getRegister(vimEditor, context, '*')
|
||||
assertNotNull<Any>(register)
|
||||
kotlin.test.assertEquals(
|
||||
"""
|
||||
|
@ -10,10 +10,10 @@ 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.newapi.vim
|
||||
import org.jetbrains.plugins.ideavim.ExceptionHandler
|
||||
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
@ -52,15 +52,18 @@ class MacroActionTest : VimTestCase() {
|
||||
configureByText("")
|
||||
enterCommand("imap pp hello")
|
||||
typeText(injector.parser.parseKeys("qa" + "i" + "pp<Esc>" + "q"))
|
||||
assertRegister('a', "ipp<Esc>")
|
||||
assertRegister('a', "ipp^[")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRecordMacroWithDigraph() {
|
||||
typeTextInFile(injector.parser.parseKeys("qa" + "i" + "<C-K>OK<Esc>" + "q"), "")
|
||||
val register = VimPlugin.getRegister().getRegister('a')
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val register = registerService.getRegister(vimEditor, context, 'a')
|
||||
assertNotNull<Any>(register)
|
||||
assertRegister('a', "i<C-K>OK<Esc>")
|
||||
assertRegister('a', "i^KOK^[")
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -96,7 +99,10 @@ class MacroActionTest : VimTestCase() {
|
||||
configureByText(content)
|
||||
typeText(injector.parser.parseKeys("qa" + ":map x y<CR>" + "q"))
|
||||
|
||||
val register = VimPlugin.getRegister().getRegister('a')
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val register = registerService.getRegister(vimEditor, context, 'a')
|
||||
val registerSize = register!!.keys.size
|
||||
assertEquals(9, registerSize)
|
||||
}
|
||||
@ -244,8 +250,10 @@ class MacroActionTest : VimTestCase() {
|
||||
)
|
||||
injector.keyGroup.putKeyMapping(MappingMode.NXO, keys("abc"), exceptionMappingOwner, ExceptionHandler(), false)
|
||||
|
||||
injector.registerGroup.storeText('k', "abc")
|
||||
injector.registerGroup.storeText('q', "x@ky")
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, 'k', "abc")
|
||||
injector.registerGroup.storeText(vimEditor, context, 'q', "x@ky")
|
||||
|
||||
val exception = assertThrows<Throwable> {
|
||||
LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
|
||||
|
@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
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
|
||||
@ -28,8 +29,10 @@ class MacroWithEditingTest : VimTestCase() {
|
||||
|
||||
@Test
|
||||
fun `test copy and perform macro`() {
|
||||
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "iHello<Esc>")
|
||||
kotlin.test.assertEquals("iHello<Esc>", VimPlugin.getRegister().getRegister('w')?.rawText)
|
||||
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "iHello")
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
kotlin.test.assertEquals("iHello", VimPlugin.getRegister().getRegister(vimEditor, context, 'w')?.text)
|
||||
setText("")
|
||||
typeText(injector.parser.parseKeys("@w"))
|
||||
waitAndAssert {
|
||||
@ -39,8 +42,10 @@ class MacroWithEditingTest : VimTestCase() {
|
||||
|
||||
@Test
|
||||
fun `test copy and perform macro ctrl_a`() {
|
||||
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "<C-A>")
|
||||
kotlin.test.assertEquals("<C-A>", VimPlugin.getRegister().getRegister('w')?.rawText)
|
||||
typeTextInFile(injector.parser.parseKeys("^v\$h\"wy"), "\u0001")
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
kotlin.test.assertEquals(injector.parser.parseKeys("<C-A>"), injector.registerGroup.getRegister(vimEditor, context, 'w')!!.keys)
|
||||
setText("1")
|
||||
typeText(injector.parser.parseKeys("@w"))
|
||||
waitAndAssert {
|
||||
|
@ -17,6 +17,7 @@ import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||
import org.junit.jupiter.api.Disabled
|
||||
import org.junit.jupiter.api.Test
|
||||
import kotlin.test.assertNotNull
|
||||
|
||||
@ -1816,6 +1817,7 @@ $c five six se${c}ven eight
|
||||
)
|
||||
}
|
||||
|
||||
@Disabled("Action execution in tests is broken for 2024.2")
|
||||
@Test
|
||||
fun testInsertNewLineAboveAction() {
|
||||
typeTextInFile(
|
||||
@ -1842,6 +1844,7 @@ $c five six se${c}ven eight
|
||||
)
|
||||
}
|
||||
|
||||
@Disabled("Action execution in tests is broken for 2024.2")
|
||||
@VimBehaviorDiffers(originalVimAfter = "${c}\n${c}\nabcde\n${c}\n${c}\nabcde\n")
|
||||
@Test
|
||||
fun testInsertNewLineAboveActionWithMultipleCaretsInLine() {
|
||||
@ -1856,6 +1859,7 @@ $c five six se${c}ven eight
|
||||
assertState("${c}\nabcde\n${c}\nabcde\n")
|
||||
}
|
||||
|
||||
@Disabled("Action execution in tests is broken for 2024.2")
|
||||
@Test
|
||||
fun testInsertNewLineBelowAction() {
|
||||
typeTextInFile(
|
||||
@ -2161,7 +2165,9 @@ rtyfg${c}hzxc"""
|
||||
fun testPutTextBeforeCursor() {
|
||||
val before = "${c}qwe asd ${c}zxc rty ${c}fgh vbn"
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*P" + "3l" + "\"*P"))
|
||||
val after = "fghqwfg${c}he asd fghzxfg${c}hc rty fghfgfg${c}hh vbn"
|
||||
assertState(after)
|
||||
@ -2171,9 +2177,11 @@ rtyfg${c}hzxc"""
|
||||
fun testPutTextBeforeCursorOverlapRange() {
|
||||
val before = "${c}q${c}we asd zxc rty ${c}fgh vbn"
|
||||
val editor = configureByText(before)
|
||||
injector.registerGroup.storeText('*', "fgh")
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "fgh")
|
||||
VimPlugin.getRegister()
|
||||
.storeText(IjVimEditor(editor), editor.vim.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
|
||||
.storeText(IjVimEditor(editor), context, editor.vim.primaryCaret(), TextRange(16, 19), SelectionType.CHARACTER_WISE, false)
|
||||
typeText(injector.parser.parseKeys("\"*P"))
|
||||
val after = "fg${c}hqfg${c}hwe asd zxc rty fg${c}hfgh vbn"
|
||||
assertState(after)
|
||||
@ -2183,7 +2191,9 @@ rtyfg${c}hzxc"""
|
||||
fun testPutTextAfterCursor() {
|
||||
val before = "${c}qwe asd ${c}zxc rty ${c}fgh vbn"
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*p" + "3l" + "2\"*p"))
|
||||
val after = "qfghwe fghfg${c}hasd zfghxc fghfg${c}hrty ffghgh fghfg${c}hvbn"
|
||||
assertState(after)
|
||||
@ -2193,7 +2203,9 @@ rtyfg${c}hzxc"""
|
||||
fun testPutTextAfterCursorOverlapRange() {
|
||||
val before = "${c}q${c}we asd zxc rty ${c}fgh vbn"
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
typeText(injector.parser.parseKeys("2\"*p"))
|
||||
val after = "qfghfg${c}hwfghfg${c}he asd zxc rty ffghfg${c}hgh vbn"
|
||||
assertState(after)
|
||||
@ -2208,7 +2220,9 @@ rtyfg${c}hzxc"""
|
||||
|
||||
""".trimIndent()
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "zxcvbn\n", SelectionType.LINE_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn\n", SelectionType.LINE_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*P"))
|
||||
val after = """
|
||||
${c}zxcvbn
|
||||
@ -2342,7 +2356,9 @@ rtyfg${c}hzxc"""
|
||||
|
||||
private fun testPutOverlapLine(before: String, after: String, beforeCursor: Boolean) {
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "zxcvbn\n", SelectionType.LINE_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn\n", SelectionType.LINE_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*" + if (beforeCursor) "P" else "p"))
|
||||
assertState(after)
|
||||
}
|
||||
@ -2356,7 +2372,9 @@ rtyfg${c}hzxc"""
|
||||
|
||||
""".trimIndent()
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "zxcvbn", SelectionType.LINE_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn", SelectionType.LINE_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*p"))
|
||||
val after = """
|
||||
qwerty
|
||||
@ -2374,7 +2392,9 @@ rtyfg${c}hzxc"""
|
||||
fun testPutTextBeforeCursorMoveCursor() {
|
||||
val before = "qw${c}e asd z${c}xc rty ${c}fgh vbn"
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
typeText(injector.parser.parseKeys("l" + "\"*gP" + "b" + "\"*gP"))
|
||||
val after = "fgh${c}qwefgh asd fgh${c}zxfghc rty fgh${c}ffghgh vbn"
|
||||
assertState(after)
|
||||
@ -2384,7 +2404,9 @@ rtyfg${c}hzxc"""
|
||||
fun testPutTextAfterCursorMoveCursor() {
|
||||
val before = "qw${c}e asd z${c}xc rty ${c}fgh vbn"
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "fgh", SelectionType.CHARACTER_WISE)
|
||||
typeText(injector.parser.parseKeys("l" + "\"*gp" + "b" + "\"*gp"))
|
||||
val after = "qwe ffgh${c}ghasd zfgh${c}xcfgh rty ffgh${c}gfghh vbn"
|
||||
assertState(after)
|
||||
@ -2399,7 +2421,9 @@ rtyfg${c}hzxc"""
|
||||
|
||||
""".trimIndent()
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "zxcvbn\n", SelectionType.LINE_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn\n", SelectionType.LINE_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*gP"))
|
||||
val after = """
|
||||
zxcvbn
|
||||
@ -2422,7 +2446,9 @@ rtyfg${c}hzxc"""
|
||||
|
||||
""".trimIndent()
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "zxcvbn", SelectionType.LINE_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "zxcvbn", SelectionType.LINE_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*gp"))
|
||||
val after = """
|
||||
qwerty
|
||||
@ -2442,7 +2468,9 @@ rtyfg${c}hzxc"""
|
||||
* two
|
||||
"""
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', " *\n *\n", SelectionType.BLOCK_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', " *\n *\n", SelectionType.BLOCK_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*p"))
|
||||
val after = """ * $c *one$c *
|
||||
* *two *
|
||||
@ -2456,7 +2484,9 @@ rtyfg${c}hzxc"""
|
||||
* two
|
||||
"""
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', " *\n \n", SelectionType.BLOCK_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', " *\n \n", SelectionType.BLOCK_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*P"))
|
||||
val after = """ *$c * on$c *e
|
||||
* tw o
|
||||
@ -2475,7 +2505,9 @@ rtyfg${c}hzxc"""
|
||||
vb${c}n
|
||||
""".trimIndent()
|
||||
configureByText(before)
|
||||
injector.registerGroup.storeText('*', "qwe\n", SelectionType.LINE_WISE)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
injector.registerGroup.storeText(vimEditor, context, '*', "qwe\n", SelectionType.LINE_WISE)
|
||||
typeText(injector.parser.parseKeys("\"*p"))
|
||||
val after = """
|
||||
qwe
|
||||
@ -2497,7 +2529,10 @@ rtyfg${c}hzxc"""
|
||||
val before = "qwe ${c}asd ${c}zxc"
|
||||
configureByText(before)
|
||||
typeText(injector.parser.parseKeys("ye"))
|
||||
val lastRegister = VimPlugin.getRegister().lastRegister
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val lastRegister = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)
|
||||
assertNotNull<Any>(lastRegister)
|
||||
val text = lastRegister.text
|
||||
assertNotNull<Any>(text)
|
||||
@ -2519,7 +2554,10 @@ rtyfg${c}hzxc"""
|
||||
""".trimIndent()
|
||||
configureByText(before)
|
||||
typeText(injector.parser.parseKeys("yj"))
|
||||
val lastRegister = VimPlugin.getRegister().lastRegister
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val lastRegister = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)
|
||||
assertNotNull<Any>(lastRegister)
|
||||
val text = lastRegister.text
|
||||
assertNotNull<Any>(text)
|
||||
@ -2553,7 +2591,10 @@ rtyfg${c}hzxc"""
|
||||
""".trimIndent()
|
||||
configureByText(before)
|
||||
typeText(injector.parser.parseKeys("2yy"))
|
||||
val lastRegister = VimPlugin.getRegister().lastRegister
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val lastRegister = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)
|
||||
assertNotNull<Any>(lastRegister)
|
||||
val text = lastRegister.text
|
||||
assertNotNull<Any>(text)
|
||||
|
@ -9,6 +9,7 @@ package org.jetbrains.plugins.ideavim.action
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
|
||||
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER
|
||||
import com.maddyhome.idea.vim.register.RegisterConstants.SMALL_DELETION_REGISTER
|
||||
@ -210,7 +211,9 @@ class SpecialRegistersTest : VimTestCase() {
|
||||
|
||||
private fun getRegisterText(registerName: Char): String? {
|
||||
val registerGroup = VimPlugin.getRegister()
|
||||
val register = registerGroup.getRegister(registerName)
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val register = registerGroup.getRegister(vimEditor, context, registerName)
|
||||
assertNotNull<Any>(register)
|
||||
return register!!.text
|
||||
}
|
||||
|
@ -162,6 +162,16 @@ class UndoActionTest : VimTestCase() {
|
||||
configureByText("Lorem ${c}ipsum dolor sit amet")
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestFor(issues = ["VIM-3671"])
|
||||
fun `test undo scrolls caret to reset scrolloff`() {
|
||||
configureByLines(200, "lorem ipsum dolor sit amet")
|
||||
enterCommand("set scrolloff=10")
|
||||
typeText("50G", "dd", "G", "u")
|
||||
assertPosition(49, 0)
|
||||
assertVisibleArea(39, 73)
|
||||
}
|
||||
|
||||
private fun hasSelection(): Boolean {
|
||||
val editor = fixture.editor
|
||||
return editor.caretModel.primaryCaret.hasSelection()
|
||||
|
@ -194,4 +194,16 @@ Mode.INSERT,
|
||||
fun testLastSymbolInWord() {
|
||||
doTest("cw", "fo${c}o", "fo${c}", Mode.INSERT)
|
||||
}
|
||||
|
||||
// VIM-3729
|
||||
@Test
|
||||
fun `test change with count applies only to motion when repeated`() {
|
||||
doTest(listOf("2c3l", "foo<Esc>", "w", "."),
|
||||
"""
|
||||
banana banana
|
||||
""".trimIndent(),
|
||||
"""
|
||||
foo foo
|
||||
""".trimIndent())
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,8 @@
|
||||
|
||||
package org.jetbrains.plugins.ideavim.action.change.delete
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin
|
||||
import com.maddyhome.idea.vim.api.injector
|
||||
import com.maddyhome.idea.vim.newapi.vim
|
||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
|
||||
@ -102,7 +103,10 @@ class DeleteMotionActionTest : VimTestCase() {
|
||||
expression${c} two
|
||||
""".trimIndent(),
|
||||
)
|
||||
val savedText = VimPlugin.getRegister().lastRegister?.text ?: ""
|
||||
val vimEditor = fixture.editor.vim
|
||||
val context = injector.executionContextManager.getEditorExecutionContext(vimEditor)
|
||||
val registerService = injector.registerGroup
|
||||
val savedText = registerService.getRegister(vimEditor, context, registerService.lastRegisterChar)?.text ?: ""
|
||||
kotlin.test.assertEquals(" expression two\n", savedText)
|
||||
}
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user