mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 10:40:10 +01:00 
			
		
		
		
	Compare commits
	
		
			76 Commits
		
	
	
		
			976791ac95
			...
			customized
		
	
	| 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 | 
							
								
								
									
										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 DEV_CHANNEL = "Dev"
 | 
			
		||||
 | 
			
		||||
  const val GITHUB_TESTS = "2023.1.2"
 | 
			
		||||
  const val NVIM_TESTS = "2023.1.2"
 | 
			
		||||
  const val PROPERTY_TESTS = "2023.1.2"
 | 
			
		||||
  const val LONG_RUNNING_TESTS = "2023.1.2"
 | 
			
		||||
  const val QODANA_TESTS = "2023.1.2"
 | 
			
		||||
  const val RELEASE = "2023.1.2"
 | 
			
		||||
  // TODO it should be 2023.3 as soon as it releases
 | 
			
		||||
  const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val RELEASE = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
  const val RELEASE_DEV = "2023.1.2"
 | 
			
		||||
  const val RELEASE_EAP = "2023.1.2"
 | 
			
		||||
  const val RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  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)
 | 
			
		||||
 | 
			
		||||
  // 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 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.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
 | 
			
		||||
  buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
 | 
			
		||||
  buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
 | 
			
		||||
  buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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-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-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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -324,7 +324,7 @@ IdeaVim tips and tricks
 | 
			
		||||
- Use the power of IJ and Vim:
 | 
			
		||||
    - `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.
 | 
			
		||||
    - 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).
 | 
			
		||||
 | 
			
		||||
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,8 @@ plugins {
 | 
			
		||||
  kotlin("plugin.serialization") version "1.8.21"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val kotlinxSerializationVersion: String by project
 | 
			
		||||
 | 
			
		||||
group = "com.intellij"
 | 
			
		||||
version = "SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
@@ -19,6 +21,10 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14")
 | 
			
		||||
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
 | 
			
		||||
  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'),
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Insert mode
 | 
			
		||||
   * Indicates this key mapping applies to Insert or Replace modes
 | 
			
		||||
   */
 | 
			
		||||
  INSERT('I'),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -53,7 +53,7 @@ buildscript {
 | 
			
		||||
        classpath("org.kohsuke:github-api:1.305")
 | 
			
		||||
 | 
			
		||||
        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-content-negotiation:2.3.6")
 | 
			
		||||
        classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
 | 
			
		||||
@@ -116,7 +116,7 @@ repositories {
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    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
 | 
			
		||||
    testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
 | 
			
		||||
@@ -141,7 +141,7 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    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-params:5.10.1")
 | 
			
		||||
}
 | 
			
		||||
@@ -344,8 +344,6 @@ tasks {
 | 
			
		||||
    val pluginVersion = version
 | 
			
		||||
    // Don't forget to update plugin.xml
 | 
			
		||||
    patchPluginXml {
 | 
			
		||||
        sinceBuild.set("231.7515.13")
 | 
			
		||||
 | 
			
		||||
        // Get the latest available change notes from the changelog file
 | 
			
		||||
        changeNotes.set(
 | 
			
		||||
            provider {
 | 
			
		||||
@@ -524,10 +522,12 @@ tasks.register("releaseActions") {
 | 
			
		||||
        if (tickets.isNotEmpty()) {
 | 
			
		||||
            println("Updating statuses for tickets: $tickets")
 | 
			
		||||
            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())
 | 
			
		||||
            } else {
 | 
			
		||||
                println("Version $version is already exists in YouTrack")
 | 
			
		||||
                println("Version $version already exists in YouTrack. Version id: $versionId")
 | 
			
		||||
            }
 | 
			
		||||
            setYoutrackFixVersion(tickets, version.toString())
 | 
			
		||||
        } 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.
 | 
			
		||||
 | 
			
		||||
: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:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@
 | 
			
		||||
ideaVersion=2023.2
 | 
			
		||||
downloadIdeaSources=true
 | 
			
		||||
instrumentPluginCode=true
 | 
			
		||||
version=chylex-21
 | 
			
		||||
version=chylex-22
 | 
			
		||||
javaVersion=17
 | 
			
		||||
remoteRobotVersion=0.11.17
 | 
			
		||||
antlrVersion=4.10.1
 | 
			
		||||
@@ -19,10 +19,15 @@ antlrVersion=4.10.1
 | 
			
		||||
kotlin.incremental.useClasspathSnapshot=false
 | 
			
		||||
 | 
			
		||||
# Please don't forget to update kotlin version in buildscript section
 | 
			
		||||
# Also update kotlinxSerializationVersion version
 | 
			
		||||
kotlinVersion=1.8.21
 | 
			
		||||
publishToken=token
 | 
			
		||||
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=
 | 
			
		||||
youtrackToken=
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,10 +20,10 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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-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-serialization-kotlinx-json:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.6")
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
 | 
			
		||||
  println("HI!")
 | 
			
		||||
  val projectDir = args[0]
 | 
			
		||||
  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}")
 | 
			
		||||
 | 
			
		||||
  val branch = withRepo(projectDir) { it.branch }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
 | 
			
		||||
  println("HI!")
 | 
			
		||||
  val projectDir = args[0]
 | 
			
		||||
  println("Working directory: $projectDir")
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = false)
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, ReleaseType.ANY)
 | 
			
		||||
 | 
			
		||||
  val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
 | 
			
		||||
    lastVersion.nextMinor().withSuffix("eap.1").value
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ fun main(args: Array<String>) {
 | 
			
		||||
  val releaseType = args[1]
 | 
			
		||||
  println("Working directory: $projectDir")
 | 
			
		||||
  println("Release type: $releaseType")
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = true)
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, ReleaseType.ONLY_STABLE)
 | 
			
		||||
 | 
			
		||||
  val nextVersion = when (releaseType) {
 | 
			
		||||
    "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 git = Git(repository)
 | 
			
		||||
  println(git.log().call().first())
 | 
			
		||||
@@ -75,10 +81,10 @@ internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, O
 | 
			
		||||
  }
 | 
			
		||||
    .sortedBy { it.first }
 | 
			
		||||
 | 
			
		||||
  val version = if (onlyStable) {
 | 
			
		||||
    versions.last { it.first.isStable }
 | 
			
		||||
  } else {
 | 
			
		||||
    versions.last()
 | 
			
		||||
  val version = when (releaseType) {
 | 
			
		||||
    ReleaseType.ANY -> versions.last()
 | 
			
		||||
    ReleaseType.ONLY_STABLE -> versions.last { it.first.isStable }
 | 
			
		||||
    ReleaseType.STABLE_NO_PATCH -> versions.last { it.first.isStable && it.first.patch == 0 }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return version
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ package com.maddyhome.idea.vim
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
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.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.localEditors
 | 
			
		||||
@@ -20,16 +20,11 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
/**
 | 
			
		||||
 * @author Alex Plate
 | 
			
		||||
 */
 | 
			
		||||
// This service should be migrated to ProjectActivity. But we should cariful because simple replacement
 | 
			
		||||
// 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*/ {
 | 
			
		||||
internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
 | 
			
		||||
 | 
			
		||||
  private var firstInitializationOccurred = false
 | 
			
		||||
 | 
			
		||||
  override fun runActivity(project: Project) {
 | 
			
		||||
  override suspend fun execute(project: Project) {
 | 
			
		||||
    if (firstInitializationOccurred) return
 | 
			
		||||
    firstInitializationOccurred = true
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -232,7 +232,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
      getInstance().turnOnPlugin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    StatusBarIconFactory.Companion.updateIcon();
 | 
			
		||||
    StatusBarIconFactory.Util.INSTANCE.updateIcon();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static String getMessage() {
 | 
			
		||||
@@ -264,7 +264,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      Application application = ApplicationManager.getApplication();
 | 
			
		||||
      if (application.isUnitTestMode()) {
 | 
			
		||||
        application.invokeAndWait(this::turnOnPlugin);
 | 
			
		||||
        turnOnPlugin();
 | 
			
		||||
        //application.invokeAndWait(this::turnOnPlugin);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        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
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -60,7 +60,12 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
 | 
			
		||||
 */
 | 
			
		||||
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) {
 | 
			
		||||
    LOG.trace("Executing shortcut key action")
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
 | 
			
		||||
    get() {
 | 
			
		||||
      val myMode = machine.mode
 | 
			
		||||
      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
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
@@ -275,8 +275,8 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
 | 
			
		||||
  private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
 | 
			
		||||
    for (KeyStroke key : keys) {
 | 
			
		||||
      if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
 | 
			
		||||
          key.getKeyCode() != KeyEvent.VK_ESCAPE &&
 | 
			
		||||
          key.getKeyCode() != KeyEvent.VK_ENTER) {
 | 
			
		||||
          !(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
 | 
			
		||||
          !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.completion.CompletionPhase
 | 
			
		||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.progress.ProcessCanceledException
 | 
			
		||||
@@ -19,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
			
		||||
import com.maddyhome.idea.vim.macro.VimMacroBase
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Used to handle playback of macros
 | 
			
		||||
@@ -71,12 +74,18 @@ internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
            } catch (e: ProcessCanceledException) {
 | 
			
		||||
              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
 | 
			
		||||
          }
 | 
			
		||||
          keyStack.resetFirst()
 | 
			
		||||
        }
 | 
			
		||||
        keyStack.removeFirst()
 | 
			
		||||
        if (!isInternalMacro) {
 | 
			
		||||
          MacroAutoImport.run(editor.ij, context.ij)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isInternalMacro) {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,11 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.ide.CopyPasteManager
 | 
			
		||||
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.project.DumbAwareAction
 | 
			
		||||
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.globalOptions
 | 
			
		||||
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.key.ShortcutOwner
 | 
			
		||||
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
 | 
			
		||||
@@ -180,6 +184,77 @@ internal class NotificationService(private val project: 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 {
 | 
			
		||||
    private var notification: Notification? = null
 | 
			
		||||
    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 ideajoinExamplesUrl = "https://jb.gg/f9zji9"
 | 
			
		||||
 | 
			
		||||
    private val LOG = logger<NotificationService>()
 | 
			
		||||
 | 
			
		||||
    private fun createIdeaVimRcManually(message: String, project: Project?) {
 | 
			
		||||
      val notification =
 | 
			
		||||
        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
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
 | 
			
		||||
import com.intellij.openapi.extensions.ExtensionPointName
 | 
			
		||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
			
		||||
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
 | 
			
		||||
@@ -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
 | 
			
		||||
 *   otherwise, so let's use it as long as we can.
 | 
			
		||||
 */
 | 
			
		||||
internal class EditorHandlersChainLogger : StartupActivity {
 | 
			
		||||
internal class EditorHandlersChainLogger : ProjectActivity {
 | 
			
		||||
  @Suppress("UnresolvedPluginConfigReference")
 | 
			
		||||
  private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
 | 
			
		||||
 | 
			
		||||
  override fun runActivity(project: Project) {
 | 
			
		||||
  override suspend fun execute(project: Project) {
 | 
			
		||||
    val escHandlers = editorHandlers.extensionList
 | 
			
		||||
      .filter { it.action == "EditorEscape" }
 | 
			
		||||
      .joinToString("\n") { it.implementationClass }
 | 
			
		||||
@@ -40,6 +43,22 @@ internal class EditorHandlersChainLogger : StartupActivity {
 | 
			
		||||
 | 
			
		||||
    LOG.info("Esc handlers chain:\n$escHandlers")
 | 
			
		||||
    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 {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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.lookup.LookupManager
 | 
			
		||||
import com.intellij.formatting.LineWrappingUtil
 | 
			
		||||
import com.intellij.ide.DataManager
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
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.Editor
 | 
			
		||||
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.fileEditor.FileDocumentManager
 | 
			
		||||
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).
 | 
			
		||||
        // 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.
 | 
			
		||||
        //
 | 
			
		||||
        // 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 {
 | 
			
		||||
        executionHandler()
 | 
			
		||||
      }
 | 
			
		||||
@@ -106,7 +116,11 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
			
		||||
 | 
			
		||||
  private fun executeInInvokeLater(editor: Editor): Boolean {
 | 
			
		||||
    // 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
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -131,7 +145,20 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
			
		||||
        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
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -174,11 +201,6 @@ internal class VimEnterHandler(nextHandler: EditorActionHandler?) : VimKeyHandle
 | 
			
		||||
    // See VIM-2974 for example where it was broken
 | 
			
		||||
    return !editor.isOneLineMode
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
			
		||||
    if (caret == null || caret === editor.caretModel.primaryCaret)
 | 
			
		||||
    super.executeHandler(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -249,11 +271,17 @@ internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Workaround to support shift-enter in normal mode.
 | 
			
		||||
 * IJ executes enter handler on shift-enter. This causes an issue that IdeaVim thinks that this is just an enter key.
 | 
			
		||||
 * Workaround to support "Start New Line" action in normal mode.
 | 
			
		||||
 * 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
 | 
			
		||||
 *
 | 
			
		||||
 * 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?) {
 | 
			
		||||
    DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
 | 
			
		||||
    nextHandler.execute(editor, caret, dataContext)
 | 
			
		||||
@@ -264,7 +292,7 @@ internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object Util {
 | 
			
		||||
    val key = Key.create<Boolean>("vim.is.shift.enter")
 | 
			
		||||
    val key = Key.create<Boolean>("vim.is.start.new.line")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
@@ -316,9 +344,9 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
 | 
			
		||||
  // 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.
 | 
			
		||||
  if (editor.vim.mode is Mode.CMD_LINE) return false
 | 
			
		||||
  when (s.keyCode) {
 | 
			
		||||
    KeyEvent.VK_ENTER -> return true
 | 
			
		||||
    KeyEvent.VK_ESCAPE -> return true
 | 
			
		||||
  when {
 | 
			
		||||
    s.keyCode == KeyEvent.VK_ENTER && s.modifiers == 0 -> return true
 | 
			
		||||
    s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
 | 
			
		||||
  get() {
 | 
			
		||||
    val mode = this.vim.vimStateMachine.mode
 | 
			
		||||
    return when (mode) {
 | 
			
		||||
      Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
      is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
      Mode.INSERT -> CommandState.Mode.INSERT
 | 
			
		||||
      is Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
			
		||||
      is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
			
		||||
 
 | 
			
		||||
@@ -15,10 +15,12 @@ import com.intellij.openapi.actionSystem.AnAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionResult
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContextWrapper
 | 
			
		||||
import com.intellij.openapi.actionSystem.EmptyAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
 | 
			
		||||
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.UndoConfirmationPolicy
 | 
			
		||||
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.runFromVimKey
 | 
			
		||||
import org.jetbrains.annotations.NonNls
 | 
			
		||||
import java.awt.Component
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
import javax.swing.SwingUtilities
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@@ -150,11 +154,44 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
   * @param context The context to run it in
 | 
			
		||||
   */
 | 
			
		||||
  override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
 | 
			
		||||
    val aMgr = ActionManager.getInstance()
 | 
			
		||||
    val action = aMgr.getAction(name)
 | 
			
		||||
    val action = getAction(name, 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(
 | 
			
		||||
    editor: VimEditor?,
 | 
			
		||||
    runnable: Runnable,
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.ChangesListener
 | 
			
		||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
@@ -44,22 +45,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      } else {
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.undo(fileEditor)
 | 
			
		||||
        if (hasSelection(editor) && undoManager.isUndoAvailable(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
 | 
			
		||||
        editor.carets().forEach {
 | 
			
		||||
          val ijCaret = it.ij
 | 
			
		||||
          val hasSelection = ijCaret.hasSelection()
 | 
			
		||||
          if (hasSelection) {
 | 
			
		||||
            val selectionStart = ijCaret.selectionStart
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
              it.ij.removeSelection()
 | 
			
		||||
              it.ij.moveToOffset(selectionStart)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          removeSelections(editor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -88,12 +84,59 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
          editor.carets().forEach { it.ij.removeSelection() }
 | 
			
		||||
        }
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.redo(fileEditor)
 | 
			
		||||
 | 
			
		||||
          // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
          if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
 | 
			
		||||
            undoManager.redo(fileEditor)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
          removeSelections(editor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    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)
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,8 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
			
		||||
import com.maddyhome.idea.vim.handler.correctorRequester
 | 
			
		||||
import com.maddyhome.idea.vim.handler.keymapCheckRequester
 | 
			
		||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.helper.StrictMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.exitSelectMode
 | 
			
		||||
@@ -129,11 +131,14 @@ internal object VimListenerManager {
 | 
			
		||||
  fun turnOn() {
 | 
			
		||||
    GlobalListeners.enable()
 | 
			
		||||
    EditorListeners.addAll()
 | 
			
		||||
    correctorRequester.request()
 | 
			
		||||
    keymapCheckRequester.request()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun turnOff() {
 | 
			
		||||
    GlobalListeners.disable()
 | 
			
		||||
    EditorListeners.removeAll()
 | 
			
		||||
    correctorRequester.request()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object GlobalListeners {
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ import com.intellij.openapi.editor.CaretStateTransferableData
 | 
			
		||||
import com.intellij.openapi.editor.RawText
 | 
			
		||||
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData
 | 
			
		||||
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData
 | 
			
		||||
import com.intellij.openapi.project.DumbService
 | 
			
		||||
import com.intellij.openapi.project.IndexNotReadyException
 | 
			
		||||
import com.intellij.psi.PsiDocumentManager
 | 
			
		||||
import com.intellij.util.ui.EmptyClipboardOwner
 | 
			
		||||
@@ -100,8 +101,7 @@ internal class IjClipboardManager : VimClipboardManager {
 | 
			
		||||
 | 
			
		||||
    // This thing enables alternative context resolve for dumb mode.
 | 
			
		||||
    // 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) {
 | 
			
		||||
        try {
 | 
			
		||||
          logger.debug { "Copy paste processor: ${processor.javaClass.name}" }
 | 
			
		||||
@@ -116,7 +116,7 @@ internal class IjClipboardManager : VimClipboardManager {
 | 
			
		||||
        } catch (ignore: IndexNotReadyException) {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
//    }
 | 
			
		||||
    }
 | 
			
		||||
    transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
 | 
			
		||||
 | 
			
		||||
    // 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 javax.swing.Icon
 | 
			
		||||
import javax.swing.SwingConstants
 | 
			
		||||
import javax.swing.Timer
 | 
			
		||||
 | 
			
		||||
@NonNls
 | 
			
		||||
internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
 | 
			
		||||
@@ -73,6 +74,14 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
 | 
			
		||||
 | 
			
		||||
  override fun createWidget(project: Project): StatusBarWidget {
 | 
			
		||||
    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()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -89,10 +98,10 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
 | 
			
		||||
      statusBarWidgetsManager.updateWidget(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    updateIcon()
 | 
			
		||||
    Util.updateIcon()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
  object Util {
 | 
			
		||||
    fun updateIcon() {
 | 
			
		||||
      val projectManager = ProjectManager.getInstanceIfCreated() ?: return
 | 
			
		||||
      for (project in projectManager.openProjects) {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,10 @@
 | 
			
		||||
              topic="com.intellij.ide.ui.LafManagerListener"/>
 | 
			
		||||
    <listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter"
 | 
			
		||||
              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>
 | 
			
		||||
  <projectListeners>
 | 
			
		||||
    <listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"
 | 
			
		||||
 
 | 
			
		||||
@@ -71,11 +71,12 @@
 | 
			
		||||
         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.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"/>
 | 
			
		||||
 | 
			
		||||
    <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"/>
 | 
			
		||||
 | 
			
		||||
@@ -117,8 +118,12 @@
 | 
			
		||||
                         id="ideavim-enter-logger"
 | 
			
		||||
                         order="first"/>
 | 
			
		||||
    <editorActionHandler action="EditorStartNewLine"
 | 
			
		||||
                         implementationClass="com.maddyhome.idea.vim.handler.ShiftEnterDetector"
 | 
			
		||||
                         id="ideavim-shift-enter-detector"
 | 
			
		||||
                         implementationClass="com.maddyhome.idea.vim.handler.StartNewLineDetector"
 | 
			
		||||
                         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"/>
 | 
			
		||||
  </extensions>
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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 {
 | 
			
		||||
    if (keyChar != KeyEvent.CHAR_UNDEFINED) return CharType.CharDetected(keyChar)
 | 
			
		||||
    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")
 | 
			
		||||
    }
 | 
			
		||||
    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.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
import org.junit.jupiter.api.Disabled
 | 
			
		||||
import org.junit.jupiter.api.Test
 | 
			
		||||
 | 
			
		||||
// [VERSION UPDATE] 232+ enable tests
 | 
			
		||||
@Suppress("unused")
 | 
			
		||||
class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -23,7 +21,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testEmpty() {
 | 
			
		||||
    configureByJavaText("<caret>")
 | 
			
		||||
    typeText(injector.parser.parseKeys("gqq"))
 | 
			
		||||
@@ -32,7 +29,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testWithCount() {
 | 
			
		||||
    configureByJavaText("class C {\n\tint a;\n\tint <caret>b;\n\tint c;\n\tint d;\n}\n")
 | 
			
		||||
    typeText(injector.parser.parseKeys("2gqq"))
 | 
			
		||||
@@ -41,7 +37,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testWithUpMotion() {
 | 
			
		||||
    configureByJavaText("class C {\n" + "\tint a;\n" + "\tint b;\n" + "\tint <caret>c;\n" + "\tint d;\n" + "}\n")
 | 
			
		||||
    typeText(injector.parser.parseKeys("gqk"))
 | 
			
		||||
@@ -50,7 +45,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testWithRightMotion() {
 | 
			
		||||
    configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
 | 
			
		||||
    typeText(injector.parser.parseKeys("gql"))
 | 
			
		||||
@@ -59,7 +53,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testWithTextObject() {
 | 
			
		||||
    configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
 | 
			
		||||
    typeText(injector.parser.parseKeys("gqi{"))
 | 
			
		||||
@@ -75,7 +68,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testWithCountsAndDownMotion() {
 | 
			
		||||
    configureByJavaText("class C {\n" + "\tint <caret>a;\n" + "\tint b;\n" + "\tint c;\n" + "\tint d;\n" + "}\n")
 | 
			
		||||
    typeText(injector.parser.parseKeys("2gqj"))
 | 
			
		||||
@@ -84,7 +76,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testVisual() {
 | 
			
		||||
    configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
 | 
			
		||||
    typeText(injector.parser.parseKeys("v" + "l" + "gq"))
 | 
			
		||||
@@ -93,7 +84,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testLinewiseVisual() {
 | 
			
		||||
    configureByJavaText("class C {\n" + "\tint a;\n" + "\tint <caret>b;\n" + "\tint c;\n" + "}\n")
 | 
			
		||||
    typeText(injector.parser.parseKeys("V" + "l" + "gq"))
 | 
			
		||||
@@ -102,7 +92,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testVisualMultiline() {
 | 
			
		||||
    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"))
 | 
			
		||||
@@ -111,7 +100,6 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.DIFFERENT)
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testVisualBlock() {
 | 
			
		||||
    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"))
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@
 | 
			
		||||
 | 
			
		||||
package org.jetbrains.plugins.ideavim.action.change.delete
 | 
			
		||||
 | 
			
		||||
import com.intellij.notification.ActionCenter
 | 
			
		||||
import com.intellij.notification.EventLog
 | 
			
		||||
import com.intellij.notification.Notification
 | 
			
		||||
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.TraceOptions
 | 
			
		||||
import org.jetbrains.plugins.ideavim.impl.VimOption
 | 
			
		||||
import org.junit.jupiter.api.Disabled
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Alex Plate
 | 
			
		||||
 */
 | 
			
		||||
@TraceOptions(TestIjOptionConstants.ideajoin)
 | 
			
		||||
class JoinNotificationTest : VimTestCase() {
 | 
			
		||||
  @Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
 | 
			
		||||
  @OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
 | 
			
		||||
  fun `test notification shown for no ideajoin`() {
 | 
			
		||||
    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"]))
 | 
			
		||||
  fun `test notification not shown for ideajoin`() {
 | 
			
		||||
    val before = "I found${c} it\n in a legendary land"
 | 
			
		||||
@@ -58,8 +56,7 @@ class JoinNotificationTest : VimTestCase() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun notifications(): MutableList<Notification> {
 | 
			
		||||
    TODO()
 | 
			
		||||
//    return ActionCenter.getNotifications(fixture.project)
 | 
			
		||||
    return ActionCenter.getNotifications(fixture.project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @OptionTest(VimOption(TestIjOptionConstants.ideajoin, limitedValues = ["false"]))
 | 
			
		||||
 
 | 
			
		||||
@@ -60,7 +60,7 @@ class InsertEnterActionTest : VimTestCase() {
 | 
			
		||||
    } else if (repetitionInfo.currentRepetition == 3) {
 | 
			
		||||
      ExtensionTestUtil.maskExtensions(
 | 
			
		||||
        ExtensionPointName("com.intellij.editorActionHandler"),
 | 
			
		||||
        listOf(singleBean, mainBean),
 | 
			
		||||
        listOf(forEachBean, mainBean),
 | 
			
		||||
        fixture.testRootDisposable
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,14 +12,38 @@ import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
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
 | 
			
		||||
  fun `test enter visual`() {
 | 
			
		||||
    doTest(
 | 
			
		||||
      listOf("i", "<C-O>", "vlll", "<Esc>"),
 | 
			
		||||
      listOf(command, "<C-O>", "vlll", "<Esc>"),
 | 
			
		||||
      "I found ${c}it 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
 | 
			
		||||
 | 
			
		||||
import com.intellij.notification.ActionCenter
 | 
			
		||||
import com.intellij.notification.EventLog
 | 
			
		||||
import com.intellij.notification.Notification
 | 
			
		||||
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.VimOption
 | 
			
		||||
import org.jetbrains.plugins.ideavim.rangeOf
 | 
			
		||||
import org.junit.jupiter.api.Disabled
 | 
			
		||||
 | 
			
		||||
@TraceOptions(TestOptionConstants.clipboard)
 | 
			
		||||
class IdeaPutNotificationsTest : VimTestCase() {
 | 
			
		||||
  @Disabled("[VERSION UPDATE] Enable when min version is 2023.2+")
 | 
			
		||||
  @OptionTest(VimOption(TestOptionConstants.clipboard, limitedValues = [""]))
 | 
			
		||||
  fun `test notification exists if no ideaput`() {
 | 
			
		||||
    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]))
 | 
			
		||||
  fun `test no notification on ideaput`() {
 | 
			
		||||
    val before = "${c}I found it in a legendary land"
 | 
			
		||||
@@ -63,8 +61,7 @@ class IdeaPutNotificationsTest : VimTestCase() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun notifications(): MutableList<Notification> {
 | 
			
		||||
    TODO()
 | 
			
		||||
//    return ActionCenter.getNotifications(fixture.project)
 | 
			
		||||
    return ActionCenter.getNotifications(fixture.project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @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
 | 
			
		||||
 | 
			
		||||
import com.intellij.idea.TestFor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
import org.junit.jupiter.api.Test
 | 
			
		||||
 | 
			
		||||
class MarksCommandTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  // https://youtrack.jetbrains.com/issue/VIM-2223
 | 
			
		||||
  @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`() {
 | 
			
		||||
    configureByText(
 | 
			
		||||
      """I found it in a legendary land
 | 
			
		||||
@@ -35,8 +125,8 @@ class MarksCommandTest : VimTestCase() {
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // https://youtrack.jetbrains.com/issue/VIM-1684
 | 
			
		||||
  @Test
 | 
			
		||||
  @TestFor(issues = ["VIM-1684"])
 | 
			
		||||
  fun `test reselecting different text length`() {
 | 
			
		||||
    configureByText(
 | 
			
		||||
      """
 | 
			
		||||
@@ -53,8 +143,8 @@ class MarksCommandTest : VimTestCase() {
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // https://youtrack.jetbrains.com/issue/VIM-2491
 | 
			
		||||
  @Test
 | 
			
		||||
  @TestFor(issues = ["VIM-2491"])
 | 
			
		||||
  fun `test mapping with gv`() {
 | 
			
		||||
    configureByText("Oh, hi ${c}Andy Tom John")
 | 
			
		||||
    typeText(commandToKeys("xnoremap p pgvy"))
 | 
			
		||||
 
 | 
			
		||||
@@ -11,10 +11,10 @@ package org.jetbrains.plugins.ideavim.propertybased
 | 
			
		||||
import com.intellij.ide.IdeEventQueue
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
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.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.key.CommandNode
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import org.jetbrains.jetCheck.Generator
 | 
			
		||||
import org.jetbrains.jetCheck.ImperativeCommand
 | 
			
		||||
@@ -99,7 +99,7 @@ private class AvailableActions(private val editor: Editor) : ImperativeCommand {
 | 
			
		||||
    val usedKey = env.generateValue(keyGenerator, null)
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    IdeEventQueue.getInstance().flushQueue()
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ plugins {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val kotlinVersion: String by project
 | 
			
		||||
val kotlinxSerializationVersion: String by project
 | 
			
		||||
 | 
			
		||||
// group 'org.jetbrains.ideavim'
 | 
			
		||||
// version 'SNAPSHOT'
 | 
			
		||||
@@ -35,18 +36,18 @@ afterEvaluate {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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")
 | 
			
		||||
 | 
			
		||||
    // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-test
 | 
			
		||||
    testImplementation("org.jetbrains.kotlin:kotlin-test:$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"))
 | 
			
		||||
    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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.key.Node
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
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 java.awt.event.InputEvent
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
@@ -498,7 +499,7 @@ public class KeyHandler {
 | 
			
		||||
      val text = injector.processGroup.endSearchCommand()
 | 
			
		||||
      commandBuilder.popCommandPart() // Pop ProcessExEntryAction
 | 
			
		||||
      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.
 | 
			
		||||
        injector.processGroup.startSearchCommand(editor, context, commandBuilder.count, key)
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,12 @@ import kotlinx.serialization.json.Json
 | 
			
		||||
import kotlinx.serialization.json.decodeFromStream
 | 
			
		||||
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 val commandListFileName: String
 | 
			
		||||
 | 
			
		||||
@@ -29,12 +35,7 @@ public interface CommandProvider {
 | 
			
		||||
      .map {
 | 
			
		||||
        val keys = it.value.map { bean -> injector.parser.parseKeys(bean.keys) }.toSet()
 | 
			
		||||
        val modes = it.value.first().modes.map { mode -> MappingMode.parseModeChar(mode) }.toSet()
 | 
			
		||||
        LazyVimCommand(
 | 
			
		||||
          keys,
 | 
			
		||||
          modes,
 | 
			
		||||
          it.key,
 | 
			
		||||
          classLoader
 | 
			
		||||
        )
 | 
			
		||||
        LazyVimCommand(keys, modes, it.key, classLoader)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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.state.VimStateMachine.Companion.getInstance
 | 
			
		||||
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.mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.toReturnTo
 | 
			
		||||
import org.jetbrains.annotations.NonNls
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import java.util.*
 | 
			
		||||
@@ -614,7 +615,7 @@ public abstract class VimChangeGroupBase : VimChangeGroup {
 | 
			
		||||
   * @param editor The editor to put into NORMAL mode for one command
 | 
			
		||||
   */
 | 
			
		||||
  override fun processSingleCommand(editor: VimEditor) {
 | 
			
		||||
    getInstance(editor).mode = Mode.NORMAL(returnTo = ReturnTo.INSERT)
 | 
			
		||||
    getInstance(editor).mode = Mode.NORMAL(returnTo = editor.mode.toReturnTo)
 | 
			
		||||
    clearStrokes(editor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -166,7 +166,10 @@ public abstract class VimKeyGroupBase : VimKeyGroup {
 | 
			
		||||
  private fun registerKeyMapping(fromKeys: List<KeyStroke>, owner: MappingOwner) {
 | 
			
		||||
    val oldSize = requiredShortcutKeys.size
 | 
			
		||||
    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))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -334,13 +334,13 @@ public abstract class VimMarkServiceBase : VimMarkService {
 | 
			
		||||
 | 
			
		||||
      val startPosition = 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)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val endPosition = selectionInfo.end
 | 
			
		||||
      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)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -13,11 +13,11 @@ import javax.swing.KeyStroke
 | 
			
		||||
public interface VimProcessGroup {
 | 
			
		||||
  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 processExKey(editor: VimEditor, stroke: KeyStroke): Boolean
 | 
			
		||||
  public fun startFilterCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command)
 | 
			
		||||
  public fun startExCommand(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 processExEntry(editor: VimEditor, context: ExecutionContext): Boolean
 | 
			
		||||
  public fun cancelExEntry(editor: VimEditor, resetCaret: Boolean)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -23,7 +23,7 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
 | 
			
		||||
  override val lastCommand: String
 | 
			
		||||
    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")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -35,11 +35,11 @@ public class VimProcessGroupStub : VimProcessGroupBase() {
 | 
			
		||||
    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")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun startExCommand(editor: VimEditor, context: ExecutionContext?, cmd: Command) {
 | 
			
		||||
  public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
 | 
			
		||||
    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.injector
 | 
			
		||||
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.options.OptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isSingleModeActive
 | 
			
		||||
@@ -41,7 +41,7 @@ public val VimEditor.isEndAllowed: Boolean
 | 
			
		||||
public fun VimEditor.isEndAllowed(mode: Mode): Boolean {
 | 
			
		||||
  return when (mode) {
 | 
			
		||||
    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
 | 
			
		||||
      if (mode.isSingleModeActive) true else usesVirtualSpace
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,5 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import org.jetbrains.annotations.NonNls
 | 
			
		||||
 | 
			
		||||
// [VERSION UPDATE] 203+ replace this annotation with @VimNlsSafe
 | 
			
		||||
@NonNls
 | 
			
		||||
@VimNlsSafe
 | 
			
		||||
public annotation class VimNlsSafe
 | 
			
		||||
 
 | 
			
		||||
@@ -237,7 +237,7 @@ public class VimStateMachineImpl(private val editor: VimEditor?) : VimStateMachi
 | 
			
		||||
        Mode.INSERT, Mode.REPLACE -> MappingMode.INSERT
 | 
			
		||||
        is Mode.VISUAL -> MappingMode.VISUAL
 | 
			
		||||
        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
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -251,9 +251,11 @@ public class ToActionMappingInfo(
 | 
			
		||||
    val editorDataContext = injector.executionContextManager.onEditor(editor, context)
 | 
			
		||||
    val dataContext = injector.executionContextManager.onCaret(editor.currentCaret(), editorDataContext)
 | 
			
		||||
 | 
			
		||||
    for (i in 0 until editor.vimStateMachine.commandBuilder.count.coerceAtLeast(1)) {
 | 
			
		||||
    val commandBuilder = editor.vimStateMachine.commandBuilder
 | 
			
		||||
    for (i in 0 until commandBuilder.count.coerceAtLeast(1)) {
 | 
			
		||||
      injector.actionExecutor.executeAction(action, dataContext)
 | 
			
		||||
    }
 | 
			
		||||
    commandBuilder.resetCount()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
 
 | 
			
		||||
@@ -42,6 +42,7 @@ import javax.swing.KeyStroke
 | 
			
		||||
public interface Node<T>
 | 
			
		||||
 | 
			
		||||
/** Represents a complete command */
 | 
			
		||||
// Todo make T LazyVimCommand
 | 
			
		||||
public class CommandNode<T>(public val actionHolder: T) : Node<T>
 | 
			
		||||
 | 
			
		||||
/** Represents a part of the command */
 | 
			
		||||
 
 | 
			
		||||
@@ -77,7 +77,7 @@ public sealed class ShortcutOwnerInfo {
 | 
			
		||||
        is Mode.VISUAL -> this.visual
 | 
			
		||||
        is Mode.SELECT -> this.visual
 | 
			
		||||
        Mode.INSERT -> this.insert
 | 
			
		||||
        Mode.CMD_LINE -> this.normal
 | 
			
		||||
        is Mode.CMD_LINE -> this.normal
 | 
			
		||||
        is Mode.OP_PENDING -> this.normal
 | 
			
		||||
        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.injector
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.ex.exExceptionMessage
 | 
			
		||||
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 java.util.*
 | 
			
		||||
 | 
			
		||||
@@ -175,7 +175,7 @@ public enum class GuiCursorMode(public val token: String) {
 | 
			
		||||
        is Mode.SELECT -> GuiCursorMode.INSERT
 | 
			
		||||
        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
 | 
			
		||||
        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
 | 
			
		||||
 */
 | 
			
		||||
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) :
 | 
			
		||||
    Mode
 | 
			
		||||
  public data class VISUAL(public val selectionType: SelectionType, public val returnTo: ReturnTo? = null) : Mode
 | 
			
		||||
    Mode, ReturnableFromCmd
 | 
			
		||||
  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 object INSERT : Mode
 | 
			
		||||
  public object REPLACE : Mode
 | 
			
		||||
  public object CMD_LINE : Mode
 | 
			
		||||
  public data class CMD_LINE(public val returnTo: ReturnableFromCmd) : Mode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public sealed interface ReturnTo {
 | 
			
		||||
@@ -42,6 +43,9 @@ public sealed interface ReturnTo {
 | 
			
		||||
  public object REPLACE : ReturnTo
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Marks modes that can we return from CMD_LINE mode
 | 
			
		||||
public sealed interface ReturnableFromCmd
 | 
			
		||||
 | 
			
		||||
public enum class SelectionType {
 | 
			
		||||
  LINE_WISE,
 | 
			
		||||
  CHARACTER_WISE,
 | 
			
		||||
 
 | 
			
		||||
@@ -117,7 +117,55 @@ public fun Mode.toVimNotation(): String {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Mode.REPLACE -> "R"
 | 
			
		||||
    Mode.CMD_LINE -> "c"
 | 
			
		||||
    is Mode.CMD_LINE -> "c"
 | 
			
		||||
    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.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 {
 | 
			
		||||
    val aClass = classLoader.loadClass(className)
 | 
			
		||||
    val lookup = MethodHandles.privateLookupIn(aClass, MethodHandles.lookup())
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user