mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-24 22:42:53 +01:00
Compare commits
179 Commits
12575c3cb9
...
57ddf2083e
Author | SHA1 | Date | |
---|---|---|---|
57ddf2083e | |||
aad2287433 | |||
a348428422 | |||
c4c66c194a | |||
e065783486 | |||
dae5e3a8fd | |||
bb3b67f611 | |||
bf69c8b4a6 | |||
49d1d2d270 | |||
96cdf1c26d | |||
365bbce9a0 | |||
1f8a580b7f | |||
fa9529eaa5 | |||
0b29a4704b | |||
92b9046d6c | |||
f5b120ac01 | |||
70ea63c0ba | |||
|
d00bd8bb25 | ||
|
a66e44d835 | ||
|
ce05317634 | ||
|
33d88d55c9 | ||
|
a31a4a8ca7 | ||
|
c34d000b91 | ||
|
a2bfe950fa | ||
|
b44f40acd6 | ||
|
5169093bbf | ||
|
b15b1cd3f8 | ||
|
f7b6a97435 | ||
|
f64c99c406 | ||
|
f552e43c5b | ||
|
4798198e41 | ||
|
a59de4ce05 | ||
|
2dea525665 | ||
|
8ecb1f7296 | ||
|
9ca9530061 | ||
|
54e27afc3f | ||
|
5e67032655 | ||
|
cb37f6df63 | ||
|
ca3a18cf37 | ||
|
cac36627aa | ||
|
6404e1127e | ||
|
e762a3093b | ||
|
739ac2ae5e | ||
|
77c364a2d0 | ||
|
103cd9f5ce | ||
|
99859fe857 | ||
|
4778995f3b | ||
|
0a14150840 | ||
|
e8ffc0313f | ||
|
7dbd3886b1 | ||
|
8c83164d76 | ||
|
076aab1ccf | ||
|
751f51c88f | ||
|
ab7359ffd3 | ||
|
d795d70041 | ||
|
1320034e7e | ||
|
79a216043f | ||
|
c508a3cc32 | ||
|
d40d8f34c5 | ||
|
b561a13e7c | ||
|
71bcee2a07 | ||
|
6c490b1baa | ||
|
4ab6a36aca | ||
|
c4355841ad | ||
|
f163946e38 | ||
|
7abb1fd630 | ||
|
f78d3387a5 | ||
|
69e2e3c047 | ||
|
52832cbe22 | ||
|
82ca2be51c | ||
|
8552bb8a17 | ||
|
b49a38b6d0 | ||
|
ad0751677a | ||
|
dd8bb20ba9 | ||
|
03f5f27901 | ||
|
9e3ca56afd | ||
|
5379528e3e | ||
|
12201359bc | ||
|
ba50fcf85e | ||
|
24c0db563c | ||
|
33c4905dcb | ||
|
9296c3f9a0 | ||
|
7ecc566169 | ||
|
a918da36d3 | ||
|
91ff5fdb02 | ||
|
4481631547 | ||
|
7865388086 | ||
|
26297bc5a9 | ||
|
9db5cdd8e3 | ||
|
2b17534a08 | ||
|
4e2db68acf | ||
|
ddabf8df5e | ||
|
3f28e197ca | ||
|
3a67524e8a | ||
|
33312d95b0 | ||
|
f554b21cd9 | ||
|
d9fa4e4648 | ||
|
e98a284d40 | ||
|
38ef8c1053 | ||
|
d3f560a31c | ||
|
e2cb3ff284 | ||
|
0f5a3953e1 | ||
|
633667ed7f | ||
|
656e975562 | ||
|
f7fbe89de4 | ||
|
509829b052 | ||
|
efd61e17f2 | ||
|
cf2b021d02 | ||
|
67f10aece5 | ||
|
9b5c777fcf | ||
|
c65e5f8341 | ||
|
053bb603cd | ||
|
93b07bbb90 | ||
|
764a115601 | ||
|
1adfe53f29 | ||
|
bfe0f51cb1 | ||
|
ffce61906a | ||
|
3843a193cb | ||
|
b2410dab0b | ||
|
f382544101 | ||
|
a81d54eb90 | ||
|
23519bbdae | ||
|
73fd25773a | ||
|
746d483179 | ||
|
1a7e90c7d7 | ||
|
8e2fd44f55 | ||
|
1c84917b29 | ||
|
c5522ed19d | ||
|
33a5480456 | ||
|
16cd05fc76 | ||
|
4788dacc86 | ||
|
91e54c8b0d | ||
|
15ccebfe11 | ||
|
5c849c9105 | ||
|
9e99506223 | ||
|
53ea90379f | ||
|
2619d7ebb4 | ||
|
8c8a7aceca | ||
|
726b885b60 | ||
|
a6994a09c3 | ||
|
1616aff5a1 | ||
|
5a82f05be8 | ||
|
3020504cdc | ||
|
d00e802674 | ||
|
c46008dddc | ||
|
755000c376 | ||
|
1e4a60bfe6 | ||
|
c9ee685956 | ||
|
c2b1083836 | ||
|
3518528f22 | ||
|
9bed8fec79 | ||
|
3ffbea7d42 | ||
|
39b42193cb | ||
|
c69e080b05 | ||
|
50b42036d1 | ||
|
340c40ceff | ||
|
6aeffe71de | ||
|
04c33d719e | ||
|
07f1d1e8e6 | ||
|
6c61254c50 | ||
|
c6efea8c34 | ||
|
80f43a7c66 | ||
|
9f5d3a9a28 | ||
|
8800a4f70b | ||
|
990254dcac | ||
|
73a1118e78 | ||
|
d3bedf26cf | ||
|
25b29f429a | ||
|
363f821962 | ||
|
8d873c0bf1 | ||
|
955676ed9e | ||
|
a2439a37e4 | ||
|
7b6daa8e4e | ||
|
fcf782296a | ||
|
44889b635c | ||
|
bc12ba11cd | ||
|
9b08db75e3 | ||
4c3ad3f263 | |||
|
598bcc7edd |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
@ -11,6 +11,6 @@
|
|||||||
</option>
|
</option>
|
||||||
</component>
|
</component>
|
||||||
<component name="VcsDirectoryMappings">
|
<component name="VcsDirectoryMappings">
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
<mapping directory="" vcs="Git" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
16
.teamcity/_Self/Constants.kt
vendored
16
.teamcity/_Self/Constants.kt
vendored
@ -5,13 +5,13 @@ object Constants {
|
|||||||
const val EAP_CHANNEL = "eap"
|
const val EAP_CHANNEL = "eap"
|
||||||
const val DEV_CHANNEL = "Dev"
|
const val DEV_CHANNEL = "Dev"
|
||||||
|
|
||||||
const val GITHUB_TESTS = "2023.3.2"
|
const val GITHUB_TESTS = "2024.1.1"
|
||||||
const val NVIM_TESTS = "2023.3.2"
|
const val NVIM_TESTS = "2024.1.1"
|
||||||
const val PROPERTY_TESTS = "2023.3.2"
|
const val PROPERTY_TESTS = "2024.1.1"
|
||||||
const val LONG_RUNNING_TESTS = "2023.3.2"
|
const val LONG_RUNNING_TESTS = "2024.1.1"
|
||||||
const val QODANA_TESTS = "2023.3.2"
|
const val QODANA_TESTS = "2024.1.1"
|
||||||
const val RELEASE = "2023.3.2"
|
const val RELEASE = "2024.1.1"
|
||||||
|
|
||||||
const val RELEASE_DEV = "2023.3.2"
|
const val RELEASE_DEV = "2024.1.1"
|
||||||
const val RELEASE_EAP = "2023.3.2"
|
const val RELEASE_EAP = "2024.1.1"
|
||||||
}
|
}
|
||||||
|
5
.teamcity/_Self/Project.kt
vendored
5
.teamcity/_Self/Project.kt
vendored
@ -11,6 +11,7 @@ import _Self.subprojects.GitHub
|
|||||||
import _Self.subprojects.OldTests
|
import _Self.subprojects.OldTests
|
||||||
import _Self.subprojects.Releases
|
import _Self.subprojects.Releases
|
||||||
import _Self.vcsRoots.GitHubPullRequest
|
import _Self.vcsRoots.GitHubPullRequest
|
||||||
|
import _Self.vcsRoots.ReleasesVcsRoot
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
|
import jetbrains.buildServer.configs.kotlin.v2019_2.BuildType
|
||||||
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
|
||||||
|
|
||||||
@ -21,11 +22,11 @@ object Project : Project({
|
|||||||
|
|
||||||
// VCS roots
|
// VCS roots
|
||||||
vcsRoot(GitHubPullRequest)
|
vcsRoot(GitHubPullRequest)
|
||||||
|
vcsRoot(ReleasesVcsRoot)
|
||||||
|
|
||||||
// Active tests
|
// Active tests
|
||||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
|
buildType(TestingBuildType("2024.1.1", "<default>"))
|
||||||
buildType(TestingBuildType("2024.1", "<default>"))
|
|
||||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
|
|
||||||
buildType(PropertyBased)
|
buildType(PropertyBased)
|
||||||
|
72
.teamcity/_Self/buildTypes/CreateNewReleaseBranchFromMaster.kt
vendored
Normal file
72
.teamcity/_Self/buildTypes/CreateNewReleaseBranchFromMaster.kt
vendored
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package _Self.buildTypes
|
||||||
|
|
||||||
|
import _Self.IdeaVimBuildType
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.DslContext
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
|
||||||
|
|
||||||
|
object CreateNewReleaseBranchFromMaster : IdeaVimBuildType({
|
||||||
|
name = "EXP: Create new release branch from master"
|
||||||
|
|
||||||
|
vcs {
|
||||||
|
root(DslContext.settingsRoot)
|
||||||
|
branchFilter = "+:<default>"
|
||||||
|
|
||||||
|
checkoutMode = CheckoutMode.AUTO
|
||||||
|
}
|
||||||
|
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
name = "Calculate next potential release version"
|
||||||
|
scriptContent = """
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Fetch all remote branches
|
||||||
|
git fetch --all
|
||||||
|
|
||||||
|
# Get a list of all branches matching the pattern releases/x.y.z
|
||||||
|
branches=${'$'}(git branch -r | grep -oE 'releases/[0-9]+\.[0-9]+\.x')
|
||||||
|
|
||||||
|
# If no matching branches are found, print a message and exit
|
||||||
|
if [[ -z "${'$'}branches" ]]; then
|
||||||
|
echo "No release branches found"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Find the largest release version
|
||||||
|
largest_release=${'$'}(echo "${'$'}branches" | sort -V | tail -n 1)
|
||||||
|
|
||||||
|
# Print the largest release
|
||||||
|
echo "Largest release branch: ${'$'}largest_release"
|
||||||
|
echo "##teamcity[setParameter name='env.POTENTIAL_VERSION' value='${'$'}largest_release']"
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
|
||||||
|
script {
|
||||||
|
name = "Show potential release version"
|
||||||
|
scriptContent = """
|
||||||
|
#!/bin/bash
|
||||||
|
echo "Calculated or user-provided parameter value is: %env.POTENTIAL_VERSION%"
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
params {
|
||||||
|
param("env.POTENTIAL_VERSION", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
features {
|
||||||
|
sshAgent {
|
||||||
|
teamcitySshKey = "IdeaVim ssh keys"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
42
.teamcity/_Self/buildTypes/PrintReleaseBranch.kt
vendored
Normal file
42
.teamcity/_Self/buildTypes/PrintReleaseBranch.kt
vendored
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package _Self.buildTypes
|
||||||
|
|
||||||
|
import _Self.IdeaVimBuildType
|
||||||
|
import _Self.vcsRoots.ReleasesVcsRoot
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
|
||||||
|
|
||||||
|
object PrintReleaseBranch : IdeaVimBuildType({
|
||||||
|
name = "EXP: Print release branch"
|
||||||
|
|
||||||
|
vcs {
|
||||||
|
root(ReleasesVcsRoot)
|
||||||
|
branchFilter = "+:heads/releases/*"
|
||||||
|
|
||||||
|
checkoutMode = CheckoutMode.AUTO
|
||||||
|
}
|
||||||
|
|
||||||
|
steps {
|
||||||
|
|
||||||
|
script {
|
||||||
|
name = "Print current branch"
|
||||||
|
scriptContent = """
|
||||||
|
echo "Current branch is: %teamcity.build.branch%"
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
features {
|
||||||
|
sshAgent {
|
||||||
|
teamcitySshKey = "IdeaVim ssh keys"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
@ -26,7 +26,7 @@ object PublishVimEngine : IdeaVimBuildType({
|
|||||||
|
|
||||||
vcs {
|
vcs {
|
||||||
root(DslContext.settingsRoot)
|
root(DslContext.settingsRoot)
|
||||||
branchFilter = "+:<default>"
|
branchFilter = "+:fleet"
|
||||||
|
|
||||||
checkoutMode = CheckoutMode.AUTO
|
checkoutMode = CheckoutMode.AUTO
|
||||||
}
|
}
|
||||||
|
106
.teamcity/_Self/buildTypes/ReleaseEapFromBranch.kt
vendored
Normal file
106
.teamcity/_Self/buildTypes/ReleaseEapFromBranch.kt
vendored
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
package _Self.buildTypes
|
||||||
|
|
||||||
|
import _Self.Constants.EAP_CHANNEL
|
||||||
|
import _Self.Constants.RELEASE_EAP
|
||||||
|
import _Self.IdeaVimBuildType
|
||||||
|
import _Self.vcsRoots.ReleasesVcsRoot
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.CheckoutMode
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.ParameterDisplay
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildFeatures.sshAgent
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.script
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.BuildFailureOnMetric
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.failureConditions.failOnMetricChange
|
||||||
|
|
||||||
|
object ReleaseEapFromBranch : IdeaVimBuildType({
|
||||||
|
name = "EXP: Publish EAP Build from branch"
|
||||||
|
description = "Build and publish EAP of IdeaVim plugin"
|
||||||
|
|
||||||
|
artifactRules = "build/distributions/*"
|
||||||
|
|
||||||
|
params {
|
||||||
|
param("env.ORG_GRADLE_PROJECT_ideaVersion", RELEASE_EAP)
|
||||||
|
password(
|
||||||
|
"env.ORG_GRADLE_PROJECT_publishToken",
|
||||||
|
"credentialsJSON:61a36031-4da1-4226-a876-b8148bf32bde",
|
||||||
|
label = "Password"
|
||||||
|
)
|
||||||
|
param("env.ORG_GRADLE_PROJECT_publishChannels", EAP_CHANNEL)
|
||||||
|
password(
|
||||||
|
"env.ORG_GRADLE_PROJECT_slackUrl",
|
||||||
|
"credentialsJSON:a8ab8150-e6f8-4eaf-987c-bcd65eac50b5",
|
||||||
|
label = "Slack Token"
|
||||||
|
)
|
||||||
|
password(
|
||||||
|
"env.YOUTRACK_TOKEN",
|
||||||
|
"credentialsJSON:2479995b-7b60-4fbb-b095-f0bafae7f622",
|
||||||
|
display = ParameterDisplay.HIDDEN
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
vcs {
|
||||||
|
root(ReleasesVcsRoot)
|
||||||
|
branchFilter = """
|
||||||
|
+:heads/(releases/*)
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
|
checkoutMode = CheckoutMode.AUTO
|
||||||
|
}
|
||||||
|
|
||||||
|
steps {
|
||||||
|
script {
|
||||||
|
name = "Pull git tags"
|
||||||
|
scriptContent = "git fetch --tags origin"
|
||||||
|
}
|
||||||
|
script {
|
||||||
|
name = "Pull git history"
|
||||||
|
scriptContent = "git fetch --unshallow"
|
||||||
|
}
|
||||||
|
gradle {
|
||||||
|
name = "Calculate new eap version from branch"
|
||||||
|
tasks = "scripts:calculateNewEapVersionFromBranch"
|
||||||
|
}
|
||||||
|
gradle {
|
||||||
|
name = "Set TeamCity build number"
|
||||||
|
tasks = "scripts:setTeamCityBuildNumber"
|
||||||
|
}
|
||||||
|
gradle {
|
||||||
|
name = "Add release tag"
|
||||||
|
tasks = "scripts:addReleaseTag"
|
||||||
|
}
|
||||||
|
gradle {
|
||||||
|
name = "Publish plugin"
|
||||||
|
tasks = "publishPlugin"
|
||||||
|
}
|
||||||
|
script {
|
||||||
|
name = "Push changes to the repo"
|
||||||
|
scriptContent = """
|
||||||
|
branch=$(git branch --show-current)
|
||||||
|
echo current branch is ${'$'}branch
|
||||||
|
git push origin %build.number%
|
||||||
|
""".trimIndent()
|
||||||
|
}
|
||||||
|
gradle {
|
||||||
|
name = "YouTrack post release actions"
|
||||||
|
tasks = "scripts:eapReleaseActions"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
features {
|
||||||
|
sshAgent {
|
||||||
|
teamcitySshKey = "IdeaVim ssh keys"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
failureConditions {
|
||||||
|
failOnMetricChange {
|
||||||
|
metric = BuildFailureOnMetric.MetricType.ARTIFACT_SIZE
|
||||||
|
threshold = 5
|
||||||
|
units = BuildFailureOnMetric.MetricUnit.PERCENTS
|
||||||
|
comparison = BuildFailureOnMetric.MetricComparison.DIFF
|
||||||
|
compareTo = build {
|
||||||
|
buildRule = lastSuccessful()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
8
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
8
.teamcity/_Self/buildTypes/ReleasePlugin.kt
vendored
@ -147,10 +147,10 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
|
|||||||
name = "Run Integrations"
|
name = "Run Integrations"
|
||||||
tasks = "releaseActions"
|
tasks = "releaseActions"
|
||||||
}
|
}
|
||||||
gradle {
|
// gradle {
|
||||||
name = "Slack Notification"
|
// name = "Slack Notification"
|
||||||
tasks = "slackNotification"
|
// tasks = "slackNotification"
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
features {
|
features {
|
||||||
|
4
.teamcity/_Self/subprojects/GitHub.kt
vendored
4
.teamcity/_Self/subprojects/GitHub.kt
vendored
@ -16,10 +16,10 @@ object GitHub : Project({
|
|||||||
name = "Pull Requests checks"
|
name = "Pull Requests checks"
|
||||||
description = "Automatic checking of GitHub Pull Requests"
|
description = "Automatic checking of GitHub Pull Requests"
|
||||||
|
|
||||||
buildType(Github("clean test", "Tests"))
|
buildType(GithubBuildType("clean test", "Tests"))
|
||||||
})
|
})
|
||||||
|
|
||||||
class Github(command: String, desc: String) : IdeaVimBuildType({
|
class GithubBuildType(command: String, desc: String) : IdeaVimBuildType({
|
||||||
name = "GitHub Pull Requests $desc"
|
name = "GitHub Pull Requests $desc"
|
||||||
description = "Test GitHub pull requests $desc"
|
description = "Test GitHub pull requests $desc"
|
||||||
|
|
||||||
|
7
.teamcity/_Self/subprojects/Releases.kt
vendored
7
.teamcity/_Self/subprojects/Releases.kt
vendored
@ -1,8 +1,11 @@
|
|||||||
package _Self.subprojects
|
package _Self.subprojects
|
||||||
|
|
||||||
|
import _Self.buildTypes.CreateNewReleaseBranchFromMaster
|
||||||
|
import _Self.buildTypes.PrintReleaseBranch
|
||||||
import _Self.buildTypes.PublishVimEngine
|
import _Self.buildTypes.PublishVimEngine
|
||||||
import _Self.buildTypes.ReleaseDev
|
import _Self.buildTypes.ReleaseDev
|
||||||
import _Self.buildTypes.ReleaseEap
|
import _Self.buildTypes.ReleaseEap
|
||||||
|
import _Self.buildTypes.ReleaseEapFromBranch
|
||||||
import _Self.buildTypes.ReleaseMajor
|
import _Self.buildTypes.ReleaseMajor
|
||||||
import _Self.buildTypes.ReleaseMinor
|
import _Self.buildTypes.ReleaseMinor
|
||||||
import _Self.buildTypes.ReleasePatch
|
import _Self.buildTypes.ReleasePatch
|
||||||
@ -38,4 +41,8 @@ object Releases : Project({
|
|||||||
buildType(ReleaseEap)
|
buildType(ReleaseEap)
|
||||||
buildType(ReleaseDev)
|
buildType(ReleaseDev)
|
||||||
buildType(PublishVimEngine)
|
buildType(PublishVimEngine)
|
||||||
|
|
||||||
|
buildType(CreateNewReleaseBranchFromMaster)
|
||||||
|
buildType(PrintReleaseBranch)
|
||||||
|
buildType(ReleaseEapFromBranch)
|
||||||
})
|
})
|
||||||
|
13
.teamcity/_Self/vcsRoots/Releases.kt
vendored
Normal file
13
.teamcity/_Self/vcsRoots/Releases.kt
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
package _Self.vcsRoots
|
||||||
|
|
||||||
|
import jetbrains.buildServer.configs.kotlin.v2019_2.vcs.GitVcsRoot
|
||||||
|
|
||||||
|
object ReleasesVcsRoot : GitVcsRoot({
|
||||||
|
name = "IdeaVim Releases"
|
||||||
|
url = "git@github.com:JetBrains/ideavim.git"
|
||||||
|
branch = "refs/heads/master"
|
||||||
|
branchSpec = "+:refs/(*)"
|
||||||
|
authMethod = uploadedKey {
|
||||||
|
uploadedKey = "IdeaVim ssh keys"
|
||||||
|
}
|
||||||
|
})
|
@ -5,15 +5,15 @@ import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
This patch script was generated by TeamCity on settings change in UI.
|
This patch script was generated by TeamCity on settings change in UI.
|
||||||
To apply the patch, change the buildType with id = 'PublishVimEngine'
|
To apply the patch, change the buildType with id = 'ReleaseEapFromBranch'
|
||||||
accordingly, and delete the patch script.
|
accordingly, and delete the patch script.
|
||||||
*/
|
*/
|
||||||
changeBuildType(RelativeId("PublishVimEngine")) {
|
changeBuildType(RelativeId("ReleaseEapFromBranch")) {
|
||||||
vcs {
|
vcs {
|
||||||
|
|
||||||
check(branchFilter == "+:<default>") {
|
check(branchFilter == "+:heads/(releases/*)") {
|
||||||
"Unexpected option value: branchFilter = $branchFilter"
|
"Unexpected option value: branchFilter = $branchFilter"
|
||||||
}
|
}
|
||||||
branchFilter = "+:fleet"
|
branchFilter = "heads/releases/*"
|
||||||
}
|
}
|
||||||
}
|
}
|
2
.teamcity/settings.kts
vendored
2
.teamcity/settings.kts
vendored
@ -30,5 +30,5 @@ node (Plugins -> teamcity-configs -> teamcity-configs:generate),
|
|||||||
the 'Debug' option is available in the context menu for the task.
|
the 'Debug' option is available in the context menu for the task.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
version = "2023.11"
|
version = "2024.03"
|
||||||
project(_Self.Project)
|
project(_Self.Project)
|
||||||
|
@ -503,6 +503,14 @@ Contributors:
|
|||||||
[![icon][github]](https://github.com/Parker7123)
|
[![icon][github]](https://github.com/Parker7123)
|
||||||
|
|
||||||
FilipParker
|
FilipParker
|
||||||
|
* [![icon][mail]](mailto:7138209+duhaesbaert@users.noreply.github.com)
|
||||||
|
[![icon][github]](https://github.com/duhaesbaert)
|
||||||
|
|
||||||
|
Eduardo Haesbaert
|
||||||
|
* [![icon][mail]](mailto:nikolaevsky.egor@gmail.com)
|
||||||
|
[![icon][github]](https://github.com/Aisper)
|
||||||
|
|
||||||
|
Egor Nikolaevsky
|
||||||
|
|
||||||
Previous contributors:
|
Previous contributors:
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20")
|
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.24-1.0.20")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
|
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
|
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
|
||||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||||
|
@ -51,11 +51,11 @@ buildscript {
|
|||||||
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
|
classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
|
||||||
classpath("org.kohsuke:github-api:1.305")
|
classpath("org.kohsuke:github-api:1.305")
|
||||||
|
|
||||||
classpath("io.ktor:ktor-client-core:2.3.10")
|
classpath("io.ktor:ktor-client-core:2.3.11")
|
||||||
classpath("io.ktor:ktor-client-cio:2.3.10")
|
classpath("io.ktor:ktor-client-cio:2.3.10")
|
||||||
classpath("io.ktor:ktor-client-auth:2.3.10")
|
classpath("io.ktor:ktor-client-auth:2.3.11")
|
||||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
|
classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
|
||||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
|
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
|
||||||
|
|
||||||
// This comes from the changelog plugin
|
// This comes from the changelog plugin
|
||||||
// classpath("org.jetbrains:markdown:0.3.1")
|
// classpath("org.jetbrains:markdown:0.3.1")
|
||||||
@ -310,7 +310,7 @@ tasks {
|
|||||||
|
|
||||||
patchPluginXml {
|
patchPluginXml {
|
||||||
// Don't forget to update plugin.xml
|
// Don't forget to update plugin.xml
|
||||||
sinceBuild.set("233.11799.67")
|
sinceBuild.set("241.15989.150")
|
||||||
|
|
||||||
changeNotes.set(
|
changeNotes.set(
|
||||||
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
|
"""<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
|
||||||
|
@ -27,7 +27,7 @@ Plug 'nerdtree'
|
|||||||
### Preview
|
### Preview
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Click to the the preview</summary>
|
<summary>Click for the preview</summary>
|
||||||
<img src="images/nerdtree.gif" alt="NERDTree example"/>
|
<img src="images/nerdtree.gif" alt="NERDTree example"/>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
@ -1,140 +1,268 @@
|
|||||||
List of Supported Set Commands
|
List of Supported Options
|
||||||
==============================
|
=========================
|
||||||
|
|
||||||
The following `:set` commands can appear in `~/.ideavimrc` or be set manually in the command mode:
|
The following options can be set with the `:set`, `:setglobal` and `:setlocal` commands.
|
||||||
|
They can be added to the `~/.ideavimrc` file, or set manually in Command-line mode.
|
||||||
|
For more details of each option, please see the Vim documentation.
|
||||||
|
Every effort is made to make these options compatible with Vim behaviour.
|
||||||
|
However, some differences are inevitable.
|
||||||
|
|
||||||
'clipboard' 'cb' clipboard options
|
```
|
||||||
Standard clipboard options plus
|
'clipboard' 'cb' Defines clipboard behavioue
|
||||||
|
A comma-separated list of words to control clipboard behaviour:
|
||||||
|
unnamed The clipboard register '*' is used instead of the
|
||||||
|
unnamed register
|
||||||
|
unnamedplus The clipboard register '+' is used instead of the
|
||||||
|
unnamed register
|
||||||
|
ideaput Uses the IDEs own paste implementation for put
|
||||||
|
operations rather than simply inserting the text
|
||||||
|
|
||||||
|
'digraph' 'dg' Enable using <BS> to enter digraphs in Insert mode
|
||||||
|
'gdefault' 'gd' The ":substitute" flag 'g' is by default
|
||||||
|
'guicursor' 'gcr' Controls the shape of the cursor for different modes
|
||||||
|
'history' 'hi' Number of command-lines that are remembered
|
||||||
|
'hlsearch' 'hls' Highlight matches with the last search pattern
|
||||||
|
'ignorecase' 'ic' Ignore case in search patterns
|
||||||
|
'incsearch' 'is' Show where search pattern typed so far matches
|
||||||
|
'iskeyword' 'isk' Defines keywords for commands like 'w', '*', etc.
|
||||||
|
'keymodel' 'km' Controls selection behaviour with special keys
|
||||||
|
List of comma separated words, which enable special things that keys
|
||||||
|
can do. These values can be used:
|
||||||
|
startsel Using a shifted special key starts selection (either
|
||||||
|
Select mode or Visual mode, depending on "key" being
|
||||||
|
present in 'selectmode')
|
||||||
|
stopsel Using a NOT-shifted special key stops selection.
|
||||||
|
Automatically enables `stopselect` and `stopvisual`
|
||||||
|
stopselect Using a NOT-shifted special key stops select mode
|
||||||
|
and removes selection - IdeaVim ONLY
|
||||||
|
stopvisual Using a NOT-shifted special key stops visual mode
|
||||||
|
and removes selection - IdeaVim ONLY
|
||||||
|
continueselect Using a shifted arrow key doesn't start selection,
|
||||||
|
but in select mode acts like startsel is enabled
|
||||||
|
- IdeaVim ONLY
|
||||||
|
continuevisual Using a shifted arrow key doesn't start selection,
|
||||||
|
but in visual mode acts like startsel is enabled
|
||||||
|
- IdeaVim ONLY
|
||||||
|
|
||||||
`ideaput` (default on) - IdeaVim ONLY
|
Special keys in this context are the cursor keys, <End>, <Home>,
|
||||||
enable native idea paste action for put operations
|
<PageUp> and <PageDown>.
|
||||||
|
|
||||||
'digraph' 'dg' enable the entering of digraphs in Insert mode
|
|
||||||
'gdefault' 'gd' the ":substitute" flag 'g' is by default
|
|
||||||
'history' 'hi' number of command-lines that are remembered
|
|
||||||
'hlsearch' 'hls' highlight matches with the last search pattern
|
|
||||||
'ignorecase' 'ic' ignore case in search patterns
|
|
||||||
'iskeyword' 'isk' defines keywords for commands like 'w', '*', etc.
|
|
||||||
'incsearch' 'is' show where search pattern typed so far matches
|
|
||||||
|
|
||||||
`keymodel` `km` String (default "continueselect,stopselect")
|
|
||||||
|
|
||||||
List of comma separated words, which enable special things that keys
|
'matchpairs' 'mps' Pairs of characters that "%" can match
|
||||||
can do. These values can be used:
|
'maxmapdepth' 'mmd' Maximum depth of mappings
|
||||||
startsel Using a shifted special[1] key starts selection (either
|
'more' 'more' When on, listings pause when the whole screen is filled
|
||||||
Select mode or Visual mode, depending on "key" being
|
'nrformats' 'nf' Number formats recognized for CTRL-A command
|
||||||
present in 'selectmode').
|
'operatorfunc' 'opfunc' Name of a function to call with the g@ operator
|
||||||
stopsel Using a NOT-shifted special[1] key stops selection.
|
'scroll' 'scr' Number of lines to scroll with CTRL-U and CTRL-D
|
||||||
Automatically enables `stopselect` and `stopvisual`
|
'selection' 'sel' What type of selection to use
|
||||||
stopselect Using a NOT-shifted special[1] key stops - IdeaVim ONLY
|
'selectmode' 'slm' Controls when to start Select mode instead of Visual
|
||||||
select mode and removes selection.
|
This is a comma-separated list of words:
|
||||||
stopvisual Using a NOT-shifted special[1] key stops - IdeaVim ONLY
|
|
||||||
visual mode and removes selection.
|
mouse When using the mouse
|
||||||
continueselect Using a shifted arrow key doesn't - IdeaVim ONLY
|
key When using shifted special[1] keys
|
||||||
start selection, but in select mode
|
cmd When using "v", "V", or <C-V>
|
||||||
acts like startsel is enabled
|
ideaselection When IDE sets a selection - IdeaVim ONLY
|
||||||
continuevisual Using a shifted arrow key doesn't - IdeaVim ONLY
|
(e.g.: extend selection, wrap with while, etc.)
|
||||||
start selection, but in visual mode
|
|
||||||
acts like startsel is enabled
|
|
||||||
|
|
||||||
'matchpairs' 'mps' pairs of characters that "%" can match
|
|
||||||
'maxmapdepth' 'mmd' Maximum depth of mappings
|
|
||||||
'more' 'more' When on, listings pause when the whole screen is filled.
|
|
||||||
'nrformats' 'nf' number formats recognized for CTRL-A command
|
|
||||||
'number' 'nu' print the line number in front of each line
|
|
||||||
'relativenumber' 'rnu' show the line number relative to the line with
|
|
||||||
the cursor
|
|
||||||
'scroll' 'scr' lines to scroll with CTRL-U and CTRL-D
|
|
||||||
'scrolljump' 'sj' minimum number of lines to scroll
|
|
||||||
'scrolloff' 'so' minimum number of lines above and below the cursor
|
|
||||||
'selection' 'sel' what type of selection to use
|
|
||||||
|
|
||||||
`selectmode` `slm` String (default "")
|
'shell' 'sh' The shell to use to execute commands with ! and :!
|
||||||
|
'shellcmdflag' 'shcf' The command flag passed to the shell
|
||||||
|
'shellxescape' 'sxe' The characters to be escaped when calling a shell
|
||||||
|
'shellxquote' 'sxq' The quote character to use in a shell command
|
||||||
|
'showcmd' 'sc' Show (partial) command in the status bar
|
||||||
|
'showmode' 'smd' Show the current mode in the status bar
|
||||||
|
'smartcase' 'scs' Use case sensitive search if any character in the
|
||||||
|
pattern is uppercase
|
||||||
|
'startofline' 'sol' When on, some commands move the cursor to the first
|
||||||
|
non-blank of the line
|
||||||
|
When off, the cursor is kept in the same column
|
||||||
|
(if possible)
|
||||||
|
'timeout' 'to' Use timeout for mapped key sequences
|
||||||
|
'timeoutlen' 'tm' Timeout duration for a mapped key sequence
|
||||||
|
'viminfo' 'vi' Information to remember after restart
|
||||||
|
'virtualedit' 've' Placement of the cursor where there is no actual text
|
||||||
|
A comma-separated list of these words:
|
||||||
|
block Allow virtual editing in Visual mode (not supported)
|
||||||
|
insert Allow virtual editing in Insert mode (not supported)
|
||||||
|
all Allow virtual editing in all modes (not supported)
|
||||||
|
onemore Allow the cursor to move just past the end of the line
|
||||||
|
|
||||||
This is a comma-separated list of words, which specify when to start
|
'visualbell' 'vb' When on, prevents beeping on error
|
||||||
Select mode instead of Visual mode, when a selection is started.
|
'whichwrap' 'ww' Which keys that move the cursor left/right can wrap to
|
||||||
Possible values:
|
other lines
|
||||||
mouse when using the mouse
|
A comma-separated list of these flags:
|
||||||
key when using shifted special[1] keys
|
char key modes
|
||||||
cmd when using "v", "V", or <C-V>
|
b <BS> Normal and Visual
|
||||||
ideaselection when IDE sets a selection - IdeaVim ONLY
|
s <Space> Normal and Visual
|
||||||
(examples: extend selection, wrap with while, etc.)
|
h "h" Normal and Visual
|
||||||
|
l "l" Normal and Visual
|
||||||
|
< <Left> Normal and Visual
|
||||||
|
> <Right> Normal and Visual
|
||||||
|
~ "~" Normal
|
||||||
|
[ <Left> Insert and Replace
|
||||||
|
] <Right> Insert and Replace
|
||||||
|
|
||||||
`startofline` `sol` When "on" some commands move the cursor to the first non-blank of the line.
|
'wrapscan' 'ws' Search will wrap around the end of file
|
||||||
When off the cursor is kept in the same column (if possible).
|
```
|
||||||
|
|
||||||
'showmode' 'smd' message on the status line to show current mode
|
## IdeaVim options mapped to IntelliJ-based IDE settings
|
||||||
'showcmd' 'sc' show (partial) command in the status bar
|
|
||||||
'sidescroll' 'ss' minimum number of columns to scroll horizontally
|
|
||||||
'sidescrolloff' 'siso' min. number of columns to left and right of cursor
|
|
||||||
'smartcase' 'scs' no ignore case when pattern is uppercase
|
|
||||||
'timeout' 'to' use timeout for mapped key sequences
|
|
||||||
'timeoutlen' 'tm' timeout duration for a mapped key sequence
|
|
||||||
'undolevels' 'ul' maximum number of changes that can be undone
|
|
||||||
'viminfo' 'vi' information to remember after restart
|
|
||||||
'visualbell' 'vb' use visual bell instead of beeping
|
|
||||||
'wrapscan' 'ws' searches wrap around the end of file
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
IdeaVim only commands:
|
|
||||||
|
|
||||||
`ideamarks` `ideamarks` Boolean (default true)
|
IdeaVim provides its own implementation for handling scroll jump and offset, even though IntelliJ-based IDEs have similar functionality (there are differences in behaviour).
|
||||||
|
When IdeaVim is hosted in an IntelliJ-based IDE (but not JetBrains Fleet), the following options map to the equivalent IDE settings:
|
||||||
If true, creation of global mark will trigger creation of IDE's bookmark
|
|
||||||
and vice versa.
|
|
||||||
|
|
||||||
`idearefactormode` `idearefactormode` String(default "select")
|
|
||||||
|
|
||||||
Define the mode that would be enabled during
|
|
||||||
the refactoring (renaming, live template, introduce variable, etc)
|
|
||||||
|
|
||||||
Use one of the following values:
|
|
||||||
- keep - keep the mode that was enabled before starting a refactoring
|
|
||||||
- select - start refactoring in select mode
|
|
||||||
- visual - start refactoring in visual mode
|
|
||||||
|
|
||||||
This option has effect if you are in normal, insert or replace mode before refactoring start.
|
|
||||||
Visual or select mode are not changed.
|
|
||||||
|
|
||||||
|
|
||||||
`ideajoin` `ideajoin` Boolean (default false)
|
|
||||||
|
|
||||||
If true, join command will be performed via IDE
|
|
||||||
See wiki/`ideajoin` examples
|
|
||||||
|
|
||||||
`ideastatusicon` `ideastatusicon` String(default "enabled")
|
|
||||||
|
|
||||||
Define the behavior of IdeaVim icon in the status bar.
|
|
||||||
|
|
||||||
Use one of the following values:
|
|
||||||
- enabled - icon is shown in the status bar
|
|
||||||
- gray - use the gray version of the icon
|
|
||||||
- disabled - hide the icon
|
|
||||||
|
|
||||||
`ideawrite` `ideawrite` String (default "all")
|
```
|
||||||
"file" or "all". Defines the behaviour of ":w" command.
|
'scrolljump' 'sj' Minimal number of lines to scroll
|
||||||
Value "all" enables execution of ":wa" (save all) command on ":w" (save).
|
'scrolloff' 'so' Minimal number of lines above and below the cursor
|
||||||
This feature exists because some IJ options like "Prettier on save" or "ESlint on save"
|
'sidescroll' 'ss' Minimal number of columns to scroll horizontally
|
||||||
work only with "save all" action. If this option is set to "all", these actions work
|
'sidescrolloff' 'siso' Minimal number of columns to left and right of cursor
|
||||||
also with ":w" command.
|
```
|
||||||
|
|
||||||
`lookupkeys` `lookupkeys` List of strings
|
|
||||||
|
|
||||||
List of keys that should be processed by the IDE during the active lookup (autocompletion).
|
|
||||||
For example, <Tab> and <Enter> are used by the IDE to finish the lookup,
|
|
||||||
but <C-W> should be passed to IdeaVim.
|
|
||||||
Default value:
|
|
||||||
"<Tab>", "<Down>", "<Up>", "<Enter>", "<Left>", "<Right>",
|
|
||||||
"<C-Down>", "<C-Up>", "<PageUp>", "<PageDown>",
|
|
||||||
"<C-J>", "<C-Q>"
|
|
||||||
|
|
||||||
`ideavimsupport` `ideavimsupport` List of strings (default "dialog")
|
|
||||||
|
|
||||||
Define the list of additional buffers where IdeaVim is enabled.
|
|
||||||
|
|
||||||
- dialog - enable IdeaVim in dialogs
|
|
||||||
- singleline - enable IdeaVim in single line editors (not suggested)
|
|
||||||
|
|
||||||
----------
|
## IdeaVim options for IntelliJ-based IDE features
|
||||||
[1] - cursor keys, <End>, <Home>, <PageUp> and <PageDown>
|
|
||||||
|
Some Vim features cannot be implemented by IdeaVim, and must be implemented by the host IDE, such as showing whitespace and line numbers, and enabling soft-wrap.
|
||||||
|
The following options modify equivalent settings and features implemented by IntelliJ-based IDEs.
|
||||||
|
|
||||||
|
There is some mismatch when trying to map Vim options, most of which are local options, to IDE settings, which are mostly global-local.
|
||||||
|
The Vim option will always reflect the effective value of the IDE setting for the current editor, and modifying the Vim option will update the local value of the IDE setting.
|
||||||
|
The default value of the Vim option set during startup is not passed to the IDE setting.
|
||||||
|
|
||||||
|
If the IDE setting has a way to modify the local value, such as entries in the _View | Active Editor_ menu, then changing this will update the current editor and be reflected in the Vim option value.
|
||||||
|
If the IDE setting can only modify its global setting in the main _Settings_ dialog, this change does not always update the current editor (because the local IDE setting has been modified and takes precedence).
|
||||||
|
|
||||||
|
IdeaVim tries to make this work more naturally by updating the editor and local Vim option when a global value changes unless the Vim option has been explicitly set in Command-line mode.
|
||||||
|
|
||||||
|
In other words, if the local Vim value is explicitly set for a window or buffer, interactively, then it should not be reset.
|
||||||
|
If the Vim option was explicitly set in `~/.ideavimrc` however, then the value will be reset, because this can be viewed as a "global" value - set once and applied to subsequently opened windows.
|
||||||
|
(This should not be confused with Vim's concept of global options, which are mainly used to initialise new windows.)
|
||||||
|
|
||||||
|
The local Vim option can always be reset to the global IDE setting value by resetting the Vim option to default with the `:set {option}&` syntax.
|
||||||
|
|
||||||
|
```
|
||||||
|
'bomb' 'bomb' Add or remove a byte order mark (BOM) to the
|
||||||
|
current file. Unlike Vim, the file is modified
|
||||||
|
immediately, and not when saved
|
||||||
|
'breakindent' 'bri' Indent soft wrapped lines to match the first
|
||||||
|
line's indent
|
||||||
|
'colorcolumn' 'cc' Maps to IntelliJ's visual guide columns
|
||||||
|
'cursorline' 'cul' Highlight the line containing the cursor
|
||||||
|
'fileencoding' 'fenc' Change the encoding of the current file. The file
|
||||||
|
is modified and written immediately, rather than
|
||||||
|
waiting to be saved
|
||||||
|
Note that the names of the encoding might not
|
||||||
|
match Vim's known names
|
||||||
|
'fileformat' 'ff' Change the file format - dos, unix or mac
|
||||||
|
The file is modified immediately, rather than
|
||||||
|
when saved
|
||||||
|
'list' 'list' Show whitespace. Maps to the editor's local
|
||||||
|
setting in the View | Active Editor menu
|
||||||
|
'number' 'nu' Show line numbers. Maps to the editor's local
|
||||||
|
setting in the View | Active Editor menu
|
||||||
|
'relativenumber' 'rnu' Show line numbers relative to the current line
|
||||||
|
'textwidth' 'tw' Set the column at which text is automatically
|
||||||
|
wrapped
|
||||||
|
'wrap' 'wrap' Enable soft-wraps. Maps to the editor's local
|
||||||
|
setting in the View | Active Editor menu
|
||||||
|
```
|
||||||
|
|
||||||
|
## IdeaVim only options
|
||||||
|
|
||||||
|
These options are IdeaVim only, and not supported by Vim.
|
||||||
|
They control integration with the host IDE.
|
||||||
|
Unless otherwise stated, these options do not have abbreviations.
|
||||||
|
|
||||||
|
```
|
||||||
|
'ideacopypreprocess' boolean (default off)
|
||||||
|
global or local to buffer
|
||||||
|
When enabled, the IDE will run custom copy pre-processors over text
|
||||||
|
copied to registers. These pre-processors can perform transformations
|
||||||
|
on the text, such as converting escape characters in a string literal
|
||||||
|
into the actual control characters in a Java file.
|
||||||
|
|
||||||
|
This is not usually the expected behaviour, so this option's default
|
||||||
|
value is off. The equivalent processing for paste is controlled by the
|
||||||
|
"ideaput" value to the 'clipboard' option.
|
||||||
|
|
||||||
|
'ideaglobalmode' boolean (default off)
|
||||||
|
global
|
||||||
|
This option will cause IdeaVim to share a single mode across all open
|
||||||
|
windows. In other words, entering Insert mode in one window will
|
||||||
|
enable Insert mode in all windows.
|
||||||
|
|
||||||
|
'ideajoin' boolean (default off)
|
||||||
|
global or local to buffer
|
||||||
|
When enabled, join commands will be handled by the IDE's "smart join"
|
||||||
|
feature. The IDE can change syntax when joining lines, such as merging
|
||||||
|
string literals or if statements. See the wiki for more examples. Not
|
||||||
|
all languages support smart join functionality.
|
||||||
|
|
||||||
|
'ideamarks' boolean (default on)
|
||||||
|
global
|
||||||
|
Maps Vim's global marks to IDE bookmarks.
|
||||||
|
|
||||||
|
'idearefactormode' string (default "select")
|
||||||
|
global or local to buffer
|
||||||
|
Specifies the mode to be used when a refactoring selects text to be
|
||||||
|
edited (e.g. renaming, live template fields, introduce variable, etc):
|
||||||
|
keep Keep the current mode
|
||||||
|
select Switch to Select mode
|
||||||
|
visual Switch to Visual mode
|
||||||
|
|
||||||
|
This option is only used when the refactoring is started in Normal,
|
||||||
|
Insert or Replace mode. Visual or Select modes are not changed.
|
||||||
|
|
||||||
|
'ideastatusicon' string (default "enabled")
|
||||||
|
global
|
||||||
|
This option controls the behaviour and appearance of the IdeaVim icon
|
||||||
|
in the status bar:
|
||||||
|
enabled Show the icon in the status bar
|
||||||
|
gray Show the gray version of the icon
|
||||||
|
disabled Hide the icon
|
||||||
|
|
||||||
|
'ideavimsupport' string (default "dialog")
|
||||||
|
global
|
||||||
|
A comma-separated list of additional buffers or locations where
|
||||||
|
IdeaVim should be enabled:
|
||||||
|
dialog Enable IdeaVim in editors hosted in dialogs
|
||||||
|
singleline Enable IdeaVim in single line editors (not recommended)
|
||||||
|
|
||||||
|
The IDE's editor component can be used in many places, such as VCS
|
||||||
|
commit tool window, or inside dialogs, and even as single line fields.
|
||||||
|
|
||||||
|
'ideawrite' string (default "all")
|
||||||
|
global
|
||||||
|
This option defines the behaviour of the :w command:
|
||||||
|
file Save the current file only
|
||||||
|
all The :w command works like :wa and invokes the Save All
|
||||||
|
IDE action. This allows options such as "Prettier on
|
||||||
|
save" or "ESlint on save" to work with the :w command,
|
||||||
|
but means all files are saved.
|
||||||
|
|
||||||
|
'lookupkeys' string (default "<Tab>,<Down>,<Up>,<Enter>,
|
||||||
|
<Left>,<Right>,<C-Down>,<C-Up>,
|
||||||
|
<PageUp>,<PageDown>, <C-J>,<C-Q>")
|
||||||
|
global
|
||||||
|
Comma-separated list of keys that should be processed by the IDE while
|
||||||
|
a code completion lookup popup is active. For example, <Tab> and
|
||||||
|
<Enter> are used by the IDE to complete the lookup and insert text,
|
||||||
|
but <C-W> should be passed IdeaVim to continue editing the text.
|
||||||
|
|
||||||
|
'trackactionids' boolean (default off)
|
||||||
|
global
|
||||||
|
When on, IdeaVim will try to track the current IDE action and display
|
||||||
|
the action name in a notification. This action ID can then be used in
|
||||||
|
a mapping to the action in the form <Action>(...).
|
||||||
|
|
||||||
|
'visualdelay' number (default 100)
|
||||||
|
global
|
||||||
|
This option specifies the delay, in milliseconds before converting an
|
||||||
|
IDE selection into Visual mode.
|
||||||
|
|
||||||
|
Some IDE features make a selection to help modify text (e.g. backspace
|
||||||
|
in Python or Yaml selects an indent and invokes the "remove selection"
|
||||||
|
action). IdeaVim listens for changes in selection to switch to Visual
|
||||||
|
mode, and will return to Normal mode when the selection is removed,
|
||||||
|
even if originally in Insert mode.
|
||||||
|
|
||||||
|
By waiting before converting to Visual mode, temporary selections can
|
||||||
|
be ignored and the current Vim mode maintained.
|
||||||
|
|
||||||
|
It is not expected that this value will need to be changed.
|
||||||
|
```
|
||||||
|
@ -9,16 +9,17 @@
|
|||||||
# suppress inspection "UnusedProperty" for whole file
|
# suppress inspection "UnusedProperty" for whole file
|
||||||
|
|
||||||
#ideaVersion=LATEST-EAP-SNAPSHOT
|
#ideaVersion=LATEST-EAP-SNAPSHOT
|
||||||
ideaVersion=2024.1
|
ideaVersion=2024.1.1
|
||||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
|
||||||
ideaType=IC
|
ideaType=IC
|
||||||
downloadIdeaSources=true
|
downloadIdeaSources=true
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=SNAPSHOT
|
version=chylex-35
|
||||||
javaVersion=17
|
javaVersion=17
|
||||||
remoteRobotVersion=0.11.22
|
remoteRobotVersion=0.11.22
|
||||||
antlrVersion=4.10.1
|
antlrVersion=4.10.1
|
||||||
|
|
||||||
|
kotlin.incremental.useClasspathSnapshot=false
|
||||||
|
|
||||||
# Please don't forget to update kotlin version in buildscript section
|
# Please don't forget to update kotlin version in buildscript section
|
||||||
# Also update kotlinxSerializationVersion version
|
# Also update kotlinxSerializationVersion version
|
||||||
@ -40,4 +41,4 @@ org.gradle.jvmargs='-Dfile.encoding=UTF-8'
|
|||||||
kotlin.stdlib.default.dependency=false
|
kotlin.stdlib.default.dependency=false
|
||||||
|
|
||||||
# Disable incremental annotation processing
|
# Disable incremental annotation processing
|
||||||
ksp.incremental=false
|
ksp.incremental=false
|
||||||
|
@ -20,13 +20,13 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.23")
|
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.24")
|
||||||
|
|
||||||
implementation("io.ktor:ktor-client-core:2.3.10")
|
implementation("io.ktor:ktor-client-core:2.3.11")
|
||||||
implementation("io.ktor:ktor-client-cio:2.3.10")
|
implementation("io.ktor:ktor-client-cio:2.3.10")
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
|
implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.11")
|
||||||
implementation("io.ktor:ktor-client-auth:2.3.10")
|
implementation("io.ktor:ktor-client-auth:2.3.11")
|
||||||
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
|
||||||
|
|
||||||
// This is needed for jgit to connect to ssh
|
// This is needed for jgit to connect to ssh
|
||||||
@ -107,6 +107,13 @@ tasks.register("calculateNewEapVersion", JavaExec::class) {
|
|||||||
args = listOf("${rootProject.rootDir}")
|
args = listOf("${rootProject.rootDir}")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.register("calculateNewEapVersionFromBranch", JavaExec::class) {
|
||||||
|
group = "release"
|
||||||
|
mainClass.set("scripts.release.CalculateNewEapVersionFromBranchKt")
|
||||||
|
classpath = sourceSets["main"].runtimeClasspath
|
||||||
|
args = listOf("${rootProject.rootDir}")
|
||||||
|
}
|
||||||
|
|
||||||
tasks.register("calculateNewDevVersion", JavaExec::class) {
|
tasks.register("calculateNewDevVersion", JavaExec::class) {
|
||||||
group = "release"
|
group = "release"
|
||||||
mainClass.set("scripts.release.CalculateNewDevVersionKt")
|
mainClass.set("scripts.release.CalculateNewDevVersionKt")
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* 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 scripts.release
|
||||||
|
|
||||||
|
fun main(args: Array<String> ) {
|
||||||
|
val projectDir = args[0]
|
||||||
|
println("Working directory: $projectDir")
|
||||||
|
val branch = withRepo(projectDir) { it.branch }
|
||||||
|
val (majorBranchVersion, minorBranchVersion) = versions(branch)
|
||||||
|
val versions = getVersionsExistingVersionsFor(majorBranchVersion, minorBranchVersion, projectDir)
|
||||||
|
val maxExistingVersion = versions.keys.maxOrNull()
|
||||||
|
|
||||||
|
val nextVersion = if (maxExistingVersion != null) {
|
||||||
|
if (maxExistingVersion.suffixTokens.isEmpty()) {
|
||||||
|
maxExistingVersion.nextPatch().withSuffix("eap.1").value
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
check(maxExistingVersion.suffixTokens.size == 2) {
|
||||||
|
"We should have exactly two suffix tokens. Current tokens: ${maxExistingVersion.suffixTokens.toList()}"
|
||||||
|
}
|
||||||
|
check(maxExistingVersion.suffixTokens[0] == "eap") {
|
||||||
|
"First suffix token must be eap. Current tokens: ${maxExistingVersion.suffixTokens.toList()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
val newEapNumber = maxExistingVersion.suffixTokens[1].toInt().inc()
|
||||||
|
maxExistingVersion.withSuffix("eap.$newEapNumber").value
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
"$majorBranchVersion.$minorBranchVersion.0-eap.1"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
println("Next eap version: $nextVersion")
|
||||||
|
println("##teamcity[setParameter name='env.ORG_GRADLE_PROJECT_version' value='$nextVersion']")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val regex = "releases/(\\d+)\\.(\\d+)\\.x".toRegex()
|
||||||
|
private fun versions(branchName: String): Pair<Int, Int> {
|
||||||
|
val match = regex.matchEntire(branchName) ?: error("Cannot match branch: $branchName")
|
||||||
|
val major = match.groups[1]
|
||||||
|
val minor = match.groups[2]
|
||||||
|
return major!!.value.toInt() to minor!!.value.toInt()
|
||||||
|
}
|
@ -64,6 +64,31 @@ enum class ReleaseType {
|
|||||||
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
|
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun getVersionsExistingVersionsFor(
|
||||||
|
majorVersion: Int,
|
||||||
|
minorVersion: Int,
|
||||||
|
projectDir: String,
|
||||||
|
): Map<Semver, ObjectId> {
|
||||||
|
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
|
||||||
|
val git = Git(repository)
|
||||||
|
println(git.log().call().first())
|
||||||
|
println(git.tagList().call().first())
|
||||||
|
|
||||||
|
return git.tagList().call().mapNotNull { ref ->
|
||||||
|
runCatching {
|
||||||
|
// Git has two types of tags: light and annotated. This code detect hash of the commit for both types of tags
|
||||||
|
val revWalk = RevWalk(repository)
|
||||||
|
val tag = revWalk.parseAny(ref.objectId)
|
||||||
|
val commitHash = revWalk.peel(tag).id
|
||||||
|
val semver = Semver(ref.name.removePrefix("refs/tags/"))
|
||||||
|
if (semver.major == majorVersion && semver.minor == minorVersion) {
|
||||||
|
semver to commitHash
|
||||||
|
} else null
|
||||||
|
}.getOrNull()
|
||||||
|
}
|
||||||
|
.toMap()
|
||||||
|
}
|
||||||
|
|
||||||
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
|
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
|
||||||
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
|
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
|
||||||
val git = Git(repository)
|
val git = Git(repository)
|
||||||
|
@ -101,10 +101,14 @@ command:
|
|||||||
(WS | COLON)* range? (WS | COLON)* EXECUTE WS* (expr WS*)* (NEW_LINE | BAR)+
|
(WS | COLON)* range? (WS | COLON)* EXECUTE WS* (expr WS*)* (NEW_LINE | BAR)+
|
||||||
#ExecuteCommand|
|
#ExecuteCommand|
|
||||||
|
|
||||||
(WS | COLON)* range? (WS | COLON)* lShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
|
(WS | COLON)* range? (WS | COLON)* lShift
|
||||||
|
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
|
||||||
|
(NEW_LINE | BAR)*
|
||||||
#ShiftLeftCommand|
|
#ShiftLeftCommand|
|
||||||
|
|
||||||
(WS | COLON)* range? (WS | COLON)* rShift (WS* commandArgument = ~(NEW_LINE | BAR)+)? ((inline_comment NEW_LINE+) | (NEW_LINE | BAR)+)
|
(WS | COLON)* range? (WS | COLON)* rShift
|
||||||
|
WS* ((commandArgumentWithoutBars? inline_comment NEW_LINE) | (commandArgumentWithoutBars? NEW_LINE) | (commandArgumentWithoutBars? BAR))
|
||||||
|
(NEW_LINE | BAR)*
|
||||||
#ShiftRightCommand|
|
#ShiftRightCommand|
|
||||||
|
|
||||||
(WS | COLON)* range? (WS | COLON)*
|
(WS | COLON)* range? (WS | COLON)*
|
||||||
@ -824,4 +828,4 @@ AUGROUP_SKIP: NEW_LINE (WS|COLON)* AUGROUP .*? AUGROUP WS+ END -> skip
|
|||||||
|
|
||||||
// All the other symbols
|
// All the other symbols
|
||||||
UNICODE_CHAR: '\u0000'..'\uFFFE';
|
UNICODE_CHAR: '\u0000'..'\uFFFE';
|
||||||
EMOJI: [\p{Emoji}];
|
EMOJI: [\p{Emoji}];
|
||||||
|
@ -282,7 +282,13 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
ideavimrcRegistered = true;
|
ideavimrcRegistered = true;
|
||||||
|
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
if (!ApplicationManager.getApplication().isUnitTestMode()) {
|
||||||
executeIdeaVimRc(editor);
|
try {
|
||||||
|
VimInjectorKt.injector.getOptionGroup().startInitVimRc();
|
||||||
|
executeIdeaVimRc(editor);
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
VimInjectorKt.injector.getOptionGroup().endInitVimRc();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
39
src/main/java/com/maddyhome/idea/vim/action/RedrawAction.kt
Normal file
39
src/main/java/com/maddyhome/idea/vim/action/RedrawAction.kt
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.action
|
||||||
|
|
||||||
|
import com.intellij.vim.annotations.CommandOrMotion
|
||||||
|
import com.intellij.vim.annotations.Mode
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.command.Command
|
||||||
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear and redraw the screen
|
||||||
|
*
|
||||||
|
* Typically, an IDE does not need to redraw the screen - it's editor handles scrolling, etc. However, it can be
|
||||||
|
* necessary to clear the status line.
|
||||||
|
*/
|
||||||
|
@CommandOrMotion(keys = ["<C-L>"], modes = [Mode.NORMAL])
|
||||||
|
internal class RedrawAction : VimActionHandler.SingleExecution() {
|
||||||
|
override val type: Command.Type = Command.Type.OTHER_READONLY
|
||||||
|
|
||||||
|
override fun execute(
|
||||||
|
editor: VimEditor,
|
||||||
|
context: ExecutionContext,
|
||||||
|
cmd: Command,
|
||||||
|
operatorArguments: OperatorArguments,
|
||||||
|
): Boolean {
|
||||||
|
injector.redrawService.redraw()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.KeyHandler
|
|||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.group.EditorHolderService
|
||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||||
import com.maddyhome.idea.vim.group.IjOptions
|
import com.maddyhome.idea.vim.group.IjOptions
|
||||||
import com.maddyhome.idea.vim.handler.enableOctopus
|
import com.maddyhome.idea.vim.handler.enableOctopus
|
||||||
@ -44,6 +45,7 @@ import com.maddyhome.idea.vim.listener.AceJumpService
|
|||||||
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
|
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.ui.ex.ExTextField
|
||||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
import java.awt.event.InputEvent
|
import java.awt.event.InputEvent
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
@ -252,7 +254,14 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getEditor(e: AnActionEvent): Editor? = e.getData(PlatformDataKeys.EDITOR)
|
private fun getEditor(e: AnActionEvent): Editor? {
|
||||||
|
return e.getData(PlatformDataKeys.EDITOR)
|
||||||
|
?: if (e.getData(PlatformDataKeys.CONTEXT_COMPONENT) is ExTextField) {
|
||||||
|
EditorHolderService.getInstance().editor
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Every time the key pressed with an active lookup, there is a decision:
|
* Every time the key pressed with an active lookup, there is a decision:
|
||||||
|
@ -58,11 +58,11 @@ internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
|
|||||||
)
|
)
|
||||||
} else if (!repeatHandler && lastCommand != null) {
|
} else if (!repeatHandler && lastCommand != null) {
|
||||||
if (cmd.rawCount > 0) {
|
if (cmd.rawCount > 0) {
|
||||||
lastCommand.count = cmd.count
|
lastCommand.rawCount = cmd.count
|
||||||
val arg = lastCommand.argument
|
val arg = lastCommand.argument
|
||||||
if (arg != null) {
|
if (arg != null) {
|
||||||
val mot = arg.motion
|
val mot = arg.motion
|
||||||
mot.count = 0
|
mot.rawCount = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
state.executingCommand = lastCommand
|
state.executingCommand = lastCommand
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2023 The IdeaVim authors
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style
|
|
||||||
* license that can be found in the LICENSE.txt file or at
|
|
||||||
* https://opensource.org/licenses/MIT.
|
|
||||||
*/
|
|
||||||
package com.maddyhome.idea.vim.action.ex
|
|
||||||
|
|
||||||
import com.intellij.vim.annotations.CommandOrMotion
|
|
||||||
import com.intellij.vim.annotations.Mode
|
|
||||||
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.command.Command
|
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
|
||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
|
||||||
import java.util.*
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called by KeyHandler to process the contents of the ex entry panel
|
|
||||||
*
|
|
||||||
* The mapping for this action means that the ex command is executed as a write action
|
|
||||||
*/
|
|
||||||
@CommandOrMotion(keys = ["<CR>", "<C-M>", "<C-J>"], modes = [Mode.CMD_LINE])
|
|
||||||
public class ProcessExEntryAction : VimActionHandler.SingleExecution() {
|
|
||||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
|
||||||
|
|
||||||
override val flags: EnumSet<CommandFlags> = EnumSet.of(CommandFlags.FLAG_COMPLETE_EX)
|
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
|
||||||
return VimPlugin.getProcess().processExEntry(editor, context)
|
|
||||||
}
|
|
||||||
}
|
|
@ -13,24 +13,46 @@ import com.maddyhome.idea.vim.api.VimExOutputPanel
|
|||||||
import com.maddyhome.idea.vim.helper.vimExOutput
|
import com.maddyhome.idea.vim.helper.vimExOutput
|
||||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
import com.maddyhome.idea.vim.ui.ExOutputPanel
|
||||||
|
|
||||||
/**
|
// TODO: We need a nicer way to handle output, especially wrt testing, appending + clearing
|
||||||
* @author vlan
|
|
||||||
*/
|
|
||||||
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
|
public class ExOutputModel private constructor(private val myEditor: Editor) : VimExOutputPanel {
|
||||||
|
private var isActiveInTestMode = false
|
||||||
|
|
||||||
|
override val isActive: Boolean
|
||||||
|
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
ExOutputPanel.isPanelActive(myEditor)
|
||||||
|
} else {
|
||||||
|
isActiveInTestMode
|
||||||
|
}
|
||||||
|
|
||||||
override var text: String? = null
|
override var text: String? = null
|
||||||
private set
|
get() = if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
ExOutputPanel.getInstance(myEditor).text
|
||||||
|
} else {
|
||||||
|
field
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
|
ExOutputPanel.getInstance(myEditor).setText(value ?: "")
|
||||||
|
} else {
|
||||||
|
field = value
|
||||||
|
isActiveInTestMode = !value.isNullOrEmpty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun output(text: String) {
|
override fun output(text: String) {
|
||||||
this.text = text
|
this.text = text
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
|
||||||
ExOutputPanel.getInstance(myEditor).setText(text)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clear() {
|
override fun clear() {
|
||||||
text = null
|
text = null
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
ExOutputPanel.getInstance(myEditor).deactivate(false)
|
ExOutputPanel.getInstance(myEditor).close()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
isActiveInTestMode = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,7 +24,6 @@ import com.maddyhome.idea.vim.command.MappingMode
|
|||||||
import com.maddyhome.idea.vim.common.CommandAlias
|
import com.maddyhome.idea.vim.common.CommandAlias
|
||||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
|
|
||||||
import com.maddyhome.idea.vim.helper.TestInputModel
|
import com.maddyhome.idea.vim.helper.TestInputModel
|
||||||
import com.maddyhome.idea.vim.helper.noneOfEnum
|
import com.maddyhome.idea.vim.helper.noneOfEnum
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
@ -183,7 +182,7 @@ public object VimExtensionFacade {
|
|||||||
/** Returns a string typed in the input box similar to 'input()'. */
|
/** Returns a string typed in the input box similar to 'input()'. */
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
|
public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
|
||||||
return service<CommandLineHelper>().inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
|
return injector.commandLine.inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the current contents of the given register similar to 'getreg()'. */
|
/** Get the current contents of the given register similar to 'getreg()'. */
|
||||||
|
@ -32,7 +32,8 @@ import com.maddyhome.idea.vim.command.OperatorArguments
|
|||||||
import com.maddyhome.idea.vim.command.TextObjectVisualType
|
import com.maddyhome.idea.vim.command.TextObjectVisualType
|
||||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.toTextRange
|
||||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||||
import com.maddyhome.idea.vim.extension.VimExtension
|
import com.maddyhome.idea.vim.extension.VimExtension
|
||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade
|
||||||
@ -248,8 +249,14 @@ internal class CommentaryExtension : VimExtension {
|
|||||||
* Used like `:1,3Commentary` or `g/fun/Commentary`
|
* Used like `:1,3Commentary` or `g/fun/Commentary`
|
||||||
*/
|
*/
|
||||||
private class CommentaryCommandAliasHandler : CommandAliasHandler {
|
private class CommentaryCommandAliasHandler : CommandAliasHandler {
|
||||||
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
|
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
|
||||||
Util.doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
|
Util.doCommentary(
|
||||||
|
editor,
|
||||||
|
context,
|
||||||
|
range.getLineRange(editor, editor.primaryCaret()).toTextRange(editor),
|
||||||
|
SelectionType.LINE_WISE,
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ package com.maddyhome.idea.vim.extension.highlightedyank
|
|||||||
|
|
||||||
import com.intellij.ide.ui.LafManager
|
import com.intellij.ide.ui.LafManager
|
||||||
import com.intellij.ide.ui.LafManagerListener
|
import com.intellij.ide.ui.LafManagerListener
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ModalityState
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.colors.EditorColors
|
import com.intellij.openapi.editor.colors.EditorColors
|
||||||
import com.intellij.openapi.editor.markup.EffectType
|
import com.intellij.openapi.editor.markup.EffectType
|
||||||
@ -19,25 +19,24 @@ import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
|||||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||||
import com.intellij.openapi.editor.markup.TextAttributes
|
import com.intellij.openapi.editor.markup.TextAttributes
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
|
import com.intellij.util.Alarm
|
||||||
|
import com.intellij.util.Alarm.ThreadToUse
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.VimProjectService
|
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.extension.VimExtension
|
import com.maddyhome.idea.vim.extension.VimExtension
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
import com.maddyhome.idea.vim.helper.StrictMode
|
|
||||||
import com.maddyhome.idea.vim.helper.VimNlsSafe
|
import com.maddyhome.idea.vim.helper.VimNlsSafe
|
||||||
import com.maddyhome.idea.vim.listener.VimInsertListener
|
import com.maddyhome.idea.vim.listener.VimInsertListener
|
||||||
import com.maddyhome.idea.vim.listener.VimYankListener
|
import com.maddyhome.idea.vim.listener.VimYankListener
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
|
||||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
import java.awt.Color
|
import java.awt.Color
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
import java.util.concurrent.Executors
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
internal const val DEFAULT_HIGHLIGHT_DURATION: Long = 300
|
internal const val DEFAULT_HIGHLIGHT_DURATION: Int = 300
|
||||||
|
|
||||||
@NonNls
|
@NonNls
|
||||||
private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_duration"
|
private val HIGHLIGHT_DURATION_VARIABLE_NAME = "highlightedyank_highlight_duration"
|
||||||
@ -78,111 +77,128 @@ internal class HighlightColorResetter : LafManagerListener {
|
|||||||
*/
|
*/
|
||||||
internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
|
internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertListener {
|
||||||
private val highlightHandler = HighlightHandler()
|
private val highlightHandler = HighlightHandler()
|
||||||
|
private var initialised = false
|
||||||
|
|
||||||
override fun getName() = "highlightedyank"
|
override fun getName() = "highlightedyank"
|
||||||
|
|
||||||
override fun init() {
|
override fun init() {
|
||||||
|
// Note that these listeners will still be registered when IdeaVim is disabled. However, they'll never get called
|
||||||
VimPlugin.getYank().addListener(this)
|
VimPlugin.getYank().addListener(this)
|
||||||
VimPlugin.getChange().addInsertListener(this)
|
VimPlugin.getChange().addInsertListener(this)
|
||||||
|
|
||||||
|
// Register our own disposable to remove highlights when IdeaVim is disabled. Somehow, we need to re-register when
|
||||||
|
// IdeaVim is re-enabled. We don't get a call back for that, but because the listeners are active until the
|
||||||
|
// _extension_ is disabled, make sure we're properly initialised each time we're called.
|
||||||
|
registerIdeaVimDisabledCallback()
|
||||||
|
initialised = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun registerIdeaVimDisabledCallback() {
|
||||||
|
// TODO: IdeaVim should help with lifecycle management here - VIM-3419
|
||||||
|
// IdeaVim should possibly unregister extensions, but it would also need to re-register them. We have to do this
|
||||||
|
// state management manually for now
|
||||||
|
Disposer.register(VimPlugin.getInstance().onOffDisposable) {
|
||||||
|
highlightHandler.clearYankHighlighters()
|
||||||
|
initialised = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispose() {
|
override fun dispose() {
|
||||||
|
// Called when the extension is disabled with `:set no{extension}` or if the plugin owning the extension unloads
|
||||||
VimPlugin.getYank().removeListener(this)
|
VimPlugin.getYank().removeListener(this)
|
||||||
VimPlugin.getChange().removeInsertListener(this)
|
VimPlugin.getChange().removeInsertListener(this)
|
||||||
|
|
||||||
|
highlightHandler.clearYankHighlighters()
|
||||||
|
initialised = false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun yankPerformed(editor: VimEditor, range: TextRange) {
|
override fun yankPerformed(editor: VimEditor, range: TextRange) {
|
||||||
|
ensureInitialised()
|
||||||
highlightHandler.highlightYankRange(editor.ij, range)
|
highlightHandler.highlightYankRange(editor.ij, range)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun insertModeStarted(editor: Editor) {
|
override fun insertModeStarted(editor: Editor) {
|
||||||
highlightHandler.clearAllYankHighlighters()
|
ensureInitialised()
|
||||||
|
highlightHandler.clearYankHighlighters()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureInitialised() {
|
||||||
|
if (!initialised) {
|
||||||
|
registerIdeaVimDisabledCallback()
|
||||||
|
initialised = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class HighlightHandler {
|
private class HighlightHandler {
|
||||||
private var editor: Editor? = null
|
private val alarm = Alarm(ThreadToUse.SWING_THREAD)
|
||||||
private val yankHighlighters: MutableSet<RangeHighlighter> = mutableSetOf()
|
private var lastEditor: Editor? = null
|
||||||
|
private val highlighters = mutableSetOf<RangeHighlighter>()
|
||||||
|
|
||||||
fun highlightYankRange(editor: Editor, range: 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
|
// from vim-highlightedyank docs: When a new text is yanked or user starts editing, the old highlighting would be deleted
|
||||||
clearAllYankHighlighters()
|
clearYankHighlighters()
|
||||||
|
|
||||||
this.editor = editor
|
lastEditor = editor
|
||||||
val project = editor.project
|
|
||||||
if (project != null) {
|
val attributes = getHighlightTextAttributes(editor)
|
||||||
Disposer.register(
|
for (i in 0 until range.size()) {
|
||||||
VimProjectService.getInstance(project),
|
val highlighter = editor.markupModel.addRangeHighlighter(
|
||||||
) {
|
range.startOffsets[i],
|
||||||
this.editor = null
|
range.endOffsets[i],
|
||||||
yankHighlighters.clear()
|
HighlighterLayer.SELECTION,
|
||||||
}
|
attributes,
|
||||||
|
HighlighterTargetArea.EXACT_RANGE,
|
||||||
|
)
|
||||||
|
highlighters.add(highlighter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (range.isMultiple) {
|
|
||||||
for (i in 0 until range.size()) {
|
|
||||||
highlightSingleRange(editor, range.startOffsets[i]..range.endOffsets[i])
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
highlightSingleRange(editor, range.startOffset..range.endOffset)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearAllYankHighlighters() {
|
|
||||||
yankHighlighters.forEach { highlighter ->
|
|
||||||
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
|
|
||||||
}
|
|
||||||
|
|
||||||
yankHighlighters.clear()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun highlightSingleRange(editor: Editor, range: ClosedRange<Int>) {
|
|
||||||
val highlighter = editor.markupModel.addRangeHighlighter(
|
|
||||||
range.start,
|
|
||||||
range.endInclusive,
|
|
||||||
HighlighterLayer.SELECTION,
|
|
||||||
getHighlightTextAttributes(),
|
|
||||||
HighlighterTargetArea.EXACT_RANGE,
|
|
||||||
)
|
|
||||||
|
|
||||||
yankHighlighters.add(highlighter)
|
|
||||||
|
|
||||||
setClearHighlightRangeTimer(highlighter)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setClearHighlightRangeTimer(highlighter: RangeHighlighter) {
|
|
||||||
val timeout = extractUsersHighlightDuration()
|
|
||||||
|
|
||||||
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
|
// from vim-highlightedyank docs: A negative number makes the highlight persistent.
|
||||||
|
val timeout = extractUsersHighlightDuration()
|
||||||
if (timeout >= 0) {
|
if (timeout >= 0) {
|
||||||
Executors.newSingleThreadScheduledExecutor().schedule(
|
// Note modality. This is important when highlighting an editor when a modal dialog is open, such as the resolve
|
||||||
{
|
// conflict diff view
|
||||||
ApplicationManager.getApplication().invokeLater {
|
alarm.addRequest(
|
||||||
editor?.markupModel?.removeHighlighter(highlighter) ?: StrictMode.fail("Highlighters without an editor")
|
{ clearYankHighlighters() },
|
||||||
}
|
|
||||||
},
|
|
||||||
timeout,
|
timeout,
|
||||||
TimeUnit.MILLISECONDS,
|
ModalityState.any()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getHighlightTextAttributes() = TextAttributes(
|
fun clearYankHighlighters() {
|
||||||
|
alarm.cancelAllRequests()
|
||||||
|
// Make sure the last editor we saved is still alive before we use it. We can't just use the list of open editors
|
||||||
|
// because this list is empty when IdeaVim is disabled, so we're unable to clean up
|
||||||
|
lastEditor?.let { editor ->
|
||||||
|
if (!editor.isDisposed) {
|
||||||
|
highlighters.forEach { highlighter -> editor.markupModel.removeHighlighter(highlighter) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lastEditor = null
|
||||||
|
highlighters.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getHighlightTextAttributes(editor: Editor) = TextAttributes(
|
||||||
null,
|
null,
|
||||||
extractUsersHighlightColor(),
|
extractUsersHighlightColor(),
|
||||||
editor?.colorsScheme?.getColor(EditorColors.CARET_COLOR),
|
editor.colorsScheme.getColor(EditorColors.CARET_COLOR),
|
||||||
EffectType.SEARCH_MATCH,
|
EffectType.SEARCH_MATCH,
|
||||||
Font.PLAIN,
|
Font.PLAIN,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun extractUsersHighlightDuration(): Long {
|
private fun extractUsersHighlightDuration(): Int {
|
||||||
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
|
return extractVariable(HIGHLIGHT_DURATION_VARIABLE_NAME, DEFAULT_HIGHLIGHT_DURATION) {
|
||||||
it.toLong()
|
// toVimNumber will return 0 for an invalid string. Let's force it to throw
|
||||||
|
when (it) {
|
||||||
|
is VimString -> it.value.toInt()
|
||||||
|
else -> it.toVimNumber().value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun extractUsersHighlightColor(): Color {
|
private fun extractUsersHighlightColor(): Color {
|
||||||
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value ->
|
return extractVariable(HIGHLIGHT_COLOR_VARIABLE_NAME, getDefaultHighlightTextColor()) { value ->
|
||||||
val rgba = value
|
val rgba = value.asString()
|
||||||
.substring(4)
|
.substring(4)
|
||||||
.filter { it != '(' && it != ')' && !it.isWhitespace() }
|
.filter { it != '(' && it != ')' && !it.isWhitespace() }
|
||||||
.split(',')
|
.split(',')
|
||||||
@ -192,12 +208,11 @@ internal class VimHighlightedYank : VimExtension, VimYankListener, VimInsertList
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> extractVariable(variable: String, default: T, extractFun: (value: String) -> T): T {
|
private fun <T> extractVariable(variable: String, default: T, extractFun: (value: VimDataType) -> T): T {
|
||||||
val value = VimPlugin.getVariableService().getGlobalVariableValue(variable)
|
val value = VimPlugin.getVariableService().getGlobalVariableValue(variable)
|
||||||
|
if (value != null) {
|
||||||
if (value is VimString) {
|
|
||||||
return try {
|
return try {
|
||||||
extractFun(value.value)
|
extractFun(value)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@VimNlsSafe val message = MessageHelper.message(
|
@VimNlsSafe val message = MessageHelper.message(
|
||||||
"highlightedyank.invalid.value.of.0.1",
|
"highlightedyank.invalid.value.of.0.1",
|
||||||
|
@ -233,7 +233,7 @@ private object FileTypePatterns {
|
|||||||
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
||||||
this.cMakePatterns
|
this.cMakePatterns
|
||||||
} else {
|
} else {
|
||||||
return null
|
this.htmlPatterns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ import com.maddyhome.idea.vim.api.VimEditor
|
|||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.CommandAlias
|
import com.maddyhome.idea.vim.common.CommandAlias
|
||||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
import com.maddyhome.idea.vim.common.CommandAliasHandler
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.extension.VimExtension
|
import com.maddyhome.idea.vim.extension.VimExtension
|
||||||
import com.maddyhome.idea.vim.group.KeyGroup
|
import com.maddyhome.idea.vim.group.KeyGroup
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
@ -137,13 +137,13 @@ internal class NerdTree : VimExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
|
class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
|
||||||
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
|
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
|
||||||
Util.callAction(editor, actionId, context)
|
Util.callAction(editor, actionId, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ToggleHandler : CommandAliasHandler {
|
class ToggleHandler : CommandAliasHandler {
|
||||||
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
|
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
|
||||||
val project = editor.ij.project ?: return
|
val project = editor.ij.project ?: return
|
||||||
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
|
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
|
||||||
if (toolWindow.isVisible) {
|
if (toolWindow.isVisible) {
|
||||||
@ -155,7 +155,7 @@ internal class NerdTree : VimExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class CloseHandler : CommandAliasHandler {
|
class CloseHandler : CommandAliasHandler {
|
||||||
override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
|
override fun execute(command: String, range: Range, editor: VimEditor, context: ExecutionContext) {
|
||||||
val project = editor.ij.project ?: return
|
val project = editor.ij.project ?: return
|
||||||
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
|
val toolWindow = ToolWindowManagerEx.getInstanceEx(project).getToolWindow(ToolWindowId.PROJECT_VIEW) ?: return
|
||||||
if (toolWindow.isVisible) {
|
if (toolWindow.isVisible) {
|
||||||
@ -477,6 +477,7 @@ internal class NerdTree : VimExtension {
|
|||||||
"r",
|
"r",
|
||||||
NerdAction.ToIj("SynchronizeCurrentFile"),
|
NerdAction.ToIj("SynchronizeCurrentFile"),
|
||||||
)
|
)
|
||||||
|
registerCommand("NERDTreeMapToggleHidden", "I", NerdAction.ToIj("ProjectView.ShowExcludedFiles"))
|
||||||
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
|
registerCommand("NERDTreeMapRefreshRoot", "R", NerdAction.ToIj("Synchronize"))
|
||||||
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
|
registerCommand("NERDTreeMapMenu", "m", NerdAction.ToIj("ShowPopupMenu"))
|
||||||
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
|
registerCommand("NERDTreeMapQuit", "q", NerdAction.ToIj("HideActiveWindow"))
|
||||||
|
@ -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.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
|
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.endsWithNewLine
|
import com.maddyhome.idea.vim.api.endsWithNewLine
|
||||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||||
@ -35,7 +36,10 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissin
|
|||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
||||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
|
import com.maddyhome.idea.vim.extension.exportOperatorFunction
|
||||||
import com.maddyhome.idea.vim.group.findBlockRange
|
import com.maddyhome.idea.vim.group.findBlockRange
|
||||||
|
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
|
||||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||||
|
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||||
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
||||||
@ -78,7 +82,7 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
|
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 {
|
private class YSurroundHandler : ExtensionHandler {
|
||||||
@ -106,7 +110,7 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
||||||
if (lastNonWhiteSpaceOffset != null) {
|
if (lastNonWhiteSpaceOffset != null) {
|
||||||
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
||||||
performSurround(pair, range, it)
|
performSurround(pair, range, it, count = operatorArguments.count1)
|
||||||
}
|
}
|
||||||
// it.moveToOffset(lineStartOffset)
|
// it.moveToOffset(lineStartOffset)
|
||||||
}
|
}
|
||||||
@ -126,15 +130,13 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
|
|
||||||
private class VSurroundHandler : ExtensionHandler {
|
private class VSurroundHandler : ExtensionHandler {
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
|
|
||||||
// NB: Operator ignores SelectionType anyway
|
// NB: Operator ignores SelectionType anyway
|
||||||
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
|
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
// Leave visual mode
|
// Leave visual mode
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
||||||
editor.ij.caretModel.moveToOffset(selectionStart)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -155,6 +157,10 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
|
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
// Save old register values for carets
|
// Save old register values for carets
|
||||||
val surroundings = editor.sortedCarets()
|
val surroundings = editor.sortedCarets()
|
||||||
.map {
|
.map {
|
||||||
@ -262,20 +268,41 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Operator : OperatorFunction {
|
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
|
||||||
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||||
val ijEditor = editor.ij
|
val ijEditor = vimEditor.ij
|
||||||
val c = getChar(ijEditor)
|
val c = getChar(ijEditor)
|
||||||
if (c.code == 0) return true
|
if (c.code == 0) return true
|
||||||
|
|
||||||
val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
|
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
|
runWriteAction {
|
||||||
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
|
val change = VimPlugin.getChange()
|
||||||
// Jump back to start
|
if (supportsMultipleCursors) {
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
ijEditor.runWithEveryCaretAndRestore {
|
||||||
|
applyOnce(ijEditor, change, pair, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
applyOnce(ijEditor, change, pair, count)
|
||||||
|
// Jump back to start
|
||||||
|
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
|
||||||
|
// XXX: Will it work with line-wise or block-wise selections?
|
||||||
|
val primaryCaret = editor.caretModel.primaryCaret
|
||||||
|
val range = getSurroundRange(primaryCaret.vim)
|
||||||
|
if (range != null) {
|
||||||
|
val start = RepeatedCharSequence.of(pair.first, count)
|
||||||
|
val end = RepeatedCharSequence.of(pair.second, count)
|
||||||
|
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
|
||||||
|
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
||||||
val editor = caret.editor
|
val editor = caret.editor
|
||||||
@ -362,15 +389,15 @@ private fun getChar(editor: Editor): Char {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
|
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
val editor = caret.editor
|
val editor = caret.editor
|
||||||
val change = VimPlugin.getChange()
|
val change = VimPlugin.getChange()
|
||||||
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
|
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||||
|
|
||||||
val isEOF = range.endOffset == editor.text().length
|
val isEOF = range.endOffset == editor.text().length
|
||||||
val hasNewLine = editor.endsWithNewLine()
|
val hasNewLine = editor.endsWithNewLine()
|
||||||
val rightSurround = if (tagsOnNewLines) {
|
val rightSurround = (if (tagsOnNewLines) {
|
||||||
if (isEOF && !hasNewLine) {
|
if (isEOF && !hasNewLine) {
|
||||||
"\n" + pair.second
|
"\n" + pair.second
|
||||||
} else {
|
} else {
|
||||||
@ -378,7 +405,7 @@ private fun performSurround(pair: Pair<String, String>, range: TextRange, caret:
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pair.second
|
pair.second
|
||||||
}
|
}).let { RepeatedCharSequence.of(it, count) }
|
||||||
|
|
||||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||||
|
@ -131,10 +131,11 @@ public class VimIndentObject implements VimExtension {
|
|||||||
// This is done as a separate step so that it works even when the caret is inside the indentation.
|
// This is done as a separate step so that it works even when the caret is inside the indentation.
|
||||||
int offset = caretLineStartOffset;
|
int offset = caretLineStartOffset;
|
||||||
int indentSize = 0;
|
int indentSize = 0;
|
||||||
while (++offset < charSequence.length()) {
|
while (offset < charSequence.length()) {
|
||||||
final char ch = charSequence.charAt(offset);
|
final char ch = charSequence.charAt(offset);
|
||||||
if (ch == ' ' || ch == '\t') {
|
if (ch == ' ' || ch == '\t') {
|
||||||
++indentSize;
|
++indentSize;
|
||||||
|
++offset;
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -79,7 +79,6 @@ import java.math.BigInteger
|
|||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.function.Consumer
|
import java.util.function.Consumer
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides all the insert/replace related functionality
|
* Provides all the insert/replace related functionality
|
||||||
@ -395,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
context: ExecutionContext,
|
context: ExecutionContext,
|
||||||
range: TextRange,
|
range: TextRange,
|
||||||
) {
|
) {
|
||||||
|
val startPos = editor.offsetToBufferPosition(caret.offset)
|
||||||
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
val startOffset = editor.getLineStartForOffset(range.startOffset)
|
||||||
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
val endOffset = editor.getLineEndForOffset(range.endOffset)
|
||||||
val ijEditor = (editor as IjVimEditor).editor
|
val ijEditor = (editor as IjVimEditor).editor
|
||||||
@ -419,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val afterAction = {
|
val afterAction = {
|
||||||
val firstLine = editor.offsetToBufferPosition(
|
caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
|
||||||
min(startOffset.toDouble(), endOffset.toDouble()).toInt()
|
|
||||||
).line
|
|
||||||
val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
|
|
||||||
caret.moveToOffset(newOffset)
|
|
||||||
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
|
||||||
}
|
}
|
||||||
if (project != null) {
|
if (project != null) {
|
||||||
@ -571,7 +567,7 @@ public class ChangeGroup : VimChangeGroupBase() {
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
val startLine = range.startLine
|
val startLine = range.startLine
|
||||||
val endLine = range.endLine
|
val endLine = range.endLine
|
||||||
val count = endLine - startLine + 1
|
val count = range.size
|
||||||
if (count < 2) {
|
if (count < 2) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -39,11 +39,12 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static com.intellij.openapi.editor.EditorSettings.*;
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.options;
|
|
||||||
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
|
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -55,12 +56,31 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
|
|
||||||
private Boolean isKeyRepeat = null;
|
private Boolean isKeyRepeat = null;
|
||||||
|
|
||||||
|
// TODO: Get rid of this custom line converter once we support soft wraps properly
|
||||||
|
// The builtin relative line converter looks like it's using Vim's logical lines for counting, where a Vim logical
|
||||||
|
// line is a buffer line, or a single line representing a fold of several buffer lines. This converter is counting
|
||||||
|
// screen lines (but badly - if you're on the second line of a wrapped line, it still counts like you're on the first.
|
||||||
|
// We really want to use Vim logical lines, but we don't currently support them for movement - we move by screen line.
|
||||||
|
|
||||||
private final CaretListener myLineNumbersCaretListener = new CaretListener() {
|
private final CaretListener myLineNumbersCaretListener = new CaretListener() {
|
||||||
@Override
|
@Override
|
||||||
public void caretPositionChanged(@NotNull CaretEvent e) {
|
public void caretPositionChanged(@NotNull CaretEvent e) {
|
||||||
final boolean requiresRepaint = e.getNewPosition().line != e.getOldPosition().line;
|
// We don't get notified when the IDE's settings change, so make sure we're up-to-date when the caret moves
|
||||||
if (requiresRepaint && options(injector, new IjVimEditor(e.getEditor())).getRelativenumber()) {
|
final Editor editor = e.getEditor();
|
||||||
repaintRelativeLineNumbers(e.getEditor());
|
boolean relativenumber = ijOptions(injector, new IjVimEditor(editor)).getRelativenumber();
|
||||||
|
if (relativenumber) {
|
||||||
|
if (!hasRelativeLineNumbersInstalled(editor)) {
|
||||||
|
installRelativeLineNumbers(editor);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// We must repaint on each caret move, so we update when caret's visual line doesn't match logical line
|
||||||
|
repaintRelativeLineNumbers(editor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (hasRelativeLineNumbersInstalled(editor)) {
|
||||||
|
removeRelativeLineNumbers(editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -73,11 +93,10 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
editor.getCaretModel().addCaretListener(myLineNumbersCaretListener);
|
editor.getCaretModel().addCaretListener(myLineNumbersCaretListener);
|
||||||
UserDataManager.setVimEditorGroup(editor, true);
|
UserDataManager.setVimEditorGroup(editor, true);
|
||||||
|
|
||||||
UserDataManager.setVimLineNumbersInitialState(editor, editor.getSettings().isLineNumbersShown());
|
|
||||||
updateLineNumbers(editor);
|
updateLineNumbers(editor);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deinitLineNumbers(@NotNull Editor editor, boolean isReleasing) {
|
private void deinitLineNumbers(@NotNull Editor editor) {
|
||||||
if (isProjectDisposed(editor) || !supportsVimLineNumbers(editor) || !UserDataManager.getVimEditorGroup(editor)) {
|
if (isProjectDisposed(editor) || !supportsVimLineNumbers(editor) || !UserDataManager.getVimEditorGroup(editor)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -86,14 +105,6 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
UserDataManager.setVimEditorGroup(editor, false);
|
UserDataManager.setVimEditorGroup(editor, false);
|
||||||
|
|
||||||
removeRelativeLineNumbers(editor);
|
removeRelativeLineNumbers(editor);
|
||||||
|
|
||||||
// Don't reset the built in line numbers if we're releasing the editor. If we do, EditorSettings.setLineNumbersShown
|
|
||||||
// can cause the editor to refresh settings and can call into FileManagerImpl.getCachedPsiFile AFTER FileManagerImpl
|
|
||||||
// has been disposed (Closing the project with a Find Usages result showing a preview panel is a good repro case).
|
|
||||||
// See IDEA-184351 and VIM-1671
|
|
||||||
if (!isReleasing) {
|
|
||||||
setBuiltinLineNumbers(editor, UserDataManager.getVimLineNumbersInitialState(editor));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean supportsVimLineNumbers(final @NotNull Editor editor) {
|
private static boolean supportsVimLineNumbers(final @NotNull Editor editor) {
|
||||||
@ -107,41 +118,22 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void updateLineNumbers(final @NotNull Editor editor) {
|
private static void updateLineNumbers(final @NotNull Editor editor) {
|
||||||
final EffectiveOptions options = options(injector, new IjVimEditor(editor));
|
final boolean isLineNumbersShown = editor.getSettings().isLineNumbersShown();
|
||||||
final boolean relativeNumber = options.getRelativenumber();
|
if (!isLineNumbersShown) {
|
||||||
final boolean number = options.getNumber();
|
return;
|
||||||
|
|
||||||
final boolean showBuiltinEditorLineNumbers = shouldShowBuiltinLineNumbers(editor, number, relativeNumber);
|
|
||||||
|
|
||||||
final EditorSettings settings = editor.getSettings();
|
|
||||||
if (settings.isLineNumbersShown() ^ showBuiltinEditorLineNumbers) {
|
|
||||||
// Update line numbers later since it may be called from a caret listener
|
|
||||||
// on the caret move and it may move the caret internally
|
|
||||||
ApplicationManager.getApplication().invokeLater(() -> {
|
|
||||||
if (editor.isDisposed()) return;
|
|
||||||
setBuiltinLineNumbers(editor, showBuiltinEditorLineNumbers);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (relativeNumber) {
|
final LineNumerationType lineNumerationType = editor.getSettings().getLineNumerationType();
|
||||||
|
if (lineNumerationType == LineNumerationType.RELATIVE || lineNumerationType == LineNumerationType.HYBRID) {
|
||||||
if (!hasRelativeLineNumbersInstalled(editor)) {
|
if (!hasRelativeLineNumbersInstalled(editor)) {
|
||||||
installRelativeLineNumbers(editor);
|
installRelativeLineNumbers(editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (hasRelativeLineNumbersInstalled(editor)) {
|
else {
|
||||||
removeRelativeLineNumbers(editor);
|
removeRelativeLineNumbers(editor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean shouldShowBuiltinLineNumbers(final @NotNull Editor editor, boolean number, boolean relativeNumber) {
|
|
||||||
final boolean initialState = UserDataManager.getVimLineNumbersInitialState(editor);
|
|
||||||
return initialState || number || relativeNumber;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setBuiltinLineNumbers(final @NotNull Editor editor, boolean show) {
|
|
||||||
editor.getSettings().setLineNumbersShown(show);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean hasRelativeLineNumbersInstalled(final @NotNull Editor editor) {
|
private static boolean hasRelativeLineNumbersInstalled(final @NotNull Editor editor) {
|
||||||
return UserDataManager.getVimHasRelativeLineNumbersInstalled(editor);
|
return UserDataManager.getVimHasRelativeLineNumbersInstalled(editor);
|
||||||
}
|
}
|
||||||
@ -256,8 +248,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
updateCaretsVisualAttributes(new IjVimEditor(editor));
|
updateCaretsVisualAttributes(new IjVimEditor(editor));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void editorDeinit(@NotNull Editor editor, boolean isReleased) {
|
public void editorDeinit(@NotNull Editor editor) {
|
||||||
deinitLineNumbers(editor, isReleased);
|
deinitLineNumbers(editor);
|
||||||
UserDataManager.unInitializeEditor(editor);
|
UserDataManager.unInitializeEditor(editor);
|
||||||
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
|
VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
|
||||||
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
|
CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
|
||||||
@ -322,17 +314,18 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
private static class RelativeLineNumberConverter implements LineNumberConverter {
|
private static class RelativeLineNumberConverter implements LineNumberConverter {
|
||||||
@Override
|
@Override
|
||||||
public Integer convert(@NotNull Editor editor, int lineNumber) {
|
public Integer convert(@NotNull Editor editor, int lineNumber) {
|
||||||
final boolean number = options(injector, new IjVimEditor(editor)).getNumber();
|
final IjVimEditor ijVimEditor = new IjVimEditor(editor);
|
||||||
|
final boolean number = ijOptions(injector, ijVimEditor).getNumber();
|
||||||
final int caretLine = editor.getCaretModel().getLogicalPosition().line;
|
final int caretLine = editor.getCaretModel().getLogicalPosition().line;
|
||||||
|
|
||||||
// lineNumber is 1 based
|
// lineNumber is 1 based
|
||||||
if (number && (lineNumber - 1) == caretLine) {
|
if ((lineNumber - 1) == caretLine) {
|
||||||
return lineNumber;
|
return number ? lineNumber : 0;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
final int visualLine = new IjVimEditor(editor).bufferLineToVisualLine(lineNumber - 1);
|
final int visualLine = ijVimEditor.bufferLineToVisualLine(lineNumber - 1);
|
||||||
final int currentVisualLine = new IjVimEditor(editor).bufferLineToVisualLine(caretLine);
|
final int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
|
||||||
return Math.abs(currentVisualLine - visualLine);
|
return Math.abs(caretVisualLine - visualLine);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,16 +364,22 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
|
|||||||
private Stream<Editor> getLocalEditors() {
|
private Stream<Editor> getLocalEditors() {
|
||||||
// Always fetch local editors. If we're hosting a Code With Me session, any connected guests will create hidden
|
// Always fetch local editors. If we're hosting a Code With Me session, any connected guests will create hidden
|
||||||
// editors to handle syntax highlighting, completion requests, etc. We need to make sure that IdeaVim only makes
|
// editors to handle syntax highlighting, completion requests, etc. We need to make sure that IdeaVim only makes
|
||||||
// changes (e.g. adding search highlights) to local editors, so things don't incorrectly flow through to any Clients.
|
// changes (e.g., adding search highlights) to local editors so things don't incorrectly flow through to any Clients.
|
||||||
// In non-CWM scenarios, or if IdeaVim is installed on the Client, there are only ever local editors, so this will
|
// In non-CWM scenarios, or if IdeaVim is installed on the Client, there are only ever local editors, so this will
|
||||||
// also work there. In Gateway remote development scenarios, IdeaVim should not be installed on the host, only the
|
// also work there. In Gateway remote development scenarios, IdeaVim should not be installed on the host, only the
|
||||||
// Client, so all should work there too.
|
// Client, so all should work there too.
|
||||||
// Note that most IdeaVim operations are in response to interactive keystrokes, which would mean that
|
// Note that most IdeaVim operations are in response to interactive keystrokes, which would mean that
|
||||||
// ClientEditorManager.getCurrentInstance would return local editors. However, some operations are in response to
|
// ClientEditorManager.getCurrentInstance would return local editors. However, some operations are in response to
|
||||||
// events such as document change (to update search highlights) and these can come from CWM guests, and we'd get the
|
// events such as document change (to update search highlights), and these can come from CWM guests, and we'd get
|
||||||
// remote editors.
|
// the remote editors.
|
||||||
// This invocation will always get local editors, regardless of current context.
|
// This invocation will always get local editors, regardless of the current context.
|
||||||
final ClientAppSession localSession = ClientSessionsManager.getAppSessions(ClientKind.LOCAL).get(0);
|
List<ClientAppSession> appSessions = ClientSessionsManager.getAppSessions(ClientKind.ALL);
|
||||||
return localSession.getService(ClientEditorManager.class).editors();
|
if (!appSessions.isEmpty()) {
|
||||||
|
ClientAppSession localSession = appSessions.get(0);
|
||||||
|
return localSession.getService(ClientEditorManager.class).editors();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return Stream.empty();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,18 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
|
|||||||
* As a convenience, this class also provides access to the IntelliJ specific global options, via inheritance.
|
* As a convenience, this class also provides access to the IntelliJ specific global options, via inheritance.
|
||||||
*/
|
*/
|
||||||
public class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) {
|
public class EffectiveIjOptions(scope: OptionAccessScope.EFFECTIVE): GlobalIjOptions(scope) {
|
||||||
|
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
|
||||||
|
public var breakindent: Boolean by optionProperty(IjOptions.breakindent)
|
||||||
|
public val colorcolumn: StringListOptionValue by optionProperty(IjOptions.colorcolumn)
|
||||||
|
public var cursorline: Boolean by optionProperty(IjOptions.cursorline)
|
||||||
|
public var fileformat: String by optionProperty(IjOptions.fileformat)
|
||||||
|
public var list: Boolean by optionProperty(IjOptions.list)
|
||||||
|
public var number: Boolean by optionProperty(IjOptions.number)
|
||||||
|
public var relativenumber: Boolean by optionProperty(IjOptions.relativenumber)
|
||||||
|
public var textwidth: Int by optionProperty(IjOptions.textwidth)
|
||||||
|
public var wrap: Boolean by optionProperty(IjOptions.wrap)
|
||||||
|
|
||||||
|
// IntelliJ specific options
|
||||||
public var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
|
public var ideacopypreprocess: Boolean by optionProperty(IjOptions.ideacopypreprocess)
|
||||||
public var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
|
public var ideajoin: Boolean by optionProperty(IjOptions.ideajoin)
|
||||||
public var idearefactormode: String by optionProperty(IjOptions.idearefactormode)
|
public var idearefactormode: String by optionProperty(IjOptions.idearefactormode)
|
||||||
|
@ -10,9 +10,14 @@ package com.maddyhome.idea.vim.group
|
|||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationNamesInfo
|
import com.intellij.openapi.application.ApplicationNamesInfo
|
||||||
import com.maddyhome.idea.vim.api.Options
|
import com.maddyhome.idea.vim.api.Options
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.ex.exExceptionMessage
|
||||||
|
import com.maddyhome.idea.vim.options.NumberOption
|
||||||
import com.maddyhome.idea.vim.options.Option
|
import com.maddyhome.idea.vim.options.Option
|
||||||
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL
|
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL
|
||||||
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER
|
import com.maddyhome.idea.vim.options.OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER
|
||||||
|
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_BUFFER
|
||||||
|
import com.maddyhome.idea.vim.options.OptionDeclaredScope.LOCAL_TO_WINDOW
|
||||||
import com.maddyhome.idea.vim.options.StringListOption
|
import com.maddyhome.idea.vim.options.StringListOption
|
||||||
import com.maddyhome.idea.vim.options.StringOption
|
import com.maddyhome.idea.vim.options.StringOption
|
||||||
import com.maddyhome.idea.vim.options.ToggleOption
|
import com.maddyhome.idea.vim.options.ToggleOption
|
||||||
@ -33,6 +38,56 @@ public object IjOptions {
|
|||||||
Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
|
Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vim options that are implemented purely by existing IntelliJ features and not used by vim-engine
|
||||||
|
public val breakindent: ToggleOption = addOption(ToggleOption("breakindent", LOCAL_TO_WINDOW, "bri", false))
|
||||||
|
public val colorcolumn: StringListOption = addOption(object : StringListOption("colorcolumn", LOCAL_TO_WINDOW, "cc", "") {
|
||||||
|
override fun checkIfValueValid(value: VimDataType, token: String) {
|
||||||
|
super.checkIfValueValid(value, token)
|
||||||
|
if (value != VimString.EMPTY) {
|
||||||
|
// Each element in the comma-separated string list needs to be a number. No spaces. Vim supports numbers
|
||||||
|
// beginning "+" or "-" to draw a highlight column relative to the 'textwidth' value. We don't fully support
|
||||||
|
// that, but we do automatically add "+0" because IntelliJ always displays the right margin
|
||||||
|
split((value as VimString).asString()).forEach {
|
||||||
|
if (!it.matches(Regex("[+-]?[0-9]+"))) {
|
||||||
|
throw exExceptionMessage("E474", token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
public val cursorline: ToggleOption = addOption(ToggleOption("cursorline", LOCAL_TO_WINDOW, "cul", false))
|
||||||
|
public val list: ToggleOption = addOption(ToggleOption("list", LOCAL_TO_WINDOW, "list", false))
|
||||||
|
public val number: ToggleOption = addOption(ToggleOption("number", LOCAL_TO_WINDOW, "nu", false))
|
||||||
|
public val relativenumber: ToggleOption = addOption(ToggleOption("relativenumber", LOCAL_TO_WINDOW, "rnu", false))
|
||||||
|
public val textwidth: NumberOption = addOption(UnsignedNumberOption("textwidth", LOCAL_TO_BUFFER, "tw", 0))
|
||||||
|
public val wrap: ToggleOption = addOption(ToggleOption("wrap", LOCAL_TO_WINDOW, "wrap", true))
|
||||||
|
|
||||||
|
// These options are not explicitly listed as local-noglobal in Vim's help, but are set when a new buffer is edited,
|
||||||
|
// based on the value of 'fileformats' or 'fileencodings'. To prevent unexpected file cnversion, we treat them as
|
||||||
|
// local-noglobal. See `:help local-noglobal`, `:help 'fileformats'` and `:help 'fileencodings'`
|
||||||
|
public val bomb: ToggleOption =
|
||||||
|
addOption(ToggleOption("bomb", LOCAL_TO_BUFFER, "bomb", false, isLocalNoGlobal = true))
|
||||||
|
public val fileencoding: StringOption = addOption(
|
||||||
|
StringOption(
|
||||||
|
"fileencoding",
|
||||||
|
LOCAL_TO_BUFFER,
|
||||||
|
"fenc",
|
||||||
|
VimString.EMPTY,
|
||||||
|
isLocalNoGlobal = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
public val fileformat: StringOption = addOption(
|
||||||
|
StringOption(
|
||||||
|
"fileformat",
|
||||||
|
LOCAL_TO_BUFFER,
|
||||||
|
"ff",
|
||||||
|
if (injector.systemInfoService.isWindows) "dos" else "unix",
|
||||||
|
boundedValues = setOf("dos", "unix", "mac"),
|
||||||
|
isLocalNoGlobal = true
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// IntelliJ specific functionality - custom options
|
||||||
public val ide: StringOption = addOption(
|
public val ide: StringOption = addOption(
|
||||||
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
|
StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
|
||||||
)
|
)
|
||||||
@ -85,7 +140,7 @@ public object IjOptions {
|
|||||||
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
|
public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
|
||||||
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
|
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
|
||||||
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
|
public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
|
||||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isHidden = true))
|
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
|
||||||
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
|
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
|
||||||
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
|
public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
|
||||||
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
|
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = true))
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.group
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.event.DocumentEvent
|
||||||
|
import com.intellij.openapi.editor.event.DocumentListener
|
||||||
|
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.VimRedrawService
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
|
||||||
|
public class IjVimRedrawService : VimRedrawService {
|
||||||
|
override fun redraw() {
|
||||||
|
// The only thing IntelliJ needs to redraw is the status line. Everything else is handled automatically.
|
||||||
|
redrawStatusLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun redrawStatusLine() {
|
||||||
|
injector.messages.clearStatusBarMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
/**
|
||||||
|
* Simulate Vim's redraw when the current editor changes
|
||||||
|
*/
|
||||||
|
public fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||||
|
injector.redrawService.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simulates Vim's redraw when the document changes
|
||||||
|
*
|
||||||
|
* Only redraw if lines are added/removed.
|
||||||
|
*/
|
||||||
|
public object RedrawListener : DocumentListener {
|
||||||
|
override fun documentChanged(event: DocumentEvent) {
|
||||||
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
if (event.newFragment.contains("\n") || event.oldFragment.contains("\n")) {
|
||||||
|
injector.redrawService.redraw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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.helper.MessageHelper.message
|
||||||
import com.maddyhome.idea.vim.macro.VimMacroBase
|
import com.maddyhome.idea.vim.macro.VimMacroBase
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to handle playback of macros
|
* Used to handle playback of macros
|
||||||
@ -93,6 +94,9 @@ internal class MacroGroup : VimMacroBase() {
|
|||||||
} finally {
|
} finally {
|
||||||
keyStack.removeFirst()
|
keyStack.removeFirst()
|
||||||
}
|
}
|
||||||
|
if (!isInternalMacro) {
|
||||||
|
MacroAutoImport.run(editor.ij, context.ij)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternalMacro) {
|
if (isInternalMacro) {
|
||||||
|
@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.api.VimMotionGroupBase
|
|||||||
import com.maddyhome.idea.vim.api.anyNonWhitespace
|
import com.maddyhome.idea.vim.api.anyNonWhitespace
|
||||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||||
import com.maddyhome.idea.vim.api.getVisualLineCount
|
import com.maddyhome.idea.vim.api.getVisualLineCount
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.api.lineLength
|
import com.maddyhome.idea.vim.api.lineLength
|
||||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
||||||
import com.maddyhome.idea.vim.api.normalizeVisualLine
|
import com.maddyhome.idea.vim.api.normalizeVisualLine
|
||||||
@ -46,13 +47,12 @@ import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
|
|||||||
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
|
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
|
||||||
import com.maddyhome.idea.vim.helper.isEndAllowed
|
import com.maddyhome.idea.vim.helper.isEndAllowed
|
||||||
import com.maddyhome.idea.vim.helper.vimLastColumn
|
import com.maddyhome.idea.vim.helper.vimLastColumn
|
||||||
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.listener.AppCodeTemplates
|
import com.maddyhome.idea.vim.listener.AppCodeTemplates
|
||||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
|
||||||
import org.jetbrains.annotations.Range
|
import org.jetbrains.annotations.Range
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@ -302,16 +302,21 @@ internal class MotionGroup : VimMotionGroupBase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
|
||||||
ExEntryPanel.deactivateAll()
|
|
||||||
val fileEditor = event.oldEditor
|
val fileEditor = event.oldEditor
|
||||||
if (fileEditor is TextEditor) {
|
if (fileEditor is TextEditor) {
|
||||||
val editor = fileEditor.editor
|
val editor = fileEditor.editor
|
||||||
if (!editor.isDisposed) {
|
if (!editor.isDisposed) {
|
||||||
ExOutputModel.getInstance(editor).clear()
|
|
||||||
editor.vim.let { vimEditor ->
|
editor.vim.let { vimEditor ->
|
||||||
if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
|
when (vimEditor.vimStateMachine.mode) {
|
||||||
vimEditor.exitVisualMode()
|
is Mode.VISUAL -> {
|
||||||
KeyHandler.getInstance().reset(vimEditor)
|
vimEditor.exitVisualMode()
|
||||||
|
KeyHandler.getInstance().reset(vimEditor)
|
||||||
|
}
|
||||||
|
is Mode.CMD_LINE -> {
|
||||||
|
injector.processGroup.cancelExEntry(vimEditor, false)
|
||||||
|
ExOutputModel.getInstance(editor).clear()
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -12,7 +12,6 @@ import com.intellij.execution.configurations.GeneralCommandLine
|
|||||||
import com.intellij.execution.process.CapturingProcessHandler
|
import com.intellij.execution.process.CapturingProcessHandler
|
||||||
import com.intellij.execution.process.ProcessAdapter
|
import com.intellij.execution.process.ProcessAdapter
|
||||||
import com.intellij.execution.process.ProcessEvent
|
import com.intellij.execution.process.ProcessEvent
|
||||||
import com.intellij.openapi.diagnostic.debug
|
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||||
@ -28,25 +27,20 @@ import com.maddyhome.idea.vim.api.VimProcessGroupBase
|
|||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.Command
|
import com.maddyhome.idea.vim.command.Command
|
||||||
import com.maddyhome.idea.vim.ex.ExException
|
|
||||||
import com.maddyhome.idea.vim.ex.InvalidCommandException
|
|
||||||
import com.maddyhome.idea.vim.helper.requestFocus
|
import com.maddyhome.idea.vim.helper.requestFocus
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
|
||||||
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
|
||||||
import java.io.BufferedWriter
|
import java.io.BufferedWriter
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.io.OutputStreamWriter
|
import java.io.OutputStreamWriter
|
||||||
import java.io.Reader
|
import java.io.Reader
|
||||||
import java.io.Writer
|
import java.io.Writer
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
import javax.swing.SwingUtilities
|
|
||||||
|
|
||||||
public class ProcessGroup : VimProcessGroupBase() {
|
public class ProcessGroup : VimProcessGroupBase() {
|
||||||
override var lastCommand: String? = null
|
override var lastCommand: String? = null
|
||||||
@ -54,25 +48,13 @@ public class ProcessGroup : VimProcessGroupBase() {
|
|||||||
override var isCommandProcessing: Boolean = false
|
override var isCommandProcessing: Boolean = false
|
||||||
override var modeBeforeCommandProcessing: Mode? = null
|
override var modeBeforeCommandProcessing: Mode? = null
|
||||||
|
|
||||||
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
|
public override fun startExEntry(
|
||||||
// Don't allow searching in one line editors
|
editor: VimEditor,
|
||||||
if (editor.isOneLineMode()) return
|
context: ExecutionContext,
|
||||||
|
command: Command,
|
||||||
val initText = ""
|
label: String,
|
||||||
val label = leader.toString()
|
initialCommandText: String,
|
||||||
|
) {
|
||||||
val panel = ExEntryPanel.getInstance()
|
|
||||||
panel.activate(editor.ij, context.ij, label, initText, count)
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun endSearchCommand(): String {
|
|
||||||
val panel = ExEntryPanel.getInstance()
|
|
||||||
panel.deactivate(true)
|
|
||||||
|
|
||||||
return panel.text
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
|
||||||
// Don't allow ex commands in one line editors
|
// Don't allow ex commands in one line editors
|
||||||
if (editor.isOneLineMode()) return
|
if (editor.isOneLineMode()) return
|
||||||
|
|
||||||
@ -83,11 +65,24 @@ public class ProcessGroup : VimProcessGroupBase() {
|
|||||||
|
|
||||||
isCommandProcessing = true
|
isCommandProcessing = true
|
||||||
modeBeforeCommandProcessing = currentMode
|
modeBeforeCommandProcessing = currentMode
|
||||||
val initText = getRange(editor, cmd)
|
|
||||||
|
// Make sure the Visual selection marks are up to date before we use them.
|
||||||
injector.markService.setVisualSelectionMarks(editor)
|
injector.markService.setVisualSelectionMarks(editor)
|
||||||
|
|
||||||
|
val rangeText = getRange(editor, command)
|
||||||
|
|
||||||
|
// Note that we should remove selection and reset caret offset before we switch back to Normal mode and then enter
|
||||||
|
// Command-line mode. However, some IdeaVim commands can handle multiple carets, including multiple carets with
|
||||||
|
// selection (which might or might not be a block selection). Unfortunately, because we're just entering
|
||||||
|
// Command-line mode, we don't know which command is going to be entered, so we can't remove selection here.
|
||||||
|
// Therefore, we switch to Normal and then Command-line even though we might still have a Visual selection...
|
||||||
|
// On the plus side, it means we still show selection while editing the command line, which Vim also does
|
||||||
|
// (Normal then Command-line is not strictly necessary, but done for completeness and autocmd)
|
||||||
|
// Caret selection is finally handled in Command.execute
|
||||||
|
editor.mode = Mode.NORMAL()
|
||||||
editor.mode = Mode.CMD_LINE(currentMode)
|
editor.mode = Mode.CMD_LINE(currentMode)
|
||||||
val panel = ExEntryPanel.getInstance()
|
|
||||||
panel.activate(editor.ij, context.ij, ":", initText, 1)
|
injector.commandLine.create(editor, context, ":", rangeText + initialCommandText, 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
|
public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): Boolean {
|
||||||
@ -103,86 +98,27 @@ public class ProcessGroup : VimProcessGroupBase() {
|
|||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
|
||||||
lambdaEditor.mode = NORMAL()
|
lambdaEditor.mode = Mode.NORMAL()
|
||||||
getInstance().reset(lambdaEditor)
|
getInstance().reset(lambdaEditor)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean {
|
|
||||||
val panel = ExEntryPanel.getInstance()
|
|
||||||
panel.deactivate(true)
|
|
||||||
var res = true
|
|
||||||
try {
|
|
||||||
editor.mode = NORMAL()
|
|
||||||
|
|
||||||
logger.debug("processing command")
|
|
||||||
|
|
||||||
val text = panel.text
|
|
||||||
|
|
||||||
if (panel.label != ":") {
|
|
||||||
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
|
|
||||||
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
|
|
||||||
// We should never be invoked for anything other than an actual ex command.
|
|
||||||
throw InvalidCommandException("Expected ':' command. Got '" + panel.label + "'", text)
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug {
|
|
||||||
"swing=" + SwingUtilities.isEventDispatchThread()
|
|
||||||
}
|
|
||||||
|
|
||||||
injector.vimscriptExecutor.execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext)
|
|
||||||
} catch (e: ExException) {
|
|
||||||
VimPlugin.showMessage(e.message)
|
|
||||||
VimPlugin.indicateError()
|
|
||||||
res = false
|
|
||||||
} catch (bad: Exception) {
|
|
||||||
logger.error(bad)
|
|
||||||
VimPlugin.indicateError()
|
|
||||||
res = false
|
|
||||||
} finally {
|
|
||||||
isCommandProcessing = false
|
|
||||||
modeBeforeCommandProcessing = null
|
|
||||||
}
|
|
||||||
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
// commands executed from map command / macro should not be added to history
|
|
||||||
private fun skipHistory(editor: VimEditor): Boolean {
|
|
||||||
return getInstance(editor).mappingState.isExecutingMap() || injector.macro.isExecutingMacro
|
|
||||||
}
|
|
||||||
|
|
||||||
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
|
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
|
||||||
editor.mode = NORMAL()
|
// If 'cpoptions' contains 'x', then Escape should execute the command line. This is the default for Vi but not Vim.
|
||||||
|
// IdeaVim does not (currently?) support 'cpoptions', so sticks with Vim's default behaviour. Escape cancels.
|
||||||
|
editor.mode = editor.mode.returnTo()
|
||||||
getInstance().reset(editor)
|
getInstance().reset(editor)
|
||||||
val panel = ExEntryPanel.getInstance()
|
val panel = ExEntryPanel.getInstance()
|
||||||
panel.deactivate(true, resetCaret)
|
panel.deactivate(true, resetCaret)
|
||||||
}
|
}
|
||||||
|
|
||||||
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
private fun getRange(editor: VimEditor, cmd: Command) = when {
|
||||||
val initText = getRange(editor, cmd) + "!"
|
editor.inVisualMode -> "'<,'>"
|
||||||
val currentMode = editor.mode
|
cmd.rawCount == 1 -> "."
|
||||||
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
|
cmd.rawCount > 1 -> ".,.+" + (cmd.count - 1)
|
||||||
editor.mode = Mode.CMD_LINE(currentMode)
|
else -> ""
|
||||||
val panel = ExEntryPanel.getInstance()
|
|
||||||
panel.activate(editor.ij, context.ij, ":", initText, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getRange(editor: VimEditor, cmd: Command): String {
|
|
||||||
var initText = ""
|
|
||||||
if (editor.vimStateMachine.mode is VISUAL) {
|
|
||||||
initText = "'<,'>"
|
|
||||||
} else if (cmd.rawCount > 0) {
|
|
||||||
initText = if (cmd.count == 1) {
|
|
||||||
"."
|
|
||||||
} else {
|
|
||||||
".,.+" + (cmd.count - 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return initText
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Throws(ExecutionException::class, ProcessCanceledException::class)
|
@Throws(ExecutionException::class, ProcessCanceledException::class)
|
||||||
|
@ -24,7 +24,6 @@ import com.maddyhome.idea.vim.helper.ScrollViewHelper
|
|||||||
import com.maddyhome.idea.vim.helper.StrictMode
|
import com.maddyhome.idea.vim.helper.StrictMode
|
||||||
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
|
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
|
||||||
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
|
import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
|
||||||
import com.maddyhome.idea.vim.helper.vimEditorGroup
|
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
|
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
|
||||||
@ -258,11 +257,7 @@ internal class ScrollGroup : VimScrollGroup {
|
|||||||
|
|
||||||
object ScrollOptionsChangeListener : EffectiveOptionValueChangeListener {
|
object ScrollOptionsChangeListener : EffectiveOptionValueChangeListener {
|
||||||
override fun onEffectiveValueChanged(editor: VimEditor) {
|
override fun onEffectiveValueChanged(editor: VimEditor) {
|
||||||
editor.ij.apply {
|
editor.ij.apply { ScrollViewHelper.scrollCaretIntoView(this) }
|
||||||
if (vimEditorGroup) {
|
|
||||||
ScrollViewHelper.scrollCaretIntoView(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
|
|||||||
import com.intellij.openapi.util.Ref;
|
import com.intellij.openapi.util.Ref;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.*;
|
import com.maddyhome.idea.vim.api.*;
|
||||||
|
import com.maddyhome.idea.vim.command.MotionType;
|
||||||
import com.maddyhome.idea.vim.common.CharacterPosition;
|
import com.maddyhome.idea.vim.common.CharacterPosition;
|
||||||
import com.maddyhome.idea.vim.common.Direction;
|
import com.maddyhome.idea.vim.common.Direction;
|
||||||
import com.maddyhome.idea.vim.common.TextRange;
|
import com.maddyhome.idea.vim.common.TextRange;
|
||||||
@ -60,23 +61,19 @@ import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
|
|||||||
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
|
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
|
||||||
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
|
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Replace with IjVimSearchGroup
|
||||||
|
*/
|
||||||
@State(name = "VimSearchSettings", storages = {
|
@State(name = "VimSearchSettings", storages = {
|
||||||
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
|
@Storage(value = "$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)
|
||||||
})
|
})
|
||||||
@Deprecated
|
@Deprecated
|
||||||
/**
|
|
||||||
* @deprecated Replace with IjVimSearchGroup
|
|
||||||
*/
|
|
||||||
public class SearchGroup extends IjVimSearchGroup implements PersistentStateComponent<Element> {
|
public class SearchGroup extends IjVimSearchGroup implements PersistentStateComponent<Element> {
|
||||||
public SearchGroup() {
|
public SearchGroup() {
|
||||||
super();
|
super();
|
||||||
if (!globalIjOptions(injector).getUseNewRegex()) {
|
if (!globalIjOptions(injector).getUseNewRegex()) {
|
||||||
// TODO: Investigate migrating these listeners to use the effective value change listener
|
// We use the global option listener instead of the effective listener that gets called for each affected editor
|
||||||
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
|
// because we handle updating the affected editors ourselves (e.g., we can filter for visible windows).
|
||||||
// the highlights in that project's current document's open editors (see VIM-2779).
|
|
||||||
// However, we probably only want to update the editors associated with the current document, so maybe the whole
|
|
||||||
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
|
|
||||||
// changes in the search options.
|
|
||||||
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch, () -> {
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch, () -> {
|
||||||
resetShowSearchHighlight();
|
resetShowSearchHighlight();
|
||||||
forceUpdateSearchHighlights();
|
forceUpdateSearchHighlights();
|
||||||
@ -150,11 +147,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
@Override
|
@Override
|
||||||
protected @Nullable String getLastUsedPattern() {
|
protected @Nullable String getLastUsedPattern() {
|
||||||
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastUsedPattern();
|
if (globalIjOptions(injector).getUseNewRegex()) return super.getLastUsedPattern();
|
||||||
switch (lastPatternIdx) {
|
return switch (lastPatternIdx) {
|
||||||
case RE_SEARCH: return lastSearch;
|
case RE_SEARCH -> lastSearch;
|
||||||
case RE_SUBST: return lastSubstitute;
|
case RE_SUBST -> lastSubstitute;
|
||||||
}
|
default -> null;
|
||||||
return null;
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -210,8 +207,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
* @param patternOffset The pattern offset, e.g. `/{pattern}/{offset}`
|
||||||
* @param direction The direction to search
|
* @param direction The direction to search
|
||||||
*/
|
*/
|
||||||
@TestOnly
|
@Override
|
||||||
public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
|
public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
|
||||||
@NotNull String patternOffset, Direction direction) {
|
@NotNull String patternOffset, Direction direction) {
|
||||||
if (globalIjOptions(injector).getUseNewRegex()) {
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
super.setLastSearchState(pattern, patternOffset, direction);
|
super.setLastSearchState(pattern, patternOffset, direction);
|
||||||
@ -272,11 +269,18 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
* Can include a trailing offset, e.g. /{pattern}/{offset}, or multiple commands separated by a semicolon.
|
* Can include a trailing offset, e.g. /{pattern}/{offset}, or multiple commands separated by a semicolon.
|
||||||
* If the pattern is empty, the last used (search? substitute?) pattern (and offset?) is used.
|
* If the pattern is empty, the last used (search? substitute?) pattern (and offset?) is used.
|
||||||
* @param dir The direction to search
|
* @param dir The direction to search
|
||||||
* @return Offset to the next occurrence of the pattern or -1 if not found
|
* @return Pair containing the offset to the next occurrence of the pattern, and the [MotionType] based on
|
||||||
|
* the search offset. The value will be `null` if no result is found.
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public int processSearchCommand(@NotNull VimEditor editor, @NotNull String command, int startOffset, @NotNull Direction dir) {
|
public Pair<Integer, MotionType> processSearchCommand(@NotNull VimEditor editor,
|
||||||
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchCommand(editor, command, startOffset, dir);
|
@NotNull String command,
|
||||||
|
int startOffset,
|
||||||
|
int count1,
|
||||||
|
@NotNull Direction dir) {
|
||||||
|
|
||||||
|
if (globalIjOptions(injector).getUseNewRegex()) return super.processSearchCommand(editor, command, startOffset, count1, dir);
|
||||||
|
|
||||||
boolean isNewPattern = false;
|
boolean isNewPattern = false;
|
||||||
String pattern = null;
|
String pattern = null;
|
||||||
@ -284,7 +288,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
|
|
||||||
final char type = dir == Direction.FORWARDS ? '/' : '?';
|
final char type = dir == Direction.FORWARDS ? '/' : '?';
|
||||||
|
|
||||||
if (command.length() > 0) {
|
if (!command.isEmpty()) {
|
||||||
if (command.charAt(0) != type) {
|
if (command.charAt(0) != type) {
|
||||||
CharPointer p = new CharPointer(command);
|
CharPointer p = new CharPointer(command);
|
||||||
CharPointer end = RegExp.skip_regexp(p.ref(0), type, true);
|
CharPointer end = RegExp.skip_regexp(p.ref(0), type, true);
|
||||||
@ -336,17 +340,19 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
// Direction is saved in do_search
|
// Direction is saved in do_search
|
||||||
// IgnoreSmartCase is only ever set for searching words (`*`, `#`, `g*`, etc.) and is reset for all other operations
|
// IgnoreSmartCase is only ever set for searching words (`*`, `#`, `g*`, etc.) and is reset for all other operations
|
||||||
|
|
||||||
if (pattern == null || pattern.length() == 0) {
|
if (pattern == null || pattern.isEmpty()) {
|
||||||
pattern = getLastSearchPattern();
|
pattern = getLastSearchPattern();
|
||||||
patternOffset = lastPatternOffset;
|
if (pattern == null || pattern.isEmpty()) {
|
||||||
if (pattern == null || pattern.length() == 0) {
|
|
||||||
isNewPattern = true;
|
isNewPattern = true;
|
||||||
pattern = getLastSubstitutePattern();
|
pattern = getLastSubstitutePattern();
|
||||||
if (pattern == null || pattern.length() == 0) {
|
if (pattern == null || pattern.isEmpty()) {
|
||||||
VimPlugin.showMessage(MessageHelper.message("e_noprevre"));
|
VimPlugin.showMessage(MessageHelper.message("e_noprevre"));
|
||||||
return -1;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (patternOffset == null || patternOffset.isEmpty()) {
|
||||||
|
patternOffset = lastPatternOffset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the pattern. If it's explicitly entered, or comes from RE_SUBST, isNewPattern is true, and this becomes
|
// Save the pattern. If it's explicitly entered, or comes from RE_SUBST, isNewPattern is true, and this becomes
|
||||||
@ -365,7 +371,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
resetShowSearchHighlight();
|
resetShowSearchHighlight();
|
||||||
forceUpdateSearchHighlights();
|
forceUpdateSearchHighlights();
|
||||||
|
|
||||||
return findItOffset(((IjVimEditor)editor).getEditor(), startOffset, 1, lastDir);
|
return findItOffset(((IjVimEditor)editor).getEditor(), startOffset, count1, lastDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -407,7 +413,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
resetShowSearchHighlight();
|
resetShowSearchHighlight();
|
||||||
forceUpdateSearchHighlights();
|
forceUpdateSearchHighlights();
|
||||||
|
|
||||||
final int result = findItOffset(editor, startOffset, 1, lastDir);
|
final Pair<Integer, MotionType> result = findItOffset(editor, startOffset, 1, lastDir);
|
||||||
|
|
||||||
// Set lastPatternOffset AFTER searching so it doesn't affect the result
|
// Set lastPatternOffset AFTER searching so it doesn't affect the result
|
||||||
lastPatternOffset = patternOffset != 0 ? Integer.toString(patternOffset) : "";
|
lastPatternOffset = patternOffset != 0 ? Integer.toString(patternOffset) : "";
|
||||||
@ -418,7 +424,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
logger.debug("lastDir=" + lastDir);
|
logger.debug("lastDir=" + lastDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result != null ? result.getFirst() : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -463,8 +469,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
resetShowSearchHighlight();
|
resetShowSearchHighlight();
|
||||||
forceUpdateSearchHighlights();
|
forceUpdateSearchHighlights();
|
||||||
|
|
||||||
final int offset = findItOffset(((IjVimEditor)editor).getEditor(), range.getStartOffset(), count, lastDir);
|
final Pair<Integer, MotionType> offsetAndMotion =
|
||||||
return offset == -1 ? range.getStartOffset() : offset;
|
findItOffset(((IjVimEditor)editor).getEditor(), range.getStartOffset(), count, lastDir);
|
||||||
|
return offsetAndMotion == null ? range.getStartOffset() : offsetAndMotion.getFirst();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -507,14 +514,14 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
resetShowSearchHighlight();
|
resetShowSearchHighlight();
|
||||||
updateSearchHighlights();
|
updateSearchHighlights();
|
||||||
final int startOffset = caret.getOffset();
|
final int startOffset = caret.getOffset();
|
||||||
int offset = findItOffset(editor, startOffset, count, dir);
|
Pair<Integer, MotionType> offsetAndMotion = findItOffset(editor, startOffset, count, dir);
|
||||||
if (offset == startOffset) {
|
if (offsetAndMotion != null && offsetAndMotion.getFirst() == startOffset) {
|
||||||
/* Avoid getting stuck on the current cursor position, which can
|
/* Avoid getting stuck on the current cursor position, which can
|
||||||
* happen when an offset is given and the cursor is on the last char
|
* happen when an offset is given and the cursor is on the last char
|
||||||
* in the buffer: Repeat with count + 1. */
|
* in the buffer: Repeat with count + 1. */
|
||||||
offset = findItOffset(editor, startOffset, count + 1, dir);
|
offsetAndMotion = findItOffset(editor, startOffset, count + 1, dir);
|
||||||
}
|
}
|
||||||
return offset;
|
return offsetAndMotion != null ? offsetAndMotion.getFirst() : -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -804,7 +811,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
CharacterPosition startpos = new CharacterPosition(lnum + regmatch.startpos[0].lnum, regmatch.startpos[0].col);
|
CharacterPosition startpos = new CharacterPosition(lnum + regmatch.startpos[0].lnum, regmatch.startpos[0].col);
|
||||||
CharacterPosition endpos = new CharacterPosition(lnum + regmatch.endpos[0].lnum, regmatch.endpos[0].col);
|
CharacterPosition endpos = new CharacterPosition(lnum + regmatch.endpos[0].lnum, regmatch.endpos[0].col);
|
||||||
int startoff = startpos.toOffset(((IjVimEditor)editor).getEditor());
|
int startoff = startpos.toOffset(((IjVimEditor)editor).getEditor());
|
||||||
int endoff = endpos.toOffset(((IjVimEditor)editor).getEditor());
|
int endoff = (endpos.line >= lcount) ? (int) editor.fileSize() : endpos.toOffset(((IjVimEditor)editor).getEditor());
|
||||||
|
|
||||||
if (do_all || line != lastLine) {
|
if (do_all || line != lastLine) {
|
||||||
boolean doReplace = true;
|
boolean doReplace = true;
|
||||||
@ -863,23 +870,23 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
|
|
||||||
lastLine = line;
|
lastLine = line;
|
||||||
|
|
||||||
lnum += nmatch - 1;
|
|
||||||
if (do_all && startoff != endoff) {
|
if (do_all && startoff != endoff) {
|
||||||
if (newpos != null) {
|
if (newpos != null) {
|
||||||
lnum = newpos.line;
|
lnum = newpos.line;
|
||||||
searchcol = newpos.column;
|
searchcol = newpos.column;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
lnum += Math.max(1, nmatch - 1);
|
||||||
searchcol = endpos.column;
|
searchcol = endpos.column;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
lnum += Math.max(1, nmatch - 1);
|
||||||
searchcol = 0;
|
searchcol = 0;
|
||||||
lnum++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
lnum++;
|
lnum += Math.max(1, nmatch - 1);
|
||||||
searchcol = 0;
|
searchcol = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -890,7 +897,8 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, editor.offsetToBufferPosition(lastMatch).getLine()));
|
VimPlugin.getMotion().moveCaretToLineStartSkipLeading(editor, editor.offsetToBufferPosition(lastMatch).getLine()));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
VimPlugin.showMessage(MessageHelper.message(Msg.e_patnotf2, pattern));
|
// E486: Pattern not found: {0}
|
||||||
|
VimPlugin.showMessage(MessageHelper.message("E486", pattern));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -952,17 +960,17 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
if (which_pat == RE_LAST) {
|
if (which_pat == RE_LAST) {
|
||||||
which_pat = lastPatternIdx;
|
which_pat = lastPatternIdx;
|
||||||
}
|
}
|
||||||
String errorMessage = null;
|
String errorMessage = switch (which_pat) {
|
||||||
switch (which_pat) {
|
case RE_SEARCH -> {
|
||||||
case RE_SEARCH:
|
|
||||||
pattern = lastSearch;
|
pattern = lastSearch;
|
||||||
errorMessage = MessageHelper.message("e_nopresub");
|
yield MessageHelper.message("e_nopresub");
|
||||||
break;
|
}
|
||||||
case RE_SUBST:
|
case RE_SUBST -> {
|
||||||
pattern = lastSubstitute;
|
pattern = lastSubstitute;
|
||||||
errorMessage = MessageHelper.message("e_noprevre");
|
yield MessageHelper.message("e_noprevre");
|
||||||
break;
|
}
|
||||||
}
|
default -> null;
|
||||||
|
};
|
||||||
|
|
||||||
// Pattern was never defined
|
// Pattern was never defined
|
||||||
if (pattern == null) {
|
if (pattern == null) {
|
||||||
@ -1212,27 +1220,33 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
final Document document = event.getDocument();
|
final Document document = event.getDocument();
|
||||||
for (VimEditor vimEditor : injector.getEditorGroup().getEditors(new IjVimDocument(document))) {
|
for (VimEditor vimEditor : injector.getEditorGroup().getEditors(new IjVimDocument(document))) {
|
||||||
final Editor editor = ((IjVimEditor)vimEditor).getEditor();
|
final Editor editor = ((IjVimEditor)vimEditor).getEditor();
|
||||||
Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor);
|
Collection<RangeHighlighter> existingHighlighters = UserDataManager.getVimLastHighlighters(editor);
|
||||||
if (hls == null) {
|
if (existingHighlighters == null) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
logger.debug("hls=" + hls);
|
logger.debug("hls=" + existingHighlighters);
|
||||||
logger.debug("event=" + event);
|
logger.debug("event=" + event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can only re-highlight whole lines, so clear any highlights in the affected lines
|
// We can only re-highlight whole lines, so clear any highlights in the affected lines.
|
||||||
|
// If we're deleting lines, this will clear + re-highlight the new current line, which hasn't been modified.
|
||||||
|
// However, we still want to re-highlight this line in case any highlights cross the line boundaries.
|
||||||
|
// If we're adding lines, this will clear + re-highlight all new lines.
|
||||||
final LogicalPosition startPosition = editor.offsetToLogicalPosition(event.getOffset());
|
final LogicalPosition startPosition = editor.offsetToLogicalPosition(event.getOffset());
|
||||||
final LogicalPosition endPosition = editor.offsetToLogicalPosition(event.getOffset() + event.getNewLength());
|
final LogicalPosition endPosition = editor.offsetToLogicalPosition(event.getOffset() + event.getNewLength());
|
||||||
final int startLineOffset = document.getLineStartOffset(startPosition.line);
|
final int startLineOffset = document.getLineStartOffset(startPosition.line);
|
||||||
final int endLineOffset = document.getLineEndOffset(endPosition.line);
|
final int endLineOffset = document.getLineEndOffset(endPosition.line);
|
||||||
|
|
||||||
final Iterator<RangeHighlighter> iter = hls.iterator();
|
// Remove any highlights that have already been deleted, and remove + clear those that intersect with the change
|
||||||
|
final Iterator<RangeHighlighter> iter = existingHighlighters.iterator();
|
||||||
while (iter.hasNext()) {
|
while (iter.hasNext()) {
|
||||||
final RangeHighlighter highlighter = iter.next();
|
final RangeHighlighter highlighter = iter.next();
|
||||||
if (!highlighter.isValid() ||
|
if (!highlighter.isValid()) {
|
||||||
(highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
|
iter.remove();
|
||||||
|
}
|
||||||
|
else if (highlighter.getTextRange().intersects(startLineOffset, endLineOffset)) {
|
||||||
iter.remove();
|
iter.remove();
|
||||||
editor.getMarkupModel().removeHighlighter(highlighter);
|
editor.getMarkupModel().removeHighlighter(highlighter);
|
||||||
}
|
}
|
||||||
@ -1241,9 +1255,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
VimPlugin.getSearch().highlightSearchLines(editor, startPosition.line, endPosition.line);
|
VimPlugin.getSearch().highlightSearchLines(editor, startPosition.line, endPosition.line);
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
if (logger.isDebugEnabled()) {
|
||||||
hls = UserDataManager.getVimLastHighlighters(editor);
|
existingHighlighters = UserDataManager.getVimLastHighlighters(editor);
|
||||||
logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line);
|
logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line);
|
||||||
logger.debug("hls=" + hls);
|
logger.debug("hls=" + existingHighlighters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1269,9 +1283,11 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
* @param startOffset The offset to search from
|
* @param startOffset The offset to search from
|
||||||
* @param count Find the nth occurrence
|
* @param count Find the nth occurrence
|
||||||
* @param dir The direction to search in
|
* @param dir The direction to search in
|
||||||
* @return The offset to the occurrence or -1 if not found
|
* @return Pair containing the offset to the next occurrence of the pattern, and the [MotionType] based
|
||||||
|
* on the search offset. The value will be `null` if no result is found.
|
||||||
*/
|
*/
|
||||||
private int findItOffset(@NotNull Editor editor, int startOffset, int count, Direction dir) {
|
@Nullable
|
||||||
|
private Pair<Integer, MotionType> findItOffset(@NotNull Editor editor, int startOffset, int count, Direction dir) {
|
||||||
boolean wrap = globalOptions(injector).getWrapscan();
|
boolean wrap = globalOptions(injector).getWrapscan();
|
||||||
logger.debug("Perform search. Direction: " + dir + " wrap: " + wrap);
|
logger.debug("Perform search. Direction: " + dir + " wrap: " + wrap);
|
||||||
|
|
||||||
@ -1281,7 +1297,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
|
|
||||||
ParsePosition pp = new ParsePosition(0);
|
ParsePosition pp = new ParsePosition(0);
|
||||||
|
|
||||||
if (lastPatternOffset.length() > 0) {
|
if (!lastPatternOffset.isEmpty()) {
|
||||||
if (Character.isDigit(lastPatternOffset.charAt(0)) || lastPatternOffset.charAt(0) == '+' || lastPatternOffset.charAt(0) == '-') {
|
if (Character.isDigit(lastPatternOffset.charAt(0)) || lastPatternOffset.charAt(0) == '+' || lastPatternOffset.charAt(0) == '-') {
|
||||||
offsetIsLineOffset = true;
|
offsetIsLineOffset = true;
|
||||||
|
|
||||||
@ -1317,6 +1333,18 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// `/{pattern}/{offset}` is inclusive if offset contains `e`, and linewise if there's a line offset
|
||||||
|
final MotionType motionType;
|
||||||
|
if (offset != 0 && !hasEndOffset) {
|
||||||
|
motionType = MotionType.LINE_WISE;
|
||||||
|
}
|
||||||
|
else if (hasEndOffset) {
|
||||||
|
motionType = MotionType.INCLUSIVE;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
motionType = MotionType.EXCLUSIVE;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* If there is a character offset, subtract it from the current
|
* If there is a character offset, subtract it from the current
|
||||||
* position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
|
* position, so we don't get stuck at "?pat?e+2" or "/pat/s-2".
|
||||||
@ -1338,7 +1366,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
TextRange range = injector.getSearchHelper().findPattern(new IjVimEditor(editor), getLastUsedPattern(), startOffset, count, searchOptions);
|
TextRange range = injector.getSearchHelper().findPattern(new IjVimEditor(editor), getLastUsedPattern(), startOffset, count, searchOptions);
|
||||||
if (range == null) {
|
if (range == null) {
|
||||||
logger.warn("No range is found");
|
logger.warn("No range is found");
|
||||||
return -1;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
int res = range.getStartOffset();
|
int res = range.getStartOffset();
|
||||||
@ -1346,8 +1374,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
if (offsetIsLineOffset) {
|
if (offsetIsLineOffset) {
|
||||||
int line = editor.offsetToLogicalPosition(range.getStartOffset()).line;
|
int line = editor.offsetToLogicalPosition(range.getStartOffset()).line;
|
||||||
int newLine = EngineEditorHelperKt.normalizeLine(new IjVimEditor(editor), line + offset);
|
int newLine = EngineEditorHelperKt.normalizeLine(new IjVimEditor(editor), line + offset);
|
||||||
|
|
||||||
// TODO: Don't move the caret!
|
|
||||||
res = VimPlugin.getMotion().moveCaretToLineStart(new IjVimEditor(editor), newLine);
|
res = VimPlugin.getMotion().moveCaretToLineStart(new IjVimEditor(editor), newLine);
|
||||||
}
|
}
|
||||||
else if (hasEndOffset || offset != 0) {
|
else if (hasEndOffset || offset != 0) {
|
||||||
@ -1365,16 +1391,19 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
nextDir = Direction.BACKWARDS;
|
nextDir = Direction.BACKWARDS;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
return res;
|
return new Pair(res, motionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (lastPatternOffset.length() - ppos > 2) {
|
if (lastPatternOffset.length() - ppos > 2) {
|
||||||
ppos++;
|
ppos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
res = processSearchCommand(new IjVimEditor(editor), lastPatternOffset.substring(ppos + 1), res, nextDir);
|
Pair<Integer, MotionType> offsetAndMotion =
|
||||||
|
processSearchCommand(new IjVimEditor(editor), lastPatternOffset.substring(ppos + 1), res, 1, nextDir);
|
||||||
|
res = offsetAndMotion != null ? offsetAndMotion.getFirst() : -1;
|
||||||
}
|
}
|
||||||
return res;
|
|
||||||
|
return new Pair<Integer, MotionType>(res, motionType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -1390,7 +1419,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
|
|||||||
|
|
||||||
addOptionalTextElement(search, "last-search", lastSearch);
|
addOptionalTextElement(search, "last-search", lastSearch);
|
||||||
addOptionalTextElement(search, "last-substitute", lastSubstitute);
|
addOptionalTextElement(search, "last-substitute", lastSubstitute);
|
||||||
addOptionalTextElement(search, "last-offset", lastPatternOffset.length() > 0 ? lastPatternOffset : null);
|
addOptionalTextElement(search, "last-offset", !lastPatternOffset.isEmpty() ? lastPatternOffset : null);
|
||||||
addOptionalTextElement(search, "last-replace", lastReplace);
|
addOptionalTextElement(search, "last-replace", lastReplace);
|
||||||
addOptionalTextElement(search, "last-pattern", lastPatternIdx == RE_SEARCH ? lastSearch : lastSubstitute);
|
addOptionalTextElement(search, "last-pattern", lastPatternIdx == RE_SEARCH ? lastSearch : lastSubstitute);
|
||||||
addOptionalTextElement(search, "last-dir", Integer.toString(lastDir.toInt()));
|
addOptionalTextElement(search, "last-dir", Integer.toString(lastDir.toInt()));
|
||||||
|
@ -26,9 +26,11 @@ import com.maddyhome.idea.vim.listener.VimListenerManager
|
|||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.options.OptionConstants
|
import com.maddyhome.idea.vim.options.OptionConstants
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inCommandLineMode
|
||||||
import com.maddyhome.idea.vim.state.mode.inNormalMode
|
import com.maddyhome.idea.vim.state.mode.inNormalMode
|
||||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||||
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
|
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
|
||||||
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep
|
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep
|
||||||
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
|
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
|
||||||
@ -67,6 +69,11 @@ internal object IdeaSelectionControl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (editor.selectionModel.hasSelection(true)) {
|
if (editor.selectionModel.hasSelection(true)) {
|
||||||
|
if (editor.vim.inCommandLineMode && editor.vim.mode.returnTo().hasVisualSelection) {
|
||||||
|
logger.trace { "Modifying selection while in Command-line mode, most likely incsearch" }
|
||||||
|
return@singleTask
|
||||||
|
}
|
||||||
|
|
||||||
if (dontChangeMode(editor)) {
|
if (dontChangeMode(editor)) {
|
||||||
IdeaRefactorModeHelper.correctSelection(editor)
|
IdeaRefactorModeHelper.correctSelection(editor)
|
||||||
logger.trace { "Selection corrected for refactoring" }
|
logger.trace { "Selection corrected for refactoring" }
|
||||||
|
@ -8,10 +8,12 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.helper;
|
package com.maddyhome.idea.vim.helper;
|
||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
|
||||||
import com.intellij.openapi.editor.*;
|
import com.intellij.openapi.editor.*;
|
||||||
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
||||||
|
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||||
|
import com.intellij.openapi.util.SystemInfo;
|
||||||
|
import com.intellij.openapi.util.registry.Registry;
|
||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import com.intellij.testFramework.LightVirtualFile;
|
import com.intellij.testFramework.LightVirtualFile;
|
||||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
||||||
@ -52,6 +54,10 @@ public class EditorHelper {
|
|||||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||||
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
|
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
|
||||||
scrollingModel.scroll(area.x, verticalOffset);
|
scrollingModel.scroll(area.x, verticalOffset);
|
||||||
|
// Simulate Vim's redraw (essentially to clear messages) if the screen is scrolled
|
||||||
|
if (area.y != verticalOffset && area.y >= 0) {
|
||||||
|
injector.getRedrawService().redraw();
|
||||||
|
}
|
||||||
return scrollingModel.getVisibleAreaOnScrollingFinished().y != area.y;
|
return scrollingModel.getVisibleAreaOnScrollingFinished().y != area.y;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +65,10 @@ public class EditorHelper {
|
|||||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||||
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
|
final Rectangle area = scrollingModel.getVisibleAreaOnScrollingFinished();
|
||||||
scrollingModel.scroll(horizontalOffset, area.y);
|
scrollingModel.scroll(horizontalOffset, area.y);
|
||||||
|
// Simulate Vim's redraw (essentially to clear messages) if the screen is scrolled
|
||||||
|
if (area.x != horizontalOffset && area.x >= 0) {
|
||||||
|
injector.getRedrawService().redraw();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getVisualLineAtTopOfScreen(final @NotNull Editor editor) {
|
public static int getVisualLineAtTopOfScreen(final @NotNull Editor editor) {
|
||||||
@ -77,10 +87,12 @@ public class EditorHelper {
|
|||||||
|
|
||||||
public static int getNonNormalizedVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
|
public static int getNonNormalizedVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
|
||||||
// The editor will return line numbers of virtual space if the text doesn't reach the end of the visible area
|
// The editor will return line numbers of virtual space if the text doesn't reach the end of the visible area
|
||||||
// (either because it's too short, or it's been scrolled up)
|
// (either because it's too short, or it's been scrolled up).
|
||||||
|
// Adjust available height if the ex entry text field is visible
|
||||||
final Rectangle visibleArea = getVisibleArea(editor);
|
final Rectangle visibleArea = getVisibleArea(editor);
|
||||||
return getFullVisualLine(editor, visibleArea.y + visibleArea.height, visibleArea.y,
|
final int height = visibleArea.height - getExEntryHeight() - getHorizontalScrollbarHeight(editor);
|
||||||
visibleArea.y + visibleArea.height);
|
return getFullVisualLine(editor, visibleArea.y + height, visibleArea.y,
|
||||||
|
visibleArea.y + height);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int getVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
|
public static int getVisualLineAtBottomOfScreen(final @NotNull Editor editor) {
|
||||||
@ -323,7 +335,7 @@ public class EditorHelper {
|
|||||||
|
|
||||||
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
|
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
|
||||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
|
||||||
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
||||||
|
|
||||||
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
||||||
@ -355,24 +367,40 @@ public class EditorHelper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static int getOffsetToScrollVisualLineToBottomOfScreen(@NotNull Editor editor, int nonNormalisedVisualLine) {
|
private static int getOffsetToScrollVisualLineToBottomOfScreen(@NotNull Editor editor, int nonNormalisedVisualLine) {
|
||||||
int exPanelHeight = 0;
|
|
||||||
if (ExEntryPanel.getInstance().isActive()) {
|
|
||||||
exPanelHeight = ExEntryPanel.getInstance().getHeight();
|
|
||||||
}
|
|
||||||
if (ExEntryPanel.getInstanceWithoutShortcuts().isActive()) {
|
|
||||||
exPanelHeight += ExEntryPanel.getInstanceWithoutShortcuts().getHeight();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Note that we explicitly do not normalise the visual line, as we might be trying to scroll a virtual line, at the
|
// Note that we explicitly do not normalise the visual line, as we might be trying to scroll a virtual line, at the
|
||||||
// end of the file
|
// end of the file.
|
||||||
|
// Adjust available height if the ex entry text field is visible
|
||||||
final int lineHeight = editor.getLineHeight();
|
final int lineHeight = editor.getLineHeight();
|
||||||
final int screenHeight = getVisibleArea(editor).height - exPanelHeight;
|
final int screenHeight = getVisibleArea(editor).height - getExEntryHeight() - getHorizontalScrollbarHeight(editor);
|
||||||
final int inlayHeight = EditorUtil.getInlaysHeight(editor, nonNormalisedVisualLine, false);
|
final int inlayHeight = EditorUtil.getInlaysHeight(editor, nonNormalisedVisualLine, false);
|
||||||
final int maxInlayHeight = BLOCK_INLAY_MAX_LINE_HEIGHT * lineHeight;
|
final int maxInlayHeight = BLOCK_INLAY_MAX_LINE_HEIGHT * lineHeight;
|
||||||
final int y = editor.visualLineToY(nonNormalisedVisualLine) + lineHeight + min(inlayHeight, maxInlayHeight);
|
final int y = editor.visualLineToY(nonNormalisedVisualLine) + lineHeight + min(inlayHeight, maxInlayHeight);
|
||||||
return max(0, y - screenHeight);
|
return max(0, y - screenHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getExEntryHeight() {
|
||||||
|
if (ExEntryPanel.getInstance().isActive()) {
|
||||||
|
return ExEntryPanel.getInstance().getHeight();
|
||||||
|
}
|
||||||
|
if (ExEntryPanel.getInstanceWithoutShortcuts().isActive()) {
|
||||||
|
return ExEntryPanel.getInstanceWithoutShortcuts().getHeight();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getHorizontalScrollbarHeight(@NotNull final 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
|
||||||
|
// means they can obscure text, so we want to adjust by the scrollbar height. If they are not transparent, then they
|
||||||
|
// are not overlays and are outside the content area. We don't need to adjust
|
||||||
|
if (!SystemInfo.isMac && editor instanceof EditorImpl editorImpl && Registry.is("editor.transparent.scrollbar")) {
|
||||||
|
return editorImpl.getScrollPane().getHorizontalScrollBar().getHeight();
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
public static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) {
|
public static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int visualLine, int visualColumn) {
|
||||||
int targetVisualColumn = visualColumn;
|
int targetVisualColumn = visualColumn;
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.codeWithMe.ClientId
|
import com.intellij.codeWithMe.ClientId
|
||||||
import com.intellij.openapi.editor.Caret
|
import com.intellij.openapi.editor.Caret
|
||||||
|
import com.intellij.openapi.editor.CaretState
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||||
@ -20,6 +21,8 @@ import com.maddyhome.idea.vim.api.StringListOptionValue
|
|||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
@ -96,3 +99,41 @@ internal val Caret.vimLine: Int
|
|||||||
*/
|
*/
|
||||||
internal val Editor.vimLine: Int
|
internal val Editor.vimLine: Int
|
||||||
get() = this.caretModel.currentCaret.vimLine
|
get() = this.caretModel.currentCaret.vimLine
|
||||||
|
|
||||||
|
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
|
||||||
|
val caretModel = this.caretModel
|
||||||
|
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
|
||||||
|
if (carets == null || carets.size == 1) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var initialDocumentSize = this.document.textLength
|
||||||
|
var documentSizeDifference = 0
|
||||||
|
|
||||||
|
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
|
||||||
|
val restoredCarets = mutableListOf<CaretState>()
|
||||||
|
|
||||||
|
caretModel.removeSecondaryCarets()
|
||||||
|
|
||||||
|
for ((selectionStart, selectionEnd) in caretOffsets) {
|
||||||
|
if (selectionStart == selectionEnd) {
|
||||||
|
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
caretModel.primaryCaret.setSelection(
|
||||||
|
selectionStart + documentSizeDifference,
|
||||||
|
selectionEnd + documentSizeDifference
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
action()
|
||||||
|
restoredCarets.add(caretModel.caretsAndSelections.single())
|
||||||
|
|
||||||
|
val documentLength = this.document.textLength
|
||||||
|
documentSizeDifference += documentLength - initialDocumentSize
|
||||||
|
initialDocumentSize = documentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
caretModel.caretsAndSelections = restoredCarets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ import com.intellij.openapi.actionSystem.PlatformDataKeys
|
|||||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
|
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
|
||||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||||
|
import com.intellij.openapi.actionSystem.impl.Utils
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
import com.intellij.openapi.command.UndoConfirmationPolicy
|
import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
@ -86,6 +87,7 @@ internal class IjActionExecutor : VimActionExecutor {
|
|||||||
ActionManager.getInstance(),
|
ActionManager.getInstance(),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
Utils.initUpdateSession(event)
|
||||||
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
|
// beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
|
||||||
// because rider use async update method. See VIM-1819.
|
// because rider use async update method. See VIM-1819.
|
||||||
// This method executes inside of lastUpdateAndCheckDumb
|
// This method executes inside of lastUpdateAndCheckDumb
|
||||||
|
@ -8,6 +8,7 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.VisualPosition
|
import com.intellij.openapi.editor.VisualPosition
|
||||||
|
import com.intellij.openapi.editor.textarea.TextComponentEditor
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.getVisualLineCount
|
import com.maddyhome.idea.vim.api.getVisualLineCount
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
@ -39,6 +40,9 @@ import kotlin.math.roundToInt
|
|||||||
internal object ScrollViewHelper {
|
internal object ScrollViewHelper {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun scrollCaretIntoView(editor: Editor) {
|
fun scrollCaretIntoView(editor: Editor) {
|
||||||
|
// TextComponentEditor doesn't support scrolling. We only support TextComponentEditor for the fallback window
|
||||||
|
if (editor is TextComponentEditor) return
|
||||||
|
|
||||||
val position = editor.caretModel.visualPosition
|
val position = editor.caretModel.visualPosition
|
||||||
scrollCaretIntoViewVertically(editor, position.line)
|
scrollCaretIntoViewVertically(editor, position.line)
|
||||||
scrollCaretIntoViewHorizontally(editor, position)
|
scrollCaretIntoViewHorizontally(editor, position)
|
||||||
@ -56,7 +60,7 @@ internal object ScrollViewHelper {
|
|||||||
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
||||||
val topLine = getVisualLineAtTopOfScreen(editor)
|
val topLine = getVisualLineAtTopOfScreen(editor)
|
||||||
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
||||||
val lastLine = vimEditor.getVisualLineCount() - 1
|
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
|
||||||
|
|
||||||
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
||||||
val scrollOffset = injector.options(vimEditor).scrolloff
|
val scrollOffset = injector.options(vimEditor).scrolloff
|
||||||
|
@ -10,18 +10,10 @@ package com.maddyhome.idea.vim.helper;
|
|||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.collect.Lists;
|
||||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
|
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
|
||||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
|
|
||||||
import com.intellij.lang.Commenter;
|
|
||||||
import com.intellij.lang.Language;
|
|
||||||
import com.intellij.lang.LanguageCommenters;
|
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.editor.Caret;
|
import com.intellij.openapi.editor.Caret;
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.openapi.project.Project;
|
import com.intellij.openapi.project.Project;
|
||||||
import com.intellij.psi.PsiComment;
|
|
||||||
import com.intellij.psi.PsiElement;
|
|
||||||
import com.intellij.psi.PsiFile;
|
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
|
||||||
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
|
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
|
||||||
@ -318,11 +310,17 @@ public class SearchHelper {
|
|||||||
lnum = lineCount - 1;
|
lnum = lineCount - 1;
|
||||||
//if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
|
//if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
|
||||||
// give_warning((char_u *)_(top_bot_msg), TRUE);
|
// give_warning((char_u *)_(top_bot_msg), TRUE);
|
||||||
|
if (searchOptions.contains(SearchOptions.SHOW_MESSAGES)) {
|
||||||
|
VimPlugin.showMessage(MessageHelper.message("message.search.hit.top"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
lnum = 0;
|
lnum = 0;
|
||||||
//if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
|
//if (!shortmess(SHM_SEARCH) && (options & SEARCH_MSG))
|
||||||
// give_warning((char_u *)_(bot_top_msg), TRUE);
|
// give_warning((char_u *)_(bot_top_msg), TRUE);
|
||||||
|
if (searchOptions.contains(SearchOptions.SHOW_MESSAGES)) {
|
||||||
|
VimPlugin.showMessage(MessageHelper.message("message.search.hit.bottom"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//if (got_int || called_emsg || break_loop)
|
//if (got_int || called_emsg || break_loop)
|
||||||
@ -334,12 +332,15 @@ public class SearchHelper {
|
|||||||
//if ((options & SEARCH_MSG) == SEARCH_MSG)
|
//if ((options & SEARCH_MSG) == SEARCH_MSG)
|
||||||
if (searchOptions.contains(SearchOptions.SHOW_MESSAGES)) {
|
if (searchOptions.contains(SearchOptions.SHOW_MESSAGES)) {
|
||||||
if (searchOptions.contains(SearchOptions.WRAP)) {
|
if (searchOptions.contains(SearchOptions.WRAP)) {
|
||||||
VimPlugin.showMessage(MessageHelper.message(Msg.e_patnotf2, pattern));
|
// E486: Pattern not found: {0}
|
||||||
|
VimPlugin.showMessage(MessageHelper.message("E486", pattern));
|
||||||
}
|
}
|
||||||
else if (lnum <= 0) {
|
else if (lnum <= 0) {
|
||||||
|
// E384: Search hit TOP without match for: {0}
|
||||||
VimPlugin.showMessage(MessageHelper.message(Msg.E384, pattern));
|
VimPlugin.showMessage(MessageHelper.message(Msg.E384, pattern));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
// E385: Search hit BOTTOM without match for: {0}
|
||||||
VimPlugin.showMessage(MessageHelper.message(Msg.E385, pattern));
|
VimPlugin.showMessage(MessageHelper.message(Msg.E385, pattern));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -375,7 +376,7 @@ public class SearchHelper {
|
|||||||
final List<TextRange> results = Lists.newArrayList();
|
final List<TextRange> results = Lists.newArrayList();
|
||||||
|
|
||||||
if (globalIjOptions(injector).getUseNewRegex()) {
|
if (globalIjOptions(injector).getUseNewRegex()) {
|
||||||
final List<VimRegexOptions> options = new ArrayList<>();
|
final EnumSet<VimRegexOptions> options = EnumSet.noneOf(VimRegexOptions.class);
|
||||||
if (globalOptions(injector).getSmartcase()) options.add(VimRegexOptions.SMART_CASE);
|
if (globalOptions(injector).getSmartcase()) options.add(VimRegexOptions.SMART_CASE);
|
||||||
if (globalOptions(injector).getIgnorecase()) options.add(VimRegexOptions.IGNORE_CASE);
|
if (globalOptions(injector).getIgnorecase()) options.add(VimRegexOptions.IGNORE_CASE);
|
||||||
VimEditor vimEditor = new IjVimEditor(editor);
|
VimEditor vimEditor = new IjVimEditor(editor);
|
||||||
@ -414,7 +415,7 @@ public class SearchHelper {
|
|||||||
final CharacterPosition endPos = new CharacterPosition(line + regMatch.endpos[0].lnum,
|
final CharacterPosition endPos = new CharacterPosition(line + regMatch.endpos[0].lnum,
|
||||||
regMatch.endpos[0].col);
|
regMatch.endpos[0].col);
|
||||||
int start = startPos.toOffset(editor);
|
int start = startPos.toOffset(editor);
|
||||||
int end = endPos.toOffset(editor);
|
int end = endPos.line >= lineCount ? editor.getDocument().getTextLength() : endPos.toOffset(editor);
|
||||||
results.add(new TextRange(start, end));
|
results.add(new TextRange(start, end));
|
||||||
|
|
||||||
if (start != end) {
|
if (start != end) {
|
||||||
|
@ -12,25 +12,20 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.colors.EditorColors
|
import com.intellij.openapi.editor.colors.EditorColors
|
||||||
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
|
||||||
import com.intellij.openapi.editor.markup.EffectType
|
import com.intellij.openapi.editor.markup.EffectType
|
||||||
import com.intellij.openapi.editor.markup.HighlighterLayer
|
import com.intellij.openapi.editor.markup.HighlighterLayer
|
||||||
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
import com.intellij.openapi.editor.markup.HighlighterTargetArea
|
||||||
import com.intellij.openapi.editor.markup.RangeHighlighter
|
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||||
import com.intellij.openapi.editor.markup.TextAttributes
|
import com.intellij.openapi.editor.markup.TextAttributes
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.intellij.openapi.project.ProjectManager
|
|
||||||
import com.intellij.ui.ColorUtil
|
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.api.options
|
import com.maddyhome.idea.vim.api.options
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimDocument
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import org.jetbrains.annotations.Contract
|
import org.jetbrains.annotations.Contract
|
||||||
import java.awt.Color
|
|
||||||
import java.awt.Font
|
import java.awt.Font
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -40,20 +35,35 @@ internal fun updateSearchHighlights(
|
|||||||
showHighlights: Boolean,
|
showHighlights: Boolean,
|
||||||
forceUpdate: Boolean,
|
forceUpdate: Boolean,
|
||||||
) {
|
) {
|
||||||
updateSearchHighlights(pattern, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate)
|
updateSearchHighlights(null, pattern, 1, shouldIgnoreSmartCase, showHighlights, -1, null, true, forceUpdate)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun updateIncsearchHighlights(
|
internal fun updateIncsearchHighlights(
|
||||||
editor: Editor,
|
editor: Editor,
|
||||||
pattern: String,
|
pattern: String,
|
||||||
|
count1: Int,
|
||||||
forwards: Boolean,
|
forwards: Boolean,
|
||||||
caretOffset: Int,
|
caretOffset: Int,
|
||||||
searchRange: LineRange?,
|
searchRange: LineRange?,
|
||||||
): Int {
|
): Int {
|
||||||
val searchStartOffset =
|
val searchStartOffset = if (searchRange != null && searchRange.startLine < editor.document.lineCount) {
|
||||||
if (searchRange != null) editor.vim.getLineStartOffset(searchRange.startLine) else caretOffset
|
editor.vim.getLineStartOffset(searchRange.startLine)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
caretOffset
|
||||||
|
}
|
||||||
val showHighlights = injector.options(editor.vim).hlsearch
|
val showHighlights = injector.options(editor.vim).hlsearch
|
||||||
return updateSearchHighlights(pattern, false, showHighlights, searchStartOffset, searchRange, forwards, false)
|
return updateSearchHighlights(
|
||||||
|
editor.vim,
|
||||||
|
pattern,
|
||||||
|
count1,
|
||||||
|
false,
|
||||||
|
showHighlights,
|
||||||
|
searchStartOffset,
|
||||||
|
searchRange,
|
||||||
|
forwards,
|
||||||
|
false
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, end: Int): RangeHighlighter {
|
internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, end: Int): RangeHighlighter {
|
||||||
@ -74,10 +84,12 @@ internal fun addSubstitutionConfirmationHighlight(editor: Editor, start: Int, en
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refreshes current search highlights for all editors of currently active text editor/document
|
* Refreshes current search highlights for all visible editors
|
||||||
*/
|
*/
|
||||||
private fun updateSearchHighlights(
|
private fun updateSearchHighlights(
|
||||||
|
currentEditor: VimEditor?,
|
||||||
pattern: String?,
|
pattern: String?,
|
||||||
|
count1: Int,
|
||||||
shouldIgnoreSmartCase: Boolean,
|
shouldIgnoreSmartCase: Boolean,
|
||||||
showHighlights: Boolean,
|
showHighlights: Boolean,
|
||||||
initialOffset: Int,
|
initialOffset: Int,
|
||||||
@ -85,72 +97,82 @@ private fun updateSearchHighlights(
|
|||||||
forwards: Boolean,
|
forwards: Boolean,
|
||||||
forceUpdate: Boolean,
|
forceUpdate: Boolean,
|
||||||
): Int {
|
): Int {
|
||||||
var currentMatchOffset = -1
|
var currentEditorCurrentMatchOffset = -1
|
||||||
val projectManager = ProjectManager.getInstanceIfCreated() ?: return currentMatchOffset
|
|
||||||
|
|
||||||
// TODO: This implementation needs rethinking
|
// Update highlights in all visible editors. We update non-visible editors when they get focus.
|
||||||
// It's a bit weird that we update search highlights across all open projects. It would make more sense to treat top
|
// Note that this now includes all editors - main, diff windows, even toolwindows like the Commit editor and consoles
|
||||||
// level project frame windows as separate applications, but we can't do this because IdeaVim does not maintain state
|
val editors = injector.editorGroup.getEditors().filter {
|
||||||
// per-project.
|
injector.application.isUnitTest() || it.ij.component.isShowing
|
||||||
// So, to be clear, this will loop over each project, and therefore, for each project top-level frame, will update
|
}
|
||||||
// search highlights in all editors for the document of the currently selected editor. It does not update highlights
|
|
||||||
// for editors for the document that are in other projects.
|
|
||||||
|
|
||||||
for (project in projectManager.openProjects) {
|
editors.forEach {
|
||||||
val current = FileEditorManager.getInstance(project).selectedTextEditor ?: continue
|
val editor = it.ij
|
||||||
val editors = injector.editorGroup.getEditors(IjVimDocument(current.document))
|
var currentMatchOffset = -1
|
||||||
for (vimEditor in editors) {
|
|
||||||
val editor = (vimEditor as IjVimEditor).editor
|
|
||||||
if (editor.project != project) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
|
// Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
|
||||||
// Force update for the situations where the text is the same, but the ignore case values have changed.
|
// Force update for the situations where the text is the same, but the ignore case values have changed.
|
||||||
// E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
|
// E.g., Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
|
||||||
// which will match smartcase. Or changing the smartcase/ignorecase settings
|
// which will match smartcase. Or changing the smartcase/ignorecase settings
|
||||||
if (shouldRemoveSearchHighlights(editor, pattern, showHighlights) || forceUpdate) {
|
if (shouldRemoveSearchHighlights(editor, pattern, showHighlights) || forceUpdate) {
|
||||||
removeSearchHighlights(editor)
|
removeSearchHighlights(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pattern == null) continue
|
if (pattern == null) return@forEach
|
||||||
|
|
||||||
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
if (shouldAddAllSearchHighlights(editor, pattern, showHighlights)) {
|
||||||
// hlsearch (+ incsearch/noincsearch)
|
// hlsearch (+ incsearch/noincsearch)
|
||||||
val startLine = searchRange?.startLine ?: 0
|
// Make sure the range fits this editor. Note that Vim will use the same range for all windows. E.g., given
|
||||||
val endLine = searchRange?.endLine ?: -1
|
// `:1,5s/foo`, Vim will highlight all occurrences of `foo` in the first five lines of all visible windows
|
||||||
|
val vimEditor = editor.vim
|
||||||
|
val editorLastLine = vimEditor.lineCount() - 1
|
||||||
|
val searchStartLine = searchRange?.startLine ?: 0
|
||||||
|
val searchEndLine = (searchRange?.endLine ?: -1).coerceAtMost(editorLastLine)
|
||||||
|
if (searchStartLine <= editorLastLine) {
|
||||||
val results =
|
val results =
|
||||||
injector.searchHelper.findAll(IjVimEditor(editor), pattern, startLine, endLine, shouldIgnoreCase(pattern, shouldIgnoreSmartCase))
|
injector.searchHelper.findAll(
|
||||||
|
vimEditor,
|
||||||
|
pattern,
|
||||||
|
searchStartLine,
|
||||||
|
searchEndLine,
|
||||||
|
shouldIgnoreCase(pattern, shouldIgnoreSmartCase)
|
||||||
|
)
|
||||||
if (results.isNotEmpty()) {
|
if (results.isNotEmpty()) {
|
||||||
currentMatchOffset = findClosestMatch(editor, results, initialOffset, forwards)
|
if (editor === currentEditor?.ij) {
|
||||||
|
currentMatchOffset = findClosestMatch(results, initialOffset, count1, forwards)
|
||||||
|
}
|
||||||
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
||||||
}
|
}
|
||||||
editor.vimLastSearch = pattern
|
}
|
||||||
} else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) {
|
editor.vimLastSearch = pattern
|
||||||
// nohlsearch + incsearch
|
} else if (shouldAddCurrentMatchSearchHighlight(pattern, showHighlights, initialOffset)) {
|
||||||
|
// nohlsearch + incsearch. Only highlight the current editor
|
||||||
|
if (editor === currentEditor?.ij) {
|
||||||
val searchOptions = EnumSet.of(SearchOptions.WHOLE_FILE)
|
val searchOptions = EnumSet.of(SearchOptions.WHOLE_FILE)
|
||||||
if (injector.globalOptions().wrapscan) {
|
if (injector.globalOptions().wrapscan) searchOptions.add(SearchOptions.WRAP)
|
||||||
searchOptions.add(SearchOptions.WRAP)
|
|
||||||
}
|
|
||||||
if (shouldIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE)
|
if (shouldIgnoreSmartCase) searchOptions.add(SearchOptions.IGNORE_SMARTCASE)
|
||||||
if (!forwards) searchOptions.add(SearchOptions.BACKWARDS)
|
if (!forwards) searchOptions.add(SearchOptions.BACKWARDS)
|
||||||
val result = injector.searchHelper.findPattern(IjVimEditor(editor), pattern, initialOffset, 1, searchOptions)
|
val result = injector.searchHelper.findPattern(it, pattern, initialOffset, count1, searchOptions)
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
currentMatchOffset = result.startOffset
|
|
||||||
val results = listOf(result)
|
val results = listOf(result)
|
||||||
highlightSearchResults(editor, pattern, results, currentMatchOffset)
|
highlightSearchResults(editor, pattern, results, result.startOffset)
|
||||||
}
|
currentMatchOffset = result.startOffset
|
||||||
} else if (shouldMaintainCurrentMatchOffset(pattern, initialOffset)) {
|
|
||||||
// incsearch. If nothing has changed (e.g. we've edited offset values in `/foo/e+2`) make sure we return the
|
|
||||||
// current match offset so the caret remains at the current incsarch match
|
|
||||||
val offset = editor.vimIncsearchCurrentMatchOffset
|
|
||||||
if (offset != null) {
|
|
||||||
currentMatchOffset = offset
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (shouldMaintainCurrentMatchOffset(pattern, initialOffset)) {
|
||||||
|
// incsearch. If nothing has changed (e.g., we've edited offset values in `/foo/e+2`) make sure we return the
|
||||||
|
// current match offset so the caret remains at the current incsarch match
|
||||||
|
val offset = editor.vimIncsearchCurrentMatchOffset
|
||||||
|
if (offset != null && editor === currentEditor?.ij) {
|
||||||
|
currentMatchOffset = offset
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editor === currentEditor?.ij) {
|
||||||
|
currentEditorCurrentMatchOffset = currentMatchOffset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return currentMatchOffset
|
|
||||||
|
return currentEditorCurrentMatchOffset
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -178,37 +200,22 @@ private fun shouldAddAllSearchHighlights(editor: Editor, newPattern: String?, hl
|
|||||||
return hlSearch && newPattern != null && newPattern != editor.vimLastSearch && newPattern != ""
|
return hlSearch && newPattern != null && newPattern != editor.vimLastSearch && newPattern != ""
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun findClosestMatch(editor: Editor, results: List<TextRange>, initialOffset: Int, forwards: Boolean): Int {
|
private fun findClosestMatch(
|
||||||
|
results: List<TextRange>,
|
||||||
|
initialOffset: Int,
|
||||||
|
count: Int,
|
||||||
|
forwards: Boolean,
|
||||||
|
): Int {
|
||||||
if (results.isEmpty() || initialOffset == -1) {
|
if (results.isEmpty() || initialOffset == -1) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
val size = editor.fileSize
|
|
||||||
val max = Collections.max(results) { r1: TextRange, r2: TextRange ->
|
|
||||||
val d1 = distance(r1, initialOffset, forwards, size)
|
|
||||||
val d2 = distance(r2, initialOffset, forwards, size)
|
|
||||||
if (d1 < 0 && d2 >= 0) {
|
|
||||||
return@max Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
d2 - d1
|
|
||||||
}
|
|
||||||
if (!injector.globalOptions().wrapscan) {
|
|
||||||
val start = max.startOffset
|
|
||||||
if (forwards && start < initialOffset) {
|
|
||||||
return -1
|
|
||||||
} else if (start >= initialOffset) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return max.startOffset
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun distance(range: TextRange, pos: Int, forwards: Boolean, size: Int): Int {
|
val sortedResults = results.sortedBy { it.startOffset }.let { if (!forwards) it.reversed() else it }
|
||||||
val start = range.startOffset
|
val nextIndex = sortedResults.indexOfFirst {
|
||||||
return if (start <= pos) {
|
if (forwards) it.startOffset > initialOffset else it.startOffset < initialOffset
|
||||||
if (forwards) size - pos + start else pos - start
|
|
||||||
} else {
|
|
||||||
if (forwards) start - pos else pos + size - start
|
|
||||||
}
|
}
|
||||||
|
val toDrop = (nextIndex + count - 1).let { if (injector.globalOptions().wrapscan) it % results.size else it }
|
||||||
|
return sortedResults.drop(toDrop).firstOrNull()?.startOffset ?: -1
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun highlightSearchResults(editor: Editor, pattern: String, results: List<TextRange>, currentMatchOffset: Int) {
|
internal fun highlightSearchResults(editor: Editor, pattern: String, results: List<TextRange>, currentMatchOffset: Int) {
|
||||||
@ -226,57 +233,46 @@ internal fun highlightSearchResults(editor: Editor, pattern: String, results: Li
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun highlightMatch(editor: Editor, start: Int, end: Int, current: Boolean, tooltip: String): RangeHighlighter {
|
private fun highlightMatch(editor: Editor, start: Int, end: Int, current: Boolean, tooltip: String): RangeHighlighter {
|
||||||
var attributes = editor.colorsScheme.getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES)
|
val layer = HighlighterLayer.SELECTION - 1
|
||||||
if (current) {
|
val targetArea = HighlighterTargetArea.EXACT_RANGE
|
||||||
// This mimics what IntelliJ does with the Find live preview
|
if (!current) {
|
||||||
attributes = attributes.clone()
|
// If we use a text attribute key, it will update automatically when the editor's colour scheme changes
|
||||||
attributes.effectType = EffectType.ROUNDED_BOX
|
val highlighter =
|
||||||
attributes.effectColor = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
|
editor.markupModel.addRangeHighlighter(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES, start, end, layer, targetArea)
|
||||||
|
highlighter.errorStripeTooltip = tooltip
|
||||||
|
return highlighter
|
||||||
}
|
}
|
||||||
if (attributes.errorStripeColor == null) {
|
|
||||||
attributes.errorStripeColor = getFallbackErrorStripeColor(attributes, editor.colorsScheme)
|
// There isn't a text attribute key for current selection. This means we won't update automatically when the editor's
|
||||||
|
// colour scheme changes. However, this is only used during incsearch, so it should be replaced pretty quickly. It's a
|
||||||
|
// small visual glitch that will fix itself quickly. Let's not bother implementing an editor colour scheme listener
|
||||||
|
// just for this.
|
||||||
|
// These are the same modifications that the Find live preview does. We could look at using LivePreviewPresentation,
|
||||||
|
// which might also be useful for text attributes in selection (if we supported that)
|
||||||
|
val attributes = editor.colorsScheme.getAttributes(EditorColors.TEXT_SEARCH_RESULT_ATTRIBUTES).clone().apply {
|
||||||
|
effectType = EffectType.ROUNDED_BOX
|
||||||
|
effectColor = editor.colorsScheme.getColor(EditorColors.CARET_COLOR)
|
||||||
|
}
|
||||||
|
return editor.markupModel.addRangeHighlighter(start, end, layer, attributes, targetArea).apply {
|
||||||
|
errorStripeTooltip = tooltip
|
||||||
}
|
}
|
||||||
val highlighter = editor.markupModel.addRangeHighlighter(
|
|
||||||
start,
|
|
||||||
end,
|
|
||||||
HighlighterLayer.SELECTION - 1,
|
|
||||||
attributes,
|
|
||||||
HighlighterTargetArea.EXACT_RANGE,
|
|
||||||
)
|
|
||||||
highlighter.errorStripeTooltip = tooltip
|
|
||||||
return highlighter
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a valid error stripe colour based on editor background
|
* Add search highlight for current match if hlsearch is false, and we're performing incsearch highlights
|
||||||
*
|
|
||||||
*
|
|
||||||
* Based on HighlightManager#addRangeHighlight behaviour, which we can't use because it will hide highlights
|
|
||||||
* when hitting Escape.
|
|
||||||
*/
|
|
||||||
private fun getFallbackErrorStripeColor(attributes: TextAttributes, colorsScheme: EditorColorsScheme): Color? {
|
|
||||||
if (attributes.backgroundColor != null) {
|
|
||||||
val isDark = ColorUtil.isDark(colorsScheme.defaultBackground)
|
|
||||||
return if (isDark) attributes.backgroundColor.brighter() else attributes.backgroundColor.darker()
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add search highlight for current match if hlsearch is false and we're performing incsearch highlights
|
|
||||||
*/
|
*/
|
||||||
@Contract("_, true, _ -> false")
|
@Contract("_, true, _ -> false")
|
||||||
private fun shouldAddCurrentMatchSearchHighlight(pattern: String?, hlSearch: Boolean, initialOffset: Int): Boolean {
|
private fun shouldAddCurrentMatchSearchHighlight(pattern: String?, hlSearch: Boolean, initialOffset: Int): Boolean {
|
||||||
return !hlSearch && isIncrementalSearchHighlights(initialOffset) && pattern != null && pattern.isNotEmpty()
|
return !hlSearch && isIncrementalSearchHighlights(initialOffset) && !pattern.isNullOrEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keep the current match offset if the pattern is still valid and we're performing incremental search highlights
|
* Keep the current match offset if the pattern is still valid, and we're performing incremental search highlights
|
||||||
* This will keep the caret position when editing the offset in e.g. `/foo/e+1`
|
* This will keep the caret position when editing the offset in e.g. `/foo/e+1`
|
||||||
*/
|
*/
|
||||||
@Contract("null, _ -> false")
|
@Contract("null, _ -> false")
|
||||||
private fun shouldMaintainCurrentMatchOffset(pattern: String?, initialOffset: Int): Boolean {
|
private fun shouldMaintainCurrentMatchOffset(pattern: String?, initialOffset: Int): Boolean {
|
||||||
return pattern != null && pattern.isNotEmpty() && isIncrementalSearchHighlights(initialOffset)
|
return !pattern.isNullOrEmpty() && isIncrementalSearchHighlights(initialOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,20 +10,21 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||||
import com.intellij.openapi.application.ApplicationInfo
|
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
import com.intellij.openapi.command.undo.UndoManager
|
import com.intellij.openapi.command.undo.UndoManager
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
import com.intellij.openapi.fileEditor.TextEditor
|
import com.intellij.openapi.fileEditor.TextEditor
|
||||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||||
import com.intellij.openapi.util.registry.Registry
|
import com.intellij.openapi.util.registry.Registry
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.ChangesListener
|
import com.maddyhome.idea.vim.common.ChangesListener
|
||||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -40,12 +41,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
val scrollingModel = editor.getScrollingModel()
|
val scrollingModel = editor.getScrollingModel()
|
||||||
scrollingModel.accumulateViewportChanges()
|
scrollingModel.accumulateViewportChanges()
|
||||||
|
|
||||||
// [VERSION UPDATE] 241+ remove this if
|
performUndo(editor, undoManager, fileEditor)
|
||||||
if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
|
|
||||||
undoFor241plus(editor, undoManager, fileEditor)
|
|
||||||
} else {
|
|
||||||
undoForLessThan241(undoManager, fileEditor, editor)
|
|
||||||
}
|
|
||||||
|
|
||||||
scrollingModel.flushViewportChanges()
|
scrollingModel.flushViewportChanges()
|
||||||
|
|
||||||
@ -54,32 +50,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun undoForLessThan241(
|
private fun performUndo(
|
||||||
undoManager: UndoManager,
|
|
||||||
fileEditor: TextEditor,
|
|
||||||
editor: VimEditor,
|
|
||||||
) {
|
|
||||||
if (injector.globalIjOptions().oldundo) {
|
|
||||||
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
|
||||||
} else {
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
private fun undoFor241plus(
|
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
undoManager: UndoManager,
|
undoManager: UndoManager,
|
||||||
fileEditor: TextEditor,
|
fileEditor: TextEditor,
|
||||||
@ -88,15 +59,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||||
editor.runWithChangeTracking {
|
editor.runWithChangeTracking {
|
||||||
undoManager.undo(fileEditor)
|
undoManager.undo(fileEditor)
|
||||||
|
restoreVisualMode(editor)
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
|
||||||
@ -112,54 +75,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
private fun hasSelection(editor: VimEditor): Boolean {
|
private fun hasSelection(editor: VimEditor): Boolean {
|
||||||
return editor.primaryCaret().ij.hasSelection()
|
return editor.primaryCaret().ij.hasSelection()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
|
override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||||
val ijContext = context.context as DataContext
|
val ijContext = context.context as DataContext
|
||||||
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
|
||||||
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
|
val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
|
||||||
val undoManager = UndoManager.getInstance(project)
|
val undoManager = UndoManager.getInstance(project)
|
||||||
if (undoManager.isRedoAvailable(fileEditor)) {
|
if (undoManager.isRedoAvailable(fileEditor)) {
|
||||||
// [VERSION UPDATE] 241+ remove this if
|
performRedo(undoManager, fileEditor, editor)
|
||||||
if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
|
|
||||||
redoFor241Plus(undoManager, fileEditor, editor)
|
|
||||||
} else {
|
|
||||||
redoForLessThan241(undoManager, fileEditor, editor)
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun redoForLessThan241(
|
private fun performRedo(
|
||||||
undoManager: UndoManager,
|
|
||||||
fileEditor: TextEditor,
|
|
||||||
editor: VimEditor,
|
|
||||||
) {
|
|
||||||
if (injector.globalIjOptions().oldundo) {
|
|
||||||
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
|
||||||
} else {
|
|
||||||
undoManager.redo(fileEditor)
|
|
||||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
|
||||||
editor.carets().forEach { it.ij.removeSelection() }
|
|
||||||
}
|
|
||||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
|
||||||
editor.runWithChangeTracking {
|
|
||||||
undoManager.redo(fileEditor)
|
|
||||||
|
|
||||||
// We execute undo one more time if the previous one just restored selection
|
|
||||||
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
|
|
||||||
undoManager.redo(fileEditor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
|
||||||
removeSelections(editor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun redoFor241Plus(
|
|
||||||
undoManager: UndoManager,
|
undoManager: UndoManager,
|
||||||
fileEditor: TextEditor,
|
fileEditor: TextEditor,
|
||||||
editor: VimEditor,
|
editor: VimEditor,
|
||||||
@ -237,4 +167,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
val hasChanges: Boolean
|
val hasChanges: Boolean
|
||||||
get() = changeListener.hasChanged || initialPath != editor.getPath()
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,7 +121,6 @@ internal var Editor.vimIncsearchCurrentMatchOffset: Int? by userData()
|
|||||||
internal var Editor.vimLastSelectionType: SelectionType? by userData()
|
internal var Editor.vimLastSelectionType: SelectionType? by userData()
|
||||||
internal var Editor.vimStateMachine: VimStateMachine? by userData()
|
internal var Editor.vimStateMachine: VimStateMachine? by userData()
|
||||||
internal var Editor.vimEditorGroup: Boolean by userDataOr { false }
|
internal var Editor.vimEditorGroup: Boolean by userDataOr { false }
|
||||||
internal var Editor.vimLineNumbersInitialState: Boolean by userDataOr { false }
|
|
||||||
internal var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false }
|
internal var Editor.vimHasRelativeLineNumbersInstalled: Boolean by userDataOr { false }
|
||||||
internal var Editor.vimMorePanel: ExOutputPanel? by userData()
|
internal var Editor.vimMorePanel: ExOutputPanel? by userData()
|
||||||
internal var Editor.vimExOutput: ExOutputModel? by userData()
|
internal var Editor.vimExOutput: ExOutputModel? by userData()
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2023 The IdeaVim authors
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style
|
|
||||||
* license that can be found in the LICENSE.txt file or at
|
|
||||||
* https://opensource.org/licenses/MIT.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.helper
|
|
||||||
|
|
||||||
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
|
|
||||||
import com.intellij.openapi.components.Service
|
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
|
||||||
import com.maddyhome.idea.vim.group.NotificationService
|
|
||||||
import com.maddyhome.idea.vim.icons.VimIcons
|
|
||||||
|
|
||||||
@Service(Service.Level.APP)
|
|
||||||
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
|
|
||||||
VimPlugin.getPluginId(),
|
|
||||||
updateTimestampProperty = PROPERTY_NAME,
|
|
||||||
NotificationService.IDEAVIM_STICKY_GROUP,
|
|
||||||
VimIcons.IDEAVIM,
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun skipUpdateCheck(): Boolean = VimPlugin.isNotEnabled() || "dev" in VimPlugin.getVersion()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
|
|
||||||
val instance: VimStandalonePluginUpdateChecker = service()
|
|
||||||
}
|
|
||||||
}
|
|
@ -28,6 +28,7 @@ import com.intellij.openapi.actionSystem.CommonDataKeys
|
|||||||
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
import com.intellij.openapi.actionSystem.ex.AnActionListener
|
||||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.editor.impl.ScrollingModelImpl
|
||||||
import com.intellij.openapi.project.DumbAwareToggleAction
|
import com.intellij.openapi.project.DumbAwareToggleAction
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
@ -57,6 +58,7 @@ internal object IdeaSpecifics {
|
|||||||
private val surrounderAction =
|
private val surrounderAction =
|
||||||
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
"com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
|
||||||
private var editor: Editor? = null
|
private var editor: Editor? = null
|
||||||
|
private var caretOffset = -1
|
||||||
private var completionPrevDocumentLength: Int? = null
|
private var completionPrevDocumentLength: Int? = null
|
||||||
private var completionPrevDocumentOffset: Int? = null
|
private var completionPrevDocumentOffset: Int? = null
|
||||||
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
|
||||||
@ -65,6 +67,7 @@ internal object IdeaSpecifics {
|
|||||||
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
|
||||||
if (hostEditor != null) {
|
if (hostEditor != null) {
|
||||||
editor = hostEditor
|
editor = hostEditor
|
||||||
|
caretOffset = hostEditor.caretModel.offset
|
||||||
}
|
}
|
||||||
|
|
||||||
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
|
||||||
@ -96,43 +99,58 @@ internal object IdeaSpecifics {
|
|||||||
if (VimPlugin.isNotEnabled()) return
|
if (VimPlugin.isNotEnabled()) return
|
||||||
|
|
||||||
val editor = editor
|
val editor = editor
|
||||||
if (editor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
|
if (editor != null) {
|
||||||
val prevDocumentLength = completionPrevDocumentLength
|
if (action is ChooseItemAction && injector.registerGroup.isRecording) {
|
||||||
val prevDocumentOffset = completionPrevDocumentOffset
|
val prevDocumentLength = completionPrevDocumentLength
|
||||||
|
val prevDocumentOffset = completionPrevDocumentOffset
|
||||||
|
|
||||||
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
if (prevDocumentLength != null && prevDocumentOffset != null) {
|
||||||
val register = VimPlugin.getRegister()
|
val register = VimPlugin.getRegister()
|
||||||
val addedTextLength = editor.document.textLength - prevDocumentLength
|
val addedTextLength = editor.document.textLength - prevDocumentLength
|
||||||
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
|
||||||
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
|
||||||
|
|
||||||
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
|
||||||
repeat(caretShift.coerceAtLeast(0)) {
|
repeat(caretShift.coerceAtLeast(0)) {
|
||||||
register.recordKeyStroke(leftArrow)
|
register.recordKeyStroke(leftArrow)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.completionPrevDocumentLength = null
|
||||||
|
this.completionPrevDocumentOffset = null
|
||||||
|
}
|
||||||
|
|
||||||
|
//region Enter insert mode after surround with if
|
||||||
|
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
||||||
|
action.templatePresentation.text.endsWith(
|
||||||
|
it,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
editor?.let {
|
||||||
|
val commandState = it.vim.vimStateMachine
|
||||||
|
it.vim.mode = Mode.NORMAL()
|
||||||
|
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
||||||
|
KeyHandler.getInstance().reset(it.vim)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
//endregion
|
||||||
|
|
||||||
this.completionPrevDocumentLength = null
|
if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
|
||||||
this.completionPrevDocumentOffset = null
|
val scrollModel = editor.scrollingModel as ScrollingModelImpl
|
||||||
}
|
if (scrollModel.isScrollingNow) {
|
||||||
|
val v = scrollModel.verticalScrollOffset
|
||||||
//region Enter insert mode after surround with if
|
val h = scrollModel.horizontalScrollOffset
|
||||||
if (surrounderAction == action.javaClass.name && surrounderItems.any {
|
scrollModel.finishAnimation()
|
||||||
action.templatePresentation.text.endsWith(
|
scrollModel.scroll(h, v)
|
||||||
it,
|
scrollModel.finishAnimation()
|
||||||
)
|
}
|
||||||
}
|
injector.scroll.scrollCaretIntoView(editor.vim)
|
||||||
) {
|
|
||||||
editor?.let {
|
|
||||||
val commandState = it.vim.vimStateMachine
|
|
||||||
it.vim.mode = Mode.NORMAL()
|
|
||||||
VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
|
|
||||||
KeyHandler.getInstance().reset(it.vim)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//endregion
|
|
||||||
|
|
||||||
this.editor = null
|
this.editor = null
|
||||||
|
this.caretOffset = -1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,6 +35,7 @@ import com.intellij.openapi.editor.ex.DocumentEx
|
|||||||
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
|
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
|
||||||
import com.intellij.openapi.editor.ex.FocusChangeListener
|
import com.intellij.openapi.editor.ex.FocusChangeListener
|
||||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
|
import com.intellij.openapi.editor.impl.EditorComponentImpl
|
||||||
|
import com.intellij.openapi.editor.impl.EditorImpl
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
|
||||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
import com.intellij.openapi.fileEditor.FileEditorManagerListener
|
||||||
@ -45,11 +46,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
|
|||||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
|
import com.intellij.openapi.fileEditor.impl.EditorComposite
|
||||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
|
import com.intellij.openapi.fileEditor.impl.EditorWindow
|
||||||
import com.intellij.openapi.project.ProjectManager
|
import com.intellij.openapi.project.ProjectManager
|
||||||
|
import com.intellij.openapi.rd.createLifetime
|
||||||
|
import com.intellij.openapi.rd.createNestedDisposable
|
||||||
import com.intellij.openapi.util.Disposer
|
import com.intellij.openapi.util.Disposer
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
import com.intellij.openapi.util.removeUserData
|
import com.intellij.openapi.util.removeUserData
|
||||||
import com.intellij.openapi.vfs.VirtualFile
|
import com.intellij.openapi.vfs.VirtualFile
|
||||||
import com.intellij.util.ExceptionUtil
|
import com.intellij.util.ExceptionUtil
|
||||||
|
import com.jetbrains.rd.util.lifetime.Lifetime
|
||||||
import com.maddyhome.idea.vim.EventFacade
|
import com.maddyhome.idea.vim.EventFacade
|
||||||
import com.maddyhome.idea.vim.KeyHandler
|
import com.maddyhome.idea.vim.KeyHandler
|
||||||
import com.maddyhome.idea.vim.KeyHandlerStateResetter
|
import com.maddyhome.idea.vim.KeyHandlerStateResetter
|
||||||
@ -66,6 +70,8 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.group.EditorGroup
|
import com.maddyhome.idea.vim.group.EditorGroup
|
||||||
import com.maddyhome.idea.vim.group.FileGroup
|
import com.maddyhome.idea.vim.group.FileGroup
|
||||||
|
import com.maddyhome.idea.vim.group.IjOptions
|
||||||
|
import com.maddyhome.idea.vim.group.IjVimRedrawService
|
||||||
import com.maddyhome.idea.vim.group.MotionGroup
|
import com.maddyhome.idea.vim.group.MotionGroup
|
||||||
import com.maddyhome.idea.vim.group.OptionGroup
|
import com.maddyhome.idea.vim.group.OptionGroup
|
||||||
import com.maddyhome.idea.vim.group.ScrollGroup
|
import com.maddyhome.idea.vim.group.ScrollGroup
|
||||||
@ -79,7 +85,6 @@ import com.maddyhome.idea.vim.handler.keyCheckRequests
|
|||||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
|
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
|
||||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||||
import com.maddyhome.idea.vim.helper.StrictMode
|
import com.maddyhome.idea.vim.helper.StrictMode
|
||||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
|
|
||||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||||
import com.maddyhome.idea.vim.helper.forceBarCursor
|
import com.maddyhome.idea.vim.helper.forceBarCursor
|
||||||
@ -92,6 +97,7 @@ import com.maddyhome.idea.vim.helper.vimDisabled
|
|||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
|
||||||
@ -101,7 +107,6 @@ 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.macro.macroWidgetOptionListener
|
||||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
|
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
|
||||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
|
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
|
||||||
import com.maddyhome.idea.vim.vimDisposable
|
|
||||||
import java.awt.event.MouseAdapter
|
import java.awt.event.MouseAdapter
|
||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
@ -177,8 +182,8 @@ internal object VimListenerManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val optionGroup = VimPlugin.getOptionGroup()
|
val optionGroup = VimPlugin.getOptionGroup()
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
|
optionGroup.addEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
optionGroup.addEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||||
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
|
||||||
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||||
@ -209,8 +214,8 @@ internal object VimListenerManager {
|
|||||||
EventFacade.getInstance().restoreTypedActionHandler()
|
EventFacade.getInstance().restoreTypedActionHandler()
|
||||||
|
|
||||||
val optionGroup = VimPlugin.getOptionGroup()
|
val optionGroup = VimPlugin.getOptionGroup()
|
||||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
|
optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.number, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
optionGroup.removeEffectiveOptionValueChangeListener(IjOptions.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
|
||||||
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
|
||||||
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
|
||||||
optionGroup.removeGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener)
|
optionGroup.removeGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener)
|
||||||
@ -264,12 +269,10 @@ internal object VimListenerManager {
|
|||||||
// TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
|
// TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
|
||||||
if (vimDisabled(editor)) return
|
if (vimDisabled(editor)) return
|
||||||
|
|
||||||
// As I understand, there is no need to pass a disposable that also disposes on editor close
|
val pluginLifetime = VimPlugin.getInstance().createLifetime()
|
||||||
// because all editor resources will be garbage collected anyway on editor close
|
val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
|
||||||
// Note that this uses the plugin's main disposable, rather than VimPlugin.onOffDisposable, because we don't need
|
val disposable =
|
||||||
// to - we explicitly call VimListenerManager.removeAll from VimPlugin.turnOffPlugin, and this disposes each
|
Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
|
||||||
// editor's disposable individually.
|
|
||||||
val disposable = editor.project?.vimDisposable ?: return
|
|
||||||
|
|
||||||
val listenersDisposable = Disposer.newDisposable(disposable)
|
val listenersDisposable = Disposer.newDisposable(disposable)
|
||||||
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
|
editor.putUserData(editorListenersDisposableKey, listenersDisposable)
|
||||||
@ -300,7 +303,7 @@ internal object VimListenerManager {
|
|||||||
injector.listenersNotifier.notifyEditorCreated(vimEditor)
|
injector.listenersNotifier.notifyEditorCreated(vimEditor)
|
||||||
|
|
||||||
Disposer.register(listenersDisposable) {
|
Disposer.register(listenersDisposable) {
|
||||||
VimPlugin.getEditor().editorDeinit(editor, true)
|
VimPlugin.getEditor().editorDeinit(editor)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,11 +344,13 @@ internal object VimListenerManager {
|
|||||||
override fun beforeDocumentChange(event: DocumentEvent) {
|
override fun beforeDocumentChange(event: DocumentEvent) {
|
||||||
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
|
VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
|
||||||
SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
|
SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
|
||||||
|
IjVimRedrawService.RedrawListener.beforeDocumentChange(event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun documentChanged(event: DocumentEvent) {
|
override fun documentChanged(event: DocumentEvent) {
|
||||||
VimMarkServiceImpl.MarkUpdater.documentChanged(event)
|
VimMarkServiceImpl.MarkUpdater.documentChanged(event)
|
||||||
SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
|
SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
|
||||||
|
IjVimRedrawService.RedrawListener.documentChanged(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,11 +363,21 @@ internal object VimListenerManager {
|
|||||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||||
// We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
|
// 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
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
|
VimPlugin.getSearch().fileEditorManagerSelectionChangedCallback(event)
|
||||||
OptionGroup.fileEditorManagerSelectionChangedCallback(event)
|
OptionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
|
IjVimRedrawService.fileEditorManagerSelectionChangedCallback(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -429,8 +444,6 @@ internal object VimListenerManager {
|
|||||||
|
|
||||||
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
||||||
}
|
}
|
||||||
|
|
||||||
VimStandalonePluginUpdateChecker.instance.pluginUsed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editorReleased(event: EditorFactoryEvent) {
|
override fun editorReleased(event: EditorFactoryEvent) {
|
||||||
|
@ -24,6 +24,7 @@ import com.maddyhome.idea.vim.api.VimApplication
|
|||||||
import com.maddyhome.idea.vim.api.VimChangeGroup
|
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||||
import com.maddyhome.idea.vim.api.VimClipboardManager
|
import com.maddyhome.idea.vim.api.VimClipboardManager
|
||||||
import com.maddyhome.idea.vim.api.VimCommandGroup
|
import com.maddyhome.idea.vim.api.VimCommandGroup
|
||||||
|
import com.maddyhome.idea.vim.api.VimCommandLineService
|
||||||
import com.maddyhome.idea.vim.api.VimDigraphGroup
|
import com.maddyhome.idea.vim.api.VimDigraphGroup
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.VimEditorGroup
|
import com.maddyhome.idea.vim.api.VimEditorGroup
|
||||||
@ -43,6 +44,7 @@ import com.maddyhome.idea.vim.api.VimMotionGroup
|
|||||||
import com.maddyhome.idea.vim.api.VimOptionGroup
|
import com.maddyhome.idea.vim.api.VimOptionGroup
|
||||||
import com.maddyhome.idea.vim.api.VimProcessGroup
|
import com.maddyhome.idea.vim.api.VimProcessGroup
|
||||||
import com.maddyhome.idea.vim.api.VimPsiService
|
import com.maddyhome.idea.vim.api.VimPsiService
|
||||||
|
import com.maddyhome.idea.vim.api.VimRedrawService
|
||||||
import com.maddyhome.idea.vim.api.VimRegexpService
|
import com.maddyhome.idea.vim.api.VimRegexpService
|
||||||
import com.maddyhome.idea.vim.api.VimScrollGroup
|
import com.maddyhome.idea.vim.api.VimScrollGroup
|
||||||
import com.maddyhome.idea.vim.api.VimSearchGroup
|
import com.maddyhome.idea.vim.api.VimSearchGroup
|
||||||
@ -74,12 +76,10 @@ import com.maddyhome.idea.vim.group.TabService
|
|||||||
import com.maddyhome.idea.vim.group.VimWindowGroup
|
import com.maddyhome.idea.vim.group.VimWindowGroup
|
||||||
import com.maddyhome.idea.vim.group.WindowGroup
|
import com.maddyhome.idea.vim.group.WindowGroup
|
||||||
import com.maddyhome.idea.vim.group.copy.PutGroup
|
import com.maddyhome.idea.vim.group.copy.PutGroup
|
||||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
|
|
||||||
import com.maddyhome.idea.vim.helper.IjActionExecutor
|
import com.maddyhome.idea.vim.helper.IjActionExecutor
|
||||||
import com.maddyhome.idea.vim.helper.IjEditorHelper
|
import com.maddyhome.idea.vim.helper.IjEditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.IjVimStringParser
|
import com.maddyhome.idea.vim.helper.IjVimStringParser
|
||||||
import com.maddyhome.idea.vim.helper.UndoRedoHelper
|
import com.maddyhome.idea.vim.helper.UndoRedoHelper
|
||||||
import com.maddyhome.idea.vim.helper.VimCommandLineHelper
|
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.history.VimHistory
|
import com.maddyhome.idea.vim.history.VimHistory
|
||||||
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
|
import com.maddyhome.idea.vim.impl.state.VimStateMachineImpl
|
||||||
@ -151,8 +151,6 @@ internal class IjVimInjector : VimInjectorBase() {
|
|||||||
get() = service<UndoRedoHelper>()
|
get() = service<UndoRedoHelper>()
|
||||||
override val psiService: VimPsiService
|
override val psiService: VimPsiService
|
||||||
get() = service<IjVimPsiService>()
|
get() = service<IjVimPsiService>()
|
||||||
override val commandLineHelper: VimCommandLineHelper
|
|
||||||
get() = service<CommandLineHelper>()
|
|
||||||
override val nativeActionManager: NativeActionManager
|
override val nativeActionManager: NativeActionManager
|
||||||
get() = service<IjNativeActionManager>()
|
get() = service<IjNativeActionManager>()
|
||||||
override val messages: VimMessages
|
override val messages: VimMessages
|
||||||
@ -197,6 +195,8 @@ internal class IjVimInjector : VimInjectorBase() {
|
|||||||
get() = service<Executor>()
|
get() = service<Executor>()
|
||||||
override val vimscriptParser: VimscriptParser
|
override val vimscriptParser: VimscriptParser
|
||||||
get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
|
get() = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
|
||||||
|
override val commandLine: VimCommandLineService
|
||||||
|
get() = service()
|
||||||
|
|
||||||
override val optionGroup: VimOptionGroup
|
override val optionGroup: VimOptionGroup
|
||||||
get() = service()
|
get() = service()
|
||||||
@ -207,6 +207,8 @@ internal class IjVimInjector : VimInjectorBase() {
|
|||||||
get() = service()
|
get() = service()
|
||||||
override val vimStorageService: VimStorageService
|
override val vimStorageService: VimStorageService
|
||||||
get() = service()
|
get() = service()
|
||||||
|
override val redrawService: VimRedrawService
|
||||||
|
get() = service()
|
||||||
|
|
||||||
override fun commandStateFor(editor: VimEditor): VimStateMachine {
|
override fun commandStateFor(editor: VimEditor): VimStateMachine {
|
||||||
var res = editor.ij.vimStateMachine
|
var res = editor.ij.vimStateMachine
|
||||||
|
@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.newapi
|
|||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.ProjectManager
|
import com.intellij.openapi.project.ProjectManager
|
||||||
import com.intellij.openapi.wm.WindowManager
|
import com.intellij.openapi.wm.WindowManager
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
@ -26,27 +27,59 @@ internal class IjVimMessages : VimMessagesBase() {
|
|||||||
private var message: String? = null
|
private var message: String? = null
|
||||||
private var error = false
|
private var error = false
|
||||||
private var lastBeepTimeMillis = 0L
|
private var lastBeepTimeMillis = 0L
|
||||||
|
private var allowClearStatusBarMessage = true
|
||||||
|
|
||||||
override fun showStatusBarMessage(editor: VimEditor?, message: String?) {
|
override fun showStatusBarMessage(editor: VimEditor?, message: String?) {
|
||||||
if (ApplicationManager.getApplication().isUnitTestMode) {
|
fun setStatusBarMessage(project: Project, message: String?) {
|
||||||
this.message = message
|
WindowManager.getInstance().getStatusBar(project)?.let {
|
||||||
}
|
it.info = if (message.isNullOrBlank()) "" else "Vim - $message"
|
||||||
val pm = ProjectManager.getInstance()
|
|
||||||
val projects = pm.openProjects
|
|
||||||
for (project in projects) {
|
|
||||||
val bar = WindowManager.getInstance().getStatusBar(project)
|
|
||||||
if (bar != null) {
|
|
||||||
if (message.isNullOrEmpty()) {
|
|
||||||
bar.info = ""
|
|
||||||
} else {
|
|
||||||
bar.info = "VIM - $message"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.message = message
|
||||||
|
|
||||||
|
val project = editor?.ij?.project
|
||||||
|
if (project != null) {
|
||||||
|
setStatusBarMessage(project, message)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// TODO: We really shouldn't set the status bar text for other projects. That's rude.
|
||||||
|
ProjectManager.getInstance().openProjects.forEach {
|
||||||
|
setStatusBarMessage(it, message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw happens automatically based on changes or scrolling. If we've just set the message (e.g., searching for a
|
||||||
|
// string, hitting the bottom and scrolling to the top), make sure we don't immediately clear it when scrolling.
|
||||||
|
allowClearStatusBarMessage = false
|
||||||
|
ApplicationManager.getApplication().invokeLater {
|
||||||
|
allowClearStatusBarMessage = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getStatusBarMessage(): String? = message
|
override fun getStatusBarMessage(): String? = message
|
||||||
|
|
||||||
|
// Vim doesn't appear to have a policy about clearing the status bar, other than on "redraw". This can be forced with
|
||||||
|
// <C-L> or the `:redraw` command, but also happens as the screen changes, e.g., when inserting or deleting lines,
|
||||||
|
// scrolling, entering Command-line mode and probably lots more. We should manually clear the status bar when these
|
||||||
|
// things happen.
|
||||||
|
override fun clearStatusBarMessage() {
|
||||||
|
if (message.isNullOrEmpty()) return
|
||||||
|
|
||||||
|
// Don't clear the status bar message if we've only just set it
|
||||||
|
if (!allowClearStatusBarMessage) return
|
||||||
|
|
||||||
|
ProjectManager.getInstance().openProjects.forEach { project ->
|
||||||
|
WindowManager.getInstance().getStatusBar(project)?.let { statusBar ->
|
||||||
|
// Only clear the status bar if it's showing our last message
|
||||||
|
if (statusBar.info?.contains(message.toString()) == true) {
|
||||||
|
statusBar.info = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
message = null
|
||||||
|
}
|
||||||
|
|
||||||
override fun indicateError() {
|
override fun indicateError() {
|
||||||
error = true
|
error = true
|
||||||
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
if (!ApplicationManager.getApplication().isUnitTestMode) {
|
||||||
@ -68,6 +101,7 @@ internal class IjVimMessages : VimMessagesBase() {
|
|||||||
override fun isError(): Boolean = error
|
override fun isError(): Boolean = error
|
||||||
|
|
||||||
override fun message(key: String, vararg params: Any): String = MessageHelper.message(key, *params)
|
override fun message(key: String, vararg params: Any): String = MessageHelper.message(key, *params)
|
||||||
|
|
||||||
override fun updateStatusBar(editor: VimEditor) {
|
override fun updateStatusBar(editor: VimEditor) {
|
||||||
ShowCmd.update()
|
ShowCmd.update()
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@
|
|||||||
package com.maddyhome.idea.vim.newapi
|
package com.maddyhome.idea.vim.newapi
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.editor.markup.RangeHighlighter
|
||||||
import com.intellij.openapi.util.Ref
|
import com.intellij.openapi.util.Ref
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
@ -31,17 +33,13 @@ import com.maddyhome.idea.vim.vimscript.model.functions.handlers.SubmatchFunctio
|
|||||||
import org.jetbrains.annotations.TestOnly
|
import org.jetbrains.annotations.TestOnly
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
public open class IjVimSearchGroup : VimSearchGroupBase() {
|
public abstract class IjVimSearchGroup : VimSearchGroupBase() {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// TODO: Investigate migrating these listeners to use the effective value change listener
|
// We use the global option listener instead of the effective listener that gets called for each affected editor
|
||||||
// This would allow us to update the editor we're told to update, rather than looping over all projects and updating
|
// because we handle updating the affected editors ourselves (e.g., we can filter for visible windows).
|
||||||
// the highlights in that project's current document's open editors (see VIM-2779).
|
|
||||||
// However, we probably only want to update the editors associated with the current document, so maybe the whole
|
|
||||||
// code needs to be reworked. We're currently using the same update code for changes in the search term as well as
|
|
||||||
// changes in the search options.
|
|
||||||
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch) {
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(Options.hlsearch) {
|
||||||
resetSearchHighlight()
|
setShouldShowSearchHighlights()
|
||||||
updateSearchHighlights(true)
|
updateSearchHighlights(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,13 +133,19 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
|||||||
return result.get()
|
return result.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun addSubstitutionConfirmationHighlight(editor: VimEditor, startOffset: Int, endOffset: Int) {
|
override fun addSubstitutionConfirmationHighlight(
|
||||||
val hl = addSubstitutionConfirmationHighlight(
|
editor: VimEditor,
|
||||||
(editor as IjVimEditor).editor,
|
startOffset: Int,
|
||||||
|
endOffset: Int,
|
||||||
|
): SearchHighlight {
|
||||||
|
|
||||||
|
val ijEditor = (editor as IjVimEditor).editor
|
||||||
|
val highlighter = addSubstitutionConfirmationHighlight(
|
||||||
|
ijEditor,
|
||||||
startOffset,
|
startOffset,
|
||||||
endOffset
|
endOffset
|
||||||
)
|
)
|
||||||
editor.editor.markupModel.removeHighlighter(hl)
|
return IjSearchHighlight(ijEditor, highlighter)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun setLatestMatch(match: String) {
|
override fun setLatestMatch(match: String) {
|
||||||
@ -165,7 +169,7 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
|||||||
showSearchHighlight = injector.globalOptions().hlsearch
|
showSearchHighlight = injector.globalOptions().hlsearch
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resetSearchHighlight() {
|
override fun setShouldShowSearchHighlights() {
|
||||||
showSearchHighlight = injector.globalOptions().hlsearch
|
showSearchHighlight = injector.globalOptions().hlsearch
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,4 +177,12 @@ public open class IjVimSearchGroup : VimSearchGroupBase() {
|
|||||||
showSearchHighlight = false
|
showSearchHighlight = false
|
||||||
updateSearchHighlights(false)
|
updateSearchHighlights(false)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
private class IjSearchHighlight(private val editor: Editor, private val highlighter: RangeHighlighter) :
|
||||||
|
SearchHighlight() {
|
||||||
|
|
||||||
|
override fun remove() {
|
||||||
|
editor.markupModel.removeHighlighter(highlighter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -136,7 +136,7 @@
|
|||||||
* |-| {@link com.maddyhome.idea.vim.action.motion.updown.MotionUpFirstNonSpaceAction}
|
* |-| {@link com.maddyhome.idea.vim.action.motion.updown.MotionUpFirstNonSpaceAction}
|
||||||
* |.| {@link com.maddyhome.idea.vim.action.change.RepeatChangeAction}
|
* |.| {@link com.maddyhome.idea.vim.action.change.RepeatChangeAction}
|
||||||
* |/| {@link com.maddyhome.idea.vim.action.motion.search.SearchEntryFwdAction}
|
* |/| {@link com.maddyhome.idea.vim.action.motion.search.SearchEntryFwdAction}
|
||||||
* |:| {@link com.maddyhome.idea.vim.action.ExEntryAction}
|
* |:| {@link com.maddyhome.idea.vim.action.ex.ExEntryAction}
|
||||||
* |;| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionLastMatchCharAction}
|
* |;| {@link com.maddyhome.idea.vim.action.motion.leftright.MotionLastMatchCharAction}
|
||||||
* |<| {@link com.maddyhome.idea.vim.action.change.shift.ShiftLeftMotionAction}
|
* |<| {@link com.maddyhome.idea.vim.action.change.shift.ShiftLeftMotionAction}
|
||||||
* |<<| translated to <_
|
* |<<| translated to <_
|
||||||
@ -618,7 +618,6 @@
|
|||||||
* |c_CTRL-N| {@link com.maddyhome.idea.vim.ui.ex.HistoryDownAction}
|
* |c_CTRL-N| {@link com.maddyhome.idea.vim.ui.ex.HistoryDownAction}
|
||||||
* |c_CTRL-P| {@link com.maddyhome.idea.vim.ui.ex.HistoryUpAction}
|
* |c_CTRL-P| {@link com.maddyhome.idea.vim.ui.ex.HistoryUpAction}
|
||||||
* |c_CTRL-Q| Handled by KeyHandler
|
* |c_CTRL-Q| Handled by KeyHandler
|
||||||
* |c_CTRL-R| {@link com.maddyhome.idea.vim.ui.ex.InsertRegisterAction}
|
|
||||||
* |c_CTRL-R_CTRL-A| TO BE IMPLEMENTED
|
* |c_CTRL-R_CTRL-A| TO BE IMPLEMENTED
|
||||||
* |c_CTRL-R_CTRL-F| TO BE IMPLEMENTED
|
* |c_CTRL-R_CTRL-F| TO BE IMPLEMENTED
|
||||||
* |c_CTRL-R_CTRL-L| TO BE IMPLEMENTED
|
* |c_CTRL-R_CTRL-L| TO BE IMPLEMENTED
|
||||||
|
@ -1460,6 +1460,9 @@ internal class RegExp {
|
|||||||
* can't go before line 1 */
|
* can't go before line 1 */
|
||||||
return if (reg_firstlnum + lnum < 0) {
|
return if (reg_firstlnum + lnum < 0) {
|
||||||
null
|
null
|
||||||
|
} else if (reg_firstlnum + lnum >= reg_buf!!.lineCount()) {
|
||||||
|
// Must have matched the "\n" in the last line.
|
||||||
|
CharPointer("")
|
||||||
} else {
|
} else {
|
||||||
CharPointer(
|
CharPointer(
|
||||||
reg_buf!!.getLineBuffer(reg_firstlnum + lnum),
|
reg_buf!!.getLineBuffer(reg_firstlnum + lnum),
|
||||||
@ -1507,7 +1510,7 @@ internal class RegExp {
|
|||||||
reg_buf = buf
|
reg_buf = buf
|
||||||
// reg_win = win;
|
// reg_win = win;
|
||||||
reg_firstlnum = lnum
|
reg_firstlnum = lnum
|
||||||
reg_maxline = lcount - lnum
|
reg_maxline = lcount - lnum - 1 // Remember, lnum is 0-based, while in Vim, it's 1-based
|
||||||
ireg_ic = rmp.rmm_ic
|
ireg_ic = rmp.rmm_ic
|
||||||
|
|
||||||
/* Need to switch to buffer "buf" to make vim_iswordc() work. */
|
/* Need to switch to buffer "buf" to make vim_iswordc() work. */
|
||||||
@ -1874,7 +1877,9 @@ internal class RegExp {
|
|||||||
reginput!!.inc()
|
reginput!!.inc()
|
||||||
}
|
}
|
||||||
IDENT -> {
|
IDENT -> {
|
||||||
if (!Character.isJavaIdentifierPart(c)) {
|
// Character.isJavaIdentifier treats '\0' as a valid identifier!
|
||||||
|
// Also, this should really be using 'isident' instead of Character.isJavaIdentifierPart
|
||||||
|
if (c == '\u0000' || !Character.isJavaIdentifierPart(c)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
reginput!!.inc()
|
reginput!!.inc()
|
||||||
@ -2488,7 +2493,7 @@ internal class RegExp {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
NEWL -> {
|
NEWL -> {
|
||||||
if (c != '\u0000' || reglnum == reg_maxline) {
|
if (c != '\u0000' || reglnum > reg_maxline) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2530,7 +2535,7 @@ internal class RegExp {
|
|||||||
++count
|
++count
|
||||||
scan.inc()
|
scan.inc()
|
||||||
}
|
}
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline || count == maxcount) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline || count == maxcount) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
++count /* count the line-break */
|
++count /* count the line-break */
|
||||||
@ -2548,7 +2553,7 @@ internal class RegExp {
|
|||||||
) {
|
) {
|
||||||
scan.inc()
|
scan.inc()
|
||||||
} else if (scan.isNul) {
|
} else if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2568,7 +2573,7 @@ internal class RegExp {
|
|||||||
) {
|
) {
|
||||||
scan.inc()
|
scan.inc()
|
||||||
} else if (scan.isNul) {
|
} else if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2587,7 +2592,7 @@ internal class RegExp {
|
|||||||
if (CharacterClasses.isWord(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
if (CharacterClasses.isWord(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
||||||
scan.inc()
|
scan.inc()
|
||||||
} else if (scan.isNul) {
|
} else if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2605,7 +2610,7 @@ internal class RegExp {
|
|||||||
if (CharacterClasses.isWord(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
if (CharacterClasses.isWord(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
||||||
scan.inc()
|
scan.inc()
|
||||||
} else if (scan.isNul) {
|
} else if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2624,7 +2629,7 @@ internal class RegExp {
|
|||||||
if (CharacterClasses.isFile(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
if (CharacterClasses.isFile(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
||||||
scan.inc()
|
scan.inc()
|
||||||
} else if (scan.isNul) {
|
} else if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2642,7 +2647,7 @@ internal class RegExp {
|
|||||||
if (CharacterClasses.isFile(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
if (CharacterClasses.isFile(scan.charAt()) && (testval == 1 || !Character.isDigit(scan.charAt()))) {
|
||||||
scan.inc()
|
scan.inc()
|
||||||
} else if (scan.isNul) {
|
} else if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2659,7 +2664,7 @@ internal class RegExp {
|
|||||||
testval = 1
|
testval = 1
|
||||||
while (count < maxcount) {
|
while (count < maxcount) {
|
||||||
if (scan.isNul) {
|
if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2679,7 +2684,7 @@ internal class RegExp {
|
|||||||
}
|
}
|
||||||
SPRINT, SPRINT + ADD_NL -> while (count < maxcount) {
|
SPRINT, SPRINT + ADD_NL -> while (count < maxcount) {
|
||||||
if (scan.isNul) {
|
if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2765,7 +2770,7 @@ internal class RegExp {
|
|||||||
testval = 1
|
testval = 1
|
||||||
while (count < maxcount) {
|
while (count < maxcount) {
|
||||||
if (scan.isNul) {
|
if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2784,7 +2789,7 @@ internal class RegExp {
|
|||||||
}
|
}
|
||||||
ANYBUT, ANYBUT + ADD_NL -> while (count < maxcount) {
|
ANYBUT, ANYBUT + ADD_NL -> while (count < maxcount) {
|
||||||
if (scan.isNul) {
|
if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum > reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -2800,7 +2805,7 @@ internal class RegExp {
|
|||||||
}
|
}
|
||||||
++count
|
++count
|
||||||
}
|
}
|
||||||
NEWL -> while (count < maxcount && scan.isNul && reglnum < reg_maxline) {
|
NEWL -> while (count < maxcount && scan.isNul && reglnum <= reg_maxline) {
|
||||||
count++
|
count++
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
scan = reginput!!.ref(0)
|
scan = reginput!!.ref(0)
|
||||||
@ -2813,7 +2818,7 @@ internal class RegExp {
|
|||||||
if (mask != 0) {
|
if (mask != 0) {
|
||||||
while (count < maxcount) {
|
while (count < maxcount) {
|
||||||
if (scan.isNul) {
|
if (scan.isNul) {
|
||||||
if (!WITH_NL(p.OP()) || reglnum == reg_maxline) {
|
if (!WITH_NL(p.OP()) || reglnum < reg_maxline) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
reg_nextline()
|
reg_nextline()
|
||||||
@ -3165,7 +3170,7 @@ internal class RegExp {
|
|||||||
reg_mmatch = rmp
|
reg_mmatch = rmp
|
||||||
// reg_buf = curbuf; /* always works on the current buffer! */
|
// reg_buf = curbuf; /* always works on the current buffer! */
|
||||||
reg_firstlnum = lnum
|
reg_firstlnum = lnum
|
||||||
reg_maxline = reg_buf!!.lineCount() - lnum
|
reg_maxline = reg_buf!!.lineCount() - lnum - 1 // lnum is 0-based, so make sure maxline is too
|
||||||
return vim_regsub_both(source, magic, backslash)
|
return vim_regsub_both(source, magic, backslash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3307,7 +3312,7 @@ internal class RegExp {
|
|||||||
if (reg_mmatch!!.endpos[no]!!.lnum == clnum) {
|
if (reg_mmatch!!.endpos[no]!!.lnum == clnum) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
dst.append('\r')
|
dst.append('\n')
|
||||||
s = reg_getline(++clnum)
|
s = reg_getline(++clnum)
|
||||||
len = if (reg_mmatch!!.endpos[no]!!.lnum == clnum) {
|
len = if (reg_mmatch!!.endpos[no]!!.lnum == clnum) {
|
||||||
reg_mmatch!!.endpos[no]!!.col
|
reg_mmatch!!.endpos[no]!!.col
|
||||||
|
@ -151,6 +151,10 @@ public class ExOutputPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return myText.getText();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Override
|
@Override
|
||||||
public Color getForeground() {
|
public Color getForeground() {
|
||||||
@ -288,7 +292,7 @@ public class ExOutputPanel extends JPanel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close() {
|
public void close() {
|
||||||
close(null);
|
close(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,10 +26,12 @@ import javax.swing.text.TextAction
|
|||||||
import kotlin.math.abs
|
import kotlin.math.abs
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal interface MultiStepAction : Action {
|
internal interface MultiStepAction : Action {
|
||||||
fun reset()
|
fun reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class HistoryUpAction : TextAction(ExEditorKit.HistoryUp) {
|
internal class HistoryUpAction : TextAction(ExEditorKit.HistoryUp) {
|
||||||
override fun actionPerformed(actionEvent: ActionEvent) {
|
override fun actionPerformed(actionEvent: ActionEvent) {
|
||||||
val target = getTextComponent(actionEvent) as ExTextField
|
val target = getTextComponent(actionEvent) as ExTextField
|
||||||
@ -37,6 +39,7 @@ internal class HistoryUpAction : TextAction(ExEditorKit.HistoryUp) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class HistoryDownAction : TextAction(ExEditorKit.HistoryDown) {
|
internal class HistoryDownAction : TextAction(ExEditorKit.HistoryDown) {
|
||||||
override fun actionPerformed(actionEvent: ActionEvent) {
|
override fun actionPerformed(actionEvent: ActionEvent) {
|
||||||
val target = getTextComponent(actionEvent) as ExTextField
|
val target = getTextComponent(actionEvent) as ExTextField
|
||||||
@ -44,6 +47,7 @@ internal class HistoryDownAction : TextAction(ExEditorKit.HistoryDown) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class HistoryUpFilterAction : TextAction(ExEditorKit.HistoryUpFilter) {
|
internal class HistoryUpFilterAction : TextAction(ExEditorKit.HistoryUpFilter) {
|
||||||
override fun actionPerformed(actionEvent: ActionEvent) {
|
override fun actionPerformed(actionEvent: ActionEvent) {
|
||||||
val target = getTextComponent(actionEvent) as ExTextField
|
val target = getTextComponent(actionEvent) as ExTextField
|
||||||
@ -51,6 +55,7 @@ internal class HistoryUpFilterAction : TextAction(ExEditorKit.HistoryUpFilter) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class HistoryDownFilterAction : TextAction(ExEditorKit.HistoryDownFilter) {
|
internal class HistoryDownFilterAction : TextAction(ExEditorKit.HistoryDownFilter) {
|
||||||
override fun actionPerformed(actionEvent: ActionEvent) {
|
override fun actionPerformed(actionEvent: ActionEvent) {
|
||||||
val target = getTextComponent(actionEvent) as ExTextField
|
val target = getTextComponent(actionEvent) as ExTextField
|
||||||
@ -58,51 +63,7 @@ internal class HistoryDownFilterAction : TextAction(ExEditorKit.HistoryDownFilte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class InsertRegisterAction : TextAction(ExEditorKit.InsertRegister), MultiStepAction {
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
private enum class State {
|
|
||||||
SKIP_CTRL_R, WAIT_REGISTER
|
|
||||||
}
|
|
||||||
|
|
||||||
private var state = State.SKIP_CTRL_R
|
|
||||||
|
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
|
||||||
val target = getTextComponent(e) as ExTextField
|
|
||||||
val key = ExEditorKit.convert(e)
|
|
||||||
if (key != null) {
|
|
||||||
when (state) {
|
|
||||||
State.SKIP_CTRL_R -> {
|
|
||||||
state = State.WAIT_REGISTER
|
|
||||||
target.setCurrentAction(this, '\"')
|
|
||||||
}
|
|
||||||
State.WAIT_REGISTER -> {
|
|
||||||
state = State.SKIP_CTRL_R
|
|
||||||
target.clearCurrentAction()
|
|
||||||
val c = key.keyChar
|
|
||||||
if (c != KeyEvent.CHAR_UNDEFINED) {
|
|
||||||
val register = VimPlugin.getRegister().getRegister(c)
|
|
||||||
if (register != null) {
|
|
||||||
val oldText = target.actualText
|
|
||||||
val text = register.text
|
|
||||||
if (text != null) {
|
|
||||||
val offset = target.caretPosition
|
|
||||||
target.text = oldText.substring(0, offset) + text + oldText.substring(offset)
|
|
||||||
target.caretPosition = offset + text.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (key.modifiers and KeyEvent.CTRL_DOWN_MASK != 0 && key.keyCode == KeyEvent.VK_C) {
|
|
||||||
// Eat any unused keys, unless it's <C-C>, in which case forward on and cancel entry
|
|
||||||
target.handleKey(key)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun reset() {
|
|
||||||
state = State.SKIP_CTRL_R
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) {
|
internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) {
|
||||||
override fun actionPerformed(actionEvent: ActionEvent) {
|
override fun actionPerformed(actionEvent: ActionEvent) {
|
||||||
logger.debug("complete entry")
|
logger.debug("complete entry")
|
||||||
@ -123,6 +84,7 @@ internal class CompleteEntryAction : TextAction(ExEditorKit.CompleteEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class CancelEntryAction : TextAction(ExEditorKit.CancelEntry) {
|
internal class CancelEntryAction : TextAction(ExEditorKit.CancelEntry) {
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
val target = getTextComponent(e) as ExTextField
|
val target = getTextComponent(e) as ExTextField
|
||||||
@ -130,6 +92,7 @@ internal class CancelEntryAction : TextAction(ExEditorKit.CancelEntry) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class EscapeCharAction : TextAction(ExEditorKit.EscapeChar) {
|
internal class EscapeCharAction : TextAction(ExEditorKit.EscapeChar) {
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
val target = getTextComponent(e) as ExTextField
|
val target = getTextComponent(e) as ExTextField
|
||||||
@ -137,6 +100,7 @@ internal class EscapeCharAction : TextAction(ExEditorKit.EscapeChar) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal abstract class DeleteCharAction internal constructor(name: String?) : TextAction(name) {
|
internal abstract class DeleteCharAction internal constructor(name: String?) : TextAction(name) {
|
||||||
@kotlin.jvm.Throws(BadLocationException::class)
|
@kotlin.jvm.Throws(BadLocationException::class)
|
||||||
fun deleteSelection(doc: Document, dot: Int, mark: Int): Boolean {
|
fun deleteSelection(doc: Document, dot: Int, mark: Int): Boolean {
|
||||||
@ -184,6 +148,7 @@ internal abstract class DeleteCharAction internal constructor(name: String?) : T
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class DeleteNextCharAction : DeleteCharAction(DefaultEditorKit.deleteNextCharAction) {
|
internal class DeleteNextCharAction : DeleteCharAction(DefaultEditorKit.deleteNextCharAction) {
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
val target = getTextComponent(e) as ExTextField
|
val target = getTextComponent(e) as ExTextField
|
||||||
@ -202,6 +167,7 @@ internal class DeleteNextCharAction : DeleteCharAction(DefaultEditorKit.deleteNe
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class DeletePreviousCharAction : DeleteCharAction(DefaultEditorKit.deletePrevCharAction) {
|
internal class DeletePreviousCharAction : DeleteCharAction(DefaultEditorKit.deletePrevCharAction) {
|
||||||
override fun actionPerformed(e: ActionEvent) {
|
override fun actionPerformed(e: ActionEvent) {
|
||||||
val target = getTextComponent(e) as ExTextField
|
val target = getTextComponent(e) as ExTextField
|
||||||
@ -222,6 +188,7 @@ internal class DeletePreviousCharAction : DeleteCharAction(DefaultEditorKit.dele
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrevWordAction) {
|
internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrevWordAction) {
|
||||||
/**
|
/**
|
||||||
* Invoked when an action occurs.
|
* Invoked when an action occurs.
|
||||||
@ -258,6 +225,7 @@ internal class DeletePreviousWordAction : TextAction(DefaultEditorKit.deletePrev
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class DeleteToCursorAction : TextAction(ExEditorKit.DeleteToCursor) {
|
internal class DeleteToCursorAction : TextAction(ExEditorKit.DeleteToCursor) {
|
||||||
/**
|
/**
|
||||||
* Invoked when an action occurs.
|
* Invoked when an action occurs.
|
||||||
@ -275,6 +243,7 @@ internal class DeleteToCursorAction : TextAction(ExEditorKit.DeleteToCursor) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class ToggleInsertReplaceAction : TextAction(ExEditorKit.ToggleInsertReplace) {
|
internal class ToggleInsertReplaceAction : TextAction(ExEditorKit.ToggleInsertReplace) {
|
||||||
/**
|
/**
|
||||||
* Invoked when an action occurs.
|
* Invoked when an action occurs.
|
||||||
|
@ -20,6 +20,7 @@ import javax.swing.text.DefaultEditorKit
|
|||||||
import javax.swing.text.Document
|
import javax.swing.text.Document
|
||||||
import javax.swing.text.TextAction
|
import javax.swing.text.TextAction
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal object ExEditorKit : DefaultEditorKit() {
|
internal object ExEditorKit : DefaultEditorKit() {
|
||||||
|
|
||||||
@NonNls
|
@NonNls
|
||||||
@ -37,9 +38,6 @@ internal object ExEditorKit : DefaultEditorKit() {
|
|||||||
@NonNls
|
@NonNls
|
||||||
val ToggleInsertReplace: String = "toggle-insert"
|
val ToggleInsertReplace: String = "toggle-insert"
|
||||||
|
|
||||||
@NonNls
|
|
||||||
val InsertRegister: String = "insert-register"
|
|
||||||
|
|
||||||
@NonNls
|
@NonNls
|
||||||
val HistoryUp: String = "history-up"
|
val HistoryUp: String = "history-up"
|
||||||
|
|
||||||
@ -107,7 +105,6 @@ internal object ExEditorKit : DefaultEditorKit() {
|
|||||||
HistoryUpFilterAction(),
|
HistoryUpFilterAction(),
|
||||||
HistoryDownFilterAction(),
|
HistoryDownFilterAction(),
|
||||||
ToggleInsertReplaceAction(),
|
ToggleInsertReplaceAction(),
|
||||||
InsertRegisterAction(),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class DefaultExKeyHandler : DefaultKeyTypedAction() {
|
class DefaultExKeyHandler : DefaultKeyTypedAction() {
|
||||||
|
@ -18,7 +18,13 @@ import com.intellij.openapi.editor.Editor;
|
|||||||
import com.intellij.openapi.editor.ScrollingModel;
|
import com.intellij.openapi.editor.ScrollingModel;
|
||||||
import com.intellij.ui.DocumentAdapter;
|
import com.intellij.ui.DocumentAdapter;
|
||||||
import com.intellij.util.IJSwingUtilities;
|
import com.intellij.util.IJSwingUtilities;
|
||||||
|
import com.maddyhome.idea.vim.EventFacade;
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
|
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
|
||||||
|
import com.maddyhome.idea.vim.api.VimCommandLine;
|
||||||
|
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
|
||||||
|
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
|
||||||
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
import com.maddyhome.idea.vim.ex.ranges.LineRange;
|
||||||
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
|
import com.maddyhome.idea.vim.helper.SearchHighlightsHelper;
|
||||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||||
@ -28,6 +34,7 @@ import com.maddyhome.idea.vim.regexp.CharPointer;
|
|||||||
import com.maddyhome.idea.vim.regexp.RegExp;
|
import com.maddyhome.idea.vim.regexp.RegExp;
|
||||||
import com.maddyhome.idea.vim.ui.ExPanelBorder;
|
import com.maddyhome.idea.vim.ui.ExPanelBorder;
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.Command;
|
import com.maddyhome.idea.vim.vimscript.model.commands.Command;
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.commands.GlobalCommand;
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand;
|
import com.maddyhome.idea.vim.vimscript.model.commands.SubstituteCommand;
|
||||||
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser;
|
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser;
|
||||||
import org.jetbrains.annotations.Contract;
|
import org.jetbrains.annotations.Contract;
|
||||||
@ -44,12 +51,13 @@ import java.awt.event.ComponentListener;
|
|||||||
|
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
|
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
||||||
|
import static com.maddyhome.idea.vim.group.KeyGroup.toShortcutSet;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is used to enter ex commands such as searches and "colon" commands
|
* This is used to enter ex commands such as searches and "colon" commands
|
||||||
*/
|
*/
|
||||||
public class ExEntryPanel extends JPanel {
|
public class ExEntryPanel extends JPanel implements VimCommandLine {
|
||||||
private static ExEntryPanel instance;
|
public static ExEntryPanel instance;
|
||||||
private static ExEntryPanel instanceWithoutShortcuts;
|
private static ExEntryPanel instanceWithoutShortcuts;
|
||||||
|
|
||||||
private ExEntryPanel(boolean enableShortcuts) {
|
private ExEntryPanel(boolean enableShortcuts) {
|
||||||
@ -71,6 +79,11 @@ public class ExEntryPanel extends JPanel {
|
|||||||
|
|
||||||
if (enableShortcuts) {
|
if (enableShortcuts) {
|
||||||
// This does not need to be unregistered, it's registered as a custom UI property on this
|
// This does not need to be unregistered, it's registered as a custom UI property on this
|
||||||
|
EventFacade.getInstance().registerCustomShortcutSet(
|
||||||
|
VimShortcutKeyAction.getInstance(),
|
||||||
|
toShortcutSet(((VimKeyGroupBase) injector.getKeyGroup()).getRequiredShortcutKeys()),
|
||||||
|
entry
|
||||||
|
);
|
||||||
new ExShortcutKeyAction(this).registerCustomShortcutSet();
|
new ExShortcutKeyAction(this).registerCustomShortcutSet();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,6 +187,7 @@ public class ExEntryPanel extends JPanel {
|
|||||||
/**
|
/**
|
||||||
* Turns off the ex entry field and optionally puts the focus back to the original component
|
* Turns off the ex entry field and optionally puts the focus back to the original component
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void deactivate(boolean refocusOwningEditor, boolean resetCaret) {
|
public void deactivate(boolean refocusOwningEditor, boolean resetCaret) {
|
||||||
logger.info("Deactivate ex entry panel");
|
logger.info("Deactivate ex entry panel");
|
||||||
if (!active) return;
|
if (!active) return;
|
||||||
@ -254,51 +268,72 @@ public class ExEntryPanel extends JPanel {
|
|||||||
private final @NotNull DocumentListener incSearchDocumentListener = new DocumentAdapter() {
|
private final @NotNull DocumentListener incSearchDocumentListener = new DocumentAdapter() {
|
||||||
@Override
|
@Override
|
||||||
protected void textChanged(@NotNull DocumentEvent e) {
|
protected void textChanged(@NotNull DocumentEvent e) {
|
||||||
final Editor editor = entry.getEditor();
|
try {
|
||||||
|
final Editor editor = entry.getEditor();
|
||||||
|
|
||||||
boolean searchCommand = false;
|
final String labelText = label.getText(); // Either '/', '?' or ':'boolean searchCommand = false;
|
||||||
LineRange searchRange = null;
|
|
||||||
char separator = label.getText().charAt(0);
|
boolean searchCommand = false;
|
||||||
String searchText = entry.getActualText();
|
LineRange searchRange = null;
|
||||||
if (label.getText().equals(":")) {
|
char separator = labelText.charAt(0);
|
||||||
if (searchText.isEmpty()) return;
|
String searchText = entry.getActualText();
|
||||||
final Command command = getIncsearchCommand(searchText);
|
if (labelText.equals(":")) {
|
||||||
if (command == null) {
|
if (searchText.isEmpty()) return;
|
||||||
return;
|
final Command command = getIncsearchCommand(searchText);
|
||||||
|
if (command == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
searchCommand = true;
|
||||||
|
searchText = "";
|
||||||
|
final String argument = command.getCommandArgument();
|
||||||
|
if (argument.length() > 1) { // E.g. skip '/' in `:%s/`. `%` is range, `s` is command, `/` is argument
|
||||||
|
separator = argument.charAt(0);
|
||||||
|
searchText = argument.substring(1);
|
||||||
|
}
|
||||||
|
if (!searchText.isEmpty()) {
|
||||||
|
searchRange = command.getLineRangeSafe(new IjVimEditor(editor));
|
||||||
}
|
}
|
||||||
searchCommand = true;
|
if (searchText.isEmpty() || searchRange == null) {
|
||||||
searchText = "";
|
// Reset back to the original search highlights after deleting a search from a substitution command.Or if
|
||||||
final String argument = command.getCommandArgument();
|
// there is no search range (because the user entered an invalid range, e.g. mark not set).
|
||||||
if (argument.length() > 1) { // E.g. skip '/' in `:%s/`. `%` is range, `s` is command, `/` is argument
|
// E.g. Highlight `whatever`, type `:%s/foo` + highlight `foo`, delete back to `:%s/` and reset highlights
|
||||||
separator = argument.charAt(0);
|
// back to `whatever`
|
||||||
searchText = argument.substring(1);
|
VimPlugin.getSearch().resetIncsearchHighlights();
|
||||||
|
resetCaretOffset(editor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (searchText.length() == 0) {
|
|
||||||
// Reset back to the original search highlights after deleting a search from a substitution command.
|
// Get the current count from the command builder. This value is coerced to at least 1, so will always be valid.
|
||||||
// E.g. Highlight `whatever`, type `:%s/foo` + highlight `foo`, delete back to `:%s/` and reset highlights
|
// The aggregated value includes any counts for operator and register selections, and the uncommitted count for
|
||||||
// back to `whatever`
|
// the search command (`/` or `?`). E.g., `2"a3"b4"c5d6/` would return 720.
|
||||||
VimPlugin.getSearch().resetIncsearchHighlights();
|
// If we're showing highlights for an ex command like `:s`, there won't be a command, but the value is already
|
||||||
return;
|
// coerced to at least 1.
|
||||||
|
int count1 = KeyHandler.getInstance().getKeyHandlerState().getEditorCommandBuilder().getAggregatedUncommittedCount();
|
||||||
|
|
||||||
|
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
|
||||||
|
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
||||||
|
final String pattern;
|
||||||
|
final CharPointer p = new CharPointer(searchText);
|
||||||
|
final CharPointer end = RegExp.skip_regexp(new CharPointer(searchText), separator, true);
|
||||||
|
pattern = p.substring(end.pointer() - p.pointer());
|
||||||
|
|
||||||
|
VimPlugin.getEditor().closeEditorSearchSession(editor);
|
||||||
|
final int matchOffset =
|
||||||
|
SearchHighlightsHelper.updateIncsearchHighlights(editor, pattern, count1, forwards, caretOffset,
|
||||||
|
searchRange);
|
||||||
|
if (matchOffset != -1) {
|
||||||
|
new IjVimCaret(editor.getCaretModel().getPrimaryCaret()).moveToOffset(matchOffset);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
resetCaretOffset(editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
searchRange = command.getLineRange(new IjVimEditor(editor));
|
|
||||||
}
|
}
|
||||||
|
catch (Throwable ex) {
|
||||||
final String labelText = label.getText();
|
// Make sure the exception doesn't leak out of the handler, because it can break the text entry field and
|
||||||
if (labelText.equals("/") || labelText.equals("?") || searchCommand) {
|
// require the editor to be closed/reopened. The worst that will happen is no incsearch highlights
|
||||||
final boolean forwards = !labelText.equals("?"); // :s, :g, :v are treated as forwards
|
logger.warn("Error while trying to show incsearch highlights", ex);
|
||||||
final String pattern;
|
|
||||||
final CharPointer p = new CharPointer(searchText);
|
|
||||||
final CharPointer end = RegExp.skip_regexp(new CharPointer(searchText), separator, true);
|
|
||||||
pattern = p.substring(end.pointer() - p.pointer());
|
|
||||||
|
|
||||||
VimPlugin.getEditor().closeEditorSearchSession(editor);
|
|
||||||
final int matchOffset = SearchHighlightsHelper.updateIncsearchHighlights(editor, pattern, forwards, caretOffset, searchRange);
|
|
||||||
if (matchOffset != -1) {
|
|
||||||
new IjVimCaret(editor.getCaretModel().getPrimaryCaret()).moveToOffset(matchOffset);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
resetCaretOffset(editor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -307,8 +342,8 @@ public class ExEntryPanel extends JPanel {
|
|||||||
if (commandText == null) return null;
|
if (commandText == null) return null;
|
||||||
try {
|
try {
|
||||||
final Command exCommand = VimscriptParser.INSTANCE.parseCommand(commandText);
|
final Command exCommand = VimscriptParser.INSTANCE.parseCommand(commandText);
|
||||||
// TODO: Add global, vglobal, smagic and snomagic here when the commands are supported
|
// TODO: Add smagic and snomagic here if/when the commands are supported
|
||||||
if (exCommand instanceof SubstituteCommand) {
|
if (exCommand instanceof SubstituteCommand || exCommand instanceof GlobalCommand) {
|
||||||
return exCommand;
|
return exCommand;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -325,6 +360,7 @@ public class ExEntryPanel extends JPanel {
|
|||||||
*
|
*
|
||||||
* @return The ex entry label
|
* @return The ex entry label
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public String getLabel() {
|
public String getLabel() {
|
||||||
return label.getText();
|
return label.getText();
|
||||||
}
|
}
|
||||||
@ -352,10 +388,16 @@ public class ExEntryPanel extends JPanel {
|
|||||||
*
|
*
|
||||||
* @return The user entered text
|
* @return The user entered text
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public @NotNull String getText() {
|
public @NotNull String getText() {
|
||||||
return entry.getActualText();
|
return entry.getActualText();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setText(@NotNull String s) {
|
||||||
|
entry.setText(s);
|
||||||
|
}
|
||||||
|
|
||||||
public @NotNull ExTextField getEntry() {
|
public @NotNull ExTextField getEntry() {
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
@ -365,6 +407,7 @@ public class ExEntryPanel extends JPanel {
|
|||||||
*
|
*
|
||||||
* @param stroke The keystroke
|
* @param stroke The keystroke
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void handleKey(@NotNull KeyStroke stroke) {
|
public void handleKey(@NotNull KeyStroke stroke) {
|
||||||
entry.handleKey(stroke);
|
entry.handleKey(stroke);
|
||||||
}
|
}
|
||||||
@ -450,6 +493,12 @@ public class ExEntryPanel extends JPanel {
|
|||||||
|
|
||||||
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
private static final Logger logger = Logger.getInstance(ExEntryPanel.class.getName());
|
||||||
|
|
||||||
|
@NotNull
|
||||||
|
@Override
|
||||||
|
public VimCommandLineCaret getCaret() {
|
||||||
|
return (VimCommandLineCaret) entry.getCaret();
|
||||||
|
}
|
||||||
|
|
||||||
public static class LafListener implements LafManagerListener {
|
public static class LafListener implements LafManagerListener {
|
||||||
@Override
|
@Override
|
||||||
public void lookAndFeelChanged(@NotNull LafManager source) {
|
public void lookAndFeelChanged(@NotNull LafManager source) {
|
||||||
|
@ -1,27 +1,32 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2003-2023 The IdeaVim authors
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
*
|
*
|
||||||
* Use of this source code is governed by an MIT-style
|
* Use of this source code is governed by an MIT-style
|
||||||
* license that can be found in the LICENSE.txt file or at
|
* license that can be found in the LICENSE.txt file or at
|
||||||
* https://opensource.org/licenses/MIT.
|
* https://opensource.org/licenses/MIT.
|
||||||
*/
|
*/
|
||||||
|
package com.maddyhome.idea.vim.ui.ex
|
||||||
package com.maddyhome.idea.vim.helper
|
|
||||||
|
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.components.Service
|
|
||||||
import com.maddyhome.idea.vim.action.change.Extension
|
import com.maddyhome.idea.vim.action.change.Extension
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimCommandLine
|
||||||
|
import com.maddyhome.idea.vim.api.VimCommandLineService
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.helper.TestInputModel
|
||||||
|
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
|
||||||
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.ui.ModalEntry
|
import com.maddyhome.idea.vim.ui.ModalEntry
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
@Service
|
public class ExEntryPanelService : VimCommandLineService {
|
||||||
internal class CommandLineHelper : VimCommandLineHelper {
|
public override fun getActiveCommandLine(): VimCommandLine? {
|
||||||
|
return ExEntryPanel.instance
|
||||||
|
}
|
||||||
|
|
||||||
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
|
override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
|
||||||
val editor = vimEditor.ij
|
val editor = vimEditor.ij
|
||||||
@ -52,27 +57,26 @@ internal class CommandLineHelper : VimCommandLineHelper {
|
|||||||
} else {
|
} else {
|
||||||
var text: String? = null
|
var text: String? = null
|
||||||
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
|
// XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
|
||||||
val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts()
|
val commandLine = injector.commandLine.create(vimEditor, context, prompt.ifEmpty { " " }, "", 1)
|
||||||
exEntryPanel.activate(editor, context.ij, prompt.ifEmpty { " " }, "", 1)
|
|
||||||
ModalEntry.activate(editor.vim) { key: KeyStroke ->
|
ModalEntry.activate(editor.vim) { key: KeyStroke ->
|
||||||
return@activate when {
|
return@activate when {
|
||||||
key.isCloseKeyStroke() -> {
|
key.isCloseKeyStroke() -> {
|
||||||
exEntryPanel.deactivate(true)
|
commandLine.deactivate(true)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
key.keyCode == KeyEvent.VK_ENTER -> {
|
key.keyCode == KeyEvent.VK_ENTER -> {
|
||||||
text = exEntryPanel.text
|
text = commandLine.text
|
||||||
exEntryPanel.deactivate(true)
|
commandLine.deactivate(true)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
finishOn != null && key.keyChar == finishOn -> {
|
finishOn != null && key.keyChar == finishOn -> {
|
||||||
exEntryPanel.handleKey(key)
|
commandLine.handleKey(key)
|
||||||
text = exEntryPanel.text
|
text = commandLine.text
|
||||||
exEntryPanel.deactivate(true)
|
commandLine.deactivate(true)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
exEntryPanel.handleKey(key)
|
commandLine.handleKey(key)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,4 +87,10 @@ internal class CommandLineHelper : VimCommandLineHelper {
|
|||||||
return text
|
return text
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override fun create(editor: VimEditor, context: ExecutionContext, label: String, initText: String, count: Int): VimCommandLine {
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
panel.activate(editor.ij, context.ij, label, initText, count)
|
||||||
|
return panel
|
||||||
|
}
|
||||||
}
|
}
|
@ -13,6 +13,7 @@ import javax.swing.KeyStroke
|
|||||||
import javax.swing.text.DefaultEditorKit
|
import javax.swing.text.DefaultEditorKit
|
||||||
import javax.swing.text.JTextComponent.KeyBinding
|
import javax.swing.text.JTextComponent.KeyBinding
|
||||||
|
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal object ExKeyBindings {
|
internal object ExKeyBindings {
|
||||||
|
|
||||||
// TODO - add the following keys:
|
// TODO - add the following keys:
|
||||||
@ -71,8 +72,6 @@ internal object ExKeyBindings {
|
|||||||
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
|
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
|
||||||
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
|
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_Q, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.StartLiteral),
|
||||||
|
|
||||||
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_R, KeyEvent.CTRL_DOWN_MASK), ExEditorKit.InsertRegister),
|
|
||||||
|
|
||||||
// These appear to be non-Vim shortcuts
|
// These appear to be non-Vim shortcuts
|
||||||
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_DOWN_MASK), DefaultEditorKit.pasteAction),
|
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_V, KeyEvent.META_DOWN_MASK), DefaultEditorKit.pasteAction),
|
||||||
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_DOWN_MASK), DefaultEditorKit.pasteAction),
|
KeyBinding(KeyStroke.getKeyStroke(KeyEvent.VK_INSERT, KeyEvent.SHIFT_DOWN_MASK), DefaultEditorKit.pasteAction),
|
||||||
|
@ -29,6 +29,7 @@ import javax.swing.KeyStroke
|
|||||||
* component has focus. It registers all shortcuts used by the Swing actions and forwards them directly to the key
|
* component has focus. It registers all shortcuts used by the Swing actions and forwards them directly to the key
|
||||||
* handler.
|
* handler.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated("ExCommands should be migrated to KeyHandler like commands for other modes")
|
||||||
internal class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : DumbAwareAction() {
|
internal class ExShortcutKeyAction(private val exEntryPanel: ExEntryPanel) : DumbAwareAction() {
|
||||||
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
@ -14,6 +14,7 @@ import com.intellij.openapi.editor.Editor;
|
|||||||
import com.intellij.ui.paint.PaintUtil;
|
import com.intellij.ui.paint.PaintUtil;
|
||||||
import com.intellij.util.ui.JBUI;
|
import com.intellij.util.ui.JBUI;
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
import com.maddyhome.idea.vim.VimPlugin;
|
||||||
|
import com.maddyhome.idea.vim.api.VimCommandLineCaret;
|
||||||
import com.maddyhome.idea.vim.group.EditorHolderService;
|
import com.maddyhome.idea.vim.group.EditorHolderService;
|
||||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
import com.maddyhome.idea.vim.helper.UiHelper;
|
||||||
import com.maddyhome.idea.vim.history.HistoryConstants;
|
import com.maddyhome.idea.vim.history.HistoryConstants;
|
||||||
@ -408,7 +409,7 @@ public class ExTextField extends JTextField {
|
|||||||
caret.setAttributes(GuiCursorOptionHelper.INSTANCE.getAttributes(GuiCursorMode.CMD_LINE_REPLACE));
|
caret.setAttributes(GuiCursorOptionHelper.INSTANCE.getAttributes(GuiCursorMode.CMD_LINE_REPLACE));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class CommandLineCaret extends DefaultCaret {
|
private static class CommandLineCaret extends DefaultCaret implements VimCommandLineCaret {
|
||||||
|
|
||||||
private GuiCursorType mode;
|
private GuiCursorType mode;
|
||||||
private int thickness = 100;
|
private int thickness = 100;
|
||||||
@ -557,6 +558,16 @@ public class ExTextField extends JTextField {
|
|||||||
}
|
}
|
||||||
return fullHeight;
|
return fullHeight;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOffset() {
|
||||||
|
return getDot();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOffset(int i) {
|
||||||
|
setDot(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestOnly
|
@TestOnly
|
||||||
|
@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.api.VimEditor
|
|||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||||
@ -26,7 +26,7 @@ import java.util.*
|
|||||||
* @author smartbomb
|
* @author smartbomb
|
||||||
*/
|
*/
|
||||||
@ExCommand(command = "actionl[ist]")
|
@ExCommand(command = "actionl[ist]")
|
||||||
internal data class ActionListCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
|
internal data class ActionListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||||
|
|
||||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||||
|
@ -15,7 +15,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
|||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
@ -27,7 +27,7 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
|||||||
* @author John Weigel
|
* @author John Weigel
|
||||||
*/
|
*/
|
||||||
@ExCommand(command = "b[uffer]")
|
@ExCommand(command = "b[uffer]")
|
||||||
internal data class BufferCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
|
internal data class BufferCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||||
|
|
||||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||||
|
@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.api.ExecutionContext
|
|||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.vimLine
|
import com.maddyhome.idea.vim.helper.vimLine
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
@ -32,7 +32,7 @@ import org.jetbrains.annotations.NonNls
|
|||||||
* @author John Weigel
|
* @author John Weigel
|
||||||
*/
|
*/
|
||||||
@ExCommand(command = "ls,files,buffers")
|
@ExCommand(command = "ls,files,buffers")
|
||||||
internal data class BufferListCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
|
internal data class BufferListCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||||
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
override val argFlags = flags(RangeFlag.RANGE_FORBIDDEN, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -18,7 +18,8 @@ import com.maddyhome.idea.vim.api.VimEditor
|
|||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ExException
|
import com.maddyhome.idea.vim.ex.ExException
|
||||||
import com.maddyhome.idea.vim.ex.ExOutputModel
|
import com.maddyhome.idea.vim.ex.ExOutputModel
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.toTextRange
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
@ -28,7 +29,7 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
|||||||
* see "h :!"
|
* see "h :!"
|
||||||
*/
|
*/
|
||||||
@ExCommand(command = "!")
|
@ExCommand(command = "!")
|
||||||
internal data class CmdFilterCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges) {
|
internal data class CmdFilterCommand(val range: Range, val argument: String) : Command.SingleExecution(range) {
|
||||||
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
|
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
|
||||||
|
|
||||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||||
@ -70,7 +71,7 @@ internal data class CmdFilterCommand(val ranges: Ranges, val argument: String) :
|
|||||||
|
|
||||||
val workingDirectory = editor.ij.project?.basePath
|
val workingDirectory = editor.ij.project?.basePath
|
||||||
return try {
|
return try {
|
||||||
if (ranges.size() == 0) {
|
if (range.size() == 0) {
|
||||||
// Show command output in a window
|
// Show command output in a window
|
||||||
VimPlugin.getProcess().executeCommand(editor, command, null, workingDirectory)?.let {
|
VimPlugin.getProcess().executeCommand(editor, command, null, workingDirectory)?.let {
|
||||||
ExOutputModel.getInstance(editor.ij).output(it)
|
ExOutputModel.getInstance(editor.ij).output(it)
|
||||||
@ -78,7 +79,7 @@ internal data class CmdFilterCommand(val ranges: Ranges, val argument: String) :
|
|||||||
ExecutionResult.Success
|
ExecutionResult.Success
|
||||||
} else {
|
} else {
|
||||||
// Filter
|
// Filter
|
||||||
val range = this.getTextRange(editor, false)
|
val range = getLineRange(editor).toTextRange(editor)
|
||||||
val input = editor.ij.document.charsSequence.subSequence(range.startOffset, range.endOffset)
|
val input = editor.ij.document.charsSequence.subSequence(range.startOffset, range.endOffset)
|
||||||
VimPlugin.getProcess().executeCommand(editor, command, input, workingDirectory)?.let {
|
VimPlugin.getProcess().executeCommand(editor, command, input, workingDirectory)?.let {
|
||||||
ApplicationManager.getApplication().runWriteAction {
|
ApplicationManager.getApplication().runWriteAction {
|
||||||
|
@ -13,11 +13,12 @@ import com.intellij.vim.annotations.ExCommand
|
|||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.VimSearchGroupBase
|
||||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
import com.maddyhome.idea.vim.api.getLineStartForOffset
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
import com.maddyhome.idea.vim.ex.ranges.LineRange
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup
|
import com.maddyhome.idea.vim.group.SearchGroup
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup.RE_BOTH
|
import com.maddyhome.idea.vim.group.SearchGroup.RE_BOTH
|
||||||
import com.maddyhome.idea.vim.group.SearchGroup.RE_LAST
|
import com.maddyhome.idea.vim.group.SearchGroup.RE_LAST
|
||||||
@ -29,7 +30,6 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
|
|||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.regexp.CharPointer
|
import com.maddyhome.idea.vim.regexp.CharPointer
|
||||||
import com.maddyhome.idea.vim.regexp.RegExp
|
import com.maddyhome.idea.vim.regexp.RegExp
|
||||||
import com.maddyhome.idea.vim.regexp.VimRegex
|
|
||||||
import com.maddyhome.idea.vim.regexp.VimRegexException
|
import com.maddyhome.idea.vim.regexp.VimRegexException
|
||||||
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
|
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
|
||||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||||
@ -38,20 +38,20 @@ import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
|||||||
* see "h :global" / "h :vglobal"
|
* see "h :global" / "h :vglobal"
|
||||||
*/
|
*/
|
||||||
@ExCommand(command = "g[lobal],v[global]")
|
@ExCommand(command = "g[lobal],v[global]")
|
||||||
internal data class GlobalCommand(val ranges: Ranges, val argument: String, val invert: Boolean) : Command.SingleExecution(ranges, argument) {
|
internal data class GlobalCommand(val range: Range, val argument: String, val invert: Boolean) : Command.SingleExecution(range, argument) {
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Most commands have a default range of the current line ("."). Global has a default range of the whole file
|
||||||
|
defaultRange = "%"
|
||||||
|
}
|
||||||
|
|
||||||
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
|
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.SELF_SYNCHRONIZED)
|
||||||
|
|
||||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||||
var result: ExecutionResult = ExecutionResult.Success
|
var result: ExecutionResult = ExecutionResult.Success
|
||||||
editor.removeSecondaryCarets()
|
editor.removeSecondaryCarets()
|
||||||
val caret = editor.currentCaret()
|
val caret = editor.currentCaret()
|
||||||
|
val lineRange = getLineRange(editor, caret)
|
||||||
// For :g command the default range is %
|
|
||||||
val lineRange: LineRange = if (ranges.size() == 0) {
|
|
||||||
LineRange(0, editor.lineCount() - 1)
|
|
||||||
} else {
|
|
||||||
getLineRange(editor, caret)
|
|
||||||
}
|
|
||||||
if (!processGlobalCommand(editor, context, lineRange)) {
|
if (!processGlobalCommand(editor, context, lineRange)) {
|
||||||
result = ExecutionResult.Error
|
result = ExecutionResult.Error
|
||||||
}
|
}
|
||||||
@ -106,7 +106,7 @@ internal data class GlobalCommand(val ranges: Ranges, val argument: String, val
|
|||||||
|
|
||||||
if (injector.globalIjOptions().useNewRegex) {
|
if (injector.globalIjOptions().useNewRegex) {
|
||||||
val regex = try {
|
val regex = try {
|
||||||
VimRegex(pat.toString())
|
(injector.searchGroup as VimSearchGroupBase).prepareRegex(pat, whichPat, RE_BOTH)
|
||||||
} catch (e: VimRegexException) {
|
} catch (e: VimRegexException) {
|
||||||
injector.messages.showStatusBarMessage(editor, e.message)
|
injector.messages.showStatusBarMessage(editor, e.message)
|
||||||
return false
|
return false
|
||||||
|
@ -13,7 +13,7 @@ import com.intellij.vim.annotations.ExCommand
|
|||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
import java.io.UnsupportedEncodingException
|
import java.io.UnsupportedEncodingException
|
||||||
@ -24,7 +24,7 @@ import java.net.URLEncoder
|
|||||||
* see "h :help"
|
* see "h :help"
|
||||||
*/
|
*/
|
||||||
@ExCommand(command = "h[elp]")
|
@ExCommand(command = "h[elp]")
|
||||||
internal data class HelpCommand(val ranges: Ranges, val argument: String) : Command.SingleExecution(ranges, argument) {
|
internal data class HelpCommand(val range: Range, val argument: String) : Command.SingleExecution(range, argument) {
|
||||||
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
override val argFlags = flags(RangeFlag.RANGE_OPTIONAL, ArgumentFlag.ARGUMENT_OPTIONAL, Access.READ_ONLY)
|
||||||
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
override fun processCommand(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments): ExecutionResult {
|
||||||
BrowserUtil.browse(helpTopicUrl(argument))
|
BrowserUtil.browse(helpTopicUrl(argument))
|
||||||
|
@ -106,13 +106,14 @@ private fun variableToPosition(editor: VimEditor, variable: VimDataType, dollarF
|
|||||||
|
|
||||||
// Visual start
|
// Visual start
|
||||||
if (name == "v") {
|
if (name == "v") {
|
||||||
if (editor.inVisualMode) {
|
if (!editor.inVisualMode) {
|
||||||
return editor.ij.vimLine.asVimInt() to currentCol(editor)
|
return editor.ij.vimLine.asVimInt() to currentCol(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
val vimStart = editor.currentCaret().vimSelectionStart
|
val vimStart = editor.currentCaret().vimSelectionStart
|
||||||
val visualLine = (editor.offsetToBufferPosition(vimStart).line + 1).asVimInt()
|
val bufferPosition = editor.offsetToBufferPosition(vimStart)
|
||||||
val visualCol = (editor.offsetToBufferPosition(vimStart).column + 1).asVimInt()
|
val visualLine = (bufferPosition.line + 1).asVimInt()
|
||||||
|
val visualCol = (bufferPosition.column + 1).asVimInt()
|
||||||
|
|
||||||
return visualLine to visualCol
|
return visualLine to visualCol
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,9 @@ import com.intellij.openapi.diagnostic.logger
|
|||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.ex.ExException
|
import com.maddyhome.idea.vim.ex.ExException
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.Address
|
||||||
|
import com.maddyhome.idea.vim.ex.ranges.Address.Companion.createRangeAddresses
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Range
|
import com.maddyhome.idea.vim.ex.ranges.Range
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Range.Companion.createRange
|
|
||||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
|
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.ActionCommand
|
import com.maddyhome.idea.vim.vimscript.model.commands.ActionCommand
|
||||||
import com.maddyhome.idea.vim.vimscript.model.commands.ActionListCommand
|
import com.maddyhome.idea.vim.vimscript.model.commands.ActionListCommand
|
||||||
@ -147,140 +147,145 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseRangesUnit(ctx: VimscriptParser.RangeUnitContext): Array<Range> {
|
private fun parseRangeUnit(ctx: VimscriptParser.RangeUnitContext): Array<Address> {
|
||||||
val valueAndOffset = parseRangeExpression(ctx.rangeExpression())
|
val valueAndOffset = parseRangeExpression(ctx.rangeExpression())
|
||||||
val move = ctx.rangeSeparator()?.text == ";"
|
val move = ctx.rangeSeparator()?.text == ";"
|
||||||
val ranges = createRange(valueAndOffset.first, valueAndOffset.second, move)
|
val addresses = createRangeAddresses(valueAndOffset.first, valueAndOffset.second, move)
|
||||||
if (ranges == null) {
|
if (addresses == null) {
|
||||||
logger.warn("Could not create a range for node ${ctx.text}")
|
logger.warn("Could not create an address for node ${ctx.text}")
|
||||||
throw ExException("Could not create a range ${ctx.text}")
|
throw ExException("Could not create an address ${ctx.text}")
|
||||||
}
|
}
|
||||||
return ranges
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseRanges(ctx: RangeContext?): Ranges {
|
private fun parseRange(ctx: RangeContext?): Range {
|
||||||
val ranges = Ranges()
|
val range = Range()
|
||||||
if (ctx?.rangeUnit() != null) {
|
if (ctx?.rangeUnit() != null) {
|
||||||
for (unit in ctx.rangeUnit()) {
|
val addresses = ctx.rangeUnit()
|
||||||
ranges.addRange(parseRangesUnit(unit))
|
for (unit in addresses) {
|
||||||
|
range.addAddresses(parseRangeUnit(unit))
|
||||||
|
}
|
||||||
|
// If the range ends with a dangling separator, the last address is an implied current line address
|
||||||
|
if (addresses.last().rangeSeparator()?.text == ",") {
|
||||||
|
createRangeAddresses(".", 0, false)?.let { range.addAddresses(it) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ranges
|
return range
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitLet1Command(ctx: VimscriptParser.Let1CommandContext): Command {
|
override fun visitLet1Command(ctx: VimscriptParser.Let1CommandContext): Command {
|
||||||
val ranges: Ranges = parseRanges(ctx.range())
|
val range: Range = parseRange(ctx.range())
|
||||||
val variable: Expression = expressionVisitor.visit(ctx.expr(0))
|
val variable: Expression = expressionVisitor.visit(ctx.expr(0))
|
||||||
val operator = getByValue(ctx.assignmentOperator().text)
|
val operator = getByValue(ctx.assignmentOperator().text)
|
||||||
val expression: Expression = expressionVisitor.visit(ctx.expr(1))
|
val expression: Expression = expressionVisitor.visit(ctx.expr(1))
|
||||||
val command = LetCommand(ranges, variable, operator, expression, true)
|
val command = LetCommand(range, variable, operator, expression, true)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitLet2Command(ctx: VimscriptParser.Let2CommandContext): Command {
|
override fun visitLet2Command(ctx: VimscriptParser.Let2CommandContext): Command {
|
||||||
val command = LetCommand(Ranges(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
|
val command = LetCommand(Range(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitEchoCommand(ctx: EchoCommandContext): Command {
|
override fun visitEchoCommand(ctx: EchoCommandContext): Command {
|
||||||
val ranges: Ranges = parseRanges(ctx.range())
|
val range: Range = parseRange(ctx.range())
|
||||||
val expressions = ctx.expr().stream()
|
val expressions = ctx.expr().stream()
|
||||||
.map { tree: ExprContext ->
|
.map { tree: ExprContext ->
|
||||||
expressionVisitor.visit(tree)
|
expressionVisitor.visit(tree)
|
||||||
}
|
}
|
||||||
.collect(Collectors.toList())
|
.collect(Collectors.toList())
|
||||||
val command = EchoCommand(ranges, expressions)
|
val command = EchoCommand(range, expressions)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCallCommand(ctx: CallCommandContext): Command {
|
override fun visitCallCommand(ctx: CallCommandContext): Command {
|
||||||
val ranges: Ranges = parseRanges(ctx.range())
|
val range: Range = parseRange(ctx.range())
|
||||||
val functionCall = ExpressionVisitor.visit(ctx.expr())
|
val functionCall = ExpressionVisitor.visit(ctx.expr())
|
||||||
val command = CallCommand(ranges, functionCall)
|
val command = CallCommand(range, functionCall)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitDelfunctionCommand(ctx: DelfunctionCommandContext): DelfunctionCommand {
|
override fun visitDelfunctionCommand(ctx: DelfunctionCommandContext): DelfunctionCommand {
|
||||||
val ranges: Ranges = parseRanges(ctx.range())
|
val range: Range = parseRange(ctx.range())
|
||||||
val functionScope =
|
val functionScope =
|
||||||
if (ctx.functionScope() != null) Scope.getByValue(ctx.functionScope().text) else null
|
if (ctx.functionScope() != null) Scope.getByValue(ctx.functionScope().text) else null
|
||||||
val functionName = ctx.functionName().text
|
val functionName = ctx.functionName().text
|
||||||
val ignoreIfMissing = ctx.replace != null
|
val ignoreIfMissing = ctx.replace != null
|
||||||
val command = DelfunctionCommand(ranges, functionScope, functionName, ignoreIfMissing)
|
val command = DelfunctionCommand(range, functionScope, functionName, ignoreIfMissing)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitGoToLineCommand(ctx: VimscriptParser.GoToLineCommandContext): Command {
|
override fun visitGoToLineCommand(ctx: VimscriptParser.GoToLineCommandContext): Command {
|
||||||
val ranges: Ranges
|
val range: Range
|
||||||
if (ctx.range() != null) {
|
if (ctx.range() != null) {
|
||||||
ranges = parseRanges(ctx.range())
|
range = parseRange(ctx.range())
|
||||||
} else {
|
} else {
|
||||||
ranges = Ranges()
|
range = Range()
|
||||||
ranges.addRange(
|
range.addAddresses(
|
||||||
createRange(ctx.shortRange().text, 0, false)
|
createRangeAddresses(ctx.shortRange().text, 0, false)
|
||||||
?: throw ExException("Could not create a range"),
|
?: throw ExException("Could not create a range"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val command = GoToLineCommand(ranges)
|
val command = GoToLineCommand(range)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCommandWithComment(ctx: VimscriptParser.CommandWithCommentContext): Command {
|
override fun visitCommandWithComment(ctx: VimscriptParser.CommandWithCommentContext): Command {
|
||||||
val ranges = parseRanges(ctx.range())
|
val ranges = parseRange(ctx.range())
|
||||||
val commandName = ctx.name.text
|
val commandName = ctx.name.text
|
||||||
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
|
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
|
||||||
return createCommandByCommandContext(ranges, argument, commandName, ctx)
|
return createCommandByCommandContext(ranges, argument, commandName, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCommandWithoutComments(ctx: VimscriptParser.CommandWithoutCommentsContext): Command {
|
override fun visitCommandWithoutComments(ctx: VimscriptParser.CommandWithoutCommentsContext): Command {
|
||||||
val ranges = parseRanges(ctx.range())
|
val ranges = parseRange(ctx.range())
|
||||||
val commandName = ctx.name.text
|
val commandName = ctx.name.text
|
||||||
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
|
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
|
||||||
return createCommandByCommandContext(ranges, argument, commandName, ctx)
|
return createCommandByCommandContext(ranges, argument, commandName, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitCommandWithBars(ctx: VimscriptParser.CommandWithBarsContext): Command {
|
override fun visitCommandWithBars(ctx: VimscriptParser.CommandWithBarsContext): Command {
|
||||||
val ranges = parseRanges(ctx.range())
|
val ranges = parseRange(ctx.range())
|
||||||
val commandName = ctx.name.text
|
val commandName = ctx.name.text
|
||||||
val argument = ctx.commandArgumentWithBars()?.text ?: ""
|
val argument = ctx.commandArgumentWithBars()?.text ?: ""
|
||||||
return createCommandByCommandContext(ranges, argument, commandName, ctx)
|
return createCommandByCommandContext(ranges, argument, commandName, ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createCommandByCommandContext(ranges: Ranges, argument: String, commandName: String, ctx: ParserRuleContext): Command {
|
private fun createCommandByCommandContext(range: Range, argument: String, commandName: String, ctx: ParserRuleContext): Command {
|
||||||
val command = when (getCommandByName(commandName)) {
|
val command = when (getCommandByName(commandName)) {
|
||||||
MapCommand::class -> MapCommand(ranges, argument, commandName)
|
MapCommand::class -> MapCommand(range, argument, commandName)
|
||||||
MapClearCommand::class -> MapClearCommand(ranges, argument, commandName)
|
MapClearCommand::class -> MapClearCommand(range, argument, commandName)
|
||||||
UnMapCommand::class -> UnMapCommand(ranges, argument, commandName)
|
UnMapCommand::class -> UnMapCommand(range, argument, commandName)
|
||||||
GlobalCommand::class -> {
|
GlobalCommand::class -> {
|
||||||
if (commandName.startsWith("v")) {
|
if (commandName.startsWith("v")) {
|
||||||
GlobalCommand(ranges, argument, true)
|
GlobalCommand(range, argument, true)
|
||||||
} else {
|
} else {
|
||||||
if (argument.startsWith("!")) GlobalCommand(ranges, argument.substring(1), true) else GlobalCommand(ranges, argument, false)
|
if (argument.startsWith("!")) GlobalCommand(range, argument.substring(1), true) else GlobalCommand(range, argument, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SplitCommand::class -> {
|
SplitCommand::class -> {
|
||||||
if (commandName.startsWith("v")) {
|
if (commandName.startsWith("v")) {
|
||||||
SplitCommand(ranges, argument, SplitType.VERTICAL)
|
SplitCommand(range, argument, SplitType.VERTICAL)
|
||||||
} else {
|
} else {
|
||||||
SplitCommand(ranges, argument, SplitType.HORIZONTAL)
|
SplitCommand(range, argument, SplitType.HORIZONTAL)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SubstituteCommand::class -> SubstituteCommand(ranges, argument, commandName)
|
SubstituteCommand::class -> SubstituteCommand(range, argument, commandName)
|
||||||
else -> getCommandByName(commandName).primaryConstructor!!.call(ranges, argument)
|
else -> getCommandByName(commandName).primaryConstructor!!.call(range, argument)
|
||||||
}
|
}
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitShiftLeftCommand(ctx: VimscriptParser.ShiftLeftCommandContext): ShiftLeftCommand {
|
override fun visitShiftLeftCommand(ctx: VimscriptParser.ShiftLeftCommandContext): ShiftLeftCommand {
|
||||||
val ranges = parseRanges(ctx.range())
|
val ranges = parseRange(ctx.range())
|
||||||
val argument = (ctx.commandArgument?.text ?: "").trim()
|
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
|
||||||
val length = ctx.lShift().text.length
|
val length = ctx.lShift().text.length
|
||||||
val command = ShiftLeftCommand(ranges, argument, length)
|
val command = ShiftLeftCommand(ranges, argument, length)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
@ -288,8 +293,8 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitShiftRightCommand(ctx: VimscriptParser.ShiftRightCommandContext): ShiftRightCommand {
|
override fun visitShiftRightCommand(ctx: VimscriptParser.ShiftRightCommandContext): ShiftRightCommand {
|
||||||
val ranges = parseRanges(ctx.range())
|
val ranges = parseRange(ctx.range())
|
||||||
val argument = (ctx.commandArgument?.text ?: "").trim()
|
val argument = ctx.commandArgumentWithoutBars()?.text ?: ""
|
||||||
val length = ctx.rShift().text.length
|
val length = ctx.rShift().text.length
|
||||||
val command = ShiftRightCommand(ranges, argument, length)
|
val command = ShiftRightCommand(ranges, argument, length)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
@ -297,7 +302,7 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitExecuteCommand(ctx: VimscriptParser.ExecuteCommandContext): ExecuteCommand {
|
override fun visitExecuteCommand(ctx: VimscriptParser.ExecuteCommandContext): ExecuteCommand {
|
||||||
val ranges = parseRanges(ctx.range())
|
val ranges = parseRange(ctx.range())
|
||||||
val expressions = ctx.expr().stream()
|
val expressions = ctx.expr().stream()
|
||||||
.map { tree: ExprContext ->
|
.map { tree: ExprContext ->
|
||||||
expressionVisitor.visit(tree)
|
expressionVisitor.visit(tree)
|
||||||
@ -309,26 +314,26 @@ internal object CommandVisitor : VimscriptBaseVisitor<Command>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun visitLetCommand(ctx: VimscriptParser.LetCommandContext): Command {
|
override fun visitLetCommand(ctx: VimscriptParser.LetCommandContext): Command {
|
||||||
val command = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseLetCommand(ctx.text) ?: LetCommand(Ranges(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
|
val command = com.maddyhome.idea.vim.vimscript.parser.VimscriptParser.parseLetCommand(ctx.text) ?: LetCommand(Range(), SimpleExpression(0), AssignmentOperator.ASSIGNMENT, SimpleExpression(0), false)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitOtherCommand(ctx: OtherCommandContext): Command {
|
override fun visitOtherCommand(ctx: OtherCommandContext): Command {
|
||||||
val ranges: Ranges = parseRanges(ctx.range())
|
val range: Range = parseRange(ctx.range())
|
||||||
val name = ctx.commandName().text
|
val name = ctx.commandName().text
|
||||||
val argument = ctx.commandArgumentWithBars()?.text ?: ""
|
val argument = ctx.commandArgumentWithBars()?.text ?: ""
|
||||||
|
|
||||||
val alphabeticPart = name.split(Regex("\\P{Alpha}"))[0]
|
val alphabeticPart = name.split(Regex("\\P{Alpha}"))[0]
|
||||||
if (setOf("s", "su", "sub", "subs", "subst", "substi", "substit", "substitu", "substitut", "substitut", "substitute").contains(alphabeticPart)) {
|
if (setOf("s", "su", "sub", "subs", "subst", "substi", "substit", "substitu", "substitut", "substitut", "substitute").contains(alphabeticPart)) {
|
||||||
val command = SubstituteCommand(ranges, name.replaceFirst(alphabeticPart, "") + argument, alphabeticPart)
|
val command = SubstituteCommand(range, name.replaceFirst(alphabeticPart, "") + argument, alphabeticPart)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
val commandConstructor = getCommandByName(name).constructors
|
val commandConstructor = getCommandByName(name).constructors
|
||||||
.filter { it.parameters.size == 2 }
|
.filter { it.parameters.size == 2 }
|
||||||
.firstOrNull { it.parameters[0].type == Ranges::class.createType() && it.parameters[1].type == String::class.createType() }
|
.firstOrNull { it.parameters[0].type == Range::class.createType() && it.parameters[1].type == String::class.createType() }
|
||||||
val command = commandConstructor?.call(ranges, argument) ?: UnknownCommand(ranges, name, argument)
|
val command = commandConstructor?.call(range, argument) ?: UnknownCommand(range, name, argument)
|
||||||
command.rangeInScript = ctx.getTextRange()
|
command.rangeInScript = ctx.getTextRange()
|
||||||
return command
|
return command
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@
|
|||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.SearchGroup"/>
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.SearchGroup"/>
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.ProcessGroup"
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.ProcessGroup"
|
||||||
serviceInterface="com.maddyhome.idea.vim.api.VimProcessGroup"/>
|
serviceInterface="com.maddyhome.idea.vim.api.VimProcessGroup"/>
|
||||||
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.ui.ex.ExEntryPanelService"
|
||||||
|
serviceInterface="com.maddyhome.idea.vim.api.VimCommandLineService"/>
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup"
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.DigraphGroup"
|
||||||
serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/>
|
serviceInterface="com.maddyhome.idea.vim.api.VimDigraphGroup"/>
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/>
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.HistoryGroup"/>
|
||||||
@ -47,6 +49,8 @@
|
|||||||
serviceInterface="com.maddyhome.idea.vim.api.VimStorageService"/>
|
serviceInterface="com.maddyhome.idea.vim.api.VimStorageService"/>
|
||||||
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.IjVimSystemInfoService"
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.IjVimSystemInfoService"
|
||||||
serviceInterface="com.maddyhome.idea.vim.api.SystemInfoService"/>
|
serviceInterface="com.maddyhome.idea.vim.api.SystemInfoService"/>
|
||||||
|
<applicationService serviceImplementation="com.maddyhome.idea.vim.group.IjVimRedrawService"
|
||||||
|
serviceInterface="com.maddyhome.idea.vim.api.VimRedrawService"/>
|
||||||
|
|
||||||
</extensions>
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
<!--
|
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
~ Copyright 2003-2023 The IdeaVim authors
|
|
||||||
~
|
|
||||||
~ Use of this source code is governed by an MIT-style
|
|
||||||
~ license that can be found in the LICENSE.txt file or at
|
|
||||||
~ https://opensource.org/licenses/MIT.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
|
|
||||||
<name>IdeaVim</name>
|
<name>IdeaVim</name>
|
||||||
<id>IdeaVIM</id>
|
<id>IdeaVIM</id>
|
||||||
<description><![CDATA[
|
<description><![CDATA[
|
||||||
@ -21,13 +13,13 @@
|
|||||||
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
|
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
|
||||||
</ul>
|
</ul>
|
||||||
]]></description>
|
]]></description>
|
||||||
<version>SNAPSHOT</version>
|
<version>chylex</version>
|
||||||
<vendor>JetBrains</vendor>
|
<vendor>JetBrains</vendor>
|
||||||
|
|
||||||
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
|
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
|
||||||
<!-- Check for [Version Update] tag in YouTrack as well -->
|
<!-- Check for [Version Update] tag in YouTrack as well -->
|
||||||
<!-- Also, please update the value in build.gradle.kts file-->
|
<!-- Also, please update the value in build.gradle.kts file-->
|
||||||
<idea-version since-build="233.11799.67"/>
|
<idea-version since-build="241.15989.150"/>
|
||||||
|
|
||||||
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
|
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
|
||||||
<depends>com.intellij.modules.platform</depends>
|
<depends>com.intellij.modules.platform</depends>
|
||||||
@ -159,5 +151,6 @@
|
|||||||
</group>
|
</group>
|
||||||
|
|
||||||
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
|
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
|
||||||
|
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
|
||||||
</actions>
|
</actions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
|
@ -2,24 +2,31 @@ ideavim
|
|||||||
ideavimrc
|
ideavimrc
|
||||||
maddyhome
|
maddyhome
|
||||||
|
|
||||||
|
setglobal
|
||||||
|
setlocal
|
||||||
|
vglobal
|
||||||
|
|
||||||
|
breakindent
|
||||||
|
colorcolumn
|
||||||
|
cursorline
|
||||||
|
fileencoding
|
||||||
|
fileformat
|
||||||
gdefault
|
gdefault
|
||||||
guicursor
|
guicursor
|
||||||
hlsearch
|
hlsearch
|
||||||
ideamarks
|
|
||||||
ignorecase
|
ignorecase
|
||||||
incsearch
|
incsearch
|
||||||
iskeyword
|
iskeyword
|
||||||
keymodel
|
keymodel
|
||||||
lookupKeys
|
lookupKeys
|
||||||
matchpairs
|
|
||||||
mapleader
|
mapleader
|
||||||
|
matchpairs
|
||||||
|
maxmapdepth
|
||||||
nrformats
|
nrformats
|
||||||
relativenumber
|
relativenumber
|
||||||
scrolljump
|
scrolljump
|
||||||
scrolloff
|
scrolloff
|
||||||
selectmode
|
selectmode
|
||||||
setglobal
|
|
||||||
setlocal
|
|
||||||
shellcmdflag
|
shellcmdflag
|
||||||
shellxescape
|
shellxescape
|
||||||
shellxquote
|
shellxquote
|
||||||
@ -29,25 +36,47 @@ sidescroll
|
|||||||
sidescrolloff
|
sidescrolloff
|
||||||
smartcase
|
smartcase
|
||||||
startofline
|
startofline
|
||||||
ideajoin
|
swapfile
|
||||||
|
textwidth
|
||||||
timeoutlen
|
timeoutlen
|
||||||
undolevels
|
undolevels
|
||||||
viminfo
|
viminfo
|
||||||
virtualedit
|
virtualedit
|
||||||
visualbell
|
visualbell
|
||||||
wrapscan
|
|
||||||
visualdelay
|
visualdelay
|
||||||
|
wrapscan
|
||||||
|
|
||||||
|
nobomb
|
||||||
|
nobreakindent
|
||||||
|
nocursorline
|
||||||
|
nodigraph
|
||||||
|
nogdefault
|
||||||
|
nohlsearch
|
||||||
|
noignorecase
|
||||||
|
noincsearch
|
||||||
|
nolist
|
||||||
|
nomore
|
||||||
|
nonumber
|
||||||
|
norelativenumber
|
||||||
|
noshowcmd
|
||||||
|
noshowmode
|
||||||
|
nosmartcase
|
||||||
|
nostartofline
|
||||||
|
notimeout
|
||||||
|
novisualbell
|
||||||
|
nowrap
|
||||||
|
nowrapscan
|
||||||
|
|
||||||
|
ideacopypreprocess
|
||||||
|
ideaglobalmode
|
||||||
|
ideajoin
|
||||||
|
ideamarks
|
||||||
idearefactormode
|
idearefactormode
|
||||||
ideastatusicon
|
ideastatusicon
|
||||||
ideastrictmode
|
ideastrictmode
|
||||||
ideawrite
|
|
||||||
ideavimsupport
|
|
||||||
maxmapdepth
|
|
||||||
ideacopypreprocess
|
|
||||||
ideatracetime
|
ideatracetime
|
||||||
swapfile
|
ideavimsupport
|
||||||
noswapfile
|
ideawrite
|
||||||
ideaglobalmode
|
|
||||||
|
|
||||||
sethandler
|
sethandler
|
||||||
packadd
|
packadd
|
||||||
@ -149,4 +178,4 @@ mauris
|
|||||||
Cras
|
Cras
|
||||||
tellus
|
tellus
|
||||||
imperdiet
|
imperdiet
|
||||||
egestas
|
egestas
|
||||||
|
@ -8,22 +8,28 @@
|
|||||||
|
|
||||||
notexcmd=Not an editor command: {0}
|
notexcmd=Not an editor command: {0}
|
||||||
intbadcmd=Internal error - invalid command: {0}
|
intbadcmd=Internal error - invalid command: {0}
|
||||||
e_backslash=E10: \\ should be followed by /, ? or &
|
|
||||||
e_badrange=Unexpected character ''{0}'' in range
|
e_badrange=Unexpected character ''{0}'' in range
|
||||||
e_norange=No range allowed
|
|
||||||
e_rangereq=Range required
|
e_rangereq=Range required
|
||||||
e_argreq=Argument required
|
e_argreq=Argument required
|
||||||
e_argforb=Argument forbidden
|
e_argforb=Argument forbidden
|
||||||
e_nopresub=E33: No previous substitute regular expression
|
|
||||||
e_noprev=E34: No previous command
|
|
||||||
e_noprevre=E35: No previous regular expression
|
|
||||||
E191=E191: Argument must be a letter or forward/backward quote
|
|
||||||
e_backrange=Backwards range given
|
e_backrange=Backwards range given
|
||||||
E146=E146: Regular expressions can''t be delimited by letters
|
|
||||||
e_zerocount=Zero count
|
e_zerocount=Zero count
|
||||||
e_trailing=Trailing characters
|
e_trailing=Trailing characters
|
||||||
e_invcmd=Invalid command
|
e_invcmd=Invalid command
|
||||||
e_null=Null argument
|
e_null=Null argument
|
||||||
|
e_internal=Internal error
|
||||||
|
synerror=Syntax error in {0}{...}
|
||||||
|
unkopt=Unknown option: {0}
|
||||||
|
e_invarg=Invalid argument: {0}
|
||||||
|
|
||||||
|
e_backslash=E10: \\ should be followed by /, ? or &
|
||||||
|
e_invrange=E16: Invalid range
|
||||||
|
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
|
||||||
|
e_re_damg=E43: Damaged match string
|
||||||
|
e_re_corr=E44: Currupted regexp program
|
||||||
E50=E50: Too many \\z(
|
E50=E50: Too many \\z(
|
||||||
E51=E51: Too many {0}(
|
E51=E51: Too many {0}(
|
||||||
E52=E52: Unmatched \\z(
|
E52=E52: Unmatched \\z(
|
||||||
@ -46,23 +52,26 @@ E68=E68: Invalid character after \\z
|
|||||||
E69=E69: Missing ] after {0}%[
|
E69=E69: Missing ] after {0}%[
|
||||||
E70=E70: Empty {0}%[]
|
E70=E70: Empty {0}%[]
|
||||||
E71=E71: Invalid character after {0}%
|
E71=E71: Invalid character after {0}%
|
||||||
|
e_toomsbra=E76: Too many [
|
||||||
|
E146=E146: Regular expressions can''t be delimited by letters
|
||||||
E147=E147: Cannot do :global recursive with a range
|
E147=E147: Cannot do :global recursive with a range
|
||||||
E148=E148: Regular expression missing from global
|
E148=E148: Regular expression missing from global
|
||||||
e_invrange=E16: Invalid range
|
e174.command.already.exists.add.to.replace.it=E174: Command already exists: add ! to replace it
|
||||||
e_toomsbra=E76: Too many [
|
e176.invalid.number.of.arguments=E176: Invalid number of arguments
|
||||||
e_internal=Internal error
|
e183.user.defined.commands.must.start.with.an.uppercase.letter=E183: User defined commands must start with an uppercase letter
|
||||||
synerror=Syntax error in {0}{...}
|
e184.no.such.user.defined.command.0=E184: No such user-defined command: {0}
|
||||||
|
E191=E191: Argument must be a letter or forward/backward quote
|
||||||
|
E223=E223: recursive mapping
|
||||||
E363=E363: pattern caused out-of-stack error
|
E363=E363: pattern caused out-of-stack error
|
||||||
e_re_corr=E44: Currupted regexp program
|
|
||||||
e_re_damg=E43: Damaged match string
|
|
||||||
E369=E369: invalid item in {0}%[]
|
E369=E369: invalid item in {0}%[]
|
||||||
E384=E384: search hit TOP without match for: {0}
|
E384=E384: Search hit TOP without match for: {0}
|
||||||
E385=E385: search hit BOTTOM without match for: {0}
|
E385=E385: Search hit BOTTOM without match for: {0}
|
||||||
e_patnotf2=Pattern not found: {0}
|
E471=E471: Argument required
|
||||||
unkopt=Unknown option: {0}
|
|
||||||
e_invarg=Invalid argument: {0}
|
|
||||||
E474=E474: Invalid argument: {0}
|
E474=E474: Invalid argument: {0}
|
||||||
E475=E475: Invalid argument: {0}
|
E475=E475: Invalid argument: {0}
|
||||||
|
E481=E481: No range allowed
|
||||||
|
E486=E486: Pattern not found: {0}
|
||||||
|
E488=E488: Trailing characters: {0}
|
||||||
# Vim's message includes alternate files and the :p:h file name modifier, which we don't support
|
# Vim's message includes alternate files and the :p:h file name modifier, which we don't support
|
||||||
# E499: Empty file name for '%' or '#', only works with ":p:h"
|
# E499: Empty file name for '%' or '#', only works with ":p:h"
|
||||||
E499=E499: Empty file name for '%'
|
E499=E499: Empty file name for '%'
|
||||||
@ -73,6 +82,11 @@ E546=E546: Illegal mode: {0}
|
|||||||
E548=E548: Digit expected: {0}
|
E548=E548: Digit expected: {0}
|
||||||
E549=E549: Illegal percentage: {0}
|
E549=E549: Illegal percentage: {0}
|
||||||
E774=E774: 'operatorfunc' is empty
|
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
|
||||||
|
|
||||||
|
message.search.hit.bottom=search hit BOTTOM, continuing at TOP
|
||||||
|
message.search.hit.top=search hit TOP, continuing at BOTTOM
|
||||||
|
|
||||||
action.VimPluginToggle.text=Vim
|
action.VimPluginToggle.text=Vim
|
||||||
action.VimPluginToggle.description=Toggle the vim plugin On/Off
|
action.VimPluginToggle.description=Toggle the vim plugin On/Off
|
||||||
@ -90,19 +104,13 @@ action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
|
|||||||
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
|
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
|
||||||
|
|
||||||
ex.show.all.actions.0.1=--- Actions ---{0}{1}
|
ex.show.all.actions.0.1=--- Actions ---{0}{1}
|
||||||
e471.argument.required=E471: Argument required
|
|
||||||
|
|
||||||
buffer.0.does.not.exist=Buffer {0} does not exist
|
buffer.0.does.not.exist=Buffer {0} does not exist
|
||||||
no.matching.buffer.for.0=No matching buffer for {0}
|
no.matching.buffer.for.0=No matching buffer for {0}
|
||||||
no.write.since.last.change.add.to.override=No write since last change (add ! to override)
|
no.write.since.last.change.add.to.override=No write since last change (add ! to override)
|
||||||
more.than.one.match.for.0=More than one match for {0}
|
more.than.one.match.for.0=More than one match for {0}
|
||||||
e176.invalid.number.of.arguments=E176: Invalid number of arguments
|
|
||||||
e183.user.defined.commands.must.start.with.an.uppercase.letter=E183: User defined commands must start with an uppercase letter
|
|
||||||
e841.reserved.name.cannot.be.used.for.user.defined.command=E841: Reserved name, cannot be used for user defined command
|
|
||||||
e174.command.already.exists.add.to.replace.it=E174: Command already exists: add ! to replace it
|
|
||||||
recursion.detected.maximum.alias.depth.reached=Recursion detected, maximum alias depth reached.
|
recursion.detected.maximum.alias.depth.reached=Recursion detected, maximum alias depth reached.
|
||||||
show.mode.recording=recording
|
show.mode.recording=recording
|
||||||
e184.no.such.user.defined.command.0=E184: No such user-defined command: {0}
|
|
||||||
unable.to.find.0=Unable to find {0}
|
unable.to.find.0=Unable to find {0}
|
||||||
more.ret.line.space.page.d.half.page.q.quit=-- MORE -- (RET: line, SPACE: page, d: half page, q: quit)
|
more.ret.line.space.page.d.half.page.q.quit=-- MORE -- (RET: line, SPACE: page, d: half page, q: quit)
|
||||||
hit.enter.or.type.command.to.continue=Hit ENTER or type command to continue
|
hit.enter.or.type.command.to.continue=Hit ENTER or type command to continue
|
||||||
@ -158,7 +166,6 @@ configurable.keyhandler.link=<html>Use <a>sethandler</a> command to configure ha
|
|||||||
configurable.noneditablehandler.helper.text.with.example=Non-editable handlers are defined in .ideavimrc file. E.g. ''{0}'' for {1}.
|
configurable.noneditablehandler.helper.text.with.example=Non-editable handlers are defined in .ideavimrc file. E.g. ''{0}'' for {1}.
|
||||||
border.title.shortcut.conflicts.for.active.keymap=Shortcut Conflicts for Active Keymap
|
border.title.shortcut.conflicts.for.active.keymap=Shortcut Conflicts for Active Keymap
|
||||||
message.no.more.matches=No more matches
|
message.no.more.matches=No more matches
|
||||||
E223=E223: recursive mapping
|
|
||||||
|
|
||||||
action.copy.action.id.text=Copy Action Id
|
action.copy.action.id.text=Copy Action Id
|
||||||
|
|
||||||
|
@ -9,7 +9,6 @@ package org.jetbrains.plugins.ideavim.action
|
|||||||
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
|
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
@ -17,6 +16,7 @@ import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
|||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
|
import org.jetbrains.plugins.ideavim.VimBehaviorDiffers
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -51,6 +51,7 @@ class MotionActionTest : VimTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@Disabled("VIM-3376")
|
||||||
fun testEscapeInCommand() {
|
fun testEscapeInCommand() {
|
||||||
val content = """
|
val content = """
|
||||||
on${c}e two
|
on${c}e two
|
||||||
@ -684,6 +685,81 @@ class MotionActionTest : VimTestCase() {
|
|||||||
doTest(keys, before, after, Mode.INSERT)
|
doTest(keys, before, after, Mode.INSERT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToSearchResult() {
|
||||||
|
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ${c}sit amet, consectetur adipiscing elit"
|
||||||
|
doTest("d/sit<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToLastSearchResult() {
|
||||||
|
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ${c}sit amet, consectetur adipiscing elit"
|
||||||
|
doTest("dn", before, after) {
|
||||||
|
enterSearch("sit")
|
||||||
|
typeText("0w") // Cursor back on first "ipsum"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToSearchResultWithCount() {
|
||||||
|
doTest(
|
||||||
|
"d3/ipsum<CR>",
|
||||||
|
"lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum",
|
||||||
|
"ipsum lorem 4 ipsum lorem 5 ipsum"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToSearchResultWithCountAndOperatorCount() {
|
||||||
|
doTest(
|
||||||
|
"2d3/ipsum<CR>",
|
||||||
|
"lorem 1 ipsum lorem 2 ipsum lorem 3 ipsum lorem 4 ipsum lorem 5 ipsum lorem 6 ipsum lorem 7 ipsum",
|
||||||
|
"ipsum lorem 7 ipsum"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToSearchResultWithLinewiseOffset() {
|
||||||
|
val before = """
|
||||||
|
|First line
|
||||||
|
|Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
|Last line
|
||||||
|
""".trimMargin()
|
||||||
|
val after = """
|
||||||
|
|First line
|
||||||
|
|${c}Last line
|
||||||
|
""".trimMargin()
|
||||||
|
doTest("d/sit/3<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToEndOfSearchResultInclusive() {
|
||||||
|
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ${c} amet, consectetur adipiscing elit"
|
||||||
|
doTest("d/sit/e<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToEndOfSearchResultWithOffset() {
|
||||||
|
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ${c}et, consectetur adipiscing elit"
|
||||||
|
doTest("d/sit/e+3<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteToSearchResultWithIncsearch() {
|
||||||
|
val before = "Lorem ${c}ipsum dolor sit amet, consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ${c}sit amet, consectetur adipiscing elit"
|
||||||
|
doTest("d/sit<CR>", before, after) {
|
||||||
|
enterCommand("set incsearch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testDeleteToDigraph() {
|
fun testDeleteToDigraph() {
|
||||||
val keys = listOf("d/<C-K>O:<CR>")
|
val keys = listOf("d/<C-K>O:<CR>")
|
||||||
@ -692,6 +768,64 @@ class MotionActionTest : VimTestCase() {
|
|||||||
doTest(keys, before, after, Mode.NORMAL())
|
doTest(keys, before, after, Mode.NORMAL())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteBackwardsToSearchResult() {
|
||||||
|
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ipsum dolor ${c}consectetur adipiscing elit"
|
||||||
|
doTest("d/sit<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteBackwardsToLastSearchResult() {
|
||||||
|
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ipsum dolor ${c}elit"
|
||||||
|
doTest("dn", before, after) {
|
||||||
|
enterSearch("sit", false)
|
||||||
|
typeText("\$b") // Cursor to start of "elit"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteBackwardsToSearchResultWithLinewiseOffset() {
|
||||||
|
val before = """
|
||||||
|
|First line
|
||||||
|
|Lorem ipsum dolor sit amet, consectetur ${c}adipiscing elit
|
||||||
|
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
|Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
|Last line
|
||||||
|
""".trimMargin()
|
||||||
|
val after = """
|
||||||
|
|First line
|
||||||
|
|${c}Last line
|
||||||
|
""".trimMargin()
|
||||||
|
doTest("d?sit?3<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("SpellCheckingInspection")
|
||||||
|
@Test
|
||||||
|
fun testDeleteBackwardsToEndOfSearchResultInclusive() {
|
||||||
|
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ipsum dolor si${c}onsectetur adipiscing elit"
|
||||||
|
doTest("d?sit?e<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteBackwardsToEndOfSearchResultWithOffset() {
|
||||||
|
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ipsum dolor sit a${c}onsectetur adipiscing elit"
|
||||||
|
doTest("d?sit?e+3<CR>", before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testDeleteBackwardsToSearchResultWithIncsearch() {
|
||||||
|
val before = "Lorem ipsum dolor sit amet, ${c}consectetur adipiscing elit"
|
||||||
|
val after = "Lorem ipsum dolor ${c}consectetur adipiscing elit"
|
||||||
|
doTest("d?sit<CR>", before, after) {
|
||||||
|
enterCommand("set incsearch")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// |[(|
|
// |[(|
|
||||||
@Test
|
@Test
|
||||||
fun testUnmatchedOpenParenthesis() {
|
fun testUnmatchedOpenParenthesis() {
|
||||||
@ -1212,14 +1346,14 @@ two
|
|||||||
@Test
|
@Test
|
||||||
fun `test gv after backwards selection`() {
|
fun `test gv after backwards selection`() {
|
||||||
configureByText("${c}Oh, hi Mark\n")
|
configureByText("${c}Oh, hi Mark\n")
|
||||||
typeText(parseKeys("yw" + "$" + "vb" + "p" + "gv"))
|
typeText("yw", "$", "vb", "p", "gv")
|
||||||
assertSelection("Oh")
|
assertSelection("Oh")
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test gv after linewise selection`() {
|
fun `test gv after linewise selection`() {
|
||||||
configureByText("${c}Oh, hi Mark\nOh, hi Markus\n")
|
configureByText("${c}Oh, hi Mark\nOh, hi Markus\n")
|
||||||
typeText(parseKeys("V" + "y" + "j" + "V" + "p" + "gv"))
|
typeText("V", "y", "j", "V", "p", "gv")
|
||||||
assertSelection("Oh, hi Mark\n")
|
assertSelection("Oh, hi Mark\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2024 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action
|
||||||
|
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class RedrawActionTest : VimTestCase() {
|
||||||
|
@Test
|
||||||
|
fun `test redraw action clears status line`() {
|
||||||
|
configureByText("lorem ipsum")
|
||||||
|
enterSearch("dolor") // Pattern not found: dolor
|
||||||
|
assertStatusLineMessageContains("Pattern not found: dolor")
|
||||||
|
typeText("<C-L>")
|
||||||
|
assertStatusLineCleared()
|
||||||
|
}
|
||||||
|
}
|
@ -112,6 +112,7 @@ class DeleteCharacterLeftActionTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun `test deleting characters scrolls caret into view`() {
|
fun `test deleting characters scrolls caret into view`() {
|
||||||
configureByText("Hello world".repeat(200))
|
configureByText("Hello world".repeat(200))
|
||||||
|
enterCommand("set nowrap")
|
||||||
enterCommand("set sidescrolloff=5")
|
enterCommand("set sidescrolloff=5")
|
||||||
|
|
||||||
// Scroll 70 characters to the left. First character on line should now be 71. sidescrolloff puts us at 76
|
// Scroll 70 characters to the left. First character on line should now be 71. sidescrolloff puts us at 76
|
||||||
|
@ -244,4 +244,13 @@ class DeleteMotionActionTest : VimTestCase() {
|
|||||||
""".trimIndent(),
|
""".trimIndent(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test delete line clears status line`() {
|
||||||
|
configureByPages(5) // Lorem ipsum
|
||||||
|
enterSearch("egestas")
|
||||||
|
assertStatusLineMessageContains("Pattern not found: egestas")
|
||||||
|
typeText("dd")
|
||||||
|
assertStatusLineCleared()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,12 @@ class InsertBackspaceActionTest : VimTestCase() {
|
|||||||
enterCommand("set sidescrolloff=10")
|
enterCommand("set sidescrolloff=10")
|
||||||
|
|
||||||
typeText("70zl", "i", "<BS>")
|
typeText("70zl", "i", "<BS>")
|
||||||
|
|
||||||
|
// Note that because 'sidescroll' has the default value of 0, we scroll the caret to the middle of the screen, as
|
||||||
|
// well as applying sidescrolloff. Leftmost column was 69 (zero-based), and the caret is on column 80. Deleting a
|
||||||
|
// character moves the caret to column 79, which is within 'sidescrolloff' of the left edge of the screen. The
|
||||||
|
// screen is scrolled by 'sidescroll', which has the default value of 0, so we scroll until the caret is in the
|
||||||
|
// middle of the screen, which is 80 characters wide: 79-(80/2)=39
|
||||||
assertVisibleLineBounds(0, 39, 118)
|
assertVisibleLineBounds(0, 39, 118)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,11 +8,16 @@
|
|||||||
|
|
||||||
package org.jetbrains.plugins.ideavim.action.change.insert
|
package org.jetbrains.plugins.ideavim.action.change.insert
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.newapi.ijOptions
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
import kotlin.test.assertEquals
|
||||||
|
import kotlin.test.assertTrue
|
||||||
|
|
||||||
class InsertNewLineAboveActionTest : VimTestCase() {
|
class InsertNewLineAboveActionTest : VimTestCase() {
|
||||||
@Test
|
@Test
|
||||||
@ -125,4 +130,46 @@ class InsertNewLineAboveActionTest : VimTestCase() {
|
|||||||
""".trimMargin()
|
""".trimMargin()
|
||||||
doTest("O", before, after, Mode.INSERT)
|
doTest("O", before, after, Mode.INSERT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test insert line above first line that is soft-wrapped`() {
|
||||||
|
val before = """
|
||||||
|
Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat ${c}purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
|
||||||
|
|
||||||
|
Aliquam tristique eros vel magna dictum tincidunt. Duis sagittis mi et bibendum congue. Donec sollicitudin, ipsum quis pellentesque efficitur, metus quam congue nulla, vel rutrum neque lectus vitae sem. In accumsan scelerisque risus, ac sollicitudin purus ornare in. Proin leo erat, tempus vitae purus nec, lobortis bibendum tortor. Aenean mauris sem, interdum id facilisis et, ullamcorper ut libero. Quisque magna ligula, euismod sit amet ipsum non, maximus ultrices nulla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras facilisis arcu vitae orci scelerisque, vel dignissim massa dapibus. Fusce sed urna ut orci pellentesque consectetur. Maecenas rutrum erat ac libero elementum dictum. Donec pulvinar, sem feugiat suscipit mattis, turpis tellus consectetur dui, vitae vehicula dolor purus eu lectus. Nullam lorem ligula, aliquet id eros sed, rhoncus consequat neque. Cras eget erat non nunc convallis accumsan id in ipsum.
|
||||||
|
|
||||||
|
In id lacus diam. Curabitur orci libero, sollicitudin sed magna efficitur, finibus elementum mi. Cras aliquam enim eu scelerisque consectetur. Ut lacinia, velit sed dictum sollicitudin, mauris metus fringilla quam, vitae pellentesque tortor leo ut lectus. Fusce facilisis, eros ac egestas porttitor, enim arcu molestie purus, ut porta erat neque ac est. Ut facilisis, ante vel feugiat ultricies, metus nulla vestibulum dui, eget luctus lorem urna sed ex. Mauris quis lectus efficitur, sollicitudin urna vel, suscipit mi. Aliquam fringilla fermentum nunc. Phasellus suscipit nunc a dui gravida, sed euismod elit mattis. Donec pharetra, sem at finibus fermentum, dui lacus ornare arcu, eget maximus massa purus at ipsum.
|
||||||
|
|
||||||
|
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc quis ligula sed quam suscipit varius id et metus. Ut hendrerit diam eu turpis semper luctus. Aliquam efficitur tortor ut eros consectetur tristique. Vestibulum odio nunc, finibus eu ex auctor, pharetra congue urna. Proin sit amet malesuada nisl. Proin sagittis metus diam, vitae sollicitudin eros rutrum id. Nam imperdiet lacus et mi iaculis, vitae suscipit felis consequat.
|
||||||
|
|
||||||
|
Fusce in lectus eros. Vivamus imperdiet sodales enim id vulputate. Ut tincidunt hendrerit cursus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras maximus et justo et congue. Nam iaculis elementum ultrices. Quisque nec semper eros. Nulla nisl nunc, finibus ac ligula vel, ullamcorper egestas risus. Nunc dictum cursus leo, id pulvinar augue ullamcorper ac. Vivamus condimentum nunc non justo convallis, in condimentum ante malesuada. Vivamus gravida et metus vitae porta. Integer blandit magna metus, sodales commodo nibh rutrum ac. Ut tincidunt et justo a luctus. Nunc lacus lorem, finibus id vehicula eu, gravida ut augue.
|
||||||
|
""".trimMargin()
|
||||||
|
val after = """
|
||||||
|
${c}
|
||||||
|
Lorem Ipsumm dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
|
||||||
|
|
||||||
|
Aliquam tristique eros vel magna dictum tincidunt. Duis sagittis mi et bibendum congue. Donec sollicitudin, ipsum quis pellentesque efficitur, metus quam congue nulla, vel rutrum neque lectus vitae sem. In accumsan scelerisque risus, ac sollicitudin purus ornare in. Proin leo erat, tempus vitae purus nec, lobortis bibendum tortor. Aenean mauris sem, interdum id facilisis et, ullamcorper ut libero. Quisque magna ligula, euismod sit amet ipsum non, maximus ultrices nulla. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras facilisis arcu vitae orci scelerisque, vel dignissim massa dapibus. Fusce sed urna ut orci pellentesque consectetur. Maecenas rutrum erat ac libero elementum dictum. Donec pulvinar, sem feugiat suscipit mattis, turpis tellus consectetur dui, vitae vehicula dolor purus eu lectus. Nullam lorem ligula, aliquet id eros sed, rhoncus consequat neque. Cras eget erat non nunc convallis accumsan id in ipsum.
|
||||||
|
|
||||||
|
In id lacus diam. Curabitur orci libero, sollicitudin sed magna efficitur, finibus elementum mi. Cras aliquam enim eu scelerisque consectetur. Ut lacinia, velit sed dictum sollicitudin, mauris metus fringilla quam, vitae pellentesque tortor leo ut lectus. Fusce facilisis, eros ac egestas porttitor, enim arcu molestie purus, ut porta erat neque ac est. Ut facilisis, ante vel feugiat ultricies, metus nulla vestibulum dui, eget luctus lorem urna sed ex. Mauris quis lectus efficitur, sollicitudin urna vel, suscipit mi. Aliquam fringilla fermentum nunc. Phasellus suscipit nunc a dui gravida, sed euismod elit mattis. Donec pharetra, sem at finibus fermentum, dui lacus ornare arcu, eget maximus massa purus at ipsum.
|
||||||
|
|
||||||
|
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc quis ligula sed quam suscipit varius id et metus. Ut hendrerit diam eu turpis semper luctus. Aliquam efficitur tortor ut eros consectetur tristique. Vestibulum odio nunc, finibus eu ex auctor, pharetra congue urna. Proin sit amet malesuada nisl. Proin sagittis metus diam, vitae sollicitudin eros rutrum id. Nam imperdiet lacus et mi iaculis, vitae suscipit felis consequat.
|
||||||
|
|
||||||
|
Fusce in lectus eros. Vivamus imperdiet sodales enim id vulputate. Ut tincidunt hendrerit cursus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras maximus et justo et congue. Nam iaculis elementum ultrices. Quisque nec semper eros. Nulla nisl nunc, finibus ac ligula vel, ullamcorper egestas risus. Nunc dictum cursus leo, id pulvinar augue ullamcorper ac. Vivamus condimentum nunc non justo convallis, in condimentum ante malesuada. Vivamus gravida et metus vitae porta. Integer blandit magna metus, sodales commodo nibh rutrum ac. Ut tincidunt et justo a luctus. Nunc lacus lorem, finibus id vehicula eu, gravida ut augue.
|
||||||
|
""".trimMargin()
|
||||||
|
doTest("O", before, after, Mode.INSERT) {
|
||||||
|
// This test is for soft wraps. The caret should be on 0 logical position and 1 visual position
|
||||||
|
val editor = fixture.editor.vim
|
||||||
|
assertTrue(injector.ijOptions(editor).wrap)
|
||||||
|
assertEquals(1, editor.carets().single().getVisualPosition().line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test insert new line above clears status line`() {
|
||||||
|
configureByText("lorem ipsum")
|
||||||
|
enterSearch("dolor")
|
||||||
|
assertStatusLineMessageContains("Pattern not found: dolor")
|
||||||
|
typeText("O")
|
||||||
|
assertStatusLineCleared()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -202,4 +202,13 @@ class InsertNewLineBelowActionTest : VimTestCase() {
|
|||||||
|
|
||||||
performTest("o", after, Mode.INSERT)
|
performTest("o", after, Mode.INSERT)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test insert new line above clears status line`() {
|
||||||
|
configureByText("lorem ipsum")
|
||||||
|
enterSearch("dolor")
|
||||||
|
assertStatusLineMessageContains("Pattern not found: dolor")
|
||||||
|
typeText("o")
|
||||||
|
assertStatusLineCleared()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1885,6 +1885,7 @@ class PutVisualTextActionTest : VimTestCase() {
|
|||||||
Fusce in lectus eros. Vivamus imperdiet sodales enim$c id vulputate. Ut tincidunt hendrerit cursus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras maximus et justo et congue. Nam iaculis elementum ultrices. Quisque nec semper eros. Nulla nisl nunc, finibus ac ligula vel, ullamcorper egestas risus. Nunc dictum cursus leo, id pulvinar augue ullamcorper ac. Vivamus condimentum nunc non justo convallis, in condimentum ante malesuada. Vivamus gravida et metus vitae porta. Integer blandit magna metus, sodales commodo nibh rutrum ac. Ut tincidunt et justo a luctus. Nunc lacus lorem, finibus id vehicula eu, gravida ut augue.
|
Fusce in lectus eros. Vivamus imperdiet sodales enim$c id vulputate. Ut tincidunt hendrerit cursus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Cras maximus et justo et congue. Nam iaculis elementum ultrices. Quisque nec semper eros. Nulla nisl nunc, finibus ac ligula vel, ullamcorper egestas risus. Nunc dictum cursus leo, id pulvinar augue ullamcorper ac. Vivamus condimentum nunc non justo convallis, in condimentum ante malesuada. Vivamus gravida et metus vitae porta. Integer blandit magna metus, sodales commodo nibh rutrum ac. Ut tincidunt et justo a luctus. Nunc lacus lorem, finibus id vehicula eu, gravida ut augue.
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
configureByText(before)
|
configureByText(before)
|
||||||
|
enterCommand("set nowrap")
|
||||||
typeText(injector.parser.parseKeys("Ypj<C-V>P"))
|
typeText(injector.parser.parseKeys("Ypj<C-V>P"))
|
||||||
val after = """
|
val after = """
|
||||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi gravida commodo orci, egestas placerat purus rhoncus non. Donec efficitur placerat lorem, non ullamcorper nisl. Aliquam vestibulum, purus a pretium sodales, lorem libero placerat tortor, ut gravida est arcu nec purus. Suspendisse luctus euismod mi, at consectetur sapien facilisis sed. Duis eu magna id nisi lacinia vehicula in quis mauris. Donec tincidunt, erat in euismod placerat, tortor eros efficitur ligula, non finibus metus enim in ex. Nam commodo libero quis vestibulum congue. Vivamus sit amet tincidunt orci, in luctus tortor. Ut aliquam porttitor pharetra. Sed vel mi lacinia, auctor eros vel, condimentum eros. Fusce suscipit auctor venenatis. Aliquam elit risus, eleifend quis mollis eu, venenatis quis ex. Nunc varius consectetur eros sit amet efficitur. Donec a elit rutrum, tristique est in, maximus sem. Donec eleifend magna vitae suscipit viverra. Phasellus luctus aliquam tellus viverra consequat.
|
||||||
|
@ -13,6 +13,7 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
|
|||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.Direction
|
import com.maddyhome.idea.vim.common.Direction
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
|
|||||||
|
|
||||||
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
||||||
configureByText(before)
|
configureByText(before)
|
||||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||||
typeText(keys)
|
typeText(keys)
|
||||||
assertState(after)
|
assertState(after)
|
||||||
assertState(Mode.NORMAL())
|
assertState(Mode.NORMAL())
|
||||||
|
@ -13,6 +13,7 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
|
|||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.Direction
|
import com.maddyhome.idea.vim.common.Direction
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
|
|||||||
|
|
||||||
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
|
||||||
configureByText(before)
|
configureByText(before)
|
||||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||||
typeText(keys)
|
typeText(keys)
|
||||||
assertState(after)
|
assertState(after)
|
||||||
assertState(Mode.NORMAL())
|
assertState(Mode.NORMAL())
|
||||||
|
@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
|||||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.Direction
|
import com.maddyhome.idea.vim.common.Direction
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testWithoutSpaces() {
|
fun testWithoutSpaces() {
|
||||||
configureByText("test<caret>test")
|
configureByText("test<caret>test")
|
||||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||||
typeText(injector.parser.parseKeys("gn"))
|
typeText(injector.parser.parseKeys("gn"))
|
||||||
assertOffset(7)
|
assertOffset(7)
|
||||||
assertSelection("test")
|
assertSelection("test")
|
||||||
|
@ -12,6 +12,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
|||||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.common.Direction
|
import com.maddyhome.idea.vim.common.Direction
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
|
|||||||
@Test
|
@Test
|
||||||
fun testWithoutSpaces() {
|
fun testWithoutSpaces() {
|
||||||
configureByText("tes<caret>ttest")
|
configureByText("tes<caret>ttest")
|
||||||
VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
|
VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
|
||||||
typeText(injector.parser.parseKeys("gN"))
|
typeText(injector.parser.parseKeys("gN"))
|
||||||
assertOffset(0)
|
assertOffset(0)
|
||||||
assertSelection("test")
|
assertSelection("test")
|
||||||
|
@ -12,6 +12,42 @@ import org.jetbrains.plugins.ideavim.VimTestCase
|
|||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class SearchAgainNextActionTest : VimTestCase() {
|
class SearchAgainNextActionTest : VimTestCase() {
|
||||||
|
@Test
|
||||||
|
fun `test search next updates status line`() {
|
||||||
|
doTest(
|
||||||
|
listOf(searchCommand("/ipsum"), "n"),
|
||||||
|
"""
|
||||||
|
${c}Lorem ipsum dolor sit amet,
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
""".trimIndent(),
|
||||||
|
"""
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
Lorem ${c}ipsum dolor sit amet,
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
assertStatusLineMessageContains("/ipsum")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `test search next for backwards search updates status line correctly`() {
|
||||||
|
doTest(
|
||||||
|
listOf(searchCommand("?ipsum"), "n"),
|
||||||
|
"""
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
${c}Lorem ipsum dolor sit amet,
|
||||||
|
""".trimIndent(),
|
||||||
|
"""
|
||||||
|
Lorem ${c}ipsum dolor sit amet,
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
Lorem ipsum dolor sit amet,
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
assertStatusLineMessageContains("?ipsum")
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test search next after search command with offset`() {
|
fun `test search next after search command with offset`() {
|
||||||
val before = """
|
val before = """
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user