mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2024-11-25 16:42:55 +01:00
Compare commits
76 Commits
976791ac95
...
fd1a706e4a
Author | SHA1 | Date | |
---|---|---|---|
fd1a706e4a | |||
5b2f3e1f12 | |||
ec704fc9f9 | |||
ef8955e9d1 | |||
806f6f8eaa | |||
6dcf0f9458 | |||
8eb201a941 | |||
a288feca2a | |||
ffe337b145 | |||
ee178e58d0 | |||
12ac424ae0 | |||
c96ca2e405 | |||
0265432ce5 | |||
f7e5f15ed2 | |||
68f21ad7c9 | |||
|
9d5aa83786 | ||
|
463164cb88 | ||
|
4809742088 | ||
|
9cf0e285b4 | ||
|
a6ca6f1cf9 | ||
|
bd7479e1c0 | ||
|
b35b51c203 | ||
|
5652774888 | ||
|
836e9a2fbc | ||
|
64538c255d | ||
|
62a9293dcf | ||
|
1faae92f33 | ||
|
dee808752f | ||
|
5590af6995 | ||
|
5afd161fba | ||
|
336efa1e8b | ||
|
568d5ca4ff | ||
|
a9991f2a50 | ||
|
1c8096444a | ||
|
f424de46e6 | ||
|
8fcca05565 | ||
|
ed1f3cec59 | ||
|
c29a409f28 | ||
|
1a46936ad6 | ||
|
e82abfb948 | ||
|
c3409be780 | ||
|
1557ab3474 | ||
|
75fdda4fbf | ||
|
4d75ef2849 | ||
|
a1da23d1ba | ||
|
c4bc751df7 | ||
|
972d89ec6e | ||
|
70f040e104 | ||
|
d4de0b49c8 | ||
|
2a42d58361 | ||
|
14308956d7 | ||
|
71339a66d7 | ||
|
85f0664b56 | ||
|
2f86ac0dfa | ||
|
79d7b7a08d | ||
|
b550d1990e | ||
|
22062f0c77 | ||
|
515f613a53 | ||
|
615ed6b713 | ||
|
f6eab62c3c | ||
|
7d1e00ff0d | ||
|
692439953c | ||
|
6960a34d02 | ||
|
b3662d4e6e | ||
|
50c9b7c352 | ||
|
f395d3b2bf | ||
|
4fbf6cbc50 | ||
|
9916958d6c | ||
|
184a069c7f | ||
|
0b65346633 | ||
|
11f23dcc9e | ||
|
f80d1defcb | ||
|
e95d6343cb | ||
|
a9052a068f | ||
|
b1323c0d67 | ||
|
87ceb8fb58 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
@ -6,6 +6,7 @@
|
|||||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||||
</value>
|
</value>
|
||||||
</option>
|
</option>
|
||||||
|
<option name="LINE_SEPARATOR" value=" " />
|
||||||
<JavaCodeStyleSettings>
|
<JavaCodeStyleSettings>
|
||||||
<option name="FIELD_NAME_PREFIX" value="my" />
|
<option name="FIELD_NAME_PREFIX" value="my" />
|
||||||
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />
|
<option name="STATIC_FIELD_NAME_PREFIX" value="our" />
|
||||||
|
17
.teamcity/_Self/Constants.kt
vendored
17
.teamcity/_Self/Constants.kt
vendored
@ -5,13 +5,14 @@ object Constants {
|
|||||||
const val EAP_CHANNEL = "eap"
|
const val EAP_CHANNEL = "eap"
|
||||||
const val DEV_CHANNEL = "Dev"
|
const val DEV_CHANNEL = "Dev"
|
||||||
|
|
||||||
const val GITHUB_TESTS = "2023.1.2"
|
// TODO it should be 2023.3 as soon as it releases
|
||||||
const val NVIM_TESTS = "2023.1.2"
|
const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||||
const val PROPERTY_TESTS = "2023.1.2"
|
const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||||
const val LONG_RUNNING_TESTS = "2023.1.2"
|
const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||||
const val QODANA_TESTS = "2023.1.2"
|
const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||||
const val RELEASE = "2023.1.2"
|
const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
|
||||||
|
const val RELEASE = "LATEST-EAP-SNAPSHOT"
|
||||||
|
|
||||||
const val RELEASE_DEV = "2023.1.2"
|
const val RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
|
||||||
const val RELEASE_EAP = "2023.1.2"
|
const val RELEASE_EAP = "LATEST-EAP-SNAPSHOT"
|
||||||
}
|
}
|
||||||
|
2
.teamcity/_Self/Project.kt
vendored
2
.teamcity/_Self/Project.kt
vendored
@ -23,8 +23,6 @@ object Project : Project({
|
|||||||
vcsRoot(GitHubPullRequest)
|
vcsRoot(GitHubPullRequest)
|
||||||
|
|
||||||
// Active tests
|
// Active tests
|
||||||
buildType(TestingBuildType("2023.2", "<default>", version = "2023.2.3"))
|
|
||||||
buildType(TestingBuildType("2023.1", "<default>", version = "2023.1.5"))
|
|
||||||
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
|
||||||
|
|
||||||
|
2
.teamcity/_Self/subprojects/OldTests.kt
vendored
2
.teamcity/_Self/subprojects/OldTests.kt
vendored
@ -20,4 +20,6 @@ object OldTests : Project({
|
|||||||
buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
|
buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
|
||||||
buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
|
buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
|
||||||
buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
|
buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
|
||||||
|
buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
|
||||||
|
buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
|
||||||
})
|
})
|
||||||
|
15
CHANGES.md
15
CHANGES.md
@ -31,6 +31,21 @@ usual beta standards.
|
|||||||
* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
|
* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
|
||||||
* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
|
* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
|
||||||
* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
|
* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
|
||||||
|
* [VIM-3195](https://youtrack.jetbrains.com/issue/VIM-3195) Fix escape in injected editor
|
||||||
|
* [VIM-3190](https://youtrack.jetbrains.com/issue/VIM-3190) Do not use octopus handler if the enter key is used with modifiers like shift or control
|
||||||
|
* [VIM-3203](https://youtrack.jetbrains.com/issue/VIM-3203) Split action not works in normal mode
|
||||||
|
* [VIM-3184](https://youtrack.jetbrains.com/issue/VIM-3184) Revert "VIM-3184: Temporally disable new handlers for the thin client"
|
||||||
|
* [VIM-3186](https://youtrack.jetbrains.com/issue/VIM-3186) Do not multiply the enter action by the amount of carets
|
||||||
|
* [VIM-3177](https://youtrack.jetbrains.com/issue/VIM-3177) Formatting of commit message works again
|
||||||
|
* [VIM-1611](https://youtrack.jetbrains.com/issue/VIM-1611) actions related to resolving conflicts doesn't seem to work
|
||||||
|
* [VIM-3204](https://youtrack.jetbrains.com/issue/VIM-3204) Add checker that verifies the configuratin of the keymap
|
||||||
|
* [VIM-3084](https://youtrack.jetbrains.com/issue/VIM-3084) Double update for the status bar icon
|
||||||
|
* [VIM-3176](https://youtrack.jetbrains.com/issue/VIM-3176) Reselecting visual selection after pasting above it select wrong lines
|
||||||
|
* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
|
||||||
|
* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
|
||||||
|
|
||||||
|
### Merged PRs:
|
||||||
|
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
|
||||||
|
|
||||||
## 2.7.0, 2023-11-07
|
## 2.7.0, 2023-11-07
|
||||||
|
|
||||||
|
@ -324,7 +324,7 @@ IdeaVim tips and tricks
|
|||||||
- Use the power of IJ and Vim:
|
- Use the power of IJ and Vim:
|
||||||
- `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
|
- `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
|
||||||
- Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
|
- Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
|
||||||
- Sync IJ bookmarks and Vim marks: `set ideamarks`
|
- Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
|
||||||
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
|
- Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
|
||||||
|
|
||||||
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
|
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
|
||||||
|
@ -11,6 +11,8 @@ plugins {
|
|||||||
kotlin("plugin.serialization") version "1.8.21"
|
kotlin("plugin.serialization") version "1.8.21"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val kotlinxSerializationVersion: String by project
|
||||||
|
|
||||||
group = "com.intellij"
|
group = "com.intellij"
|
||||||
version = "SNAPSHOT"
|
version = "SNAPSHOT"
|
||||||
|
|
||||||
@ -19,6 +21,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14")
|
compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
|
||||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
|
||||||
|
// kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
|
||||||
|
exclude("org.jetbrains.kotlin", "kotlin-stdlib")
|
||||||
|
exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ enum class Mode(val abbrev: Char) {
|
|||||||
OP_PENDING('O'),
|
OP_PENDING('O'),
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Indicates this key mapping applies to Insert mode
|
* Indicates this key mapping applies to Insert or Replace modes
|
||||||
*/
|
*/
|
||||||
INSERT('I'),
|
INSERT('I'),
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ buildscript {
|
|||||||
classpath("org.kohsuke:github-api:1.305")
|
classpath("org.kohsuke:github-api:1.305")
|
||||||
|
|
||||||
classpath("io.ktor:ktor-client-core:2.3.6")
|
classpath("io.ktor:ktor-client-core:2.3.6")
|
||||||
classpath("io.ktor:ktor-client-cio:2.3.5")
|
classpath("io.ktor:ktor-client-cio:2.3.6")
|
||||||
classpath("io.ktor:ktor-client-auth:2.3.6")
|
classpath("io.ktor:ktor-client-auth:2.3.6")
|
||||||
classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
|
classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
|
||||||
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
||||||
@ -116,7 +116,7 @@ repositories {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||||
compileOnly("org.jetbrains:annotations:24.0.1")
|
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
|
// https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
|
||||||
testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
|
testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
|
||||||
@ -141,7 +141,7 @@ dependencies {
|
|||||||
|
|
||||||
testApi("com.squareup.okhttp3:okhttp:4.12.0")
|
testApi("com.squareup.okhttp3:okhttp:4.12.0")
|
||||||
|
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
|
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
|
testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
|
||||||
}
|
}
|
||||||
@ -344,8 +344,6 @@ tasks {
|
|||||||
val pluginVersion = version
|
val pluginVersion = version
|
||||||
// Don't forget to update plugin.xml
|
// Don't forget to update plugin.xml
|
||||||
patchPluginXml {
|
patchPluginXml {
|
||||||
sinceBuild.set("231.7515.13")
|
|
||||||
|
|
||||||
// Get the latest available change notes from the changelog file
|
// Get the latest available change notes from the changelog file
|
||||||
changeNotes.set(
|
changeNotes.set(
|
||||||
provider {
|
provider {
|
||||||
@ -524,10 +522,12 @@ tasks.register("releaseActions") {
|
|||||||
if (tickets.isNotEmpty()) {
|
if (tickets.isNotEmpty()) {
|
||||||
println("Updating statuses for tickets: $tickets")
|
println("Updating statuses for tickets: $tickets")
|
||||||
setYoutrackStatus(tickets, "Fixed")
|
setYoutrackStatus(tickets, "Fixed")
|
||||||
if (getVersionIdByName(version.toString()) != null) {
|
println("Checking if version $version exists...")
|
||||||
|
val versionId = getVersionIdByName(version.toString())
|
||||||
|
if (versionId == null) {
|
||||||
addReleaseToYoutrack(version.toString())
|
addReleaseToYoutrack(version.toString())
|
||||||
} else {
|
} else {
|
||||||
println("Version $version is already exists in YouTrack")
|
println("Version $version already exists in YouTrack. Version id: $versionId")
|
||||||
}
|
}
|
||||||
setYoutrackFixVersion(tickets, version.toString())
|
setYoutrackFixVersion(tickets, version.toString())
|
||||||
} else {
|
} else {
|
||||||
|
@ -3,6 +3,11 @@ Put `set ideajoin` to your `~/.ideavimrc` to enable this functionality.
|
|||||||
|
|
||||||
Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
|
Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
|
||||||
|
|
||||||
|
:warning: This feature is language-specific. This means that the IDE should implement this feature for a particular
|
||||||
|
language in order for the IDE to work as described below. If any of the examples provided below don't match your case,
|
||||||
|
please file an issue in the project related to your IDE: https://youtrack.jetbrains.com/.
|
||||||
|
Here is a list of known requests: https://youtrack.jetbrains.com/issues?q=links:VIM-3214.
|
||||||
|
|
||||||
* Automatic join concatenated lines:
|
* Automatic join concatenated lines:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
@ -8,20 +8,26 @@
|
|||||||
|
|
||||||
# suppress inspection "UnusedProperty" for whole file
|
# suppress inspection "UnusedProperty" for whole file
|
||||||
|
|
||||||
ideaVersion=2023.1.2
|
ideaVersion=2023.2
|
||||||
downloadIdeaSources=true
|
downloadIdeaSources=true
|
||||||
instrumentPluginCode=true
|
instrumentPluginCode=true
|
||||||
version=SNAPSHOT
|
version=chylex-22
|
||||||
javaVersion=17
|
javaVersion=17
|
||||||
remoteRobotVersion=0.11.17
|
remoteRobotVersion=0.11.17
|
||||||
antlrVersion=4.10.1
|
antlrVersion=4.10.1
|
||||||
|
|
||||||
|
kotlin.incremental.useClasspathSnapshot=false
|
||||||
|
|
||||||
# Please don't forget to update kotlin version in buildscript section
|
# Please don't forget to update kotlin version in buildscript section
|
||||||
|
# Also update kotlinxSerializationVersion version
|
||||||
kotlinVersion=1.8.21
|
kotlinVersion=1.8.21
|
||||||
publishToken=token
|
publishToken=token
|
||||||
publishChannels=eap
|
publishChannels=eap
|
||||||
|
|
||||||
|
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
|
||||||
|
# we exclude this version from the dependency and use our own version of kotlin that is specified above
|
||||||
|
kotlinxSerializationVersion=1.5.1
|
||||||
|
|
||||||
slackUrl=
|
slackUrl=
|
||||||
youtrackToken=
|
youtrackToken=
|
||||||
|
|
||||||
|
@ -20,10 +20,10 @@ repositories {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
|
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.21")
|
||||||
|
|
||||||
implementation("io.ktor:ktor-client-core:2.3.6")
|
implementation("io.ktor:ktor-client-core:2.3.6")
|
||||||
implementation("io.ktor:ktor-client-cio:2.3.5")
|
implementation("io.ktor:ktor-client-cio:2.3.6")
|
||||||
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
|
implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
|
||||||
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
|
||||||
implementation("io.ktor:ktor-client-auth:2.3.6")
|
implementation("io.ktor:ktor-client-auth:2.3.6")
|
||||||
|
@ -12,7 +12,7 @@ fun main(args: Array<String>) {
|
|||||||
println("HI!")
|
println("HI!")
|
||||||
val projectDir = args[0]
|
val projectDir = args[0]
|
||||||
println("Working directory: $projectDir")
|
println("Working directory: $projectDir")
|
||||||
val (lastVersion, objectId) = getVersion(projectDir, onlyStable = true)
|
val (lastVersion, objectId) = getVersion(projectDir, ReleaseType.STABLE_NO_PATCH)
|
||||||
println("Last version: $lastVersion, hash: ${objectId.name}")
|
println("Last version: $lastVersion, hash: ${objectId.name}")
|
||||||
|
|
||||||
val branch = withRepo(projectDir) { it.branch }
|
val branch = withRepo(projectDir) { it.branch }
|
||||||
|
@ -12,7 +12,7 @@ fun main(args: Array<String>) {
|
|||||||
println("HI!")
|
println("HI!")
|
||||||
val projectDir = args[0]
|
val projectDir = args[0]
|
||||||
println("Working directory: $projectDir")
|
println("Working directory: $projectDir")
|
||||||
val (lastVersion, _) = getVersion(projectDir, onlyStable = false)
|
val (lastVersion, _) = getVersion(projectDir, ReleaseType.ANY)
|
||||||
|
|
||||||
val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
|
val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
|
||||||
lastVersion.nextMinor().withSuffix("eap.1").value
|
lastVersion.nextMinor().withSuffix("eap.1").value
|
||||||
|
@ -14,7 +14,7 @@ fun main(args: Array<String>) {
|
|||||||
val releaseType = args[1]
|
val releaseType = args[1]
|
||||||
println("Working directory: $projectDir")
|
println("Working directory: $projectDir")
|
||||||
println("Release type: $releaseType")
|
println("Release type: $releaseType")
|
||||||
val (lastVersion, _) = getVersion(projectDir, onlyStable = true)
|
val (lastVersion, _) = getVersion(projectDir, ReleaseType.ONLY_STABLE)
|
||||||
|
|
||||||
val nextVersion = when (releaseType) {
|
val nextVersion = when (releaseType) {
|
||||||
"major" -> lastVersion.nextMajor()
|
"major" -> lastVersion.nextMajor()
|
||||||
|
@ -58,7 +58,13 @@ internal fun checkBranch(rootDir: String, releaseType: String) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, ObjectId> {
|
enum class ReleaseType {
|
||||||
|
ANY,
|
||||||
|
ONLY_STABLE,
|
||||||
|
STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
|
||||||
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
|
val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
|
||||||
val git = Git(repository)
|
val git = Git(repository)
|
||||||
println(git.log().call().first())
|
println(git.log().call().first())
|
||||||
@ -75,10 +81,10 @@ internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, O
|
|||||||
}
|
}
|
||||||
.sortedBy { it.first }
|
.sortedBy { it.first }
|
||||||
|
|
||||||
val version = if (onlyStable) {
|
val version = when (releaseType) {
|
||||||
versions.last { it.first.isStable }
|
ReleaseType.ANY -> versions.last()
|
||||||
} else {
|
ReleaseType.ONLY_STABLE -> versions.last { it.first.isStable }
|
||||||
versions.last()
|
ReleaseType.STABLE_NO_PATCH -> versions.last { it.first.isStable && it.first.patch == 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
return version
|
return version
|
||||||
|
@ -11,7 +11,7 @@ package com.maddyhome.idea.vim
|
|||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.project.ProjectManagerListener
|
import com.intellij.openapi.project.ProjectManagerListener
|
||||||
import com.intellij.openapi.startup.StartupActivity
|
import com.intellij.openapi.startup.ProjectActivity
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper
|
import com.maddyhome.idea.vim.helper.EditorHelper
|
||||||
import com.maddyhome.idea.vim.helper.localEditors
|
import com.maddyhome.idea.vim.helper.localEditors
|
||||||
@ -20,16 +20,11 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
|
|||||||
/**
|
/**
|
||||||
* @author Alex Plate
|
* @author Alex Plate
|
||||||
*/
|
*/
|
||||||
// This service should be migrated to ProjectActivity. But we should cariful because simple replacement
|
internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
|
||||||
// leads to deadlock in tests. I'm not sure about the exact reasons, but "invokeAndWait" inside "initialize" function
|
|
||||||
// causes this deadlock. Good new: it's easy reproducible in tests.
|
|
||||||
// Previous migration: fc7efd5484a13b40ba9bf86a1d5429e215d973f3
|
|
||||||
// Revert: 24dd84b31cffb99eb6114524859a46d02717d33f
|
|
||||||
internal class PluginStartup : StartupActivity.DumbAware/*, LightEditCompatible*/ {
|
|
||||||
|
|
||||||
private var firstInitializationOccurred = false
|
private var firstInitializationOccurred = false
|
||||||
|
|
||||||
override fun runActivity(project: Project) {
|
override suspend fun execute(project: Project) {
|
||||||
if (firstInitializationOccurred) return
|
if (firstInitializationOccurred) return
|
||||||
firstInitializationOccurred = true
|
firstInitializationOccurred = true
|
||||||
|
|
||||||
|
@ -232,7 +232,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
getInstance().turnOnPlugin();
|
getInstance().turnOnPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
StatusBarIconFactory.Companion.updateIcon();
|
StatusBarIconFactory.Util.INSTANCE.updateIcon();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getMessage() {
|
public static String getMessage() {
|
||||||
@ -264,7 +264,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
|
|||||||
if (enabled) {
|
if (enabled) {
|
||||||
Application application = ApplicationManager.getApplication();
|
Application application = ApplicationManager.getApplication();
|
||||||
if (application.isUnitTestMode()) {
|
if (application.isUnitTestMode()) {
|
||||||
application.invokeAndWait(this::turnOnPlugin);
|
turnOnPlugin();
|
||||||
|
//application.invokeAndWait(this::turnOnPlugin);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
application.invokeLater(this::turnOnPlugin);
|
application.invokeLater(this::turnOnPlugin);
|
||||||
|
@ -1,74 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2023 The IdeaVim authors
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style
|
|
||||||
* license that can be found in the LICENSE.txt file or at
|
|
||||||
* https://opensource.org/licenses/MIT.
|
|
||||||
*/
|
|
||||||
package com.maddyhome.idea.vim.action
|
|
||||||
|
|
||||||
import com.intellij.codeInsight.hint.HintManagerImpl
|
|
||||||
import com.intellij.openapi.actionSystem.ActionManager
|
|
||||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
|
|
||||||
import com.intellij.openapi.actionSystem.AnAction
|
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
|
||||||
import com.intellij.openapi.actionSystem.CommonDataKeys
|
|
||||||
import com.intellij.openapi.actionSystem.PerformWithDocumentsCommitted
|
|
||||||
import com.intellij.openapi.actionSystem.PopupAction
|
|
||||||
import com.intellij.openapi.actionSystem.impl.ActionConfigurationCustomizer
|
|
||||||
import com.intellij.openapi.editor.Editor
|
|
||||||
import com.intellij.openapi.editor.EditorMouseHoverPopupManager
|
|
||||||
import com.intellij.openapi.editor.event.EditorMouseEvent
|
|
||||||
import com.intellij.openapi.editor.event.EditorMouseEventArea
|
|
||||||
import com.intellij.openapi.project.DumbAware
|
|
||||||
import java.awt.event.MouseEvent
|
|
||||||
|
|
||||||
// [VERSION UPDATE] 233+ Remove class
|
|
||||||
// The ShowHoverInfo action is built into the platform (using a nicer EditorMouseHoverPopupManager API)
|
|
||||||
public class VimActionConfigurationCustomizer : ActionConfigurationCustomizer {
|
|
||||||
public override fun customize(actionManager: ActionManager) {
|
|
||||||
// If the ShowHoverInfo action doesn't exist in the platform, add our own implementation
|
|
||||||
if (actionManager.getAction("ShowHoverInfo") == null) {
|
|
||||||
actionManager.registerAction("ShowHoverInfo", VimShowHoverInfoAction())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class VimShowHoverInfoAction : AnAction(), HintManagerImpl.ActionToIgnore, PopupAction, DumbAware,
|
|
||||||
PerformWithDocumentsCommitted {
|
|
||||||
override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
|
|
||||||
|
|
||||||
override fun update(e: AnActionEvent) {
|
|
||||||
val dataContext = e.dataContext
|
|
||||||
val editor = CommonDataKeys.EDITOR.getData(dataContext)
|
|
||||||
if (editor == null) {
|
|
||||||
e.presentation.isEnabledAndVisible = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
|
||||||
val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return
|
|
||||||
|
|
||||||
val editorMouseEvent = createFakeEditorMouseEvent(editor)
|
|
||||||
EditorMouseHoverPopupManager.getInstance().showInfoTooltip(editorMouseEvent)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createFakeEditorMouseEvent(editor: Editor): EditorMouseEvent {
|
|
||||||
val xy = editor.offsetToXY(editor.caretModel.offset)
|
|
||||||
val mouseEvent =
|
|
||||||
MouseEvent(editor.component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, xy.x, xy.y, 0, false)
|
|
||||||
val editorMouseEvent = EditorMouseEvent(
|
|
||||||
editor,
|
|
||||||
mouseEvent,
|
|
||||||
EditorMouseEventArea.EDITING_AREA,
|
|
||||||
editor.caretModel.offset,
|
|
||||||
editor.caretModel.logicalPosition,
|
|
||||||
editor.caretModel.visualPosition,
|
|
||||||
true,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null
|
|
||||||
)
|
|
||||||
return editorMouseEvent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -14,10 +14,14 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
|
|||||||
import com.intellij.openapi.actionSystem.AnAction
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
import com.intellij.openapi.actionSystem.AnActionWrapper
|
import com.intellij.openapi.actionSystem.AnActionWrapper
|
||||||
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
|
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||||
import com.intellij.openapi.application.invokeLater
|
import com.intellij.openapi.application.invokeLater
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.editor.actionSystem.EditorActionManager
|
||||||
|
import com.intellij.openapi.keymap.KeymapManager
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
import com.intellij.openapi.project.DumbAware
|
import com.intellij.openapi.project.DumbAware
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
@ -56,7 +60,12 @@ import javax.swing.KeyStroke
|
|||||||
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
|
* These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
|
||||||
*/
|
*/
|
||||||
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
|
||||||
private val traceTime = injector.globalOptions().ideatracetime
|
private val traceTime: Boolean
|
||||||
|
get() {
|
||||||
|
// Make sure the injector is initialized
|
||||||
|
VimPlugin.getInstance()
|
||||||
|
return injector.globalOptions().ideatracetime
|
||||||
|
}
|
||||||
|
|
||||||
override fun actionPerformed(e: AnActionEvent) {
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
LOG.trace("Executing shortcut key action")
|
LOG.trace("Executing shortcut key action")
|
||||||
@ -159,6 +168,14 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
|
|||||||
return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
|
return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val nextTemplateVariableShortcuts = KeymapManager.getInstance().activeKeymap.getShortcuts(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
|
||||||
|
if (nextTemplateVariableShortcuts.any { it is KeyboardShortcut && it.firstKeyStroke == keyStroke }) {
|
||||||
|
val handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
|
||||||
|
if (handler.isEnabled(editor, null, e.dataContext)) {
|
||||||
|
return ActionEnableStatus.no("Next template variable or finish in-place refactoring", LogLevel.INFO)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (editor.inInsertMode) {
|
if (editor.inInsertMode) {
|
||||||
if (keyCode == KeyEvent.VK_TAB) {
|
if (keyCode == KeyEvent.VK_TAB) {
|
||||||
// TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
|
// TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
|
||||||
|
@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
|
|||||||
get() {
|
get() {
|
||||||
val myMode = machine.mode
|
val myMode = machine.mode
|
||||||
return when (myMode) {
|
return when (myMode) {
|
||||||
com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
||||||
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
|
com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
|
||||||
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
|
is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
|
||||||
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
|
is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
|
||||||
|
@ -231,7 +231,7 @@ private object FileTypePatterns {
|
|||||||
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
} else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
|
||||||
this.cMakePatterns
|
this.cMakePatterns
|
||||||
} else {
|
} else {
|
||||||
return null
|
this.htmlPatterns
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,16 +12,14 @@ import com.intellij.openapi.editor.Editor
|
|||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
|
import com.maddyhome.idea.vim.api.VimChangeGroup
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.endsWithNewLine
|
import com.maddyhome.idea.vim.api.endsWithNewLine
|
||||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.api.setChangeMarks
|
import com.maddyhome.idea.vim.api.setChangeMarks
|
||||||
import com.maddyhome.idea.vim.command.MappingMode
|
import com.maddyhome.idea.vim.command.MappingMode
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
|
||||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
import com.maddyhome.idea.vim.extension.ExtensionHandler
|
||||||
import com.maddyhome.idea.vim.extension.VimExtension
|
import com.maddyhome.idea.vim.extension.VimExtension
|
||||||
@ -33,12 +31,18 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
|
|||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
|
||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
|
||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
|
||||||
import com.maddyhome.idea.vim.state.mode.mode
|
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
|
||||||
import com.maddyhome.idea.vim.key.OperatorFunction
|
import com.maddyhome.idea.vim.key.OperatorFunction
|
||||||
|
import com.maddyhome.idea.vim.newapi.IjVimCaret
|
||||||
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
|
||||||
import com.maddyhome.idea.vim.put.PutData
|
import com.maddyhome.idea.vim.put.PutData
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
@ -79,7 +83,7 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
override val isRepeatable = true
|
override val isRepeatable = true
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
setOperatorFunction(Operator())
|
setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +104,7 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
|
||||||
if (lastNonWhiteSpaceOffset != null) {
|
if (lastNonWhiteSpaceOffset != null) {
|
||||||
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
|
||||||
performSurround(pair, range, it)
|
performSurround(pair, range, it, count = operatorArguments.count1)
|
||||||
}
|
}
|
||||||
// it.moveToOffset(lineStartOffset)
|
// it.moveToOffset(lineStartOffset)
|
||||||
}
|
}
|
||||||
@ -120,15 +124,13 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
|
|
||||||
private class VSurroundHandler : ExtensionHandler {
|
private class VSurroundHandler : ExtensionHandler {
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
|
||||||
val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
|
|
||||||
// NB: Operator ignores SelectionType anyway
|
// NB: Operator ignores SelectionType anyway
|
||||||
if (!Operator().apply(editor, context, editor.mode.selectionType)) {
|
if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
// Leave visual mode
|
// Leave visual mode
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
|
||||||
editor.ij.caretModel.moveToOffset(selectionStart)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -149,6 +151,10 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
|
editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
|
||||||
// Save old register values for carets
|
// Save old register values for carets
|
||||||
val surroundings = editor.sortedCarets()
|
val surroundings = editor.sortedCarets()
|
||||||
.map {
|
.map {
|
||||||
@ -255,21 +261,42 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Operator : OperatorFunction {
|
private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
|
||||||
override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
|
||||||
val ijEditor = editor.ij
|
val editor = vimEditor.ij
|
||||||
val c = getChar(ijEditor)
|
val c = getChar(editor)
|
||||||
if (c.code == 0) return true
|
if (c.code == 0) return true
|
||||||
|
|
||||||
val pair = getOrInputPair(c, ijEditor) ?: return false
|
val pair = getOrInputPair(c, editor) ?: return false
|
||||||
// XXX: Will it work with line-wise or block-wise selections?
|
|
||||||
val range = getSurroundRange(editor.currentCaret()) ?: return false
|
runWriteAction {
|
||||||
performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
|
val change = VimPlugin.getChange()
|
||||||
// Jump back to start
|
if (supportsMultipleCursors) {
|
||||||
executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
|
editor.runWithEveryCaretAndRestore {
|
||||||
|
applyOnce(editor, change, pair, count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
applyOnce(editor, change, pair, count)
|
||||||
|
// Jump back to start
|
||||||
|
executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
|
||||||
|
}
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
|
||||||
|
// XXX: Will it work with line-wise or block-wise selections?
|
||||||
|
val primaryCaret = editor.caretModel.primaryCaret
|
||||||
|
val range = getSurroundRange(primaryCaret.vim)
|
||||||
|
if (range != null) {
|
||||||
|
val start = RepeatedCharSequence.of(pair.first, count)
|
||||||
|
val end = RepeatedCharSequence.of(pair.second, count)
|
||||||
|
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
|
||||||
|
change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
private fun getSurroundRange(caret: VimCaret): TextRange? {
|
||||||
val editor = caret.editor
|
val editor = caret.editor
|
||||||
val ijEditor = editor.ij
|
val ijEditor = editor.ij
|
||||||
@ -348,15 +375,15 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
|
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
|
||||||
runWriteAction {
|
runWriteAction {
|
||||||
val editor = caret.editor
|
val editor = caret.editor
|
||||||
val change = VimPlugin.getChange()
|
val change = VimPlugin.getChange()
|
||||||
val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
|
val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
|
||||||
|
|
||||||
val isEOF = range.endOffset == editor.text().length
|
val isEOF = range.endOffset == editor.text().length
|
||||||
val hasNewLine = editor.endsWithNewLine()
|
val hasNewLine = editor.endsWithNewLine()
|
||||||
val rightSurround = if (tagsOnNewLines) {
|
val rightSurround = (if (tagsOnNewLines) {
|
||||||
if (isEOF && !hasNewLine) {
|
if (isEOF && !hasNewLine) {
|
||||||
"\n" + pair.second
|
"\n" + pair.second
|
||||||
} else {
|
} else {
|
||||||
@ -364,7 +391,7 @@ internal class VimSurroundExtension : VimExtension {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
pair.second
|
pair.second
|
||||||
}
|
}).let { RepeatedCharSequence.of(it, count) }
|
||||||
|
|
||||||
change.insertText(editor, caret, range.startOffset, leftSurround)
|
change.insertText(editor, caret, range.startOffset, leftSurround)
|
||||||
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
|
||||||
|
@ -83,7 +83,7 @@ public object IjOptions {
|
|||||||
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
|
||||||
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
|
public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
|
||||||
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
|
||||||
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true))
|
public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
|
||||||
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
|
public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
|
||||||
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
|
public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
|
||||||
|
|
||||||
|
@ -275,8 +275,8 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
|
|||||||
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
|
private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
|
||||||
for (KeyStroke key : keys) {
|
for (KeyStroke key : keys) {
|
||||||
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
|
if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
|
||||||
key.getKeyCode() != KeyEvent.VK_ESCAPE &&
|
!(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
|
||||||
key.getKeyCode() != KeyEvent.VK_ENTER) {
|
!(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
|
||||||
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
|
getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.maddyhome.idea.vim.group
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.daemon.ReferenceImporter
|
||||||
|
import com.intellij.openapi.actionSystem.CommonDataKeys
|
||||||
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.application.ReadAction
|
||||||
|
import com.intellij.openapi.command.WriteCommandAction
|
||||||
|
import com.intellij.openapi.editor.Editor
|
||||||
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicator
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.progress.Task
|
||||||
|
import com.intellij.psi.PsiDocumentManager
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
|
||||||
|
import java.util.function.BooleanSupplier
|
||||||
|
|
||||||
|
internal object MacroAutoImport {
|
||||||
|
fun run(editor: Editor, dataContext: DataContext) {
|
||||||
|
val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
|
||||||
|
val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
|
||||||
|
|
||||||
|
if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val importers = ReferenceImporter.EP_NAME.extensionList
|
||||||
|
if (importers.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
|
||||||
|
override fun run(indicator: ProgressIndicator) {
|
||||||
|
val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
|
||||||
|
val fixes = mutableListOf<BooleanSupplier>()
|
||||||
|
|
||||||
|
file.accept(object : PsiRecursiveElementWalkingVisitor() {
|
||||||
|
override fun visitElement(element: PsiElement) {
|
||||||
|
for (reference in element.references) {
|
||||||
|
if (reference.resolve() != null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
for (importer in importers) {
|
||||||
|
importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
|
||||||
|
?.let(fixes::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.visitElement(element)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return@nonBlocking fixes
|
||||||
|
}.executeSynchronously()
|
||||||
|
|
||||||
|
ApplicationManager.getApplication().invokeAndWait {
|
||||||
|
WriteCommandAction.writeCommandAction(project)
|
||||||
|
.withName("Auto Import")
|
||||||
|
.withGroupId("IdeaVimAutoImportAfterMacro")
|
||||||
|
.shouldRecordActionForActiveDocument(true)
|
||||||
|
.run<RuntimeException> {
|
||||||
|
fixes.forEach { it.asBoolean }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.maddyhome.idea.vim.group
|
package com.maddyhome.idea.vim.group
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.completion.CompletionPhase
|
||||||
|
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
@ -19,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
import com.maddyhome.idea.vim.helper.MessageHelper.message
|
||||||
import com.maddyhome.idea.vim.macro.VimMacroBase
|
import com.maddyhome.idea.vim.macro.VimMacroBase
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to handle playback of macros
|
* Used to handle playback of macros
|
||||||
@ -71,12 +74,18 @@ internal class MacroGroup : VimMacroBase() {
|
|||||||
} catch (e: ProcessCanceledException) {
|
} catch (e: ProcessCanceledException) {
|
||||||
return@runnable
|
return@runnable
|
||||||
}
|
}
|
||||||
ProgressManager.getInstance().executeNonCancelableSection { getInstance().handleKey(editor, key, context) }
|
ProgressManager.getInstance().executeNonCancelableSection {
|
||||||
|
CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
|
||||||
|
getInstance().handleKey(editor, key, context)
|
||||||
|
}
|
||||||
if (injector.messages.isError()) return@runnable
|
if (injector.messages.isError()) return@runnable
|
||||||
}
|
}
|
||||||
keyStack.resetFirst()
|
keyStack.resetFirst()
|
||||||
}
|
}
|
||||||
keyStack.removeFirst()
|
keyStack.removeFirst()
|
||||||
|
if (!isInternalMacro) {
|
||||||
|
MacroAutoImport.run(editor.ij, context.ij)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isInternalMacro) {
|
if (isInternalMacro) {
|
||||||
|
@ -21,8 +21,11 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
|
|||||||
import com.intellij.openapi.actionSystem.AnAction
|
import com.intellij.openapi.actionSystem.AnAction
|
||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.ide.CopyPasteManager
|
import com.intellij.openapi.ide.CopyPasteManager
|
||||||
import com.intellij.openapi.keymap.KeymapUtil
|
import com.intellij.openapi.keymap.KeymapUtil
|
||||||
|
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||||
|
import com.intellij.openapi.keymap.impl.ui.KeymapPanel
|
||||||
import com.intellij.openapi.options.ShowSettingsUtil
|
import com.intellij.openapi.options.ShowSettingsUtil
|
||||||
import com.intellij.openapi.project.DumbAwareAction
|
import com.intellij.openapi.project.DumbAwareAction
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
@ -32,6 +35,7 @@ import com.maddyhome.idea.vim.VimPlugin
|
|||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.globalOptions
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.handler.KeyMapIssue
|
||||||
import com.maddyhome.idea.vim.helper.MessageHelper
|
import com.maddyhome.idea.vim.helper.MessageHelper
|
||||||
import com.maddyhome.idea.vim.key.ShortcutOwner
|
import com.maddyhome.idea.vim.key.ShortcutOwner
|
||||||
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
|
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
|
||||||
@ -180,6 +184,77 @@ internal class NotificationService(private val project: Project?) {
|
|||||||
ActionIdNotifier.notifyActionId(id, project)
|
ActionIdNotifier.notifyActionId(id, project)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
|
||||||
|
val keymapManager = KeymapManagerEx.getInstanceEx()
|
||||||
|
val keymap = keymapManager.activeKeymap
|
||||||
|
val message = buildString {
|
||||||
|
appendLine("Current IDE keymap (${keymap.name}) has issues:<br/>")
|
||||||
|
issues.forEach {
|
||||||
|
when (it) {
|
||||||
|
is KeyMapIssue.AddShortcut -> {
|
||||||
|
appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
|
||||||
|
}
|
||||||
|
is KeyMapIssue.RemoveShortcut -> {
|
||||||
|
appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val notification = IDEAVIM_STICKY_GROUP.createNotification(
|
||||||
|
IDEAVIM_NOTIFICATION_TITLE,
|
||||||
|
message,
|
||||||
|
NotificationType.ERROR,
|
||||||
|
)
|
||||||
|
notification.subtitle = "IDE keymap misconfigured"
|
||||||
|
notification.addAction(object : DumbAwareAction("Fix Keymap") {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
issues.forEach {
|
||||||
|
when (it) {
|
||||||
|
is KeyMapIssue.AddShortcut -> {
|
||||||
|
keymap.addShortcut(it.actionId, KeyboardShortcut(it.keyStroke, null))
|
||||||
|
}
|
||||||
|
|
||||||
|
is KeyMapIssue.RemoveShortcut -> {
|
||||||
|
keymap.removeShortcut(it.actionId, it.shortcut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LOG.info("Shortcuts updated $issues")
|
||||||
|
notification.expire()
|
||||||
|
requiredShortcutsAssigned()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
|
||||||
|
notification.hideBalloon()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
notification.addAction(object : DumbAwareAction("Ignore") {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
LOG.info("Ignored to update shortcuts $issues")
|
||||||
|
notification.hideBalloon()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
notification.notify(project)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requiredShortcutsAssigned() {
|
||||||
|
val notification = Notification(
|
||||||
|
IDEAVIM_NOTIFICATION_ID,
|
||||||
|
IDEAVIM_NOTIFICATION_TITLE,
|
||||||
|
"Keymap fixed",
|
||||||
|
NotificationType.INFORMATION,
|
||||||
|
)
|
||||||
|
notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
|
||||||
|
override fun actionPerformed(e: AnActionEvent) {
|
||||||
|
ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
|
||||||
|
notification.hideBalloon()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
notification.notify(project)
|
||||||
|
}
|
||||||
|
|
||||||
object ActionIdNotifier {
|
object ActionIdNotifier {
|
||||||
private var notification: Notification? = null
|
private var notification: Notification? = null
|
||||||
private const val NO_ID = "<i>Cannot detect action id</i>"
|
private const val NO_ID = "<i>Cannot detect action id</i>"
|
||||||
@ -314,6 +389,8 @@ internal class NotificationService(private val project: Project?) {
|
|||||||
const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
|
const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
|
||||||
const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
|
const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
|
||||||
|
|
||||||
|
private val LOG = logger<NotificationService>()
|
||||||
|
|
||||||
private fun createIdeaVimRcManually(message: String, project: Project?) {
|
private fun createIdeaVimRcManually(message: String, project: Project?) {
|
||||||
val notification =
|
val notification =
|
||||||
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
|
Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
|
||||||
|
@ -1,285 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2023 The IdeaVim authors
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style
|
|
||||||
* license that can be found in the LICENSE.txt file or at
|
|
||||||
* https://opensource.org/licenses/MIT.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.group;
|
|
||||||
|
|
||||||
import com.intellij.execution.ExecutionException;
|
|
||||||
import com.intellij.execution.configurations.GeneralCommandLine;
|
|
||||||
import com.intellij.execution.process.CapturingProcessHandler;
|
|
||||||
import com.intellij.execution.process.ProcessAdapter;
|
|
||||||
import com.intellij.execution.process.ProcessEvent;
|
|
||||||
import com.intellij.execution.process.ProcessOutput;
|
|
||||||
import com.intellij.openapi.actionSystem.DataContext;
|
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
|
||||||
import com.intellij.openapi.editor.Editor;
|
|
||||||
import com.intellij.openapi.progress.ProcessCanceledException;
|
|
||||||
import com.intellij.openapi.progress.ProgressIndicator;
|
|
||||||
import com.intellij.openapi.progress.ProgressIndicatorProvider;
|
|
||||||
import com.intellij.openapi.progress.ProgressManager;
|
|
||||||
import com.intellij.util.execution.ParametersListUtil;
|
|
||||||
import com.intellij.util.text.CharSequenceReader;
|
|
||||||
import com.maddyhome.idea.vim.KeyHandler;
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin;
|
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext;
|
|
||||||
import com.maddyhome.idea.vim.api.VimEditor;
|
|
||||||
import com.maddyhome.idea.vim.api.VimInjectorKt;
|
|
||||||
import com.maddyhome.idea.vim.api.VimProcessGroupBase;
|
|
||||||
import com.maddyhome.idea.vim.command.Command;
|
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode;
|
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine;
|
|
||||||
import com.maddyhome.idea.vim.ex.ExException;
|
|
||||||
import com.maddyhome.idea.vim.ex.InvalidCommandException;
|
|
||||||
import com.maddyhome.idea.vim.helper.UiHelper;
|
|
||||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
|
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
|
|
||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
|
|
||||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import javax.swing.*;
|
|
||||||
import java.io.*;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
|
|
||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
|
|
||||||
|
|
||||||
|
|
||||||
public class ProcessGroup extends VimProcessGroupBase {
|
|
||||||
public String getLastCommand() {
|
|
||||||
return lastCommand;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startSearchCommand(@NotNull VimEditor editor, ExecutionContext context, int count, char leader) {
|
|
||||||
if (((IjVimEditor)editor).getEditor().isOneLineMode()) // Don't allow searching in one line editors
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String initText = "";
|
|
||||||
String label = String.valueOf(leader);
|
|
||||||
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
|
||||||
panel.activate(((IjVimEditor)editor).getEditor(), ((DataContext)context.getContext()), label, initText, count);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public @NotNull String endSearchCommand() {
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
|
||||||
panel.deactivate(true);
|
|
||||||
|
|
||||||
return panel.getText();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void startExCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
|
|
||||||
// Don't allow ex commands in one line editors
|
|
||||||
if (editor.isOneLineMode()) return;
|
|
||||||
|
|
||||||
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd);
|
|
||||||
injector.getMarkService().setVisualSelectionMarks(editor);
|
|
||||||
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
|
||||||
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean processExKey(@NotNull VimEditor editor, @NotNull KeyStroke stroke) {
|
|
||||||
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
|
|
||||||
// is open. So I'll put focus back in the editor and process the key.
|
|
||||||
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
|
||||||
if (panel.isActive()) {
|
|
||||||
UiHelper.requestFocus(panel.getEntry());
|
|
||||||
panel.handleKey(stroke);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
|
|
||||||
KeyHandler.getInstance().reset(editor);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean processExEntry(final @NotNull VimEditor editor, final @NotNull ExecutionContext context) {
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
|
||||||
panel.deactivate(true);
|
|
||||||
boolean res = true;
|
|
||||||
try {
|
|
||||||
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
|
|
||||||
|
|
||||||
logger.debug("processing command");
|
|
||||||
|
|
||||||
final String text = panel.getText();
|
|
||||||
|
|
||||||
if (!panel.getLabel().equals(":")) {
|
|
||||||
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
|
|
||||||
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
|
|
||||||
// We should never be invoked for anything other than an actual ex command.
|
|
||||||
throw new InvalidCommandException("Expected ':' command. Got '" + panel.getLabel() + "'", text);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) logger.debug("swing=" + SwingUtilities.isEventDispatchThread());
|
|
||||||
|
|
||||||
VimInjectorKt.getInjector().getVimscriptExecutor().execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext.INSTANCE);
|
|
||||||
}
|
|
||||||
catch (ExException e) {
|
|
||||||
VimPlugin.showMessage(e.getMessage());
|
|
||||||
VimPlugin.indicateError();
|
|
||||||
res = false;
|
|
||||||
}
|
|
||||||
catch (Exception bad) {
|
|
||||||
ProcessGroup.logger.error(bad);
|
|
||||||
VimPlugin.indicateError();
|
|
||||||
res = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
|
||||||
|
|
||||||
// commands executed from map command / macro should not be added to history
|
|
||||||
private boolean skipHistory(VimEditor editor) {
|
|
||||||
return VimStateMachine.Companion.getInstance(editor).getMappingState().isExecutingMap() || injector.getMacro().isExecutingMacro();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void cancelExEntry(final @NotNull VimEditor editor, boolean resetCaret) {
|
|
||||||
VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
|
|
||||||
KeyHandler.getInstance().reset(editor);
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
|
||||||
panel.deactivate(true, resetCaret);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void startFilterCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
|
|
||||||
String initText = getRange(((IjVimEditor) editor).getEditor(), cmd) + "!";
|
|
||||||
VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
|
|
||||||
ExEntryPanel panel = ExEntryPanel.getInstance();
|
|
||||||
panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private @NotNull String getRange(Editor editor, @NotNull Command cmd) {
|
|
||||||
String initText = "";
|
|
||||||
if (VimStateMachine.Companion.getInstance(new IjVimEditor(editor)).getMode() instanceof Mode.VISUAL) {
|
|
||||||
initText = "'<,'>";
|
|
||||||
}
|
|
||||||
else if (cmd.getRawCount() > 0) {
|
|
||||||
if (cmd.getCount() == 1) {
|
|
||||||
initText = ".";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
initText = ".,.+" + (cmd.getCount() - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return initText;
|
|
||||||
}
|
|
||||||
|
|
||||||
public @Nullable String executeCommand(@NotNull VimEditor editor, @NotNull String command, @Nullable CharSequence input, @Nullable String currentDirectoryPath)
|
|
||||||
throws ExecutionException, ProcessCanceledException {
|
|
||||||
|
|
||||||
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
|
|
||||||
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
|
|
||||||
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
|
|
||||||
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
|
|
||||||
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
|
|
||||||
return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
|
|
||||||
|
|
||||||
final String shell = globalOptions(injector).getShell();
|
|
||||||
final String shellcmdflag = globalOptions(injector).getShellcmdflag();
|
|
||||||
final String shellxescape = globalOptions(injector).getShellxescape();
|
|
||||||
final String shellxquote = globalOptions(injector).getShellxquote();
|
|
||||||
|
|
||||||
// For Win32. See :help 'shellxescape'
|
|
||||||
final String escapedCommand = shellxquote.equals("(")
|
|
||||||
? doEscape(command, shellxescape, "^")
|
|
||||||
: command;
|
|
||||||
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
|
|
||||||
final String quotedCommand = shellxquote.equals("(")
|
|
||||||
? "(" + escapedCommand + ")"
|
|
||||||
: (shellxquote.equals("\"(")
|
|
||||||
? "\"(" + escapedCommand + ")\""
|
|
||||||
: shellxquote + escapedCommand + shellxquote);
|
|
||||||
|
|
||||||
final ArrayList<String> commands = new ArrayList<>();
|
|
||||||
commands.add(shell);
|
|
||||||
if (!shellcmdflag.isEmpty()) {
|
|
||||||
// Note that Vim also does a simple whitespace split for multiple parameters
|
|
||||||
commands.addAll(ParametersListUtil.parse(shellcmdflag));
|
|
||||||
}
|
|
||||||
commands.add(quotedCommand);
|
|
||||||
|
|
||||||
if (logger.isDebugEnabled()) {
|
|
||||||
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand));
|
|
||||||
}
|
|
||||||
|
|
||||||
final GeneralCommandLine commandLine = new GeneralCommandLine(commands);
|
|
||||||
if (currentDirectoryPath != null) {
|
|
||||||
commandLine.setWorkDirectory(currentDirectoryPath);
|
|
||||||
}
|
|
||||||
final CapturingProcessHandler handler = new CapturingProcessHandler(commandLine);
|
|
||||||
if (input != null) {
|
|
||||||
handler.addProcessListener(new ProcessAdapter() {
|
|
||||||
@Override
|
|
||||||
public void startNotified(@NotNull ProcessEvent event) {
|
|
||||||
try {
|
|
||||||
final CharSequenceReader charSequenceReader = new CharSequenceReader(input);
|
|
||||||
final BufferedWriter outputStreamWriter = new BufferedWriter(new OutputStreamWriter(handler.getProcessInput()));
|
|
||||||
copy(charSequenceReader, outputStreamWriter);
|
|
||||||
outputStreamWriter.close();
|
|
||||||
}
|
|
||||||
catch (IOException e) {
|
|
||||||
logger.error(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getInstance().getProgressIndicator();
|
|
||||||
final ProcessOutput output = handler.runProcessWithProgressIndicator(progressIndicator);
|
|
||||||
|
|
||||||
lastCommand = command;
|
|
||||||
|
|
||||||
if (output.isCancelled()) {
|
|
||||||
// TODO: Vim will use whatever text has already been written to stdout
|
|
||||||
// For whatever reason, we're not getting any here, so just throw an exception
|
|
||||||
throw new ProcessCanceledException();
|
|
||||||
}
|
|
||||||
|
|
||||||
final Integer exitCode = handler.getExitCode();
|
|
||||||
if (exitCode != null && exitCode != 0) {
|
|
||||||
VimPlugin.showMessage("shell returned " + exitCode);
|
|
||||||
VimPlugin.indicateError();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get stderr; stdout and strip colors, which are not handles properly.
|
|
||||||
return (output.getStderr() + output.getStdout()).replaceAll("\u001B\\[[;\\d]*m", "");
|
|
||||||
}, "IdeaVim - !" + command, true, ((IjVimEditor) editor).getEditor().getProject());
|
|
||||||
}
|
|
||||||
|
|
||||||
private String doEscape(String original, String charsToEscape, String escapeChar) {
|
|
||||||
String result = original;
|
|
||||||
for (char c : charsToEscape.toCharArray()) {
|
|
||||||
result = result.replace("" + c, escapeChar + c);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Java 10 has a transferTo method we could use instead
|
|
||||||
private void copy(@NotNull Reader from, @NotNull Writer to) throws IOException {
|
|
||||||
char[] buf = new char[2048];
|
|
||||||
int cnt;
|
|
||||||
while ((cnt = from.read(buf)) != -1) {
|
|
||||||
to.write(buf, 0, cnt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String lastCommand;
|
|
||||||
|
|
||||||
private static final Logger logger = Logger.getInstance(ProcessGroup.class.getName());
|
|
||||||
}
|
|
281
src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt
Normal file
281
src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt
Normal file
@ -0,0 +1,281 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
package com.maddyhome.idea.vim.group
|
||||||
|
|
||||||
|
import com.intellij.execution.ExecutionException
|
||||||
|
import com.intellij.execution.configurations.GeneralCommandLine
|
||||||
|
import com.intellij.execution.process.CapturingProcessHandler
|
||||||
|
import com.intellij.execution.process.ProcessAdapter
|
||||||
|
import com.intellij.execution.process.ProcessEvent
|
||||||
|
import com.intellij.openapi.diagnostic.debug
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
|
import com.intellij.openapi.progress.ProcessCanceledException
|
||||||
|
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.util.execution.ParametersListUtil
|
||||||
|
import com.intellij.util.text.CharSequenceReader
|
||||||
|
import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
|
import com.maddyhome.idea.vim.api.VimProcessGroupBase
|
||||||
|
import com.maddyhome.idea.vim.api.globalOptions
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.command.Command
|
||||||
|
import com.maddyhome.idea.vim.ex.ExException
|
||||||
|
import com.maddyhome.idea.vim.ex.InvalidCommandException
|
||||||
|
import com.maddyhome.idea.vim.helper.requestFocus
|
||||||
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
|
||||||
|
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||||
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
|
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
|
||||||
|
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
||||||
|
import java.io.BufferedWriter
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.OutputStreamWriter
|
||||||
|
import java.io.Reader
|
||||||
|
import java.io.Writer
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
|
public class ProcessGroup : VimProcessGroupBase() {
|
||||||
|
override var lastCommand: String? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
|
||||||
|
// Don't allow searching in one line editors
|
||||||
|
if (editor.isOneLineMode()) return
|
||||||
|
|
||||||
|
val initText = ""
|
||||||
|
val label = leader.toString()
|
||||||
|
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
panel.activate(editor.ij, context.ij, label, initText, count)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun endSearchCommand(): String {
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
panel.deactivate(true)
|
||||||
|
|
||||||
|
return panel.text
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||||
|
// Don't allow ex commands in one line editors
|
||||||
|
if (editor.isOneLineMode()) return
|
||||||
|
|
||||||
|
val currentMode = editor.vimStateMachine.mode
|
||||||
|
check(currentMode is ReturnableFromCmd) {
|
||||||
|
"Cannot enable cmd mode from current mode $currentMode"
|
||||||
|
}
|
||||||
|
|
||||||
|
val initText = getRange(editor, cmd)
|
||||||
|
injector.markService.setVisualSelectionMarks(editor)
|
||||||
|
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
panel.activate(editor.ij, context.ij, ":", initText, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
|
||||||
|
// This will only get called if somehow the key focus ended up in the editor while the ex entry window
|
||||||
|
// is open. So I'll put focus back in the editor and process the key.
|
||||||
|
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
if (panel.isActive) {
|
||||||
|
requestFocus(panel.entry)
|
||||||
|
panel.handleKey(stroke)
|
||||||
|
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
getInstance(editor).mode = NORMAL()
|
||||||
|
getInstance().reset(editor)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean {
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
panel.deactivate(true)
|
||||||
|
var res = true
|
||||||
|
try {
|
||||||
|
getInstance(editor).mode = NORMAL()
|
||||||
|
|
||||||
|
logger.debug("processing command")
|
||||||
|
|
||||||
|
val text = panel.text
|
||||||
|
|
||||||
|
if (panel.label != ":") {
|
||||||
|
// Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
|
||||||
|
// <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
|
||||||
|
// We should never be invoked for anything other than an actual ex command.
|
||||||
|
throw InvalidCommandException("Expected ':' command. Got '" + panel.label + "'", text)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debug {
|
||||||
|
"swing=" + SwingUtilities.isEventDispatchThread()
|
||||||
|
}
|
||||||
|
|
||||||
|
injector.vimscriptExecutor.execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext)
|
||||||
|
} catch (e: ExException) {
|
||||||
|
VimPlugin.showMessage(e.message)
|
||||||
|
VimPlugin.indicateError()
|
||||||
|
res = false
|
||||||
|
} catch (bad: Exception) {
|
||||||
|
logger.error(bad)
|
||||||
|
VimPlugin.indicateError()
|
||||||
|
res = false
|
||||||
|
}
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// commands executed from map command / macro should not be added to history
|
||||||
|
private fun skipHistory(editor: VimEditor): Boolean {
|
||||||
|
return getInstance(editor).mappingState.isExecutingMap() || injector.macro.isExecutingMacro
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
|
||||||
|
editor.vimStateMachine.mode = NORMAL()
|
||||||
|
getInstance().reset(editor)
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
panel.deactivate(true, resetCaret)
|
||||||
|
}
|
||||||
|
|
||||||
|
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||||
|
val initText = getRange(editor, cmd) + "!"
|
||||||
|
val currentMode = editor.mode
|
||||||
|
check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
|
||||||
|
editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
|
||||||
|
val panel = ExEntryPanel.getInstance()
|
||||||
|
panel.activate(editor.ij, context.ij, ":", initText, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getRange(editor: VimEditor, cmd: Command): String {
|
||||||
|
var initText = ""
|
||||||
|
if (editor.vimStateMachine.mode is VISUAL) {
|
||||||
|
initText = "'<,'>"
|
||||||
|
} else if (cmd.rawCount > 0) {
|
||||||
|
initText = if (cmd.count == 1) {
|
||||||
|
"."
|
||||||
|
} else {
|
||||||
|
".,.+" + (cmd.count - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return initText
|
||||||
|
}
|
||||||
|
|
||||||
|
@Throws(ExecutionException::class, ProcessCanceledException::class)
|
||||||
|
public override fun executeCommand(
|
||||||
|
editor: VimEditor,
|
||||||
|
command: String,
|
||||||
|
input: CharSequence?,
|
||||||
|
currentDirectoryPath: String?
|
||||||
|
): String? {
|
||||||
|
// This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
|
||||||
|
// redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
|
||||||
|
// not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
|
||||||
|
// better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
|
||||||
|
// Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
|
||||||
|
|
||||||
|
return ProgressManager.getInstance().runProcessWithProgressSynchronously<String, ExecutionException>(
|
||||||
|
{
|
||||||
|
val shell = injector.globalOptions().shell
|
||||||
|
val shellcmdflag = injector.globalOptions().shellcmdflag
|
||||||
|
val shellxescape = injector.globalOptions().shellxescape
|
||||||
|
val shellxquote = injector.globalOptions().shellxquote
|
||||||
|
|
||||||
|
// For Win32. See :help 'shellxescape'
|
||||||
|
val escapedCommand = if (shellxquote == "(") doEscape(command, shellxescape, "^")
|
||||||
|
else command
|
||||||
|
// Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
|
||||||
|
val quotedCommand = if (shellxquote == "(") "($escapedCommand)"
|
||||||
|
else (if (shellxquote == "\"(") "\"($escapedCommand)\""
|
||||||
|
else shellxquote + escapedCommand + shellxquote)
|
||||||
|
|
||||||
|
val commands = ArrayList<String>()
|
||||||
|
commands.add(shell)
|
||||||
|
if (shellcmdflag.isNotEmpty()) {
|
||||||
|
// Note that Vim also does a simple whitespace split for multiple parameters
|
||||||
|
commands.addAll(ParametersListUtil.parse(shellcmdflag))
|
||||||
|
}
|
||||||
|
commands.add(quotedCommand)
|
||||||
|
|
||||||
|
if (logger.isDebugEnabled) {
|
||||||
|
logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand))
|
||||||
|
}
|
||||||
|
|
||||||
|
val commandLine = GeneralCommandLine(commands)
|
||||||
|
if (currentDirectoryPath != null) {
|
||||||
|
commandLine.setWorkDirectory(currentDirectoryPath)
|
||||||
|
}
|
||||||
|
val handler = CapturingProcessHandler(commandLine)
|
||||||
|
if (input != null) {
|
||||||
|
handler.addProcessListener(object : ProcessAdapter() {
|
||||||
|
override fun startNotified(event: ProcessEvent) {
|
||||||
|
try {
|
||||||
|
val charSequenceReader = CharSequenceReader(input)
|
||||||
|
val outputStreamWriter = BufferedWriter(OutputStreamWriter(handler.processInput))
|
||||||
|
copy(charSequenceReader, outputStreamWriter)
|
||||||
|
outputStreamWriter.close()
|
||||||
|
} catch (e: IOException) {
|
||||||
|
logger.error(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
|
||||||
|
val output = handler.runProcessWithProgressIndicator(progressIndicator)
|
||||||
|
|
||||||
|
lastCommand = command
|
||||||
|
|
||||||
|
if (output.isCancelled) {
|
||||||
|
// TODO: Vim will use whatever text has already been written to stdout
|
||||||
|
// For whatever reason, we're not getting any here, so just throw an exception
|
||||||
|
throw ProcessCanceledException()
|
||||||
|
}
|
||||||
|
|
||||||
|
val exitCode = handler.exitCode
|
||||||
|
if (exitCode != null && exitCode != 0) {
|
||||||
|
VimPlugin.showMessage("shell returned $exitCode")
|
||||||
|
VimPlugin.indicateError()
|
||||||
|
}
|
||||||
|
(output.stderr + output.stdout).replace("\u001B\\[[;\\d]*m".toRegex(), "")
|
||||||
|
}, "IdeaVim - !$command", true, editor.ij.project
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("SameParameterValue")
|
||||||
|
private fun doEscape(original: String, charsToEscape: String, escapeChar: String): String {
|
||||||
|
var result = original
|
||||||
|
for (c in charsToEscape.toCharArray()) {
|
||||||
|
result = result.replace("" + c, escapeChar + c)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Java 10 has a transferTo method we could use instead
|
||||||
|
@Throws(IOException::class)
|
||||||
|
private fun copy(from: Reader, to: Writer) {
|
||||||
|
val buf = CharArray(2048)
|
||||||
|
var cnt: Int
|
||||||
|
while ((from.read(buf).also { cnt = it }) != -1) {
|
||||||
|
to.write(buf, 0, cnt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public companion object {
|
||||||
|
private val logger = logger<ProcessGroup>()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.handler
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||||
|
import com.intellij.openapi.diagnostic.logger
|
||||||
|
import com.intellij.openapi.keymap.Keymap
|
||||||
|
import com.intellij.openapi.keymap.KeymapManagerListener
|
||||||
|
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.startup.StartupActivity
|
||||||
|
import com.intellij.util.SingleAlarm
|
||||||
|
import com.jetbrains.rd.util.ConcurrentHashMap
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.api.key
|
||||||
|
|
||||||
|
|
||||||
|
// We use alarm with delay to avoid many actions in case many events are fired at the same time
|
||||||
|
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
|
||||||
|
internal val correctorRequester = SingleAlarm({ correctCopilotKeymap() }, 1_000)
|
||||||
|
|
||||||
|
private val LOG = logger<CopilotKeymapCorrector>()
|
||||||
|
|
||||||
|
internal class CopilotKeymapCorrector : StartupActivity {
|
||||||
|
override fun runActivity(project: Project) {
|
||||||
|
correctorRequester.request()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
|
||||||
|
override fun activeKeymapChanged(keymap: Keymap?) {
|
||||||
|
correctorRequester.request()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
||||||
|
correctorRequester.request()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
||||||
|
correctorRequester.request()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val copilotHideActionMap = ConcurrentHashMap<String, Unit>()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* See VIM-3206
|
||||||
|
* The user expected to both copilot suggestion and the insert mode to be exited on a single esc.
|
||||||
|
* However, for the moment, the first esc hides copilot suggestion and the second one exits insert mode.
|
||||||
|
* To fix this, we remove the esc shortcut from the copilot action if the IdeaVim is active.
|
||||||
|
*
|
||||||
|
* This workaround is not the best solution, however, I don't see the better way with the current architecture of
|
||||||
|
* actions and EditorHandlers. Firstly, I wanted to suggest to copilot to migrate to EditorActionHandler as well,
|
||||||
|
* but this doesn't seem correct for me because in this case the user will lose an ability to change the shorcut for
|
||||||
|
* it. It seems like copilot has a similar problem as we do - we don't want to make a handler for "Editor enter action",
|
||||||
|
* but a handler for the esc key press. And, moreover, be able to communicate with other plugins about the ordering.
|
||||||
|
* Before this feature is implemented, hiding the copilot suggestion on esc looks like a good workaround.
|
||||||
|
*/
|
||||||
|
private fun correctCopilotKeymap() {
|
||||||
|
// This is needed to initialize the injector in case this verification is called to fast
|
||||||
|
VimPlugin.getInstance()
|
||||||
|
|
||||||
|
if (injector.enabler.isEnabled()) {
|
||||||
|
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
||||||
|
val res = keymap.getShortcuts("copilot.disposeInlays")
|
||||||
|
if (res.isEmpty()) return
|
||||||
|
|
||||||
|
|
||||||
|
val escapeShortcut = res.find { it.toString() == "[pressed ESCAPE]" } ?: return
|
||||||
|
keymap.removeShortcut("copilot.disposeInlays", escapeShortcut)
|
||||||
|
copilotHideActionMap[keymap.name] = Unit
|
||||||
|
LOG.info("Remove copilot escape shortcut from keymap ${keymap.name}")
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
copilotHideActionMap.forEach { (name, _) ->
|
||||||
|
val keymap = KeymapManagerEx.getInstanceEx().getKeymap(name) ?: return@forEach
|
||||||
|
val currentShortcuts = keymap.getShortcuts("copilot.disposeInlays")
|
||||||
|
if ("[pressed ESCAPE]" !in currentShortcuts.map { it.toString() }) {
|
||||||
|
keymap.addShortcut("copilot.disposeInlays", KeyboardShortcut(key("<esc>"), null))
|
||||||
|
}
|
||||||
|
LOG.info("Restore copilot escape shortcut in keymap ${keymap.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,11 +8,14 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.handler
|
package com.maddyhome.idea.vim.handler
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
import com.intellij.openapi.diagnostic.logger
|
import com.intellij.openapi.diagnostic.logger
|
||||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
|
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
|
||||||
import com.intellij.openapi.extensions.ExtensionPointName
|
import com.intellij.openapi.extensions.ExtensionPointName
|
||||||
|
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||||
import com.intellij.openapi.project.Project
|
import com.intellij.openapi.project.Project
|
||||||
import com.intellij.openapi.startup.StartupActivity
|
import com.intellij.openapi.startup.ProjectActivity
|
||||||
|
import com.maddyhome.idea.vim.api.key
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Logs the chain of handlers for esc and enter
|
* Logs the chain of handlers for esc and enter
|
||||||
@ -26,11 +29,11 @@ import com.intellij.openapi.startup.StartupActivity
|
|||||||
* Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
|
* Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
|
||||||
* otherwise, so let's use it as long as we can.
|
* otherwise, so let's use it as long as we can.
|
||||||
*/
|
*/
|
||||||
internal class EditorHandlersChainLogger : StartupActivity {
|
internal class EditorHandlersChainLogger : ProjectActivity {
|
||||||
@Suppress("UnresolvedPluginConfigReference")
|
@Suppress("UnresolvedPluginConfigReference")
|
||||||
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
|
private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
|
||||||
|
|
||||||
override fun runActivity(project: Project) {
|
override suspend fun execute(project: Project) {
|
||||||
val escHandlers = editorHandlers.extensionList
|
val escHandlers = editorHandlers.extensionList
|
||||||
.filter { it.action == "EditorEscape" }
|
.filter { it.action == "EditorEscape" }
|
||||||
.joinToString("\n") { it.implementationClass }
|
.joinToString("\n") { it.implementationClass }
|
||||||
@ -40,6 +43,22 @@ internal class EditorHandlersChainLogger : StartupActivity {
|
|||||||
|
|
||||||
LOG.info("Esc handlers chain:\n$escHandlers")
|
LOG.info("Esc handlers chain:\n$escHandlers")
|
||||||
LOG.info("Enter handlers chain:\n$enterHandlers")
|
LOG.info("Enter handlers chain:\n$enterHandlers")
|
||||||
|
|
||||||
|
val keymapManager = KeymapManagerEx.getInstanceEx()
|
||||||
|
val keymap = keymapManager.activeKeymap
|
||||||
|
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE).joinToString()
|
||||||
|
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER).joinToString()
|
||||||
|
|
||||||
|
LOG.info("Active keymap (${keymap.name}) shortcuts for esc: $keymapShortcutsForEsc, Shortcuts for enter: $keymapShortcutsForEnter")
|
||||||
|
|
||||||
|
val actionsForEsc = keymap.getActionIds(key("<esc>")).joinToString("\n")
|
||||||
|
val actionsForEnter = keymap.getActionIds(key("<enter>")).joinToString("\n")
|
||||||
|
|
||||||
|
LOG.info(
|
||||||
|
"Also keymap (${keymap.name}) has " +
|
||||||
|
"the following actions assigned to esc:\n$actionsForEsc " +
|
||||||
|
"\nand following actions assigned to enter:\n$actionsForEnter"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
128
src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt
Normal file
128
src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.maddyhome.idea.vim.handler
|
||||||
|
|
||||||
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
|
import com.intellij.openapi.actionSystem.KeyboardShortcut
|
||||||
|
import com.intellij.openapi.actionSystem.Shortcut
|
||||||
|
import com.intellij.openapi.keymap.Keymap
|
||||||
|
import com.intellij.openapi.keymap.KeymapManagerListener
|
||||||
|
import com.intellij.openapi.keymap.ex.KeymapManagerEx
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.startup.StartupActivity
|
||||||
|
import com.intellij.util.SingleAlarm
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.api.key
|
||||||
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
|
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
|
||||||
|
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
|
||||||
|
internal val keymapCheckRequester = SingleAlarm({ verifyKeymap() }, 5_000)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
|
||||||
|
*/
|
||||||
|
internal class KeymapChecker : StartupActivity {
|
||||||
|
override fun runActivity(project: Project) {
|
||||||
|
keymapCheckRequester.request()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
|
||||||
|
override fun activeKeymapChanged(keymap: Keymap?) {
|
||||||
|
keymapCheckRequester.request()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shortcutChanged(keymap: Keymap, actionId: String) {
|
||||||
|
keymapCheckRequester.request()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
|
||||||
|
keymapCheckRequester.request()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After migration to the editor action handlers, we have to make sure that the keymap has a correct configuration.
|
||||||
|
* For example, that esc key is assigned to esc editor action
|
||||||
|
*
|
||||||
|
* Usually this is not a problem because this is a standard mapping, but the problem may appear in a misconfiguration
|
||||||
|
* like it was in VIM-3204
|
||||||
|
*/
|
||||||
|
private fun verifyKeymap() {
|
||||||
|
// This is needed to initialize the injector in case this verification is called to fast
|
||||||
|
VimPlugin.getInstance()
|
||||||
|
|
||||||
|
if (!injector.enabler.isEnabled()) return
|
||||||
|
|
||||||
|
val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
|
||||||
|
val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE)
|
||||||
|
val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER)
|
||||||
|
|
||||||
|
val issues = ArrayList<KeyMapIssue>()
|
||||||
|
val correctShortcutMissing = keymapShortcutsForEsc
|
||||||
|
.filterIsInstance<KeyboardShortcut>()
|
||||||
|
.none { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke == null }
|
||||||
|
|
||||||
|
// We also check if there are any shortcuts starting from esc and with a second key. This should also be removed.
|
||||||
|
// For example, VIM-3162 has a case when two escapes were assigned to editor escape action
|
||||||
|
val shortcutsStartingFromEsc = keymapShortcutsForEsc
|
||||||
|
.filterIsInstance<KeyboardShortcut>()
|
||||||
|
.filter { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke != null }
|
||||||
|
if (correctShortcutMissing) {
|
||||||
|
issues += KeyMapIssue.AddShortcut(
|
||||||
|
"esc",
|
||||||
|
"editor escape",
|
||||||
|
IdeActions.ACTION_EDITOR_ESCAPE,
|
||||||
|
key("<esc>")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
shortcutsStartingFromEsc.forEach {
|
||||||
|
issues += KeyMapIssue.RemoveShortcut("editor escape", IdeActions.ACTION_EDITOR_ESCAPE, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val correctEnterShortcutMissing = keymapShortcutsForEnter
|
||||||
|
.filterIsInstance<KeyboardShortcut>()
|
||||||
|
.none { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke == null }
|
||||||
|
val shortcutsStartingFromEnter = keymapShortcutsForEnter
|
||||||
|
.filterIsInstance<KeyboardShortcut>()
|
||||||
|
.filter { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke != null }
|
||||||
|
if (correctEnterShortcutMissing) {
|
||||||
|
issues += KeyMapIssue.AddShortcut(
|
||||||
|
"enter",
|
||||||
|
"editor enter",
|
||||||
|
IdeActions.ACTION_EDITOR_ENTER,
|
||||||
|
key("<enter>")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
shortcutsStartingFromEnter.forEach {
|
||||||
|
issues += KeyMapIssue.RemoveShortcut("editor enter", IdeActions.ACTION_EDITOR_ENTER, it)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (issues.isNotEmpty()) {
|
||||||
|
VimPlugin.getNotifications(null).notifyKeymapIssues(issues)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed interface KeyMapIssue {
|
||||||
|
data class AddShortcut(
|
||||||
|
val key: String,
|
||||||
|
val action: String,
|
||||||
|
val actionId: String,
|
||||||
|
val keyStroke: KeyStroke,
|
||||||
|
) : KeyMapIssue
|
||||||
|
|
||||||
|
data class RemoveShortcut(
|
||||||
|
val action: String,
|
||||||
|
val actionId: String,
|
||||||
|
val shortcut: Shortcut,
|
||||||
|
): KeyMapIssue
|
||||||
|
}
|
@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.handler
|
|||||||
|
|
||||||
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
|
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
|
||||||
import com.intellij.codeInsight.lookup.LookupManager
|
import com.intellij.codeInsight.lookup.LookupManager
|
||||||
|
import com.intellij.formatting.LineWrappingUtil
|
||||||
import com.intellij.ide.DataManager
|
import com.intellij.ide.DataManager
|
||||||
import com.intellij.openapi.actionSystem.DataContext
|
import com.intellij.openapi.actionSystem.DataContext
|
||||||
import com.intellij.openapi.application.ApplicationManager
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
@ -18,6 +19,7 @@ import com.intellij.openapi.diagnostic.logger
|
|||||||
import com.intellij.openapi.editor.Caret
|
import com.intellij.openapi.editor.Caret
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
|
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
|
||||||
|
import com.intellij.openapi.editor.actions.SplitLineAction
|
||||||
import com.intellij.openapi.editor.impl.CaretModelImpl
|
import com.intellij.openapi.editor.impl.CaretModelImpl
|
||||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||||
import com.intellij.openapi.util.Key
|
import com.intellij.openapi.util.Key
|
||||||
@ -95,7 +97,15 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
|
|||||||
// the condition (see VIM-3103 for example).
|
// the condition (see VIM-3103 for example).
|
||||||
// Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
|
// Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
|
||||||
// done by scheduling the execution of our code later via the invokeLater function.
|
// done by scheduling the execution of our code later via the invokeLater function.
|
||||||
ApplicationManager.getApplication().invokeLater(executionHandler)
|
//
|
||||||
|
// We run this job only once for a primary caret. In the handler itself, we'll multiply the execution by the
|
||||||
|
// number of carets. If we run this job for each caret, we may end up in the issue like VIM-3186.
|
||||||
|
// However, I think that we may do some refactoring to run this job for each caret (if needed).
|
||||||
|
//
|
||||||
|
// For the moment, the known case when the caret is null - work in injected editor - VIM-3195
|
||||||
|
if (caret == null || caret == editor.caretModel.primaryCaret) {
|
||||||
|
ApplicationManager.getApplication().invokeLater(executionHandler)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
executionHandler()
|
executionHandler()
|
||||||
}
|
}
|
||||||
@ -106,7 +116,11 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
|
|||||||
|
|
||||||
private fun executeInInvokeLater(editor: Editor): Boolean {
|
private fun executeInInvokeLater(editor: Editor): Boolean {
|
||||||
// Currently we have a workaround for the PY console VIM-3157
|
// Currently we have a workaround for the PY console VIM-3157
|
||||||
if (FileDocumentManager.getInstance().getFile(editor.document)?.name == "Python Console.py") return false
|
val fileName = FileDocumentManager.getInstance().getFile(editor.document)?.name
|
||||||
|
if (
|
||||||
|
fileName == "Python Console.py" || // This is the name in 232+
|
||||||
|
fileName == "Python Console" // This is the name in 231
|
||||||
|
) return false
|
||||||
return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
|
return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +145,20 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dataManager.loadFromDataContext(dataContext, ShiftEnterDetector.Util.key) == true) {
|
// From VIM-3177
|
||||||
|
val wrapLongLineDuringFormattingInProgress = dataManager
|
||||||
|
.loadFromDataContext(dataContext, LineWrappingUtil.WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY)
|
||||||
|
if (wrapLongLineDuringFormattingInProgress == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// From VIM-3203
|
||||||
|
val splitLineInProgress = dataManager.loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY)
|
||||||
|
if (splitLineInProgress == true) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dataManager.loadFromDataContext(dataContext, StartNewLineDetectorBase.Util.key) == true) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -244,11 +271,17 @@ internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler)
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Workaround to support shift-enter in normal mode.
|
* Workaround to support "Start New Line" action in normal mode.
|
||||||
* IJ executes enter handler on shift-enter. This causes an issue that IdeaVim thinks that this is just an enter key.
|
* IJ executes enter handler on "Start New Line". This causes an issue that IdeaVim thinks that this is just an enter key.
|
||||||
* This thing should be refactored, but for now we'll use this workaround VIM-3159
|
* This thing should be refactored, but for now we'll use this workaround VIM-3159
|
||||||
|
*
|
||||||
|
* The Same thing happens with "Start New Line Before Current" action.
|
||||||
*/
|
*/
|
||||||
internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
internal class StartNewLineDetector(nextHandler: EditorActionHandler) : StartNewLineDetectorBase(nextHandler)
|
||||||
|
internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandler) :
|
||||||
|
StartNewLineDetectorBase(nextHandler)
|
||||||
|
|
||||||
|
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
|
||||||
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
|
||||||
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
|
DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
|
||||||
nextHandler.execute(editor, caret, dataContext)
|
nextHandler.execute(editor, caret, dataContext)
|
||||||
@ -259,7 +292,7 @@ internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler)
|
|||||||
}
|
}
|
||||||
|
|
||||||
object Util {
|
object Util {
|
||||||
val key = Key.create<Boolean>("vim.is.shift.enter")
|
val key = Key.create<Boolean>("vim.is.start.new.line")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -311,9 +344,9 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
|
|||||||
// CMD line has a different processing mechanizm: the processing actions are registered
|
// CMD line has a different processing mechanizm: the processing actions are registered
|
||||||
// for the input field component. These keys are not dispatched via the octopus handler.
|
// for the input field component. These keys are not dispatched via the octopus handler.
|
||||||
if (editor.vim.mode is Mode.CMD_LINE) return false
|
if (editor.vim.mode is Mode.CMD_LINE) return false
|
||||||
when (s.keyCode) {
|
when {
|
||||||
KeyEvent.VK_ENTER -> return true
|
s.keyCode == KeyEvent.VK_ENTER && s.modifiers == 0 -> return true
|
||||||
KeyEvent.VK_ESCAPE -> return true
|
s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
|
|||||||
get() {
|
get() {
|
||||||
val mode = this.vim.vimStateMachine.mode
|
val mode = this.vim.vimStateMachine.mode
|
||||||
return when (mode) {
|
return when (mode) {
|
||||||
Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
|
||||||
Mode.INSERT -> CommandState.Mode.INSERT
|
Mode.INSERT -> CommandState.Mode.INSERT
|
||||||
is Mode.NORMAL -> CommandState.Mode.COMMAND
|
is Mode.NORMAL -> CommandState.Mode.COMMAND
|
||||||
is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
|
is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
|
||||||
|
@ -335,7 +335,7 @@ public class EditorHelper {
|
|||||||
|
|
||||||
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
|
final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
|
||||||
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
@NotNull final VimEditor editor1 = new IjVimEditor(editor);
|
||||||
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
|
final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
|
||||||
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
|
||||||
|
|
||||||
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
// For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
|
||||||
|
@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
|
|||||||
|
|
||||||
import com.intellij.codeWithMe.ClientId
|
import com.intellij.codeWithMe.ClientId
|
||||||
import com.intellij.openapi.editor.Caret
|
import com.intellij.openapi.editor.Caret
|
||||||
|
import com.intellij.openapi.editor.CaretState
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
|
||||||
@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
|
|||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
|
import com.maddyhome.idea.vim.group.IjOptionConstants
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inBlockSelection
|
||||||
import java.awt.Component
|
import java.awt.Component
|
||||||
import javax.swing.JComponent
|
import javax.swing.JComponent
|
||||||
import javax.swing.JTable
|
import javax.swing.JTable
|
||||||
@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
|
|||||||
*/
|
*/
|
||||||
internal val Editor.vimLine: Int
|
internal val Editor.vimLine: Int
|
||||||
get() = this.caretModel.currentCaret.vimLine
|
get() = this.caretModel.currentCaret.vimLine
|
||||||
|
|
||||||
|
internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
|
||||||
|
val caretModel = this.caretModel
|
||||||
|
val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
|
||||||
|
if (carets == null || carets.size == 1) {
|
||||||
|
action()
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var initialDocumentSize = this.document.textLength
|
||||||
|
var documentSizeDifference = 0
|
||||||
|
|
||||||
|
val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
|
||||||
|
val restoredCarets = mutableListOf<CaretState>()
|
||||||
|
|
||||||
|
caretModel.removeSecondaryCarets()
|
||||||
|
|
||||||
|
for ((selectionStart, selectionEnd) in caretOffsets) {
|
||||||
|
if (selectionStart == selectionEnd) {
|
||||||
|
caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
caretModel.primaryCaret.setSelection(
|
||||||
|
selectionStart + documentSizeDifference,
|
||||||
|
selectionEnd + documentSizeDifference
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
action()
|
||||||
|
restoredCarets.add(caretModel.caretsAndSelections.single())
|
||||||
|
|
||||||
|
val documentLength = this.document.textLength
|
||||||
|
documentSizeDifference += documentLength - initialDocumentSize
|
||||||
|
initialDocumentSize = documentLength
|
||||||
|
}
|
||||||
|
|
||||||
|
caretModel.caretsAndSelections = restoredCarets
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -15,10 +15,12 @@ import com.intellij.openapi.actionSystem.AnAction
|
|||||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||||
import com.intellij.openapi.actionSystem.AnActionResult
|
import com.intellij.openapi.actionSystem.AnActionResult
|
||||||
import com.intellij.openapi.actionSystem.DataContextWrapper
|
import com.intellij.openapi.actionSystem.DataContextWrapper
|
||||||
|
import com.intellij.openapi.actionSystem.EmptyAction
|
||||||
import com.intellij.openapi.actionSystem.IdeActions
|
import com.intellij.openapi.actionSystem.IdeActions
|
||||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
import com.intellij.openapi.actionSystem.PlatformDataKeys
|
||||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
|
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
|
||||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
import com.intellij.openapi.actionSystem.ex.ActionUtil
|
||||||
|
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
|
||||||
import com.intellij.openapi.command.CommandProcessor
|
import com.intellij.openapi.command.CommandProcessor
|
||||||
import com.intellij.openapi.command.UndoConfirmationPolicy
|
import com.intellij.openapi.command.UndoConfirmationPolicy
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
@ -39,6 +41,8 @@ import com.maddyhome.idea.vim.newapi.IjNativeAction
|
|||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
import com.maddyhome.idea.vim.newapi.runFromVimKey
|
import com.maddyhome.idea.vim.newapi.runFromVimKey
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
|
import java.awt.Component
|
||||||
|
import javax.swing.JComponent
|
||||||
import javax.swing.SwingUtilities
|
import javax.swing.SwingUtilities
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@ -150,11 +154,44 @@ internal class IjActionExecutor : VimActionExecutor {
|
|||||||
* @param context The context to run it in
|
* @param context The context to run it in
|
||||||
*/
|
*/
|
||||||
override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
|
override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
|
||||||
val aMgr = ActionManager.getInstance()
|
val action = getAction(name, context)
|
||||||
val action = aMgr.getAction(name)
|
|
||||||
return action != null && executeAction(null, IjNativeAction(action), context)
|
return action != null && executeAction(null, IjNativeAction(action), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getAction(name: String, context: ExecutionContext): AnAction? {
|
||||||
|
val actionManager = ActionManager.getInstance()
|
||||||
|
val action = actionManager.getAction(name)
|
||||||
|
if (action !is EmptyAction) return action
|
||||||
|
|
||||||
|
// But if the action is an instance of EmptyAction, the fun begins
|
||||||
|
var component: Component? = context.ij.getData(PlatformDataKeys.CONTEXT_COMPONENT) ?: return null
|
||||||
|
while (component != null) {
|
||||||
|
if (component !is JComponent) {
|
||||||
|
component = component.parent
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
val listOfActions = ActionUtil.getActions(component)
|
||||||
|
if (listOfActions.isEmpty()) {
|
||||||
|
component = component.getParent()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun AnAction.getId(): String? {
|
||||||
|
return actionManager.getId(this)
|
||||||
|
?: (shortcutSet as? ProxyShortcutSet)?.actionId
|
||||||
|
}
|
||||||
|
|
||||||
|
for (action in listOfActions) {
|
||||||
|
if (action.getId() == name) {
|
||||||
|
return action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
component = component.getParent()
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
override fun executeCommand(
|
override fun executeCommand(
|
||||||
editor: VimEditor?,
|
editor: VimEditor?,
|
||||||
runnable: Runnable,
|
runnable: Runnable,
|
||||||
|
@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
import com.maddyhome.idea.vim.api.normalizeVisualColumn
|
||||||
import com.maddyhome.idea.vim.api.options
|
import com.maddyhome.idea.vim.api.options
|
||||||
import com.maddyhome.idea.vim.command.CommandFlags
|
import com.maddyhome.idea.vim.command.CommandFlags
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
|
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
|
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
|
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
|
||||||
@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
|
|||||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
|
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
|
||||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
|
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
import kotlin.math.roundToInt
|
import kotlin.math.roundToInt
|
||||||
@ -56,7 +56,7 @@ internal object ScrollViewHelper {
|
|||||||
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
// that this needs to be replaced as a more or less dumb line for line rewrite.
|
||||||
val topLine = getVisualLineAtTopOfScreen(editor)
|
val topLine = getVisualLineAtTopOfScreen(editor)
|
||||||
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
val bottomLine = getVisualLineAtBottomOfScreen(editor)
|
||||||
val lastLine = vimEditor.getVisualLineCount() - 1
|
val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
|
||||||
|
|
||||||
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
// We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
|
||||||
val scrollOffset = injector.options(vimEditor).scrolloff
|
val scrollOffset = injector.options(vimEditor).scrolloff
|
||||||
|
@ -14,12 +14,16 @@ import com.intellij.openapi.command.CommandProcessor
|
|||||||
import com.intellij.openapi.command.undo.UndoManager
|
import com.intellij.openapi.command.undo.UndoManager
|
||||||
import com.intellij.openapi.components.Service
|
import com.intellij.openapi.components.Service
|
||||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
|
||||||
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
|
import com.maddyhome.idea.vim.common.ChangesListener
|
||||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
|
||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
import com.maddyhome.idea.vim.newapi.globalIjOptions
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
import com.maddyhome.idea.vim.newapi.ij
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.state.mode.inVisualMode
|
||||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
import com.maddyhome.idea.vim.undo.UndoRedoBase
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -38,24 +42,20 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
|
|
||||||
if (injector.globalIjOptions().oldundo) {
|
if (injector.globalIjOptions().oldundo) {
|
||||||
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
|
||||||
|
restoreVisualMode(editor)
|
||||||
} else {
|
} else {
|
||||||
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||||
undoManager.undo(fileEditor)
|
editor.runWithChangeTracking {
|
||||||
if (hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
|
undoManager.undo(fileEditor)
|
||||||
undoManager.undo(fileEditor) // execute one more time if the previous undo just restored selection
|
|
||||||
|
// We execute undo one more time if the previous one just restored selection
|
||||||
|
if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
|
||||||
|
undoManager.undo(fileEditor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove selection
|
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||||
editor.carets().forEach {
|
removeSelections(editor)
|
||||||
val ijCaret = it.ij
|
|
||||||
val hasSelection = ijCaret.hasSelection()
|
|
||||||
if (hasSelection) {
|
|
||||||
val selectionStart = ijCaret.selectionStart
|
|
||||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
|
||||||
it.ij.removeSelection()
|
|
||||||
it.ij.moveToOffset(selectionStart)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,14 +78,79 @@ internal class UndoRedoHelper : UndoRedoBase() {
|
|||||||
if (undoManager.isRedoAvailable(fileEditor)) {
|
if (undoManager.isRedoAvailable(fileEditor)) {
|
||||||
if (injector.globalIjOptions().oldundo) {
|
if (injector.globalIjOptions().oldundo) {
|
||||||
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
|
||||||
|
restoreVisualMode(editor)
|
||||||
} else {
|
} else {
|
||||||
undoManager.redo(fileEditor)
|
undoManager.redo(fileEditor)
|
||||||
CommandProcessor.getInstance().runUndoTransparentAction {
|
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||||
editor.carets().forEach { it.ij.removeSelection() }
|
editor.carets().forEach { it.ij.removeSelection() }
|
||||||
}
|
}
|
||||||
|
// TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
|
||||||
|
editor.runWithChangeTracking {
|
||||||
|
undoManager.redo(fileEditor)
|
||||||
|
|
||||||
|
// We execute undo one more time if the previous one just restored selection
|
||||||
|
if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
|
||||||
|
undoManager.redo(fileEditor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandProcessor.getInstance().runUndoTransparentAction {
|
||||||
|
removeSelections(editor)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun removeSelections(editor: VimEditor) {
|
||||||
|
editor.carets().forEach {
|
||||||
|
val ijCaret = it.ij
|
||||||
|
if (!ijCaret.hasSelection()) return@forEach
|
||||||
|
|
||||||
|
val selectionStart = ijCaret.selectionStart
|
||||||
|
ijCaret.removeSelection()
|
||||||
|
ijCaret.moveToOffset(selectionStart)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
|
||||||
|
val tracker = ChangeTracker(this)
|
||||||
|
tracker.block()
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ChangeTracker(private val editor: VimEditor) {
|
||||||
|
private val initialPath = editor.getPath()
|
||||||
|
private val changeListener = object : ChangesListener {
|
||||||
|
var hasChanged = false
|
||||||
|
|
||||||
|
override fun documentChanged(change: ChangesListener.Change) {
|
||||||
|
hasChanged = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
editor.document.addChangeListener(changeListener)
|
||||||
|
}
|
||||||
|
|
||||||
|
val hasChanges: Boolean
|
||||||
|
get() = changeListener.hasChanged || initialPath != editor.getPath()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun restoreVisualMode(editor: VimEditor) {
|
||||||
|
if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
|
||||||
|
val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
|
||||||
|
|
||||||
|
// Visual block selection is restored into multiple carets, so multi-carets that form a block are always
|
||||||
|
// identified as visual block mode, leading to false positives.
|
||||||
|
// Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
|
||||||
|
// visual block mode.
|
||||||
|
val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
|
||||||
|
SelectionType.CHARACTER_WISE
|
||||||
|
else
|
||||||
|
detectedMode
|
||||||
|
|
||||||
|
VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2003-2023 The IdeaVim authors
|
|
||||||
*
|
|
||||||
* Use of this source code is governed by an MIT-style
|
|
||||||
* license that can be found in the LICENSE.txt file or at
|
|
||||||
* https://opensource.org/licenses/MIT.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.maddyhome.idea.vim.helper
|
|
||||||
|
|
||||||
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
|
|
||||||
import com.intellij.openapi.components.Service
|
|
||||||
import com.intellij.openapi.components.service
|
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
|
||||||
import com.maddyhome.idea.vim.group.NotificationService
|
|
||||||
import com.maddyhome.idea.vim.icons.VimIcons
|
|
||||||
|
|
||||||
@Service(Service.Level.APP)
|
|
||||||
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
|
|
||||||
VimPlugin.getPluginId(),
|
|
||||||
updateTimestampProperty = PROPERTY_NAME,
|
|
||||||
NotificationService.IDEAVIM_STICKY_GROUP,
|
|
||||||
VimIcons.IDEAVIM,
|
|
||||||
) {
|
|
||||||
|
|
||||||
override fun skipUpdateCheck(): Boolean = !VimPlugin.isEnabled() || "dev" in VimPlugin.getVersion()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
|
|
||||||
val instance: VimStandalonePluginUpdateChecker = service()
|
|
||||||
}
|
|
||||||
}
|
|
@ -70,9 +70,10 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
|
|||||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
|
||||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
|
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
|
||||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
|
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
|
||||||
|
import com.maddyhome.idea.vim.handler.correctorRequester
|
||||||
|
import com.maddyhome.idea.vim.handler.keymapCheckRequester
|
||||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
|
||||||
import com.maddyhome.idea.vim.helper.StrictMode
|
import com.maddyhome.idea.vim.helper.StrictMode
|
||||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
|
|
||||||
import com.maddyhome.idea.vim.helper.exitSelectMode
|
import com.maddyhome.idea.vim.helper.exitSelectMode
|
||||||
import com.maddyhome.idea.vim.helper.exitVisualMode
|
import com.maddyhome.idea.vim.helper.exitVisualMode
|
||||||
import com.maddyhome.idea.vim.helper.forceBarCursor
|
import com.maddyhome.idea.vim.helper.forceBarCursor
|
||||||
@ -89,6 +90,8 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
|
|||||||
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
|
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
|
||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
import com.maddyhome.idea.vim.newapi.IjVimEditor
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
import com.maddyhome.idea.vim.state.mode.inSelectMode
|
||||||
import com.maddyhome.idea.vim.state.mode.mode
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||||
@ -128,11 +131,14 @@ internal object VimListenerManager {
|
|||||||
fun turnOn() {
|
fun turnOn() {
|
||||||
GlobalListeners.enable()
|
GlobalListeners.enable()
|
||||||
EditorListeners.addAll()
|
EditorListeners.addAll()
|
||||||
|
correctorRequester.request()
|
||||||
|
keymapCheckRequester.request()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun turnOff() {
|
fun turnOff() {
|
||||||
GlobalListeners.disable()
|
GlobalListeners.disable()
|
||||||
EditorListeners.removeAll()
|
EditorListeners.removeAll()
|
||||||
|
correctorRequester.request()
|
||||||
}
|
}
|
||||||
|
|
||||||
object GlobalListeners {
|
object GlobalListeners {
|
||||||
@ -267,6 +273,16 @@ internal object VimListenerManager {
|
|||||||
class VimFileEditorManagerListener : FileEditorManagerListener {
|
class VimFileEditorManagerListener : FileEditorManagerListener {
|
||||||
override fun selectionChanged(event: FileEditorManagerEvent) {
|
override fun selectionChanged(event: FileEditorManagerEvent) {
|
||||||
if (!VimPlugin.isEnabled()) return
|
if (!VimPlugin.isEnabled()) return
|
||||||
|
|
||||||
|
val newEditor = event.newEditor
|
||||||
|
if (newEditor is TextEditor) {
|
||||||
|
val editor = newEditor.editor
|
||||||
|
if (editor.isInsertMode) {
|
||||||
|
VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
|
||||||
|
KeyHandler.getInstance().reset(editor.vim)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
MotionGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
FileGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
SearchGroup.fileEditorManagerSelectionChangedCallback(event)
|
SearchGroup.fileEditorManagerSelectionChangedCallback(event)
|
||||||
@ -331,8 +347,6 @@ internal object VimListenerManager {
|
|||||||
|
|
||||||
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
|
||||||
}
|
}
|
||||||
|
|
||||||
VimStandalonePluginUpdateChecker.instance.pluginUsed()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editorReleased(event: EditorFactoryEvent) {
|
override fun editorReleased(event: EditorFactoryEvent) {
|
||||||
|
@ -18,6 +18,7 @@ import com.intellij.openapi.editor.CaretStateTransferableData
|
|||||||
import com.intellij.openapi.editor.RawText
|
import com.intellij.openapi.editor.RawText
|
||||||
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData
|
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData
|
||||||
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData
|
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData
|
||||||
|
import com.intellij.openapi.project.DumbService
|
||||||
import com.intellij.openapi.project.IndexNotReadyException
|
import com.intellij.openapi.project.IndexNotReadyException
|
||||||
import com.intellij.psi.PsiDocumentManager
|
import com.intellij.psi.PsiDocumentManager
|
||||||
import com.intellij.util.ui.EmptyClipboardOwner
|
import com.intellij.util.ui.EmptyClipboardOwner
|
||||||
@ -100,8 +101,7 @@ internal class IjClipboardManager : VimClipboardManager {
|
|||||||
|
|
||||||
// This thing enables alternative context resolve for dumb mode.
|
// This thing enables alternative context resolve for dumb mode.
|
||||||
// Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled
|
// Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled
|
||||||
// [VERSION UPDATE] 2023.2+ Enable alternative context back
|
DumbService.getInstance(project).withAlternativeResolveEnabled {
|
||||||
// DumbService.getInstance(project).withAlternativeResolveEnabled {
|
|
||||||
for (processor in CopyPastePostProcessor.EP_NAME.extensionList) {
|
for (processor in CopyPastePostProcessor.EP_NAME.extensionList) {
|
||||||
try {
|
try {
|
||||||
logger.debug { "Copy paste processor: ${processor.javaClass.name}" }
|
logger.debug { "Copy paste processor: ${processor.javaClass.name}" }
|
||||||
@ -116,7 +116,7 @@ internal class IjClipboardManager : VimClipboardManager {
|
|||||||
} catch (ignore: IndexNotReadyException) {
|
} catch (ignore: IndexNotReadyException) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// }
|
}
|
||||||
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
|
transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
|
||||||
|
|
||||||
// These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with
|
// These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with
|
||||||
|
@ -52,6 +52,7 @@ import java.awt.Point
|
|||||||
import java.awt.event.MouseEvent
|
import java.awt.event.MouseEvent
|
||||||
import javax.swing.Icon
|
import javax.swing.Icon
|
||||||
import javax.swing.SwingConstants
|
import javax.swing.SwingConstants
|
||||||
|
import javax.swing.Timer
|
||||||
|
|
||||||
@NonNls
|
@NonNls
|
||||||
internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
|
internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
|
||||||
@ -73,6 +74,14 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
|
|||||||
|
|
||||||
override fun createWidget(project: Project): StatusBarWidget {
|
override fun createWidget(project: Project): StatusBarWidget {
|
||||||
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(IjOptions.ideastatusicon) { updateAll() }
|
VimPlugin.getOptionGroup().addGlobalOptionChangeListener(IjOptions.ideastatusicon) { updateAll() }
|
||||||
|
|
||||||
|
// Double update the status bar icon with 5-second delay
|
||||||
|
// There is an issue VIM-3084 that must probably caused by some race between status bar icon initialization
|
||||||
|
// and .ideavimrc reading. I believe this is a simple fix for it.
|
||||||
|
val timer = Timer(5_000) { updateAll() }
|
||||||
|
timer.isRepeats = false
|
||||||
|
timer.start()
|
||||||
|
|
||||||
return VimStatusBar()
|
return VimStatusBar()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,10 +98,10 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
|
|||||||
statusBarWidgetsManager.updateWidget(this)
|
statusBarWidgetsManager.updateWidget(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
updateIcon()
|
Util.updateIcon()
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
object Util {
|
||||||
fun updateIcon() {
|
fun updateIcon() {
|
||||||
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
|
val projectManager = ProjectManager.getInstanceIfCreated() ?: return
|
||||||
for (project in projectManager.openProjects) {
|
for (project in projectManager.openProjects) {
|
||||||
|
@ -226,12 +226,12 @@
|
|||||||
|
|
||||||
<!-- Change -->
|
<!-- Change -->
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>
|
<!-- <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>-->
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>
|
<!-- <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>-->
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/>
|
||||||
@ -329,8 +329,8 @@
|
|||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="N" keys="«C-R»"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="NX" keys="U,«C-R»"/>
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="N"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="NX"/>
|
||||||
|
|
||||||
<!-- Keys -->
|
<!-- Keys -->
|
||||||
<vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>
|
<vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>
|
||||||
|
@ -12,6 +12,10 @@
|
|||||||
topic="com.intellij.ide.ui.LafManagerListener"/>
|
topic="com.intellij.ide.ui.LafManagerListener"/>
|
||||||
<listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter"
|
<listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter"
|
||||||
topic="com.intellij.ide.ui.LafManagerListener"/>
|
topic="com.intellij.ide.ui.LafManagerListener"/>
|
||||||
|
<listener class="com.maddyhome.idea.vim.handler.IdeaVimKeymapChangedListener"
|
||||||
|
topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
|
||||||
|
<listener class="com.maddyhome.idea.vim.handler.IdeaVimCorrectorKeymapChangedListener"
|
||||||
|
topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
|
||||||
</applicationListeners>
|
</applicationListeners>
|
||||||
<projectListeners>
|
<projectListeners>
|
||||||
<listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"
|
<listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"
|
||||||
|
@ -1,12 +1,4 @@
|
|||||||
<!--
|
<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
|
||||||
~ Copyright 2003-2023 The IdeaVim authors
|
|
||||||
~
|
|
||||||
~ Use of this source code is governed by an MIT-style
|
|
||||||
~ license that can be found in the LICENSE.txt file or at
|
|
||||||
~ https://opensource.org/licenses/MIT.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
|
|
||||||
<name>IdeaVim</name>
|
<name>IdeaVim</name>
|
||||||
<id>IdeaVIM</id>
|
<id>IdeaVIM</id>
|
||||||
<description><![CDATA[
|
<description><![CDATA[
|
||||||
@ -21,13 +13,13 @@
|
|||||||
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
|
<li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
|
||||||
</ul>
|
</ul>
|
||||||
]]></description>
|
]]></description>
|
||||||
<version>SNAPSHOT</version>
|
<version>chylex</version>
|
||||||
<vendor>JetBrains</vendor>
|
<vendor>JetBrains</vendor>
|
||||||
|
|
||||||
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
|
<!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
|
||||||
<!-- Check for [Version Update] tag in YouTrack as well -->
|
<!-- Check for [Version Update] tag in YouTrack as well -->
|
||||||
<!-- Also, please update the value in build.gradle.kts file-->
|
<!-- Also, please update the value in build.gradle.kts file-->
|
||||||
<idea-version since-build="231.8109.175"/>
|
<idea-version since-build="232"/>
|
||||||
|
|
||||||
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
|
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
|
||||||
<depends>com.intellij.modules.platform</depends>
|
<depends>com.intellij.modules.platform</depends>
|
||||||
@ -79,11 +71,12 @@
|
|||||||
core platform activities have IDs, so we can't use "before ID". We have to use "first" -->
|
core platform activities have IDs, so we can't use "before ID". We have to use "first" -->
|
||||||
<postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/>
|
<postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/>
|
||||||
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.EditorHandlersChainLogger"/>
|
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.EditorHandlersChainLogger"/>
|
||||||
|
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.KeymapChecker"/>
|
||||||
|
<postStartupActivity implementation="com.maddyhome.idea.vim.handler.CopilotKeymapCorrector"/>
|
||||||
|
|
||||||
<editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/>
|
<editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/>
|
||||||
|
|
||||||
<actionPromoter implementation="com.maddyhome.idea.vim.key.VimActionsPromoter" order="last"/>
|
<actionPromoter implementation="com.maddyhome.idea.vim.key.VimActionsPromoter" order="last"/>
|
||||||
<actionConfigurationCustomizer implementation="com.maddyhome.idea.vim.action.VimActionConfigurationCustomizer"/>
|
|
||||||
|
|
||||||
<spellchecker.bundledDictionaryProvider implementation="com.maddyhome.idea.vim.VimBundledDictionaryProvider"/>
|
<spellchecker.bundledDictionaryProvider implementation="com.maddyhome.idea.vim.VimBundledDictionaryProvider"/>
|
||||||
|
|
||||||
@ -125,8 +118,12 @@
|
|||||||
id="ideavim-enter-logger"
|
id="ideavim-enter-logger"
|
||||||
order="first"/>
|
order="first"/>
|
||||||
<editorActionHandler action="EditorStartNewLine"
|
<editorActionHandler action="EditorStartNewLine"
|
||||||
implementationClass="com.maddyhome.idea.vim.handler.ShiftEnterDetector"
|
implementationClass="com.maddyhome.idea.vim.handler.StartNewLineDetector"
|
||||||
id="ideavim-shift-enter-detector"
|
id="ideavim-start-new-line-detector"
|
||||||
|
order="first"/>
|
||||||
|
<editorActionHandler action="EditorStartNewLineBefore"
|
||||||
|
implementationClass="com.maddyhome.idea.vim.handler.StartNewLineBeforeCurrentDetector"
|
||||||
|
id="ideavim-start-new-line-before-current-detector"
|
||||||
order="first"/>
|
order="first"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
@ -157,5 +154,6 @@
|
|||||||
</group>
|
</group>
|
||||||
|
|
||||||
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
|
<action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
|
||||||
|
<action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
|
||||||
</actions>
|
</actions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
|
11
src/main/resources/icons/ideavim_outline.svg
Normal file
11
src/main/resources/icons/ideavim_outline.svg
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<!--
|
||||||
|
- Copyright 2003-2023 The IdeaVim authors
|
||||||
|
-
|
||||||
|
- Use of this source code is governed by an MIT-style
|
||||||
|
- license that can be found in the LICENSE.txt file or at
|
||||||
|
- https://opensource.org/licenses/MIT.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 40 40" fill-opacity="0" stroke="#6E6E6E" stroke-width="3">
|
||||||
|
<path d="M 28.019 4 L 15.988 24.119 L 15.988 4 L 4 4 L 4 36 L 17.953 36 L 36 4z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 446 B |
@ -771,7 +771,11 @@ abstract class VimTestCase {
|
|||||||
private fun KeyStroke.getChar(editor: Editor): CharType {
|
private fun KeyStroke.getChar(editor: Editor): CharType {
|
||||||
if (keyChar != KeyEvent.CHAR_UNDEFINED) return CharType.CharDetected(keyChar)
|
if (keyChar != KeyEvent.CHAR_UNDEFINED) return CharType.CharDetected(keyChar)
|
||||||
if (isOctopusEnabled(this, editor)) {
|
if (isOctopusEnabled(this, editor)) {
|
||||||
if (keyCode in setOf(KeyEvent.VK_ENTER)) return CharType.CharDetected(keyCode.toChar())
|
if (keyCode in setOf(KeyEvent.VK_ENTER)) {
|
||||||
|
if (modifiers == 0) {
|
||||||
|
return CharType.CharDetected(keyCode.toChar())
|
||||||
|
}
|
||||||
|
}
|
||||||
if (keyCode == KeyEvent.VK_ESCAPE) return CharType.EditorAction("EditorEscape")
|
if (keyCode == KeyEvent.VK_ESCAPE) return CharType.EditorAction("EditorEscape")
|
||||||
}
|
}
|
||||||
return CharType.UNDEFINED
|
return CharType.UNDEFINED
|
||||||
|
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action
|
||||||
|
|
||||||
|
import com.intellij.idea.TestFor
|
||||||
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class ActionsTest : VimTestCase() {
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3203"])
|
||||||
|
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
|
||||||
|
fun `split line action`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
fixture.performEditorAction("EditorSplitLine")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c
|
||||||
|
consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3159"])
|
||||||
|
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
|
||||||
|
fun `start new line before`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
fixture.performEditorAction("EditorStartNewLineBefore")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
$c
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3159"])
|
||||||
|
@TestWithoutNeovim(SkipNeovimReason.NOT_VIM_TESTING)
|
||||||
|
fun `start new line`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
fixture.performEditorAction("EditorStartNewLine")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
$c
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
101
src/test/java/org/jetbrains/plugins/ideavim/action/EscapeTest.kt
Normal file
101
src/test/java/org/jetbrains/plugins/ideavim/action/EscapeTest.kt
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action
|
||||||
|
|
||||||
|
import com.intellij.idea.TestFor
|
||||||
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class EscapeTest : VimTestCase() {
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3190"])
|
||||||
|
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||||
|
fun `mapping to control esc`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
typeText(commandToKeys("nmap <C-Esc> k"))
|
||||||
|
typeText("<C-Esc>")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
$c
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3190"])
|
||||||
|
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||||
|
fun `mapping to alt esc`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
typeText(commandToKeys("nmap <A-Esc> k"))
|
||||||
|
typeText("<A-Esc>")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
$c
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3190"])
|
||||||
|
@TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
|
||||||
|
fun `mapping to shift esc`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
typeText(commandToKeys("nmap <S-Esc> k"))
|
||||||
|
typeText("<S-Esc>")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
$c
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -11,10 +11,8 @@ import com.maddyhome.idea.vim.api.injector
|
|||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
import org.jetbrains.plugins.ideavim.SkipNeovimReason
|
||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
// [VERSION UPDATE] 232+ enable tests
|
|
||||||
@Suppress("unused")
|
@Suppress("unused")
|
||||||
class ReformatCodeTest : VimTestCase() {
|
class ReformatCodeTest : VimTestCase() {
|
||||||
@Test
|
@Test
|
||||||
@ -23,7 +21,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testEmpty() {
|
fun testEmpty() {
|
||||||
configureByJavaText("<caret>")
|
configureByJavaText("<caret>")
|
||||||
typeText(injector.parser.parseKeys("gqq"))
|
typeText(injector.parser.parseKeys("gqq"))
|
||||||
@ -32,7 +29,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testWithCount() {
|
fun testWithCount() {
|
||||||
configureByJavaText("class C {\n\tint a;\n\tint <caret>b;\n\tint c;\n\tint d;\n}\n")
|
configureByJavaText("class C {\n\tint a;\n\tint <caret>b;\n\tint c;\n\tint d;\n}\n")
|
||||||
typeText(injector.parser.parseKeys("2gqq"))
|
typeText(injector.parser.parseKeys("2gqq"))
|
||||||
@ -41,7 +37,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testWithUpMotion() {
|
fun testWithUpMotion() {
|
||||||
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint b;\n" + "\tint <caret>c;\n" + "\tint d;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint b;\n" + "\tint <caret>c;\n" + "\tint d;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("gqk"))
|
typeText(injector.parser.parseKeys("gqk"))
|
||||||
@ -50,7 +45,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testWithRightMotion() {
|
fun testWithRightMotion() {
|
||||||
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("gql"))
|
typeText(injector.parser.parseKeys("gql"))
|
||||||
@ -59,7 +53,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testWithTextObject() {
|
fun testWithTextObject() {
|
||||||
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("gqi{"))
|
typeText(injector.parser.parseKeys("gqi{"))
|
||||||
@ -75,7 +68,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testWithCountsAndDownMotion() {
|
fun testWithCountsAndDownMotion() {
|
||||||
configureByJavaText("class C {\n" + "\tint <caret>a;\n" + "\tint b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint <caret>a;\n" + "\tint b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("2gqj"))
|
typeText(injector.parser.parseKeys("2gqj"))
|
||||||
@ -84,7 +76,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testVisual() {
|
fun testVisual() {
|
||||||
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("v" + "l" + "gq"))
|
typeText(injector.parser.parseKeys("v" + "l" + "gq"))
|
||||||
@ -93,7 +84,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testLinewiseVisual() {
|
fun testLinewiseVisual() {
|
||||||
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("V" + "l" + "gq"))
|
typeText(injector.parser.parseKeys("V" + "l" + "gq"))
|
||||||
@ -102,7 +92,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testVisualMultiline() {
|
fun testVisualMultiline() {
|
||||||
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("v" + "j" + "gq"))
|
typeText(injector.parser.parseKeys("v" + "j" + "gq"))
|
||||||
@ -111,7 +100,6 @@ class ReformatCodeTest : VimTestCase() {
|
|||||||
|
|
||||||
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
@TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
|
||||||
@Test
|
@Test
|
||||||
@Disabled
|
|
||||||
fun testVisualBlock() {
|
fun testVisualBlock() {
|
||||||
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
|
configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
|
||||||
typeText(injector.parser.parseKeys("<C-V>" + "j" + "gq"))
|
typeText(injector.parser.parseKeys("<C-V>" + "j" + "gq"))
|
||||||
|
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.plugins.ideavim.action.change.delete
|
package org.jetbrains.plugins.ideavim.action.change.delete
|
||||||
|
|
||||||
|
import com.intellij.notification.ActionCenter
|
||||||
import com.intellij.notification.EventLog
|
import com.intellij.notification.EventLog
|
||||||
import com.intellij.notification.Notification
|
import com.intellij.notification.Notification
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
@ -20,14 +21,12 @@ import org.jetbrains.plugins.ideavim.VimTestCase
|
|||||||
import org.jetbrains.plugins.ideavim.impl.OptionTest
|
import org.jetbrains.plugins.ideavim.impl.OptionTest
|
||||||
import org.jetbrains.plugins.ideavim.impl.TraceOptions
|
import org.jetbrains.plugins.ideavim.impl.TraceOptions
|
||||||
import org.jetbrains.plugins.ideavim.impl.VimOption
|
import org.jetbrains.plugins.ideavim.impl.VimOption
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Alex Plate
|
* @author Alex Plate
|
||||||
*/
|
*/
|
||||||
@TraceOptions(TestIjOptionConstants.ideajoin)
|
@TraceOptions(TestIjOptionConstants.ideajoin)
|
||||||
class JoinNotificationTest : VimTestCase() {
|
class JoinNotificationTest : VimTestCase() {
|
||||||
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
|
|
||||||
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
|
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
|
||||||
fun `test notification shown for no ideajoin`() {
|
fun `test notification shown for no ideajoin`() {
|
||||||
val before = "I found${c} it\n in a legendary land"
|
val before = "I found${c} it\n in a legendary land"
|
||||||
@ -45,7 +44,6 @@ class JoinNotificationTest : VimTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
|
|
||||||
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["true"]))
|
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["true"]))
|
||||||
fun `test notification not shown for ideajoin`() {
|
fun `test notification not shown for ideajoin`() {
|
||||||
val before = "I found${c} it\n in a legendary land"
|
val before = "I found${c} it\n in a legendary land"
|
||||||
@ -58,8 +56,7 @@ class JoinNotificationTest : VimTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifications(): MutableList<Notification> {
|
private fun notifications(): MutableList<Notification> {
|
||||||
TODO()
|
return ActionCenter.getNotifications(fixture.project)
|
||||||
// return ActionCenter.getNotifications(fixture.project)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
|
@OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
|
||||||
|
@ -60,7 +60,7 @@ class InsertEnterActionTest : VimTestCase() {
|
|||||||
} else if (repetitionInfo.currentRepetition == 3) {
|
} else if (repetitionInfo.currentRepetition == 3) {
|
||||||
ExtensionTestUtil.maskExtensions(
|
ExtensionTestUtil.maskExtensions(
|
||||||
ExtensionPointName("com.intellij.editorActionHandler"),
|
ExtensionPointName("com.intellij.editorActionHandler"),
|
||||||
listOf(singleBean, mainBean),
|
listOf(forEachBean, mainBean),
|
||||||
fixture.testRootDisposable
|
fixture.testRootDisposable
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -12,14 +12,38 @@ import com.maddyhome.idea.vim.state.mode.Mode
|
|||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class InsertSingleCommandActionTest : VimTestCase() {
|
class InsertSingleCommandActionTest : SingleCommandActionTest() {
|
||||||
|
override val command: String = "i"
|
||||||
|
override val mode: Mode = Mode.INSERT
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplaceSingleCommandActionTest : SingleCommandActionTest() {
|
||||||
|
override val command: String = "R"
|
||||||
|
override val mode: Mode = Mode.REPLACE
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class SingleCommandActionTest : VimTestCase() {
|
||||||
|
|
||||||
|
abstract val command: String
|
||||||
|
abstract val mode: Mode
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one operation`() {
|
||||||
|
doTest(
|
||||||
|
listOf(command, "<C-O>", "l"),
|
||||||
|
"I found ${c}it in a legendary land",
|
||||||
|
"I found i${c}t in a legendary land",
|
||||||
|
mode,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test enter visual`() {
|
fun `test enter visual`() {
|
||||||
doTest(
|
doTest(
|
||||||
listOf("i", "<C-O>", "vlll", "<Esc>"),
|
listOf(command, "<C-O>", "vlll", "<Esc>"),
|
||||||
"I found ${c}it in a legendary land",
|
"I found ${c}it in a legendary land",
|
||||||
"I found it ${c}in a legendary land",
|
"I found it ${c}in a legendary land",
|
||||||
Mode.INSERT,
|
mode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
package org.jetbrains.plugins.ideavim.action.copy
|
package org.jetbrains.plugins.ideavim.action.copy
|
||||||
|
|
||||||
|
import com.intellij.notification.ActionCenter
|
||||||
import com.intellij.notification.EventLog
|
import com.intellij.notification.EventLog
|
||||||
import com.intellij.notification.Notification
|
import com.intellij.notification.Notification
|
||||||
import com.maddyhome.idea.vim.VimPlugin
|
import com.maddyhome.idea.vim.VimPlugin
|
||||||
@ -22,11 +23,9 @@ import org.jetbrains.plugins.ideavim.impl.OptionTest
|
|||||||
import org.jetbrains.plugins.ideavim.impl.TraceOptions
|
import org.jetbrains.plugins.ideavim.impl.TraceOptions
|
||||||
import org.jetbrains.plugins.ideavim.impl.VimOption
|
import org.jetbrains.plugins.ideavim.impl.VimOption
|
||||||
import org.jetbrains.plugins.ideavim.rangeOf
|
import org.jetbrains.plugins.ideavim.rangeOf
|
||||||
import org.junit.jupiter.api.Disabled
|
|
||||||
|
|
||||||
@TraceOptions(TestOptionConstants.clipboard)
|
@TraceOptions(TestOptionConstants.clipboard)
|
||||||
class IdeaPutNotificationsTest : VimTestCase() {
|
class IdeaPutNotificationsTest : VimTestCase() {
|
||||||
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
|
|
||||||
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))
|
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))
|
||||||
fun `test notification exists if no ideaput`() {
|
fun `test notification exists if no ideaput`() {
|
||||||
val before = "${c}I found it in a legendary land"
|
val before = "${c}I found it in a legendary land"
|
||||||
@ -47,7 +46,6 @@ class IdeaPutNotificationsTest : VimTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
|
|
||||||
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [OptionConstants.clipboard_ideaput]))
|
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [OptionConstants.clipboard_ideaput]))
|
||||||
fun `test no notification on ideaput`() {
|
fun `test no notification on ideaput`() {
|
||||||
val before = "${c}I found it in a legendary land"
|
val before = "${c}I found it in a legendary land"
|
||||||
@ -63,8 +61,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun notifications(): MutableList<Notification> {
|
private fun notifications(): MutableList<Notification> {
|
||||||
TODO()
|
return ActionCenter.getNotifications(fixture.project)
|
||||||
// return ActionCenter.getNotifications(fixture.project)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))
|
@OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))
|
||||||
|
@ -0,0 +1,122 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action.motion.search
|
||||||
|
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.junit.jupiter.api.Disabled
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class SearchEntryFwdActionTest : VimTestCase() {
|
||||||
|
@Test
|
||||||
|
fun `search in visual mode`() {
|
||||||
|
doTest(
|
||||||
|
"v/id<CR>",
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${s}consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras ${c}i${se}d tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.VISUAL(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `search in one time visual mode`() {
|
||||||
|
doTest(
|
||||||
|
"i<C-O>v/id<CR>",
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras ${c}id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.INSERT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `search in one time visual mode from replace`() {
|
||||||
|
doTest(
|
||||||
|
"R<C-O>v/id<CR>",
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras ${c}id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.REPLACE,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `search in op pending`() {
|
||||||
|
doTest(
|
||||||
|
"d/id<CR>",
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.NORMAL(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `search in op pending from one time mode`() {
|
||||||
|
doTest(
|
||||||
|
"i<C-O>d/id<CR>",
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.INSERT,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Disabled("Ctrl-o doesn't work yet in select mode")
|
||||||
|
@Test
|
||||||
|
fun `search in one time from select mode`() {
|
||||||
|
doTest(
|
||||||
|
"gh<C-O>/id<CR>",
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|${c}consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
"""Lorem ipsum dolor sit amet,
|
||||||
|
|consectetur adipiscing elit
|
||||||
|
|Sed in orci mauris.
|
||||||
|
|Cras ${c}id tellus in ex imperdiet egestas.
|
||||||
|
""".trimMargin(),
|
||||||
|
Mode.SELECT(SelectionType.CHARACTER_WISE),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2003-2023 The IdeaVim authors
|
||||||
|
*
|
||||||
|
* Use of this source code is governed by an MIT-style
|
||||||
|
* license that can be found in the LICENSE.txt file or at
|
||||||
|
* https://opensource.org/licenses/MIT.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.jetbrains.plugins.ideavim.action.motion.updown
|
||||||
|
|
||||||
|
import com.intellij.idea.TestFor
|
||||||
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
|
class EnterNormalActionTest : VimTestCase() {
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3190"])
|
||||||
|
fun `mapping to control enter`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
typeText(commandToKeys("nmap <C-Enter> k"))
|
||||||
|
typeText("<C-Enter>")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
$c
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3190"])
|
||||||
|
fun `mapping to alt enter`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
typeText(commandToKeys("nmap <A-Enter> k"))
|
||||||
|
typeText("<A-Enter>")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
$c
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3190"])
|
||||||
|
fun `mapping to shift enter`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
|
||||||
|
Lorem ipsum dolor sit amet,$c consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
|
||||||
|
typeText(commandToKeys("nmap <S-Enter> k"))
|
||||||
|
typeText("<S-Enter>")
|
||||||
|
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
Lorem Ipsum
|
||||||
|
$c
|
||||||
|
Lorem ipsum dolor sit amet, consectetur adipiscing elit
|
||||||
|
Sed in orci mauris.
|
||||||
|
Cras id tellus in ex imperdiet egestas.
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -8,15 +8,105 @@
|
|||||||
|
|
||||||
package org.jetbrains.plugins.ideavim.ex.implementation.commands
|
package org.jetbrains.plugins.ideavim.ex.implementation.commands
|
||||||
|
|
||||||
|
import com.intellij.idea.TestFor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import org.jetbrains.plugins.ideavim.VimTestCase
|
import org.jetbrains.plugins.ideavim.VimTestCase
|
||||||
import org.junit.jupiter.api.Test
|
import org.junit.jupiter.api.Test
|
||||||
|
|
||||||
class MarksCommandTest : VimTestCase() {
|
class MarksCommandTest : VimTestCase() {
|
||||||
|
|
||||||
// https://youtrack.jetbrains.com/issue/VIM-2223
|
|
||||||
@Test
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3176"])
|
||||||
|
fun `test gv after pasting to the same line`() {
|
||||||
|
configureByText(
|
||||||
|
"""${c}I found it in a legendary land
|
||||||
|
|all rocks and lavender and tufted grass,
|
||||||
|
|where it was settled on some sodden sand
|
||||||
|
|hard by the torrent of a mountain pass.
|
||||||
|
""".trimMargin(),
|
||||||
|
)
|
||||||
|
typeText(injector.parser.parseKeys("V3j" + "y" + "P" + "gv"))
|
||||||
|
assertState(
|
||||||
|
"""I found it in a legendary land
|
||||||
|
|all rocks and lavender and tufted grass,
|
||||||
|
|where it was settled on some sodden sand
|
||||||
|
|hard by the torrent of a mountain pass.
|
||||||
|
|${s}I found it in a legendary land
|
||||||
|
|all rocks and lavender and tufted grass,
|
||||||
|
|where it was settled on some sodden sand
|
||||||
|
|${c}hard by the torrent of a mountain pass.${se}
|
||||||
|
""".trimMargin(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3176"])
|
||||||
|
fun `test gv after pasting to the same line reversed selection`() {
|
||||||
|
configureByText(
|
||||||
|
"""I found it in a legendary land
|
||||||
|
|all rocks and lavender and tufted grass,
|
||||||
|
|where it was settled on some sodden sand
|
||||||
|
|${c}hard by the torrent of a mountain pass.
|
||||||
|
""".trimMargin(),
|
||||||
|
)
|
||||||
|
typeText(injector.parser.parseKeys("V3k" + "y" + "P" + "gv"))
|
||||||
|
assertState(
|
||||||
|
"""I found it in a legendary land
|
||||||
|
|all rocks and lavender and tufted grass,
|
||||||
|
|where it was settled on some sodden sand
|
||||||
|
|hard by the torrent of a mountain pass.
|
||||||
|
|${s}${c}I found it in a legendary land
|
||||||
|
|all rocks and lavender and tufted grass,
|
||||||
|
|where it was settled on some sodden sand
|
||||||
|
|hard by the torrent of a mountain pass.${se}
|
||||||
|
""".trimMargin(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3176"])
|
||||||
|
fun `test gv after pasting inside selection expanded selection`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
${c}line1
|
||||||
|
line2
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
typeText(injector.parser.parseKeys("Vj" + "y" + "j" + "P" + "gv"))
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
${s}line1
|
||||||
|
line1
|
||||||
|
line2
|
||||||
|
line2${se}
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-3176"])
|
||||||
|
fun `test gv after pasting below selection not changing selection`() {
|
||||||
|
configureByText(
|
||||||
|
"""
|
||||||
|
${c}line1
|
||||||
|
line2
|
||||||
|
not selected
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
typeText(injector.parser.parseKeys("Vj" + "y" + "j" + "p" + "gv"))
|
||||||
|
assertState(
|
||||||
|
"""
|
||||||
|
${s}line1
|
||||||
|
line2
|
||||||
|
${se}line1
|
||||||
|
line2
|
||||||
|
not selected
|
||||||
|
""".trimIndent(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-2223"])
|
||||||
fun `test gv after replacing a line`() {
|
fun `test gv after replacing a line`() {
|
||||||
configureByText(
|
configureByText(
|
||||||
"""I found it in a legendary land
|
"""I found it in a legendary land
|
||||||
@ -35,8 +125,8 @@ class MarksCommandTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://youtrack.jetbrains.com/issue/VIM-1684
|
|
||||||
@Test
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-1684"])
|
||||||
fun `test reselecting different text length`() {
|
fun `test reselecting different text length`() {
|
||||||
configureByText(
|
configureByText(
|
||||||
"""
|
"""
|
||||||
@ -53,8 +143,8 @@ class MarksCommandTest : VimTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://youtrack.jetbrains.com/issue/VIM-2491
|
|
||||||
@Test
|
@Test
|
||||||
|
@TestFor(issues = ["VIM-2491"])
|
||||||
fun `test mapping with gv`() {
|
fun `test mapping with gv`() {
|
||||||
configureByText("Oh, hi ${c}Andy Tom John")
|
configureByText("Oh, hi ${c}Andy Tom John")
|
||||||
typeText(commandToKeys("xnoremap p pgvy"))
|
typeText(commandToKeys("xnoremap p pgvy"))
|
||||||
|
@ -11,10 +11,10 @@ package org.jetbrains.plugins.ideavim.propertybased
|
|||||||
import com.intellij.ide.IdeEventQueue
|
import com.intellij.ide.IdeEventQueue
|
||||||
import com.intellij.openapi.editor.Editor
|
import com.intellij.openapi.editor.Editor
|
||||||
import com.intellij.testFramework.PlatformTestUtil
|
import com.intellij.testFramework.PlatformTestUtil
|
||||||
|
import com.maddyhome.idea.vim.action.change.LazyVimCommand
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
import com.maddyhome.idea.vim.key.CommandNode
|
import com.maddyhome.idea.vim.key.CommandNode
|
||||||
import com.maddyhome.idea.vim.newapi.ij
|
|
||||||
import com.maddyhome.idea.vim.newapi.vim
|
import com.maddyhome.idea.vim.newapi.vim
|
||||||
import org.jetbrains.jetCheck.Generator
|
import org.jetbrains.jetCheck.Generator
|
||||||
import org.jetbrains.jetCheck.ImperativeCommand
|
import org.jetbrains.jetCheck.ImperativeCommand
|
||||||
@ -99,7 +99,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
|
|||||||
val usedKey = env.generateValue(keyGenerator, null)
|
val usedKey = env.generateValue(keyGenerator, null)
|
||||||
val node = currentNode[usedKey]
|
val node = currentNode[usedKey]
|
||||||
|
|
||||||
env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${node.actionHolder.ij.actionId}" else ""}")
|
env.logMessage("Use command: ${injector.parser.toKeyNotation(usedKey)}. ${if (node is CommandNode) "Action: ${(node.actionHolder as LazyVimCommand).actionId}" else ""}")
|
||||||
VimTestCase.typeText(listOf(usedKey), editor, editor.project)
|
VimTestCase.typeText(listOf(usedKey), editor, editor.project)
|
||||||
|
|
||||||
IdeEventQueue.getInstance().flushQueue()
|
IdeEventQueue.getInstance().flushQueue()
|
||||||
|
@ -15,6 +15,7 @@ plugins {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val kotlinVersion: String by project
|
val kotlinVersion: String by project
|
||||||
|
val kotlinxSerializationVersion: String by project
|
||||||
|
|
||||||
// group 'org.jetbrains.ideavim'
|
// group 'org.jetbrains.ideavim'
|
||||||
// version 'SNAPSHOT'
|
// version 'SNAPSHOT'
|
||||||
@ -35,18 +36,18 @@ afterEvaluate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
|
testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
|
||||||
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
|
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.1")
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
|
// https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
|
||||||
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
|
||||||
|
|
||||||
compileOnly("org.jetbrains:annotations:24.0.1")
|
compileOnly("org.jetbrains:annotations:24.1.0")
|
||||||
|
|
||||||
ksp(project(":annotation-processors"))
|
ksp(project(":annotation-processors"))
|
||||||
implementation(project(":annotation-processors"))
|
implementation(project(":annotation-processors"))
|
||||||
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
|
compileOnly("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks {
|
tasks {
|
||||||
|
@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.key.Node
|
|||||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
||||||
|
import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
|
||||||
import com.maddyhome.idea.vim.state.mode.returnTo
|
import com.maddyhome.idea.vim.state.mode.returnTo
|
||||||
import java.awt.event.InputEvent
|
import java.awt.event.InputEvent
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
@ -498,7 +499,7 @@ public class KeyHandler {
|
|||||||
val text = injector.processGroup.endSearchCommand()
|
val text = injector.processGroup.endSearchCommand()
|
||||||
commandBuilder.popCommandPart() // Pop ProcessExEntryAction
|
commandBuilder.popCommandPart() // Pop ProcessExEntryAction
|
||||||
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
|
commandBuilder.completeCommandPart(Argument(text)) // Set search text on SearchEntry(Fwd|Rev)Action
|
||||||
editorState.mode = Mode.NORMAL()
|
editorState.mode = editorState.mode.returnTo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -545,7 +546,9 @@ public class KeyHandler {
|
|||||||
// state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument.
|
// state hits READY. Start the ex input field, push CMD_LINE mode and wait for the argument.
|
||||||
injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key)
|
injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key)
|
||||||
commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
commandBuilder.commandState = CurrentCommandState.NEW_COMMAND
|
||||||
editorState.mode = Mode.CMD_LINE
|
val currentMode = editorState.mode
|
||||||
|
check(currentMode is ReturnableFromCmd) { "Cannot enable command line mode $currentMode" }
|
||||||
|
editorState.mode = Mode.CMD_LINE(currentMode)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
|
@ -17,6 +17,12 @@ import kotlinx.serialization.json.Json
|
|||||||
import kotlinx.serialization.json.decodeFromStream
|
import kotlinx.serialization.json.decodeFromStream
|
||||||
import java.io.InputStream
|
import java.io.InputStream
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An interface defining the contract for providers responsible for reading and parsing JSON files.
|
||||||
|
* These files contain a list of command beans that are intended to be lazily loaded during runtime.
|
||||||
|
* The primary functionality of this interface is to transform the JSON data into a collection of
|
||||||
|
* {@code LazyVimCommand} instances.
|
||||||
|
*/
|
||||||
public interface CommandProvider {
|
public interface CommandProvider {
|
||||||
public val commandListFileName: String
|
public val commandListFileName: String
|
||||||
|
|
||||||
@ -29,12 +35,7 @@ public interface CommandProvider {
|
|||||||
.map {
|
.map {
|
||||||
val keys = it.value.map { bean -> injector.parser.parseKeys(bean.keys) }.toSet()
|
val keys = it.value.map { bean -> injector.parser.parseKeys(bean.keys) }.toSet()
|
||||||
val modes = it.value.first().modes.map { mode -> MappingMode.parseModeChar(mode) }.toSet()
|
val modes = it.value.first().modes.map { mode -> MappingMode.parseModeChar(mode) }.toSet()
|
||||||
LazyVimCommand(
|
LazyVimCommand(keys, modes, it.key, classLoader)
|
||||||
keys,
|
|
||||||
modes,
|
|
||||||
it.key,
|
|
||||||
classLoader
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
|
|||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
|
import com.maddyhome.idea.vim.handler.VimActionHandler
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
|
@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||||
public class RedoAction : VimActionHandler.SingleExecution() {
|
public class RedoAction : VimActionHandler.SingleExecution() {
|
||||||
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
|
|||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import javax.swing.KeyStroke
|
import javax.swing.KeyStroke
|
||||||
|
|
||||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
|
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
|
||||||
public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
|
public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
|
||||||
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
|
||||||
injector.parser.parseKeys("u"),
|
injector.parser.parseKeys("u"),
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
package com.maddyhome.idea.vim.action.change.change
|
package com.maddyhome.idea.vim.action.change.change
|
||||||
|
|
||||||
import com.intellij.vim.annotations.CommandOrMotion
|
import com.intellij.vim.annotations.CommandOrMotion
|
||||||
import com.intellij.vim.annotations.Mode
|
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
@ -25,7 +24,7 @@ import java.util.*
|
|||||||
/**
|
/**
|
||||||
* @author vlan
|
* @author vlan
|
||||||
*/
|
*/
|
||||||
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL])
|
@CommandOrMotion(keys = [], modes = [])
|
||||||
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||||
override val type: Command.Type = Command.Type.CHANGE
|
override val type: Command.Type = Command.Type.CHANGE
|
||||||
|
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
package com.maddyhome.idea.vim.action.change.change
|
package com.maddyhome.idea.vim.action.change.change
|
||||||
|
|
||||||
import com.intellij.vim.annotations.CommandOrMotion
|
import com.intellij.vim.annotations.CommandOrMotion
|
||||||
import com.intellij.vim.annotations.Mode
|
|
||||||
import com.maddyhome.idea.vim.api.ExecutionContext
|
import com.maddyhome.idea.vim.api.ExecutionContext
|
||||||
import com.maddyhome.idea.vim.api.VimCaret
|
import com.maddyhome.idea.vim.api.VimCaret
|
||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
@ -25,7 +24,7 @@ import java.util.*
|
|||||||
/**
|
/**
|
||||||
* @author vlan
|
* @author vlan
|
||||||
*/
|
*/
|
||||||
@CommandOrMotion(keys = ["U"], modes = [Mode.VISUAL])
|
@CommandOrMotion(keys = [], modes = [])
|
||||||
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
|
||||||
override val type: Command.Type = Command.Type.CHANGE
|
override val type: Command.Type = Command.Type.CHANGE
|
||||||
|
|
||||||
|
@ -30,6 +30,7 @@ public class FilterVisualLinesAction : VimActionHandler.SingleExecution() {
|
|||||||
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
|
override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
|
||||||
|
|
||||||
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
override fun execute(editor: VimEditor, context: ExecutionContext, cmd: Command, operatorArguments: OperatorArguments): Boolean {
|
||||||
|
injector.markService.setVisualSelectionMarks(editor)
|
||||||
injector.processGroup.startFilterCommand(editor, context, cmd)
|
injector.processGroup.startFilterCommand(editor, context, cmd)
|
||||||
editor.exitVisualMode()
|
editor.exitVisualMode()
|
||||||
return true
|
return true
|
||||||
|
@ -144,7 +144,7 @@ public interface VimChangeGroup {
|
|||||||
operatorArguments: OperatorArguments,
|
operatorArguments: OperatorArguments,
|
||||||
)
|
)
|
||||||
|
|
||||||
public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret
|
public fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret
|
||||||
|
|
||||||
public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret
|
public fun insertText(editor: VimEditor, caret: VimCaret, str: String): VimCaret
|
||||||
|
|
||||||
|
@ -35,8 +35,9 @@ import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_START
|
|||||||
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
|
import com.maddyhome.idea.vim.register.RegisterConstants.LAST_INSERTED_TEXT_REGISTER
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.ReturnTo
|
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.toReturnTo
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -202,7 +203,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
* @param caret The caret to start insertion in
|
* @param caret The caret to start insertion in
|
||||||
* @param str The text to insert
|
* @param str The text to insert
|
||||||
*/
|
*/
|
||||||
override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: String): VimCaret {
|
override fun insertText(editor: VimEditor, caret: VimCaret, offset: Int, str: CharSequence): VimCaret {
|
||||||
(editor as MutableVimEditor).insertText(Offset(offset), str)
|
(editor as MutableVimEditor).insertText(Offset(offset), str)
|
||||||
val newCaret = caret.moveToInlayAwareOffset(offset + str.length)
|
val newCaret = caret.moveToInlayAwareOffset(offset + str.length)
|
||||||
|
|
||||||
@ -614,7 +615,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
|
|||||||
* @param editor The editor to put into NORMAL mode for one command
|
* @param editor The editor to put into NORMAL mode for one command
|
||||||
*/
|
*/
|
||||||
override fun processSingleCommand(editor: VimEditor) {
|
override fun processSingleCommand(editor: VimEditor) {
|
||||||
getInstance(editor).mode = Mode.NORMAL(returnTo = ReturnTo.INSERT)
|
getInstance(editor).mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo)
|
||||||
clearStrokes(editor)
|
clearStrokes(editor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -166,7 +166,10 @@ public abstract class VimKeyGroupBase : VimKeyGroup {
|
|||||||
private fun registerKeyMapping(fromKeys: List<KeyStroke>, owner: MappingOwner) {
|
private fun registerKeyMapping(fromKeys: List<KeyStroke>, owner: MappingOwner) {
|
||||||
val oldSize = requiredShortcutKeys.size
|
val oldSize = requiredShortcutKeys.size
|
||||||
for (key in fromKeys) {
|
for (key in fromKeys) {
|
||||||
if (key.keyChar == KeyEvent.CHAR_UNDEFINED && key.keyCode != KeyEvent.VK_ESCAPE && key.keyCode != KeyEvent.VK_ENTER) {
|
if (key.keyChar == KeyEvent.CHAR_UNDEFINED &&
|
||||||
|
!(key.keyCode == KeyEvent.VK_ESCAPE && key.modifiers == 0) &&
|
||||||
|
!(key.keyCode == KeyEvent.VK_ENTER && key.modifiers == 0)
|
||||||
|
) {
|
||||||
requiredShortcutKeys.add(RequiredShortcut(key, owner))
|
requiredShortcutKeys.add(RequiredShortcut(key, owner))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -334,13 +334,13 @@ public abstract class VimMarkServiceBase : VimMarkService {
|
|||||||
|
|
||||||
val startPosition = selectionInfo.start
|
val startPosition = selectionInfo.start
|
||||||
var newStartPosition = selectionInfo.start
|
var newStartPosition = selectionInfo.start
|
||||||
if (startPosition != null && insStart.line < startPosition.line) {
|
if (startPosition != null && insStart.line <= startPosition.line) {
|
||||||
newStartPosition = BufferPosition(startPosition.line + lines, startPosition.column, startPosition.leansForward)
|
newStartPosition = BufferPosition(startPosition.line + lines, startPosition.column, startPosition.leansForward)
|
||||||
}
|
}
|
||||||
|
|
||||||
val endPosition = selectionInfo.end
|
val endPosition = selectionInfo.end
|
||||||
var newEndPosition = endPosition
|
var newEndPosition = endPosition
|
||||||
if (endPosition != null && insStart.line < endPosition.line) {
|
if (endPosition != null && insStart.line <= endPosition.line) {
|
||||||
newEndPosition = BufferPosition(endPosition.line + lines, endPosition.column, endPosition.leansForward)
|
newEndPosition = BufferPosition(endPosition.line + lines, endPosition.column, endPosition.leansForward)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,11 +13,11 @@ import javax.swing.KeyStroke
|
|||||||
public interface VimProcessGroup {
|
public interface VimProcessGroup {
|
||||||
public val lastCommand: String?
|
public val lastCommand: String?
|
||||||
|
|
||||||
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext?, count: Int, leader: Char)
|
public fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char)
|
||||||
public fun endSearchCommand(): String
|
public fun endSearchCommand(): String
|
||||||
public fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean
|
public fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean
|
||||||
public fun startFilterCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command)
|
public fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
|
||||||
public fun startExCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command)
|
public fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command)
|
||||||
public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean
|
public fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean
|
||||||
public fun cancelExEntry(editor: VimEditor, resetCaret: Boolean)
|
public fun cancelExEntry(editor: VimEditor, resetCaret: Boolean)
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
|
|||||||
override val lastCommand: String
|
override val lastCommand: String
|
||||||
get() = TODO("Not yet implemented")
|
get() = TODO("Not yet implemented")
|
||||||
|
|
||||||
override fun startSearchCommand(editor: VimEditor, context: ExecutionContext?, count: Int, leader: Char) {
|
override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -35,11 +35,11 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
|
|||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startFilterCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) {
|
public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startExCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) {
|
public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
|
||||||
TODO("Not yet implemented")
|
TODO("Not yet implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,9 +11,9 @@ package com.maddyhome.idea.vim.helper
|
|||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.api.options
|
import com.maddyhome.idea.vim.api.options
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
|
||||||
import com.maddyhome.idea.vim.common.TextRange
|
import com.maddyhome.idea.vim.common.TextRange
|
||||||
import com.maddyhome.idea.vim.options.OptionConstants
|
import com.maddyhome.idea.vim.options.OptionConstants
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
|
import com.maddyhome.idea.vim.state.mode.SelectionType
|
||||||
import com.maddyhome.idea.vim.state.mode.isSingleModeActive
|
import com.maddyhome.idea.vim.state.mode.isSingleModeActive
|
||||||
@ -41,7 +41,7 @@ public val VimEditor.isEndAllowed: Boolean
|
|||||||
public fun VimEditor.isEndAllowed(mode: Mode): Boolean {
|
public fun VimEditor.isEndAllowed(mode: Mode): Boolean {
|
||||||
return when (mode) {
|
return when (mode) {
|
||||||
is Mode.INSERT, is Mode.VISUAL, is Mode.SELECT -> true
|
is Mode.INSERT, is Mode.VISUAL, is Mode.SELECT -> true
|
||||||
is Mode.NORMAL, Mode.CMD_LINE, Mode.REPLACE, is Mode.OP_PENDING -> {
|
is Mode.NORMAL, is Mode.CMD_LINE, Mode.REPLACE, is Mode.OP_PENDING -> {
|
||||||
// One day we'll use a proper insert_normal mode
|
// One day we'll use a proper insert_normal mode
|
||||||
if (mode.isSingleModeActive) true else usesVirtualSpace
|
if (mode.isSingleModeActive) true else usesVirtualSpace
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,5 @@
|
|||||||
|
|
||||||
package com.maddyhome.idea.vim.helper
|
package com.maddyhome.idea.vim.helper
|
||||||
|
|
||||||
import org.jetbrains.annotations.NonNls
|
@VimNlsSafe
|
||||||
|
|
||||||
// [VERSION UPDATE] 203+ replace this annotation with @VimNlsSafe
|
|
||||||
@NonNls
|
|
||||||
public annotation class VimNlsSafe
|
public annotation class VimNlsSafe
|
||||||
|
@ -237,7 +237,7 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
|
|||||||
Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT
|
Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT
|
||||||
is Mode.VISUAL -> MappingMode.VISUAL
|
is Mode.VISUAL -> MappingMode.VISUAL
|
||||||
is Mode.SELECT -> MappingMode.SELECT
|
is Mode.SELECT -> MappingMode.SELECT
|
||||||
Mode.CMD_LINE -> MappingMode.CMD_LINE
|
is Mode.CMD_LINE -> MappingMode.CMD_LINE
|
||||||
is Mode.OP_PENDING -> MappingMode.OP_PENDING
|
is Mode.OP_PENDING -> MappingMode.OP_PENDING
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,11 +16,7 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
|
|||||||
import com.maddyhome.idea.vim.api.VimEditor
|
import com.maddyhome.idea.vim.api.VimEditor
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.command.Argument
|
import com.maddyhome.idea.vim.command.Argument
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
|
||||||
import com.maddyhome.idea.vim.command.OperatorArguments
|
import com.maddyhome.idea.vim.command.OperatorArguments
|
||||||
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
|
|
||||||
import com.maddyhome.idea.vim.state.VimStateMachine
|
|
||||||
import com.maddyhome.idea.vim.state.mode.selectionType
|
|
||||||
import com.maddyhome.idea.vim.common.Offset
|
import com.maddyhome.idea.vim.common.Offset
|
||||||
import com.maddyhome.idea.vim.common.argumentCaptured
|
import com.maddyhome.idea.vim.common.argumentCaptured
|
||||||
import com.maddyhome.idea.vim.common.offset
|
import com.maddyhome.idea.vim.common.offset
|
||||||
@ -29,8 +25,12 @@ import com.maddyhome.idea.vim.extension.ExtensionHandler
|
|||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
|
import com.maddyhome.idea.vim.group.visual.VimSelection
|
||||||
import com.maddyhome.idea.vim.group.visual.VimSelection.Companion.create
|
import com.maddyhome.idea.vim.group.visual.VimSelection.Companion.create
|
||||||
import com.maddyhome.idea.vim.helper.VimNlsSafe
|
import com.maddyhome.idea.vim.helper.VimNlsSafe
|
||||||
import com.maddyhome.idea.vim.state.mode.mode
|
|
||||||
import com.maddyhome.idea.vim.helper.vimStateMachine
|
import com.maddyhome.idea.vim.helper.vimStateMachine
|
||||||
|
import com.maddyhome.idea.vim.state.VimStateMachine
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
|
||||||
|
import com.maddyhome.idea.vim.state.mode.mode
|
||||||
|
import com.maddyhome.idea.vim.state.mode.selectionType
|
||||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
|
||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
|
||||||
import java.awt.event.KeyEvent
|
import java.awt.event.KeyEvent
|
||||||
@ -250,7 +250,12 @@ public class ToActionMappingInfo(
|
|||||||
LOG.debug("Executing 'ToAction' mapping...")
|
LOG.debug("Executing 'ToAction' mapping...")
|
||||||
val editorDataContext = injector.executionContextManager.onEditor(editor, context)
|
val editorDataContext = injector.executionContextManager.onEditor(editor, context)
|
||||||
val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext)
|
val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext)
|
||||||
injector.actionExecutor.executeAction(action, dataContext)
|
|
||||||
|
val commandBuilder = editor.vimStateMachine.commandBuilder
|
||||||
|
for (i in 0 until commandBuilder.count.coerceAtLeast(1)) {
|
||||||
|
injector.actionExecutor.executeAction(action, dataContext)
|
||||||
|
}
|
||||||
|
commandBuilder.resetCount()
|
||||||
}
|
}
|
||||||
|
|
||||||
public companion object {
|
public companion object {
|
||||||
|
@ -42,6 +42,7 @@ import javax.swing.KeyStroke
|
|||||||
public interface Node<T>
|
public interface Node<T>
|
||||||
|
|
||||||
/** Represents a complete command */
|
/** Represents a complete command */
|
||||||
|
// Todo make T LazyVimCommand
|
||||||
public class CommandNode<T>(public val actionHolder: T) : Node<T>
|
public class CommandNode<T>(public val actionHolder: T) : Node<T>
|
||||||
|
|
||||||
/** Represents a part of the command */
|
/** Represents a part of the command */
|
||||||
|
@ -77,7 +77,7 @@ public sealed class ShortcutOwnerInfo {
|
|||||||
is Mode.VISUAL -> this.visual
|
is Mode.VISUAL -> this.visual
|
||||||
is Mode.SELECT -> this.visual
|
is Mode.SELECT -> this.visual
|
||||||
Mode.INSERT -> this.insert
|
Mode.INSERT -> this.insert
|
||||||
Mode.CMD_LINE -> this.normal
|
is Mode.CMD_LINE -> this.normal
|
||||||
is Mode.OP_PENDING -> this.normal
|
is Mode.OP_PENDING -> this.normal
|
||||||
Mode.REPLACE -> this.insert
|
Mode.REPLACE -> this.insert
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ package com.maddyhome.idea.vim.options.helpers
|
|||||||
|
|
||||||
import com.maddyhome.idea.vim.api.Options
|
import com.maddyhome.idea.vim.api.Options
|
||||||
import com.maddyhome.idea.vim.api.injector
|
import com.maddyhome.idea.vim.api.injector
|
||||||
import com.maddyhome.idea.vim.state.mode.Mode
|
|
||||||
import com.maddyhome.idea.vim.ex.exExceptionMessage
|
import com.maddyhome.idea.vim.ex.exExceptionMessage
|
||||||
import com.maddyhome.idea.vim.helper.enumSetOf
|
import com.maddyhome.idea.vim.helper.enumSetOf
|
||||||
|
import com.maddyhome.idea.vim.state.mode.Mode
|
||||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
@ -175,7 +175,7 @@ public enum class GuiCursorMode(public val token: String) {
|
|||||||
is Mode.SELECT -> GuiCursorMode.INSERT
|
is Mode.SELECT -> GuiCursorMode.INSERT
|
||||||
is Mode.VISUAL -> GuiCursorMode.VISUAL // TODO: VISUAL_EXCLUSIVE
|
is Mode.VISUAL -> GuiCursorMode.VISUAL // TODO: VISUAL_EXCLUSIVE
|
||||||
// This doesn't handle ci and cr, but we don't care - our CMD_LINE will never call this
|
// This doesn't handle ci and cr, but we don't care - our CMD_LINE will never call this
|
||||||
Mode.CMD_LINE -> GuiCursorMode.CMD_LINE
|
is Mode.CMD_LINE -> GuiCursorMode.CMD_LINE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,14 +27,15 @@ import com.maddyhome.idea.vim.state.VimStateMachine
|
|||||||
* Also read about how modes work in Vim: https://github.com/JetBrains/ideavim/wiki/how-many-modes-does-vim-have
|
* Also read about how modes work in Vim: https://github.com/JetBrains/ideavim/wiki/how-many-modes-does-vim-have
|
||||||
*/
|
*/
|
||||||
public sealed interface Mode {
|
public sealed interface Mode {
|
||||||
public data class NORMAL(public val returnTo: ReturnTo? = null) : Mode
|
public data class NORMAL(public val returnTo: ReturnTo? = null) : Mode, ReturnableFromCmd
|
||||||
public data class OP_PENDING(public val returnTo: ReturnTo? = null, public val forcedVisual: SelectionType? = null) :
|
public data class OP_PENDING(public val returnTo: ReturnTo? = null, public val forcedVisual: SelectionType? = null) :
|
||||||
Mode
|
Mode, ReturnableFromCmd
|
||||||
public data class VISUAL(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode
|
public data class VISUAL(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode,
|
||||||
|
ReturnableFromCmd
|
||||||
public data class SELECT(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode
|
public data class SELECT(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode
|
||||||
public object INSERT : Mode
|
public object INSERT : Mode
|
||||||
public object REPLACE : Mode
|
public object REPLACE : Mode
|
||||||
public object CMD_LINE : Mode
|
public data class CMD_LINE(public val returnTo: ReturnableFromCmd) : Mode
|
||||||
}
|
}
|
||||||
|
|
||||||
public sealed interface ReturnTo {
|
public sealed interface ReturnTo {
|
||||||
@ -42,6 +43,9 @@ public sealed interface ReturnTo {
|
|||||||
public object REPLACE : ReturnTo
|
public object REPLACE : ReturnTo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Marks modes that can we return from CMD_LINE mode
|
||||||
|
public sealed interface ReturnableFromCmd
|
||||||
|
|
||||||
public enum class SelectionType {
|
public enum class SelectionType {
|
||||||
LINE_WISE,
|
LINE_WISE,
|
||||||
CHARACTER_WISE,
|
CHARACTER_WISE,
|
||||||
|
@ -117,7 +117,55 @@ public fun Mode.toVimNotation(): String {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Mode.REPLACE -> "R"
|
Mode.REPLACE -> "R"
|
||||||
Mode.CMD_LINE -> "c"
|
is Mode.CMD_LINE -> "c"
|
||||||
is Mode.OP_PENDING -> "no"
|
is Mode.OP_PENDING -> "no"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public fun Mode.returnTo(): Mode {
|
||||||
|
return when (this) {
|
||||||
|
is Mode.CMD_LINE -> {
|
||||||
|
val returnMode = returnTo as Mode
|
||||||
|
// We need to understand logic that doesn't exit visual if it's just visual,
|
||||||
|
// but exits visual if it's one-time visual
|
||||||
|
if (returnMode.returnTo != null) {
|
||||||
|
returnMode.returnTo()
|
||||||
|
} else {
|
||||||
|
returnMode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode.INSERT -> Mode.NORMAL()
|
||||||
|
is Mode.NORMAL -> when (returnTo) {
|
||||||
|
ReturnTo.INSERT -> Mode.INSERT
|
||||||
|
ReturnTo.REPLACE -> Mode.REPLACE
|
||||||
|
null -> Mode.NORMAL()
|
||||||
|
}
|
||||||
|
|
||||||
|
is Mode.OP_PENDING -> when (returnTo) {
|
||||||
|
ReturnTo.INSERT -> Mode.INSERT
|
||||||
|
ReturnTo.REPLACE -> Mode.REPLACE
|
||||||
|
null -> Mode.NORMAL()
|
||||||
|
}
|
||||||
|
|
||||||
|
Mode.REPLACE -> Mode.NORMAL()
|
||||||
|
is Mode.SELECT -> when (returnTo) {
|
||||||
|
ReturnTo.INSERT -> Mode.INSERT
|
||||||
|
ReturnTo.REPLACE -> Mode.REPLACE
|
||||||
|
null -> Mode.NORMAL()
|
||||||
|
}
|
||||||
|
|
||||||
|
is Mode.VISUAL -> when (returnTo) {
|
||||||
|
ReturnTo.INSERT -> Mode.INSERT
|
||||||
|
ReturnTo.REPLACE -> Mode.REPLACE
|
||||||
|
null -> Mode.NORMAL()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public val Mode.toReturnTo: ReturnTo
|
||||||
|
get() = when (this) {
|
||||||
|
Mode.INSERT -> ReturnTo.INSERT
|
||||||
|
Mode.REPLACE -> ReturnTo.REPLACE
|
||||||
|
else -> error("Cannot get return to from $this")
|
||||||
|
}
|
||||||
|
@ -11,7 +11,13 @@ package com.maddyhome.idea.vim.vimscript.model
|
|||||||
import java.lang.invoke.MethodHandles
|
import java.lang.invoke.MethodHandles
|
||||||
import java.lang.invoke.MethodType
|
import java.lang.invoke.MethodType
|
||||||
|
|
||||||
public open class LazyInstance<T>(private val className: String, private val classLoader: ClassLoader) {
|
/**
|
||||||
|
* Abstract class representing a lazily loaded instance of a specified class. The class is dynamically
|
||||||
|
* loaded and instantiated at runtime, using the provided class name and class loader. This approach is
|
||||||
|
* useful for deferring the loading and instantiation of a class until it is actually needed, reducing
|
||||||
|
* initial memory footprint and startup time.
|
||||||
|
*/
|
||||||
|
public abstract class LazyInstance<T>(private val className: String, private val classLoader: ClassLoader) {
|
||||||
public open val instance: T by lazy {
|
public open val instance: T by lazy {
|
||||||
val aClass = classLoader.loadClass(className)
|
val aClass = classLoader.loadClass(className)
|
||||||
val lookup = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup())
|
val lookup = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup())
|
||||||
|
Loading…
Reference in New Issue
Block a user