mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 01:40:12 +01:00 
			
		
		
		
	Compare commits
	
		
			15 Commits
		
	
	
		
			customized
			...
			976791ac95
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						976791ac95
	
				 | 
					
					
						|||
| 
						
						
							
						
						9b30831b2f
	
				 | 
					
					
						|||
| 
						
						
							
						
						0c3544c1fe
	
				 | 
					
					
						|||
| 
						
						
							
						
						29813f12fb
	
				 | 
					
					
						|||
| 
						
						
							
						
						0bb5739adc
	
				 | 
					
					
						|||
| 
						
						
							
						
						4e5e94cd98
	
				 | 
					
					
						|||
| 
						
						
							
						
						8b6925e5e4
	
				 | 
					
					
						|||
| 
						
						
							
						
						e451ebf361
	
				 | 
					
					
						|||
| 
						
						
							
						
						056d704297
	
				 | 
					
					
						|||
| 
						
						
							
						
						dbb0f79113
	
				 | 
					
					
						|||
| 
						
						
							
						
						1bc6dfac1c
	
				 | 
					
					
						|||
| 
						
						
							
						
						de449adcb9
	
				 | 
					
					
						|||
| 
						
						
							
						
						2ef9742b71
	
				 | 
					
					
						|||
| 
						
						
							
						
						a2833aa088
	
				 | 
					
					
						|||
| 
						
						
							
						
						13ebac83c6
	
				 | 
					
					
						
							
								
								
									
										24
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							@@ -8,20 +8,18 @@ jobs:
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
    runs-on: macos-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - name: Setup Java
 | 
			
		||||
        uses: actions/setup-java@v4
 | 
			
		||||
        uses: actions/setup-java@v2.1.0
 | 
			
		||||
        with:
 | 
			
		||||
          distribution: zulu
 | 
			
		||||
          java-version: 11
 | 
			
		||||
      - name: Setup FFmpeg
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v3
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v1
 | 
			
		||||
        with:
 | 
			
		||||
          # Not strictly necessary, but it may prevent rate limit
 | 
			
		||||
          # errors especially on GitHub-hosted macos machines.
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: Setup Gradle
 | 
			
		||||
        uses: gradle/gradle-build-action@v2.4.2
 | 
			
		||||
          token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: Build Plugin
 | 
			
		||||
        run: gradle :buildPlugin
 | 
			
		||||
      - name: Run Idea
 | 
			
		||||
@@ -29,7 +27,7 @@ jobs:
 | 
			
		||||
          mkdir -p build/reports
 | 
			
		||||
          gradle :runIdeForUiTests > build/reports/idea.log &
 | 
			
		||||
      - name: Wait for Idea started
 | 
			
		||||
        uses: jtalk/url-health-check-action@v3
 | 
			
		||||
        uses: jtalk/url-health-check-action@1.5
 | 
			
		||||
        with:
 | 
			
		||||
          url: http://127.0.0.1:8082
 | 
			
		||||
          max-attempts: 20
 | 
			
		||||
@@ -37,19 +35,15 @@ jobs:
 | 
			
		||||
      - name: Tests
 | 
			
		||||
        run: gradle :testUi
 | 
			
		||||
      - name: Move video
 | 
			
		||||
        if: always()
 | 
			
		||||
        if: ${{ failure() }}
 | 
			
		||||
        run: mv video build/reports
 | 
			
		||||
      - name: Move sandbox logs
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: mv build/idea-sandbox/system/log sandbox-idea-log
 | 
			
		||||
      - name: Save report
 | 
			
		||||
        if: always()
 | 
			
		||||
        uses: actions/upload-artifact@v4
 | 
			
		||||
      - name: Save fails report
 | 
			
		||||
        if: ${{ failure() }}
 | 
			
		||||
        uses: actions/upload-artifact@v2
 | 
			
		||||
        with:
 | 
			
		||||
          name: ui-test-fails-report-mac
 | 
			
		||||
          path: |
 | 
			
		||||
            build/reports
 | 
			
		||||
            sandbox-idea-log
 | 
			
		||||
#  build-for-ui-test-linux:
 | 
			
		||||
#    runs-on: ubuntu-latest
 | 
			
		||||
#    steps:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.idea/copyright/IdeaVim.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/copyright/IdeaVim.xml
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
<component name="CopyrightManager">
 | 
			
		||||
  <copyright>
 | 
			
		||||
    <option name="notice" value="Copyright 2003-&#36;today.year The IdeaVim authors

Use of this source code is governed by an MIT-style
license that can be found in the LICENSE.txt file or at
https://opensource.org/licenses/MIT." />
 | 
			
		||||
    <option name="notice" value="Copyright 2003-2023 The IdeaVim authors

Use of this source code is governed by an MIT-style
license that can be found in the LICENSE.txt file or at
https://opensource.org/licenses/MIT." />
 | 
			
		||||
    <option name="myName" value="IdeaVim" />
 | 
			
		||||
  </copyright>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										16
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							@@ -5,13 +5,13 @@ object Constants {
 | 
			
		||||
  const val EAP_CHANNEL = "eap"
 | 
			
		||||
  const val DEV_CHANNEL = "Dev"
 | 
			
		||||
 | 
			
		||||
  const val GITHUB_TESTS = "2023.3.2"
 | 
			
		||||
  const val NVIM_TESTS = "2023.3.2"
 | 
			
		||||
  const val PROPERTY_TESTS = "2023.3.2"
 | 
			
		||||
  const val LONG_RUNNING_TESTS = "2023.3.2"
 | 
			
		||||
  const val QODANA_TESTS = "2023.3.2"
 | 
			
		||||
  const val RELEASE = "2023.3.2"
 | 
			
		||||
  const val 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"
 | 
			
		||||
 | 
			
		||||
  const val RELEASE_DEV = "2023.3.2"
 | 
			
		||||
  const val RELEASE_EAP = "2023.3.2"
 | 
			
		||||
  const val RELEASE_DEV = "2023.1.2"
 | 
			
		||||
  const val RELEASE_EAP = "2023.1.2"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							@@ -23,8 +23,9 @@ 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("2023.3", "<default>", version = "2023.3"))
 | 
			
		||||
  buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
			
		||||
 | 
			
		||||
  buildType(PropertyBased)
 | 
			
		||||
@@ -39,11 +40,6 @@ object Project : Project({
 | 
			
		||||
 | 
			
		||||
// Common build type for all configurations
 | 
			
		||||
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
 | 
			
		||||
  artifactRules = """
 | 
			
		||||
        +:build/reports => build/reports
 | 
			
		||||
        +:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
 | 
			
		||||
  init()
 | 
			
		||||
 | 
			
		||||
  requirements {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.teamcity/_Self/subprojects/OldTests.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.teamcity/_Self/subprojects/OldTests.kt
									
									
									
									
										vendored
									
									
								
							@@ -20,6 +20,4 @@ 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))
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,29 +0,0 @@
 | 
			
		||||
package patches.buildTypes
 | 
			
		||||
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.RelativeId
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.GradleBuildStep
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.buildSteps.gradle
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.changeBuildType
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.expectSteps
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.update
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
This patch script was generated by TeamCity on settings change in UI.
 | 
			
		||||
To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
 | 
			
		||||
accordingly, and delete the patch script.
 | 
			
		||||
*/
 | 
			
		||||
changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) {
 | 
			
		||||
    expectSteps {
 | 
			
		||||
        gradle {
 | 
			
		||||
            tasks = "clean test"
 | 
			
		||||
            buildFile = ""
 | 
			
		||||
            enableStacktrace = true
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    steps {
 | 
			
		||||
        update<GradleBuildStep>(0) {
 | 
			
		||||
            clearConditions()
 | 
			
		||||
            jdkHome = "/usr/lib/jvm/java-17-amazon-corretto"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								.teamcity/patches/projects/_Self.kts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.teamcity/patches/projects/_Self.kts
									
									
									
									
										vendored
									
									
								
							@@ -1,17 +0,0 @@
 | 
			
		||||
package patches.projects
 | 
			
		||||
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.Project
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
This patch script was generated by TeamCity on settings change in UI.
 | 
			
		||||
To apply the patch, change the root project
 | 
			
		||||
accordingly, and delete the patch script.
 | 
			
		||||
*/
 | 
			
		||||
changeProject(DslContext.projectId) {
 | 
			
		||||
    check(description == "Vim engine for IDEs based on the IntelliJ platform") {
 | 
			
		||||
        "Unexpected description: '$description'"
 | 
			
		||||
    }
 | 
			
		||||
    description = "Vim engine for JetBrains IDEs"
 | 
			
		||||
}
 | 
			
		||||
@@ -487,10 +487,6 @@ Contributors:
 | 
			
		||||
  [![icon][github]](https://github.com/pWydmuch)
 | 
			
		||||
   
 | 
			
		||||
  pWydmuch
 | 
			
		||||
* [![icon][mail]](mailto:leonid989@gmail.com)
 | 
			
		||||
  [![icon][github]](https://github.com/Infonautica)
 | 
			
		||||
   
 | 
			
		||||
  Leonid Danilov
 | 
			
		||||
 | 
			
		||||
Previous contributors:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										20
									
								
								CHANGES.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								CHANGES.md
									
									
									
									
									
								
							@@ -31,26 +31,6 @@ 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
 | 
			
		||||
* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
 | 
			
		||||
 | 
			
		||||
### Merged PRs:
 | 
			
		||||
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
 | 
			
		||||
* [772](https://github.com/JetBrains/ideavim/pull/772) by [chylex](https://github.com/chylex): Prevent code completion popup from appearing after running a macro
 | 
			
		||||
* [787](https://github.com/JetBrains/ideavim/pull/787) by [Leonid Danilov](https://github.com/Infonautica): Added "Which-Key" to Plugins
 | 
			
		||||
* [778](https://github.com/JetBrains/ideavim/pull/778) by [lippfi](https://github.com/lippfi): Showmode
 | 
			
		||||
* [788](https://github.com/JetBrains/ideavim/pull/788) by [Matt Ellis](https://github.com/citizenmatt): Refactor VimOptionGroupBase
 | 
			
		||||
 | 
			
		||||
## 2.7.0, 2023-11-07
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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 IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
 | 
			
		||||
    - Sync IJ bookmarks and Vim marks: `set ideamarks`
 | 
			
		||||
    - 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,8 +11,6 @@ plugins {
 | 
			
		||||
  kotlin("plugin.serialization") version "1.8.21"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val kotlinxSerializationVersion: String by project
 | 
			
		||||
 | 
			
		||||
group = "com.intellij"
 | 
			
		||||
version = "SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
@@ -21,10 +19,6 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16")
 | 
			
		||||
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
 | 
			
		||||
    // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
 | 
			
		||||
    exclude("org.jetbrains.kotlin", "kotlin-stdlib")
 | 
			
		||||
    exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
 | 
			
		||||
  }
 | 
			
		||||
  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")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,7 @@ enum class Mode(val abbrev: Char) {
 | 
			
		||||
  OP_PENDING('O'),
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Insert or Replace modes
 | 
			
		||||
   * Indicates this key mapping applies to Insert mode
 | 
			
		||||
   */
 | 
			
		||||
  INSERT('I'),
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -49,14 +49,14 @@ buildscript {
 | 
			
		||||
        classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
			
		||||
 | 
			
		||||
        // This is needed for jgit to connect to ssh
 | 
			
		||||
        classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
        classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
 | 
			
		||||
        classpath("org.kohsuke:github-api:1.305")
 | 
			
		||||
 | 
			
		||||
        classpath("io.ktor:ktor-client-core:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-client-cio:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-client-auth:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-client-core:2.3.6")
 | 
			
		||||
        classpath("io.ktor:ktor-client-cio:2.3.5")
 | 
			
		||||
        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")
 | 
			
		||||
 | 
			
		||||
        // This comes from the changelog plugin
 | 
			
		||||
//        classpath("org.jetbrains:markdown:0.3.1")
 | 
			
		||||
@@ -69,7 +69,7 @@ plugins {
 | 
			
		||||
    kotlin("jvm") version "1.8.21"
 | 
			
		||||
    application
 | 
			
		||||
 | 
			
		||||
    id("org.jetbrains.intellij") version "1.16.1"
 | 
			
		||||
    id("org.jetbrains.intellij") version "1.16.0"
 | 
			
		||||
    id("org.jetbrains.changelog") version "2.2.0"
 | 
			
		||||
 | 
			
		||||
    // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
 | 
			
		||||
@@ -116,7 +116,7 @@ repositories {
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
 | 
			
		||||
    compileOnly("org.jetbrains:annotations:24.1.0")
 | 
			
		||||
    compileOnly("org.jetbrains:annotations:24.0.1")
 | 
			
		||||
 | 
			
		||||
    // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
 | 
			
		||||
    testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
 | 
			
		||||
@@ -126,11 +126,11 @@ dependencies {
 | 
			
		||||
    testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
 | 
			
		||||
 | 
			
		||||
    // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
 | 
			
		||||
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
 | 
			
		||||
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
 | 
			
		||||
 | 
			
		||||
    testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
 | 
			
		||||
    testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
 | 
			
		||||
    testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
 | 
			
		||||
    testImplementation("com.automation-remarks:video-recorder-junit:2.0")
 | 
			
		||||
    runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
 | 
			
		||||
    antlr("org.antlr:antlr4:$antlrVersion")
 | 
			
		||||
 | 
			
		||||
@@ -141,7 +141,7 @@ dependencies {
 | 
			
		||||
 | 
			
		||||
    testApi("com.squareup.okhttp3:okhttp:4.12.0")
 | 
			
		||||
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
 | 
			
		||||
}
 | 
			
		||||
@@ -184,14 +184,6 @@ tasks {
 | 
			
		||||
        include("**/*test.class")
 | 
			
		||||
        include("**/*Tests.class")
 | 
			
		||||
        exclude("**/ParserTest.class")
 | 
			
		||||
 | 
			
		||||
        // Set teamcity env variable locally to run additional tests for leaks.
 | 
			
		||||
        // By default, this test runs on TC only, but this test doesn't take a lot of time,
 | 
			
		||||
        //   so we can turn it on for local development
 | 
			
		||||
        if (environment["TEAMCITY_VERSION"] == null) {
 | 
			
		||||
            println("Set env TEAMCITY_VERSION to X")
 | 
			
		||||
            environment("TEAMCITY_VERSION" to "X")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val testWithNeovim by getting(Test::class) {
 | 
			
		||||
@@ -302,7 +294,6 @@ tasks {
 | 
			
		||||
        systemProperty("ide.mac.message.dialogs.as.sheets", "false")
 | 
			
		||||
        systemProperty("jb.privacy.policy.text", "<!--999.999-->")
 | 
			
		||||
        systemProperty("jb.consents.confirmation.enabled", "false")
 | 
			
		||||
        systemProperty("ide.show.tips.on.startup.default.value", "false")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    runPluginVerifier {
 | 
			
		||||
@@ -353,6 +344,8 @@ 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 {
 | 
			
		||||
@@ -531,12 +524,10 @@ tasks.register("releaseActions") {
 | 
			
		||||
        if (tickets.isNotEmpty()) {
 | 
			
		||||
            println("Updating statuses for tickets: $tickets")
 | 
			
		||||
            setYoutrackStatus(tickets, "Fixed")
 | 
			
		||||
            println("Checking if version $version exists...")
 | 
			
		||||
            val versionId = getVersionIdByName(version.toString())
 | 
			
		||||
            if (versionId == null) {
 | 
			
		||||
            if (getVersionIdByName(version.toString()) != null) {
 | 
			
		||||
                addReleaseToYoutrack(version.toString())
 | 
			
		||||
            } else {
 | 
			
		||||
                println("Version $version already exists in YouTrack. Version id: $versionId")
 | 
			
		||||
                println("Version $version is already exists in YouTrack")
 | 
			
		||||
            }
 | 
			
		||||
            setYoutrackFixVersion(tickets, version.toString())
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -396,19 +396,3 @@ Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
 | 
			
		||||
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
<summary><h2>Which-Key</h2></summary>
 | 
			
		||||
 | 
			
		||||
Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
 | 
			
		||||
 | 
			
		||||
### Setup:
 | 
			
		||||
- Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin.
 | 
			
		||||
- Add the following command to `~/.ideavimrc`: `set which-key`
 | 
			
		||||
 | 
			
		||||
### Instructions
 | 
			
		||||
 | 
			
		||||
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,11 +3,6 @@ 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:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -8,26 +8,21 @@
 | 
			
		||||
 | 
			
		||||
# suppress inspection "UnusedProperty" for whole file
 | 
			
		||||
 | 
			
		||||
ideaVersion=2023.3.2
 | 
			
		||||
ideaVersion=2023.2
 | 
			
		||||
downloadIdeaSources=true
 | 
			
		||||
instrumentPluginCode=true
 | 
			
		||||
version=chylex-24
 | 
			
		||||
version=chylex-21
 | 
			
		||||
javaVersion=17
 | 
			
		||||
remoteRobotVersion=0.11.21
 | 
			
		||||
remoteRobotVersion=0.11.17
 | 
			
		||||
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,17 +20,17 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
 | 
			
		||||
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-cio:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-client-cio:2.3.5")
 | 
			
		||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.6")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
			
		||||
 | 
			
		||||
  // This is needed for jgit to connect to ssh
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
 | 
			
		||||
  implementation("com.vdurmont:semver4j:3.1.0")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
 | 
			
		||||
  println("HI!")
 | 
			
		||||
  val projectDir = args[0]
 | 
			
		||||
  println("Working directory: $projectDir")
 | 
			
		||||
  val (lastVersion, objectId) = getVersion(projectDir, ReleaseType.STABLE_NO_PATCH)
 | 
			
		||||
  val (lastVersion, objectId) = getVersion(projectDir, onlyStable = true)
 | 
			
		||||
  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, ReleaseType.ANY)
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = false)
 | 
			
		||||
 | 
			
		||||
  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, ReleaseType.ONLY_STABLE)
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = true)
 | 
			
		||||
 | 
			
		||||
  val nextVersion = when (releaseType) {
 | 
			
		||||
    "major" -> lastVersion.nextMajor()
 | 
			
		||||
 
 | 
			
		||||
@@ -58,13 +58,7 @@ internal fun checkBranch(rootDir: String, releaseType: String) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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> {
 | 
			
		||||
internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, ObjectId> {
 | 
			
		||||
  val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
 | 
			
		||||
  val git = Git(repository)
 | 
			
		||||
  println(git.log().call().first())
 | 
			
		||||
@@ -81,10 +75,10 @@ internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semv
 | 
			
		||||
  }
 | 
			
		||||
    .sortedBy { it.first }
 | 
			
		||||
 | 
			
		||||
  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 }
 | 
			
		||||
  val version = if (onlyStable) {
 | 
			
		||||
    versions.last { it.first.isStable }
 | 
			
		||||
  } else {
 | 
			
		||||
    versions.last()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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.ProjectActivity
 | 
			
		||||
import com.intellij.openapi.startup.StartupActivity
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.localEditors
 | 
			
		||||
@@ -20,11 +20,16 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
/**
 | 
			
		||||
 * @author Alex Plate
 | 
			
		||||
 */
 | 
			
		||||
internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
 | 
			
		||||
// 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*/ {
 | 
			
		||||
 | 
			
		||||
  private var firstInitializationOccurred = false
 | 
			
		||||
 | 
			
		||||
  override suspend fun execute(project: Project) {
 | 
			
		||||
  override fun runActivity(project: Project) {
 | 
			
		||||
    if (firstInitializationOccurred) return
 | 
			
		||||
    firstInitializationOccurred = true
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -219,10 +219,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
    return getInstance().enabled;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static boolean isNotEnabled() {
 | 
			
		||||
    return !isEnabled();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static void setEnabled(final boolean enabled) {
 | 
			
		||||
    if (isEnabled() == enabled) return;
 | 
			
		||||
 | 
			
		||||
@@ -236,13 +232,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
      getInstance().turnOnPlugin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
 | 
			
		||||
    } else {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    StatusBarIconFactory.Util.INSTANCE.updateIcon();
 | 
			
		||||
    StatusBarIconFactory.Companion.updateIcon();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static String getMessage() {
 | 
			
		||||
@@ -274,8 +264,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      Application application = ApplicationManager.getApplication();
 | 
			
		||||
      if (application.isUnitTestMode()) {
 | 
			
		||||
        turnOnPlugin();
 | 
			
		||||
        //application.invokeAndWait(this::turnOnPlugin);
 | 
			
		||||
        application.invokeAndWait(this::turnOnPlugin);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        application.invokeLater(this::turnOnPlugin);
 | 
			
		||||
 
 | 
			
		||||
@@ -28,11 +28,8 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * Accepts all regular keystrokes and passes them on to the Vim key handler.
 | 
			
		||||
 *
 | 
			
		||||
 * IDE shortcut keys used by Vim commands are handled by [com.maddyhome.idea.vim.action.VimShortcutKeyAction].
 | 
			
		||||
 *
 | 
			
		||||
 * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
 | 
			
		||||
 *   way to get ideavim keys for this plugin. See VIM-3085
 | 
			
		||||
 */
 | 
			
		||||
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
  private val handler = KeyHandler.getInstance()
 | 
			
		||||
  private val traceTime = injector.globalOptions().ideatracetime
 | 
			
		||||
 | 
			
		||||
@@ -89,7 +86,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  internal companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
    private val LOG = logger<VimTypedActionHandler>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,74 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.hint.HintManagerImpl
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionManager
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.PerformWithDocumentsCommitted
 | 
			
		||||
import com.intellij.openapi.actionSystem.PopupAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.impl.ActionConfigurationCustomizer
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.EditorMouseHoverPopupManager
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorMouseEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorMouseEventArea
 | 
			
		||||
import com.intellij.openapi.project.DumbAware
 | 
			
		||||
import java.awt.event.MouseEvent
 | 
			
		||||
 | 
			
		||||
// [VERSION UPDATE] 233+ Remove class
 | 
			
		||||
// The ShowHoverInfo action is built into the platform (using a nicer EditorMouseHoverPopupManager API)
 | 
			
		||||
public class VimActionConfigurationCustomizer : ActionConfigurationCustomizer {
 | 
			
		||||
  public override fun customize(actionManager: ActionManager) {
 | 
			
		||||
    // If the ShowHoverInfo action doesn't exist in the platform, add our own implementation
 | 
			
		||||
    if (actionManager.getAction("ShowHoverInfo") == null) {
 | 
			
		||||
      actionManager.registerAction("ShowHoverInfo", VimShowHoverInfoAction())
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class VimShowHoverInfoAction : AnAction(), HintManagerImpl.ActionToIgnore, PopupAction, DumbAware,
 | 
			
		||||
    PerformWithDocumentsCommitted {
 | 
			
		||||
    override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
 | 
			
		||||
 | 
			
		||||
    override fun update(e: AnActionEvent) {
 | 
			
		||||
      val dataContext = e.dataContext
 | 
			
		||||
      val editor = CommonDataKeys.EDITOR.getData(dataContext)
 | 
			
		||||
      if (editor == null) {
 | 
			
		||||
        e.presentation.isEnabledAndVisible = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
      val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return
 | 
			
		||||
 | 
			
		||||
      val editorMouseEvent = createFakeEditorMouseEvent(editor)
 | 
			
		||||
      EditorMouseHoverPopupManager.getInstance().showInfoTooltip(editorMouseEvent)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createFakeEditorMouseEvent(editor: Editor): EditorMouseEvent {
 | 
			
		||||
      val xy = editor.offsetToXY(editor.caretModel.offset)
 | 
			
		||||
      val mouseEvent =
 | 
			
		||||
        MouseEvent(editor.component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, xy.x, xy.y, 0, false)
 | 
			
		||||
      val editorMouseEvent = EditorMouseEvent(
 | 
			
		||||
        editor,
 | 
			
		||||
        mouseEvent,
 | 
			
		||||
        EditorMouseEventArea.EDITING_AREA,
 | 
			
		||||
        editor.caretModel.offset,
 | 
			
		||||
        editor.caretModel.logicalPosition,
 | 
			
		||||
        editor.caretModel.visualPosition,
 | 
			
		||||
        true,
 | 
			
		||||
        null,
 | 
			
		||||
        null,
 | 
			
		||||
        null
 | 
			
		||||
      )
 | 
			
		||||
      return editorMouseEvent
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,10 +14,14 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionWrapper
 | 
			
		||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
			
		||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.application.invokeLater
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.EditorActionManager
 | 
			
		||||
import com.intellij.openapi.keymap.KeymapManager
 | 
			
		||||
import com.intellij.openapi.progress.ProcessCanceledException
 | 
			
		||||
import com.intellij.openapi.project.DumbAware
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
@@ -54,17 +58,9 @@ import javax.swing.KeyStroke
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 * These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
 | 
			
		||||
 *
 | 
			
		||||
 * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
 | 
			
		||||
 *   way to get ideavim keys for this plugin. See VIM-3085
 | 
			
		||||
 */
 | 
			
		||||
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
  private val traceTime: Boolean
 | 
			
		||||
    get() {
 | 
			
		||||
      // Make sure the injector is initialized
 | 
			
		||||
      VimPlugin.getInstance()
 | 
			
		||||
      return injector.globalOptions().ideatracetime
 | 
			
		||||
    }
 | 
			
		||||
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
  private val traceTime = injector.globalOptions().ideatracetime
 | 
			
		||||
 | 
			
		||||
  override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
    LOG.trace("Executing shortcut key action")
 | 
			
		||||
@@ -98,7 +94,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
 | 
			
		||||
  // There is a chance that we can use BGT, but we call for isCell inside the update.
 | 
			
		||||
  // Not sure if can can use BGT with this call. Let's use EDT for now.
 | 
			
		||||
  override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
 | 
			
		||||
  override fun getActionUpdateThread() = ActionUpdateThread.EDT
 | 
			
		||||
 | 
			
		||||
  override fun update(e: AnActionEvent) {
 | 
			
		||||
    val start = if (traceTime) System.currentTimeMillis() else null
 | 
			
		||||
@@ -113,7 +109,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
 | 
			
		||||
    if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
 | 
			
		||||
    val editor = getEditor(e)
 | 
			
		||||
    if (editor != null && keyStroke != null) {
 | 
			
		||||
      if (isOctopusEnabled(keyStroke, editor)) {
 | 
			
		||||
@@ -167,6 +163,14 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
        return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val nextTemplateVariableShortcuts = KeymapManager.getInstance().activeKeymap.getShortcuts(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
 | 
			
		||||
      if (nextTemplateVariableShortcuts.any { it is KeyboardShortcut && it.firstKeyStroke == keyStroke }) {
 | 
			
		||||
        val handler = EditorActionManager.getInstance().getActionHandler(IdeActions.ACTION_EDITOR_NEXT_TEMPLATE_VARIABLE)
 | 
			
		||||
        if (handler.isEnabled(editor, null, e.dataContext)) {
 | 
			
		||||
          return ActionEnableStatus.no("Next template variable or finish in-place refactoring", LogLevel.INFO)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      
 | 
			
		||||
      if (editor.inInsertMode) {
 | 
			
		||||
        if (keyCode == KeyEvent.VK_TAB) {
 | 
			
		||||
          // TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
 | 
			
		||||
@@ -232,9 +236,9 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
  /**
 | 
			
		||||
   * getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
 | 
			
		||||
   * but we should cache the value because on the second call (isEnabled -> actionPerformed)
 | 
			
		||||
   * the event is already consumed and getDefaultKeyStroke returns null
 | 
			
		||||
   * the event is already consumed
 | 
			
		||||
   */
 | 
			
		||||
  private var keyStrokeCache: Pair<Long?, KeyStroke?> = null to null
 | 
			
		||||
  private var keyStrokeCache: Pair<KeyEvent?, KeyStroke?> = null to null
 | 
			
		||||
 | 
			
		||||
  private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
 | 
			
		||||
    val inputEvent = e.inputEvent
 | 
			
		||||
@@ -242,9 +246,9 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
      val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
 | 
			
		||||
      val strokeCache = keyStrokeCache
 | 
			
		||||
      if (defaultKeyStroke != null) {
 | 
			
		||||
        keyStrokeCache = inputEvent.`when` to defaultKeyStroke
 | 
			
		||||
        keyStrokeCache = inputEvent to defaultKeyStroke
 | 
			
		||||
        return defaultKeyStroke
 | 
			
		||||
      } else if (strokeCache.first == inputEvent.`when`) {
 | 
			
		||||
      } else if (strokeCache.first === inputEvent) {
 | 
			
		||||
        keyStrokeCache = null to null
 | 
			
		||||
        return strokeCache.second
 | 
			
		||||
      }
 | 
			
		||||
@@ -277,7 +281,7 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
      .toSet()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  internal companion object {
 | 
			
		||||
  companion object {
 | 
			
		||||
    @JvmField
 | 
			
		||||
    val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
 | 
			
		||||
      ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,9 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
			
		||||
import com.maddyhome.idea.vim.command.Argument
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.common.argumentCaptured
 | 
			
		||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
			
		||||
@@ -26,9 +28,10 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
// todo make it multicaret
 | 
			
		||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
 | 
			
		||||
@@ -101,6 +104,8 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,13 @@ import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
@@ -26,6 +29,8 @@ import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeForAllCarets(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,10 +14,13 @@ import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
@@ -26,6 +29,8 @@ import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeForAllCarets(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
 | 
			
		||||
    get() {
 | 
			
		||||
      val myMode = machine.mode
 | 
			
		||||
      return when (myMode) {
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
        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
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@ package com.maddyhome.idea.vim.extension
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
@@ -18,6 +17,7 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.MappingMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.CommandAlias
 | 
			
		||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
 | 
			
		||||
@@ -26,7 +26,6 @@ import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.key.MappingOwner
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ModalEntry
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
@@ -39,9 +38,6 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
public object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  private val LOG = logger<VimExtensionFacade>()
 | 
			
		||||
 | 
			
		||||
  /** The 'map' command for mapping keys to handlers defined in extensions. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun putExtensionHandlerMapping(
 | 
			
		||||
@@ -144,12 +140,10 @@ public object VimExtensionFacade {
 | 
			
		||||
  public fun inputKeyStroke(editor: Editor): KeyStroke {
 | 
			
		||||
    if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
 | 
			
		||||
      val input = Extension.consumeKeystroke()
 | 
			
		||||
      LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
 | 
			
		||||
      return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
      LOG.trace("Unit test mode is active")
 | 
			
		||||
      val mappingStack = KeyHandler.getInstance().keyStack
 | 
			
		||||
      mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
 | 
			
		||||
        if (editor.vim.vimStateMachine.isRecording) {
 | 
			
		||||
@@ -157,13 +151,11 @@ public object VimExtensionFacade {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      LOG.trace("Getting char from the modal entry...")
 | 
			
		||||
      var ref: KeyStroke? = null
 | 
			
		||||
      ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
 | 
			
		||||
        ref = stroke
 | 
			
		||||
        false
 | 
			
		||||
      }
 | 
			
		||||
      LOG.trace("Got char $ref")
 | 
			
		||||
      ref
 | 
			
		||||
    }
 | 
			
		||||
    val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
 | 
			
		||||
 
 | 
			
		||||
@@ -156,6 +156,11 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
  private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
 | 
			
		||||
    override val isRepeatable = true
 | 
			
		||||
 | 
			
		||||
    // In this operator we process selection by ourselves. This is necessary for rider, VIM-1758
 | 
			
		||||
    override fun postProcessSelection(): Boolean {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      setOperatorFunction(this)
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
			
		||||
 
 | 
			
		||||
@@ -217,8 +217,6 @@ private object FileTypePatterns {
 | 
			
		||||
 | 
			
		||||
    return if (fileTypeName in htmlLikeFileTypes) {
 | 
			
		||||
      this.htmlPatterns
 | 
			
		||||
    } else if (fileTypeName == "JAVA" || fileExtension == "java") {
 | 
			
		||||
      this.javaPatterns
 | 
			
		||||
    } else if (fileTypeName == "Ruby" || fileExtension == "rb") {
 | 
			
		||||
      this.rubyPatterns
 | 
			
		||||
    } else if (fileTypeName == "RHTML" || fileExtension == "erb") {
 | 
			
		||||
@@ -244,7 +242,6 @@ private object FileTypePatterns {
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  private val htmlPatterns = createHtmlPatterns()
 | 
			
		||||
  private val javaPatterns = createJavaPatterns()
 | 
			
		||||
  private val rubyPatterns = createRubyPatterns()
 | 
			
		||||
  private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
 | 
			
		||||
  private val phpPatterns = createPhpPatterns()
 | 
			
		||||
@@ -273,14 +270,6 @@ private object FileTypePatterns {
 | 
			
		||||
        LanguagePatterns(linkedMapOf(openingTagPattern to htmlSearchPair), linkedMapOf(closingTagPattern to htmlSearchPair))
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  private fun createJavaPatterns(): LanguagePatterns {
 | 
			
		||||
    return (
 | 
			
		||||
        LanguagePatterns("\\b(?<!else\\s+)if\\b", "\\belse\\s+if\\b", "\\belse(?!\\s+if)\\b") +
 | 
			
		||||
          LanguagePatterns("\\bdo\\b", "\\bwhile\\b") +
 | 
			
		||||
          LanguagePatterns("\\btry\\b", "\\bcatch\\b", "\\bfinally\\b")
 | 
			
		||||
      )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun createRubyPatterns(): LanguagePatterns {
 | 
			
		||||
    // Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,6 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.surround
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.runWriteAction
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -256,7 +255,6 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      // Deleting surround is just changing the surrounding to "nothing"
 | 
			
		||||
      val charFrom = getChar(editor.ij)
 | 
			
		||||
      LOG.debug("DSurroundHandler: charFrom = $charFrom")
 | 
			
		||||
      if (charFrom.code == 0) return
 | 
			
		||||
 | 
			
		||||
      runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
 | 
			
		||||
@@ -309,101 +307,96 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private val LOG = logger<VimSurroundExtension>()
 | 
			
		||||
  companion object {
 | 
			
		||||
    private const val REGISTER = '"'
 | 
			
		||||
 | 
			
		||||
private const val REGISTER = '"'
 | 
			
		||||
    private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
 | 
			
		||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
 | 
			
		||||
private val SURROUND_PAIRS = mapOf(
 | 
			
		||||
  'b' to ("(" to ")"),
 | 
			
		||||
  '(' to ("( " to " )"),
 | 
			
		||||
  ')' to ("(" to ")"),
 | 
			
		||||
  'B' to ("{" to "}"),
 | 
			
		||||
  '{' to ("{ " to " }"),
 | 
			
		||||
  '}' to ("{" to "}"),
 | 
			
		||||
  'r' to ("[" to "]"),
 | 
			
		||||
  '[' to ("[ " to " ]"),
 | 
			
		||||
  ']' to ("[" to "]"),
 | 
			
		||||
  'a' to ("<" to ">"),
 | 
			
		||||
  '>' to ("<" to ">"),
 | 
			
		||||
  's' to (" " to ""),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
 | 
			
		||||
  SURROUND_PAIRS[c]
 | 
			
		||||
} else if (!c.isLetter()) {
 | 
			
		||||
  val s = c.toString()
 | 
			
		||||
  s to s
 | 
			
		||||
} else {
 | 
			
		||||
  null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
			
		||||
  val tagInput = inputString(editor, "<", '>')
 | 
			
		||||
  val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
 | 
			
		||||
  return if (matcher.find()) {
 | 
			
		||||
    val tagName = matcher.group(1)
 | 
			
		||||
    val tagAttributes = matcher.group(2)
 | 
			
		||||
    "<$tagName$tagAttributes>" to "</$tagName>"
 | 
			
		||||
  } else {
 | 
			
		||||
    null
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun inputFunctionName(
 | 
			
		||||
  editor: Editor,
 | 
			
		||||
  withInternalSpaces: Boolean,
 | 
			
		||||
): Pair<String, String>? {
 | 
			
		||||
  val functionNameInput = inputString(editor, "function: ", null)
 | 
			
		||||
  if (functionNameInput.isEmpty()) return null
 | 
			
		||||
  return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
 | 
			
		||||
  '<', 't' -> inputTagPair(editor)
 | 
			
		||||
  'f' -> inputFunctionName(editor, false)
 | 
			
		||||
  'F' -> inputFunctionName(editor, true)
 | 
			
		||||
  else -> getSurroundPair(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun getChar(editor: Editor): Char {
 | 
			
		||||
  val key = inputKeyStroke(editor)
 | 
			
		||||
  val keyChar = key.keyChar
 | 
			
		||||
  val res = if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
 | 
			
		||||
    0.toChar()
 | 
			
		||||
  } else {
 | 
			
		||||
    keyChar
 | 
			
		||||
  }
 | 
			
		||||
  LOG.trace("getChar: $res")
 | 
			
		||||
  return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
 | 
			
		||||
  runWriteAction {
 | 
			
		||||
    val editor = caret.editor
 | 
			
		||||
    val change = VimPlugin.getChange()
 | 
			
		||||
    val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
 | 
			
		||||
 | 
			
		||||
    val isEOF = range.endOffset == editor.text().length
 | 
			
		||||
    val hasNewLine = editor.endsWithNewLine()
 | 
			
		||||
    val rightSurround = (if (tagsOnNewLines) {
 | 
			
		||||
      if (isEOF && !hasNewLine) {
 | 
			
		||||
        "\n" + pair.second
 | 
			
		||||
      } else {
 | 
			
		||||
        pair.second + "\n"
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      pair.second
 | 
			
		||||
    }).let { RepeatedCharSequence.of(it, count) }
 | 
			
		||||
 | 
			
		||||
    change.insertText(editor, caret, range.startOffset, leftSurround)
 | 
			
		||||
    change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
 | 
			
		||||
    injector.markService.setChangeMarks(
 | 
			
		||||
      caret,
 | 
			
		||||
      TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length)
 | 
			
		||||
    private val SURROUND_PAIRS = mapOf(
 | 
			
		||||
      'b' to ("(" to ")"),
 | 
			
		||||
      '(' to ("( " to " )"),
 | 
			
		||||
      ')' to ("(" to ")"),
 | 
			
		||||
      'B' to ("{" to "}"),
 | 
			
		||||
      '{' to ("{ " to " }"),
 | 
			
		||||
      '}' to ("{" to "}"),
 | 
			
		||||
      'r' to ("[" to "]"),
 | 
			
		||||
      '[' to ("[ " to " ]"),
 | 
			
		||||
      ']' to ("[" to "]"),
 | 
			
		||||
      'a' to ("<" to ">"),
 | 
			
		||||
      '>' to ("<" to ">"),
 | 
			
		||||
      's' to (" " to ""),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
 | 
			
		||||
      SURROUND_PAIRS[c]
 | 
			
		||||
    } else if (!c.isLetter()) {
 | 
			
		||||
      val s = c.toString()
 | 
			
		||||
      s to s
 | 
			
		||||
    } else {
 | 
			
		||||
      null
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
			
		||||
      val tagInput = inputString(editor, "<", '>')
 | 
			
		||||
      val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
 | 
			
		||||
      return if (matcher.find()) {
 | 
			
		||||
        val tagName = matcher.group(1)
 | 
			
		||||
        val tagAttributes = matcher.group(2)
 | 
			
		||||
        "<$tagName$tagAttributes>" to "</$tagName>"
 | 
			
		||||
      } else {
 | 
			
		||||
        null
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun inputFunctionName(
 | 
			
		||||
      editor: Editor,
 | 
			
		||||
      withInternalSpaces: Boolean,
 | 
			
		||||
    ): Pair<String, String>? {
 | 
			
		||||
      val functionNameInput = inputString(editor, "function: ", null)
 | 
			
		||||
      if (functionNameInput.isEmpty()) return null
 | 
			
		||||
      return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
 | 
			
		||||
      '<', 't' -> inputTagPair(editor)
 | 
			
		||||
      'f' -> inputFunctionName(editor, false)
 | 
			
		||||
      'F' -> inputFunctionName(editor, true)
 | 
			
		||||
      else -> getSurroundPair(c)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getChar(editor: Editor): Char {
 | 
			
		||||
      val key = inputKeyStroke(editor)
 | 
			
		||||
      val keyChar = key.keyChar
 | 
			
		||||
      return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
 | 
			
		||||
        0.toChar()
 | 
			
		||||
      } else {
 | 
			
		||||
        keyChar
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
 | 
			
		||||
      runWriteAction {
 | 
			
		||||
        val editor = caret.editor
 | 
			
		||||
        val change = VimPlugin.getChange()
 | 
			
		||||
        val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
 | 
			
		||||
 | 
			
		||||
        val isEOF = range.endOffset == editor.text().length
 | 
			
		||||
        val hasNewLine = editor.endsWithNewLine()
 | 
			
		||||
        val rightSurround = (if (tagsOnNewLines) {
 | 
			
		||||
          if (isEOF && !hasNewLine) {
 | 
			
		||||
            "\n" + pair.second
 | 
			
		||||
          } else {
 | 
			
		||||
            pair.second + "\n"
 | 
			
		||||
          }
 | 
			
		||||
        } else {
 | 
			
		||||
          pair.second
 | 
			
		||||
        }).let { RepeatedCharSequence.of(it, count) }
 | 
			
		||||
 | 
			
		||||
        change.insertText(editor, caret, range.startOffset, leftSurround)
 | 
			
		||||
        change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
 | 
			
		||||
        injector.markService.setChangeMarks(caret, TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,9 @@ import com.intellij.openapi.editor.actions.EnterAction
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorMouseEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorMouseListener
 | 
			
		||||
import com.intellij.openapi.editor.impl.TextRangeInterval
 | 
			
		||||
import com.intellij.openapi.ui.MessageType
 | 
			
		||||
import com.intellij.openapi.ui.popup.Balloon
 | 
			
		||||
import com.intellij.openapi.ui.popup.JBPopupFactory
 | 
			
		||||
import com.intellij.openapi.util.UserDataHolder
 | 
			
		||||
import com.intellij.openapi.util.text.StringUtil
 | 
			
		||||
import com.intellij.psi.codeStyle.CodeStyleManager
 | 
			
		||||
@@ -62,6 +65,7 @@ import com.maddyhome.idea.vim.helper.endOffsetInclusive
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inInsertMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.icons.VimIcons
 | 
			
		||||
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimInsertListener
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
 | 
			
		||||
@@ -78,12 +82,14 @@ import java.math.BigInteger
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.function.Consumer
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
import kotlin.math.min
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides all the insert/replace related functionality
 | 
			
		||||
 */
 | 
			
		||||
public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
  private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
 | 
			
		||||
  private var lastShownTime = 0L
 | 
			
		||||
  private val listener: EditorMouseListener = object : EditorMouseListener {
 | 
			
		||||
    override fun mouseClicked(event: EditorMouseEvent) {
 | 
			
		||||
      val editor = event.editor
 | 
			
		||||
@@ -97,6 +103,10 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun editorReleased(editor: Editor?) {
 | 
			
		||||
    EventFacade.getInstance().removeEditorMouseListener(editor!!, listener)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
 | 
			
		||||
    val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
    val ijContext = context.ij
 | 
			
		||||
@@ -394,7 +404,6 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
    range: TextRange,
 | 
			
		||||
  ) {
 | 
			
		||||
    val startPos = editor.offsetToBufferPosition(caret.offset.point)
 | 
			
		||||
    val startOffset = editor.getLineStartForOffset(range.startOffset)
 | 
			
		||||
    val endOffset = editor.getLineEndForOffset(range.endOffset)
 | 
			
		||||
    val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
@@ -419,7 +428,11 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    val afterAction = {
 | 
			
		||||
      caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
 | 
			
		||||
      val firstLine = editor.offsetToBufferPosition(
 | 
			
		||||
        min(startOffset.toDouble(), endOffset.toDouble()).toInt()
 | 
			
		||||
      ).line
 | 
			
		||||
      val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
 | 
			
		||||
      caret.moveToOffset(newOffset)
 | 
			
		||||
      restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
 | 
			
		||||
    }
 | 
			
		||||
    if (project != null) {
 | 
			
		||||
@@ -632,6 +645,25 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    avalanche: Boolean,
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
 | 
			
		||||
    // Just an easter egg
 | 
			
		||||
    if (avalanche) {
 | 
			
		||||
      val currentTime = System.currentTimeMillis()
 | 
			
		||||
      if (currentTime - lastShownTime > 60000) {
 | 
			
		||||
        lastShownTime = currentTime
 | 
			
		||||
        ApplicationManager.getApplication().invokeLater {
 | 
			
		||||
          val balloon = JBPopupFactory.getInstance()
 | 
			
		||||
            .createHtmlTextBalloonBuilder(
 | 
			
		||||
              "Wow, nice vim skills!", VimIcons.IDEAVIM,
 | 
			
		||||
              MessageType.INFO.titleForeground, MessageType.INFO.popupBackground,
 | 
			
		||||
              null
 | 
			
		||||
            ).createBalloon()
 | 
			
		||||
          balloon.show(
 | 
			
		||||
            JBPopupFactory.getInstance().guessBestPopupLocation((editor as IjVimEditor).editor),
 | 
			
		||||
            Balloon.Position.below
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    val nf: List<String> = injector.options(editor).nrformats
 | 
			
		||||
    val alpha = nf.contains("alpha")
 | 
			
		||||
    val hex = nf.contains("hex")
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,6 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
 | 
			
		||||
  public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
 | 
			
		||||
  public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
 | 
			
		||||
  public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
 | 
			
		||||
  public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
 | 
			
		||||
  public var colorfulmodewidget: Boolean by optionProperty(IjOptions.colorfulmodewidget)
 | 
			
		||||
 | 
			
		||||
  // Temporary options to control work-in-progress behaviour
 | 
			
		||||
  public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
 | 
			
		||||
 
 | 
			
		||||
@@ -86,8 +86,6 @@ public object IjOptions {
 | 
			
		||||
  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
 | 
			
		||||
  public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
 | 
			
		||||
  public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
 | 
			
		||||
  public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
 | 
			
		||||
  public val colorfulmodewidget: ToggleOption = addOption(ToggleOption("colorfulmodewidget", GLOBAL, "colorfulmodewidget", false, isTemporary = true))
 | 
			
		||||
 | 
			
		||||
  // This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
 | 
			
		||||
  // derives from Option<VimInt>
 | 
			
		||||
 
 | 
			
		||||
@@ -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.getModifiers() == 0) &&
 | 
			
		||||
          !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
 | 
			
		||||
          key.getKeyCode() != KeyEvent.VK_ESCAPE &&
 | 
			
		||||
          key.getKeyCode() != KeyEvent.VK_ENTER) {
 | 
			
		||||
        getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -1,68 +0,0 @@
 | 
			
		||||
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,8 +7,6 @@
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
@@ -21,7 +19,6 @@ 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
 | 
			
		||||
@@ -64,36 +61,22 @@ internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
    try {
 | 
			
		||||
      myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
 | 
			
		||||
      val runnable = runnable@{
 | 
			
		||||
        try {
 | 
			
		||||
          // Handle one keystroke then queue up the next key
 | 
			
		||||
          for (i in 0 until total) {
 | 
			
		||||
        // Handle one keystroke then queue up the next key
 | 
			
		||||
        for (i in 0 until total) {
 | 
			
		||||
          myPotemkinProgress.fraction = (i + 1).toDouble() / total
 | 
			
		||||
          while (keyStack.hasStroke()) {
 | 
			
		||||
            val key = keyStack.feedStroke()
 | 
			
		||||
            try {
 | 
			
		||||
              myPotemkinProgress.fraction = (i + 1).toDouble() / total
 | 
			
		||||
              while (keyStack.hasStroke()) {
 | 
			
		||||
                val key = keyStack.feedStroke()
 | 
			
		||||
                try {
 | 
			
		||||
                  myPotemkinProgress.checkCanceled()
 | 
			
		||||
                } catch (e: ProcessCanceledException) {
 | 
			
		||||
                  return@runnable
 | 
			
		||||
                }
 | 
			
		||||
                ProgressManager.getInstance().executeNonCancelableSection {
 | 
			
		||||
                  // Prevent autocompletion during macros.
 | 
			
		||||
                  // See https://github.com/JetBrains/ideavim/pull/772 for details
 | 
			
		||||
                  CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
 | 
			
		||||
                  getInstance().handleKey(editor, key, context)
 | 
			
		||||
                }
 | 
			
		||||
                if (injector.messages.isError()) return@runnable
 | 
			
		||||
              }
 | 
			
		||||
            } finally {
 | 
			
		||||
              keyStack.resetFirst()
 | 
			
		||||
              myPotemkinProgress.checkCanceled()
 | 
			
		||||
            } catch (e: ProcessCanceledException) {
 | 
			
		||||
              return@runnable
 | 
			
		||||
            }
 | 
			
		||||
            ProgressManager.getInstance().executeNonCancelableSection { getInstance().handleKey(editor, key, context) }
 | 
			
		||||
            if (injector.messages.isError()) return@runnable
 | 
			
		||||
          }
 | 
			
		||||
        } finally {
 | 
			
		||||
          keyStack.removeFirst()
 | 
			
		||||
        }
 | 
			
		||||
        if (!isInternalMacro) {
 | 
			
		||||
          MacroAutoImport.run(editor.ij, context.ij)
 | 
			
		||||
          keyStack.resetFirst()
 | 
			
		||||
        }
 | 
			
		||||
        keyStack.removeFirst()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isInternalMacro) {
 | 
			
		||||
 
 | 
			
		||||
@@ -48,7 +48,9 @@ import com.maddyhome.idea.vim.api.options
 | 
			
		||||
import com.maddyhome.idea.vim.api.visualLineToBufferLine
 | 
			
		||||
import com.maddyhome.idea.vim.command.Argument
 | 
			
		||||
import com.maddyhome.idea.vim.command.MotionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
			
		||||
import com.maddyhome.idea.vim.handler.Motion
 | 
			
		||||
@@ -72,8 +74,6 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
			
		||||
import org.jetbrains.annotations.Range
 | 
			
		||||
import java.io.File
 | 
			
		||||
@@ -461,13 +461,11 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
      val fileEditor = event.oldEditor
 | 
			
		||||
      if (fileEditor is TextEditor) {
 | 
			
		||||
        val editor = fileEditor.editor
 | 
			
		||||
        if (!editor.isDisposed) {
 | 
			
		||||
          ExOutputModel.getInstance(editor).clear()
 | 
			
		||||
          editor.vim.let { vimEditor ->
 | 
			
		||||
            if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
 | 
			
		||||
              vimEditor.exitVisualMode()
 | 
			
		||||
              KeyHandler.getInstance().reset(vimEditor)
 | 
			
		||||
            }
 | 
			
		||||
        ExOutputModel.getInstance(editor).clear()
 | 
			
		||||
        editor.vim.let { vimEditor ->
 | 
			
		||||
          if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
 | 
			
		||||
            vimEditor.exitVisualMode()
 | 
			
		||||
            KeyHandler.getInstance().reset(vimEditor)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -21,11 +21,8 @@ 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
 | 
			
		||||
@@ -35,7 +32,6 @@ 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
 | 
			
		||||
@@ -184,77 +180,6 @@ 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>"
 | 
			
		||||
@@ -389,8 +314,6 @@ 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)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,6 +40,10 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
			
		||||
  override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
 | 
			
		||||
  override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
 | 
			
		||||
 | 
			
		||||
  private fun updateFallbackWindow(fallbackWindow: VimEditor, targetEditor: VimEditor) {
 | 
			
		||||
    copyPerWindowGlobalValues(fallbackWindow, targetEditor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
 | 
			
		||||
      // Vim only has one window, and it's not possible to close it. This means that editing a new file will always
 | 
			
		||||
@@ -54,8 +58,6 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
			
		||||
      // Unfortunately, we can't reliably know if a closing editor is the selected editor. Instead, we rely on selection
 | 
			
		||||
      // change events. If an editor is losing selection and there is no new selection, we can assume this means that
 | 
			
		||||
      // the last editor has been closed, and use the closed editor to update the fallback window
 | 
			
		||||
      //
 | 
			
		||||
      // XXX: event.oldEditor will must probably return a disposed editor. So, it should be treated with care
 | 
			
		||||
      if (event.newEditor == null) {
 | 
			
		||||
        (event.oldEditor as? TextEditor)?.editor?.let {
 | 
			
		||||
          (VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
 | 
			
		||||
@@ -66,7 +68,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class IjOptionConstants {
 | 
			
		||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
 | 
			
		||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
 | 
			
		||||
  companion object {
 | 
			
		||||
 | 
			
		||||
    const val idearefactormode_keep = "keep"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										285
									
								
								src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										285
									
								
								src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.java
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,285 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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());
 | 
			
		||||
}
 | 
			
		||||
@@ -1,281 +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.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>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -192,8 +192,8 @@ public class SearchGroup extends VimSearchGroupBase implements PersistentStateCo
 | 
			
		||||
   * @param patternOffset   The pattern offset, e.g. `/{pattern}/{offset}`
 | 
			
		||||
   * @param direction       The direction to search
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
 | 
			
		||||
  @TestOnly
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
 | 
			
		||||
                                 @NotNull String patternOffset, Direction direction) {
 | 
			
		||||
    setLastUsedPattern(pattern, RE_SEARCH, true);
 | 
			
		||||
    lastIgnoreSmartCase = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -205,7 +205,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
     * @param event The change event
 | 
			
		||||
     */
 | 
			
		||||
    override fun beforeDocumentChange(event: DocumentEvent) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
 | 
			
		||||
      if (event.oldLength == 0) return
 | 
			
		||||
      val doc = event.document
 | 
			
		||||
@@ -221,7 +221,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
     * @param event The change event
 | 
			
		||||
     */
 | 
			
		||||
    override fun documentChanged(event: DocumentEvent) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
 | 
			
		||||
      if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
 | 
			
		||||
      val doc = event.document
 | 
			
		||||
@@ -242,7 +242,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
 | 
			
		||||
  class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
 | 
			
		||||
    override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
@@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,12 +27,15 @@ import com.maddyhome.idea.vim.api.getLineEndOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isBlock
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isChar
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isLine
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.diagnostic.debug
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.RWLockLabel
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.ide.isClionNova
 | 
			
		||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
@@ -45,10 +48,6 @@ import com.maddyhome.idea.vim.put.PutData
 | 
			
		||||
import com.maddyhome.idea.vim.put.VimPasteProvider
 | 
			
		||||
import com.maddyhome.idea.vim.put.VimPutBase
 | 
			
		||||
import com.maddyhome.idea.vim.register.RegisterConstants
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isBlock
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isChar
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isLine
 | 
			
		||||
import java.awt.datatransfer.DataFlavor
 | 
			
		||||
 | 
			
		||||
internal class PutGroup : VimPutBase() {
 | 
			
		||||
@@ -190,7 +189,7 @@ internal class PutGroup : VimPutBase() {
 | 
			
		||||
    endOffset: Int,
 | 
			
		||||
  ): Int {
 | 
			
		||||
    // Temp fix for VIM-2808. Should be removed after rider will fix it's issues
 | 
			
		||||
    if (PlatformUtils.isRider() || isClionNova()) return endOffset
 | 
			
		||||
    if (PlatformUtils.isRider()) return endOffset
 | 
			
		||||
 | 
			
		||||
    val startLine = editor.offsetToBufferPosition(startOffset).line
 | 
			
		||||
    val endLine = editor.offsetToBufferPosition(endOffset - 1).line
 | 
			
		||||
 
 | 
			
		||||
@@ -40,15 +40,9 @@ internal object IdeaSelectionControl {
 | 
			
		||||
   * This method should be in sync with [predictMode]
 | 
			
		||||
   *
 | 
			
		||||
   * Control unexpected (non vim) selection change and adjust a mode to it. The new mode is not enabled immediately,
 | 
			
		||||
   *   but with some delay (using [VimVisualTimer]). The delay is used because some platform functionality
 | 
			
		||||
   *   makes features by using selection. E.g. PyCharm unindent firstly select the indenting then applies delete action.
 | 
			
		||||
   *   Such "quick" selection breaks IdeaVim behaviour.
 | 
			
		||||
   *   but with some delay (using [VimVisualTimer])
 | 
			
		||||
   *
 | 
			
		||||
   * See [VimVisualTimer] to more info.
 | 
			
		||||
   *
 | 
			
		||||
   * XXX: This method can be split into "change calculation" and "change apply". In this way, we would be able
 | 
			
		||||
   *   to calculate if we need to make a change or not and reduce the number of these calls.
 | 
			
		||||
   *   If this refactoring ever is applied, please add `assertNull(VimVisualTimer.timer)` to `tearDown` of VimTestCase.
 | 
			
		||||
   */
 | 
			
		||||
  fun controlNonVimSelectionChange(
 | 
			
		||||
    editor: Editor,
 | 
			
		||||
@@ -56,7 +50,6 @@ internal object IdeaSelectionControl {
 | 
			
		||||
  ) {
 | 
			
		||||
    VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
 | 
			
		||||
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return@singleTask
 | 
			
		||||
      if (editor.isIdeaVimDisabledHere) return@singleTask
 | 
			
		||||
 | 
			
		||||
      logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
 | 
			
		||||
@@ -128,9 +121,8 @@ internal object IdeaSelectionControl {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun dontChangeMode(editor: Editor): Boolean {
 | 
			
		||||
    return editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
 | 
			
		||||
  }
 | 
			
		||||
  private fun dontChangeMode(editor: Editor): Boolean =
 | 
			
		||||
    editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
 | 
			
		||||
 | 
			
		||||
  private fun chooseNonSelectionMode(editor: Editor): Mode {
 | 
			
		||||
    val templateActive = editor.isTemplateActive()
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,10 @@
 | 
			
		||||
package com.maddyhome.idea.vim.group.visual
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import java.awt.event.ActionEvent
 | 
			
		||||
import javax.swing.Timer
 | 
			
		||||
 | 
			
		||||
@@ -79,11 +79,6 @@ internal object VimVisualTimer {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun drop() {
 | 
			
		||||
    swingTimer?.stop()
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
 | 
			
		||||
    task(mode)
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
 
 | 
			
		||||
@@ -1,91 +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.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,14 +8,11 @@
 | 
			
		||||
 | 
			
		||||
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.ProjectActivity
 | 
			
		||||
import com.maddyhome.idea.vim.api.key
 | 
			
		||||
import com.intellij.openapi.startup.StartupActivity
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Logs the chain of handlers for esc and enter
 | 
			
		||||
@@ -29,11 +26,11 @@ import com.maddyhome.idea.vim.api.key
 | 
			
		||||
 * 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 : ProjectActivity {
 | 
			
		||||
internal class EditorHandlersChainLogger : StartupActivity {
 | 
			
		||||
  @Suppress("UnresolvedPluginConfigReference")
 | 
			
		||||
  private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
 | 
			
		||||
 | 
			
		||||
  override suspend fun execute(project: Project) {
 | 
			
		||||
  override fun runActivity(project: Project) {
 | 
			
		||||
    val escHandlers = editorHandlers.extensionList
 | 
			
		||||
      .filter { it.action == "EditorEscape" }
 | 
			
		||||
      .joinToString("\n") { it.implementationClass }
 | 
			
		||||
@@ -43,22 +40,6 @@ internal class EditorHandlersChainLogger : ProjectActivity {
 | 
			
		||||
 | 
			
		||||
    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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,155 +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.handler
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
			
		||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
			
		||||
import com.intellij.openapi.actionSystem.Shortcut
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.keymap.Keymap
 | 
			
		||||
import com.intellij.openapi.keymap.KeymapManagerListener
 | 
			
		||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.startup.ProjectActivity
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.key
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import kotlinx.coroutines.FlowPreview
 | 
			
		||||
import kotlinx.coroutines.channels.BufferOverflow
 | 
			
		||||
import kotlinx.coroutines.flow.MutableSharedFlow
 | 
			
		||||
import kotlinx.coroutines.flow.collectLatest
 | 
			
		||||
import kotlinx.coroutines.flow.debounce
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
 | 
			
		||||
internal val keyCheckRequests = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
 | 
			
		||||
 */
 | 
			
		||||
internal class KeymapChecker : ProjectActivity {
 | 
			
		||||
  override suspend fun execute(project: Project) {
 | 
			
		||||
    project.service<KeymapCheckerService>().start()
 | 
			
		||||
    keyCheckRequests.emit(Unit)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * At the moment of release 2023.3 there is a problem that starting a coroutine like this
 | 
			
		||||
 *   right in the project activity will block this project activity in tests.
 | 
			
		||||
 * To avoid that, there is an intermediate service that will allow to avoid this issue.
 | 
			
		||||
 *
 | 
			
		||||
 * However, in general we should start this coroutine right in the [KeymapChecker]
 | 
			
		||||
 */
 | 
			
		||||
@OptIn(FlowPreview::class)
 | 
			
		||||
@Service(Service.Level.PROJECT)
 | 
			
		||||
internal class KeymapCheckerService(private val cs: CoroutineScope) {
 | 
			
		||||
  fun start() {
 | 
			
		||||
    cs.launch {
 | 
			
		||||
      keyCheckRequests
 | 
			
		||||
        .debounce(5_000)
 | 
			
		||||
        .collectLatest { verifyKeymap() }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
 | 
			
		||||
  override fun activeKeymapChanged(keymap: Keymap?) {
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun shortcutChanged(keymap: Keymap, actionId: String) {
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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,7 +10,6 @@ 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
 | 
			
		||||
@@ -19,7 +18,6 @@ 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
 | 
			
		||||
@@ -97,15 +95,7 @@ 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)
 | 
			
		||||
        }
 | 
			
		||||
        ApplicationManager.getApplication().invokeLater(executionHandler)
 | 
			
		||||
      } else {
 | 
			
		||||
        executionHandler()
 | 
			
		||||
      }
 | 
			
		||||
@@ -116,16 +106,12 @@ 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
 | 
			
		||||
    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
 | 
			
		||||
    if (FileDocumentManager.getInstance().getFile(editor.document)?.name == "Python Console.py") return false
 | 
			
		||||
    return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean {
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return false
 | 
			
		||||
    if (!VimPlugin.isEnabled()) return false
 | 
			
		||||
    if (!isHandlerEnabled(editor, dataContext)) return false
 | 
			
		||||
    if (isNotActualKeyPress(dataContext)) return false
 | 
			
		||||
    return true
 | 
			
		||||
@@ -145,20 +131,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
			
		||||
        return 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) {
 | 
			
		||||
      if (dataManager.loadFromDataContext(dataContext, ShiftEnterDetector.Util.key) == true) {
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -201,6 +174,11 @@ 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)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -229,7 +207,7 @@ internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(n
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rider (and CLion Nova) uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
 | 
			
		||||
 * Rider uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
 | 
			
		||||
 *   designer to get all the esc presses, and if there is a completion close it and do not pass the execution further.
 | 
			
		||||
 *   This doesn't work the same as in IJ.
 | 
			
		||||
 * In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
 | 
			
		||||
@@ -271,17 +249,11 @@ internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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.
 | 
			
		||||
 * 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.
 | 
			
		||||
 * 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 StartNewLineDetector(nextHandler: EditorActionHandler) : StartNewLineDetectorBase(nextHandler)
 | 
			
		||||
internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandler) :
 | 
			
		||||
  StartNewLineDetectorBase(nextHandler)
 | 
			
		||||
 | 
			
		||||
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
			
		||||
internal class ShiftEnterDetector(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)
 | 
			
		||||
@@ -292,7 +264,7 @@ internal open class StartNewLineDetectorBase(private val nextHandler: EditorActi
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object Util {
 | 
			
		||||
    val key = Key.create<Boolean>("vim.is.start.new.line")
 | 
			
		||||
    val key = Key.create<Boolean>("vim.is.shift.enter")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
@@ -344,9 +316,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 && s.modifiers == 0 -> return true
 | 
			
		||||
    s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
 | 
			
		||||
  when (s.keyCode) {
 | 
			
		||||
    KeyEvent.VK_ENTER -> return true
 | 
			
		||||
    KeyEvent.VK_ESCAPE -> return true
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ private fun Editor.guicursorMode(): GuiCursorMode {
 | 
			
		||||
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
 | 
			
		||||
 | 
			
		||||
private fun Editor.updatePrimaryCaretVisualAttributes() {
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
 | 
			
		||||
 | 
			
		||||
  // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
 | 
			
		||||
@@ -89,7 +89,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun Editor.updateSecondaryCaretsVisualAttributes() {
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  // IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
 | 
			
		||||
  val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
 | 
			
		||||
  this.caretModel.allCarets.forEach {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
 | 
			
		||||
  get() {
 | 
			
		||||
    val mode = this.vim.vimStateMachine.mode
 | 
			
		||||
    return when (mode) {
 | 
			
		||||
      is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
      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
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun vimEnabled(editor: Editor?): Boolean {
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) return false
 | 
			
		||||
  if (!VimPlugin.isEnabled()) return false
 | 
			
		||||
  if (editor != null && editor.isIdeaVimDisabledHere) return false
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,12 +15,10 @@ 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
 | 
			
		||||
@@ -41,8 +39,6 @@ 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
 | 
			
		||||
@@ -143,7 +139,7 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
      manager.fireAfterActionPerformed(action, event, result!!)
 | 
			
		||||
    }
 | 
			
		||||
    if (indexError != null) {
 | 
			
		||||
      ActionUtil.showDumbModeWarning(project, action, event)
 | 
			
		||||
      ActionUtil.showDumbModeWarning(project, event)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -154,43 +150,10 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
   * @param context The context to run it in
 | 
			
		||||
   */
 | 
			
		||||
  override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
 | 
			
		||||
    val action = getAction(name, context)
 | 
			
		||||
    val aMgr = ActionManager.getInstance()
 | 
			
		||||
    val action = aMgr.getAction(name)
 | 
			
		||||
    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?,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@
 | 
			
		||||
package com.maddyhome.idea.vim.helper;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
 | 
			
		||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
 | 
			
		||||
import com.intellij.lang.Commenter;
 | 
			
		||||
import com.intellij.lang.Language;
 | 
			
		||||
@@ -17,15 +16,15 @@ import com.intellij.lang.LanguageCommenters;
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger;
 | 
			
		||||
import com.intellij.openapi.editor.Caret;
 | 
			
		||||
import com.intellij.openapi.editor.Editor;
 | 
			
		||||
import com.intellij.openapi.project.Project;
 | 
			
		||||
import com.intellij.psi.PsiComment;
 | 
			
		||||
import com.intellij.psi.PsiElement;
 | 
			
		||||
import com.intellij.psi.PsiFile;
 | 
			
		||||
import com.intellij.psi.util.PsiTreeUtil;
 | 
			
		||||
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.common.CharacterPosition;
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction;
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange;
 | 
			
		||||
@@ -33,12 +32,6 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.CharPointer;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.RegExp;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparator;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntIterator;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntSortedSet;
 | 
			
		||||
import kotlin.Pair;
 | 
			
		||||
import org.jetbrains.annotations.Contract;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
@@ -1530,42 +1523,6 @@ public class SearchHelper {
 | 
			
		||||
    return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static int findMisspelledWords(@NotNull Editor editor,
 | 
			
		||||
                                       int startOffset,
 | 
			
		||||
                                       int endOffset,
 | 
			
		||||
                                       int skipCount,
 | 
			
		||||
                                       IntComparator offsetOrdering) {
 | 
			
		||||
    Project project = editor.getProject();
 | 
			
		||||
    if (project == null) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
 | 
			
		||||
    DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
 | 
			
		||||
                                           startOffset, endOffset, highlight -> {
 | 
			
		||||
        if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
 | 
			
		||||
          int offset = highlight.getStartOffset();
 | 
			
		||||
          if (offset >= startOffset && offset <= endOffset) {
 | 
			
		||||
            offsets.add(offset);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
      });
 | 
			
		||||
    
 | 
			
		||||
    if (offsets.isEmpty()) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (skipCount >= offsets.size()) {
 | 
			
		||||
      return offsets.lastInt();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      IntIterator offsetIterator = offsets.iterator();
 | 
			
		||||
      offsetIterator.skip(skipCount);
 | 
			
		||||
      return offsetIterator.nextInt();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
 | 
			
		||||
    List<String> pairs = options(injector, vimEditor).getMatchpairs();
 | 
			
		||||
    StringBuilder res = new StringBuilder();
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ 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
 | 
			
		||||
@@ -45,17 +44,22 @@ 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)
 | 
			
		||||
 | 
			
		||||
          // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
          if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
			
		||||
            undoManager.undo(fileEditor)
 | 
			
		||||
          }
 | 
			
		||||
        undoManager.undo(fileEditor)
 | 
			
		||||
        if (hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
			
		||||
          undoManager.undo(fileEditor) // execute one more time if the previous undo just restored selection
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
          removeSelections(editor)
 | 
			
		||||
        
 | 
			
		||||
        // 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)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -84,59 +88,12 @@ 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)
 | 
			
		||||
 
 | 
			
		||||
@@ -124,6 +124,10 @@ internal var Editor.vimMorePanel: ExOutputPanel? by userData()
 | 
			
		||||
internal var Editor.vimExOutput: ExOutputModel? by userData()
 | 
			
		||||
internal var Editor.vimTestInputModel: TestInputModel? by userData()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks whether a keeping visual mode visual operator action is performed on editor.
 | 
			
		||||
 */
 | 
			
		||||
internal var Editor.vimKeepingVisualOperatorAction: Boolean by userDataOr { false }
 | 
			
		||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -1,25 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2024 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.ide
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.extensions.ExtensionPointName
 | 
			
		||||
 | 
			
		||||
internal val clionEP = ExtensionPointName.create<ClionNovaProvider>("IdeaVIM.clionNovaProvider")
 | 
			
		||||
 | 
			
		||||
internal interface ClionNovaProvider {
 | 
			
		||||
  fun isClionNova(): Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class ClionNovaProviderImpl : ClionNovaProvider {
 | 
			
		||||
  override fun isClionNova(): Boolean = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun isClionNova(): Boolean {
 | 
			
		||||
  return clionEP.extensions.any { it.isClionNova() }
 | 
			
		||||
}
 | 
			
		||||
@@ -40,7 +40,7 @@ internal object AppCodeTemplates {
 | 
			
		||||
    private var editor: Editor? = null
 | 
			
		||||
 | 
			
		||||
    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
 | 
			
		||||
      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
			
		||||
      if (hostEditor != null) {
 | 
			
		||||
@@ -49,7 +49,7 @@ internal object AppCodeTemplates {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
 | 
			
		||||
      if (ActionManager.getInstance().getId(action) == IdeActions.ACTION_CHOOSE_LOOKUP_ITEM) {
 | 
			
		||||
        val myEditor = editor
 | 
			
		||||
 
 | 
			
		||||
@@ -59,7 +59,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
    private var completionPrevDocumentLength: Int? = null
 | 
			
		||||
    private var completionPrevDocumentOffset: Int? = null
 | 
			
		||||
    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
 | 
			
		||||
      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
			
		||||
      if (hostEditor != null) {
 | 
			
		||||
@@ -92,47 +92,45 @@ internal object IdeaSpecifics {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
 | 
			
		||||
      val editor = editor
 | 
			
		||||
      if (editor != null) {
 | 
			
		||||
        if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
 | 
			
		||||
          val prevDocumentLength = completionPrevDocumentLength
 | 
			
		||||
          val prevDocumentOffset = completionPrevDocumentOffset
 | 
			
		||||
      if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
 | 
			
		||||
        val prevDocumentLength = completionPrevDocumentLength
 | 
			
		||||
        val prevDocumentOffset = completionPrevDocumentOffset
 | 
			
		||||
 | 
			
		||||
          if (prevDocumentLength != null && prevDocumentOffset != null) {
 | 
			
		||||
            val register = VimPlugin.getRegister()
 | 
			
		||||
            val addedTextLength = editor.document.textLength - prevDocumentLength
 | 
			
		||||
            val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
 | 
			
		||||
            val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
 | 
			
		||||
        if (prevDocumentLength != null && prevDocumentOffset != null) {
 | 
			
		||||
          val register = VimPlugin.getRegister()
 | 
			
		||||
          val addedTextLength = editor.document.textLength - prevDocumentLength
 | 
			
		||||
          val caretShift = addedTextLength - (editor.caretModel.primaryCaret.offset - prevDocumentOffset)
 | 
			
		||||
          val leftArrow = KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0)
 | 
			
		||||
 | 
			
		||||
            register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
 | 
			
		||||
            repeat(caretShift.coerceAtLeast(0)) {
 | 
			
		||||
              register.recordKeyStroke(leftArrow)
 | 
			
		||||
            }
 | 
			
		||||
          register.recordText(editor.document.getText(TextRange(prevDocumentOffset, prevDocumentOffset + addedTextLength)))
 | 
			
		||||
          repeat(caretShift.coerceAtLeast(0)) {
 | 
			
		||||
            register.recordKeyStroke(leftArrow)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          this.completionPrevDocumentLength = null
 | 
			
		||||
          this.completionPrevDocumentOffset = null
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        //region Enter insert mode after surround with if
 | 
			
		||||
        if (surrounderAction == action.javaClass.name && surrounderItems.any {
 | 
			
		||||
            action.templatePresentation.text.endsWith(
 | 
			
		||||
              it,
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
        ) {
 | 
			
		||||
          val commandState = editor.vim.vimStateMachine
 | 
			
		||||
          commandState.mode = Mode.NORMAL()
 | 
			
		||||
          VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
 | 
			
		||||
          KeyHandler.getInstance().reset(editor.vim)
 | 
			
		||||
        }
 | 
			
		||||
        //endregion
 | 
			
		||||
 | 
			
		||||
        injector.scroll.scrollCaretIntoView(editor.vim)
 | 
			
		||||
        this.completionPrevDocumentLength = null
 | 
			
		||||
        this.completionPrevDocumentOffset = null
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      //region Enter insert mode after surround with if
 | 
			
		||||
      if (surrounderAction == action.javaClass.name && surrounderItems.any {
 | 
			
		||||
          action.templatePresentation.text.endsWith(
 | 
			
		||||
            it,
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      ) {
 | 
			
		||||
        editor?.let {
 | 
			
		||||
          val commandState = it.vim.vimStateMachine
 | 
			
		||||
          commandState.mode = Mode.NORMAL()
 | 
			
		||||
          VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
 | 
			
		||||
          KeyHandler.getInstance().reset(it.vim)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      //endregion
 | 
			
		||||
 | 
			
		||||
      this.editor = null
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -140,7 +138,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
  //region Enter insert mode for surround templates without selection
 | 
			
		||||
  class VimTemplateManagerListener : TemplateManagerListener {
 | 
			
		||||
    override fun templateStarted(state: TemplateState) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      val editor = state.editor ?: return
 | 
			
		||||
 | 
			
		||||
      state.addTemplateStateListener(object : TemplateEditingAdapter() {
 | 
			
		||||
@@ -178,7 +176,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
  //region Register shortcuts for lookup and perform partial reset
 | 
			
		||||
  class LookupTopicListener : LookupManagerListener {
 | 
			
		||||
    override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
 | 
			
		||||
      // Lookup opened
 | 
			
		||||
      if (oldLookup == null && newLookup is LookupImpl) {
 | 
			
		||||
@@ -201,7 +199,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
  //region Hide Vim search highlights when showing IntelliJ search results
 | 
			
		||||
  class VimFindModelListener : FindModelListener {
 | 
			
		||||
    override fun findNextModelChanged() {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      VimPlugin.getSearch().clearSearchHighlight()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ internal class RiderActionListener : AnActionListener {
 | 
			
		||||
 | 
			
		||||
  private var editor: Editor? = null
 | 
			
		||||
  override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
    if (!VimPlugin.isEnabled()) return
 | 
			
		||||
 | 
			
		||||
    val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
			
		||||
    if (hostEditor != null) {
 | 
			
		||||
@@ -36,7 +36,7 @@ internal class RiderActionListener : AnActionListener {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
    if (!VimPlugin.isEnabled()) return
 | 
			
		||||
 | 
			
		||||
    //region Extend Selection for Rider
 | 
			
		||||
    when (ActionManager.getInstance().getId(action)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,6 @@
 | 
			
		||||
package com.maddyhome.idea.vim.listener
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.ui.UISettings
 | 
			
		||||
import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger
 | 
			
		||||
import com.intellij.openapi.diagnostic.trace
 | 
			
		||||
@@ -29,9 +28,8 @@ import com.intellij.openapi.editor.event.EditorMouseMotionListener
 | 
			
		||||
import com.intellij.openapi.editor.event.SelectionEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.SelectionListener
 | 
			
		||||
import com.intellij.openapi.editor.ex.DocumentEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.FocusChangeListener
 | 
			
		||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
 | 
			
		||||
import com.intellij.openapi.editor.impl.EditorImpl
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
 | 
			
		||||
@@ -42,11 +40,14 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager
 | 
			
		||||
import com.intellij.openapi.rd.createLifetime
 | 
			
		||||
import com.intellij.openapi.rd.createNestedDisposable
 | 
			
		||||
import com.intellij.openapi.util.Disposer
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.util.removeUserData
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFile
 | 
			
		||||
import com.intellij.util.ExceptionUtil
 | 
			
		||||
import com.jetbrains.rd.util.lifetime.Lifetime
 | 
			
		||||
import com.maddyhome.idea.vim.EventFacade
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimKeyListener
 | 
			
		||||
@@ -61,7 +62,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
			
		||||
import com.maddyhome.idea.vim.group.EditorGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.FileGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.OptionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.ScrollGroup
 | 
			
		||||
@@ -70,8 +70,6 @@ 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.keyCheckRequests
 | 
			
		||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.helper.StrictMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.exitSelectMode
 | 
			
		||||
@@ -97,9 +95,6 @@ import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
 | 
			
		||||
import com.maddyhome.idea.vim.vimDisposable
 | 
			
		||||
import java.awt.event.MouseAdapter
 | 
			
		||||
import java.awt.event.MouseEvent
 | 
			
		||||
import javax.swing.SwingUtilities
 | 
			
		||||
@@ -134,14 +129,11 @@ internal object VimListenerManager {
 | 
			
		||||
  fun turnOn() {
 | 
			
		||||
    GlobalListeners.enable()
 | 
			
		||||
    EditorListeners.addAll()
 | 
			
		||||
    correctorRequester.request()
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun turnOff() {
 | 
			
		||||
    GlobalListeners.disable()
 | 
			
		||||
    EditorListeners.removeAll()
 | 
			
		||||
    correctorRequester.request()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object GlobalListeners {
 | 
			
		||||
@@ -159,13 +151,6 @@ internal object VimListenerManager {
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
			
		||||
 | 
			
		||||
      // This code is executed after ideavimrc execution, so we trigger onGlobalOptionChanged just in case
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
 | 
			
		||||
      modeWidgetOptionListener.onGlobalOptionChanged()
 | 
			
		||||
      macroWidgetOptionListener.onGlobalOptionChanged()
 | 
			
		||||
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
			
		||||
 | 
			
		||||
      EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
@@ -173,8 +158,6 @@ internal object VimListenerManager {
 | 
			
		||||
      busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
 | 
			
		||||
 | 
			
		||||
      EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
      val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
 | 
			
		||||
      eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun disable() {
 | 
			
		||||
@@ -185,12 +168,10 @@ internal object VimListenerManager {
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  object EditorListeners {
 | 
			
		||||
    fun addAll() {
 | 
			
		||||
      val initialisedEditors = mutableSetOf<Editor>()
 | 
			
		||||
@@ -229,67 +210,49 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
 | 
			
		||||
      // As I understand, there is no need to pass a disposable that also disposes on editor close
 | 
			
		||||
      //   because all editor resources will be garbage collected anyway on editor close
 | 
			
		||||
      val disposable = editor.project?.vimDisposable ?: return
 | 
			
		||||
 | 
			
		||||
      val listenersDisposable = Disposer.newDisposable(disposable)
 | 
			
		||||
      editor.putUserData(editorListenersDisposable, listenersDisposable)
 | 
			
		||||
 | 
			
		||||
      Disposer.register(listenersDisposable) {
 | 
			
		||||
        if (VimListenerTestObject.enabled) {
 | 
			
		||||
          VimListenerTestObject.disposedCounter += 1
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      val pluginLifetime = VimPlugin.getInstance().createLifetime()
 | 
			
		||||
      val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
 | 
			
		||||
      val disposable =
 | 
			
		||||
        Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
 | 
			
		||||
 | 
			
		||||
      editor.contentComponent.addKeyListener(VimKeyListener)
 | 
			
		||||
      Disposer.register(listenersDisposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
 | 
			
		||||
      Disposer.register(disposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
 | 
			
		||||
 | 
			
		||||
      // Initialise the local options. We MUST do this before anything has the chance to query options
 | 
			
		||||
      val vimEditor = editor.vim
 | 
			
		||||
      VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
 | 
			
		||||
      VimPlugin.getOptionGroup().initialiseLocalOptions(editor.vim, openingEditor, scenario)
 | 
			
		||||
 | 
			
		||||
      val eventFacade = EventFacade.getInstance()
 | 
			
		||||
      eventFacade.addEditorMouseListener(editor, EditorMouseHandler, listenersDisposable)
 | 
			
		||||
      eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, listenersDisposable)
 | 
			
		||||
      eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, listenersDisposable)
 | 
			
		||||
      eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, listenersDisposable)
 | 
			
		||||
      eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
 | 
			
		||||
      eventFacade.addEditorMouseListener(editor, EditorMouseHandler, disposable)
 | 
			
		||||
      eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, disposable)
 | 
			
		||||
      eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, disposable)
 | 
			
		||||
      eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, disposable)
 | 
			
		||||
      eventFacade.addCaretListener(editor, EditorCaretHandler, disposable)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getEditor().editorCreated(editor)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getChange().editorCreated(editor, listenersDisposable)
 | 
			
		||||
      VimPlugin.getChange().editorCreated(editor, disposable)
 | 
			
		||||
 | 
			
		||||
      injector.listenersNotifier.notifyEditorCreated(vimEditor)
 | 
			
		||||
 | 
			
		||||
      Disposer.register(listenersDisposable) {
 | 
			
		||||
      Disposer.register(disposable) {
 | 
			
		||||
        VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun remove(editor: Editor, isReleased: Boolean) {
 | 
			
		||||
      val editorDisposable = editor.getUserData(editorListenersDisposable)
 | 
			
		||||
      if (editorDisposable != null) {
 | 
			
		||||
        Disposer.dispose(editorDisposable)
 | 
			
		||||
      }
 | 
			
		||||
      else StrictMode.fail("Editor doesn't have disposable attached. $editor")
 | 
			
		||||
      editor.contentComponent.removeKeyListener(VimKeyListener)
 | 
			
		||||
      val eventFacade = EventFacade.getInstance()
 | 
			
		||||
      eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
 | 
			
		||||
      eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)
 | 
			
		||||
      eventFacade.removeEditorSelectionListener(editor, EditorSelectionHandler)
 | 
			
		||||
      eventFacade.removeComponentMouseListener(editor.contentComponent, ComponentMouseListener)
 | 
			
		||||
      eventFacade.removeCaretListener(editor, EditorCaretHandler)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  private object VimFocusListener : FocusChangeListener {
 | 
			
		||||
    override fun focusGained(editor: Editor) {
 | 
			
		||||
      injector.listenersNotifier.notifyEditorFocusGained(editor.vim)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getChange().editorReleased(editor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun focusLost(editor: Editor) {
 | 
			
		||||
      injector.listenersNotifier.notifyEditorFocusLost(editor.vim)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val editorListenersDisposable = Key.create<Disposable>("IdeaVim listeners disposable")
 | 
			
		||||
 | 
			
		||||
  object VimCaretListener : CaretListener {
 | 
			
		||||
    override fun caretAdded(event: CaretEvent) {
 | 
			
		||||
      if (vimDisabled(event.editor)) return
 | 
			
		||||
@@ -304,7 +267,7 @@ internal object VimListenerManager {
 | 
			
		||||
 | 
			
		||||
  class VimFileEditorManagerListener : FileEditorManagerListener {
 | 
			
		||||
    override fun selectionChanged(event: FileEditorManagerEvent) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      
 | 
			
		||||
      val newEditor = event.newEditor
 | 
			
		||||
      if (newEditor is TextEditor) {
 | 
			
		||||
@@ -382,15 +345,13 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun editorReleased(event: EditorFactoryEvent) {
 | 
			
		||||
      val vimEditor = event.editor.vim
 | 
			
		||||
      injector.listenersNotifier.notifyEditorReleased(vimEditor)
 | 
			
		||||
      injector.markService.editorReleased(vimEditor)
 | 
			
		||||
      injector.markService.editorReleased(event.editor.vim)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun fileOpenedSync(
 | 
			
		||||
      source: FileEditorManager,
 | 
			
		||||
      file: VirtualFile,
 | 
			
		||||
      editorsWithProviders: List<FileEditorWithProvider>,
 | 
			
		||||
      editorsWithProviders: List<FileEditorWithProvider>
 | 
			
		||||
    ) {
 | 
			
		||||
      // This callback is called once all editors are created for a file being opened. The EditorComposite has been
 | 
			
		||||
      // created (and the list of editors and providers is passed here) and added to an EditorWindow tab, inside a
 | 
			
		||||
@@ -448,7 +409,6 @@ internal object VimListenerManager {
 | 
			
		||||
     */
 | 
			
		||||
    override fun selectionChanged(selectionEvent: SelectionEvent) {
 | 
			
		||||
      if (selectionEvent.editor.isIdeaVimDisabledHere) return
 | 
			
		||||
      VimVisualTimer.drop()
 | 
			
		||||
      val editor = selectionEvent.editor
 | 
			
		||||
      val document = editor.document
 | 
			
		||||
      val ijVimEditor = IjVimEditor(editor)
 | 
			
		||||
@@ -742,11 +702,6 @@ internal object VimListenerManager {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal object VimListenerTestObject {
 | 
			
		||||
  var enabled: Boolean = false
 | 
			
		||||
  var disposedCounter = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private object MouseEventsDataHolder {
 | 
			
		||||
  const val skipEvents = 3
 | 
			
		||||
  var skipNDragEvents = skipEvents
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ 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
 | 
			
		||||
@@ -101,7 +100,8 @@ internal class IjClipboardManager : VimClipboardManager {
 | 
			
		||||
 | 
			
		||||
    // This thing enables alternative context resolve for dumb mode.
 | 
			
		||||
    // Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled
 | 
			
		||||
    DumbService.getInstance(project).withAlternativeResolveEnabled {
 | 
			
		||||
    // [VERSION UPDATE] 2023.2+ Enable alternative context back
 | 
			
		||||
//    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
 | 
			
		||||
 
 | 
			
		||||
@@ -54,6 +54,7 @@ import com.maddyhome.idea.vim.helper.isTemplateActive
 | 
			
		||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
 | 
			
		||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimKeepingVisualOperatorAction
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimLastSelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
@@ -66,7 +67,7 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
 | 
			
		||||
  companion object {
 | 
			
		||||
    // For cases where Editor does not have a project (for some reason)
 | 
			
		||||
    // It's something IJ Platform related and stored here because of this reason
 | 
			
		||||
    const val DEFAULT_PROJECT_ID = "no project"
 | 
			
		||||
    const val DEFAULT_PROJECT_ID = "no project" 
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // All the editor actions should be performed with top level editor!!!
 | 
			
		||||
@@ -81,6 +82,11 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
 | 
			
		||||
    set(value) {
 | 
			
		||||
      editor.vimChangeActionSwitchMode = value
 | 
			
		||||
    }
 | 
			
		||||
  override var vimKeepingVisualOperatorAction: Boolean
 | 
			
		||||
    get() = editor.vimKeepingVisualOperatorAction
 | 
			
		||||
    set(value) {
 | 
			
		||||
      editor.vimKeepingVisualOperatorAction = value
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  override fun fileSize(): Long = editor.fileSize.toLong()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -15,8 +15,6 @@ import com.maddyhome.idea.vim.api.VimSearchHelperBase
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchOptions
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparator
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparators
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@@ -95,26 +93,4 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
 | 
			
		||||
  ): TextRange? {
 | 
			
		||||
    return SearchHelper.findBlockRange(editor.ij, caret.ij, type, count, isOuter)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
 | 
			
		||||
    val startOffset: Int
 | 
			
		||||
    val endOffset: Int
 | 
			
		||||
    val skipCount: Int
 | 
			
		||||
    val offsetOrdering: IntComparator
 | 
			
		||||
    
 | 
			
		||||
    if (count < 0) {
 | 
			
		||||
      startOffset = 0
 | 
			
		||||
      endOffset = caret.offset.point - 1
 | 
			
		||||
      skipCount = -count - 1
 | 
			
		||||
      offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      startOffset = caret.offset.point + 1
 | 
			
		||||
      endOffset = editor.ij.document.textLength
 | 
			
		||||
      skipCount = count - 1
 | 
			
		||||
      offsetOrdering = IntComparators.NATURAL_COMPARATOR
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -333,7 +333,7 @@
 | 
			
		||||
 * |[m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
 | 
			
		||||
 * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
 | 
			
		||||
 * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
 | 
			
		||||
 * |[s|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction}
 | 
			
		||||
 * |[s|                   TO BE IMPLEMENTED
 | 
			
		||||
 * |[z|                   TO BE IMPLEMENTED
 | 
			
		||||
 * |[{|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
 | 
			
		||||
 * |]_CTRL-D|             TO BE IMPLEMENTED
 | 
			
		||||
@@ -358,7 +358,7 @@
 | 
			
		||||
 * |]m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
 | 
			
		||||
 * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
 | 
			
		||||
 * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
 | 
			
		||||
 * |]s|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
 | 
			
		||||
 * |]s|                   TO BE IMPLEMENTED
 | 
			
		||||
 * |]z|                   TO BE IMPLEMENTED
 | 
			
		||||
 * |]}|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
@@ -18,7 +18,6 @@ import com.intellij.util.IJSwingUtilities;
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext;
 | 
			
		||||
import com.maddyhome.idea.vim.diagnostic.VimLogger;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.HelperKt;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.UiHelper;
 | 
			
		||||
@@ -60,8 +59,6 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
 | 
			
		||||
  private boolean myActive = false;
 | 
			
		||||
 | 
			
		||||
  private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
 | 
			
		||||
 | 
			
		||||
  private ExOutputPanel(@NotNull Editor editor) {
 | 
			
		||||
    myEditor = editor;
 | 
			
		||||
 | 
			
		||||
@@ -302,10 +299,6 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
        final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
 | 
			
		||||
        final List<KeyStroke> keys = new ArrayList<>(1);
 | 
			
		||||
        keys.add(key);
 | 
			
		||||
        if (LOG.isTrace()) {
 | 
			
		||||
          LOG.trace("Adding new keys to keyStack as part of playback. State before adding keys: " +
 | 
			
		||||
                    KeyHandler.getInstance().getKeyStack().dump());
 | 
			
		||||
        }
 | 
			
		||||
        KeyHandler.getInstance().getKeyStack().addKeys(keys);
 | 
			
		||||
        ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
 | 
			
		||||
        VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
 | 
			
		||||
@@ -365,7 +358,7 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
  public static class LafListener implements LafManagerListener {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return;
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return;
 | 
			
		||||
      // Calls updateUI on this and child components
 | 
			
		||||
      for (Editor editor : HelperKt.localEditors()) {
 | 
			
		||||
        if (!ExOutputPanel.isPanelActive(editor)) continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,6 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.ui
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.diagnostic.trace
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
 | 
			
		||||
@@ -25,19 +22,13 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * @author dhleong
 | 
			
		||||
 */
 | 
			
		||||
public object ModalEntry {
 | 
			
		||||
 | 
			
		||||
  public val LOG: Logger = logger<ModalEntry>()
 | 
			
		||||
 | 
			
		||||
  public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
 | 
			
		||||
    // Firstly we pull the unfinished keys of the current mapping
 | 
			
		||||
    val mappingStack = KeyHandler.getInstance().keyStack
 | 
			
		||||
    LOG.trace("Dumping key stack:")
 | 
			
		||||
    LOG.trace { mappingStack.dump() }
 | 
			
		||||
    var stroke = mappingStack.feedSomeStroke()
 | 
			
		||||
    while (stroke != null) {
 | 
			
		||||
      val result = processor(stroke)
 | 
			
		||||
      if (!result) {
 | 
			
		||||
        LOG.trace("Got char from mapping stack")
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      stroke = mappingStack.feedSomeStroke()
 | 
			
		||||
@@ -64,7 +55,6 @@ public object ModalEntry {
 | 
			
		||||
          KeyHandler.getInstance().modalEntryKeys += stroke
 | 
			
		||||
        }
 | 
			
		||||
        if (!processor(stroke)) {
 | 
			
		||||
          LOG.trace("Got char from keyboard input: $stroke. Event: $e")
 | 
			
		||||
          KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this)
 | 
			
		||||
          loop.exit()
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import com.intellij.openapi.wm.StatusBarWidget
 | 
			
		||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
 | 
			
		||||
import com.intellij.openapi.wm.WindowManager
 | 
			
		||||
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
 | 
			
		||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
 | 
			
		||||
import com.intellij.util.Consumer
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
@@ -67,6 +68,13 @@ internal object ShowCmd {
 | 
			
		||||
internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
 | 
			
		||||
  override fun onGlobalOptionChanged() {
 | 
			
		||||
    ShowCmd.update()
 | 
			
		||||
 | 
			
		||||
    val extension = StatusBarWidgetFactory.EP_NAME.findExtension(ShowCmdStatusBarWidgetFactory::class.java) ?: return
 | 
			
		||||
    val projectManager = ProjectManager.getInstanceIfCreated() ?: return
 | 
			
		||||
    for (project in projectManager.openProjects) {
 | 
			
		||||
      val statusBarWidgetsManager = project.getService(StatusBarWidgetsManager::class.java) ?: continue
 | 
			
		||||
      statusBarWidgetsManager.updateWidget(extension)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -52,7 +52,6 @@ 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"
 | 
			
		||||
@@ -74,14 +73,6 @@ 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()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -98,10 +89,10 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
 | 
			
		||||
      statusBarWidgetsManager.updateWidget(this)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Util.updateIcon()
 | 
			
		||||
    updateIcon()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object Util {
 | 
			
		||||
  companion object {
 | 
			
		||||
    fun updateIcon() {
 | 
			
		||||
      val projectManager = ProjectManager.getInstanceIfCreated() ?: return
 | 
			
		||||
      for (project in projectManager.openProjects) {
 | 
			
		||||
 
 | 
			
		||||
@@ -453,7 +453,7 @@ public class ExEntryPanel extends JPanel {
 | 
			
		||||
  public static class LafListener implements LafManagerListener {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return;
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return;
 | 
			
		||||
      // Calls updateUI on this and child components
 | 
			
		||||
      if (ExEntryPanel.isInstanceWithShortcutsActive()) {
 | 
			
		||||
        IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstance());
 | 
			
		||||
 
 | 
			
		||||
@@ -1,31 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2024 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.ui.widgets
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.VimPluginListener
 | 
			
		||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener
 | 
			
		||||
 | 
			
		||||
public class VimWidgetListener(private val updateWidget: Runnable) : GlobalOptionChangeListener, VimPluginListener {
 | 
			
		||||
  init {
 | 
			
		||||
    injector.listenersNotifier.vimPluginListeners.add(this)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun onGlobalOptionChanged() {
 | 
			
		||||
    updateWidget.run()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun turnedOn() {
 | 
			
		||||
    updateWidget.run()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun turnedOff() {
 | 
			
		||||
    updateWidget.run()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,94 +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.ui.widgets.macro
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager
 | 
			
		||||
import com.intellij.openapi.wm.StatusBarWidget
 | 
			
		||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
 | 
			
		||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.MacroRecordingListener
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.VimStatusBarWidget
 | 
			
		||||
import java.awt.Component
 | 
			
		||||
 | 
			
		||||
private const val ID = "IdeaVim::Macro"
 | 
			
		||||
 | 
			
		||||
internal class MacroWidgetFactory : StatusBarWidgetFactory, VimStatusBarWidget {
 | 
			
		||||
  private var content: String = ""
 | 
			
		||||
 | 
			
		||||
  private val macroRecordingListener = object : MacroRecordingListener {
 | 
			
		||||
    override fun recordingStarted(editor: VimEditor, register: Char) {
 | 
			
		||||
      content = "recording @$register"
 | 
			
		||||
      updateWidgetInStatusBar(ID, editor.ij.project)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun recordingFinished(editor: VimEditor, register: Char) {
 | 
			
		||||
      content = ""
 | 
			
		||||
      updateWidgetInStatusBar(ID, editor.ij.project)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getId(): String {
 | 
			
		||||
    return ID
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getDisplayName(): String {
 | 
			
		||||
    return "IdeaVim Macro Recording Widget"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun createWidget(project: Project): StatusBarWidget {
 | 
			
		||||
    injector.listenersNotifier.macroRecordingListeners.add(macroRecordingListener)
 | 
			
		||||
    return VimMacroWidget()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isAvailable(project: Project): Boolean {
 | 
			
		||||
    return VimPlugin.isEnabled() && injector.globalIjOptions().showmodewidget
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private inner class VimMacroWidget : StatusBarWidget {
 | 
			
		||||
    override fun ID(): String {
 | 
			
		||||
      return ID
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getPresentation(): StatusBarWidget.WidgetPresentation {
 | 
			
		||||
      return VimModeWidgetPresentation()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private inner class VimModeWidgetPresentation : StatusBarWidget.TextPresentation {
 | 
			
		||||
    override fun getAlignment(): Float = Component.CENTER_ALIGNMENT
 | 
			
		||||
 | 
			
		||||
    override fun getText(): String {
 | 
			
		||||
      return content
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getTooltipText(): String {
 | 
			
		||||
      return content.ifEmpty {
 | 
			
		||||
        "No macro recording in progress"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun updateMacroWidget() {
 | 
			
		||||
  val factory = StatusBarWidgetFactory.EP_NAME.findExtension(MacroWidgetFactory::class.java) ?: return
 | 
			
		||||
  for (project in ProjectManager.getInstance().openProjects) {
 | 
			
		||||
    val statusBarWidgetsManager = project.service<StatusBarWidgetsManager>()
 | 
			
		||||
    statusBarWidgetsManager.updateWidget(factory)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public val macroWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateMacroWidget() }
 | 
			
		||||
@@ -1,45 +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.ui.widgets.mode
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.wm.StatusBarWidget
 | 
			
		||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.VimWidgetListener
 | 
			
		||||
 | 
			
		||||
public class ModeWidgetFactory : StatusBarWidgetFactory {
 | 
			
		||||
  public companion object {
 | 
			
		||||
    public const val ID: String = "IdeaVim::Mode"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getId(): String {
 | 
			
		||||
    return ID
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getDisplayName(): String {
 | 
			
		||||
    return "IdeaVim Mode Widget"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun createWidget(project: Project): StatusBarWidget {
 | 
			
		||||
    return VimModeWidget(project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isAvailable(project: Project): Boolean {
 | 
			
		||||
    return VimPlugin.isEnabled()
 | 
			
		||||
      && injector.globalIjOptions().showmodewidget
 | 
			
		||||
      && !project.isDisposed
 | 
			
		||||
      && FileEditorManager.getInstance(project).hasOpenFiles()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public val modeWidgetOptionListener: VimWidgetListener = VimWidgetListener { updateModeWidget() }
 | 
			
		||||
@@ -1,368 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2024 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.ui.widgets.mode
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.ui.LafManager
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.ui.DialogPanel
 | 
			
		||||
import com.intellij.openapi.ui.popup.JBPopup
 | 
			
		||||
import com.intellij.openapi.ui.popup.JBPopupFactory
 | 
			
		||||
import com.intellij.ui.components.JBCheckBox
 | 
			
		||||
import com.intellij.ui.components.JBScrollPane
 | 
			
		||||
import com.intellij.ui.components.JBTabbedPane
 | 
			
		||||
import com.intellij.ui.content.ContentFactory
 | 
			
		||||
import com.intellij.ui.dsl.builder.Cell
 | 
			
		||||
import com.intellij.ui.dsl.builder.RowLayout
 | 
			
		||||
import com.intellij.ui.dsl.builder.TopGap
 | 
			
		||||
import com.intellij.ui.dsl.builder.bindItem
 | 
			
		||||
import com.intellij.ui.dsl.builder.bindSelected
 | 
			
		||||
import com.intellij.ui.dsl.builder.bindText
 | 
			
		||||
import com.intellij.ui.dsl.builder.panel
 | 
			
		||||
import com.intellij.ui.dsl.builder.selected
 | 
			
		||||
import com.intellij.ui.dsl.builder.toNullableProperty
 | 
			
		||||
import com.intellij.ui.layout.not
 | 
			
		||||
import com.intellij.util.Alarm
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.asVimInt
 | 
			
		||||
import java.awt.BorderLayout
 | 
			
		||||
import java.awt.Dimension
 | 
			
		||||
import java.awt.FlowLayout
 | 
			
		||||
import javax.swing.BorderFactory
 | 
			
		||||
import javax.swing.JButton
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
import javax.swing.JLabel
 | 
			
		||||
import javax.swing.JPanel
 | 
			
		||||
import kotlin.properties.ReadWriteProperty
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
 | 
			
		||||
public class ModeWidgetPopup : AnAction() {
 | 
			
		||||
  public override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
    val project = e.project ?: return
 | 
			
		||||
    val popup = createPopup() ?: return
 | 
			
		||||
    popup.showCenteredInCurrentWindow(project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
    @Volatile
 | 
			
		||||
    private var currentPopup: JBPopup? = null
 | 
			
		||||
 | 
			
		||||
    public fun createPopup(): JBPopup? {
 | 
			
		||||
      synchronized(this) {
 | 
			
		||||
        if (currentPopup?.isDisposed == false) return null
 | 
			
		||||
        val mainPanel = JPanel(BorderLayout())
 | 
			
		||||
        val buttonPanel = JPanel(FlowLayout(FlowLayout.RIGHT))
 | 
			
		||||
 | 
			
		||||
        val applyButton = JButton("Apply").apply { isEnabled = false }
 | 
			
		||||
        val cancelButton = JButton("Close")
 | 
			
		||||
        buttonPanel.add(applyButton)
 | 
			
		||||
        buttonPanel.add(cancelButton)
 | 
			
		||||
        mainPanel.add(buttonPanel, BorderLayout.SOUTH)
 | 
			
		||||
 | 
			
		||||
        val tabbedPane = JBTabbedPane()
 | 
			
		||||
        val lightThemeSettings = createPanel(getWidgetThemeColors(true))
 | 
			
		||||
        val darkThemeSettings = createPanel(getWidgetThemeColors(false))
 | 
			
		||||
        tabbedPane.addTab(MessageHelper.getMessage("widget.mode.popup.tab.light"), lightThemeSettings.addScrollPane())
 | 
			
		||||
        tabbedPane.addTab(MessageHelper.getMessage("widget.mode.popup.tab.dark"), darkThemeSettings.addScrollPane())
 | 
			
		||||
        tabbedPane.preferredSize = Dimension(300, 300)
 | 
			
		||||
        for (i in 0 until tabbedPane.tabCount) {
 | 
			
		||||
          val label = JLabel(tabbedPane.getTitleAt(i), JLabel.CENTER)
 | 
			
		||||
          label.preferredSize = Dimension(126, tabbedPane.getTabComponentAt(i).preferredSize.height)
 | 
			
		||||
          tabbedPane.setTabComponentAt(i, label)
 | 
			
		||||
        }
 | 
			
		||||
        tabbedPane.selectedIndex = if (LafManager.getInstance().currentUIThemeLookAndFeel.isDark) 1 else 0
 | 
			
		||||
        mainPanel.add(tabbedPane, BorderLayout.CENTER)
 | 
			
		||||
 | 
			
		||||
        val popupContent = ContentFactory.getInstance().createContent(mainPanel, "", false).component
 | 
			
		||||
        val popup = JBPopupFactory.getInstance()
 | 
			
		||||
          .createComponentPopupBuilder(popupContent, popupContent)
 | 
			
		||||
          .setTitle(MessageHelper.getMessage("widget.mode.popup.title"))
 | 
			
		||||
          .setResizable(true)
 | 
			
		||||
          .setMovable(true)
 | 
			
		||||
          .setRequestFocus(true)
 | 
			
		||||
          .setCancelOnClickOutside(false)
 | 
			
		||||
          .setCancelKeyEnabled(false)
 | 
			
		||||
          .createPopup()
 | 
			
		||||
 | 
			
		||||
        applyButton.addActionListener {
 | 
			
		||||
          lightThemeSettings.apply()
 | 
			
		||||
          darkThemeSettings.apply()
 | 
			
		||||
          repaintModeWidget()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        cancelButton.addActionListener {
 | 
			
		||||
          popup.cancel()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val alarm = Alarm(popup)
 | 
			
		||||
        fun updateApplyButtonVisibility() {
 | 
			
		||||
          alarm.addRequest({
 | 
			
		||||
            applyButton.isEnabled = lightThemeSettings.isModified() || darkThemeSettings.isModified()
 | 
			
		||||
            updateApplyButtonVisibility()
 | 
			
		||||
          }, 500L)
 | 
			
		||||
        }
 | 
			
		||||
        updateApplyButtonVisibility()
 | 
			
		||||
 | 
			
		||||
        currentPopup = popup
 | 
			
		||||
        return currentPopup
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getWidgetThemeColors(isLight: Boolean): ModeColors {
 | 
			
		||||
      val keyPostfix = if (isLight) "_light" else "_dark"
 | 
			
		||||
      return ModeColors(
 | 
			
		||||
        "widget_mode_is_full_customization$keyPostfix",
 | 
			
		||||
        "widget_mode_theme$keyPostfix",
 | 
			
		||||
         "widget_mode_normal_background$keyPostfix",
 | 
			
		||||
         "widget_mode_normal_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_insert_background$keyPostfix",
 | 
			
		||||
         "widget_mode_insert_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_replace_background$keyPostfix",
 | 
			
		||||
         "widget_mode_replace_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_command_background$keyPostfix",
 | 
			
		||||
         "widget_mode_command_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_visual_background$keyPostfix",
 | 
			
		||||
         "widget_mode_visual_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_visual_line_background$keyPostfix",
 | 
			
		||||
         "widget_mode_visual_line_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_visual_block_background$keyPostfix",
 | 
			
		||||
         "widget_mode_visual_block_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_select_background$keyPostfix",
 | 
			
		||||
         "widget_mode_select_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_select_line_background$keyPostfix",
 | 
			
		||||
         "widget_mode_select_line_foreground$keyPostfix",
 | 
			
		||||
         "widget_mode_select_block_background$keyPostfix",
 | 
			
		||||
         "widget_mode_select_block_foreground$keyPostfix",
 | 
			
		||||
      )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun createPanel(modeColors: ModeColors): DialogPanel {
 | 
			
		||||
      val panel = panel {
 | 
			
		||||
        lateinit var advancedSettings: Cell<JBCheckBox>
 | 
			
		||||
        row {
 | 
			
		||||
          advancedSettings = checkBox(MessageHelper.getMessage("widget.mode.popup.field.advanced.settings")).bindSelected(modeColors::isFullCustomization)
 | 
			
		||||
        }
 | 
			
		||||
        group {
 | 
			
		||||
          row {
 | 
			
		||||
            label(MessageHelper.getMessage("widget.mode.popup.field.theme"))
 | 
			
		||||
            comboBox(ModeWidgetTheme.values().toList()).bindItem(modeColors::theme.toNullableProperty())
 | 
			
		||||
          }
 | 
			
		||||
          row { browserLink("Suggest your theme", "https://youtrack.jetbrains.com/issue/VIM-1377/Normal-mode-needs-to-be-more-obvious") }
 | 
			
		||||
        }.topGap(TopGap.NONE).visibleIf(!advancedSettings.selected)
 | 
			
		||||
        group(MessageHelper.getMessage("widget.mode.popup.group.title.full.customization")) {
 | 
			
		||||
          row { text(MessageHelper.getMessage("widget.mode.popup.color.instruction")) }
 | 
			
		||||
 | 
			
		||||
          group(MessageHelper.getMessage("widget.mode.popup.group.normal.title")) {
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
              textField().bindText(modeColors::normalBg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
              textField().bindText(modeColors::normalFg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          group(MessageHelper.getMessage("widget.mode.popup.group.insert.title")) {
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
              textField().bindText(modeColors::insertBg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
              textField().bindText(modeColors::insertFg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          group(MessageHelper.getMessage("widget.mode.popup.group.replace.title")) {
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
              textField().bindText(modeColors::replaceBg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
              textField().bindText(modeColors::replaceFg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          group(MessageHelper.getMessage("widget.mode.popup.group.command.title")) {
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
              textField().bindText(modeColors::commandBg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
              textField().bindText(modeColors::commandFg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          group(MessageHelper.getMessage("widget.mode.popup.group.visual.title")) {
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
              textField().bindText(modeColors::visualBg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
              textField().bindText(modeColors::visualFg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
 | 
			
		||||
            collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.line.title")) {
 | 
			
		||||
              row { text(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.instruction")) }
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
                textField().bindText(modeColors::visualLineBg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
                textField().bindText(modeColors::visualLineFg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.block.title")) {
 | 
			
		||||
              row { text(MessageHelper.getMessage("widget.mode.popup.group.visual.subgroup.instruction")) }
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
                textField().bindText(modeColors::visualBlockBg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
                textField().bindText(modeColors::visualBlockFg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          group(MessageHelper.getMessage("widget.mode.popup.group.select.title")) {
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
              textField().bindText(modeColors::selectBg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            row {
 | 
			
		||||
              label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
              textField().bindText(modeColors::selectFg)
 | 
			
		||||
            }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
 | 
			
		||||
            collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.line.title")) {
 | 
			
		||||
              row { text(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.instruction")) }
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
                textField().bindText(modeColors::selectLineBg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
                textField().bindText(modeColors::selectLineFg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            collapsibleGroup(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.block.title")) {
 | 
			
		||||
              row { text(MessageHelper.getMessage("widget.mode.popup.group.select.subgroup.instruction")) }
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.background"))
 | 
			
		||||
                textField().bindText(modeColors::selectBlockBg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
              row {
 | 
			
		||||
                label(MessageHelper.getMessage("widget.mode.popup.field.foreground"))
 | 
			
		||||
                textField().bindText(modeColors::selectBlockFg)
 | 
			
		||||
              }.layout(RowLayout.PARENT_GRID)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }.topGap(TopGap.NONE).visibleIf(advancedSettings.selected)
 | 
			
		||||
      }
 | 
			
		||||
      return panel
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun JComponent.addScrollPane(): JComponent {
 | 
			
		||||
      val scrollPane = JBScrollPane(this, JBScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JBScrollPane.HORIZONTAL_SCROLLBAR_NEVER)
 | 
			
		||||
      scrollPane.border = BorderFactory.createEmptyBorder()
 | 
			
		||||
      return scrollPane
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class ModeColors(
 | 
			
		||||
    isFullCustomizationKey: String, themeKey: String,
 | 
			
		||||
    normalBgKey: String, normalFgKey: String,
 | 
			
		||||
    insertBgKey: String, insertFgKey: String,
 | 
			
		||||
    replaceBgKey: String, replaceFgKey: String,
 | 
			
		||||
    commandBgKey: String, commandFgKey: String,
 | 
			
		||||
    visualBgKey: String, visualFgKey: String, visualLineBgKey: String, visualLineFgKey: String, visualBlockBgKey: String, visualBlockFgKey: String,
 | 
			
		||||
    selectBgKey: String, selectFgKey: String, selectLineBgKey: String, selectLineFgKey: String, selectBlockBgKey: String, selectBlockFgKey: String
 | 
			
		||||
  ) {
 | 
			
		||||
    var isFullCustomization: Boolean by VimScopeBooleanVariable(isFullCustomizationKey)
 | 
			
		||||
    var theme: ModeWidgetTheme by VimScopeThemeVariable(themeKey)
 | 
			
		||||
    var normalBg: String by VimScopeStringVariable(normalBgKey)
 | 
			
		||||
    var normalFg: String by VimScopeStringVariable(normalFgKey)
 | 
			
		||||
    var insertBg: String by VimScopeStringVariable(insertBgKey)
 | 
			
		||||
    var insertFg: String by VimScopeStringVariable(insertFgKey)
 | 
			
		||||
    var replaceBg: String by VimScopeStringVariable(replaceBgKey)
 | 
			
		||||
    var replaceFg: String by VimScopeStringVariable(replaceFgKey)
 | 
			
		||||
    var commandBg: String by VimScopeStringVariable(commandBgKey)
 | 
			
		||||
    var commandFg: String by VimScopeStringVariable(commandFgKey)
 | 
			
		||||
    var visualBg: String by VimScopeStringVariable(visualBgKey)
 | 
			
		||||
    var visualFg: String by VimScopeStringVariable(visualFgKey)
 | 
			
		||||
    var visualLineBg: String by VimScopeStringVariable(visualLineBgKey)
 | 
			
		||||
    var visualLineFg: String by VimScopeStringVariable(visualLineFgKey)
 | 
			
		||||
    var visualBlockBg: String by VimScopeStringVariable(visualBlockBgKey)
 | 
			
		||||
    var visualBlockFg: String by VimScopeStringVariable(visualBlockFgKey)
 | 
			
		||||
    var selectBg: String by VimScopeStringVariable(selectBgKey)
 | 
			
		||||
    var selectFg: String by VimScopeStringVariable(selectFgKey)
 | 
			
		||||
    var selectLineBg: String by VimScopeStringVariable(selectLineBgKey)
 | 
			
		||||
    var selectLineFg: String by VimScopeStringVariable(selectLineFgKey)
 | 
			
		||||
    var selectBlockBg: String by VimScopeStringVariable(selectBlockBgKey)
 | 
			
		||||
    var selectBlockFg: String by VimScopeStringVariable(selectBlockFgKey)
 | 
			
		||||
 | 
			
		||||
    private class VimScopeBooleanVariable(private var key: String): ReadWriteProperty<ModeColors, Boolean> {
 | 
			
		||||
      override fun getValue(thisRef: ModeColors, property: KProperty<*>): Boolean {
 | 
			
		||||
        return injector.variableService.getVimVariable(key)?.asBoolean() ?: false
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: Boolean) {
 | 
			
		||||
        injector.variableService.storeVimVariable(key, value.asVimInt())
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class VimScopeStringVariable(private var key: String): ReadWriteProperty<ModeColors, String> {
 | 
			
		||||
      override fun getValue(thisRef: ModeColors, property: KProperty<*>): String {
 | 
			
		||||
        return injector.variableService.getVimVariable(key)?.asString() ?: ""
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: String) {
 | 
			
		||||
        injector.variableService.storeVimVariable(key, VimString(value))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private class VimScopeThemeVariable(private var key: String): ReadWriteProperty<ModeColors, ModeWidgetTheme> {
 | 
			
		||||
      override fun getValue(thisRef: ModeColors, property: KProperty<*>): ModeWidgetTheme {
 | 
			
		||||
        val themeString = injector.variableService.getVimVariable(key)?.asString() ?: return ModeWidgetTheme.getDefaultTheme()
 | 
			
		||||
        return ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      override fun setValue(thisRef: ModeColors, property: KProperty<*>, value: ModeWidgetTheme) {
 | 
			
		||||
        injector.variableService.storeVimVariable(key, VimString(value.toString()))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public enum class ModeWidgetTheme(private var value: String) {
 | 
			
		||||
  TEST("Nord-Aurora (testing, will be removed)"),
 | 
			
		||||
  COLORLESS("Colorless");
 | 
			
		||||
 | 
			
		||||
  override fun toString(): String {
 | 
			
		||||
    return value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public companion object {
 | 
			
		||||
    public fun parseString(string: String): ModeWidgetTheme? {
 | 
			
		||||
      return ModeWidgetTheme.values().firstOrNull { it.value == string }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public fun getDefaultTheme(): ModeWidgetTheme = TEST
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,129 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2024 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.ui.widgets.mode
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.ui.LafManager
 | 
			
		||||
import com.intellij.util.ui.UIUtil
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import java.awt.Color
 | 
			
		||||
 | 
			
		||||
public fun getModeBackground(mode: Mode?): Color {
 | 
			
		||||
  val isLight = !LafManager.getInstance().currentUIThemeLookAndFeel.isDark
 | 
			
		||||
  val keyPostfix = if (isLight) "_light" else "_dark"
 | 
			
		||||
  if (injector.variableService.getVimVariable("widget_mode_is_full_customization$keyPostfix")?.asBoolean() != true) {
 | 
			
		||||
    val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
 | 
			
		||||
    val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
 | 
			
		||||
    when (theme) {
 | 
			
		||||
      ModeWidgetTheme.TEST -> {
 | 
			
		||||
        return when (mode) {
 | 
			
		||||
          Mode.INSERT -> Color.decode("#D08770")
 | 
			
		||||
          Mode.REPLACE -> Color.decode("#EBCB8B")
 | 
			
		||||
          is Mode.NORMAL -> Color.decode("#BF616A")
 | 
			
		||||
          is Mode.CMD_LINE -> Color.decode("#A3BE8C")
 | 
			
		||||
          is Mode.VISUAL -> Color.decode("#B48EAD")
 | 
			
		||||
          is Mode.SELECT -> Color.decode("#B48EAD")
 | 
			
		||||
          is Mode.OP_PENDING, null -> UIUtil.getPanelBackground()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      ModeWidgetTheme.COLORLESS -> {
 | 
			
		||||
        return UIUtil.getPanelBackground()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    val colorString = when (mode) {
 | 
			
		||||
      Mode.INSERT -> injector.variableService.getVimVariable("widget_mode_insert_background$keyPostfix")
 | 
			
		||||
      Mode.REPLACE -> injector.variableService.getVimVariable("widget_mode_replace_background$keyPostfix")
 | 
			
		||||
      is Mode.NORMAL -> injector.variableService.getVimVariable("widget_mode_normal_background$keyPostfix")
 | 
			
		||||
      is Mode.CMD_LINE -> injector.variableService.getVimVariable("widget_mode_command_background$keyPostfix")
 | 
			
		||||
      is Mode.VISUAL -> {
 | 
			
		||||
        val visualModeBackground = injector.variableService.getVimVariable("widget_mode_visual_background$keyPostfix")
 | 
			
		||||
        when (mode.selectionType) {
 | 
			
		||||
          SelectionType.CHARACTER_WISE -> visualModeBackground
 | 
			
		||||
          SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_visual_line_background$keyPostfix") ?: visualModeBackground
 | 
			
		||||
          SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_visual_block_background$keyPostfix") ?: visualModeBackground
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      is Mode.SELECT -> {
 | 
			
		||||
        val selectModeBackground = injector.variableService.getVimVariable("widget_mode_select_background$keyPostfix")
 | 
			
		||||
        when (mode.selectionType) {
 | 
			
		||||
          SelectionType.CHARACTER_WISE -> selectModeBackground
 | 
			
		||||
          SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_select_line_background$keyPostfix") ?: selectModeBackground
 | 
			
		||||
          SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_select_block_background$keyPostfix") ?: selectModeBackground
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      is Mode.OP_PENDING, null -> null
 | 
			
		||||
    }?.asString()
 | 
			
		||||
    val defaultColor = UIUtil.getPanelBackground()
 | 
			
		||||
    val color = when (colorString) {
 | 
			
		||||
      "v:status_bar_bg" -> UIUtil.getPanelBackground()
 | 
			
		||||
      "v:status_bar_fg" -> UIUtil.getLabelForeground()
 | 
			
		||||
      else -> {
 | 
			
		||||
        if (colorString == null) {
 | 
			
		||||
          defaultColor
 | 
			
		||||
        } else {
 | 
			
		||||
          try { Color.decode(colorString) } catch (e: Exception) { defaultColor }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return color
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun getModeForeground(mode: Mode?): Color {
 | 
			
		||||
  val isLight = !LafManager.getInstance().currentUIThemeLookAndFeel.isDark
 | 
			
		||||
  val keyPostfix = if (isLight) "_light" else "_dark"
 | 
			
		||||
  if (injector.variableService.getVimVariable("widget_mode_is_full_customization$keyPostfix")?.asBoolean() != true) {
 | 
			
		||||
    val themeString = injector.variableService.getVimVariable("widget_mode_theme$keyPostfix")?.asString() ?: ""
 | 
			
		||||
    val theme = ModeWidgetTheme.parseString(themeString) ?: ModeWidgetTheme.getDefaultTheme()
 | 
			
		||||
    return when (theme) {
 | 
			
		||||
      ModeWidgetTheme.TEST -> Color.decode("#2E3440")
 | 
			
		||||
      ModeWidgetTheme.COLORLESS -> UIUtil.getLabelForeground()
 | 
			
		||||
    }
 | 
			
		||||
  } else {
 | 
			
		||||
    val colorString = when (mode) {
 | 
			
		||||
      Mode.INSERT -> injector.variableService.getVimVariable("widget_mode_insert_foreground$keyPostfix")
 | 
			
		||||
      Mode.REPLACE -> injector.variableService.getVimVariable("widget_mode_replace_foreground$keyPostfix")
 | 
			
		||||
      is Mode.NORMAL -> injector.variableService.getVimVariable("widget_mode_normal_foreground$keyPostfix")
 | 
			
		||||
      is Mode.CMD_LINE -> injector.variableService.getVimVariable("widget_mode_command_foreground$keyPostfix")
 | 
			
		||||
      is Mode.VISUAL -> {
 | 
			
		||||
        val visualModeBackground = injector.variableService.getVimVariable("widget_mode_visual_foreground$keyPostfix")
 | 
			
		||||
        when (mode.selectionType) {
 | 
			
		||||
          SelectionType.CHARACTER_WISE -> visualModeBackground
 | 
			
		||||
          SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_visual_line_foreground$keyPostfix") ?: visualModeBackground
 | 
			
		||||
          SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_visual_block_foreground$keyPostfix") ?: visualModeBackground
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      is Mode.SELECT -> {
 | 
			
		||||
        val selectModeBackground = injector.variableService.getVimVariable("widget_mode_select_foreground$keyPostfix")
 | 
			
		||||
        when (mode.selectionType) {
 | 
			
		||||
          SelectionType.CHARACTER_WISE -> selectModeBackground
 | 
			
		||||
          SelectionType.LINE_WISE -> injector.variableService.getVimVariable("widget_mode_select_line_foreground$keyPostfix") ?: selectModeBackground
 | 
			
		||||
          SelectionType.BLOCK_WISE -> injector.variableService.getVimVariable("widget_mode_select_block_foreground$keyPostfix") ?: selectModeBackground
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      is Mode.OP_PENDING, null -> null
 | 
			
		||||
    }?.asString()
 | 
			
		||||
    val defaultColor = UIUtil.getLabelForeground()
 | 
			
		||||
    val color = when (colorString) {
 | 
			
		||||
      "v:status_bar_bg" -> UIUtil.getPanelBackground()
 | 
			
		||||
      "v:status_bar_fg" -> UIUtil.getLabelForeground()
 | 
			
		||||
      else -> {
 | 
			
		||||
        if (colorString == null) {
 | 
			
		||||
          defaultColor
 | 
			
		||||
        } else {
 | 
			
		||||
          try { Color.decode(colorString) } catch (e: Exception) { defaultColor }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return color
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -1,176 +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.ui.widgets.mode
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager
 | 
			
		||||
import com.intellij.openapi.wm.CustomStatusBarWidget
 | 
			
		||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
 | 
			
		||||
import com.intellij.openapi.wm.WindowManager
 | 
			
		||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
 | 
			
		||||
import com.intellij.ui.awt.RelativePoint
 | 
			
		||||
import com.intellij.ui.components.JBLabel
 | 
			
		||||
import com.intellij.ui.util.width
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetFocusListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetModeListener
 | 
			
		||||
import java.awt.Dimension
 | 
			
		||||
import java.awt.Point
 | 
			
		||||
import java.awt.event.MouseAdapter
 | 
			
		||||
import java.awt.event.MouseEvent
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
 | 
			
		||||
public class VimModeWidget(public val project: Project) : CustomStatusBarWidget, VimStatusBarWidget {
 | 
			
		||||
  private companion object {
 | 
			
		||||
    private const val INSERT = "INSERT"
 | 
			
		||||
    private const val NORMAL = "NORMAL"
 | 
			
		||||
    private const val REPLACE = "REPLACE"
 | 
			
		||||
    private const val COMMAND = "COMMAND"
 | 
			
		||||
    private const val VISUAL = "VISUAL"
 | 
			
		||||
    private const val VISUAL_LINE = "V-LINE"
 | 
			
		||||
    private const val VISUAL_BLOCK = "V-BLOCK"
 | 
			
		||||
    private const val SELECT = "SELECT"
 | 
			
		||||
    private const val SELECT_LINE = "S-LINE"
 | 
			
		||||
    private const val SELECT_BLOCK = "S-BLOCK"
 | 
			
		||||
  }
 | 
			
		||||
  private val useColors = injector.globalIjOptions().colorfulmodewidget
 | 
			
		||||
  private val label = JBLabelWiderThan(setOf(REPLACE)).apply {
 | 
			
		||||
    isOpaque = useColors
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    val mode = getFocusedEditor(project)?.vim?.mode
 | 
			
		||||
    updateLabel(mode)
 | 
			
		||||
 | 
			
		||||
    injector.listenersNotifier.apply {
 | 
			
		||||
      modeChangeListeners.add(ModeWidgetModeListener(this@VimModeWidget))
 | 
			
		||||
      myEditorListeners.add(ModeWidgetFocusListener(this@VimModeWidget))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    label.addMouseListener(object : MouseAdapter() {
 | 
			
		||||
      override fun mouseClicked(e: MouseEvent) {
 | 
			
		||||
        val popup = ModeWidgetPopup.createPopup() ?: return
 | 
			
		||||
        val dimension = popup.content.preferredSize
 | 
			
		||||
 | 
			
		||||
        val widgetLocation = e.component.locationOnScreen
 | 
			
		||||
        popup.show(RelativePoint(Point(
 | 
			
		||||
          widgetLocation.x + e.component.width - dimension.width,
 | 
			
		||||
          widgetLocation.y - dimension.height,
 | 
			
		||||
        )))
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun ID(): String {
 | 
			
		||||
    return ModeWidgetFactory.ID
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getComponent(): JComponent {
 | 
			
		||||
    return label
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun updateWidget() {
 | 
			
		||||
    val mode = getFocusedEditor(project)?.vim?.mode
 | 
			
		||||
    updateWidget(mode)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun updateWidget(mode: Mode?) {
 | 
			
		||||
    updateLabel(mode)
 | 
			
		||||
    updateWidgetInStatusBar(ModeWidgetFactory.ID, project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun updateLabel(mode: Mode?) {
 | 
			
		||||
    label.text = getModeText(mode)
 | 
			
		||||
    if (useColors) {
 | 
			
		||||
      label.foreground = getModeForeground(mode)
 | 
			
		||||
      label.background = getModeBackground(mode)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getFocusedEditor(project: Project): Editor? {
 | 
			
		||||
    val fileEditorManager = FileEditorManager.getInstance(project)
 | 
			
		||||
    return fileEditorManager.selectedTextEditor
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getModeText(mode: Mode?): String? {
 | 
			
		||||
    return when (mode) {
 | 
			
		||||
      Mode.INSERT -> INSERT
 | 
			
		||||
      Mode.REPLACE -> REPLACE
 | 
			
		||||
      is Mode.NORMAL -> NORMAL
 | 
			
		||||
      is Mode.CMD_LINE -> COMMAND
 | 
			
		||||
      is Mode.VISUAL -> getVisualModeText(mode)
 | 
			
		||||
      is Mode.SELECT -> getSelectModeText(mode)
 | 
			
		||||
      is Mode.OP_PENDING, null -> null
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getVisualModeText(mode: Mode.VISUAL) = when (mode.selectionType) {
 | 
			
		||||
    SelectionType.CHARACTER_WISE -> VISUAL
 | 
			
		||||
    SelectionType.LINE_WISE -> VISUAL_LINE
 | 
			
		||||
    SelectionType.BLOCK_WISE -> VISUAL_BLOCK
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getSelectModeText(mode: Mode.SELECT) = when (mode.selectionType) {
 | 
			
		||||
    SelectionType.CHARACTER_WISE -> SELECT
 | 
			
		||||
    SelectionType.LINE_WISE -> SELECT_LINE
 | 
			
		||||
    SelectionType.BLOCK_WISE -> SELECT_BLOCK
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class JBLabelWiderThan(private val words: Collection<String>): JBLabel("", CENTER) {
 | 
			
		||||
    private val wordWidth: Int
 | 
			
		||||
      get() {
 | 
			
		||||
        val fontMetrics = getFontMetrics(font)
 | 
			
		||||
        return words.maxOfOrNull { fontMetrics.stringWidth(it) } ?: 0
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
    override fun getMinimumSize(): Dimension {
 | 
			
		||||
      val minimumSize = super.getMinimumSize()
 | 
			
		||||
      return Dimension(max(minimumSize.width, wordWidth + insets.width), minimumSize.height)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getPreferredSize(): Dimension {
 | 
			
		||||
      val preferredSize = super.getPreferredSize()
 | 
			
		||||
      return Dimension(max(preferredSize.width, wordWidth + insets.width), preferredSize.height)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun getMaximumSize(): Dimension {
 | 
			
		||||
      val maximumSize = super.getMaximumSize()
 | 
			
		||||
      return Dimension(max(maximumSize.width, wordWidth + insets.width), maximumSize.height)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun updateModeWidget() {
 | 
			
		||||
  val factory = StatusBarWidgetFactory.EP_NAME.findExtension(ModeWidgetFactory::class.java) ?: return
 | 
			
		||||
  for (project in ProjectManager.getInstance().openProjects) {
 | 
			
		||||
    val statusBarWidgetsManager = project.service<StatusBarWidgetsManager>()
 | 
			
		||||
    statusBarWidgetsManager.updateWidget(factory)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun repaintModeWidget() {
 | 
			
		||||
  for (project in ProjectManager.getInstance().openProjects) {
 | 
			
		||||
    val widgets = WindowManager.getInstance()?.getStatusBar(project)?.allWidgets ?: continue
 | 
			
		||||
 | 
			
		||||
    for (widget in widgets) {
 | 
			
		||||
      if (widget is VimModeWidget) {
 | 
			
		||||
        widget.updateWidget()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,59 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2024 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.ui.widgets.mode
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.wm.WindowManager
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
public interface VimStatusBarWidget {
 | 
			
		||||
  public fun updateWidgetInStatusBar(widgetID: String, project: Project?) {
 | 
			
		||||
    if (project == null) return
 | 
			
		||||
    val windowManager = WindowManager.getInstance()
 | 
			
		||||
    windowManager.getStatusBar(project)?.updateWidget(widgetID) ?: run {
 | 
			
		||||
      TimerWithRetriesTask(500L, 50) {
 | 
			
		||||
        val statusBar = windowManager.getStatusBar(project) ?: return@TimerWithRetriesTask false
 | 
			
		||||
        statusBar.updateWidget(widgetID)
 | 
			
		||||
        return@TimerWithRetriesTask true
 | 
			
		||||
      }.execute()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * A task that may be used to address issues with initialization in the Platform, executing code with a reasonable number of retries and a reasonable period.
 | 
			
		||||
 * Clearly, this is a workaround and its use should be avoided when possible.
 | 
			
		||||
 *
 | 
			
		||||
 * Why is it needed for widgets?
 | 
			
		||||
 * In a single project environment, it is not necessary since the status bar is initialized before the editor opens.
 | 
			
		||||
 * However, in multi-project setups, the editor window is opened before the status bar initialization.
 | 
			
		||||
 * And this tasks tries to loops until status bar creation in order to notify it about opened editor.
 | 
			
		||||
 */
 | 
			
		||||
private class TimerWithRetriesTask(
 | 
			
		||||
  private val period: Long,
 | 
			
		||||
  private val retriesLimit: Int,
 | 
			
		||||
  private val block: () -> Boolean,
 | 
			
		||||
) {
 | 
			
		||||
  private val timer = Timer()
 | 
			
		||||
 | 
			
		||||
  fun execute() {
 | 
			
		||||
    timer.schedule(object : TimerTask() {
 | 
			
		||||
      private var counter = 0
 | 
			
		||||
 | 
			
		||||
      override fun run() {
 | 
			
		||||
        if (counter >= retriesLimit) {
 | 
			
		||||
          timer.cancel()
 | 
			
		||||
        } else {
 | 
			
		||||
          if (this@TimerWithRetriesTask.block()) timer.cancel()
 | 
			
		||||
          counter++
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }, 0L, period)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,53 +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.ui.widgets.mode.listeners
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.common.EditorListener
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.updateModeWidget
 | 
			
		||||
 | 
			
		||||
internal class ModeWidgetFocusListener(private val modeWidget: VimModeWidget): EditorListener {
 | 
			
		||||
  override fun created(editor: VimEditor) {
 | 
			
		||||
    updateModeWidget()
 | 
			
		||||
    val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
 | 
			
		||||
    modeWidget.updateWidget(mode)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun released(editor: VimEditor) {
 | 
			
		||||
    updateModeWidget()
 | 
			
		||||
    val focusedEditor = getFocusedEditorForProject(editor.ij.project)
 | 
			
		||||
    if (focusedEditor == null || focusedEditor == editor.ij) {
 | 
			
		||||
      modeWidget.updateWidget(null)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun focusGained(editor: VimEditor) {
 | 
			
		||||
    if (editor.ij.project != modeWidget.project) return
 | 
			
		||||
    val mode = editor.mode
 | 
			
		||||
    modeWidget.updateWidget(mode)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun focusLost(editor: VimEditor) {
 | 
			
		||||
    val mode = getFocusedEditorForProject(editor.ij.project)?.vim?.mode
 | 
			
		||||
    modeWidget.updateWidget(mode)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun getFocusedEditorForProject(editorProject: Project?): Editor? {
 | 
			
		||||
    if (editorProject != modeWidget.project) return null
 | 
			
		||||
    val fileEditorManager = FileEditorManager.getInstance(editorProject)
 | 
			
		||||
    return fileEditorManager.selectedTextEditor
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,25 +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.ui.widgets.mode.listeners
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.common.ModeChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.VimModeWidget
 | 
			
		||||
 | 
			
		||||
internal class ModeWidgetModeListener(private val modeWidget: VimModeWidget): ModeChangeListener {
 | 
			
		||||
  override fun modeChanged(editor: VimEditor, oldMode: Mode) {
 | 
			
		||||
    val editorMode = editor.mode
 | 
			
		||||
    if (editorMode !is Mode.OP_PENDING && editor.ij.project == modeWidget.project) {
 | 
			
		||||
      modeWidget.updateWidget(editorMode)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -36,28 +36,31 @@ public val VimEditor.isIdeaRefactorModeSelect: Boolean
 | 
			
		||||
 | 
			
		||||
internal object IdeaRefactorModeHelper {
 | 
			
		||||
 | 
			
		||||
  sealed interface Action {
 | 
			
		||||
    object RemoveSelection : Action
 | 
			
		||||
    class SetMode(val newMode: Mode) : Action
 | 
			
		||||
    class MoveToOffset(val newOffset: Int) : Action
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun applyCorrections(corrections: List<Action>, editor: Editor) {
 | 
			
		||||
    val correctionsApplier = {
 | 
			
		||||
      corrections.forEach { correction ->
 | 
			
		||||
        when (correction) {
 | 
			
		||||
          is Action.MoveToOffset -> {
 | 
			
		||||
            editor.caretModel.moveToOffset(correction.newOffset)
 | 
			
		||||
  fun correctSelection(editor: Editor) {
 | 
			
		||||
    val action: () -> Unit = {
 | 
			
		||||
      val mode = editor.vim.mode
 | 
			
		||||
      if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use {
 | 
			
		||||
          editor.selectionModel.removeSelection()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
 | 
			
		||||
        val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim)
 | 
			
		||||
        if (mode.selectionType != autodetectedSubmode) {
 | 
			
		||||
          // Update the submode
 | 
			
		||||
          val newMode = when (mode) {
 | 
			
		||||
            is Mode.SELECT -> mode.copy(selectionType = autodetectedSubmode)
 | 
			
		||||
            is Mode.VISUAL -> mode.copy(selectionType = autodetectedSubmode)
 | 
			
		||||
            else -> error("IdeaVim should be either in visual or select modes")
 | 
			
		||||
          }
 | 
			
		||||
          editor.vim.vimStateMachine.mode = newMode
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
          Action.RemoveSelection -> {
 | 
			
		||||
            SelectionVimListenerSuppressor.lock().use {
 | 
			
		||||
              editor.selectionModel.removeSelection()
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          is Action.SetMode -> {
 | 
			
		||||
            editor.vim.vimStateMachine.mode = correction.newMode
 | 
			
		||||
      if (editor.hasBlockOrUnderscoreCaret()) {
 | 
			
		||||
        TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
 | 
			
		||||
          if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
 | 
			
		||||
            editor.caretModel.moveToOffset(editor.caretModel.offset - 1)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -67,9 +70,7 @@ internal object IdeaRefactorModeHelper {
 | 
			
		||||
    if (lookup != null) {
 | 
			
		||||
      val selStart = editor.selectionModel.selectionStart
 | 
			
		||||
      val selEnd = editor.selectionModel.selectionEnd
 | 
			
		||||
      lookup.performGuardedChange {
 | 
			
		||||
        correctionsApplier()
 | 
			
		||||
      }
 | 
			
		||||
      lookup.performGuardedChange(action)
 | 
			
		||||
      lookup.addLookupListener(object : LookupListener {
 | 
			
		||||
        override fun beforeItemSelected(event: LookupEvent): Boolean {
 | 
			
		||||
          // FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
 | 
			
		||||
@@ -81,41 +82,7 @@ internal object IdeaRefactorModeHelper {
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      correctionsApplier()
 | 
			
		||||
      action()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun calculateCorrections(editor: Editor): List<Action> {
 | 
			
		||||
    val corrections = mutableListOf<Action>()
 | 
			
		||||
    val mode = editor.vim.mode
 | 
			
		||||
    if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
 | 
			
		||||
      corrections.add(Action.RemoveSelection)
 | 
			
		||||
    }
 | 
			
		||||
    if (mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
 | 
			
		||||
      val autodetectedSubmode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor.vim)
 | 
			
		||||
      if (mode.selectionType != autodetectedSubmode) {
 | 
			
		||||
        // Update the submode
 | 
			
		||||
        val newMode = when (mode) {
 | 
			
		||||
          is Mode.SELECT -> mode.copy(selectionType = autodetectedSubmode)
 | 
			
		||||
          is Mode.VISUAL -> mode.copy(selectionType = autodetectedSubmode)
 | 
			
		||||
          else -> error("IdeaVim should be either in visual or select modes")
 | 
			
		||||
        }
 | 
			
		||||
        corrections.add(Action.SetMode(newMode))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (editor.hasBlockOrUnderscoreCaret()) {
 | 
			
		||||
      TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
 | 
			
		||||
        if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
 | 
			
		||||
          corrections.add(Action.MoveToOffset(editor.caretModel.offset - 1))
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return corrections
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun correctSelection(editor: Editor) {
 | 
			
		||||
    val corrections = calculateCorrections(editor)
 | 
			
		||||
    applyCorrections(corrections, editor)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,10 +8,6 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.vimscript.services
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.PersistentStateComponent
 | 
			
		||||
import com.intellij.openapi.components.RoamingType
 | 
			
		||||
import com.intellij.openapi.components.State
 | 
			
		||||
import com.intellij.openapi.components.Storage
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
 | 
			
		||||
@@ -26,10 +22,8 @@ import com.maddyhome.idea.vim.vimscript.model.datatypes.VimList
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
 | 
			
		||||
import org.jdom.Element
 | 
			
		||||
 | 
			
		||||
@State(name = "VimVariables", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
 | 
			
		||||
internal class IjVariableService : VimVariableServiceBase(), PersistentStateComponent<Element?> {
 | 
			
		||||
internal class IjVariableService : VimVariableServiceBase() {
 | 
			
		||||
  override fun storeVariable(variable: Variable, value: VimDataType, editor: VimEditor, context: ExecutionContext, vimContext: VimLContext) {
 | 
			
		||||
    super.storeVariable(variable, value, editor, context, vimContext)
 | 
			
		||||
 | 
			
		||||
@@ -53,49 +47,4 @@ internal class IjVariableService : VimVariableServiceBase(), PersistentStateComp
 | 
			
		||||
      else -> error("Unexpected")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getState(): Element {
 | 
			
		||||
    val element = Element("variables")
 | 
			
		||||
    saveData(element)
 | 
			
		||||
    return element
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun loadState(state: Element) {
 | 
			
		||||
    readData(state)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun saveData(element: Element) {
 | 
			
		||||
    val vimVariablesElement = Element("vim-variables")
 | 
			
		||||
    for ((key, value) in vimVariables.entries) {
 | 
			
		||||
      if (value is VimString) {
 | 
			
		||||
        val variableElement = Element("variable")
 | 
			
		||||
        variableElement.setAttribute("key", key)
 | 
			
		||||
        variableElement.setAttribute("value", value.value)
 | 
			
		||||
        variableElement.setAttribute("type", "string")
 | 
			
		||||
        vimVariablesElement.addContent(variableElement)
 | 
			
		||||
      } else if (value is VimInt) {
 | 
			
		||||
        val variableElement = Element("variable")
 | 
			
		||||
        variableElement.setAttribute("key", key)
 | 
			
		||||
        variableElement.setAttribute("value", value.value.toString())
 | 
			
		||||
        variableElement.setAttribute("type", "int")
 | 
			
		||||
        vimVariablesElement.addContent(variableElement)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    element.addContent(vimVariablesElement)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun readData(element: Element) {
 | 
			
		||||
    val vimVariablesElement = element.getChild("vim-variables")
 | 
			
		||||
    val variableElements = vimVariablesElement.getChildren("variable")
 | 
			
		||||
    for (variableElement in variableElements) {
 | 
			
		||||
      when (variableElement.getAttributeValue("type")) {
 | 
			
		||||
        "string" -> {
 | 
			
		||||
          vimVariables[variableElement.getAttributeValue("key")] = VimString(variableElement.getAttributeValue("value"))
 | 
			
		||||
        }
 | 
			
		||||
        "int" -> {
 | 
			
		||||
          vimVariables[variableElement.getAttributeValue("key")] = VimInt(variableElement.getAttributeValue("value"))
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,19 +0,0 @@
 | 
			
		||||
<!--
 | 
			
		||||
  ~ Copyright 2003-2024 The IdeaVim authors
 | 
			
		||||
  ~
 | 
			
		||||
  ~ Use of this source code is governed by an MIT-style
 | 
			
		||||
  ~ license that can be found in the LICENSE.txt file or at
 | 
			
		||||
  ~ https://opensource.org/licenses/MIT.
 | 
			
		||||
  -->
 | 
			
		||||
 | 
			
		||||
<idea-plugin>
 | 
			
		||||
  <extensions defaultExtensionNs="com.intellij">
 | 
			
		||||
    <editorActionHandler action="EditorEscape"
 | 
			
		||||
                         implementationClass="com.maddyhome.idea.vim.handler.VimEscForRiderHandler"
 | 
			
		||||
                         id="ideavim-clion-nova-esc"
 | 
			
		||||
                         order="first, before idea.only.escape"/>
 | 
			
		||||
  </extensions>
 | 
			
		||||
  <extensions defaultExtensionNs="IdeaVIM">
 | 
			
		||||
    <clionNovaProvider implementation="com.maddyhome.idea.vim.ide.ClionNovaProviderImpl"/>
 | 
			
		||||
  </extensions>
 | 
			
		||||
</idea-plugin>
 | 
			
		||||
@@ -94,8 +94,6 @@
 | 
			
		||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction" mappingModes="NXO" keys="[m"/>
 | 
			
		||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction" mappingModes="NXO" keys="]M"/>
 | 
			
		||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction" mappingModes="NXO" keys="]m"/>
 | 
			
		||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction" mappingModes="NXO" keys="[s"/>
 | 
			
		||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction" mappingModes="NXO" keys="]s"/>
 | 
			
		||||
    <!-- Text Objects -->
 | 
			
		||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterWordAction" mappingModes="XO" keys="aw"/>
 | 
			
		||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterBigWordAction" mappingModes="XO" keys="aW"/>
 | 
			
		||||
 
 | 
			
		||||
@@ -12,10 +12,6 @@
 | 
			
		||||
              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"
 | 
			
		||||
 
 | 
			
		||||
@@ -29,8 +29,6 @@
 | 
			
		||||
  <!--suppress PluginXmlValidity -->
 | 
			
		||||
  <depends optional="true" config-file="ides/ideavim-withRider.xml">com.intellij.modules.rider</depends>
 | 
			
		||||
  <!--suppress PluginXmlValidity -->
 | 
			
		||||
  <depends optional="true" config-file="ides/ideavim-withClionNova.xml">org.jetbrains.plugins.clion.radler</depends>
 | 
			
		||||
  <!--suppress PluginXmlValidity -->
 | 
			
		||||
  <depends optional="true" config-file="ides/ideavim-withAppCode.xml">com.intellij.modules.appcode</depends>
 | 
			
		||||
  <depends optional="true" config-file="ideavim-withAceJump.xml">AceJump</depends>
 | 
			
		||||
 | 
			
		||||
@@ -57,8 +55,6 @@
 | 
			
		||||
    <extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
 | 
			
		||||
      <with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
 | 
			
		||||
    </extensionPoint>
 | 
			
		||||
 | 
			
		||||
    <extensionPoint interface="com.maddyhome.idea.vim.ide.ClionNovaProvider" dynamic="true" name="clionNovaProvider"/>
 | 
			
		||||
  </extensionPoints>
 | 
			
		||||
 | 
			
		||||
  <extensions defaultExtensionNs="com.intellij">
 | 
			
		||||
@@ -66,9 +62,7 @@
 | 
			
		||||
    <projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
 | 
			
		||||
    <projectService serviceImplementation="com.maddyhome.idea.vim.group.LastTabService"/>
 | 
			
		||||
    <statusBarWidgetFactory id="IdeaVim-Icon" implementation="com.maddyhome.idea.vim.ui.StatusBarIconFactory"/>
 | 
			
		||||
    <statusBarWidgetFactory id="IdeaVim::Mode" implementation="com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetFactory" order="last"/>
 | 
			
		||||
    <statusBarWidgetFactory id="IdeaVim::ShowCmd" implementation="com.maddyhome.idea.vim.ui.ShowCmdStatusBarWidgetFactory" order="first"/>
 | 
			
		||||
    <statusBarWidgetFactory id="IdeaVim::Macro" implementation="com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetFactory"/>
 | 
			
		||||
 | 
			
		||||
    <applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
 | 
			
		||||
 | 
			
		||||
@@ -77,12 +71,11 @@
 | 
			
		||||
         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"/>
 | 
			
		||||
 | 
			
		||||
@@ -124,12 +117,8 @@
 | 
			
		||||
                         id="ideavim-enter-logger"
 | 
			
		||||
                         order="first"/>
 | 
			
		||||
    <editorActionHandler action="EditorStartNewLine"
 | 
			
		||||
                         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"
 | 
			
		||||
                         implementationClass="com.maddyhome.idea.vim.handler.ShiftEnterDetector"
 | 
			
		||||
                         id="ideavim-shift-enter-detector"
 | 
			
		||||
                         order="first"/>
 | 
			
		||||
  </extensions>
 | 
			
		||||
 | 
			
		||||
@@ -152,7 +141,6 @@
 | 
			
		||||
 | 
			
		||||
    <action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction"/>
 | 
			
		||||
    <action id="VimActions" class="com.maddyhome.idea.vim.ui.VimActions"/>
 | 
			
		||||
    <action id="CustomizeModeWidget" class="com.maddyhome.idea.vim.ui.widgets.mode.ModeWidgetPopup"/>
 | 
			
		||||
 | 
			
		||||
    <group id="IdeaVim.ReloadVimRc.group" class="com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup">
 | 
			
		||||
      <action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +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.
 | 
			
		||||
  -->
 | 
			
		||||
 | 
			
		||||
<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>
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 446 B  | 
@@ -84,8 +84,6 @@ action.VimShortcutKeyAction.text=Shortcuts
 | 
			
		||||
action.VimActions.text=Vim Actions
 | 
			
		||||
action.not.found.0=Action not found: {0}
 | 
			
		||||
 | 
			
		||||
action.CustomizeModeWidget.text=Mode Widget Settings
 | 
			
		||||
 | 
			
		||||
action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
 | 
			
		||||
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
 | 
			
		||||
 | 
			
		||||
@@ -131,28 +129,6 @@ action.finish.eap.text=Finish EAP
 | 
			
		||||
# Don't forget to update README if you modify this entry
 | 
			
		||||
action.subscribe.to.eap.text=Subscribe to EAP
 | 
			
		||||
 | 
			
		||||
widget.mode.popup.title=Mode Widget Colors
 | 
			
		||||
widget.mode.popup.tab.light=Light Theme
 | 
			
		||||
widget.mode.popup.tab.dark=Dark Theme
 | 
			
		||||
widget.mode.popup.color.instruction=Use HEX color values for exact colors; use v:status_bar_bg to use your IDE's status bar background color and v:status_bar_fg for the foreground
 | 
			
		||||
widget.mode.popup.field.theme=Widget theme:
 | 
			
		||||
widget.mode.popup.field.advanced.settings=Full color customization (advanced)
 | 
			
		||||
widget.mode.popup.group.title.full.customization=Full customization
 | 
			
		||||
widget.mode.popup.group.normal.title=Normal Mode
 | 
			
		||||
widget.mode.popup.group.insert.title=Insert Mode
 | 
			
		||||
widget.mode.popup.group.replace.title=Replace Mode
 | 
			
		||||
widget.mode.popup.group.command.title=Command Mode
 | 
			
		||||
widget.mode.popup.group.visual.title=Visual Mode
 | 
			
		||||
widget.mode.popup.group.visual.subgroup.instruction=Leave fields empty to inherit colors from Visual Mode
 | 
			
		||||
widget.mode.popup.group.visual.subgroup.line.title=Visual Line
 | 
			
		||||
widget.mode.popup.group.visual.subgroup.block.title=Visual Block
 | 
			
		||||
widget.mode.popup.group.select.title=Select Mode
 | 
			
		||||
widget.mode.popup.group.select.subgroup.instruction=Leave fields empty to inherit colors from Select Mode
 | 
			
		||||
widget.mode.popup.group.select.subgroup.line.title=Select Line
 | 
			
		||||
widget.mode.popup.group.select.subgroup.block.title=Select Block
 | 
			
		||||
widget.mode.popup.field.background=Background:
 | 
			
		||||
widget.mode.popup.field.foreground=Text:
 | 
			
		||||
 | 
			
		||||
configurable.name.vim.emulation=Vim
 | 
			
		||||
configurable.keyhandler.link=<html>Use <a>sethandler</a> command to configure handlers from the .ideavimrc file</html>
 | 
			
		||||
configurable.noneditablehandler.helper.text.with.example=Non-editable handlers are defined in .ideavimrc file. E.g. ''{0}'' for {1}.
 | 
			
		||||
 
 | 
			
		||||
@@ -1,37 +0,0 @@
 | 
			
		||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
 | 
			
		||||
package org.jetbrains.plugins.ideavim
 | 
			
		||||
 | 
			
		||||
import com.intellij.testFramework.LoggedErrorProcessor
 | 
			
		||||
import com.intellij.testFramework.TestLoggerFactory.TestLoggerAssertionError
 | 
			
		||||
import org.junit.jupiter.api.assertThrows
 | 
			
		||||
import org.junit.jupiter.api.fail
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * By default, LOG.error does three things in tests:
 | 
			
		||||
 * - rethrows the exception
 | 
			
		||||
 * - logs error
 | 
			
		||||
 * - prints to stderr
 | 
			
		||||
 *
 | 
			
		||||
 * The problem is that if we catch exception in tests, such an approach will print the exception to stderr and it will
 | 
			
		||||
 *   look like the exception is not processed.
 | 
			
		||||
 * I don't see a need for printing these caught exceptions, so we can use this processor to only rethrow them.
 | 
			
		||||
 */
 | 
			
		||||
internal object OnlyThrowLoggedErrorProcessor : LoggedErrorProcessor() {
 | 
			
		||||
  override fun processError(category: String, message: String, details: Array<out String>, t: Throwable?): Set<Action> {
 | 
			
		||||
    return setOf(Action.RETHROW)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Asserts that [T] was thrown via `LOG.error("message", e)` call where `e` has a type of [T].
 | 
			
		||||
 */
 | 
			
		||||
internal inline fun <reified T: Throwable> assertThrowsLogError(crossinline action: () -> Unit): T {
 | 
			
		||||
  val exception = assertThrows<TestLoggerAssertionError> {
 | 
			
		||||
    LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
 | 
			
		||||
      action()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  val cause = exception.cause
 | 
			
		||||
  if (cause !is T) fail("Expected ${T::class.java} exception in LOG.error, but got $cause")
 | 
			
		||||
  return cause
 | 
			
		||||
}
 | 
			
		||||
@@ -14,17 +14,12 @@ import com.intellij.openapi.editor.LogicalPosition
 | 
			
		||||
import com.intellij.testFramework.EditorTestUtil
 | 
			
		||||
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
 | 
			
		||||
import com.intellij.util.containers.toArray
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.key.MappingOwner
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import org.junit.jupiter.params.provider.Arguments
 | 
			
		||||
import kotlin.test.fail
 | 
			
		||||
 | 
			
		||||
@@ -134,15 +129,3 @@ internal fun <T> product(vararg elements: List<T>): List<List<T>> {
 | 
			
		||||
  }
 | 
			
		||||
  return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class ExceptionHandler : ExtensionHandler {
 | 
			
		||||
  override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
    error(exceptionMessage)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    internal const val exceptionMessage = "Exception here"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal val exceptionMappingOwner = MappingOwner.Plugin.get("Exception mapping owner")
 | 
			
		||||
 
 | 
			
		||||
@@ -123,7 +123,7 @@ abstract class VimTestCase {
 | 
			
		||||
    VimPlugin.getOptionGroup().resetAllOptionsForTesting()
 | 
			
		||||
    VimPlugin.getKey().resetKeyMappings()
 | 
			
		||||
    VimPlugin.getSearch().resetState()
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true)
 | 
			
		||||
    if (!VimPlugin.isEnabled()) VimPlugin.setEnabled(true)
 | 
			
		||||
    injector.globalOptions().ideastrictmode = true
 | 
			
		||||
    Checks.reset()
 | 
			
		||||
    clearClipboard()
 | 
			
		||||
@@ -152,8 +152,8 @@ abstract class VimTestCase {
 | 
			
		||||
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  open fun tearDown(testInfo: TestInfo) {
 | 
			
		||||
    val swingTimer = swingTimer
 | 
			
		||||
    swingTimer?.stop()
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
    val bookmarksManager = BookmarksManager.getInstance(fixture.project)
 | 
			
		||||
    bookmarksManager?.bookmarks?.forEach { bookmark ->
 | 
			
		||||
      bookmarksManager.remove(bookmark)
 | 
			
		||||
@@ -170,7 +170,6 @@ abstract class VimTestCase {
 | 
			
		||||
    injector.jumpService.resetJumps()
 | 
			
		||||
    VimPlugin.getChange().resetRepeat()
 | 
			
		||||
    VimPlugin.getKey().savedShortcutConflicts.clear()
 | 
			
		||||
    assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
 | 
			
		||||
 | 
			
		||||
    // Tear down neovim
 | 
			
		||||
    NeovimTesting.tearDown(testInfo)
 | 
			
		||||
@@ -448,11 +447,6 @@ abstract class VimTestCase {
 | 
			
		||||
    return NeovimTesting.getMark(char)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected fun assertRegister(char: Char, expected: String?) {
 | 
			
		||||
    val actual = injector.registerGroup.getRegister(char)?.keys?.let(injector.parser::toKeyNotation)
 | 
			
		||||
    assertEquals(expected, actual, "Wrong register contents")
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  protected fun assertState(modeAfter: Mode) {
 | 
			
		||||
    assertMode(modeAfter)
 | 
			
		||||
    assertCaretsVisualAttributes()
 | 
			
		||||
@@ -777,11 +771,7 @@ 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)) {
 | 
			
		||||
        if (modifiers == 0) {
 | 
			
		||||
          return CharType.CharDetected(keyCode.toChar())
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      if (keyCode in setOf(KeyEvent.VK_ENTER)) return CharType.CharDetected(keyCode.toChar())
 | 
			
		||||
      if (keyCode == KeyEvent.VK_ESCAPE) return CharType.EditorAction("EditorEscape")
 | 
			
		||||
    }
 | 
			
		||||
    return CharType.UNDEFINED
 | 
			
		||||
 
 | 
			
		||||
@@ -1,101 +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 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()
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,101 +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 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()
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,47 +7,32 @@
 | 
			
		||||
 */
 | 
			
		||||
package org.jetbrains.plugins.ideavim.action
 | 
			
		||||
 | 
			
		||||
import com.intellij.idea.TestFor
 | 
			
		||||
import com.intellij.testFramework.LoggedErrorProcessor
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.keys
 | 
			
		||||
import com.maddyhome.idea.vim.command.MappingMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.ExceptionHandler
 | 
			
		||||
import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
 | 
			
		||||
import org.jetbrains.plugins.ideavim.rangeOf
 | 
			
		||||
import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
			
		||||
import org.junit.jupiter.api.AfterEach
 | 
			
		||||
import org.junit.jupiter.api.Test
 | 
			
		||||
import org.junit.jupiter.api.assertThrows
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertNotNull
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
class MacroActionTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  fun tearDown() {
 | 
			
		||||
    injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // |q|
 | 
			
		||||
  @Test
 | 
			
		||||
  fun testRecordMacro() {
 | 
			
		||||
    val editor = typeTextInFile(injector.parser.parseKeys("qa" + "3l" + "q"), "on<caret>e two three\n")
 | 
			
		||||
    val commandState = editor.vim.vimStateMachine
 | 
			
		||||
    kotlin.test.assertFalse(commandState.isRecording)
 | 
			
		||||
    assertRegister('a', "3l")
 | 
			
		||||
    val registerGroup = VimPlugin.getRegister()
 | 
			
		||||
    val register = registerGroup.getRegister('a')
 | 
			
		||||
    assertNotNull<Any>(register)
 | 
			
		||||
    kotlin.test.assertEquals("3l", register.text)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -55,7 +40,9 @@ class MacroActionTest : VimTestCase() {
 | 
			
		||||
    configureByText("")
 | 
			
		||||
    enterCommand("imap pp hello")
 | 
			
		||||
    typeText(injector.parser.parseKeys("qa" + "i" + "pp<Esc>" + "q"))
 | 
			
		||||
    assertRegister('a', "ipp<Esc>")
 | 
			
		||||
    val register = VimPlugin.getRegister().getRegister('a')
 | 
			
		||||
    assertNotNull<Any>(register)
 | 
			
		||||
    kotlin.test.assertEquals("ipp<Esc>", injector.parser.toKeyNotation(register.keys))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -63,7 +50,7 @@ class MacroActionTest : VimTestCase() {
 | 
			
		||||
    typeTextInFile(injector.parser.parseKeys("qa" + "i" + "<C-K>OK<Esc>" + "q"), "")
 | 
			
		||||
    val register = VimPlugin.getRegister().getRegister('a')
 | 
			
		||||
    assertNotNull<Any>(register)
 | 
			
		||||
    assertRegister('a', "i<C-K>OK<Esc>")
 | 
			
		||||
    kotlin.test.assertEquals("i<C-K>OK<Esc>", injector.parser.toKeyNotation(register.keys))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -136,8 +123,8 @@ class MacroActionTest : VimTestCase() {
 | 
			
		||||
    assertState("4\n5\n")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test macro with macro`() {
 | 
			
		||||
  // Broken, see the resulting text
 | 
			
		||||
  fun `ignore test macro with macro`() {
 | 
			
		||||
    val content = """
 | 
			
		||||
            Lorem Ipsum
 | 
			
		||||
 | 
			
		||||
@@ -147,55 +134,16 @@ class MacroActionTest : VimTestCase() {
 | 
			
		||||
            Cras id tellus in ex imperdiet egestas.
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
    configureByText(content)
 | 
			
		||||
    typeText(
 | 
			
		||||
      injector.parser.parseKeys(
 | 
			
		||||
        "qa" + "l" + "q" +
 | 
			
		||||
          "qb" + "6@a" + "q" +
 | 
			
		||||
          "^" + "3@b"
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
    typeText(injector.parser.parseKeys("qa" + "l" + "q" + "qb" + "10@a" + "q" + "2@b"))
 | 
			
		||||
 | 
			
		||||
    assertRegister('b', "6@a")
 | 
			
		||||
    assertState("""
 | 
			
		||||
            Lorem Ipsum
 | 
			
		||||
    val startOffset = content.rangeOf("rocks").startOffset
 | 
			
		||||
 | 
			
		||||
            Lorem ipsum dolor ${c}sit amet,
 | 
			
		||||
            consectetur adipiscing elit
 | 
			
		||||
            Sed in orci mauris.
 | 
			
		||||
            Cras id tellus in ex imperdiet egestas.
 | 
			
		||||
    """.trimIndent())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test macro with macro with macro`() {
 | 
			
		||||
    val content = """
 | 
			
		||||
            Lorem Ipsum
 | 
			
		||||
 | 
			
		||||
            ${c}Lorem ipsum dolor sit amet,
 | 
			
		||||
            consectetur adipiscing elit
 | 
			
		||||
            Sed in orci mauris.
 | 
			
		||||
            Cras id tellus in ex imperdiet egestas.
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
    configureByText(content)
 | 
			
		||||
    typeText(
 | 
			
		||||
      injector.parser.parseKeys(
 | 
			
		||||
        "qa" + "l" + "q" +
 | 
			
		||||
          "qb" + "3@a" + "q" +
 | 
			
		||||
          "qc" + "2@b" + "q" +
 | 
			
		||||
          "^" + "3@c"
 | 
			
		||||
      )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    assertRegister('b', "3@a")
 | 
			
		||||
    assertRegister('c', "2@b")
 | 
			
		||||
    assertState("""
 | 
			
		||||
            Lorem Ipsum
 | 
			
		||||
 | 
			
		||||
            Lorem ipsum dolor ${c}sit amet,
 | 
			
		||||
            consectetur adipiscing elit
 | 
			
		||||
            Sed in orci mauris.
 | 
			
		||||
            Cras id tellus in ex imperdiet egestas.
 | 
			
		||||
    """.trimIndent())
 | 
			
		||||
    waitAndAssert {
 | 
			
		||||
      println(fixture.editor.caretModel.offset)
 | 
			
		||||
      println(startOffset)
 | 
			
		||||
      println()
 | 
			
		||||
      startOffset == fixture.editor.caretModel.offset
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -230,33 +178,4 @@ class MacroActionTest : VimTestCase() {
 | 
			
		||||
      """.trimIndent(),
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @TestFor(issues = ["VIM-2929"])
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.ACTION_COMMAND)
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `macro to handler with exception`() {
 | 
			
		||||
    configureByText(
 | 
			
		||||
      """
 | 
			
		||||
     Lorem Ipsum
 | 
			
		||||
 | 
			
		||||
     Lorem ipsum dolor sit amet,
 | 
			
		||||
     ${c}consectetur adipiscing elit
 | 
			
		||||
     Sed in orci mauris.
 | 
			
		||||
     Cras id tellus in ex imperdiet egestas. 
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
    )
 | 
			
		||||
    injector.keyGroup.putKeyMapping(MappingMode.NXO, keys("abc"), exceptionMappingOwner, ExceptionHandler(), false)
 | 
			
		||||
 | 
			
		||||
    injector.registerGroup.storeText('k', "abc")
 | 
			
		||||
    injector.registerGroup.storeText('q', "x@ky")
 | 
			
		||||
 | 
			
		||||
    val exception = assertThrows<Throwable> {
 | 
			
		||||
      LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
 | 
			
		||||
        typeText("@q")
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    assertEquals(ExceptionHandler.exceptionMessage, exception.cause!!.cause!!.message)
 | 
			
		||||
 | 
			
		||||
    assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1025,10 +1025,10 @@ $c  tw${c}o
 | 
			
		||||
    )
 | 
			
		||||
    assertState(
 | 
			
		||||
      """
 | 
			
		||||
    ${s}one two
 | 
			
		||||
    <selection>one two
 | 
			
		||||
    three four
 | 
			
		||||
    five six
 | 
			
		||||
    $se
 | 
			
		||||
    </selection>
 | 
			
		||||
      """.trimIndent(),
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,8 +11,10 @@ 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
 | 
			
		||||
@@ -21,6 +23,7 @@ class ReformatCodeTest : VimTestCase() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @Disabled
 | 
			
		||||
  fun testEmpty() {
 | 
			
		||||
    configureByJavaText("<caret>")
 | 
			
		||||
    typeText(injector.parser.parseKeys("gqq"))
 | 
			
		||||
@@ -29,6 +32,7 @@ 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"))
 | 
			
		||||
@@ -37,6 +41,7 @@ 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"))
 | 
			
		||||
@@ -45,6 +50,7 @@ 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"))
 | 
			
		||||
@@ -53,6 +59,7 @@ 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{"))
 | 
			
		||||
@@ -68,6 +75,7 @@ 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"))
 | 
			
		||||
@@ -76,6 +84,7 @@ 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"))
 | 
			
		||||
@@ -84,6 +93,7 @@ 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"))
 | 
			
		||||
@@ -92,6 +102,7 @@ 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"))
 | 
			
		||||
@@ -100,6 +111,7 @@ 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"))
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user