mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 01:40:12 +01:00 
			
		
		
		
	Compare commits
	
		
			160 Commits
		
	
	
		
			customized
			...
			customized
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						0612367c59
	
				 | 
					
					
						|||
| 
						
						
							
						
						755f05791a
	
				 | 
					
					
						|||
| 
						
						
							
						
						2d170dd15b
	
				 | 
					
					
						|||
| 
						
						
							
						
						943dffd43a
	
				 | 
					
					
						|||
| 
						
						
							
						
						f17e99dd46
	
				 | 
					
					
						|||
| 
						
						
							
						
						aa4caaa722
	
				 | 
					
					
						|||
| 
						
						
							
						
						4380b88cbd
	
				 | 
					
					
						|||
| 
						
						
							
						
						55ce038d51
	
				 | 
					
					
						|||
| 
						
						
							
						
						deec7eef2e
	
				 | 
					
					
						|||
| 
						
						
							
						
						2feffa9ff4
	
				 | 
					
					
						|||
| 
						
						
							
						
						f7f663f29a
	
				 | 
					
					
						|||
| 
						
						
							
						
						badbcd83d6
	
				 | 
					
					
						|||
| 
						
						
							
						
						d978901edf
	
				 | 
					
					
						|||
| 
						
						
							
						
						08940fdaba
	
				 | 
					
					
						|||
| 
						
						
							
						
						3a11fb9bd3
	
				 | 
					
					
						|||
| 
						
						
							
						
						fa9bb6adf4
	
				 | 
					
					
						|||
| 
						 | 
					fb75508258 | ||
| 
						 | 
					0e69168382 | ||
| 
						 | 
					9970ab8643 | ||
| 
						 | 
					7ff82010c3 | ||
| 
						 | 
					1da8cd53d2 | ||
| 
						 | 
					9337a89eac | ||
| 
						 | 
					510564dd91 | ||
| 
						 | 
					a9ededc997 | ||
| 
						 | 
					722cffbd48 | ||
| 
						 | 
					a787befd72 | ||
| 
						 | 
					8ddd71a65a | ||
| 
						 | 
					280e1ec16d | ||
| 
						 | 
					52cf10cb2e | ||
| 
						 | 
					c12082affc | ||
| 
						 | 
					c0d7d74dac | ||
| 
						 | 
					df72b24ad2 | ||
| 
						 | 
					26bdd15400 | ||
| 
						 | 
					e13310b4e0 | ||
| 
						 | 
					e9d4218705 | ||
| 
						 | 
					56b80e4e60 | ||
| 
						 | 
					679f6471e6 | ||
| 
						 | 
					984179695c | ||
| 
						 | 
					5cca484a82 | ||
| 
						 | 
					d91e2296b0 | ||
| 
						 | 
					59768c16e2 | ||
| 
						 | 
					580efeae1a | ||
| 
						 | 
					0a3b508c8a | ||
| 
						 | 
					5e2f590b76 | ||
| 
						 | 
					ee94396afa | ||
| 
						 | 
					98764b6356 | ||
| 
						 | 
					f01cc4d0d0 | ||
| 
						 | 
					4c0f17429b | ||
| 
						 | 
					6a2ae1c572 | ||
| 
						 | 
					a2681ce6cc | ||
| 
						 | 
					4e43606932 | ||
| 
						 | 
					28c0c3207a | ||
| 
						 | 
					ecfa0e2b49 | ||
| 
						 | 
					ec3122f320 | ||
| 
						 | 
					7e4b4c973c | ||
| 
						 | 
					64753df2dd | ||
| 
						 | 
					75b36ab886 | ||
| 
						 | 
					208a78c748 | ||
| 
						 | 
					027249c575 | ||
| 
						 | 
					5ceb960205 | ||
| 
						 | 
					1cea156c5a | ||
| 
						 | 
					e1efa1ecbc | ||
| 
						 | 
					517de5e179 | ||
| 
						 | 
					825b62a2a9 | ||
| 
						 | 
					5ec817776c | ||
| 
						 | 
					3ad0519add | ||
| 
						 | 
					9868522341 | ||
| 
						 | 
					5b8d8c617f | ||
| 
						 | 
					a1f66061e3 | ||
| 
						 | 
					d8811933c9 | ||
| 
						 | 
					c9864dde8d | ||
| 
						 | 
					ca849d6649 | ||
| 
						 | 
					95a2354a86 | ||
| 
						 | 
					538e0ac48c | ||
| 
						 | 
					1c17411f04 | ||
| 
						 | 
					cbe0f89548 | ||
| 
						 | 
					615b071dcb | ||
| 
						 | 
					2d74f121aa | ||
| 
						 | 
					f65c180b8f | ||
| 
						 | 
					eb389c472d | ||
| 
						 | 
					befdf08035 | ||
| 
						 | 
					7a43ac865e | ||
| 
						 | 
					c43fcf9fbf | ||
| 
						 | 
					472a633010 | ||
| 
						 | 
					fc46acb2e4 | ||
| 
						 | 
					7fde66eb40 | ||
| 
						 | 
					b3cea3997d | ||
| 
						 | 
					2f20193086 | ||
| 
						 | 
					601e207f04 | ||
| 
						 | 
					f0d3d8b276 | ||
| 
						 | 
					e02d34f023 | ||
| 
						 | 
					0504be84b6 | ||
| 
						 | 
					216f020b70 | ||
| 
						 | 
					66505eedfa | ||
| 
						 | 
					b307c7d88b | ||
| 
						 | 
					47d4445fa8 | ||
| 
						 | 
					7098d2633a | ||
| 
						 | 
					61b5393b54 | ||
| 
						 | 
					6fe2cf13b6 | ||
| 
						 | 
					cc971eb2df | ||
| 
						 | 
					a260987f5c | ||
| 
						 | 
					5eb8f44dfc | ||
| 
						 | 
					e36131b38b | ||
| 
						 | 
					b67868afde | ||
| 
						 | 
					328fdee281 | ||
| 
						 | 
					8ab43e98fe | ||
| 
						 | 
					4f407ccc03 | ||
| 
						 | 
					5f3fddd3e4 | ||
| 
						 | 
					392f3b536d | ||
| 
						 | 
					155de2b396 | ||
| 
						 | 
					6c9930ac2a | ||
| 
						 | 
					9dddf4f4bc | ||
| 
						 | 
					314506c15c | ||
| 673222da6c | |||
| 
						 | 
					58b97e6361 | ||
| 
						 | 
					8bc2032b07 | ||
| 
						 | 
					40d4354dfc | ||
| 
						 | 
					27f2f5bb2b | ||
| 
						 | 
					490b934269 | ||
| 
						 | 
					e2e2b4d176 | ||
| 
						 | 
					7a1763bbee | ||
| 
						 | 
					ca8904b6bb | ||
| 
						 | 
					6384b28689 | ||
| 
						 | 
					e661466558 | ||
| 
						 | 
					8faf2beba4 | ||
| 
						 | 
					fb29319ec6 | ||
| 
						 | 
					7779d7d193 | ||
| 
						 | 
					2c5246b62f | ||
| 
						 | 
					e43a3f4518 | ||
| 
						 | 
					b5716f7a6d | ||
| 
						 | 
					fac5a3cc6f | ||
| 
						 | 
					671793078a | ||
| 
						 | 
					4f5ea1696f | ||
| 
						 | 
					b3e47e3bac | ||
| 
						 | 
					d67e990065 | ||
| 
						 | 
					7fb6f4b47f | ||
| 
						 | 
					df3b435a1f | ||
| 
						 | 
					5b65f1b544 | ||
| 
						 | 
					e159866d3b | ||
| 
						 | 
					aa0ce71612 | ||
| 
						 | 
					522e547f99 | ||
| 
						 | 
					9430341d4e | ||
| 
						 | 
					95838d045d | ||
| 
						 | 
					20832f11b6 | ||
| 
						 | 
					258203f400 | ||
| 
						 | 
					3b1768fa4e | ||
| 
						 | 
					23a3085bad | ||
| 
						 | 
					78c12e0ea6 | ||
| 
						 | 
					997cb85663 | ||
| 
						 | 
					968d5eabfa | ||
| 
						 | 
					590ce1f7ed | ||
| 
						 | 
					416a8838e4 | ||
| 
						 | 
					f6c349ac31 | ||
| 
						 | 
					517c6b40b5 | ||
| 
						 | 
					1fa78935a6 | ||
| 
						 | 
					4ddcd56740 | ||
| 
						 | 
					e5a2f33584 | ||
| 
						 | 
					c17cf3256a | ||
| 
						 | 
					5415bda02d | ||
| 
						 | 
					07cbaeb7aa | 
							
								
								
									
										24
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							@@ -8,18 +8,20 @@ jobs:
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
    runs-on: macos-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - name: Setup Java
 | 
			
		||||
        uses: actions/setup-java@v2.1.0
 | 
			
		||||
        uses: actions/setup-java@v4
 | 
			
		||||
        with:
 | 
			
		||||
          distribution: zulu
 | 
			
		||||
          java-version: 11
 | 
			
		||||
      - name: Setup FFmpeg
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v1
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v3
 | 
			
		||||
        with:
 | 
			
		||||
          # Not strictly necessary, but it may prevent rate limit
 | 
			
		||||
          # errors especially on GitHub-hosted macos machines.
 | 
			
		||||
          token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: Setup Gradle
 | 
			
		||||
        uses: gradle/gradle-build-action@v2.4.2
 | 
			
		||||
      - name: Build Plugin
 | 
			
		||||
        run: gradle :buildPlugin
 | 
			
		||||
      - name: Run Idea
 | 
			
		||||
@@ -27,7 +29,7 @@ jobs:
 | 
			
		||||
          mkdir -p build/reports
 | 
			
		||||
          gradle :runIdeForUiTests > build/reports/idea.log &
 | 
			
		||||
      - name: Wait for Idea started
 | 
			
		||||
        uses: jtalk/url-health-check-action@1.5
 | 
			
		||||
        uses: jtalk/url-health-check-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          url: http://127.0.0.1:8082
 | 
			
		||||
          max-attempts: 20
 | 
			
		||||
@@ -35,15 +37,19 @@ jobs:
 | 
			
		||||
      - name: Tests
 | 
			
		||||
        run: gradle :testUi
 | 
			
		||||
      - name: Move video
 | 
			
		||||
        if: ${{ failure() }}
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: mv video build/reports
 | 
			
		||||
      - name: Save fails report
 | 
			
		||||
        if: ${{ failure() }}
 | 
			
		||||
        uses: actions/upload-artifact@v2
 | 
			
		||||
      - 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
 | 
			
		||||
        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-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="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="myName" value="IdeaVim" />
 | 
			
		||||
  </copyright>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										17
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										17
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							@@ -5,14 +5,13 @@ object Constants {
 | 
			
		||||
  const val EAP_CHANNEL = "eap"
 | 
			
		||||
  const val DEV_CHANNEL = "Dev"
 | 
			
		||||
 | 
			
		||||
  // TODO it should be 2023.3 as soon as it releases
 | 
			
		||||
  const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val RELEASE = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val 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 RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val RELEASE_EAP = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val RELEASE_DEV = "2023.3.2"
 | 
			
		||||
  const val RELEASE_EAP = "2023.3.2"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							@@ -24,6 +24,7 @@ object Project : Project({
 | 
			
		||||
 | 
			
		||||
  // Active tests
 | 
			
		||||
  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)
 | 
			
		||||
@@ -38,6 +39,11 @@ 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 {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								.teamcity/patches/buildTypes/IdeaVimTests_Latest_EAP.kts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.teamcity/patches/buildTypes/IdeaVimTests_Latest_EAP.kts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
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
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								.teamcity/patches/projects/_Self.kts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
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,6 +487,10 @@ Contributors:
 | 
			
		||||
  [![icon][github]](https://github.com/pWydmuch)
 | 
			
		||||
   
 | 
			
		||||
  pWydmuch
 | 
			
		||||
* [![icon][mail]](mailto:leonid989@gmail.com)
 | 
			
		||||
  [![icon][github]](https://github.com/Infonautica)
 | 
			
		||||
   
 | 
			
		||||
  Leonid Danilov
 | 
			
		||||
 | 
			
		||||
Previous contributors:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -43,9 +43,14 @@ usual beta standards.
 | 
			
		||||
* [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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.21-1.0.15")
 | 
			
		||||
  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")
 | 
			
		||||
 
 | 
			
		||||
@@ -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.7.0.202309050840-r")
 | 
			
		||||
        classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
        classpath("org.kohsuke:github-api:1.305")
 | 
			
		||||
 | 
			
		||||
        classpath("io.ktor:ktor-client-core:2.3.6")
 | 
			
		||||
        classpath("io.ktor:ktor-client-cio:2.3.6")
 | 
			
		||||
        classpath("io.ktor:ktor-client-auth:2.3.6")
 | 
			
		||||
        classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
 | 
			
		||||
        classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
 | 
			
		||||
        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")
 | 
			
		||||
 | 
			
		||||
        // 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.0"
 | 
			
		||||
    id("org.jetbrains.intellij") version "1.16.1"
 | 
			
		||||
    id("org.jetbrains.changelog") version "2.2.0"
 | 
			
		||||
 | 
			
		||||
    // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
 | 
			
		||||
@@ -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.1.0")
 | 
			
		||||
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
 | 
			
		||||
 | 
			
		||||
    testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
 | 
			
		||||
    testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
 | 
			
		||||
    testImplementation("com.automation-remarks:video-recorder-junit:2.0")
 | 
			
		||||
    testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
 | 
			
		||||
    runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
 | 
			
		||||
    antlr("org.antlr:antlr4:$antlrVersion")
 | 
			
		||||
 | 
			
		||||
@@ -184,6 +184,14 @@ 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) {
 | 
			
		||||
@@ -294,6 +302,7 @@ 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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -396,3 +396,19 @@ 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,12 @@
 | 
			
		||||
 | 
			
		||||
# suppress inspection "UnusedProperty" for whole file
 | 
			
		||||
 | 
			
		||||
ideaVersion=2023.2
 | 
			
		||||
ideaVersion=2023.3.2
 | 
			
		||||
downloadIdeaSources=true
 | 
			
		||||
instrumentPluginCode=true
 | 
			
		||||
version=chylex-22
 | 
			
		||||
version=chylex-23
 | 
			
		||||
javaVersion=17
 | 
			
		||||
remoteRobotVersion=0.11.17
 | 
			
		||||
remoteRobotVersion=0.11.21
 | 
			
		||||
antlrVersion=4.10.1
 | 
			
		||||
 | 
			
		||||
kotlin.incremental.useClasspathSnapshot=false
 | 
			
		||||
 
 | 
			
		||||
@@ -20,17 +20,17 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.21")
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
 | 
			
		||||
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-client-cio:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.6")
 | 
			
		||||
  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("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.7.0.202309050840-r")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
  implementation("com.vdurmont:semver4j:3.1.0")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -219,6 +219,10 @@ 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;
 | 
			
		||||
 | 
			
		||||
@@ -232,6 +236,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
      getInstance().turnOnPlugin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
 | 
			
		||||
    } else {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    StatusBarIconFactory.Util.INSTANCE.updateIcon();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -28,8 +28,11 @@ 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
 | 
			
		||||
 */
 | 
			
		||||
internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
  private val handler = KeyHandler.getInstance()
 | 
			
		||||
  private val traceTime = injector.globalOptions().ideatracetime
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +89,7 @@ internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedAct
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
  internal companion object {
 | 
			
		||||
    private val LOG = logger<VimTypedActionHandler>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -14,14 +14,10 @@ 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
 | 
			
		||||
@@ -58,8 +54,11 @@ 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
 | 
			
		||||
 */
 | 
			
		||||
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
  private val traceTime: Boolean
 | 
			
		||||
    get() {
 | 
			
		||||
      // Make sure the injector is initialized
 | 
			
		||||
@@ -99,7 +98,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
 | 
			
		||||
  // 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.EDT
 | 
			
		||||
  override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
 | 
			
		||||
 | 
			
		||||
  override fun update(e: AnActionEvent) {
 | 
			
		||||
    val start = if (traceTime) System.currentTimeMillis() else null
 | 
			
		||||
@@ -114,7 +113,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
 | 
			
		||||
    if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
 | 
			
		||||
    val editor = getEditor(e)
 | 
			
		||||
    if (editor != null && keyStroke != null) {
 | 
			
		||||
      if (isOctopusEnabled(keyStroke, editor)) {
 | 
			
		||||
@@ -168,14 +167,6 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
        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
 | 
			
		||||
@@ -241,9 +232,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
  /**
 | 
			
		||||
   * 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
 | 
			
		||||
   * the event is already consumed and getDefaultKeyStroke returns null
 | 
			
		||||
   */
 | 
			
		||||
  private var keyStrokeCache: Pair<KeyEvent?, KeyStroke?> = null to null
 | 
			
		||||
  private var keyStrokeCache: Pair<Long?, KeyStroke?> = null to null
 | 
			
		||||
 | 
			
		||||
  private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
 | 
			
		||||
    val inputEvent = e.inputEvent
 | 
			
		||||
@@ -251,9 +242,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
      val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
 | 
			
		||||
      val strokeCache = keyStrokeCache
 | 
			
		||||
      if (defaultKeyStroke != null) {
 | 
			
		||||
        keyStrokeCache = inputEvent to defaultKeyStroke
 | 
			
		||||
        keyStrokeCache = inputEvent.`when` to defaultKeyStroke
 | 
			
		||||
        return defaultKeyStroke
 | 
			
		||||
      } else if (strokeCache.first === inputEvent) {
 | 
			
		||||
      } else if (strokeCache.first == inputEvent.`when`) {
 | 
			
		||||
        keyStrokeCache = null to null
 | 
			
		||||
        return strokeCache.second
 | 
			
		||||
      }
 | 
			
		||||
@@ -286,7 +277,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
      .toSet()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
  internal companion object {
 | 
			
		||||
    @JvmField
 | 
			
		||||
    val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
 | 
			
		||||
      ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
 | 
			
		||||
 
 | 
			
		||||
@@ -18,9 +18,7 @@ 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
 | 
			
		||||
@@ -28,10 +26,9 @@ 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 java.util.*
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
 | 
			
		||||
// todo make it multicaret
 | 
			
		||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
 | 
			
		||||
@@ -104,8 +101,6 @@ 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,13 +14,10 @@ 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
 | 
			
		||||
@@ -29,8 +26,6 @@ import java.util.*
 | 
			
		||||
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,13 +14,10 @@ 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
 | 
			
		||||
@@ -29,8 +26,6 @@ import java.util.*
 | 
			
		||||
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,
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ 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
 | 
			
		||||
@@ -17,7 +18,6 @@ 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,6 +26,7 @@ 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
 | 
			
		||||
@@ -38,6 +39,9 @@ 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(
 | 
			
		||||
@@ -140,10 +144,12 @@ 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) {
 | 
			
		||||
@@ -151,11 +157,13 @@ 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,11 +156,6 @@ 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,6 +217,8 @@ 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") {
 | 
			
		||||
@@ -242,6 +244,7 @@ private object FileTypePatterns {
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  private val htmlPatterns = createHtmlPatterns()
 | 
			
		||||
  private val javaPatterns = createJavaPatterns()
 | 
			
		||||
  private val rubyPatterns = createRubyPatterns()
 | 
			
		||||
  private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
 | 
			
		||||
  private val phpPatterns = createPhpPatterns()
 | 
			
		||||
@@ -270,6 +273,14 @@ 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,6 +8,7 @@
 | 
			
		||||
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
 | 
			
		||||
@@ -255,6 +256,7 @@ 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) }
 | 
			
		||||
@@ -307,96 +309,101 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    private const val REGISTER = '"'
 | 
			
		||||
private val LOG = logger<VimSurroundExtension>()
 | 
			
		||||
 | 
			
		||||
    private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
private const val REGISTER = '"'
 | 
			
		||||
 | 
			
		||||
    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 val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
 | 
			
		||||
    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 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 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 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 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))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
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)
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,6 @@ 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
 | 
			
		||||
@@ -65,7 +62,6 @@ 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
 | 
			
		||||
@@ -89,7 +85,6 @@ import kotlin.math.min
 | 
			
		||||
 */
 | 
			
		||||
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
 | 
			
		||||
@@ -103,10 +98,6 @@ 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
 | 
			
		||||
@@ -645,25 +636,6 @@ 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,6 +29,8 @@ 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,6 +86,8 @@ 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -64,25 +64,33 @@ internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
    try {
 | 
			
		||||
      myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
 | 
			
		||||
      val runnable = runnable@{
 | 
			
		||||
        // 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 {
 | 
			
		||||
          // Handle one keystroke then queue up the next key
 | 
			
		||||
          for (i in 0 until total) {
 | 
			
		||||
            try {
 | 
			
		||||
              myPotemkinProgress.checkCanceled()
 | 
			
		||||
            } catch (e: ProcessCanceledException) {
 | 
			
		||||
              return@runnable
 | 
			
		||||
              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()
 | 
			
		||||
            }
 | 
			
		||||
            ProgressManager.getInstance().executeNonCancelableSection {
 | 
			
		||||
              CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
 | 
			
		||||
              getInstance().handleKey(editor, key, context)
 | 
			
		||||
            }
 | 
			
		||||
            if (injector.messages.isError()) return@runnable
 | 
			
		||||
          }
 | 
			
		||||
          keyStack.resetFirst()
 | 
			
		||||
        } finally {
 | 
			
		||||
          keyStack.removeFirst()
 | 
			
		||||
        }
 | 
			
		||||
        keyStack.removeFirst()
 | 
			
		||||
        if (!isInternalMacro) {
 | 
			
		||||
          MacroAutoImport.run(editor.ij, context.ij)
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -48,9 +48,7 @@ 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
 | 
			
		||||
@@ -74,6 +72,8 @@ 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,11 +461,13 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
      val fileEditor = event.oldEditor
 | 
			
		||||
      if (fileEditor is TextEditor) {
 | 
			
		||||
        val editor = fileEditor.editor
 | 
			
		||||
        ExOutputModel.getInstance(editor).clear()
 | 
			
		||||
        editor.vim.let { vimEditor ->
 | 
			
		||||
          if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
 | 
			
		||||
            vimEditor.exitVisualMode()
 | 
			
		||||
            KeyHandler.getInstance().reset(vimEditor)
 | 
			
		||||
        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)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,6 @@ 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
 | 
			
		||||
@@ -58,6 +54,8 @@ 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)
 | 
			
		||||
@@ -68,7 +66,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class IjOptionConstants {
 | 
			
		||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
 | 
			
		||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
 | 
			
		||||
  companion object {
 | 
			
		||||
 | 
			
		||||
    const val idearefactormode_keep = "keep"
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
   */
 | 
			
		||||
  @TestOnly
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
 | 
			
		||||
  @Override
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor 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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) 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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) 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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
@@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,15 +27,12 @@ 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
 | 
			
		||||
@@ -48,6 +45,10 @@ 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() {
 | 
			
		||||
@@ -189,7 +190,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()) return endOffset
 | 
			
		||||
    if (PlatformUtils.isRider() || isClionNova()) return endOffset
 | 
			
		||||
 | 
			
		||||
    val startLine = editor.offsetToBufferPosition(startOffset).line
 | 
			
		||||
    val endLine = editor.offsetToBufferPosition(endOffset - 1).line
 | 
			
		||||
 
 | 
			
		||||
@@ -40,9 +40,15 @@ 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])
 | 
			
		||||
   *   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.
 | 
			
		||||
   *
 | 
			
		||||
   * 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,
 | 
			
		||||
@@ -50,6 +56,7 @@ 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")
 | 
			
		||||
@@ -121,8 +128,9 @@ internal object IdeaSelectionControl {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun dontChangeMode(editor: Editor): Boolean =
 | 
			
		||||
    editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
 | 
			
		||||
  private fun dontChangeMode(editor: Editor): Boolean {
 | 
			
		||||
    return 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,6 +79,11 @@ internal object VimVisualTimer {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun drop() {
 | 
			
		||||
    swingTimer?.stop()
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
 | 
			
		||||
    task(mode)
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
 
 | 
			
		||||
@@ -11,41 +11,68 @@ 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.StartupActivity
 | 
			
		||||
import com.intellij.util.SingleAlarm
 | 
			
		||||
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
 | 
			
		||||
// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
 | 
			
		||||
internal val keymapCheckRequester = SingleAlarm({ verifyKeymap() }, 5_000)
 | 
			
		||||
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 : StartupActivity {
 | 
			
		||||
  override fun runActivity(project: Project) {
 | 
			
		||||
    keymapCheckRequester.request()
 | 
			
		||||
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?) {
 | 
			
		||||
    keymapCheckRequester.request()
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun shortcutChanged(keymap: Keymap, actionId: String) {
 | 
			
		||||
    keymapCheckRequester.request()
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
 | 
			
		||||
    keymapCheckRequester.request()
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -125,7 +125,7 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun isThisHandlerEnabled(editor: Editor, caret: Caret?, dataContext: DataContext?): Boolean {
 | 
			
		||||
    if (!VimPlugin.isEnabled()) return false
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return false
 | 
			
		||||
    if (!isHandlerEnabled(editor, dataContext)) return false
 | 
			
		||||
    if (isNotActualKeyPress(dataContext)) return false
 | 
			
		||||
    return true
 | 
			
		||||
@@ -229,7 +229,7 @@ internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(n
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Rider uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
 | 
			
		||||
 * Rider (and CLion Nova) 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
 | 
			
		||||
 
 | 
			
		||||
@@ -81,7 +81,7 @@ private fun Editor.guicursorMode(): GuiCursorMode {
 | 
			
		||||
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
 | 
			
		||||
 | 
			
		||||
private fun Editor.updatePrimaryCaretVisualAttributes() {
 | 
			
		||||
  if (!VimPlugin.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) 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.isEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) 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 {
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun vimEnabled(editor: Editor?): Boolean {
 | 
			
		||||
  if (!VimPlugin.isEnabled()) return false
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) return false
 | 
			
		||||
  if (editor != null && editor.isIdeaVimDisabledHere) return false
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -143,7 +143,7 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
      manager.fireAfterActionPerformed(action, event, result!!)
 | 
			
		||||
    }
 | 
			
		||||
    if (indexError != null) {
 | 
			
		||||
      ActionUtil.showDumbModeWarning(project, event)
 | 
			
		||||
      ActionUtil.showDumbModeWarning(project, action, event)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -124,10 +124,6 @@ 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()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) 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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) 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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
 | 
			
		||||
      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
			
		||||
      if (hostEditor != null) {
 | 
			
		||||
@@ -92,7 +92,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
 | 
			
		||||
      val editor = editor
 | 
			
		||||
      if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
 | 
			
		||||
@@ -138,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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      val editor = state.editor ?: return
 | 
			
		||||
 | 
			
		||||
      state.addTemplateStateListener(object : TemplateEditingAdapter() {
 | 
			
		||||
@@ -176,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.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
 | 
			
		||||
      // Lookup opened
 | 
			
		||||
      if (oldLookup == null && newLookup is LookupImpl) {
 | 
			
		||||
@@ -199,7 +199,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
  //region Hide Vim search highlights when showing IntelliJ search results
 | 
			
		||||
  class VimFindModelListener : FindModelListener {
 | 
			
		||||
    override fun findNextModelChanged() {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) 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.isEnabled()) return
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) 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.isEnabled()) return
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
 | 
			
		||||
    //region Extend Selection for Rider
 | 
			
		||||
    when (ActionManager.getInstance().getId(action)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
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
 | 
			
		||||
@@ -28,8 +29,9 @@ 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
 | 
			
		||||
@@ -40,14 +42,11 @@ 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
 | 
			
		||||
@@ -62,6 +61,7 @@ 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
 | 
			
		||||
@@ -71,7 +71,7 @@ import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
			
		||||
import com.maddyhome.idea.vim.handler.correctorRequester
 | 
			
		||||
import com.maddyhome.idea.vim.handler.keymapCheckRequester
 | 
			
		||||
import com.maddyhome.idea.vim.handler.keyCheckRequests
 | 
			
		||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.helper.StrictMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.exitSelectMode
 | 
			
		||||
@@ -97,6 +97,9 @@ 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
 | 
			
		||||
@@ -132,7 +135,7 @@ internal object VimListenerManager {
 | 
			
		||||
    GlobalListeners.enable()
 | 
			
		||||
    EditorListeners.addAll()
 | 
			
		||||
    correctorRequester.request()
 | 
			
		||||
    keymapCheckRequester.request()
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun turnOff() {
 | 
			
		||||
@@ -156,6 +159,13 @@ 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)
 | 
			
		||||
@@ -163,6 +173,8 @@ 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() {
 | 
			
		||||
@@ -173,10 +185,12 @@ 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>()
 | 
			
		||||
@@ -215,49 +229,67 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
 | 
			
		||||
      val pluginLifetime = VimPlugin.getInstance().createLifetime()
 | 
			
		||||
      val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
 | 
			
		||||
      val disposable =
 | 
			
		||||
        Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
 | 
			
		||||
      // 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
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      editor.contentComponent.addKeyListener(VimKeyListener)
 | 
			
		||||
      Disposer.register(disposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
 | 
			
		||||
      Disposer.register(listenersDisposable) { editor.contentComponent.removeKeyListener(VimKeyListener) }
 | 
			
		||||
 | 
			
		||||
      // Initialise the local options. We MUST do this before anything has the chance to query options
 | 
			
		||||
      VimPlugin.getOptionGroup().initialiseLocalOptions(editor.vim, openingEditor, scenario)
 | 
			
		||||
      val vimEditor = editor.vim
 | 
			
		||||
      VimPlugin.getOptionGroup().initialiseLocalOptions(vimEditor, openingEditor, scenario)
 | 
			
		||||
 | 
			
		||||
      val eventFacade = EventFacade.getInstance()
 | 
			
		||||
      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)
 | 
			
		||||
      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)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getEditor().editorCreated(editor)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getChange().editorCreated(editor, disposable)
 | 
			
		||||
      VimPlugin.getChange().editorCreated(editor, listenersDisposable)
 | 
			
		||||
 | 
			
		||||
      Disposer.register(disposable) {
 | 
			
		||||
      injector.listenersNotifier.notifyEditorCreated(vimEditor)
 | 
			
		||||
 | 
			
		||||
      Disposer.register(listenersDisposable) {
 | 
			
		||||
        VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun remove(editor: Editor, isReleased: Boolean) {
 | 
			
		||||
      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)
 | 
			
		||||
      val editorDisposable = editor.getUserData(editorListenersDisposable)
 | 
			
		||||
      if (editorDisposable != null) {
 | 
			
		||||
        Disposer.dispose(editorDisposable)
 | 
			
		||||
      }
 | 
			
		||||
      else StrictMode.fail("Editor doesn't have disposable attached. $editor")
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getChange().editorReleased(editor)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  private object VimFocusListener : FocusChangeListener {
 | 
			
		||||
    override fun focusGained(editor: Editor) {
 | 
			
		||||
      injector.listenersNotifier.notifyEditorFocusGained(editor.vim)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
@@ -272,7 +304,7 @@ internal object VimListenerManager {
 | 
			
		||||
 | 
			
		||||
  class VimFileEditorManagerListener : FileEditorManagerListener {
 | 
			
		||||
    override fun selectionChanged(event: FileEditorManagerEvent) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      
 | 
			
		||||
      val newEditor = event.newEditor
 | 
			
		||||
      if (newEditor is TextEditor) {
 | 
			
		||||
@@ -350,13 +382,15 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun editorReleased(event: EditorFactoryEvent) {
 | 
			
		||||
      injector.markService.editorReleased(event.editor.vim)
 | 
			
		||||
      val vimEditor = event.editor.vim
 | 
			
		||||
      injector.listenersNotifier.notifyEditorReleased(vimEditor)
 | 
			
		||||
      injector.markService.editorReleased(vimEditor)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
@@ -414,6 +448,7 @@ 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)
 | 
			
		||||
@@ -707,6 +742,11 @@ internal object VimListenerManager {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal object VimListenerTestObject {
 | 
			
		||||
  var enabled: Boolean = false
 | 
			
		||||
  var disposedCounter = 0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private object MouseEventsDataHolder {
 | 
			
		||||
  const val skipEvents = 3
 | 
			
		||||
  var skipNDragEvents = skipEvents
 | 
			
		||||
 
 | 
			
		||||
@@ -54,7 +54,6 @@ 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
 | 
			
		||||
@@ -67,7 +66,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!!!
 | 
			
		||||
@@ -82,11 +81,6 @@ 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()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -18,6 +18,7 @@ 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;
 | 
			
		||||
@@ -59,6 +60,8 @@ 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;
 | 
			
		||||
 | 
			
		||||
@@ -299,6 +302,10 @@ 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);
 | 
			
		||||
@@ -358,7 +365,7 @@ public class ExOutputPanel extends JPanel {
 | 
			
		||||
  public static class LafListener implements LafManagerListener {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return;
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return;
 | 
			
		||||
      // Calls updateUI on this and child components
 | 
			
		||||
      for (Editor editor : HelperKt.localEditors()) {
 | 
			
		||||
        if (!ExOutputPanel.isPanelActive(editor)) continue;
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,9 @@
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
@@ -22,13 +25,19 @@ 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()
 | 
			
		||||
@@ -55,6 +64,7 @@ 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,7 +19,6 @@ 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
 | 
			
		||||
@@ -68,13 +67,6 @@ 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)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -453,7 +453,7 @@ public class ExEntryPanel extends JPanel {
 | 
			
		||||
  public static class LafListener implements LafManagerListener {
 | 
			
		||||
    @Override
 | 
			
		||||
    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return;
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return;
 | 
			
		||||
      // Calls updateUI on this and child components
 | 
			
		||||
      if (ExEntryPanel.isInstanceWithShortcutsActive()) {
 | 
			
		||||
        IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstance());
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,94 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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() }
 | 
			
		||||
@@ -0,0 +1,45 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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() }
 | 
			
		||||
@@ -0,0 +1,368 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										129
									
								
								src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/Util.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										129
									
								
								src/main/java/com/maddyhome/idea/vim/ui/widgets/mode/Util.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,129 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,176 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,59 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,53 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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,31 +36,28 @@ public val VimEditor.isIdeaRefactorModeSelect: Boolean
 | 
			
		||||
 | 
			
		||||
internal object IdeaRefactorModeHelper {
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
  sealed interface Action {
 | 
			
		||||
    object RemoveSelection : Action
 | 
			
		||||
    class SetMode(val newMode: Mode) : Action
 | 
			
		||||
    class MoveToOffset(val newOffset: Int) : Action
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
      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)
 | 
			
		||||
  fun applyCorrections(corrections: List<Action>, editor: Editor) {
 | 
			
		||||
    val correctionsApplier = {
 | 
			
		||||
      corrections.forEach { correction ->
 | 
			
		||||
        when (correction) {
 | 
			
		||||
          is Action.MoveToOffset -> {
 | 
			
		||||
            editor.caretModel.moveToOffset(correction.newOffset)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          Action.RemoveSelection -> {
 | 
			
		||||
            SelectionVimListenerSuppressor.lock().use {
 | 
			
		||||
              editor.selectionModel.removeSelection()
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          is Action.SetMode -> {
 | 
			
		||||
            editor.vim.vimStateMachine.mode = correction.newMode
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -70,7 +67,9 @@ internal object IdeaRefactorModeHelper {
 | 
			
		||||
    if (lookup != null) {
 | 
			
		||||
      val selStart = editor.selectionModel.selectionStart
 | 
			
		||||
      val selEnd = editor.selectionModel.selectionEnd
 | 
			
		||||
      lookup.performGuardedChange(action)
 | 
			
		||||
      lookup.performGuardedChange {
 | 
			
		||||
        correctionsApplier()
 | 
			
		||||
      }
 | 
			
		||||
      lookup.addLookupListener(object : LookupListener {
 | 
			
		||||
        override fun beforeItemSelected(event: LookupEvent): Boolean {
 | 
			
		||||
          // FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
 | 
			
		||||
@@ -82,7 +81,41 @@ internal object IdeaRefactorModeHelper {
 | 
			
		||||
        }
 | 
			
		||||
      })
 | 
			
		||||
    } else {
 | 
			
		||||
      action()
 | 
			
		||||
      correctionsApplier()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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,6 +8,10 @@
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
@@ -22,8 +26,10 @@ 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
 | 
			
		||||
 | 
			
		||||
internal class IjVariableService : VimVariableServiceBase() {
 | 
			
		||||
@State(name = "VimVariables", storages = [Storage(value = "\$APP_CONFIG$/vim_settings_local.xml", roamingType = RoamingType.DISABLED)])
 | 
			
		||||
internal class IjVariableService : VimVariableServiceBase(), PersistentStateComponent<Element?> {
 | 
			
		||||
  override fun storeVariable(variable: Variable, value: VimDataType, editor: VimEditor, context: ExecutionContext, vimContext: VimLContext) {
 | 
			
		||||
    super.storeVariable(variable, value, editor, context, vimContext)
 | 
			
		||||
 | 
			
		||||
@@ -47,4 +53,49 @@ internal class IjVariableService : VimVariableServiceBase() {
 | 
			
		||||
      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"))
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										19
									
								
								src/main/resources/META-INF/ides/ideavim-withClionNova.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/main/resources/META-INF/ides/ideavim-withClionNova.xml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
<!--
 | 
			
		||||
  ~ 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>
 | 
			
		||||
@@ -29,6 +29,8 @@
 | 
			
		||||
  <!--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>
 | 
			
		||||
 | 
			
		||||
@@ -55,6 +57,8 @@
 | 
			
		||||
    <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">
 | 
			
		||||
@@ -62,7 +66,9 @@
 | 
			
		||||
    <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"/>
 | 
			
		||||
 | 
			
		||||
@@ -146,6 +152,7 @@
 | 
			
		||||
 | 
			
		||||
    <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"
 | 
			
		||||
 
 | 
			
		||||
@@ -84,6 +84,8 @@ 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
 | 
			
		||||
 | 
			
		||||
@@ -129,6 +131,28 @@ 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}.
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
// 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,12 +14,17 @@ 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.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.key.MappingOwner
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@@ -129,3 +134,15 @@ 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.isEnabled()) VimPlugin.setEnabled(true)
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) 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,6 +170,7 @@ abstract class VimTestCase {
 | 
			
		||||
    injector.jumpService.resetJumps()
 | 
			
		||||
    VimPlugin.getChange().resetRepeat()
 | 
			
		||||
    VimPlugin.getKey().savedShortcutConflicts.clear()
 | 
			
		||||
    assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
 | 
			
		||||
 | 
			
		||||
    // Tear down neovim
 | 
			
		||||
    NeovimTesting.tearDown(testInfo)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,22 +7,40 @@
 | 
			
		||||
 */
 | 
			
		||||
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() {
 | 
			
		||||
@@ -178,4 +196,33 @@ 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(
 | 
			
		||||
      """
 | 
			
		||||
    <selection>one two
 | 
			
		||||
    ${s}one two
 | 
			
		||||
    three four
 | 
			
		||||
    five six
 | 
			
		||||
    </selection>
 | 
			
		||||
    $se
 | 
			
		||||
      """.trimIndent(),
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
@@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
			
		||||
    configureByText(before)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(keys)
 | 
			
		||||
    assertState(after)
 | 
			
		||||
    assertState(Mode.NORMAL())
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
@@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
			
		||||
    configureByText(before)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(keys)
 | 
			
		||||
    assertState(after)
 | 
			
		||||
    assertState(Mode.NORMAL())
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
@@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
 | 
			
		||||
  @Test
 | 
			
		||||
  fun testWithoutSpaces() {
 | 
			
		||||
    configureByText("test<caret>test")
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(injector.parser.parseKeys("gn"))
 | 
			
		||||
    assertOffset(7)
 | 
			
		||||
    assertSelection("test")
 | 
			
		||||
 
 | 
			
		||||
@@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
@@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
 | 
			
		||||
  @Test
 | 
			
		||||
  fun testWithoutSpaces() {
 | 
			
		||||
    configureByText("tes<caret>ttest")
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
			
		||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
			
		||||
    typeText(injector.parser.parseKeys("gN"))
 | 
			
		||||
    assertOffset(0)
 | 
			
		||||
    assertSelection("test")
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action.motion.search
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
@@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private fun doTestWithSearch(keys: String, before: String, after: String) {
 | 
			
		||||
    doTest(keys, before, after) {
 | 
			
		||||
      VimPlugin.getSearch().setLastSearchState(it, "all", "", Direction.FORWARDS)
 | 
			
		||||
      VimPlugin.getSearch().setLastSearchState(it.vim, "all", "", Direction.FORWARDS)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,26 +10,45 @@ package org.jetbrains.plugins.ideavim.ex.implementation.commands
 | 
			
		||||
import com.intellij.idea.TestFor
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
 | 
			
		||||
import com.intellij.openapi.util.Disposer
 | 
			
		||||
import com.intellij.testFramework.LoggedErrorProcessor
 | 
			
		||||
import com.intellij.testFramework.TestLoggerFactory.TestLoggerAssertionError
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
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.ex.ExException
 | 
			
		||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
 | 
			
		||||
import com.maddyhome.idea.vim.history.HistoryConstants
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
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.assertThrowsLogError
 | 
			
		||||
import org.jetbrains.plugins.ideavim.exceptionMappingOwner
 | 
			
		||||
import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
			
		||||
import org.junit.jupiter.api.AfterEach
 | 
			
		||||
import org.junit.jupiter.api.Disabled
 | 
			
		||||
import org.junit.jupiter.api.Test
 | 
			
		||||
import org.junit.jupiter.api.assertThrows
 | 
			
		||||
import javax.swing.JTextArea
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertIs
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
class MapCommandTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  fun tearDown() {
 | 
			
		||||
    injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.UNCLEAR)
 | 
			
		||||
  @Test
 | 
			
		||||
  fun testMapKtoJ() {
 | 
			
		||||
@@ -876,13 +895,14 @@ n  ,f            <Plug>Foo
 | 
			
		||||
      indicateErrors = true,
 | 
			
		||||
      null,
 | 
			
		||||
    )
 | 
			
		||||
    val exception = assertThrows<Throwable> {
 | 
			
		||||
    val exception = assertThrowsLogError<TestLoggerAssertionError> {
 | 
			
		||||
      typeText(injector.parser.parseKeys("t"))
 | 
			
		||||
    }
 | 
			
		||||
    assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
 | 
			
		||||
    assertIs<ExException>(exception.cause) // Exception is wrapped into LOG.error twice
 | 
			
		||||
 | 
			
		||||
    assertPluginError(true)
 | 
			
		||||
    assertPluginErrorMessageContains("E121: Undefined variable: s:mapping")
 | 
			
		||||
    editor.caretModel.allCarets.forEach { Disposer.dispose(it) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // todo keyPresses invoked inside a script should have access to the script context
 | 
			
		||||
@@ -923,11 +943,10 @@ n  ,f            <Plug>Foo
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
    configureByJavaText(text)
 | 
			
		||||
 | 
			
		||||
    val exception = assertThrows<Throwable> {
 | 
			
		||||
    assertThrowsLogError<ExException> {
 | 
			
		||||
      typeText(commandToKeys("inoremap <expr> <cr> unknownFunction() ? '\\<C-y>' : '\\<C-g>u\\<CR>'"))
 | 
			
		||||
      typeText(injector.parser.parseKeys("i<CR>"))
 | 
			
		||||
    }
 | 
			
		||||
    assertIs<ExException>(exception.cause) // The original exception comes from the LOG.error, so we check the cause
 | 
			
		||||
 | 
			
		||||
    assertPluginError(true)
 | 
			
		||||
    assertPluginErrorMessageContains("E117: Unknown function: unknownFunction")
 | 
			
		||||
@@ -1097,4 +1116,32 @@ n  ,i            <Action>(Back)
 | 
			
		||||
     Cras id tellus in ex imperdiet egestas. 
 | 
			
		||||
    """.trimIndent())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @TestFor(issues = ["VIM-2929"])
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.ACTION_COMMAND)
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `mapping 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)
 | 
			
		||||
 | 
			
		||||
    typeText(commandToKeys("map k abcx"))
 | 
			
		||||
 | 
			
		||||
    val exception = assertThrows<Throwable> {
 | 
			
		||||
      LoggedErrorProcessor.executeWith<Throwable>(OnlyThrowLoggedErrorProcessor) {
 | 
			
		||||
        typeText("k")
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    assertEquals(ExceptionHandler.exceptionMessage, exception.cause!!.cause!!.message)
 | 
			
		||||
 | 
			
		||||
    assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,7 +9,9 @@
 | 
			
		||||
package org.jetbrains.plugins.ideavim.extension
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.plugins.PluginManagerCore
 | 
			
		||||
import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.application.invokeLater
 | 
			
		||||
import com.intellij.openapi.util.Disposer
 | 
			
		||||
import com.intellij.testFramework.PlatformTestUtil
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -41,28 +43,23 @@ import kotlin.test.assertFalse
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
 | 
			
		||||
class OpMappingTest : VimTestCase() {
 | 
			
		||||
  private var initialized = false
 | 
			
		||||
 | 
			
		||||
  private lateinit var extension: ExtensionBeanClass
 | 
			
		||||
 | 
			
		||||
  private var disposable: Disposable = Disposer.newDisposable()
 | 
			
		||||
 | 
			
		||||
  @BeforeEach
 | 
			
		||||
  override fun setUp(testInfo: TestInfo) {
 | 
			
		||||
    super.setUp(testInfo)
 | 
			
		||||
    if (!initialized) {
 | 
			
		||||
      initialized = true
 | 
			
		||||
    extension = TestExtension.createBean()
 | 
			
		||||
 | 
			
		||||
      extension = TestExtension.createBean()
 | 
			
		||||
 | 
			
		||||
      VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
			
		||||
      enableExtensions("TestExtension")
 | 
			
		||||
    }
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
			
		||||
    enableExtensions("TestExtension")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  override fun tearDown(testInfo: TestInfo) {
 | 
			
		||||
    @Suppress("DEPRECATION")
 | 
			
		||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
			
		||||
    super.tearDown(super.testInfo)
 | 
			
		||||
    Disposer.dispose(disposable)
 | 
			
		||||
    super.tearDown(testInfo)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -138,13 +135,13 @@ class OpMappingTest : VimTestCase() {
 | 
			
		||||
    typeText(injector.parser.parseKeys("Q"))
 | 
			
		||||
    assertState("I$c found it in a legendary land")
 | 
			
		||||
 | 
			
		||||
    @Suppress("DEPRECATION")
 | 
			
		||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
			
		||||
    Disposer.dispose(disposable)
 | 
			
		||||
    disposable = Disposer.newDisposable()
 | 
			
		||||
    assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
 | 
			
		||||
    typeText(injector.parser.parseKeys("Q"))
 | 
			
		||||
    assertState("I$c found it in a legendary land")
 | 
			
		||||
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
			
		||||
    assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
 | 
			
		||||
    enableExtensions("TestExtension")
 | 
			
		||||
    typeText(injector.parser.parseKeys("Q"))
 | 
			
		||||
@@ -158,12 +155,12 @@ class OpMappingTest : VimTestCase() {
 | 
			
		||||
    assertState("I$c found it in a legendary land")
 | 
			
		||||
 | 
			
		||||
    enterCommand("set noTestExtension")
 | 
			
		||||
    @Suppress("DEPRECATION")
 | 
			
		||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
			
		||||
    Disposer.dispose(disposable)
 | 
			
		||||
    disposable = Disposer.newDisposable()
 | 
			
		||||
    typeText(injector.parser.parseKeys("Q"))
 | 
			
		||||
    assertState("I$c found it in a legendary land")
 | 
			
		||||
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
			
		||||
    enableExtensions("TestExtension")
 | 
			
		||||
    typeText(injector.parser.parseKeys("Q"))
 | 
			
		||||
    assertState("I ${c}found it in a legendary land")
 | 
			
		||||
@@ -201,19 +198,20 @@ class PlugExtensionsTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private lateinit var extension: ExtensionBeanClass
 | 
			
		||||
 | 
			
		||||
  private var disposable: Disposable = Disposer.newDisposable()
 | 
			
		||||
 | 
			
		||||
  @BeforeEach
 | 
			
		||||
  override fun setUp(testInfo: TestInfo) {
 | 
			
		||||
    super.setUp(testInfo)
 | 
			
		||||
    configureByText("\n")
 | 
			
		||||
 | 
			
		||||
    extension = TestExtension.createBean()
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  override fun tearDown(testInfo: TestInfo) {
 | 
			
		||||
    @Suppress("DEPRECATION")
 | 
			
		||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
			
		||||
    Disposer.dispose(disposable)
 | 
			
		||||
    super.tearDown(super.testInfo)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -244,19 +242,20 @@ class PlugMissingKeysTest : VimTestCase() {
 | 
			
		||||
 | 
			
		||||
  private lateinit var extension: ExtensionBeanClass
 | 
			
		||||
 | 
			
		||||
  private var disposable: Disposable = Disposer.newDisposable()
 | 
			
		||||
 | 
			
		||||
  @BeforeEach
 | 
			
		||||
  override fun setUp(testInfo: TestInfo) {
 | 
			
		||||
    super.setUp(testInfo)
 | 
			
		||||
    configureByText("\n")
 | 
			
		||||
 | 
			
		||||
    extension = TestExtension.createBean()
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
			
		||||
    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  override fun tearDown(testInfo: TestInfo) {
 | 
			
		||||
    @Suppress("DEPRECATION")
 | 
			
		||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
			
		||||
    Disposer.dispose(disposable)
 | 
			
		||||
    super.tearDown(super.testInfo)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,16 +14,16 @@ import com.intellij.codeInsight.template.TemplateManager
 | 
			
		||||
import com.intellij.codeInsight.template.impl.ConstantNode
 | 
			
		||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
			
		||||
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerManager
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.options.OptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestOptionConstants
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
@@ -33,6 +33,7 @@ import org.jetbrains.plugins.ideavim.impl.TraceOptions
 | 
			
		||||
import org.jetbrains.plugins.ideavim.impl.VimOption
 | 
			
		||||
import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
			
		||||
import org.jetbrains.plugins.ideavim.waitAndAssertMode
 | 
			
		||||
import kotlin.test.assertNull
 | 
			
		||||
 | 
			
		||||
@TraceOptions(TestOptionConstants.selectmode)
 | 
			
		||||
class IdeaVisualControlTest : VimTestCase() {
 | 
			
		||||
@@ -764,6 +765,23 @@ class IdeaVisualControlTest : VimTestCase() {
 | 
			
		||||
    assertCaretsVisualAttributes()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @OptionTest(VimOption(TestOptionConstants.selectmode, limitedValues = [""]))
 | 
			
		||||
  @TestWithoutNeovim(reason = SkipNeovimReason.NOT_VIM_TESTING)
 | 
			
		||||
  fun `test control selection interruption`() {
 | 
			
		||||
    configureByText(
 | 
			
		||||
      """
 | 
			
		||||
            Lorem Ipsum
 | 
			
		||||
 | 
			
		||||
            I $s${c}found$se it in a legendary land
 | 
			
		||||
            consectetur adipiscing elit
 | 
			
		||||
      """.trimIndent(),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    IdeaSelectionControl.controlNonVimSelectionChange(fixture.editor)
 | 
			
		||||
    typeText(injector.parser.parseKeys("V"))
 | 
			
		||||
    assertNull(VimVisualTimer.swingTimer)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun startDummyTemplate() {
 | 
			
		||||
    TemplateManagerImpl.setTemplateTesting(fixture.testRootDisposable)
 | 
			
		||||
    val templateManager = TemplateManager.getInstance(fixture.project)
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.listener
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerTestObject
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
import org.junit.jupiter.api.AfterEach
 | 
			
		||||
import org.junit.jupiter.api.Test
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
 | 
			
		||||
class VimListenersTest : VimTestCase() {
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  fun tearDown() {
 | 
			
		||||
    VimListenerTestObject.disposedCounter = 0
 | 
			
		||||
    VimListenerTestObject.enabled = false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `disposable is called on plugin disable`() {
 | 
			
		||||
    configureByText("XYZ")
 | 
			
		||||
    VimListenerTestObject.disposedCounter = 0
 | 
			
		||||
    VimListenerTestObject.enabled = true
 | 
			
		||||
 | 
			
		||||
    VimPlugin.setEnabled(false)
 | 
			
		||||
 | 
			
		||||
    assertEquals(1, VimListenerTestObject.disposedCounter)
 | 
			
		||||
 | 
			
		||||
    VimPlugin.setEnabled(true)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,11 +12,14 @@ import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
 | 
			
		||||
import com.intellij.openapi.util.Disposer
 | 
			
		||||
import com.intellij.testFramework.junit5.TestDisposable
 | 
			
		||||
import com.intellij.testFramework.replaceService
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
			
		||||
import org.junit.jupiter.api.AfterEach
 | 
			
		||||
import org.mockito.Mockito
 | 
			
		||||
import javax.swing.JTextArea
 | 
			
		||||
 | 
			
		||||
@@ -28,6 +31,11 @@ open class MockTestCase : VimTestCase() {
 | 
			
		||||
  val editorStub = TextComponentEditorImpl(null, JTextArea()).vim
 | 
			
		||||
  val contextStub: ExecutionContext = DataContext.EMPTY_CONTEXT.vim
 | 
			
		||||
 | 
			
		||||
  @AfterEach
 | 
			
		||||
  fun tearDown() {
 | 
			
		||||
    editorStub.carets().forEach { Disposer.dispose(it.ij) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun <T : Any> mockService(service: Class<T>): T {
 | 
			
		||||
    val mock = Mockito.mock(service)
 | 
			
		||||
    val applicationManager = ApplicationManager.getApplication()
 | 
			
		||||
 
 | 
			
		||||
@@ -22,9 +22,11 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.options.NumberOption
 | 
			
		||||
import com.maddyhome.idea.vim.options.OptionAccessScope
 | 
			
		||||
import com.maddyhome.idea.vim.options.OptionDeclaredScope
 | 
			
		||||
import com.maddyhome.idea.vim.options.StringOption
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimInt
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
			
		||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
			
		||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
			
		||||
@@ -34,9 +36,10 @@ import org.junit.jupiter.api.BeforeEach
 | 
			
		||||
import org.junit.jupiter.api.Test
 | 
			
		||||
import org.junit.jupiter.api.TestInfo
 | 
			
		||||
import javax.swing.SwingConstants
 | 
			
		||||
import kotlin.test.assertContentEquals
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
 | 
			
		||||
private const val defaultValue = "defaultValue"
 | 
			
		||||
private const val defaultNumberValue = 10
 | 
			
		||||
 | 
			
		||||
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
 | 
			
		||||
class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
			
		||||
@@ -109,10 +112,17 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
			
		||||
    return option
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun addNumberOption(scope: OptionDeclaredScope): NumberOption {
 | 
			
		||||
    val option = NumberOption(optionName, scope, optionName, defaultNumberValue)
 | 
			
		||||
    injector.optionGroup.addOption(option)
 | 
			
		||||
    injector.optionGroup.addEffectiveOptionValueChangeListener(option, Listener)
 | 
			
		||||
    return option
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun assertNotifiedEditors(vararg editors: Editor) {
 | 
			
		||||
    val sortedExpected = editors.sortedBy { it.virtualFile!!.path }.toTypedArray()
 | 
			
		||||
    val sortedActual = Listener.notifiedEditors.sortedBy { it.virtualFile!!.path }.toTypedArray()
 | 
			
		||||
    assertContentEquals(sortedExpected, sortedActual)
 | 
			
		||||
    val expected = editors.toSet()
 | 
			
		||||
    val actual = Listener.notifiedEditors.toSet()
 | 
			
		||||
    assertEquals(expected, actual)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun assertNoNotifications() = assertNotifiedEditors()
 | 
			
		||||
@@ -272,10 +282,23 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test listener called for all editors when locally modified global-local local-to-buffer option changes at effective scope`() {
 | 
			
		||||
    val option = addOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER)
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(otherBufferWindow.vim), VimString("localValue"))
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimString("localValue"))
 | 
			
		||||
    Listener.notifiedEditors.clear()
 | 
			
		||||
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(otherBufferWindow.vim), VimString("newValue"))
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimString("newValue"))
 | 
			
		||||
 | 
			
		||||
    assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test listener called for all editors when locally modified number global-local local-to-buffer option changes at effective scope`() {
 | 
			
		||||
    // When a number (and therefore also toggle) global-local option is set at effective scope, the local value is not
 | 
			
		||||
    // reset to [Option.unsetValue] but to a copy of the new value. The local editor(s) should still be notified.
 | 
			
		||||
    val option = addNumberOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_BUFFER)
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimInt(100))
 | 
			
		||||
    Listener.notifiedEditors.clear()
 | 
			
		||||
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimInt(200))
 | 
			
		||||
 | 
			
		||||
    assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
 | 
			
		||||
  }
 | 
			
		||||
@@ -327,6 +350,19 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
			
		||||
    assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test listener called for all editors when locally modified number global-local local-to-window option changes at effective scope`() {
 | 
			
		||||
    // When a number (and therefore also toggle) global-local option is set at effective scope, the local value is not
 | 
			
		||||
    // reset to [Option.unsetValue] but to a copy of the new value. The local editor(s) should still be notified.
 | 
			
		||||
    val option = addNumberOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_WINDOW)
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(originalEditor.vim), VimInt(100))
 | 
			
		||||
    Listener.notifiedEditors.clear()
 | 
			
		||||
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(originalEditor.vim), VimInt(200))
 | 
			
		||||
 | 
			
		||||
    assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test listener not called for locally modified editor when global-local local-to-window option changes at global scope`() {
 | 
			
		||||
    val option = addOption(OptionDeclaredScope.GLOBAL_OR_LOCAL_TO_WINDOW)
 | 
			
		||||
 
 | 
			
		||||
@@ -129,6 +129,23 @@ class OptionAccessScopeTest: VimTestCase() {
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test set local-to-buffer option at effective scope to current value changes global value`() {
 | 
			
		||||
    val defaultValue = VimInt(10)
 | 
			
		||||
    val option = NumberOption(OPTION_NAME, OptionDeclaredScope.LOCAL_TO_BUFFER, OPTION_NAME, defaultValue)
 | 
			
		||||
    injector.optionGroup.addOption(option)
 | 
			
		||||
 | 
			
		||||
    val effectiveValue = VimInt(100)
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim), effectiveValue)
 | 
			
		||||
    assertEquals(defaultValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
 | 
			
		||||
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim), effectiveValue)
 | 
			
		||||
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim)))
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // LOCAL_TO_WINDOW
 | 
			
		||||
  @Test
 | 
			
		||||
@@ -173,6 +190,23 @@ class OptionAccessScopeTest: VimTestCase() {
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  fun `test set local-to-window option at effective scope to current value changes global value`() {
 | 
			
		||||
    val defaultValue = VimInt(10)
 | 
			
		||||
    val option = NumberOption(OPTION_NAME, OptionDeclaredScope.LOCAL_TO_WINDOW, OPTION_NAME, defaultValue)
 | 
			
		||||
 | 
			
		||||
    injector.optionGroup.addOption(option)
 | 
			
		||||
    val effectiveValue = VimInt(100)
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim), effectiveValue)
 | 
			
		||||
    assertEquals(defaultValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
 | 
			
		||||
 | 
			
		||||
    injector.optionGroup.setOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim), effectiveValue)
 | 
			
		||||
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(fixture.editor.vim)))
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.LOCAL(fixture.editor.vim)))
 | 
			
		||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  // Global-local is tricky. The local value is initially not set, and the global value is used until it is. For string
 | 
			
		||||
  // options, this is represented by the local value showing as an empty string. Number options usually use the value -1
 | 
			
		||||
 
 | 
			
		||||
@@ -8,14 +8,13 @@
 | 
			
		||||
 | 
			
		||||
package ui
 | 
			
		||||
 | 
			
		||||
import com.automation.remarks.junit.VideoRule
 | 
			
		||||
import com.automation.remarks.video.annotations.Video
 | 
			
		||||
import com.automation.remarks.junit5.Video
 | 
			
		||||
import com.intellij.remoterobot.RemoteRobot
 | 
			
		||||
import com.intellij.remoterobot.fixtures.ContainerFixture
 | 
			
		||||
import com.intellij.remoterobot.steps.CommonSteps
 | 
			
		||||
import com.intellij.remoterobot.stepsProcessing.step
 | 
			
		||||
import com.intellij.remoterobot.utils.keyboard
 | 
			
		||||
import org.assertj.swing.core.MouseButton
 | 
			
		||||
import org.junit.Rule
 | 
			
		||||
import org.junit.jupiter.api.Test
 | 
			
		||||
import ui.pages.Editor
 | 
			
		||||
import ui.pages.IdeaFrame
 | 
			
		||||
@@ -25,6 +24,7 @@ import ui.pages.dialog
 | 
			
		||||
import ui.pages.editor
 | 
			
		||||
import ui.pages.gutter
 | 
			
		||||
import ui.pages.idea
 | 
			
		||||
import ui.pages.searchEverywhere
 | 
			
		||||
import ui.pages.welcomeFrame
 | 
			
		||||
import ui.utils.JavaExampleSteps
 | 
			
		||||
import ui.utils.StepsLogger
 | 
			
		||||
@@ -38,6 +38,7 @@ import ui.utils.tripleClickOnRight
 | 
			
		||||
import ui.utils.uiTest
 | 
			
		||||
import ui.utils.vimExit
 | 
			
		||||
import java.awt.Point
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import kotlin.test.assertEquals
 | 
			
		||||
import kotlin.test.assertFalse
 | 
			
		||||
import kotlin.test.assertTrue
 | 
			
		||||
@@ -47,14 +48,19 @@ class UiTests {
 | 
			
		||||
    StepsLogger.init()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Rule
 | 
			
		||||
  @JvmField
 | 
			
		||||
  var videoRule = VideoRule()
 | 
			
		||||
  private lateinit var commonSteps: CommonSteps
 | 
			
		||||
 | 
			
		||||
  private val testTextForEditor = """
 | 
			
		||||
                  |One Two
 | 
			
		||||
                  |Three Four
 | 
			
		||||
                  |Five
 | 
			
		||||
              """.trimMargin()
 | 
			
		||||
 | 
			
		||||
  @Test
 | 
			
		||||
  @Video
 | 
			
		||||
  fun ideaVimTest() = uiTest("ideaVimTest") {
 | 
			
		||||
    val sharedSteps = JavaExampleSteps(this)
 | 
			
		||||
    commonSteps = CommonSteps(this)
 | 
			
		||||
 | 
			
		||||
    startNewProject()
 | 
			
		||||
    Thread.sleep(1000)
 | 
			
		||||
@@ -63,16 +69,11 @@ class UiTests {
 | 
			
		||||
    Thread.sleep(1000)
 | 
			
		||||
 | 
			
		||||
    idea {
 | 
			
		||||
      waitSmartMode()
 | 
			
		||||
      createFile("MyDoc.txt", this@uiTest)
 | 
			
		||||
      val editor = editor("MyDoc.txt") {
 | 
			
		||||
        step("Write a text") {
 | 
			
		||||
          injectText(
 | 
			
		||||
            """
 | 
			
		||||
                |One Two
 | 
			
		||||
                |Three Four
 | 
			
		||||
                |Five
 | 
			
		||||
            """.trimMargin(),
 | 
			
		||||
          )
 | 
			
		||||
          injectText(testTextForEditor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      testSelectTextWithDelay(editor)
 | 
			
		||||
@@ -89,6 +90,11 @@ class UiTests {
 | 
			
		||||
      testClickRightFromLineEnd(editor)
 | 
			
		||||
      testClickOnWord(editor)
 | 
			
		||||
      testGutterClick(editor)
 | 
			
		||||
      testAddNewLineInNormalMode(editor)
 | 
			
		||||
      testMappingToCtrlOrAltEnter(editor)
 | 
			
		||||
      `simple enter in insert mode`(editor)
 | 
			
		||||
      testMilticaretEnter(editor)
 | 
			
		||||
      `simple enter in select mode`(editor)
 | 
			
		||||
      reenableIdeaVim(editor)
 | 
			
		||||
 | 
			
		||||
      createFile("MyTest.java", this@uiTest)
 | 
			
		||||
@@ -96,7 +102,7 @@ class UiTests {
 | 
			
		||||
        step("Write a text") {
 | 
			
		||||
          injectText(
 | 
			
		||||
            """
 | 
			
		||||
                |class Main {
 | 
			
		||||
                |class MyTest {
 | 
			
		||||
                |  public static void main() {
 | 
			
		||||
                |    System.out.println("Hello");
 | 
			
		||||
                |  }
 | 
			
		||||
@@ -115,7 +121,6 @@ class UiTests {
 | 
			
		||||
 | 
			
		||||
  private fun closeUnrelated(sharedSteps: JavaExampleSteps) {
 | 
			
		||||
    with(sharedSteps) {
 | 
			
		||||
      closeIdeaVimDialog()
 | 
			
		||||
      closeTipOfTheDay()
 | 
			
		||||
      closeAllTabs()
 | 
			
		||||
    }
 | 
			
		||||
@@ -126,7 +131,7 @@ class UiTests {
 | 
			
		||||
      createNewProjectLink.click()
 | 
			
		||||
      dialog("New Project") {
 | 
			
		||||
        findText("Java").click()
 | 
			
		||||
        checkBox("Add sample code").select()
 | 
			
		||||
        checkBox("Add sample code").unselect()
 | 
			
		||||
        button("Create").click()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -195,9 +200,11 @@ class UiTests {
 | 
			
		||||
 | 
			
		||||
  private fun IdeaFrame.testTrackActionId(editor: Editor) {
 | 
			
		||||
    remoteRobot.invokeActionJs("GotoAction")
 | 
			
		||||
    editor.keyboard {
 | 
			
		||||
      enterText("IdeaVim: Track Action Ids")
 | 
			
		||||
      enter()
 | 
			
		||||
 | 
			
		||||
    val searchEverywhere = this@testTrackActionId.searchEverywhere()
 | 
			
		||||
 | 
			
		||||
    commonSteps.invokeAction("VimFindActionIdAction")
 | 
			
		||||
    keyboard {
 | 
			
		||||
      escape()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -211,7 +218,7 @@ class UiTests {
 | 
			
		||||
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
                |EditorEscapeclass Main {
 | 
			
		||||
                |EditorEscapeclass MyTest {
 | 
			
		||||
                |  public static void main() {
 | 
			
		||||
                |      if (true) {
 | 
			
		||||
                |          System.out.println("Hello");
 | 
			
		||||
@@ -231,7 +238,7 @@ class UiTests {
 | 
			
		||||
  private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) {
 | 
			
		||||
    step("Create $fileName file") {
 | 
			
		||||
      with(projectViewTree) {
 | 
			
		||||
        setExpandTimeout(15_000)
 | 
			
		||||
        setExpandTimeout(30_000)
 | 
			
		||||
        expand(projectName, "src")
 | 
			
		||||
        findText("src").click(MouseButton.RIGHT_BUTTON)
 | 
			
		||||
      }
 | 
			
		||||
@@ -510,4 +517,187 @@ class UiTests {
 | 
			
		||||
 | 
			
		||||
    vimExit()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // For VIM-3159
 | 
			
		||||
  private fun ContainerFixture.testAddNewLineInNormalMode(editor: Editor) {
 | 
			
		||||
    println("Run testAddNewLineInNormalMode...")
 | 
			
		||||
 | 
			
		||||
    commonSteps.invokeAction("EditorStartNewLineBefore")
 | 
			
		||||
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
      |
 | 
			
		||||
      |One Two
 | 
			
		||||
      |Three Four
 | 
			
		||||
      |Five
 | 
			
		||||
    """.trimMargin(), editor.text
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    editor.injectText(testTextForEditor)
 | 
			
		||||
 | 
			
		||||
    commonSteps.invokeAction("EditorStartNewLine")
 | 
			
		||||
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
      |One Two
 | 
			
		||||
      |
 | 
			
		||||
      |Three Four
 | 
			
		||||
      |Five
 | 
			
		||||
    """.trimMargin(), editor.text
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    editor.injectText(testTextForEditor)
 | 
			
		||||
 | 
			
		||||
    vimExit()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // For VIM-3190
 | 
			
		||||
  private fun ContainerFixture.testMappingToCtrlOrAltEnter(editor: Editor) {
 | 
			
		||||
    println("Run testMappingToCtrlOrAltEnter...")
 | 
			
		||||
 | 
			
		||||
    keyboard {
 | 
			
		||||
      enterText(":nmap <C-Enter> k")
 | 
			
		||||
      enter()
 | 
			
		||||
      enterText(":nmap <A-Enter> G")
 | 
			
		||||
      enter()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Set up initial position
 | 
			
		||||
    keyboard {
 | 
			
		||||
      enterText("jll")
 | 
			
		||||
    }
 | 
			
		||||
    assertEquals(10, editor.caretOffset)
 | 
			
		||||
 | 
			
		||||
    // Checking C-ENTER
 | 
			
		||||
    keyboard {
 | 
			
		||||
      pressing(KeyEvent.VK_CONTROL) { enter() }
 | 
			
		||||
    }
 | 
			
		||||
    assertEquals(2, editor.caretOffset)
 | 
			
		||||
 | 
			
		||||
    // Checking A-ENTER
 | 
			
		||||
    keyboard {
 | 
			
		||||
      pressing(KeyEvent.VK_ALT) { enter() }
 | 
			
		||||
    }
 | 
			
		||||
    assertEquals(19, editor.caretOffset)
 | 
			
		||||
 | 
			
		||||
    vimExit()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // For VIM-3186
 | 
			
		||||
  private fun ContainerFixture.testMilticaretEnter(editor: Editor) {
 | 
			
		||||
    println("Run testMilticaretEnter...")
 | 
			
		||||
 | 
			
		||||
    keyboard {
 | 
			
		||||
      pressing(KeyEvent.VK_ALT) {
 | 
			
		||||
        pressing(KeyEvent.VK_SHIFT) {
 | 
			
		||||
          findText("One").click()
 | 
			
		||||
          findText("Three").click()
 | 
			
		||||
          findText("Five").click()
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      enterText("A")
 | 
			
		||||
      enter()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assertEquals(3, editor.caretCount)
 | 
			
		||||
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
      |One Two
 | 
			
		||||
      |
 | 
			
		||||
      |Three Four
 | 
			
		||||
      |
 | 
			
		||||
      |Five
 | 
			
		||||
      |
 | 
			
		||||
    """.trimMargin(), editor.text
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    // Reset state
 | 
			
		||||
    keyboard {
 | 
			
		||||
      escape()
 | 
			
		||||
      escape()
 | 
			
		||||
    }
 | 
			
		||||
    assertEquals(1, editor.caretCount)
 | 
			
		||||
    editor.injectText(testTextForEditor)
 | 
			
		||||
    vimExit()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun ContainerFixture.`simple enter in insert mode`(editor: Editor) {
 | 
			
		||||
    println("Run test 'simple enter in insert mode'...")
 | 
			
		||||
 | 
			
		||||
    // Start of file
 | 
			
		||||
    keyboard {
 | 
			
		||||
      enterText("i")
 | 
			
		||||
      enter()
 | 
			
		||||
    }
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
      |
 | 
			
		||||
      |One Two
 | 
			
		||||
      |Three Four
 | 
			
		||||
      |Five
 | 
			
		||||
    """.trimMargin(),
 | 
			
		||||
      editor.text
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    // Middle of file
 | 
			
		||||
    findText("Four").click()
 | 
			
		||||
    keyboard { enter() }
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
      |
 | 
			
		||||
      |One Two
 | 
			
		||||
      |Three 
 | 
			
		||||
      |Four
 | 
			
		||||
      |Five
 | 
			
		||||
    """.trimMargin(),
 | 
			
		||||
      editor.text
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    // End of file
 | 
			
		||||
    val fivePoint = findText("Five").point
 | 
			
		||||
    val endOfLine = Point(fivePoint.x + 50, fivePoint.y)
 | 
			
		||||
    click(endOfLine)
 | 
			
		||||
    keyboard { enter() }
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
      |
 | 
			
		||||
      |One Two
 | 
			
		||||
      |Three 
 | 
			
		||||
      |Four
 | 
			
		||||
      |Five
 | 
			
		||||
      |
 | 
			
		||||
    """.trimMargin(),
 | 
			
		||||
      editor.text
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    editor.injectText(testTextForEditor)
 | 
			
		||||
    vimExit()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  private fun ContainerFixture.`simple enter in select mode`(editor: Editor) {
 | 
			
		||||
    println("Run test 'simple enter in select mode'...")
 | 
			
		||||
 | 
			
		||||
    findText("Four").doubleClick()
 | 
			
		||||
 | 
			
		||||
    keyboard {
 | 
			
		||||
      pressing(KeyEvent.VK_CONTROL) { enterText("g") }
 | 
			
		||||
      enter()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    assertEquals(
 | 
			
		||||
      """
 | 
			
		||||
      |One Two
 | 
			
		||||
      |Three 
 | 
			
		||||
      |
 | 
			
		||||
      |Five
 | 
			
		||||
    """.trimMargin(),
 | 
			
		||||
      editor.text
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    editor.injectText(testTextForEditor)
 | 
			
		||||
    vimExit()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -44,6 +44,9 @@ class Editor(
 | 
			
		||||
  val caretOffset: Int
 | 
			
		||||
    get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
 | 
			
		||||
 | 
			
		||||
  val caretCount: Int
 | 
			
		||||
    get() = callJs("component.getEditor().getCaretModel().getCaretCount()", runInEdt = true)
 | 
			
		||||
 | 
			
		||||
  val isBlockCursor: Boolean
 | 
			
		||||
//    get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
 | 
			
		||||
    // Doesn't work at the moment because remote robot can't resolve classes from a plugin classloader
 | 
			
		||||
 
 | 
			
		||||
@@ -51,6 +51,14 @@ class IdeaFrame(
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun waitSmartMode(timeout: Duration = Duration.ofMinutes(5)) {
 | 
			
		||||
    step("Wait for smart mode") {
 | 
			
		||||
      waitFor(duration = timeout, interval = Duration.ofSeconds(5)) {
 | 
			
		||||
        isDumbMode().not()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun isDumbMode(): Boolean {
 | 
			
		||||
    return callJs("com.intellij.openapi. project.DumbService.isDumb(component.project);", true)
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										31
									
								
								src/test/java/ui/pages/SearchEverywhere.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								src/test/java/ui/pages/SearchEverywhere.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,31 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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 ui.pages
 | 
			
		||||
 | 
			
		||||
import com.intellij.remoterobot.RemoteRobot
 | 
			
		||||
import com.intellij.remoterobot.data.RemoteComponent
 | 
			
		||||
import com.intellij.remoterobot.fixtures.CommonContainerFixture
 | 
			
		||||
import com.intellij.remoterobot.fixtures.ContainerFixture
 | 
			
		||||
import com.intellij.remoterobot.fixtures.FixtureName
 | 
			
		||||
import com.intellij.remoterobot.search.locators.byXpath
 | 
			
		||||
 | 
			
		||||
@JvmOverloads
 | 
			
		||||
fun ContainerFixture.searchEverywhere(function: SearchEverywhere.() -> Unit = {}): SearchEverywhere {
 | 
			
		||||
  return find<SearchEverywhere>(
 | 
			
		||||
    byXpath("Search Everywhere", "//div[@accessiblename='Search everywhere' and @class='SearchEverywhereUI']"),
 | 
			
		||||
  )
 | 
			
		||||
    .apply { runJs("robot.moveMouse(component);") }
 | 
			
		||||
    .apply(function)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@FixtureName("SearchEverywhere")
 | 
			
		||||
class SearchEverywhere(
 | 
			
		||||
  remoteRobot: RemoteRobot,
 | 
			
		||||
  remoteComponent: RemoteComponent,
 | 
			
		||||
) : CommonContainerFixture(remoteRobot, remoteComponent)
 | 
			
		||||
@@ -17,19 +17,11 @@ import com.intellij.remoterobot.utils.Keyboard
 | 
			
		||||
import ui.pages.DialogFixture
 | 
			
		||||
import ui.pages.DialogFixture.Companion.byTitle
 | 
			
		||||
import ui.pages.IdeaFrame
 | 
			
		||||
import ui.pages.dialog
 | 
			
		||||
import ui.pages.idea
 | 
			
		||||
 | 
			
		||||
class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
 | 
			
		||||
  @Suppress("unused")
 | 
			
		||||
  private val keyboard: Keyboard = Keyboard(remoteRobot)
 | 
			
		||||
 | 
			
		||||
  fun closeIdeaVimDialog() = optionalStep("Close Idea Vim dialog if it appears") {
 | 
			
		||||
    remoteRobot.idea {
 | 
			
		||||
      dialog("IdeaVim") { button("Yes").click() }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun closeTipOfTheDay() = optionalStep("Close Tip of the Day if it appears") {
 | 
			
		||||
    val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
 | 
			
		||||
    idea.dumbAware {
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ import java.util.*
 | 
			
		||||
public class AutoIndentLinesVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE, CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,10 @@ 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.CharacterHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
@@ -28,8 +25,6 @@ import java.util.*
 | 
			
		||||
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,13 +14,10 @@ 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.CharacterHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
@@ -29,8 +26,6 @@ import java.util.*
 | 
			
		||||
public class ChangeCaseToggleVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
 
 | 
			
		||||
@@ -13,13 +13,10 @@ 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.CharacterHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
@@ -28,8 +25,6 @@ import java.util.*
 | 
			
		||||
public class ChangeCaseUpperVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
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.CommandFlags.FLAG_MULTIKEY_UNDO
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_NO_REPEAT_INSERT
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
 | 
			
		||||
@@ -27,7 +26,7 @@ import java.util.*
 | 
			
		||||
public class ChangeCharactersAction : ChangeEditorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT, FLAG_MULTIKEY_UNDO)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT)
 | 
			
		||||
 | 
			
		||||
  override fun execute(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -16,7 +16,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
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.CommandFlags.FLAG_MULTIKEY_UNDO
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_NO_REPEAT_INSERT
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
 | 
			
		||||
@@ -27,7 +26,7 @@ import java.util.*
 | 
			
		||||
public class ChangeEndOfLineAction : ChangeEditorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT, FLAG_MULTIKEY_UNDO)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT)
 | 
			
		||||
 | 
			
		||||
  override fun execute(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
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.CommandFlags.FLAG_MULTIKEY_UNDO
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_NO_REPEAT_INSERT
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
 | 
			
		||||
@@ -28,7 +27,7 @@ import java.util.*
 | 
			
		||||
public class ChangeLineAction : ChangeEditorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT, FLAG_MULTIKEY_UNDO)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_NO_REPEAT_INSERT)
 | 
			
		||||
 | 
			
		||||
  override fun execute(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,18 +14,13 @@ import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
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.handler.ChangeEditorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["R"], modes = [Mode.NORMAL])
 | 
			
		||||
public class ChangeReplaceAction : ChangeEditorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MULTIKEY_UNDO)
 | 
			
		||||
 | 
			
		||||
  override fun execute(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,9 @@ 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 java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
@@ -28,8 +25,6 @@ import java.util.*
 | 
			
		||||
public class ChangeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MULTIKEY_UNDO, CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ public class ChangeVisualCharacterAction : VisualOperatorActionHandler.ForEachCa
 | 
			
		||||
 | 
			
		||||
  override val argumentType: Argument.Type = Argument.Type.DIGRAPH
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH, CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_ALLOW_DIGRAPH)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,7 @@ import com.maddyhome.idea.vim.api.getLineStartForOffset
 | 
			
		||||
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.CommandFlags.FLAG_EXIT_VISUAL
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MOT_LINEWISE
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MULTIKEY_UNDO
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
@@ -35,7 +33,7 @@ import java.util.*
 | 
			
		||||
public class ChangeVisualLinesAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE, FLAG_MULTIKEY_UNDO, FLAG_EXIT_VISUAL)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -17,9 +17,7 @@ import com.maddyhome.idea.vim.api.getLineStartForOffset
 | 
			
		||||
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.CommandFlags.FLAG_EXIT_VISUAL
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MOT_LINEWISE
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags.FLAG_MULTIKEY_UNDO
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
@@ -35,7 +33,7 @@ import java.util.*
 | 
			
		||||
public class ChangeVisualLinesEndAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE, FLAG_MULTIKEY_UNDO, FLAG_EXIT_VISUAL)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(FLAG_MOT_LINEWISE)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ import java.util.*
 | 
			
		||||
public class ReformatCodeVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE, CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_MOT_LINEWISE)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,16 +14,12 @@ 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 java.util.*
 | 
			
		||||
 | 
			
		||||
public sealed class IncNumber(public val inc: Int, private val avalanche: Boolean) : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user