mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-03 16: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 | 
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					* text=auto eol=lf
 | 
				
			||||||
							
								
								
									
										24
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							@@ -8,18 +8,20 @@ jobs:
 | 
				
			|||||||
    if: github.repository == 'JetBrains/ideavim'
 | 
					    if: github.repository == 'JetBrains/ideavim'
 | 
				
			||||||
    runs-on: macos-latest
 | 
					    runs-on: macos-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v2
 | 
					      - uses: actions/checkout@v4
 | 
				
			||||||
      - name: Setup Java
 | 
					      - name: Setup Java
 | 
				
			||||||
        uses: actions/setup-java@v2.1.0
 | 
					        uses: actions/setup-java@v4
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          distribution: zulu
 | 
					          distribution: zulu
 | 
				
			||||||
          java-version: 11
 | 
					          java-version: 11
 | 
				
			||||||
      - name: Setup FFmpeg
 | 
					      - name: Setup FFmpeg
 | 
				
			||||||
        uses: FedericoCarboni/setup-ffmpeg@v1
 | 
					        uses: FedericoCarboni/setup-ffmpeg@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          # Not strictly necessary, but it may prevent rate limit
 | 
					          # Not strictly necessary, but it may prevent rate limit
 | 
				
			||||||
          # errors especially on GitHub-hosted macos machines.
 | 
					          # 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
 | 
					      - name: Build Plugin
 | 
				
			||||||
        run: gradle :buildPlugin
 | 
					        run: gradle :buildPlugin
 | 
				
			||||||
      - name: Run Idea
 | 
					      - name: Run Idea
 | 
				
			||||||
@@ -27,7 +29,7 @@ jobs:
 | 
				
			|||||||
          mkdir -p build/reports
 | 
					          mkdir -p build/reports
 | 
				
			||||||
          gradle :runIdeForUiTests > build/reports/idea.log &
 | 
					          gradle :runIdeForUiTests > build/reports/idea.log &
 | 
				
			||||||
      - name: Wait for Idea started
 | 
					      - name: Wait for Idea started
 | 
				
			||||||
        uses: jtalk/url-health-check-action@1.5
 | 
					        uses: jtalk/url-health-check-action@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          url: http://127.0.0.1:8082
 | 
					          url: http://127.0.0.1:8082
 | 
				
			||||||
          max-attempts: 20
 | 
					          max-attempts: 20
 | 
				
			||||||
@@ -35,15 +37,19 @@ jobs:
 | 
				
			|||||||
      - name: Tests
 | 
					      - name: Tests
 | 
				
			||||||
        run: gradle :testUi
 | 
					        run: gradle :testUi
 | 
				
			||||||
      - name: Move video
 | 
					      - name: Move video
 | 
				
			||||||
        if: ${{ failure() }}
 | 
					        if: always()
 | 
				
			||||||
        run: mv video build/reports
 | 
					        run: mv video build/reports
 | 
				
			||||||
      - name: Save fails report
 | 
					      - name: Move sandbox logs
 | 
				
			||||||
        if: ${{ failure() }}
 | 
					        if: always()
 | 
				
			||||||
        uses: actions/upload-artifact@v2
 | 
					        run: mv build/idea-sandbox/system/log sandbox-idea-log
 | 
				
			||||||
 | 
					      - name: Save report
 | 
				
			||||||
 | 
					        if: always()
 | 
				
			||||||
 | 
					        uses: actions/upload-artifact@v4
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          name: ui-test-fails-report-mac
 | 
					          name: ui-test-fails-report-mac
 | 
				
			||||||
          path: |
 | 
					          path: |
 | 
				
			||||||
            build/reports
 | 
					            build/reports
 | 
				
			||||||
 | 
					            sandbox-idea-log
 | 
				
			||||||
#  build-for-ui-test-linux:
 | 
					#  build-for-ui-test-linux:
 | 
				
			||||||
#    runs-on: ubuntu-latest
 | 
					#    runs-on: ubuntu-latest
 | 
				
			||||||
#    steps:
 | 
					#    steps:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/codeStyles/Project.xml
									
									
									
										generated
									
									
									
								
							@@ -6,6 +6,7 @@
 | 
				
			|||||||
        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
					        <option name="CONTINUATION_INDENT_SIZE" value="4" />
 | 
				
			||||||
      </value>
 | 
					      </value>
 | 
				
			||||||
    </option>
 | 
					    </option>
 | 
				
			||||||
 | 
					    <option name="LINE_SEPARATOR" value="
" />
 | 
				
			||||||
    <JavaCodeStyleSettings>
 | 
					    <JavaCodeStyleSettings>
 | 
				
			||||||
      <option name="FIELD_NAME_PREFIX" value="my" />
 | 
					      <option name="FIELD_NAME_PREFIX" value="my" />
 | 
				
			||||||
      <option name="STATIC_FIELD_NAME_PREFIX" value="our" />
 | 
					      <option name="STATIC_FIELD_NAME_PREFIX" value="our" />
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.idea/copyright/IdeaVim.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/copyright/IdeaVim.xml
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
				
			|||||||
<component name="CopyrightManager">
 | 
					<component name="CopyrightManager">
 | 
				
			||||||
  <copyright>
 | 
					  <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" />
 | 
					    <option name="myName" value="IdeaVim" />
 | 
				
			||||||
  </copyright>
 | 
					  </copyright>
 | 
				
			||||||
</component>
 | 
					</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 EAP_CHANNEL = "eap"
 | 
				
			||||||
  const val DEV_CHANNEL = "Dev"
 | 
					  const val DEV_CHANNEL = "Dev"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // TODO it should be 2023.3 as soon as it releases
 | 
					  const val GITHUB_TESTS = "2023.3.2"
 | 
				
			||||||
  const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
					  const val NVIM_TESTS = "2023.3.2"
 | 
				
			||||||
  const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
					  const val PROPERTY_TESTS = "2023.3.2"
 | 
				
			||||||
  const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
					  const val LONG_RUNNING_TESTS = "2023.3.2"
 | 
				
			||||||
  const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
					  const val QODANA_TESTS = "2023.3.2"
 | 
				
			||||||
  const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
					  const val RELEASE = "2023.3.2"
 | 
				
			||||||
  const val RELEASE = "LATEST-EAP-SNAPSHOT"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const val RELEASE_DEV = "LATEST-EAP-SNAPSHOT"
 | 
					  const val RELEASE_DEV = "2023.3.2"
 | 
				
			||||||
  const val RELEASE_EAP = "LATEST-EAP-SNAPSHOT"
 | 
					  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
 | 
					  // Active tests
 | 
				
			||||||
  buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
					  buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
				
			||||||
 | 
					  buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
 | 
				
			||||||
  buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
					  buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  buildType(PropertyBased)
 | 
					  buildType(PropertyBased)
 | 
				
			||||||
@@ -38,6 +39,11 @@ object Project : Project({
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// Common build type for all configurations
 | 
					// Common build type for all configurations
 | 
				
			||||||
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
 | 
					abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
 | 
				
			||||||
 | 
					  artifactRules = """
 | 
				
			||||||
 | 
					        +:build/reports => build/reports
 | 
				
			||||||
 | 
					        +:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
 | 
				
			||||||
 | 
					    """.trimIndent()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  init()
 | 
					  init()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  requirements {
 | 
					  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)
 | 
					  [![icon][github]](https://github.com/pWydmuch)
 | 
				
			||||||
   
 | 
					   
 | 
				
			||||||
  pWydmuch
 | 
					  pWydmuch
 | 
				
			||||||
 | 
					* [![icon][mail]](mailto:leonid989@gmail.com)
 | 
				
			||||||
 | 
					  [![icon][github]](https://github.com/Infonautica)
 | 
				
			||||||
 | 
					   
 | 
				
			||||||
 | 
					  Leonid Danilov
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Previous contributors:
 | 
					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-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-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-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:
 | 
					### 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…
 | 
					* [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
 | 
					## 2.7.0, 2023-11-07
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,7 +21,7 @@ repositories {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					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") {
 | 
					  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
 | 
				
			||||||
    // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
 | 
					    // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
 | 
				
			||||||
    exclude("org.jetbrains.kotlin", "kotlin-stdlib")
 | 
					    exclude("org.jetbrains.kotlin", "kotlin-stdlib")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,14 +49,14 @@ buildscript {
 | 
				
			|||||||
        classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
					        classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // This is needed for jgit to connect to ssh
 | 
					        // This is needed for jgit to connect to ssh
 | 
				
			||||||
        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("org.kohsuke:github-api:1.305")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        classpath("io.ktor:ktor-client-core:2.3.6")
 | 
					        classpath("io.ktor:ktor-client-core:2.3.7")
 | 
				
			||||||
        classpath("io.ktor:ktor-client-cio:2.3.6")
 | 
					        classpath("io.ktor:ktor-client-cio:2.3.7")
 | 
				
			||||||
        classpath("io.ktor:ktor-client-auth:2.3.6")
 | 
					        classpath("io.ktor:ktor-client-auth:2.3.7")
 | 
				
			||||||
        classpath("io.ktor:ktor-client-content-negotiation:2.3.6")
 | 
					        classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
				
			||||||
        classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
 | 
					        classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // This comes from the changelog plugin
 | 
					        // This comes from the changelog plugin
 | 
				
			||||||
//        classpath("org.jetbrains:markdown:0.3.1")
 | 
					//        classpath("org.jetbrains:markdown:0.3.1")
 | 
				
			||||||
@@ -69,7 +69,7 @@ plugins {
 | 
				
			|||||||
    kotlin("jvm") version "1.8.21"
 | 
					    kotlin("jvm") version "1.8.21"
 | 
				
			||||||
    application
 | 
					    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"
 | 
					    id("org.jetbrains.changelog") version "2.2.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
 | 
					    // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
 | 
				
			||||||
@@ -126,11 +126,11 @@ dependencies {
 | 
				
			|||||||
    testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
 | 
					    testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
 | 
					    // 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-robot:$remoteRobotVersion")
 | 
				
			||||||
    testImplementation("com.intellij.remoterobot:remote-fixtures:$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")
 | 
					    runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
 | 
				
			||||||
    antlr("org.antlr:antlr4:$antlrVersion")
 | 
					    antlr("org.antlr:antlr4:$antlrVersion")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -184,6 +184,14 @@ tasks {
 | 
				
			|||||||
        include("**/*test.class")
 | 
					        include("**/*test.class")
 | 
				
			||||||
        include("**/*Tests.class")
 | 
					        include("**/*Tests.class")
 | 
				
			||||||
        exclude("**/ParserTest.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) {
 | 
					    val testWithNeovim by getting(Test::class) {
 | 
				
			||||||
@@ -294,6 +302,7 @@ tasks {
 | 
				
			|||||||
        systemProperty("ide.mac.message.dialogs.as.sheets", "false")
 | 
					        systemProperty("ide.mac.message.dialogs.as.sheets", "false")
 | 
				
			||||||
        systemProperty("jb.privacy.policy.text", "<!--999.999-->")
 | 
					        systemProperty("jb.privacy.policy.text", "<!--999.999-->")
 | 
				
			||||||
        systemProperty("jb.consents.confirmation.enabled", "false")
 | 
					        systemProperty("jb.consents.confirmation.enabled", "false")
 | 
				
			||||||
 | 
					        systemProperty("ide.show.tips.on.startup.default.value", "false")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    runPluginVerifier {
 | 
					    runPluginVerifier {
 | 
				
			||||||
@@ -344,8 +353,6 @@ tasks {
 | 
				
			|||||||
    val pluginVersion = version
 | 
					    val pluginVersion = version
 | 
				
			||||||
    // Don't forget to update plugin.xml
 | 
					    // Don't forget to update plugin.xml
 | 
				
			||||||
    patchPluginXml {
 | 
					    patchPluginXml {
 | 
				
			||||||
        sinceBuild.set("233.11799.30")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Get the latest available change notes from the changelog file
 | 
					        // Get the latest available change notes from the changelog file
 | 
				
			||||||
        changeNotes.set(
 | 
					        changeNotes.set(
 | 
				
			||||||
            provider {
 | 
					            provider {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -396,3 +396,19 @@ Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
 | 
				
			|||||||
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
 | 
					https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
 | 
				
			||||||
 | 
					
 | 
				
			||||||
</details>
 | 
					</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,14 +8,15 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# suppress inspection "UnusedProperty" for whole file
 | 
					# suppress inspection "UnusedProperty" for whole file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ideaVersion=LATEST-EAP-SNAPSHOT
 | 
					ideaVersion=2023.3.2
 | 
				
			||||||
downloadIdeaSources=true
 | 
					downloadIdeaSources=true
 | 
				
			||||||
instrumentPluginCode=true
 | 
					instrumentPluginCode=true
 | 
				
			||||||
version=SNAPSHOT
 | 
					version=chylex-23
 | 
				
			||||||
javaVersion=17
 | 
					javaVersion=17
 | 
				
			||||||
remoteRobotVersion=0.11.17
 | 
					remoteRobotVersion=0.11.21
 | 
				
			||||||
antlrVersion=4.10.1
 | 
					antlrVersion=4.10.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					kotlin.incremental.useClasspathSnapshot=false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Please don't forget to update kotlin version in buildscript section
 | 
					# Please don't forget to update kotlin version in buildscript section
 | 
				
			||||||
# Also update kotlinxSerializationVersion version
 | 
					# Also update kotlinxSerializationVersion version
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,17 +20,17 @@ repositories {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					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-core:2.3.7")
 | 
				
			||||||
  implementation("io.ktor:ktor-client-cio:2.3.6")
 | 
					  implementation("io.ktor:ktor-client-cio:2.3.7")
 | 
				
			||||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.6")
 | 
					  implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
				
			||||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.6")
 | 
					  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
				
			||||||
  implementation("io.ktor:ktor-client-auth:2.3.6")
 | 
					  implementation("io.ktor:ktor-client-auth:2.3.7")
 | 
				
			||||||
  implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
					  implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // This is needed for jgit to connect to ssh
 | 
					  // This is needed for jgit to connect to ssh
 | 
				
			||||||
  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")
 | 
					  implementation("com.vdurmont:semver4j:3.1.0")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -219,6 +219,10 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
				
			|||||||
    return getInstance().enabled;
 | 
					    return getInstance().enabled;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public static boolean isNotEnabled() {
 | 
				
			||||||
 | 
					    return !isEnabled();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public static void setEnabled(final boolean enabled) {
 | 
					  public static void setEnabled(final boolean enabled) {
 | 
				
			||||||
    if (isEnabled() == enabled) return;
 | 
					    if (isEnabled() == enabled) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -232,6 +236,12 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
				
			|||||||
      getInstance().turnOnPlugin();
 | 
					      getInstance().turnOnPlugin();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (enabled) {
 | 
				
			||||||
 | 
					      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    StatusBarIconFactory.Util.INSTANCE.updateIcon();
 | 
					    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.
 | 
					 * 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].
 | 
					 * 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 handler = KeyHandler.getInstance()
 | 
				
			||||||
  private val traceTime = injector.globalOptions().ideatracetime
 | 
					  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>()
 | 
					    private val LOG = logger<VimTypedActionHandler>()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,8 +54,11 @@ import javax.swing.KeyStroke
 | 
				
			|||||||
 *
 | 
					 *
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
 | 
					 * These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * 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
 | 
					  private val traceTime: Boolean
 | 
				
			||||||
    get() {
 | 
					    get() {
 | 
				
			||||||
      // Make sure the injector is initialized
 | 
					      // Make sure the injector is initialized
 | 
				
			||||||
@@ -95,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.
 | 
					  // 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.
 | 
					  // 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) {
 | 
					  override fun update(e: AnActionEvent) {
 | 
				
			||||||
    val start = if (traceTime) System.currentTimeMillis() else null
 | 
					    val start = if (traceTime) System.currentTimeMillis() else null
 | 
				
			||||||
@@ -110,7 +113,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
 | 
					  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)
 | 
					    val editor = getEditor(e)
 | 
				
			||||||
    if (editor != null && keyStroke != null) {
 | 
					    if (editor != null && keyStroke != null) {
 | 
				
			||||||
      if (isOctopusEnabled(keyStroke, editor)) {
 | 
					      if (isOctopusEnabled(keyStroke, editor)) {
 | 
				
			||||||
@@ -229,9 +232,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
				
			|||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
 | 
					   * getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
 | 
				
			||||||
   * but we should cache the value because on the second call (isEnabled -> actionPerformed)
 | 
					   * 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? {
 | 
					  private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
 | 
				
			||||||
    val inputEvent = e.inputEvent
 | 
					    val inputEvent = e.inputEvent
 | 
				
			||||||
@@ -239,9 +242,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
				
			|||||||
      val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
 | 
					      val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
 | 
				
			||||||
      val strokeCache = keyStrokeCache
 | 
					      val strokeCache = keyStrokeCache
 | 
				
			||||||
      if (defaultKeyStroke != null) {
 | 
					      if (defaultKeyStroke != null) {
 | 
				
			||||||
        keyStrokeCache = inputEvent to defaultKeyStroke
 | 
					        keyStrokeCache = inputEvent.`when` to defaultKeyStroke
 | 
				
			||||||
        return defaultKeyStroke
 | 
					        return defaultKeyStroke
 | 
				
			||||||
      } else if (strokeCache.first === inputEvent) {
 | 
					      } else if (strokeCache.first == inputEvent.`when`) {
 | 
				
			||||||
        keyStrokeCache = null to null
 | 
					        keyStrokeCache = null to null
 | 
				
			||||||
        return strokeCache.second
 | 
					        return strokeCache.second
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -274,7 +277,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
				
			|||||||
      .toSet()
 | 
					      .toSet()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  companion object {
 | 
					  internal companion object {
 | 
				
			||||||
    @JvmField
 | 
					    @JvmField
 | 
				
			||||||
    val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
 | 
					    val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
 | 
				
			||||||
      ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
 | 
					      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.api.setChangeMarks
 | 
				
			||||||
import com.maddyhome.idea.vim.command.Argument
 | 
					import com.maddyhome.idea.vim.command.Argument
 | 
				
			||||||
import com.maddyhome.idea.vim.command.Command
 | 
					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.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.common.TextRange
 | 
					import com.maddyhome.idea.vim.common.TextRange
 | 
				
			||||||
import com.maddyhome.idea.vim.common.argumentCaptured
 | 
					import com.maddyhome.idea.vim.common.argumentCaptured
 | 
				
			||||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
					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.VimActionHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
					import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
					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.helper.vimStateMachine
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.ij
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
import java.util.*
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// todo make it multicaret
 | 
					// todo make it multicaret
 | 
				
			||||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
 | 
					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() {
 | 
					internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
				
			||||||
  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
					  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun executeAction(
 | 
					  override fun executeAction(
 | 
				
			||||||
    editor: VimEditor,
 | 
					    editor: VimEditor,
 | 
				
			||||||
    caret: VimCaret,
 | 
					    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.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.command.Command
 | 
					import com.maddyhome.idea.vim.command.Command
 | 
				
			||||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
					import com.maddyhome.idea.vim.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
					import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
					import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
					import com.maddyhome.idea.vim.newapi.ijOptions
 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author vlan
 | 
					 * @author vlan
 | 
				
			||||||
@@ -29,8 +26,6 @@ import java.util.*
 | 
				
			|||||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
					public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
				
			||||||
  override val type: Command.Type = Command.Type.DELETE
 | 
					  override val type: Command.Type = Command.Type.DELETE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun executeForAllCarets(
 | 
					  override fun executeForAllCarets(
 | 
				
			||||||
    editor: VimEditor,
 | 
					    editor: VimEditor,
 | 
				
			||||||
    context: ExecutionContext,
 | 
					    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.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.command.Command
 | 
					import com.maddyhome.idea.vim.command.Command
 | 
				
			||||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
					import com.maddyhome.idea.vim.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
					import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
					import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
					import com.maddyhome.idea.vim.newapi.ijOptions
 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author vlan
 | 
					 * @author vlan
 | 
				
			||||||
@@ -29,8 +26,6 @@ import java.util.*
 | 
				
			|||||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
					public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
				
			||||||
  override val type: Command.Type = Command.Type.DELETE
 | 
					  override val type: Command.Type = Command.Type.DELETE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun executeForAllCarets(
 | 
					  override fun executeForAllCarets(
 | 
				
			||||||
    editor: VimEditor,
 | 
					    editor: VimEditor,
 | 
				
			||||||
    context: ExecutionContext,
 | 
					    context: ExecutionContext,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.extension
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.intellij.openapi.application.ApplicationManager
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
import com.intellij.openapi.components.service
 | 
					import com.intellij.openapi.components.service
 | 
				
			||||||
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
import com.intellij.openapi.editor.Editor
 | 
					import com.intellij.openapi.editor.Editor
 | 
				
			||||||
import com.maddyhome.idea.vim.KeyHandler
 | 
					import com.maddyhome.idea.vim.KeyHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					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.VimCaret
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.command.MappingMode
 | 
					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.CommandAlias
 | 
				
			||||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
 | 
					import com.maddyhome.idea.vim.common.CommandAliasHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
 | 
					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.MappingOwner
 | 
				
			||||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
					import com.maddyhome.idea.vim.key.OperatorFunction
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
import com.maddyhome.idea.vim.ui.ModalEntry
 | 
					import com.maddyhome.idea.vim.ui.ModalEntry
 | 
				
			||||||
import java.awt.event.KeyEvent
 | 
					import java.awt.event.KeyEvent
 | 
				
			||||||
import javax.swing.KeyStroke
 | 
					import javax.swing.KeyStroke
 | 
				
			||||||
@@ -38,6 +39,9 @@ import javax.swing.KeyStroke
 | 
				
			|||||||
 * @author vlan
 | 
					 * @author vlan
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public object VimExtensionFacade {
 | 
					public object VimExtensionFacade {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private val LOG = logger<VimExtensionFacade>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /** The 'map' command for mapping keys to handlers defined in extensions. */
 | 
					  /** The 'map' command for mapping keys to handlers defined in extensions. */
 | 
				
			||||||
  @JvmStatic
 | 
					  @JvmStatic
 | 
				
			||||||
  public fun putExtensionHandlerMapping(
 | 
					  public fun putExtensionHandlerMapping(
 | 
				
			||||||
@@ -140,10 +144,12 @@ public object VimExtensionFacade {
 | 
				
			|||||||
  public fun inputKeyStroke(editor: Editor): KeyStroke {
 | 
					  public fun inputKeyStroke(editor: Editor): KeyStroke {
 | 
				
			||||||
    if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
 | 
					    if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
 | 
				
			||||||
      val input = Extension.consumeKeystroke()
 | 
					      val input = Extension.consumeKeystroke()
 | 
				
			||||||
 | 
					      LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
 | 
				
			||||||
      return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
 | 
					      return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
 | 
					    val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
 | 
				
			||||||
 | 
					      LOG.trace("Unit test mode is active")
 | 
				
			||||||
      val mappingStack = KeyHandler.getInstance().keyStack
 | 
					      val mappingStack = KeyHandler.getInstance().keyStack
 | 
				
			||||||
      mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
 | 
					      mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
 | 
				
			||||||
        if (editor.vim.vimStateMachine.isRecording) {
 | 
					        if (editor.vim.vimStateMachine.isRecording) {
 | 
				
			||||||
@@ -151,11 +157,13 @@ public object VimExtensionFacade {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
 | 
					      LOG.trace("Getting char from the modal entry...")
 | 
				
			||||||
      var ref: KeyStroke? = null
 | 
					      var ref: KeyStroke? = null
 | 
				
			||||||
      ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
 | 
					      ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
 | 
				
			||||||
        ref = stroke
 | 
					        ref = stroke
 | 
				
			||||||
        false
 | 
					        false
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					      LOG.trace("Got char $ref")
 | 
				
			||||||
      ref
 | 
					      ref
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
 | 
					    val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -156,11 +156,6 @@ internal class CommentaryExtension : VimExtension {
 | 
				
			|||||||
  private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
 | 
					  private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
 | 
				
			||||||
    override val isRepeatable = true
 | 
					    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) {
 | 
					    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
				
			||||||
      setOperatorFunction(this)
 | 
					      setOperatorFunction(this)
 | 
				
			||||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
					      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -217,6 +217,8 @@ private object FileTypePatterns {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return if (fileTypeName in htmlLikeFileTypes) {
 | 
					    return if (fileTypeName in htmlLikeFileTypes) {
 | 
				
			||||||
      this.htmlPatterns
 | 
					      this.htmlPatterns
 | 
				
			||||||
 | 
					    } else if (fileTypeName == "JAVA" || fileExtension == "java") {
 | 
				
			||||||
 | 
					      this.javaPatterns
 | 
				
			||||||
    } else if (fileTypeName == "Ruby" || fileExtension == "rb") {
 | 
					    } else if (fileTypeName == "Ruby" || fileExtension == "rb") {
 | 
				
			||||||
      this.rubyPatterns
 | 
					      this.rubyPatterns
 | 
				
			||||||
    } else if (fileTypeName == "RHTML" || fileExtension == "erb") {
 | 
					    } else if (fileTypeName == "RHTML" || fileExtension == "erb") {
 | 
				
			||||||
@@ -231,7 +233,7 @@ private object FileTypePatterns {
 | 
				
			|||||||
    } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
 | 
					    } else if (fileTypeName == "CMakeLists.txt" || fileName == "CMakeLists") {
 | 
				
			||||||
      this.cMakePatterns
 | 
					      this.cMakePatterns
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return null
 | 
					      this.htmlPatterns
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -242,6 +244,7 @@ private object FileTypePatterns {
 | 
				
			|||||||
  )
 | 
					  )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private val htmlPatterns = createHtmlPatterns()
 | 
					  private val htmlPatterns = createHtmlPatterns()
 | 
				
			||||||
 | 
					  private val javaPatterns = createJavaPatterns()
 | 
				
			||||||
  private val rubyPatterns = createRubyPatterns()
 | 
					  private val rubyPatterns = createRubyPatterns()
 | 
				
			||||||
  private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
 | 
					  private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
 | 
				
			||||||
  private val phpPatterns = createPhpPatterns()
 | 
					  private val phpPatterns = createPhpPatterns()
 | 
				
			||||||
@@ -271,6 +274,14 @@ private object FileTypePatterns {
 | 
				
			|||||||
      )
 | 
					      )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  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 {
 | 
					  private fun createRubyPatterns(): LanguagePatterns {
 | 
				
			||||||
    // Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
 | 
					    // Original patterns: https://github.com/vim/vim/blob/master/runtime/ftplugin/ruby.vim
 | 
				
			||||||
    // We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.
 | 
					    // We use non-capturing groups (?:) since we don't need any back refs. The \\b marker takes care of word boundaries.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					package com.maddyhome.idea.vim.extension.surround
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.util.text.CharSequenceSubSequence
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal data class RepeatedCharSequence(val text: CharSequence, val count: Int) : CharSequence {
 | 
				
			||||||
 | 
					  override val length = text.length * count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun get(index: Int): Char {
 | 
				
			||||||
 | 
					    if (index < 0 || index >= length) throw IndexOutOfBoundsException()
 | 
				
			||||||
 | 
					    return text[index % text.length]
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun subSequence(startIndex: Int, endIndex: Int): CharSequence {
 | 
				
			||||||
 | 
					    return CharSequenceSubSequence(this, startIndex, endIndex)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun toString(): String {
 | 
				
			||||||
 | 
					    return text.repeat(count)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  
 | 
				
			||||||
 | 
					  companion object {
 | 
				
			||||||
 | 
					    fun of(text: CharSequence, count: Int): CharSequence {
 | 
				
			||||||
 | 
					      return when (count) {
 | 
				
			||||||
 | 
					        0 -> ""
 | 
				
			||||||
 | 
					        1 -> text
 | 
				
			||||||
 | 
					        else -> RepeatedCharSequence(text, count)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,20 +8,19 @@
 | 
				
			|||||||
package com.maddyhome.idea.vim.extension.surround
 | 
					package com.maddyhome.idea.vim.extension.surround
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.intellij.openapi.application.runWriteAction
 | 
					import com.intellij.openapi.application.runWriteAction
 | 
				
			||||||
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
import com.intellij.openapi.editor.Editor
 | 
					import com.intellij.openapi.editor.Editor
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
					import com.maddyhome.idea.vim.api.VimCaret
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.VimChangeGroup
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.endsWithNewLine
 | 
					import com.maddyhome.idea.vim.api.endsWithNewLine
 | 
				
			||||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
 | 
					import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
					import com.maddyhome.idea.vim.api.setChangeMarks
 | 
				
			||||||
import com.maddyhome.idea.vim.command.MappingMode
 | 
					import com.maddyhome.idea.vim.command.MappingMode
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
					import com.maddyhome.idea.vim.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.common.TextRange
 | 
					import com.maddyhome.idea.vim.common.TextRange
 | 
				
			||||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
					import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.extension.VimExtension
 | 
					import com.maddyhome.idea.vim.extension.VimExtension
 | 
				
			||||||
@@ -33,12 +32,18 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
 | 
				
			|||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
					import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
				
			||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
 | 
					import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
 | 
				
			||||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
 | 
					import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegisterForCaret
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
					import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
 | 
				
			||||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
					import com.maddyhome.idea.vim.key.OperatorFunction
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.ij
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
 | 
					import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
 | 
				
			||||||
import com.maddyhome.idea.vim.put.PutData
 | 
					import com.maddyhome.idea.vim.put.PutData
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.mode
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.selectionType
 | 
				
			||||||
import org.jetbrains.annotations.NonNls
 | 
					import org.jetbrains.annotations.NonNls
 | 
				
			||||||
import java.awt.event.KeyEvent
 | 
					import java.awt.event.KeyEvent
 | 
				
			||||||
import javax.swing.KeyStroke
 | 
					import javax.swing.KeyStroke
 | 
				
			||||||
@@ -79,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
    override val isRepeatable = true
 | 
					    override val isRepeatable = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
					    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
				
			||||||
      setOperatorFunction(Operator())
 | 
					      setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
 | 
				
			||||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
					      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -100,7 +105,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
        val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
 | 
					        val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
 | 
				
			||||||
        if (lastNonWhiteSpaceOffset != null) {
 | 
					        if (lastNonWhiteSpaceOffset != null) {
 | 
				
			||||||
          val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
 | 
					          val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
 | 
				
			||||||
          performSurround(pair, range, it)
 | 
					          performSurround(pair, range, it, count = operatorArguments.count1)
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
//        it.moveToOffset(lineStartOffset)
 | 
					//        it.moveToOffset(lineStartOffset)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -120,15 +125,13 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private class VSurroundHandler : ExtensionHandler {
 | 
					  private class VSurroundHandler : ExtensionHandler {
 | 
				
			||||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
					    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
				
			||||||
      val selectionStart = editor.ij.caretModel.primaryCaret.selectionStart
 | 
					 | 
				
			||||||
      // NB: Operator ignores SelectionType anyway
 | 
					      // NB: Operator ignores SelectionType anyway
 | 
				
			||||||
      if (!Operator().apply(editor, context, editor.mode.selectionType)) {
 | 
					      if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      runWriteAction {
 | 
					      runWriteAction {
 | 
				
			||||||
        // Leave visual mode
 | 
					        // Leave visual mode
 | 
				
			||||||
        executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
 | 
					        executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
 | 
				
			||||||
        editor.ij.caretModel.moveToOffset(selectionStart)
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -149,6 +152,10 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    companion object {
 | 
					    companion object {
 | 
				
			||||||
      fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
 | 
					      fun change(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
 | 
				
			||||||
 | 
					        editor.ij.runWithEveryCaretAndRestore { changeAtCaret(editor, context, charFrom, newSurround) }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      fun changeAtCaret(editor: VimEditor, context: ExecutionContext, charFrom: Char, newSurround: Pair<String, String>?) {
 | 
				
			||||||
        // Save old register values for carets
 | 
					        // Save old register values for carets
 | 
				
			||||||
        val surroundings = editor.sortedCarets()
 | 
					        val surroundings = editor.sortedCarets()
 | 
				
			||||||
          .map {
 | 
					          .map {
 | 
				
			||||||
@@ -249,27 +256,49 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
					    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
				
			||||||
      // Deleting surround is just changing the surrounding to "nothing"
 | 
					      // Deleting surround is just changing the surrounding to "nothing"
 | 
				
			||||||
      val charFrom = getChar(editor.ij)
 | 
					      val charFrom = getChar(editor.ij)
 | 
				
			||||||
 | 
					      LOG.debug("DSurroundHandler: charFrom = $charFrom")
 | 
				
			||||||
      if (charFrom.code == 0) return
 | 
					      if (charFrom.code == 0) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
 | 
					      runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private class Operator : OperatorFunction {
 | 
					  private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
 | 
				
			||||||
    override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
 | 
					    override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
 | 
				
			||||||
      val ijEditor = editor.ij
 | 
					      val editor = vimEditor.ij
 | 
				
			||||||
      val c = getChar(ijEditor)
 | 
					      val c = getChar(editor)
 | 
				
			||||||
      if (c.code == 0) return true
 | 
					      if (c.code == 0) return true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      val pair = getOrInputPair(c, ijEditor) ?: return false
 | 
					      val pair = getOrInputPair(c, editor) ?: return false
 | 
				
			||||||
      // XXX: Will it work with line-wise or block-wise selections?
 | 
					
 | 
				
			||||||
      val range = getSurroundRange(editor.currentCaret()) ?: return false
 | 
					      runWriteAction {
 | 
				
			||||||
      performSurround(pair, range, editor.currentCaret(), selectionType == SelectionType.LINE_WISE)
 | 
					        val change = VimPlugin.getChange()
 | 
				
			||||||
 | 
					        if (supportsMultipleCursors) {
 | 
				
			||||||
 | 
					          editor.runWithEveryCaretAndRestore {
 | 
				
			||||||
 | 
					            applyOnce(editor, change, pair, count)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        else {
 | 
				
			||||||
 | 
					          applyOnce(editor, change, pair, count)
 | 
				
			||||||
          // Jump back to start
 | 
					          // Jump back to start
 | 
				
			||||||
      executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
 | 
					          executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      return true
 | 
					      return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
 | 
					    private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>, count: Int) {
 | 
				
			||||||
 | 
					      // XXX: Will it work with line-wise or block-wise selections?
 | 
				
			||||||
 | 
					      val primaryCaret = editor.caretModel.primaryCaret
 | 
				
			||||||
 | 
					      val range = getSurroundRange(primaryCaret.vim)
 | 
				
			||||||
 | 
					      if (range != null) {
 | 
				
			||||||
 | 
					        val start = RepeatedCharSequence.of(pair.first, count)
 | 
				
			||||||
 | 
					        val end = RepeatedCharSequence.of(pair.second, count)
 | 
				
			||||||
 | 
					        change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, start)
 | 
				
			||||||
 | 
					        change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.endOffset + start.length, end)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getSurroundRange(caret: VimCaret): TextRange? {
 | 
					    private fun getSurroundRange(caret: VimCaret): TextRange? {
 | 
				
			||||||
      val editor = caret.editor
 | 
					      val editor = caret.editor
 | 
				
			||||||
      val ijEditor = editor.ij
 | 
					      val ijEditor = editor.ij
 | 
				
			||||||
@@ -280,13 +309,15 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  companion object {
 | 
					private val LOG = logger<VimSurroundExtension>()
 | 
				
			||||||
    private const val REGISTER = '"'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
					private const val REGISTER = '"'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private val SURROUND_PAIRS = mapOf(
 | 
					private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private val SURROUND_PAIRS = mapOf(
 | 
				
			||||||
  'b' to ("(" to ")"),
 | 
					  'b' to ("(" to ")"),
 | 
				
			||||||
  '(' to ("( " to " )"),
 | 
					  '(' to ("( " to " )"),
 | 
				
			||||||
  ')' to ("(" to ")"),
 | 
					  ')' to ("(" to ")"),
 | 
				
			||||||
@@ -299,18 +330,18 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
  'a' to ("<" to ">"),
 | 
					  'a' to ("<" to ">"),
 | 
				
			||||||
  '>' to ("<" to ">"),
 | 
					  '>' to ("<" to ">"),
 | 
				
			||||||
  's' to (" " to ""),
 | 
					  's' to (" " to ""),
 | 
				
			||||||
    )
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
 | 
					private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
 | 
				
			||||||
  SURROUND_PAIRS[c]
 | 
					  SURROUND_PAIRS[c]
 | 
				
			||||||
    } else if (!c.isLetter()) {
 | 
					} else if (!c.isLetter()) {
 | 
				
			||||||
  val s = c.toString()
 | 
					  val s = c.toString()
 | 
				
			||||||
  s to s
 | 
					  s to s
 | 
				
			||||||
    } else {
 | 
					} else {
 | 
				
			||||||
  null
 | 
					  null
 | 
				
			||||||
    }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
					private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
				
			||||||
  val tagInput = inputString(editor, "<", '>')
 | 
					  val tagInput = inputString(editor, "<", '>')
 | 
				
			||||||
  val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
 | 
					  val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
 | 
				
			||||||
  return if (matcher.find()) {
 | 
					  return if (matcher.find()) {
 | 
				
			||||||
@@ -320,43 +351,45 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    null
 | 
					    null
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
    }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun inputFunctionName(
 | 
					private fun inputFunctionName(
 | 
				
			||||||
  editor: Editor,
 | 
					  editor: Editor,
 | 
				
			||||||
  withInternalSpaces: Boolean,
 | 
					  withInternalSpaces: Boolean,
 | 
				
			||||||
    ): Pair<String, String>? {
 | 
					): Pair<String, String>? {
 | 
				
			||||||
  val functionNameInput = inputString(editor, "function: ", null)
 | 
					  val functionNameInput = inputString(editor, "function: ", null)
 | 
				
			||||||
  if (functionNameInput.isEmpty()) return null
 | 
					  if (functionNameInput.isEmpty()) return null
 | 
				
			||||||
  return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
 | 
					  return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
 | 
				
			||||||
    }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
 | 
					private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
 | 
				
			||||||
  '<', 't' -> inputTagPair(editor)
 | 
					  '<', 't' -> inputTagPair(editor)
 | 
				
			||||||
  'f' -> inputFunctionName(editor, false)
 | 
					  'f' -> inputFunctionName(editor, false)
 | 
				
			||||||
  'F' -> inputFunctionName(editor, true)
 | 
					  'F' -> inputFunctionName(editor, true)
 | 
				
			||||||
  else -> getSurroundPair(c)
 | 
					  else -> getSurroundPair(c)
 | 
				
			||||||
    }
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun getChar(editor: Editor): Char {
 | 
					private fun getChar(editor: Editor): Char {
 | 
				
			||||||
  val key = inputKeyStroke(editor)
 | 
					  val key = inputKeyStroke(editor)
 | 
				
			||||||
  val keyChar = key.keyChar
 | 
					  val keyChar = key.keyChar
 | 
				
			||||||
      return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
 | 
					  val res = if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
 | 
				
			||||||
    0.toChar()
 | 
					    0.toChar()
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    keyChar
 | 
					    keyChar
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
    }
 | 
					  LOG.trace("getChar: $res")
 | 
				
			||||||
 | 
					  return res
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
 | 
					private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
 | 
				
			||||||
  runWriteAction {
 | 
					  runWriteAction {
 | 
				
			||||||
    val editor = caret.editor
 | 
					    val editor = caret.editor
 | 
				
			||||||
    val change = VimPlugin.getChange()
 | 
					    val change = VimPlugin.getChange()
 | 
				
			||||||
        val leftSurround = pair.first + if (tagsOnNewLines) "\n" else ""
 | 
					    val leftSurround = RepeatedCharSequence.of(pair.first + if (tagsOnNewLines) "\n" else "", count)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val isEOF = range.endOffset == editor.text().length
 | 
					    val isEOF = range.endOffset == editor.text().length
 | 
				
			||||||
    val hasNewLine = editor.endsWithNewLine()
 | 
					    val hasNewLine = editor.endsWithNewLine()
 | 
				
			||||||
        val rightSurround = if (tagsOnNewLines) {
 | 
					    val rightSurround = (if (tagsOnNewLines) {
 | 
				
			||||||
      if (isEOF && !hasNewLine) {
 | 
					      if (isEOF && !hasNewLine) {
 | 
				
			||||||
        "\n" + pair.second
 | 
					        "\n" + pair.second
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@@ -364,12 +397,13 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      pair.second
 | 
					      pair.second
 | 
				
			||||||
        }
 | 
					    }).let { RepeatedCharSequence.of(it, count) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    change.insertText(editor, caret, range.startOffset, leftSurround)
 | 
					    change.insertText(editor, caret, range.startOffset, leftSurround)
 | 
				
			||||||
    change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
 | 
					    change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
 | 
				
			||||||
        injector.markService.setChangeMarks(caret, TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length))
 | 
					    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.EditorMouseEvent
 | 
				
			||||||
import com.intellij.openapi.editor.event.EditorMouseListener
 | 
					import com.intellij.openapi.editor.event.EditorMouseListener
 | 
				
			||||||
import com.intellij.openapi.editor.impl.TextRangeInterval
 | 
					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.UserDataHolder
 | 
				
			||||||
import com.intellij.openapi.util.text.StringUtil
 | 
					import com.intellij.openapi.util.text.StringUtil
 | 
				
			||||||
import com.intellij.psi.codeStyle.CodeStyleManager
 | 
					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.inInsertMode
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
 | 
					import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
					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.key.KeyHandlerKeeper.Companion.getInstance
 | 
				
			||||||
import com.maddyhome.idea.vim.listener.VimInsertListener
 | 
					import com.maddyhome.idea.vim.listener.VimInsertListener
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
 | 
					import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
 | 
				
			||||||
@@ -89,7 +85,6 @@ import kotlin.math.min
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
public class ChangeGroup : VimChangeGroupBase() {
 | 
					public class ChangeGroup : VimChangeGroupBase() {
 | 
				
			||||||
  private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
 | 
					  private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
 | 
				
			||||||
  private var lastShownTime = 0L
 | 
					 | 
				
			||||||
  private val listener: EditorMouseListener = object : EditorMouseListener {
 | 
					  private val listener: EditorMouseListener = object : EditorMouseListener {
 | 
				
			||||||
    override fun mouseClicked(event: EditorMouseEvent) {
 | 
					    override fun mouseClicked(event: EditorMouseEvent) {
 | 
				
			||||||
      val editor = event.editor
 | 
					      val editor = event.editor
 | 
				
			||||||
@@ -103,10 +98,6 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
				
			|||||||
    EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
 | 
					    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) {
 | 
					  override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
 | 
				
			||||||
    val editor = (vimEditor as IjVimEditor).editor
 | 
					    val editor = (vimEditor as IjVimEditor).editor
 | 
				
			||||||
    val ijContext = context.ij
 | 
					    val ijContext = context.ij
 | 
				
			||||||
@@ -645,25 +636,6 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
				
			|||||||
    avalanche: Boolean,
 | 
					    avalanche: Boolean,
 | 
				
			||||||
  ): 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 nf: List<String> = injector.options(editor).nrformats
 | 
				
			||||||
    val alpha = nf.contains("alpha")
 | 
					    val alpha = nf.contains("alpha")
 | 
				
			||||||
    val hex = nf.contains("hex")
 | 
					    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 val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
 | 
				
			||||||
  public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
 | 
					  public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
 | 
				
			||||||
  public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
 | 
					  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
 | 
					  // Temporary options to control work-in-progress behaviour
 | 
				
			||||||
  public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
 | 
					  public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -83,9 +83,11 @@ public object IjOptions {
 | 
				
			|||||||
  public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
 | 
					  public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
 | 
				
			||||||
  public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
 | 
					  public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
 | 
				
			||||||
  public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
 | 
					  public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
 | 
				
			||||||
  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", false, isTemporary = true))
 | 
					  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
 | 
				
			||||||
  public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
 | 
					  public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
 | 
				
			||||||
  public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
 | 
					  public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
 | 
				
			||||||
 | 
					  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
 | 
					  // This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
 | 
				
			||||||
  // derives from Option<VimInt>
 | 
					  // derives from Option<VimInt>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					package com.maddyhome.idea.vim.group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.codeInsight.daemon.ReferenceImporter
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.DataContext
 | 
				
			||||||
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
 | 
					import com.intellij.openapi.application.ReadAction
 | 
				
			||||||
 | 
					import com.intellij.openapi.command.WriteCommandAction
 | 
				
			||||||
 | 
					import com.intellij.openapi.editor.Editor
 | 
				
			||||||
 | 
					import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.ProgressIndicator
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.ProgressManager
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.Task
 | 
				
			||||||
 | 
					import com.intellij.psi.PsiDocumentManager
 | 
				
			||||||
 | 
					import com.intellij.psi.PsiElement
 | 
				
			||||||
 | 
					import com.intellij.psi.PsiRecursiveElementWalkingVisitor
 | 
				
			||||||
 | 
					import java.util.function.BooleanSupplier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal object MacroAutoImport {
 | 
				
			||||||
 | 
					  fun run(editor: Editor, dataContext: DataContext) {
 | 
				
			||||||
 | 
					    val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
 | 
				
			||||||
 | 
					    val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val importers = ReferenceImporter.EP_NAME.extensionList
 | 
				
			||||||
 | 
					    if (importers.isEmpty()) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
 | 
				
			||||||
 | 
					      override fun run(indicator: ProgressIndicator) {
 | 
				
			||||||
 | 
					        val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
 | 
				
			||||||
 | 
					          val fixes = mutableListOf<BooleanSupplier>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          file.accept(object : PsiRecursiveElementWalkingVisitor() {
 | 
				
			||||||
 | 
					            override fun visitElement(element: PsiElement) {
 | 
				
			||||||
 | 
					              for (reference in element.references) {
 | 
				
			||||||
 | 
					                if (reference.resolve() != null) {
 | 
				
			||||||
 | 
					                  continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                for (importer in importers) {
 | 
				
			||||||
 | 
					                  importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
 | 
				
			||||||
 | 
					                    ?.let(fixes::add)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              super.visitElement(element)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return@nonBlocking fixes
 | 
				
			||||||
 | 
					        }.executeSynchronously()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ApplicationManager.getApplication().invokeAndWait {
 | 
				
			||||||
 | 
					          WriteCommandAction.writeCommandAction(project)
 | 
				
			||||||
 | 
					            .withName("Auto Import")
 | 
				
			||||||
 | 
					            .withGroupId("IdeaVimAutoImportAfterMacro")
 | 
				
			||||||
 | 
					            .shouldRecordActionForActiveDocument(true)
 | 
				
			||||||
 | 
					            .run<RuntimeException> {
 | 
				
			||||||
 | 
					              fixes.forEach { it.asBoolean }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,6 +7,8 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package com.maddyhome.idea.vim.group
 | 
					package com.maddyhome.idea.vim.group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.codeInsight.completion.CompletionPhase
 | 
				
			||||||
 | 
					import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
 | 
				
			||||||
import com.intellij.openapi.application.ApplicationManager
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
import com.intellij.openapi.diagnostic.logger
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
import com.intellij.openapi.progress.ProcessCanceledException
 | 
					import com.intellij.openapi.progress.ProcessCanceledException
 | 
				
			||||||
@@ -19,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
				
			|||||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
					import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
				
			||||||
import com.maddyhome.idea.vim.macro.VimMacroBase
 | 
					import com.maddyhome.idea.vim.macro.VimMacroBase
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
					import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Used to handle playback of macros
 | 
					 * Used to handle playback of macros
 | 
				
			||||||
@@ -61,8 +64,10 @@ internal class MacroGroup : VimMacroBase() {
 | 
				
			|||||||
    try {
 | 
					    try {
 | 
				
			||||||
      myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
 | 
					      myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
 | 
				
			||||||
      val runnable = runnable@{
 | 
					      val runnable = runnable@{
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
          // Handle one keystroke then queue up the next key
 | 
					          // Handle one keystroke then queue up the next key
 | 
				
			||||||
          for (i in 0 until total) {
 | 
					          for (i in 0 until total) {
 | 
				
			||||||
 | 
					            try {
 | 
				
			||||||
              myPotemkinProgress.fraction = (i + 1).toDouble() / total
 | 
					              myPotemkinProgress.fraction = (i + 1).toDouble() / total
 | 
				
			||||||
              while (keyStack.hasStroke()) {
 | 
					              while (keyStack.hasStroke()) {
 | 
				
			||||||
                val key = keyStack.feedStroke()
 | 
					                val key = keyStack.feedStroke()
 | 
				
			||||||
@@ -71,13 +76,25 @@ internal class MacroGroup : VimMacroBase() {
 | 
				
			|||||||
                } catch (e: ProcessCanceledException) {
 | 
					                } catch (e: ProcessCanceledException) {
 | 
				
			||||||
                  return@runnable
 | 
					                  return@runnable
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
            ProgressManager.getInstance().executeNonCancelableSection { getInstance().handleKey(editor, key, context) }
 | 
					                ProgressManager.getInstance().executeNonCancelableSection {
 | 
				
			||||||
 | 
					                  // 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
 | 
					                if (injector.messages.isError()) return@runnable
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
 | 
					            } finally {
 | 
				
			||||||
              keyStack.resetFirst()
 | 
					              keyStack.resetFirst()
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        } finally {
 | 
				
			||||||
          keyStack.removeFirst()
 | 
					          keyStack.removeFirst()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        if (!isInternalMacro) {
 | 
				
			||||||
 | 
					          MacroAutoImport.run(editor.ij, context.ij)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (isInternalMacro) {
 | 
					      if (isInternalMacro) {
 | 
				
			||||||
        runnable()
 | 
					        runnable()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,9 +48,7 @@ import com.maddyhome.idea.vim.api.options
 | 
				
			|||||||
import com.maddyhome.idea.vim.api.visualLineToBufferLine
 | 
					import com.maddyhome.idea.vim.api.visualLineToBufferLine
 | 
				
			||||||
import com.maddyhome.idea.vim.command.Argument
 | 
					import com.maddyhome.idea.vim.command.Argument
 | 
				
			||||||
import com.maddyhome.idea.vim.command.MotionType
 | 
					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.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.common.TextRange
 | 
					import com.maddyhome.idea.vim.common.TextRange
 | 
				
			||||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
					import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.Motion
 | 
					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.IjVimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.ij
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.VimStateMachine
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
					import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
				
			||||||
import org.jetbrains.annotations.Range
 | 
					import org.jetbrains.annotations.Range
 | 
				
			||||||
import java.io.File
 | 
					import java.io.File
 | 
				
			||||||
@@ -461,6 +461,7 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
				
			|||||||
      val fileEditor = event.oldEditor
 | 
					      val fileEditor = event.oldEditor
 | 
				
			||||||
      if (fileEditor is TextEditor) {
 | 
					      if (fileEditor is TextEditor) {
 | 
				
			||||||
        val editor = fileEditor.editor
 | 
					        val editor = fileEditor.editor
 | 
				
			||||||
 | 
					        if (!editor.isDisposed) {
 | 
				
			||||||
          ExOutputModel.getInstance(editor).clear()
 | 
					          ExOutputModel.getInstance(editor).clear()
 | 
				
			||||||
          editor.vim.let { vimEditor ->
 | 
					          editor.vim.let { vimEditor ->
 | 
				
			||||||
            if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
 | 
					            if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
 | 
				
			||||||
@@ -471,4 +472,5 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,10 +40,6 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
				
			|||||||
  override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
 | 
					  override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
 | 
				
			||||||
  override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
 | 
					  override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private fun updateFallbackWindow(fallbackWindow: VimEditor, targetEditor: VimEditor) {
 | 
					 | 
				
			||||||
    copyPerWindowGlobalValues(fallbackWindow, targetEditor)
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  companion object {
 | 
					  companion object {
 | 
				
			||||||
    fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
 | 
					    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
 | 
					      // 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
 | 
					      // 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
 | 
					      // 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
 | 
					      // 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) {
 | 
					      if (event.newEditor == null) {
 | 
				
			||||||
        (event.oldEditor as? TextEditor)?.editor?.let {
 | 
					        (event.oldEditor as? TextEditor)?.editor?.let {
 | 
				
			||||||
          (VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
 | 
					          (VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
 | 
				
			||||||
@@ -68,7 +66,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
internal class IjOptionConstants {
 | 
					internal class IjOptionConstants {
 | 
				
			||||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
 | 
					  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
 | 
				
			||||||
  companion object {
 | 
					  companion object {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const val idearefactormode_keep = "keep"
 | 
					    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 patternOffset   The pattern offset, e.g. `/{pattern}/{offset}`
 | 
				
			||||||
   * @param direction       The direction to search
 | 
					   * @param direction       The direction to search
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  @TestOnly
 | 
					  @Override
 | 
				
			||||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
 | 
					  public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
 | 
				
			||||||
                                 @NotNull String patternOffset, Direction direction) {
 | 
					                                 @NotNull String patternOffset, Direction direction) {
 | 
				
			||||||
    setLastUsedPattern(pattern, RE_SEARCH, true);
 | 
					    setLastUsedPattern(pattern, RE_SEARCH, true);
 | 
				
			||||||
    lastIgnoreSmartCase = false;
 | 
					    lastIgnoreSmartCase = false;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -205,7 +205,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
				
			|||||||
     * @param event The change event
 | 
					     * @param event The change event
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    override fun beforeDocumentChange(event: DocumentEvent) {
 | 
					    override fun beforeDocumentChange(event: DocumentEvent) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
      if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
 | 
					      if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
 | 
				
			||||||
      if (event.oldLength == 0) return
 | 
					      if (event.oldLength == 0) return
 | 
				
			||||||
      val doc = event.document
 | 
					      val doc = event.document
 | 
				
			||||||
@@ -221,7 +221,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
				
			|||||||
     * @param event The change event
 | 
					     * @param event The change event
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    override fun documentChanged(event: DocumentEvent) {
 | 
					    override fun documentChanged(event: DocumentEvent) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
      if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
 | 
					      if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
 | 
				
			||||||
      if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
 | 
					      if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
 | 
				
			||||||
      val doc = event.document
 | 
					      val doc = event.document
 | 
				
			||||||
@@ -242,7 +242,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
 | 
					  class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
 | 
				
			||||||
    override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
 | 
					    override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
					      if (!injector.globalIjOptions().ideamarks) {
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
 | 
					    override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
					      if (!injector.globalIjOptions().ideamarks) {
 | 
				
			||||||
        return
 | 
					        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.globalOptions
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
					import com.maddyhome.idea.vim.api.setChangeMarks
 | 
				
			||||||
import com.maddyhome.idea.vim.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.common.TextRange
 | 
				
			||||||
import com.maddyhome.idea.vim.diagnostic.debug
 | 
					import com.maddyhome.idea.vim.diagnostic.debug
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.RWLockLabel
 | 
					import com.maddyhome.idea.vim.helper.RWLockLabel
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
					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.mark.VimMarkConstants.MARK_CHANGE_POS
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
					import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
					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.VimPasteProvider
 | 
				
			||||||
import com.maddyhome.idea.vim.put.VimPutBase
 | 
					import com.maddyhome.idea.vim.put.VimPutBase
 | 
				
			||||||
import com.maddyhome.idea.vim.register.RegisterConstants
 | 
					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
 | 
					import java.awt.datatransfer.DataFlavor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
internal class PutGroup : VimPutBase() {
 | 
					internal class PutGroup : VimPutBase() {
 | 
				
			||||||
@@ -189,7 +190,7 @@ internal class PutGroup : VimPutBase() {
 | 
				
			|||||||
    endOffset: Int,
 | 
					    endOffset: Int,
 | 
				
			||||||
  ): Int {
 | 
					  ): Int {
 | 
				
			||||||
    // Temp fix for VIM-2808. Should be removed after rider will fix it's issues
 | 
					    // 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 startLine = editor.offsetToBufferPosition(startOffset).line
 | 
				
			||||||
    val endLine = editor.offsetToBufferPosition(endOffset - 1).line
 | 
					    val endLine = editor.offsetToBufferPosition(endOffset - 1).line
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -40,9 +40,15 @@ internal object IdeaSelectionControl {
 | 
				
			|||||||
   * This method should be in sync with [predictMode]
 | 
					   * 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,
 | 
					   * 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.
 | 
					   * 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(
 | 
					  fun controlNonVimSelectionChange(
 | 
				
			||||||
    editor: Editor,
 | 
					    editor: Editor,
 | 
				
			||||||
@@ -50,6 +56,7 @@ internal object IdeaSelectionControl {
 | 
				
			|||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
 | 
					    VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (VimPlugin.isNotEnabled()) return@singleTask
 | 
				
			||||||
      if (editor.isIdeaVimDisabledHere) return@singleTask
 | 
					      if (editor.isIdeaVimDisabledHere) return@singleTask
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
 | 
					      logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
 | 
				
			||||||
@@ -121,8 +128,9 @@ internal object IdeaSelectionControl {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private fun dontChangeMode(editor: Editor): Boolean =
 | 
					  private fun dontChangeMode(editor: Editor): Boolean {
 | 
				
			||||||
    editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
 | 
					    return editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private fun chooseNonSelectionMode(editor: Editor): Mode {
 | 
					  private fun chooseNonSelectionMode(editor: Editor): Mode {
 | 
				
			||||||
    val templateActive = editor.isTemplateActive()
 | 
					    val templateActive = editor.isTemplateActive()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,10 +9,10 @@
 | 
				
			|||||||
package com.maddyhome.idea.vim.group.visual
 | 
					package com.maddyhome.idea.vim.group.visual
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
 | 
					import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
 | 
					import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
					import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import java.awt.event.ActionEvent
 | 
					import java.awt.event.ActionEvent
 | 
				
			||||||
import javax.swing.Timer
 | 
					import javax.swing.Timer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -79,6 +79,11 @@ internal object VimVisualTimer {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun drop() {
 | 
				
			||||||
 | 
					    swingTimer?.stop()
 | 
				
			||||||
 | 
					    swingTimer = null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
 | 
					  inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
 | 
				
			||||||
    task(mode)
 | 
					    task(mode)
 | 
				
			||||||
    swingTimer = null
 | 
					    swingTimer = null
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,41 +11,68 @@ package com.maddyhome.idea.vim.handler
 | 
				
			|||||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
					import com.intellij.openapi.actionSystem.IdeActions
 | 
				
			||||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
					import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
				
			||||||
import com.intellij.openapi.actionSystem.Shortcut
 | 
					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.Keymap
 | 
				
			||||||
import com.intellij.openapi.keymap.KeymapManagerListener
 | 
					import com.intellij.openapi.keymap.KeymapManagerListener
 | 
				
			||||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
					import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
				
			||||||
import com.intellij.openapi.project.Project
 | 
					import com.intellij.openapi.project.Project
 | 
				
			||||||
import com.intellij.openapi.startup.StartupActivity
 | 
					import com.intellij.openapi.startup.ProjectActivity
 | 
				
			||||||
import com.intellij.util.SingleAlarm
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.api.key
 | 
					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
 | 
					import javax.swing.KeyStroke
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// We use alarm with delay to avoid many notifications in case many events are fired at the same time
 | 
					// 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 keyCheckRequests = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
 | 
				
			||||||
internal val keymapCheckRequester = SingleAlarm({ verifyKeymap() }, 5_000)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
 | 
					 * This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
internal class KeymapChecker : StartupActivity {
 | 
					internal class KeymapChecker : ProjectActivity {
 | 
				
			||||||
  override fun runActivity(project: Project) {
 | 
					  override suspend fun execute(project: Project) {
 | 
				
			||||||
    keymapCheckRequester.request()
 | 
					    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 {
 | 
					internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
 | 
				
			||||||
  override fun activeKeymapChanged(keymap: Keymap?) {
 | 
					  override fun activeKeymapChanged(keymap: Keymap?) {
 | 
				
			||||||
    keymapCheckRequester.request()
 | 
					    check(keyCheckRequests.tryEmit(Unit))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun shortcutChanged(keymap: Keymap, actionId: String) {
 | 
					  override fun shortcutChanged(keymap: Keymap, actionId: String) {
 | 
				
			||||||
    keymapCheckRequester.request()
 | 
					    check(keyCheckRequests.tryEmit(Unit))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
 | 
					  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 {
 | 
					  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 (!isHandlerEnabled(editor, dataContext)) return false
 | 
				
			||||||
    if (isNotActualKeyPress(dataContext)) return false
 | 
					    if (isNotActualKeyPress(dataContext)) return false
 | 
				
			||||||
    return true
 | 
					    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.
 | 
					 *   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.
 | 
					 *   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
 | 
					 * 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 isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private fun Editor.updatePrimaryCaretVisualAttributes() {
 | 
					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)
 | 
					  caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
 | 
					  // 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() {
 | 
					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
 | 
					  // 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)
 | 
					  val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
 | 
				
			||||||
  this.caretModel.allCarets.forEach {
 | 
					  this.caretModel.allCarets.forEach {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -335,7 +335,7 @@ public class EditorHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
 | 
					    final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
 | 
				
			||||||
    @NotNull final VimEditor editor1 = new IjVimEditor(editor);
 | 
					    @NotNull final VimEditor editor1 = new IjVimEditor(editor);
 | 
				
			||||||
    final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
 | 
					    final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
 | 
				
			||||||
    final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
 | 
					    final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
 | 
					    // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.intellij.codeWithMe.ClientId
 | 
					import com.intellij.codeWithMe.ClientId
 | 
				
			||||||
import com.intellij.openapi.editor.Caret
 | 
					import com.intellij.openapi.editor.Caret
 | 
				
			||||||
 | 
					import com.intellij.openapi.editor.CaretState
 | 
				
			||||||
import com.intellij.openapi.editor.Editor
 | 
					import com.intellij.openapi.editor.Editor
 | 
				
			||||||
import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
					import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
				
			||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
					import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
				
			||||||
@@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
 | 
				
			|||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
					import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
					import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.inBlockSelection
 | 
				
			||||||
import java.awt.Component
 | 
					import java.awt.Component
 | 
				
			||||||
import javax.swing.JComponent
 | 
					import javax.swing.JComponent
 | 
				
			||||||
import javax.swing.JTable
 | 
					import javax.swing.JTable
 | 
				
			||||||
@@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
internal val Editor.vimLine: Int
 | 
					internal val Editor.vimLine: Int
 | 
				
			||||||
  get() = this.caretModel.currentCaret.vimLine
 | 
					  get() = this.caretModel.currentCaret.vimLine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
 | 
				
			||||||
 | 
					  val caretModel = this.caretModel
 | 
				
			||||||
 | 
					  val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
 | 
				
			||||||
 | 
					  if (carets == null || carets.size == 1) {
 | 
				
			||||||
 | 
					    action()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  else {
 | 
				
			||||||
 | 
					    var initialDocumentSize = this.document.textLength
 | 
				
			||||||
 | 
					    var documentSizeDifference = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
 | 
				
			||||||
 | 
					    val restoredCarets = mutableListOf<CaretState>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    caretModel.removeSecondaryCarets()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for ((selectionStart, selectionEnd) in caretOffsets) {
 | 
				
			||||||
 | 
					      if (selectionStart == selectionEnd) {
 | 
				
			||||||
 | 
					        caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      else {
 | 
				
			||||||
 | 
					        caretModel.primaryCaret.setSelection(
 | 
				
			||||||
 | 
					          selectionStart + documentSizeDifference,
 | 
				
			||||||
 | 
					          selectionEnd + documentSizeDifference
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      action()
 | 
				
			||||||
 | 
					      restoredCarets.add(caretModel.caretsAndSelections.single())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      val documentLength = this.document.textLength
 | 
				
			||||||
 | 
					      documentSizeDifference += documentLength - initialDocumentSize
 | 
				
			||||||
 | 
					      initialDocumentSize = documentLength
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    caretModel.caretsAndSelections = restoredCarets
 | 
				
			||||||
 | 
					  } 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private fun vimEnabled(editor: Editor?): Boolean {
 | 
					private fun vimEnabled(editor: Editor?): Boolean {
 | 
				
			||||||
  if (!VimPlugin.isEnabled()) return false
 | 
					  if (VimPlugin.isNotEnabled()) return false
 | 
				
			||||||
  if (editor != null && editor.isIdeaVimDisabledHere) return false
 | 
					  if (editor != null && editor.isIdeaVimDisabledHere) return false
 | 
				
			||||||
  return true
 | 
					  return true
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
				
			|||||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
 | 
					import com.maddyhome.idea.vim.api.normalizeVisualColumn
 | 
				
			||||||
import com.maddyhome.idea.vim.api.options
 | 
					import com.maddyhome.idea.vim.api.options
 | 
				
			||||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
					import com.maddyhome.idea.vim.command.CommandFlags
 | 
				
			||||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
 | 
				
			||||||
@@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
 | 
				
			|||||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.VimStateMachine
 | 
				
			||||||
import kotlin.math.max
 | 
					import kotlin.math.max
 | 
				
			||||||
import kotlin.math.min
 | 
					import kotlin.math.min
 | 
				
			||||||
import kotlin.math.roundToInt
 | 
					import kotlin.math.roundToInt
 | 
				
			||||||
@@ -56,7 +56,7 @@ internal object ScrollViewHelper {
 | 
				
			|||||||
    // that this needs to be replaced as a more or less dumb line for line rewrite.
 | 
					    // that this needs to be replaced as a more or less dumb line for line rewrite.
 | 
				
			||||||
    val topLine = getVisualLineAtTopOfScreen(editor)
 | 
					    val topLine = getVisualLineAtTopOfScreen(editor)
 | 
				
			||||||
    val bottomLine = getVisualLineAtBottomOfScreen(editor)
 | 
					    val bottomLine = getVisualLineAtBottomOfScreen(editor)
 | 
				
			||||||
    val lastLine = vimEditor.getVisualLineCount() - 1
 | 
					    val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
 | 
					    // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
 | 
				
			||||||
    val scrollOffset = injector.options(vimEditor).scrolloff
 | 
					    val scrollOffset = injector.options(vimEditor).scrolloff
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import com.intellij.openapi.command.CommandProcessor
 | 
				
			|||||||
import com.intellij.openapi.command.undo.UndoManager
 | 
					import com.intellij.openapi.command.undo.UndoManager
 | 
				
			||||||
import com.intellij.openapi.components.Service
 | 
					import com.intellij.openapi.components.Service
 | 
				
			||||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
 | 
					import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
@@ -21,6 +22,8 @@ import com.maddyhome.idea.vim.common.ChangesListener
 | 
				
			|||||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
 | 
					import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
					import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.ij
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.inVisualMode
 | 
				
			||||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
 | 
					import com.maddyhome.idea.vim.undo.UndoRedoBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -39,6 +42,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      if (injector.globalIjOptions().oldundo) {
 | 
					      if (injector.globalIjOptions().oldundo) {
 | 
				
			||||||
        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
					        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
				
			||||||
 | 
					        restoreVisualMode(editor)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
					        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
				
			||||||
        editor.runWithChangeTracking {
 | 
					        editor.runWithChangeTracking {
 | 
				
			||||||
@@ -74,6 +78,7 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
				
			|||||||
    if (undoManager.isRedoAvailable(fileEditor)) {
 | 
					    if (undoManager.isRedoAvailable(fileEditor)) {
 | 
				
			||||||
      if (injector.globalIjOptions().oldundo) {
 | 
					      if (injector.globalIjOptions().oldundo) {
 | 
				
			||||||
        SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
					        SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
				
			||||||
 | 
					        restoreVisualMode(editor)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        undoManager.redo(fileEditor)
 | 
					        undoManager.redo(fileEditor)
 | 
				
			||||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
					        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
				
			||||||
@@ -131,4 +136,21 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
				
			|||||||
    val hasChanges: Boolean
 | 
					    val hasChanges: Boolean
 | 
				
			||||||
      get() = changeListener.hasChanged || initialPath != editor.getPath()
 | 
					      get() = changeListener.hasChanged || initialPath != editor.getPath()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun restoreVisualMode(editor: VimEditor) {
 | 
				
			||||||
 | 
					    if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
 | 
				
			||||||
 | 
					      val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      // Visual block selection is restored into multiple carets, so multi-carets that form a block are always
 | 
				
			||||||
 | 
					      // identified as visual block mode, leading to false positives.
 | 
				
			||||||
 | 
					      // Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
 | 
				
			||||||
 | 
					      // visual block mode.
 | 
				
			||||||
 | 
					      val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
 | 
				
			||||||
 | 
					        SelectionType.CHARACTER_WISE
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        detectedMode
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -124,10 +124,6 @@ internal var Editor.vimMorePanel: ExOutputPanel? by userData()
 | 
				
			|||||||
internal var Editor.vimExOutput: ExOutputModel? by userData()
 | 
					internal var Editor.vimExOutput: ExOutputModel? by userData()
 | 
				
			||||||
internal var Editor.vimTestInputModel: TestInputModel? 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()
 | 
					internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,32 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Use of this source code is governed by an MIT-style
 | 
					 | 
				
			||||||
 * license that can be found in the LICENSE.txt file or at
 | 
					 | 
				
			||||||
 * https://opensource.org/licenses/MIT.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package com.maddyhome.idea.vim.helper
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.intellij.ide.plugins.StandalonePluginUpdateChecker
 | 
					 | 
				
			||||||
import com.intellij.openapi.components.Service
 | 
					 | 
				
			||||||
import com.intellij.openapi.components.service
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.group.NotificationService
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.icons.VimIcons
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Service(Service.Level.APP)
 | 
					 | 
				
			||||||
internal class VimStandalonePluginUpdateChecker : StandalonePluginUpdateChecker(
 | 
					 | 
				
			||||||
  VimPlugin.getPluginId(),
 | 
					 | 
				
			||||||
  updateTimestampProperty = PROPERTY_NAME,
 | 
					 | 
				
			||||||
  NotificationService.IDEAVIM_STICKY_GROUP,
 | 
					 | 
				
			||||||
  VimIcons.IDEAVIM,
 | 
					 | 
				
			||||||
) {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun skipUpdateCheck(): Boolean = !VimPlugin.isEnabled() || "dev" in VimPlugin.getVersion()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  companion object {
 | 
					 | 
				
			||||||
    private const val PROPERTY_NAME = "ideavim.statistics.timestamp"
 | 
					 | 
				
			||||||
    val instance: VimStandalonePluginUpdateChecker = service()
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -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
 | 
					    private var editor: Editor? = null
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
					    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
					      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
				
			||||||
      if (hostEditor != null) {
 | 
					      if (hostEditor != null) {
 | 
				
			||||||
@@ -49,7 +49,7 @@ internal object AppCodeTemplates {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
					    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) {
 | 
					      if (ActionManager.getInstance().getId(action) == IdeActions.ACTION_CHOOSE_LOOKUP_ITEM) {
 | 
				
			||||||
        val myEditor = editor
 | 
					        val myEditor = editor
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -59,7 +59,7 @@ internal object IdeaSpecifics {
 | 
				
			|||||||
    private var completionPrevDocumentLength: Int? = null
 | 
					    private var completionPrevDocumentLength: Int? = null
 | 
				
			||||||
    private var completionPrevDocumentOffset: Int? = null
 | 
					    private var completionPrevDocumentOffset: Int? = null
 | 
				
			||||||
    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
					    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
					      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
				
			||||||
      if (hostEditor != null) {
 | 
					      if (hostEditor != null) {
 | 
				
			||||||
@@ -92,7 +92,7 @@ internal object IdeaSpecifics {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
					    override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      val editor = editor
 | 
					      val editor = editor
 | 
				
			||||||
      if (editor != null && action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
 | 
					      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
 | 
					  //region Enter insert mode for surround templates without selection
 | 
				
			||||||
  class VimTemplateManagerListener : TemplateManagerListener {
 | 
					  class VimTemplateManagerListener : TemplateManagerListener {
 | 
				
			||||||
    override fun templateStarted(state: TemplateState) {
 | 
					    override fun templateStarted(state: TemplateState) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
      val editor = state.editor ?: return
 | 
					      val editor = state.editor ?: return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      state.addTemplateStateListener(object : TemplateEditingAdapter() {
 | 
					      state.addTemplateStateListener(object : TemplateEditingAdapter() {
 | 
				
			||||||
@@ -176,7 +176,7 @@ internal object IdeaSpecifics {
 | 
				
			|||||||
  //region Register shortcuts for lookup and perform partial reset
 | 
					  //region Register shortcuts for lookup and perform partial reset
 | 
				
			||||||
  class LookupTopicListener : LookupManagerListener {
 | 
					  class LookupTopicListener : LookupManagerListener {
 | 
				
			||||||
    override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
 | 
					    override fun activeLookupChanged(oldLookup: Lookup?, newLookup: Lookup?) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      // Lookup opened
 | 
					      // Lookup opened
 | 
				
			||||||
      if (oldLookup == null && newLookup is LookupImpl) {
 | 
					      if (oldLookup == null && newLookup is LookupImpl) {
 | 
				
			||||||
@@ -199,7 +199,7 @@ internal object IdeaSpecifics {
 | 
				
			|||||||
  //region Hide Vim search highlights when showing IntelliJ search results
 | 
					  //region Hide Vim search highlights when showing IntelliJ search results
 | 
				
			||||||
  class VimFindModelListener : FindModelListener {
 | 
					  class VimFindModelListener : FindModelListener {
 | 
				
			||||||
    override fun findNextModelChanged() {
 | 
					    override fun findNextModelChanged() {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
      VimPlugin.getSearch().clearSearchHighlight()
 | 
					      VimPlugin.getSearch().clearSearchHighlight()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ internal class RiderActionListener : AnActionListener {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private var editor: Editor? = null
 | 
					  private var editor: Editor? = null
 | 
				
			||||||
  override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
					  override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
				
			||||||
    if (!VimPlugin.isEnabled()) return
 | 
					    if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
					    val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
				
			||||||
    if (hostEditor != null) {
 | 
					    if (hostEditor != null) {
 | 
				
			||||||
@@ -36,7 +36,7 @@ internal class RiderActionListener : AnActionListener {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
					  override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
				
			||||||
    if (!VimPlugin.isEnabled()) return
 | 
					    if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //region Extend Selection for Rider
 | 
					    //region Extend Selection for Rider
 | 
				
			||||||
    when (ActionManager.getInstance().getId(action)) {
 | 
					    when (ActionManager.getInstance().getId(action)) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@
 | 
				
			|||||||
package com.maddyhome.idea.vim.listener
 | 
					package com.maddyhome.idea.vim.listener
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.intellij.ide.ui.UISettings
 | 
					import com.intellij.ide.ui.UISettings
 | 
				
			||||||
 | 
					import com.intellij.openapi.Disposable
 | 
				
			||||||
import com.intellij.openapi.application.ApplicationManager
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
import com.intellij.openapi.diagnostic.Logger
 | 
					import com.intellij.openapi.diagnostic.Logger
 | 
				
			||||||
import com.intellij.openapi.diagnostic.trace
 | 
					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.SelectionEvent
 | 
				
			||||||
import com.intellij.openapi.editor.event.SelectionListener
 | 
					import com.intellij.openapi.editor.event.SelectionListener
 | 
				
			||||||
import com.intellij.openapi.editor.ex.DocumentEx
 | 
					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.EditorComponentImpl
 | 
				
			||||||
import com.intellij.openapi.editor.impl.EditorImpl
 | 
					 | 
				
			||||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
					import com.intellij.openapi.fileEditor.FileEditorManager
 | 
				
			||||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
 | 
					import com.intellij.openapi.fileEditor.FileEditorManagerEvent
 | 
				
			||||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
 | 
					import com.intellij.openapi.fileEditor.FileEditorManagerListener
 | 
				
			||||||
@@ -40,14 +42,11 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
 | 
				
			|||||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
 | 
					import com.intellij.openapi.fileEditor.impl.EditorComposite
 | 
				
			||||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
 | 
					import com.intellij.openapi.fileEditor.impl.EditorWindow
 | 
				
			||||||
import com.intellij.openapi.project.ProjectManager
 | 
					import com.intellij.openapi.project.ProjectManager
 | 
				
			||||||
import com.intellij.openapi.rd.createLifetime
 | 
					 | 
				
			||||||
import com.intellij.openapi.rd.createNestedDisposable
 | 
					 | 
				
			||||||
import com.intellij.openapi.util.Disposer
 | 
					import com.intellij.openapi.util.Disposer
 | 
				
			||||||
import com.intellij.openapi.util.Key
 | 
					import com.intellij.openapi.util.Key
 | 
				
			||||||
import com.intellij.openapi.util.removeUserData
 | 
					import com.intellij.openapi.util.removeUserData
 | 
				
			||||||
import com.intellij.openapi.vfs.VirtualFile
 | 
					import com.intellij.openapi.vfs.VirtualFile
 | 
				
			||||||
import com.intellij.util.ExceptionUtil
 | 
					import com.intellij.util.ExceptionUtil
 | 
				
			||||||
import com.jetbrains.rd.util.lifetime.Lifetime
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.EventFacade
 | 
					import com.maddyhome.idea.vim.EventFacade
 | 
				
			||||||
import com.maddyhome.idea.vim.KeyHandler
 | 
					import com.maddyhome.idea.vim.KeyHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.VimKeyListener
 | 
					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.ex.ExOutputModel
 | 
				
			||||||
import com.maddyhome.idea.vim.group.EditorGroup
 | 
					import com.maddyhome.idea.vim.group.EditorGroup
 | 
				
			||||||
import com.maddyhome.idea.vim.group.FileGroup
 | 
					import com.maddyhome.idea.vim.group.FileGroup
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.group.IjOptions
 | 
				
			||||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
					import com.maddyhome.idea.vim.group.MotionGroup
 | 
				
			||||||
import com.maddyhome.idea.vim.group.OptionGroup
 | 
					import com.maddyhome.idea.vim.group.OptionGroup
 | 
				
			||||||
import com.maddyhome.idea.vim.group.ScrollGroup
 | 
					import com.maddyhome.idea.vim.group.ScrollGroup
 | 
				
			||||||
@@ -71,10 +71,9 @@ import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
				
			|||||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
 | 
					import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
					import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.correctorRequester
 | 
					import com.maddyhome.idea.vim.handler.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.GuicursorChangeListener
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.StrictMode
 | 
					import com.maddyhome.idea.vim.helper.StrictMode
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.VimStandalonePluginUpdateChecker
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.helper.exitSelectMode
 | 
					import com.maddyhome.idea.vim.helper.exitSelectMode
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.exitVisualMode
 | 
					import com.maddyhome.idea.vim.helper.exitVisualMode
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.forceBarCursor
 | 
					import com.maddyhome.idea.vim.helper.forceBarCursor
 | 
				
			||||||
@@ -91,11 +90,16 @@ import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
 | 
				
			|||||||
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
 | 
					import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
					import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.VimStateMachine
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
 | 
					import com.maddyhome.idea.vim.state.mode.inSelectMode
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
					import com.maddyhome.idea.vim.state.mode.mode
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
					import com.maddyhome.idea.vim.state.mode.selectionType
 | 
				
			||||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
 | 
					import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
 | 
				
			||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
					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.MouseAdapter
 | 
				
			||||||
import java.awt.event.MouseEvent
 | 
					import java.awt.event.MouseEvent
 | 
				
			||||||
import javax.swing.SwingUtilities
 | 
					import javax.swing.SwingUtilities
 | 
				
			||||||
@@ -131,7 +135,7 @@ internal object VimListenerManager {
 | 
				
			|||||||
    GlobalListeners.enable()
 | 
					    GlobalListeners.enable()
 | 
				
			||||||
    EditorListeners.addAll()
 | 
					    EditorListeners.addAll()
 | 
				
			||||||
    correctorRequester.request()
 | 
					    correctorRequester.request()
 | 
				
			||||||
    keymapCheckRequester.request()
 | 
					    check(keyCheckRequests.tryEmit(Unit))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun turnOff() {
 | 
					  fun turnOff() {
 | 
				
			||||||
@@ -155,6 +159,13 @@ internal object VimListenerManager {
 | 
				
			|||||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
					      optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
				
			||||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
					      optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
				
			||||||
      optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
					      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)
 | 
					      optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
 | 
					      EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
 | 
				
			||||||
@@ -162,6 +173,8 @@ internal object VimListenerManager {
 | 
				
			|||||||
      busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
 | 
					      busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
 | 
					      EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
 | 
				
			||||||
 | 
					      val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
 | 
				
			||||||
 | 
					      eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun disable() {
 | 
					    fun disable() {
 | 
				
			||||||
@@ -172,6 +185,8 @@ internal object VimListenerManager {
 | 
				
			|||||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
					      optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
				
			||||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
					      optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
				
			||||||
      optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
					      optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
				
			||||||
 | 
					      optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
 | 
				
			||||||
 | 
					      optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
 | 
				
			||||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
					      optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -214,49 +229,67 @@ internal object VimListenerManager {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
 | 
					    fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
 | 
				
			||||||
      val pluginLifetime = VimPlugin.getInstance().createLifetime()
 | 
					      // As I understand, there is no need to pass a disposable that also disposes on editor close
 | 
				
			||||||
      val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
 | 
					      //   because all editor resources will be garbage collected anyway on editor close
 | 
				
			||||||
      val disposable =
 | 
					      val disposable = editor.project?.vimDisposable ?: return
 | 
				
			||||||
        Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
 | 
					
 | 
				
			||||||
 | 
					      val listenersDisposable = Disposer.newDisposable(disposable)
 | 
				
			||||||
 | 
					      editor.putUserData(editorListenersDisposable, listenersDisposable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Disposer.register(listenersDisposable) {
 | 
				
			||||||
 | 
					        if (VimListenerTestObject.enabled) {
 | 
				
			||||||
 | 
					          VimListenerTestObject.disposedCounter += 1
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      editor.contentComponent.addKeyListener(VimKeyListener)
 | 
					      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
 | 
					      // 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()
 | 
					      val eventFacade = EventFacade.getInstance()
 | 
				
			||||||
      eventFacade.addEditorMouseListener(editor, EditorMouseHandler, disposable)
 | 
					      eventFacade.addEditorMouseListener(editor, EditorMouseHandler, listenersDisposable)
 | 
				
			||||||
      eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, disposable)
 | 
					      eventFacade.addEditorMouseMotionListener(editor, EditorMouseHandler, listenersDisposable)
 | 
				
			||||||
      eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, disposable)
 | 
					      eventFacade.addEditorSelectionListener(editor, EditorSelectionHandler, listenersDisposable)
 | 
				
			||||||
      eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, disposable)
 | 
					      eventFacade.addComponentMouseListener(editor.contentComponent, ComponentMouseListener, listenersDisposable)
 | 
				
			||||||
      eventFacade.addCaretListener(editor, EditorCaretHandler, disposable)
 | 
					      eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      VimPlugin.getEditor().editorCreated(editor)
 | 
					      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)
 | 
					        VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    fun remove(editor: Editor, isReleased: Boolean) {
 | 
					    fun remove(editor: Editor, isReleased: Boolean) {
 | 
				
			||||||
      editor.contentComponent.removeKeyListener(VimKeyListener)
 | 
					      val editorDisposable = editor.getUserData(editorListenersDisposable)
 | 
				
			||||||
      val eventFacade = EventFacade.getInstance()
 | 
					      if (editorDisposable != null) {
 | 
				
			||||||
      eventFacade.removeEditorMouseListener(editor, EditorMouseHandler)
 | 
					        Disposer.dispose(editorDisposable)
 | 
				
			||||||
      eventFacade.removeEditorMouseMotionListener(editor, EditorMouseHandler)
 | 
					      }
 | 
				
			||||||
      eventFacade.removeEditorSelectionListener(editor, EditorSelectionHandler)
 | 
					      else StrictMode.fail("Editor doesn't have disposable attached. $editor")
 | 
				
			||||||
      eventFacade.removeComponentMouseListener(editor.contentComponent, ComponentMouseListener)
 | 
					 | 
				
			||||||
      eventFacade.removeCaretListener(editor, EditorCaretHandler)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
 | 
					      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 {
 | 
					  object VimCaretListener : CaretListener {
 | 
				
			||||||
    override fun caretAdded(event: CaretEvent) {
 | 
					    override fun caretAdded(event: CaretEvent) {
 | 
				
			||||||
      if (vimDisabled(event.editor)) return
 | 
					      if (vimDisabled(event.editor)) return
 | 
				
			||||||
@@ -271,7 +304,17 @@ internal object VimListenerManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  class VimFileEditorManagerListener : FileEditorManagerListener {
 | 
					  class VimFileEditorManagerListener : FileEditorManagerListener {
 | 
				
			||||||
    override fun selectionChanged(event: FileEditorManagerEvent) {
 | 
					    override fun selectionChanged(event: FileEditorManagerEvent) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return
 | 
					      if (VimPlugin.isNotEnabled()) return
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      val newEditor = event.newEditor
 | 
				
			||||||
 | 
					      if (newEditor is TextEditor) {
 | 
				
			||||||
 | 
					        val editor = newEditor.editor
 | 
				
			||||||
 | 
					        if (editor.isInsertMode) {
 | 
				
			||||||
 | 
					          VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
 | 
				
			||||||
 | 
					          KeyHandler.getInstance().reset(editor.vim)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
      MotionGroup.fileEditorManagerSelectionChangedCallback(event)
 | 
					      MotionGroup.fileEditorManagerSelectionChangedCallback(event)
 | 
				
			||||||
      FileGroup.fileEditorManagerSelectionChangedCallback(event)
 | 
					      FileGroup.fileEditorManagerSelectionChangedCallback(event)
 | 
				
			||||||
      SearchGroup.fileEditorManagerSelectionChangedCallback(event)
 | 
					      SearchGroup.fileEditorManagerSelectionChangedCallback(event)
 | 
				
			||||||
@@ -336,18 +379,18 @@ internal object VimListenerManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
        event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
 | 
					        event.editor.putUserData(openingEditorKey, OpeningEditor(openingEditor, owningEditorWindow, isPreview, canBeReused))
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					 | 
				
			||||||
      VimStandalonePluginUpdateChecker.instance.pluginUsed()
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun editorReleased(event: EditorFactoryEvent) {
 | 
					    override fun editorReleased(event: EditorFactoryEvent) {
 | 
				
			||||||
      injector.markService.editorReleased(event.editor.vim)
 | 
					      val vimEditor = event.editor.vim
 | 
				
			||||||
 | 
					      injector.listenersNotifier.notifyEditorReleased(vimEditor)
 | 
				
			||||||
 | 
					      injector.markService.editorReleased(vimEditor)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    override fun fileOpenedSync(
 | 
					    override fun fileOpenedSync(
 | 
				
			||||||
      source: FileEditorManager,
 | 
					      source: FileEditorManager,
 | 
				
			||||||
      file: VirtualFile,
 | 
					      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
 | 
					      // 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
 | 
					      // created (and the list of editors and providers is passed here) and added to an EditorWindow tab, inside a
 | 
				
			||||||
@@ -405,6 +448,7 @@ internal object VimListenerManager {
 | 
				
			|||||||
     */
 | 
					     */
 | 
				
			||||||
    override fun selectionChanged(selectionEvent: SelectionEvent) {
 | 
					    override fun selectionChanged(selectionEvent: SelectionEvent) {
 | 
				
			||||||
      if (selectionEvent.editor.isIdeaVimDisabledHere) return
 | 
					      if (selectionEvent.editor.isIdeaVimDisabledHere) return
 | 
				
			||||||
 | 
					      VimVisualTimer.drop()
 | 
				
			||||||
      val editor = selectionEvent.editor
 | 
					      val editor = selectionEvent.editor
 | 
				
			||||||
      val document = editor.document
 | 
					      val document = editor.document
 | 
				
			||||||
      val ijVimEditor = IjVimEditor(editor)
 | 
					      val ijVimEditor = IjVimEditor(editor)
 | 
				
			||||||
@@ -698,6 +742,11 @@ internal object VimListenerManager {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal object VimListenerTestObject {
 | 
				
			||||||
 | 
					  var enabled: Boolean = false
 | 
				
			||||||
 | 
					  var disposedCounter = 0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private object MouseEventsDataHolder {
 | 
					private object MouseEventsDataHolder {
 | 
				
			||||||
  const val skipEvents = 3
 | 
					  const val skipEvents = 3
 | 
				
			||||||
  var skipNDragEvents = skipEvents
 | 
					  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.updateCaretsVisualAttributes
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
 | 
					import com.maddyhome.idea.vim.helper.updateCaretsVisualPosition
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.vimChangeActionSwitchMode
 | 
					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.helper.vimLastSelectionType
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
@@ -82,11 +81,6 @@ internal class IjVimEditor(editor: Editor) : MutableLinearEditor() {
 | 
				
			|||||||
    set(value) {
 | 
					    set(value) {
 | 
				
			||||||
      editor.vimChangeActionSwitchMode = value
 | 
					      editor.vimChangeActionSwitchMode = value
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  override var vimKeepingVisualOperatorAction: Boolean
 | 
					 | 
				
			||||||
    get() = editor.vimKeepingVisualOperatorAction
 | 
					 | 
				
			||||||
    set(value) {
 | 
					 | 
				
			||||||
      editor.vimKeepingVisualOperatorAction = value
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun fileSize(): Long = editor.fileSize.toLong()
 | 
					  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.KeyHandler;
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
					import com.maddyhome.idea.vim.VimPlugin;
 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext;
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext;
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.diagnostic.VimLogger;
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.HelperKt;
 | 
					import com.maddyhome.idea.vim.helper.HelperKt;
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.MessageHelper;
 | 
					import com.maddyhome.idea.vim.helper.MessageHelper;
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.UiHelper;
 | 
					import com.maddyhome.idea.vim.helper.UiHelper;
 | 
				
			||||||
@@ -59,6 +60,8 @@ public class ExOutputPanel extends JPanel {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private boolean myActive = false;
 | 
					  private boolean myActive = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private static final VimLogger LOG = injector.getLogger(ExOutputPanel.class);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private ExOutputPanel(@NotNull Editor editor) {
 | 
					  private ExOutputPanel(@NotNull Editor editor) {
 | 
				
			||||||
    myEditor = editor;
 | 
					    myEditor = editor;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -299,6 +302,10 @@ public class ExOutputPanel extends JPanel {
 | 
				
			|||||||
        final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
 | 
					        final KeyStroke key = KeyStroke.getKeyStrokeForEvent(e);
 | 
				
			||||||
        final List<KeyStroke> keys = new ArrayList<>(1);
 | 
					        final List<KeyStroke> keys = new ArrayList<>(1);
 | 
				
			||||||
        keys.add(key);
 | 
					        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);
 | 
					        KeyHandler.getInstance().getKeyStack().addKeys(keys);
 | 
				
			||||||
        ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
 | 
					        ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(myEditor), null);
 | 
				
			||||||
        VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
 | 
					        VimPlugin.getMacro().playbackKeys(new IjVimEditor(myEditor), context, 1);
 | 
				
			||||||
@@ -358,7 +365,7 @@ public class ExOutputPanel extends JPanel {
 | 
				
			|||||||
  public static class LafListener implements LafManagerListener {
 | 
					  public static class LafListener implements LafManagerListener {
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
					    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return;
 | 
					      if (VimPlugin.isNotEnabled()) return;
 | 
				
			||||||
      // Calls updateUI on this and child components
 | 
					      // Calls updateUI on this and child components
 | 
				
			||||||
      for (Editor editor : HelperKt.localEditors()) {
 | 
					      for (Editor editor : HelperKt.localEditors()) {
 | 
				
			||||||
        if (!ExOutputPanel.isPanelActive(editor)) continue;
 | 
					        if (!ExOutputPanel.isPanelActive(editor)) continue;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,6 +8,9 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package com.maddyhome.idea.vim.ui
 | 
					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.KeyHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.isCloseKeyStroke
 | 
					import com.maddyhome.idea.vim.helper.isCloseKeyStroke
 | 
				
			||||||
@@ -22,13 +25,19 @@ import javax.swing.KeyStroke
 | 
				
			|||||||
 * @author dhleong
 | 
					 * @author dhleong
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
public object ModalEntry {
 | 
					public object ModalEntry {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public val LOG: Logger = logger<ModalEntry>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
 | 
					  public inline fun activate(editor: VimEditor, crossinline processor: (KeyStroke) -> Boolean) {
 | 
				
			||||||
    // Firstly we pull the unfinished keys of the current mapping
 | 
					    // Firstly we pull the unfinished keys of the current mapping
 | 
				
			||||||
    val mappingStack = KeyHandler.getInstance().keyStack
 | 
					    val mappingStack = KeyHandler.getInstance().keyStack
 | 
				
			||||||
 | 
					    LOG.trace("Dumping key stack:")
 | 
				
			||||||
 | 
					    LOG.trace { mappingStack.dump() }
 | 
				
			||||||
    var stroke = mappingStack.feedSomeStroke()
 | 
					    var stroke = mappingStack.feedSomeStroke()
 | 
				
			||||||
    while (stroke != null) {
 | 
					    while (stroke != null) {
 | 
				
			||||||
      val result = processor(stroke)
 | 
					      val result = processor(stroke)
 | 
				
			||||||
      if (!result) {
 | 
					      if (!result) {
 | 
				
			||||||
 | 
					        LOG.trace("Got char from mapping stack")
 | 
				
			||||||
        return
 | 
					        return
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      stroke = mappingStack.feedSomeStroke()
 | 
					      stroke = mappingStack.feedSomeStroke()
 | 
				
			||||||
@@ -55,6 +64,7 @@ public object ModalEntry {
 | 
				
			|||||||
          KeyHandler.getInstance().modalEntryKeys += stroke
 | 
					          KeyHandler.getInstance().modalEntryKeys += stroke
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        if (!processor(stroke)) {
 | 
					        if (!processor(stroke)) {
 | 
				
			||||||
 | 
					          LOG.trace("Got char from keyboard input: $stroke. Event: $e")
 | 
				
			||||||
          KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this)
 | 
					          KeyboardFocusManager.getCurrentKeyboardFocusManager().removeKeyEventDispatcher(this)
 | 
				
			||||||
          loop.exit()
 | 
					          loop.exit()
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,6 @@ import com.intellij.openapi.wm.StatusBarWidget
 | 
				
			|||||||
import com.intellij.openapi.wm.StatusBarWidgetFactory
 | 
					import com.intellij.openapi.wm.StatusBarWidgetFactory
 | 
				
			||||||
import com.intellij.openapi.wm.WindowManager
 | 
					import com.intellij.openapi.wm.WindowManager
 | 
				
			||||||
import com.intellij.openapi.wm.impl.status.EditorBasedWidget
 | 
					import com.intellij.openapi.wm.impl.status.EditorBasedWidget
 | 
				
			||||||
import com.intellij.openapi.wm.impl.status.widget.StatusBarWidgetsManager
 | 
					 | 
				
			||||||
import com.intellij.util.Consumer
 | 
					import com.intellij.util.Consumer
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
					import com.maddyhome.idea.vim.api.globalOptions
 | 
				
			||||||
@@ -68,13 +67,6 @@ internal object ShowCmd {
 | 
				
			|||||||
internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
 | 
					internal object ShowCmdOptionChangeListener : GlobalOptionChangeListener {
 | 
				
			||||||
  override fun onGlobalOptionChanged() {
 | 
					  override fun onGlobalOptionChanged() {
 | 
				
			||||||
    ShowCmd.update()
 | 
					    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 {
 | 
					  public static class LafListener implements LafManagerListener {
 | 
				
			||||||
    @Override
 | 
					    @Override
 | 
				
			||||||
    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
					    public void lookAndFeelChanged(@NotNull LafManager source) {
 | 
				
			||||||
      if (!VimPlugin.isEnabled()) return;
 | 
					      if (VimPlugin.isNotEnabled()) return;
 | 
				
			||||||
      // Calls updateUI on this and child components
 | 
					      // Calls updateUI on this and child components
 | 
				
			||||||
      if (ExEntryPanel.isInstanceWithShortcutsActive()) {
 | 
					      if (ExEntryPanel.isInstanceWithShortcutsActive()) {
 | 
				
			||||||
        IJSwingUtilities.updateComponentTreeUI(ExEntryPanel.getInstance());
 | 
					        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 {
 | 
					internal object IdeaRefactorModeHelper {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun correctSelection(editor: Editor) {
 | 
					  sealed interface Action {
 | 
				
			||||||
    val action: () -> Unit = {
 | 
					    object RemoveSelection : Action
 | 
				
			||||||
      val mode = editor.vim.mode
 | 
					    class SetMode(val newMode: Mode) : Action
 | 
				
			||||||
      if (!mode.hasVisualSelection && editor.selectionModel.hasSelection()) {
 | 
					    class MoveToOffset(val newOffset: Int) : Action
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun applyCorrections(corrections: List<Action>, editor: Editor) {
 | 
				
			||||||
 | 
					    val correctionsApplier = {
 | 
				
			||||||
 | 
					      corrections.forEach { correction ->
 | 
				
			||||||
 | 
					        when (correction) {
 | 
				
			||||||
 | 
					          is Action.MoveToOffset -> {
 | 
				
			||||||
 | 
					            editor.caretModel.moveToOffset(correction.newOffset)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          Action.RemoveSelection -> {
 | 
				
			||||||
            SelectionVimListenerSuppressor.lock().use {
 | 
					            SelectionVimListenerSuppressor.lock().use {
 | 
				
			||||||
              editor.selectionModel.removeSelection()
 | 
					              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
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (editor.hasBlockOrUnderscoreCaret()) {
 | 
					          is Action.SetMode -> {
 | 
				
			||||||
        TemplateManagerImpl.getTemplateState(editor)?.currentVariableRange?.let { segmentRange ->
 | 
					            editor.vim.vimStateMachine.mode = correction.newMode
 | 
				
			||||||
          if (!segmentRange.isEmpty && segmentRange.endOffset == editor.caretModel.offset && editor.caretModel.offset != 0) {
 | 
					 | 
				
			||||||
            editor.caretModel.moveToOffset(editor.caretModel.offset - 1)
 | 
					 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -70,7 +67,9 @@ internal object IdeaRefactorModeHelper {
 | 
				
			|||||||
    if (lookup != null) {
 | 
					    if (lookup != null) {
 | 
				
			||||||
      val selStart = editor.selectionModel.selectionStart
 | 
					      val selStart = editor.selectionModel.selectionStart
 | 
				
			||||||
      val selEnd = editor.selectionModel.selectionEnd
 | 
					      val selEnd = editor.selectionModel.selectionEnd
 | 
				
			||||||
      lookup.performGuardedChange(action)
 | 
					      lookup.performGuardedChange {
 | 
				
			||||||
 | 
					        correctionsApplier()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
      lookup.addLookupListener(object : LookupListener {
 | 
					      lookup.addLookupListener(object : LookupListener {
 | 
				
			||||||
        override fun beforeItemSelected(event: LookupEvent): Boolean {
 | 
					        override fun beforeItemSelected(event: LookupEvent): Boolean {
 | 
				
			||||||
          // FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
 | 
					          // FIXME: 01.11.2019 Nasty workaround because of problems in IJ platform
 | 
				
			||||||
@@ -82,7 +81,41 @@ internal object IdeaRefactorModeHelper {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
    } else {
 | 
					    } 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
 | 
					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.ExecutionContext
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.ex.vimscript.VimScriptGlobalEnvironment
 | 
					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.datatypes.VimString
 | 
				
			||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
 | 
					import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
 | 
				
			||||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Variable
 | 
					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) {
 | 
					  override fun storeVariable(variable: Variable, value: VimDataType, editor: VimEditor, context: ExecutionContext, vimContext: VimLContext) {
 | 
				
			||||||
    super.storeVariable(variable, value, editor, context, vimContext)
 | 
					    super.storeVariable(variable, value, editor, context, vimContext)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -47,4 +53,49 @@ internal class IjVariableService : VimVariableServiceBase() {
 | 
				
			|||||||
      else -> error("Unexpected")
 | 
					      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>
 | 
				
			||||||
@@ -226,12 +226,12 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <!-- Change -->
 | 
					    <!-- Change -->
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerMotionAction" mappingModes="N" keys="gu"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>
 | 
					<!--    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseLowerVisualAction" mappingModes="X" keys="u"/>-->
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleCharacterAction" mappingModes="N" keys="~"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleMotionAction" mappingModes="N" keys="g~"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseToggleVisualAction" mappingModes="X" keys="~"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperMotionAction" mappingModes="N" keys="gU"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>
 | 
					<!--    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCaseUpperVisualAction" mappingModes="X" keys="U"/>-->
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharacterAction" mappingModes="N" keys="r"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeCharactersAction" mappingModes="N" keys="s"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.change.ChangeEndOfLineAction" mappingModes="N" keys="C"/>
 | 
				
			||||||
@@ -329,8 +329,8 @@
 | 
				
			|||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="N" keys="«C-R»"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="NX" keys="U,«C-R»"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="N"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="NX"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Keys -->
 | 
					    <!-- Keys -->
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,4 @@
 | 
				
			|||||||
<!--
 | 
					<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
 | 
				
			||||||
  ~ Copyright 2003-2023 The IdeaVim authors
 | 
					 | 
				
			||||||
  ~
 | 
					 | 
				
			||||||
  ~ Use of this source code is governed by an MIT-style
 | 
					 | 
				
			||||||
  ~ license that can be found in the LICENSE.txt file or at
 | 
					 | 
				
			||||||
  ~ https://opensource.org/licenses/MIT.
 | 
					 | 
				
			||||||
  -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
 | 
					 | 
				
			||||||
  <name>IdeaVim</name>
 | 
					  <name>IdeaVim</name>
 | 
				
			||||||
  <id>IdeaVIM</id>
 | 
					  <id>IdeaVIM</id>
 | 
				
			||||||
  <description><![CDATA[
 | 
					  <description><![CDATA[
 | 
				
			||||||
@@ -21,13 +13,13 @@
 | 
				
			|||||||
        <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
 | 
					        <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
 | 
				
			||||||
      </ul>
 | 
					      </ul>
 | 
				
			||||||
    ]]></description>
 | 
					    ]]></description>
 | 
				
			||||||
  <version>SNAPSHOT</version>
 | 
					  <version>chylex</version>
 | 
				
			||||||
  <vendor>JetBrains</vendor>
 | 
					  <vendor>JetBrains</vendor>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
 | 
					  <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
 | 
				
			||||||
  <!-- Check for [Version Update] tag in YouTrack as well -->
 | 
					  <!-- Check for [Version Update] tag in YouTrack as well -->
 | 
				
			||||||
  <!-- Also, please update the value in build.gradle.kts file-->
 | 
					  <!-- Also, please update the value in build.gradle.kts file-->
 | 
				
			||||||
  <idea-version since-build="233.11799.30"/>
 | 
					  <idea-version since-build="232"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
 | 
					  <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
 | 
				
			||||||
  <depends>com.intellij.modules.platform</depends>
 | 
					  <depends>com.intellij.modules.platform</depends>
 | 
				
			||||||
@@ -37,6 +29,8 @@
 | 
				
			|||||||
  <!--suppress PluginXmlValidity -->
 | 
					  <!--suppress PluginXmlValidity -->
 | 
				
			||||||
  <depends optional="true" config-file="ides/ideavim-withRider.xml">com.intellij.modules.rider</depends>
 | 
					  <depends optional="true" config-file="ides/ideavim-withRider.xml">com.intellij.modules.rider</depends>
 | 
				
			||||||
  <!--suppress PluginXmlValidity -->
 | 
					  <!--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="ides/ideavim-withAppCode.xml">com.intellij.modules.appcode</depends>
 | 
				
			||||||
  <depends optional="true" config-file="ideavim-withAceJump.xml">AceJump</depends>
 | 
					  <depends optional="true" config-file="ideavim-withAceJump.xml">AceJump</depends>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -63,6 +57,8 @@
 | 
				
			|||||||
    <extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
 | 
					    <extensionPoint name="vimAction" beanClass="com.maddyhome.idea.vim.handler.ActionBeanClass" dynamic="true">
 | 
				
			||||||
      <with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
 | 
					      <with attribute="implementation" implements="com.maddyhome.idea.vim.handler.EditorActionHandlerBase"/>
 | 
				
			||||||
    </extensionPoint>
 | 
					    </extensionPoint>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <extensionPoint interface="com.maddyhome.idea.vim.ide.ClionNovaProvider" dynamic="true" name="clionNovaProvider"/>
 | 
				
			||||||
  </extensionPoints>
 | 
					  </extensionPoints>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <extensions defaultExtensionNs="com.intellij">
 | 
					  <extensions defaultExtensionNs="com.intellij">
 | 
				
			||||||
@@ -70,7 +66,9 @@
 | 
				
			|||||||
    <projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
 | 
					    <projectService serviceImplementation="com.maddyhome.idea.vim.group.NotificationService"/>
 | 
				
			||||||
    <projectService serviceImplementation="com.maddyhome.idea.vim.group.LastTabService"/>
 | 
					    <projectService serviceImplementation="com.maddyhome.idea.vim.group.LastTabService"/>
 | 
				
			||||||
    <statusBarWidgetFactory id="IdeaVim-Icon" implementation="com.maddyhome.idea.vim.ui.StatusBarIconFactory"/>
 | 
					    <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::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"/>
 | 
					    <applicationService serviceImplementation="com.maddyhome.idea.vim.VimPlugin"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -154,6 +152,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    <action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction"/>
 | 
					    <action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction"/>
 | 
				
			||||||
    <action id="VimActions" class="com.maddyhome.idea.vim.ui.VimActions"/>
 | 
					    <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">
 | 
					    <group id="IdeaVim.ReloadVimRc.group" class="com.maddyhome.idea.vim.ui.ReloadFloatingToolbarActionGroup">
 | 
				
			||||||
      <action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc"
 | 
					      <action id="IdeaVim.ReloadVimRc.reload" class="com.maddyhome.idea.vim.ui.ReloadVimRc"
 | 
				
			||||||
@@ -162,5 +161,6 @@
 | 
				
			|||||||
    </group>
 | 
					    </group>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
 | 
					    <action id="VimFindActionIdAction" class="com.maddyhome.idea.vim.listener.FindActionIdAction"/>
 | 
				
			||||||
 | 
					    <action id="VimJumpToSource" class="com.intellij.diff.actions.impl.OpenInEditorAction" />
 | 
				
			||||||
  </actions>
 | 
					  </actions>
 | 
				
			||||||
</idea-plugin>
 | 
					</idea-plugin>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -84,6 +84,8 @@ action.VimShortcutKeyAction.text=Shortcuts
 | 
				
			|||||||
action.VimActions.text=Vim Actions
 | 
					action.VimActions.text=Vim Actions
 | 
				
			||||||
action.not.found.0=Action not found: {0}
 | 
					action.not.found.0=Action not found: {0}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					action.CustomizeModeWidget.text=Mode Widget Settings
 | 
				
			||||||
 | 
					
 | 
				
			||||||
action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
 | 
					action.VimFindActionIdAction.text=IdeaVim: Track Action Ids
 | 
				
			||||||
action.VimFindActionIdAction.description=Starts tracking ids of executed actions
 | 
					action.VimFindActionIdAction.description=Starts tracking ids of executed actions
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,6 +131,28 @@ action.finish.eap.text=Finish EAP
 | 
				
			|||||||
# Don't forget to update README if you modify this entry
 | 
					# Don't forget to update README if you modify this entry
 | 
				
			||||||
action.subscribe.to.eap.text=Subscribe to EAP
 | 
					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.name.vim.emulation=Vim
 | 
				
			||||||
configurable.keyhandler.link=<html>Use <a>sethandler</a> command to configure handlers from the .ideavimrc file</html>
 | 
					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}.
 | 
					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.EditorTestUtil
 | 
				
			||||||
import com.intellij.testFramework.fixtures.CodeInsightTestFixture
 | 
					import com.intellij.testFramework.fixtures.CodeInsightTestFixture
 | 
				
			||||||
import com.intellij.util.containers.toArray
 | 
					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.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.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.globalIjOptions
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.mode
 | 
				
			||||||
import org.junit.jupiter.params.provider.Arguments
 | 
					import org.junit.jupiter.params.provider.Arguments
 | 
				
			||||||
import kotlin.test.fail
 | 
					import kotlin.test.fail
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -129,3 +134,15 @@ internal fun <T> product(vararg elements: List<T>): List<List<T>> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
  return res
 | 
					  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")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,7 +102,7 @@ import kotlin.test.assertTrue
 | 
				
			|||||||
 * This is done as we have no mechanism to guarantee compatibility as we update this test case.
 | 
					 * This is done as we have no mechanism to guarantee compatibility as we update this test case.
 | 
				
			||||||
 * Feel free to copy this class into your plugin, or copy just needed functions.
 | 
					 * Feel free to copy this class into your plugin, or copy just needed functions.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@RunInEdt(writeIntent = true)
 | 
					@RunInEdt
 | 
				
			||||||
@ApiStatus.Internal
 | 
					@ApiStatus.Internal
 | 
				
			||||||
abstract class VimTestCase {
 | 
					abstract class VimTestCase {
 | 
				
			||||||
  protected lateinit var fixture: CodeInsightTestFixture
 | 
					  protected lateinit var fixture: CodeInsightTestFixture
 | 
				
			||||||
@@ -123,7 +123,7 @@ abstract class VimTestCase {
 | 
				
			|||||||
    VimPlugin.getOptionGroup().resetAllOptionsForTesting()
 | 
					    VimPlugin.getOptionGroup().resetAllOptionsForTesting()
 | 
				
			||||||
    VimPlugin.getKey().resetKeyMappings()
 | 
					    VimPlugin.getKey().resetKeyMappings()
 | 
				
			||||||
    VimPlugin.getSearch().resetState()
 | 
					    VimPlugin.getSearch().resetState()
 | 
				
			||||||
    if (!VimPlugin.isEnabled()) VimPlugin.setEnabled(true)
 | 
					    if (VimPlugin.isNotEnabled()) VimPlugin.setEnabled(true)
 | 
				
			||||||
    injector.globalOptions().ideastrictmode = true
 | 
					    injector.globalOptions().ideastrictmode = true
 | 
				
			||||||
    Checks.reset()
 | 
					    Checks.reset()
 | 
				
			||||||
    clearClipboard()
 | 
					    clearClipboard()
 | 
				
			||||||
@@ -152,8 +152,8 @@ abstract class VimTestCase {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @AfterEach
 | 
					  @AfterEach
 | 
				
			||||||
  open fun tearDown(testInfo: TestInfo) {
 | 
					  open fun tearDown(testInfo: TestInfo) {
 | 
				
			||||||
    val swingTimer = swingTimer
 | 
					 | 
				
			||||||
    swingTimer?.stop()
 | 
					    swingTimer?.stop()
 | 
				
			||||||
 | 
					    swingTimer = null
 | 
				
			||||||
    val bookmarksManager = BookmarksManager.getInstance(fixture.project)
 | 
					    val bookmarksManager = BookmarksManager.getInstance(fixture.project)
 | 
				
			||||||
    bookmarksManager?.bookmarks?.forEach { bookmark ->
 | 
					    bookmarksManager?.bookmarks?.forEach { bookmark ->
 | 
				
			||||||
      bookmarksManager.remove(bookmark)
 | 
					      bookmarksManager.remove(bookmark)
 | 
				
			||||||
@@ -170,6 +170,7 @@ abstract class VimTestCase {
 | 
				
			|||||||
    injector.jumpService.resetJumps()
 | 
					    injector.jumpService.resetJumps()
 | 
				
			||||||
    VimPlugin.getChange().resetRepeat()
 | 
					    VimPlugin.getChange().resetRepeat()
 | 
				
			||||||
    VimPlugin.getKey().savedShortcutConflicts.clear()
 | 
					    VimPlugin.getKey().savedShortcutConflicts.clear()
 | 
				
			||||||
 | 
					    assertTrue(KeyHandler.getInstance().keyStack.isEmpty())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Tear down neovim
 | 
					    // Tear down neovim
 | 
				
			||||||
    NeovimTesting.tearDown(testInfo)
 | 
					    NeovimTesting.tearDown(testInfo)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,22 +7,40 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package org.jetbrains.plugins.ideavim.action
 | 
					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.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					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.helper.vimStateMachine
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					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.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
 | 
					import org.jetbrains.plugins.ideavim.exceptionMappingOwner
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.rangeOf
 | 
					import org.jetbrains.plugins.ideavim.rangeOf
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
					import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.AfterEach
 | 
				
			||||||
import org.junit.jupiter.api.Test
 | 
					import org.junit.jupiter.api.Test
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.assertThrows
 | 
				
			||||||
 | 
					import kotlin.test.assertEquals
 | 
				
			||||||
import kotlin.test.assertNotNull
 | 
					import kotlin.test.assertNotNull
 | 
				
			||||||
 | 
					import kotlin.test.assertTrue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author vlan
 | 
					 * @author vlan
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class MacroActionTest : VimTestCase() {
 | 
					class MacroActionTest : VimTestCase() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @AfterEach
 | 
				
			||||||
 | 
					  fun tearDown() {
 | 
				
			||||||
 | 
					    injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // |q|
 | 
					  // |q|
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  fun testRecordMacro() {
 | 
					  fun testRecordMacro() {
 | 
				
			||||||
@@ -178,4 +196,33 @@ class MacroActionTest : VimTestCase() {
 | 
				
			|||||||
      """.trimIndent(),
 | 
					      """.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(
 | 
					    assertState(
 | 
				
			||||||
      """
 | 
					      """
 | 
				
			||||||
    <selection>one two
 | 
					    ${s}one two
 | 
				
			||||||
    three four
 | 
					    three four
 | 
				
			||||||
    five six
 | 
					    five six
 | 
				
			||||||
    </selection>
 | 
					    $se
 | 
				
			||||||
      """.trimIndent(),
 | 
					      """.trimIndent(),
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.common.Direction
 | 
					import com.maddyhome.idea.vim.common.Direction
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
					import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
@@ -85,7 +86,7 @@ class GnNextTextObjectTest : VimTestCase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
					  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
				
			||||||
    configureByText(before)
 | 
					    configureByText(before)
 | 
				
			||||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
					    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
				
			||||||
    typeText(keys)
 | 
					    typeText(keys)
 | 
				
			||||||
    assertState(after)
 | 
					    assertState(after)
 | 
				
			||||||
    assertState(Mode.NORMAL())
 | 
					    assertState(Mode.NORMAL())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,8 +12,9 @@ package org.jetbrains.plugins.ideavim.action.motion.gn
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.common.Direction
 | 
					import com.maddyhome.idea.vim.common.Direction
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
					import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
@@ -63,7 +64,7 @@ class GnPreviousTextObjectTest : VimTestCase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
					  private fun doTestWithSearch(keys: List<KeyStroke>, before: String, after: String) {
 | 
				
			||||||
    configureByText(before)
 | 
					    configureByText(before)
 | 
				
			||||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
					    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
				
			||||||
    typeText(keys)
 | 
					    typeText(keys)
 | 
				
			||||||
    assertState(after)
 | 
					    assertState(after)
 | 
				
			||||||
    assertState(Mode.NORMAL())
 | 
					    assertState(Mode.NORMAL())
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
 | 
				
			|||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
					import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.common.Direction
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
import com.maddyhome.idea.vim.common.Direction
 | 
					 | 
				
			||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
					import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
@@ -57,7 +58,7 @@ class VisualSelectNextSearchTest : VimTestCase() {
 | 
				
			|||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  fun testWithoutSpaces() {
 | 
					  fun testWithoutSpaces() {
 | 
				
			||||||
    configureByText("test<caret>test")
 | 
					    configureByText("test<caret>test")
 | 
				
			||||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
					    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
				
			||||||
    typeText(injector.parser.parseKeys("gn"))
 | 
					    typeText(injector.parser.parseKeys("gn"))
 | 
				
			||||||
    assertOffset(7)
 | 
					    assertOffset(7)
 | 
				
			||||||
    assertSelection("test")
 | 
					    assertSelection("test")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,9 +11,10 @@ import com.intellij.idea.TestFor
 | 
				
			|||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
					import com.maddyhome.idea.vim.action.motion.search.SearchWholeWordForwardAction
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.common.Direction
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
import com.maddyhome.idea.vim.common.Direction
 | 
					 | 
				
			||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
					import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
@@ -54,7 +55,7 @@ class VisualSelectPreviousSearchTest : VimTestCase() {
 | 
				
			|||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  fun testWithoutSpaces() {
 | 
					  fun testWithoutSpaces() {
 | 
				
			||||||
    configureByText("tes<caret>ttest")
 | 
					    configureByText("tes<caret>ttest")
 | 
				
			||||||
    VimPlugin.getSearch().setLastSearchState(fixture.editor, "test", "", Direction.FORWARDS)
 | 
					    VimPlugin.getSearch().setLastSearchState(fixture.editor.vim, "test", "", Direction.FORWARDS)
 | 
				
			||||||
    typeText(injector.parser.parseKeys("gN"))
 | 
					    typeText(injector.parser.parseKeys("gN"))
 | 
				
			||||||
    assertOffset(0)
 | 
					    assertOffset(0)
 | 
				
			||||||
    assertSelection("test")
 | 
					    assertSelection("test")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ package org.jetbrains.plugins.ideavim.action.motion.search
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.common.Direction
 | 
					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.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
@@ -167,7 +168,7 @@ class SearchAgainPreviousActionTest : VimTestCase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private fun doTestWithSearch(keys: String, before: String, after: String) {
 | 
					  private fun doTestWithSearch(keys: String, before: String, after: String) {
 | 
				
			||||||
    doTest(keys, before, after) {
 | 
					    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.idea.TestFor
 | 
				
			||||||
import com.intellij.openapi.actionSystem.DataContext
 | 
					import com.intellij.openapi.actionSystem.DataContext
 | 
				
			||||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
 | 
					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.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.ex.ExException
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
 | 
					import com.maddyhome.idea.vim.helper.StringHelper.parseKeys
 | 
				
			||||||
import com.maddyhome.idea.vim.history.HistoryConstants
 | 
					import com.maddyhome.idea.vim.history.HistoryConstants
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
 | 
					import org.jetbrains.plugins.ideavim.ExceptionHandler
 | 
				
			||||||
 | 
					import org.jetbrains.plugins.ideavim.OnlyThrowLoggedErrorProcessor
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
					import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
 | 
					import org.jetbrains.plugins.ideavim.assertThrowsLogError
 | 
				
			||||||
 | 
					import org.jetbrains.plugins.ideavim.exceptionMappingOwner
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
					import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.AfterEach
 | 
				
			||||||
import org.junit.jupiter.api.Disabled
 | 
					import org.junit.jupiter.api.Disabled
 | 
				
			||||||
import org.junit.jupiter.api.Test
 | 
					import org.junit.jupiter.api.Test
 | 
				
			||||||
import org.junit.jupiter.api.assertThrows
 | 
					import org.junit.jupiter.api.assertThrows
 | 
				
			||||||
import javax.swing.JTextArea
 | 
					import javax.swing.JTextArea
 | 
				
			||||||
 | 
					import kotlin.test.assertEquals
 | 
				
			||||||
import kotlin.test.assertIs
 | 
					import kotlin.test.assertIs
 | 
				
			||||||
 | 
					import kotlin.test.assertTrue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author vlan
 | 
					 * @author vlan
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
class MapCommandTest : VimTestCase() {
 | 
					class MapCommandTest : VimTestCase() {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @AfterEach
 | 
				
			||||||
 | 
					  fun tearDown() {
 | 
				
			||||||
 | 
					    injector.keyGroup.removeKeyMapping(exceptionMappingOwner)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @TestWithoutNeovim(reason = SkipNeovimReason.UNCLEAR)
 | 
					  @TestWithoutNeovim(reason = SkipNeovimReason.UNCLEAR)
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  fun testMapKtoJ() {
 | 
					  fun testMapKtoJ() {
 | 
				
			||||||
@@ -876,13 +895,14 @@ n  ,f            <Plug>Foo
 | 
				
			|||||||
      indicateErrors = true,
 | 
					      indicateErrors = true,
 | 
				
			||||||
      null,
 | 
					      null,
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
    val exception = assertThrows<Throwable> {
 | 
					    val exception = assertThrowsLogError<TestLoggerAssertionError> {
 | 
				
			||||||
      typeText(injector.parser.parseKeys("t"))
 | 
					      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)
 | 
					    assertPluginError(true)
 | 
				
			||||||
    assertPluginErrorMessageContains("E121: Undefined variable: s:mapping")
 | 
					    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
 | 
					  // todo keyPresses invoked inside a script should have access to the script context
 | 
				
			||||||
@@ -923,11 +943,10 @@ n  ,f            <Plug>Foo
 | 
				
			|||||||
    """.trimIndent()
 | 
					    """.trimIndent()
 | 
				
			||||||
    configureByJavaText(text)
 | 
					    configureByJavaText(text)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    val exception = assertThrows<Throwable> {
 | 
					    assertThrowsLogError<ExException> {
 | 
				
			||||||
      typeText(commandToKeys("inoremap <expr> <cr> unknownFunction() ? '\\<C-y>' : '\\<C-g>u\\<CR>'"))
 | 
					      typeText(commandToKeys("inoremap <expr> <cr> unknownFunction() ? '\\<C-y>' : '\\<C-g>u\\<CR>'"))
 | 
				
			||||||
      typeText(injector.parser.parseKeys("i<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)
 | 
					    assertPluginError(true)
 | 
				
			||||||
    assertPluginErrorMessageContains("E117: Unknown function: unknownFunction")
 | 
					    assertPluginErrorMessageContains("E117: Unknown function: unknownFunction")
 | 
				
			||||||
@@ -1097,4 +1116,32 @@ n  ,i            <Action>(Back)
 | 
				
			|||||||
     Cras id tellus in ex imperdiet egestas. 
 | 
					     Cras id tellus in ex imperdiet egestas. 
 | 
				
			||||||
    """.trimIndent())
 | 
					    """.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
 | 
					package org.jetbrains.plugins.ideavim.extension
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.intellij.ide.plugins.PluginManagerCore
 | 
					import com.intellij.ide.plugins.PluginManagerCore
 | 
				
			||||||
 | 
					import com.intellij.openapi.Disposable
 | 
				
			||||||
import com.intellij.openapi.application.invokeLater
 | 
					import com.intellij.openapi.application.invokeLater
 | 
				
			||||||
 | 
					import com.intellij.openapi.util.Disposer
 | 
				
			||||||
import com.intellij.testFramework.PlatformTestUtil
 | 
					import com.intellij.testFramework.PlatformTestUtil
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext
 | 
				
			||||||
@@ -41,28 +43,23 @@ import kotlin.test.assertFalse
 | 
				
			|||||||
import kotlin.test.assertTrue
 | 
					import kotlin.test.assertTrue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class OpMappingTest : VimTestCase() {
 | 
					class OpMappingTest : VimTestCase() {
 | 
				
			||||||
  private var initialized = false
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private lateinit var extension: ExtensionBeanClass
 | 
					  private lateinit var extension: ExtensionBeanClass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private var disposable: Disposable = Disposer.newDisposable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @BeforeEach
 | 
					  @BeforeEach
 | 
				
			||||||
  override fun setUp(testInfo: TestInfo) {
 | 
					  override fun setUp(testInfo: TestInfo) {
 | 
				
			||||||
    super.setUp(testInfo)
 | 
					    super.setUp(testInfo)
 | 
				
			||||||
    if (!initialized) {
 | 
					 | 
				
			||||||
      initialized = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    extension = TestExtension.createBean()
 | 
					    extension = TestExtension.createBean()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
					    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
				
			||||||
    enableExtensions("TestExtension")
 | 
					    enableExtensions("TestExtension")
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @AfterEach
 | 
					  @AfterEach
 | 
				
			||||||
  override fun tearDown(testInfo: TestInfo) {
 | 
					  override fun tearDown(testInfo: TestInfo) {
 | 
				
			||||||
    @Suppress("DEPRECATION")
 | 
					    Disposer.dispose(disposable)
 | 
				
			||||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
					    super.tearDown(testInfo)
 | 
				
			||||||
    super.tearDown(super.testInfo)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
@@ -138,13 +135,13 @@ class OpMappingTest : VimTestCase() {
 | 
				
			|||||||
    typeText(injector.parser.parseKeys("Q"))
 | 
					    typeText(injector.parser.parseKeys("Q"))
 | 
				
			||||||
    assertState("I$c found it in a legendary land")
 | 
					    assertState("I$c found it in a legendary land")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    @Suppress("DEPRECATION")
 | 
					    Disposer.dispose(disposable)
 | 
				
			||||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
					    disposable = Disposer.newDisposable()
 | 
				
			||||||
    assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
 | 
					    assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
 | 
				
			||||||
    typeText(injector.parser.parseKeys("Q"))
 | 
					    typeText(injector.parser.parseKeys("Q"))
 | 
				
			||||||
    assertState("I$c found it in a legendary land")
 | 
					    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))
 | 
					    assertEmpty(VimPlugin.getKey().getKeyMappingByOwner(extension.instance.owner))
 | 
				
			||||||
    enableExtensions("TestExtension")
 | 
					    enableExtensions("TestExtension")
 | 
				
			||||||
    typeText(injector.parser.parseKeys("Q"))
 | 
					    typeText(injector.parser.parseKeys("Q"))
 | 
				
			||||||
@@ -158,12 +155,12 @@ class OpMappingTest : VimTestCase() {
 | 
				
			|||||||
    assertState("I$c found it in a legendary land")
 | 
					    assertState("I$c found it in a legendary land")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    enterCommand("set noTestExtension")
 | 
					    enterCommand("set noTestExtension")
 | 
				
			||||||
    @Suppress("DEPRECATION")
 | 
					    Disposer.dispose(disposable)
 | 
				
			||||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
					    disposable = Disposer.newDisposable()
 | 
				
			||||||
    typeText(injector.parser.parseKeys("Q"))
 | 
					    typeText(injector.parser.parseKeys("Q"))
 | 
				
			||||||
    assertState("I$c found it in a legendary land")
 | 
					    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")
 | 
					    enableExtensions("TestExtension")
 | 
				
			||||||
    typeText(injector.parser.parseKeys("Q"))
 | 
					    typeText(injector.parser.parseKeys("Q"))
 | 
				
			||||||
    assertState("I ${c}found it in a legendary land")
 | 
					    assertState("I ${c}found it in a legendary land")
 | 
				
			||||||
@@ -201,19 +198,20 @@ class PlugExtensionsTest : VimTestCase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private lateinit var extension: ExtensionBeanClass
 | 
					  private lateinit var extension: ExtensionBeanClass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private var disposable: Disposable = Disposer.newDisposable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @BeforeEach
 | 
					  @BeforeEach
 | 
				
			||||||
  override fun setUp(testInfo: TestInfo) {
 | 
					  override fun setUp(testInfo: TestInfo) {
 | 
				
			||||||
    super.setUp(testInfo)
 | 
					    super.setUp(testInfo)
 | 
				
			||||||
    configureByText("\n")
 | 
					    configureByText("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extension = TestExtension.createBean()
 | 
					    extension = TestExtension.createBean()
 | 
				
			||||||
    VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
					    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @AfterEach
 | 
					  @AfterEach
 | 
				
			||||||
  override fun tearDown(testInfo: TestInfo) {
 | 
					  override fun tearDown(testInfo: TestInfo) {
 | 
				
			||||||
    @Suppress("DEPRECATION")
 | 
					    Disposer.dispose(disposable)
 | 
				
			||||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
					 | 
				
			||||||
    super.tearDown(super.testInfo)
 | 
					    super.tearDown(super.testInfo)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -244,19 +242,20 @@ class PlugMissingKeysTest : VimTestCase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private lateinit var extension: ExtensionBeanClass
 | 
					  private lateinit var extension: ExtensionBeanClass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private var disposable: Disposable = Disposer.newDisposable()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @BeforeEach
 | 
					  @BeforeEach
 | 
				
			||||||
  override fun setUp(testInfo: TestInfo) {
 | 
					  override fun setUp(testInfo: TestInfo) {
 | 
				
			||||||
    super.setUp(testInfo)
 | 
					    super.setUp(testInfo)
 | 
				
			||||||
    configureByText("\n")
 | 
					    configureByText("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    extension = TestExtension.createBean()
 | 
					    extension = TestExtension.createBean()
 | 
				
			||||||
    VimExtension.EP_NAME.point.registerExtension(extension, VimPlugin.getInstance())
 | 
					    VimExtension.EP_NAME.point.registerExtension(extension, disposable)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @AfterEach
 | 
					  @AfterEach
 | 
				
			||||||
  override fun tearDown(testInfo: TestInfo) {
 | 
					  override fun tearDown(testInfo: TestInfo) {
 | 
				
			||||||
    @Suppress("DEPRECATION")
 | 
					    Disposer.dispose(disposable)
 | 
				
			||||||
    VimExtension.EP_NAME.point.unregisterExtension(extension)
 | 
					 | 
				
			||||||
    super.tearDown(super.testInfo)
 | 
					    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.ConstantNode
 | 
				
			||||||
import com.intellij.codeInsight.template.impl.TemplateManagerImpl
 | 
					import com.intellij.codeInsight.template.impl.TemplateManagerImpl
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.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.IdeaSelectionControl
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
					import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.VimBehaviorDiffers
 | 
					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.listener.VimListenerManager
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
import com.maddyhome.idea.vim.options.OptionConstants
 | 
					import com.maddyhome.idea.vim.options.OptionConstants
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.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.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestOptionConstants
 | 
					import org.jetbrains.plugins.ideavim.TestOptionConstants
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					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.impl.VimOption
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
					import org.jetbrains.plugins.ideavim.waitAndAssert
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.waitAndAssertMode
 | 
					import org.jetbrains.plugins.ideavim.waitAndAssertMode
 | 
				
			||||||
 | 
					import kotlin.test.assertNull
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@TraceOptions(TestOptionConstants.selectmode)
 | 
					@TraceOptions(TestOptionConstants.selectmode)
 | 
				
			||||||
class IdeaVisualControlTest : VimTestCase() {
 | 
					class IdeaVisualControlTest : VimTestCase() {
 | 
				
			||||||
@@ -764,6 +765,23 @@ class IdeaVisualControlTest : VimTestCase() {
 | 
				
			|||||||
    assertCaretsVisualAttributes()
 | 
					    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() {
 | 
					  private fun startDummyTemplate() {
 | 
				
			||||||
    TemplateManagerImpl.setTemplateTesting(fixture.testRootDisposable)
 | 
					    TemplateManagerImpl.setTemplateTesting(fixture.testRootDisposable)
 | 
				
			||||||
    val templateManager = TemplateManager.getInstance(fixture.project)
 | 
					    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.actionSystem.DataContext
 | 
				
			||||||
import com.intellij.openapi.application.ApplicationManager
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
 | 
					import com.intellij.openapi.editor.textarea.TextComponentEditorImpl
 | 
				
			||||||
 | 
					import com.intellij.openapi.util.Disposer
 | 
				
			||||||
import com.intellij.testFramework.junit5.TestDisposable
 | 
					import com.intellij.testFramework.junit5.TestDisposable
 | 
				
			||||||
import com.intellij.testFramework.replaceService
 | 
					import com.intellij.testFramework.replaceService
 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.VimTestCase
 | 
					import org.jetbrains.plugins.ideavim.VimTestCase
 | 
				
			||||||
 | 
					import org.junit.jupiter.api.AfterEach
 | 
				
			||||||
import org.mockito.Mockito
 | 
					import org.mockito.Mockito
 | 
				
			||||||
import javax.swing.JTextArea
 | 
					import javax.swing.JTextArea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,6 +31,11 @@ open class MockTestCase : VimTestCase() {
 | 
				
			|||||||
  val editorStub = TextComponentEditorImpl(null, JTextArea()).vim
 | 
					  val editorStub = TextComponentEditorImpl(null, JTextArea()).vim
 | 
				
			||||||
  val contextStub: ExecutionContext = DataContext.EMPTY_CONTEXT.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 {
 | 
					  fun <T : Any> mockService(service: Class<T>): T {
 | 
				
			||||||
    val mock = Mockito.mock(service)
 | 
					    val mock = Mockito.mock(service)
 | 
				
			||||||
    val applicationManager = ApplicationManager.getApplication()
 | 
					    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.ij
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
 | 
					import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.options.NumberOption
 | 
				
			||||||
import com.maddyhome.idea.vim.options.OptionAccessScope
 | 
					import com.maddyhome.idea.vim.options.OptionAccessScope
 | 
				
			||||||
import com.maddyhome.idea.vim.options.OptionDeclaredScope
 | 
					import com.maddyhome.idea.vim.options.OptionDeclaredScope
 | 
				
			||||||
import com.maddyhome.idea.vim.options.StringOption
 | 
					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 com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
					import org.jetbrains.plugins.ideavim.SkipNeovimReason
 | 
				
			||||||
import org.jetbrains.plugins.ideavim.TestWithoutNeovim
 | 
					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.Test
 | 
				
			||||||
import org.junit.jupiter.api.TestInfo
 | 
					import org.junit.jupiter.api.TestInfo
 | 
				
			||||||
import javax.swing.SwingConstants
 | 
					import javax.swing.SwingConstants
 | 
				
			||||||
import kotlin.test.assertContentEquals
 | 
					import kotlin.test.assertEquals
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private const val defaultValue = "defaultValue"
 | 
					private const val defaultValue = "defaultValue"
 | 
				
			||||||
 | 
					private const val defaultNumberValue = 10
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
 | 
					@TestWithoutNeovim(reason = SkipNeovimReason.OPTION)
 | 
				
			||||||
class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
					class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
				
			||||||
@@ -109,10 +112,17 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
				
			|||||||
    return option
 | 
					    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) {
 | 
					  private fun assertNotifiedEditors(vararg editors: Editor) {
 | 
				
			||||||
    val sortedExpected = editors.sortedBy { it.virtualFile!!.path }.toTypedArray()
 | 
					    val expected = editors.toSet()
 | 
				
			||||||
    val sortedActual = Listener.notifiedEditors.sortedBy { it.virtualFile!!.path }.toTypedArray()
 | 
					    val actual = Listener.notifiedEditors.toSet()
 | 
				
			||||||
    assertContentEquals(sortedExpected, sortedActual)
 | 
					    assertEquals(expected, actual)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private fun assertNoNotifications() = assertNotifiedEditors()
 | 
					  private fun assertNoNotifications() = assertNotifiedEditors()
 | 
				
			||||||
@@ -272,10 +282,23 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
				
			|||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  fun `test listener called for all editors when locally modified global-local local-to-buffer option changes at effective scope`() {
 | 
					  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)
 | 
					    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()
 | 
					    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)
 | 
					    assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -327,6 +350,19 @@ class EffectiveOptionChangeListenerTest : VimTestCase() {
 | 
				
			|||||||
    assertNotifiedEditors(originalEditor, splitWindow, otherBufferWindow)
 | 
					    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
 | 
					  @Test
 | 
				
			||||||
  fun `test listener not called for locally modified editor when global-local local-to-window option changes at global scope`() {
 | 
					  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)
 | 
					    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)))
 | 
					    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
 | 
					  // LOCAL_TO_WINDOW
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
@@ -173,6 +190,23 @@ class OptionAccessScopeTest: VimTestCase() {
 | 
				
			|||||||
    assertEquals(effectiveValue, injector.optionGroup.getOptionValue(option, OptionAccessScope.EFFECTIVE(fixture.editor.vim)))
 | 
					    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
 | 
					  // 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
 | 
					  // 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
 | 
					package ui
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.automation.remarks.junit.VideoRule
 | 
					import com.automation.remarks.junit5.Video
 | 
				
			||||||
import com.automation.remarks.video.annotations.Video
 | 
					 | 
				
			||||||
import com.intellij.remoterobot.RemoteRobot
 | 
					import com.intellij.remoterobot.RemoteRobot
 | 
				
			||||||
import com.intellij.remoterobot.fixtures.ContainerFixture
 | 
					import com.intellij.remoterobot.fixtures.ContainerFixture
 | 
				
			||||||
 | 
					import com.intellij.remoterobot.steps.CommonSteps
 | 
				
			||||||
import com.intellij.remoterobot.stepsProcessing.step
 | 
					import com.intellij.remoterobot.stepsProcessing.step
 | 
				
			||||||
import com.intellij.remoterobot.utils.keyboard
 | 
					import com.intellij.remoterobot.utils.keyboard
 | 
				
			||||||
import org.assertj.swing.core.MouseButton
 | 
					import org.assertj.swing.core.MouseButton
 | 
				
			||||||
import org.junit.Rule
 | 
					 | 
				
			||||||
import org.junit.jupiter.api.Test
 | 
					import org.junit.jupiter.api.Test
 | 
				
			||||||
import ui.pages.Editor
 | 
					import ui.pages.Editor
 | 
				
			||||||
import ui.pages.IdeaFrame
 | 
					import ui.pages.IdeaFrame
 | 
				
			||||||
@@ -25,6 +24,7 @@ import ui.pages.dialog
 | 
				
			|||||||
import ui.pages.editor
 | 
					import ui.pages.editor
 | 
				
			||||||
import ui.pages.gutter
 | 
					import ui.pages.gutter
 | 
				
			||||||
import ui.pages.idea
 | 
					import ui.pages.idea
 | 
				
			||||||
 | 
					import ui.pages.searchEverywhere
 | 
				
			||||||
import ui.pages.welcomeFrame
 | 
					import ui.pages.welcomeFrame
 | 
				
			||||||
import ui.utils.JavaExampleSteps
 | 
					import ui.utils.JavaExampleSteps
 | 
				
			||||||
import ui.utils.StepsLogger
 | 
					import ui.utils.StepsLogger
 | 
				
			||||||
@@ -38,6 +38,7 @@ import ui.utils.tripleClickOnRight
 | 
				
			|||||||
import ui.utils.uiTest
 | 
					import ui.utils.uiTest
 | 
				
			||||||
import ui.utils.vimExit
 | 
					import ui.utils.vimExit
 | 
				
			||||||
import java.awt.Point
 | 
					import java.awt.Point
 | 
				
			||||||
 | 
					import java.awt.event.KeyEvent
 | 
				
			||||||
import kotlin.test.assertEquals
 | 
					import kotlin.test.assertEquals
 | 
				
			||||||
import kotlin.test.assertFalse
 | 
					import kotlin.test.assertFalse
 | 
				
			||||||
import kotlin.test.assertTrue
 | 
					import kotlin.test.assertTrue
 | 
				
			||||||
@@ -47,14 +48,19 @@ class UiTests {
 | 
				
			|||||||
    StepsLogger.init()
 | 
					    StepsLogger.init()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Rule
 | 
					  private lateinit var commonSteps: CommonSteps
 | 
				
			||||||
  @JvmField
 | 
					
 | 
				
			||||||
  var videoRule = VideoRule()
 | 
					  private val testTextForEditor = """
 | 
				
			||||||
 | 
					                  |One Two
 | 
				
			||||||
 | 
					                  |Three Four
 | 
				
			||||||
 | 
					                  |Five
 | 
				
			||||||
 | 
					              """.trimMargin()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Test
 | 
					  @Test
 | 
				
			||||||
  @Video
 | 
					  @Video
 | 
				
			||||||
  fun ideaVimTest() = uiTest("ideaVimTest") {
 | 
					  fun ideaVimTest() = uiTest("ideaVimTest") {
 | 
				
			||||||
    val sharedSteps = JavaExampleSteps(this)
 | 
					    val sharedSteps = JavaExampleSteps(this)
 | 
				
			||||||
 | 
					    commonSteps = CommonSteps(this)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    startNewProject()
 | 
					    startNewProject()
 | 
				
			||||||
    Thread.sleep(1000)
 | 
					    Thread.sleep(1000)
 | 
				
			||||||
@@ -63,16 +69,11 @@ class UiTests {
 | 
				
			|||||||
    Thread.sleep(1000)
 | 
					    Thread.sleep(1000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    idea {
 | 
					    idea {
 | 
				
			||||||
 | 
					      waitSmartMode()
 | 
				
			||||||
      createFile("MyDoc.txt", this@uiTest)
 | 
					      createFile("MyDoc.txt", this@uiTest)
 | 
				
			||||||
      val editor = editor("MyDoc.txt") {
 | 
					      val editor = editor("MyDoc.txt") {
 | 
				
			||||||
        step("Write a text") {
 | 
					        step("Write a text") {
 | 
				
			||||||
          injectText(
 | 
					          injectText(testTextForEditor)
 | 
				
			||||||
            """
 | 
					 | 
				
			||||||
                |One Two
 | 
					 | 
				
			||||||
                |Three Four
 | 
					 | 
				
			||||||
                |Five
 | 
					 | 
				
			||||||
            """.trimMargin(),
 | 
					 | 
				
			||||||
          )
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      testSelectTextWithDelay(editor)
 | 
					      testSelectTextWithDelay(editor)
 | 
				
			||||||
@@ -89,6 +90,11 @@ class UiTests {
 | 
				
			|||||||
      testClickRightFromLineEnd(editor)
 | 
					      testClickRightFromLineEnd(editor)
 | 
				
			||||||
      testClickOnWord(editor)
 | 
					      testClickOnWord(editor)
 | 
				
			||||||
      testGutterClick(editor)
 | 
					      testGutterClick(editor)
 | 
				
			||||||
 | 
					      testAddNewLineInNormalMode(editor)
 | 
				
			||||||
 | 
					      testMappingToCtrlOrAltEnter(editor)
 | 
				
			||||||
 | 
					      `simple enter in insert mode`(editor)
 | 
				
			||||||
 | 
					      testMilticaretEnter(editor)
 | 
				
			||||||
 | 
					      `simple enter in select mode`(editor)
 | 
				
			||||||
      reenableIdeaVim(editor)
 | 
					      reenableIdeaVim(editor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      createFile("MyTest.java", this@uiTest)
 | 
					      createFile("MyTest.java", this@uiTest)
 | 
				
			||||||
@@ -96,7 +102,7 @@ class UiTests {
 | 
				
			|||||||
        step("Write a text") {
 | 
					        step("Write a text") {
 | 
				
			||||||
          injectText(
 | 
					          injectText(
 | 
				
			||||||
            """
 | 
					            """
 | 
				
			||||||
                |class Main {
 | 
					                |class MyTest {
 | 
				
			||||||
                |  public static void main() {
 | 
					                |  public static void main() {
 | 
				
			||||||
                |    System.out.println("Hello");
 | 
					                |    System.out.println("Hello");
 | 
				
			||||||
                |  }
 | 
					                |  }
 | 
				
			||||||
@@ -115,7 +121,6 @@ class UiTests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private fun closeUnrelated(sharedSteps: JavaExampleSteps) {
 | 
					  private fun closeUnrelated(sharedSteps: JavaExampleSteps) {
 | 
				
			||||||
    with(sharedSteps) {
 | 
					    with(sharedSteps) {
 | 
				
			||||||
      closeIdeaVimDialog()
 | 
					 | 
				
			||||||
      closeTipOfTheDay()
 | 
					      closeTipOfTheDay()
 | 
				
			||||||
      closeAllTabs()
 | 
					      closeAllTabs()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -126,7 +131,7 @@ class UiTests {
 | 
				
			|||||||
      createNewProjectLink.click()
 | 
					      createNewProjectLink.click()
 | 
				
			||||||
      dialog("New Project") {
 | 
					      dialog("New Project") {
 | 
				
			||||||
        findText("Java").click()
 | 
					        findText("Java").click()
 | 
				
			||||||
        checkBox("Add sample code").select()
 | 
					        checkBox("Add sample code").unselect()
 | 
				
			||||||
        button("Create").click()
 | 
					        button("Create").click()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -195,9 +200,11 @@ class UiTests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private fun IdeaFrame.testTrackActionId(editor: Editor) {
 | 
					  private fun IdeaFrame.testTrackActionId(editor: Editor) {
 | 
				
			||||||
    remoteRobot.invokeActionJs("GotoAction")
 | 
					    remoteRobot.invokeActionJs("GotoAction")
 | 
				
			||||||
    editor.keyboard {
 | 
					
 | 
				
			||||||
      enterText("IdeaVim: Track Action Ids")
 | 
					    val searchEverywhere = this@testTrackActionId.searchEverywhere()
 | 
				
			||||||
      enter()
 | 
					
 | 
				
			||||||
 | 
					    commonSteps.invokeAction("VimFindActionIdAction")
 | 
				
			||||||
 | 
					    keyboard {
 | 
				
			||||||
      escape()
 | 
					      escape()
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -211,7 +218,7 @@ class UiTests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    assertEquals(
 | 
					    assertEquals(
 | 
				
			||||||
      """
 | 
					      """
 | 
				
			||||||
                |EditorEscapeclass Main {
 | 
					                |EditorEscapeclass MyTest {
 | 
				
			||||||
                |  public static void main() {
 | 
					                |  public static void main() {
 | 
				
			||||||
                |      if (true) {
 | 
					                |      if (true) {
 | 
				
			||||||
                |          System.out.println("Hello");
 | 
					                |          System.out.println("Hello");
 | 
				
			||||||
@@ -231,7 +238,7 @@ class UiTests {
 | 
				
			|||||||
  private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) {
 | 
					  private fun IdeaFrame.createFile(fileName: String, remoteRobot: RemoteRobot) {
 | 
				
			||||||
    step("Create $fileName file") {
 | 
					    step("Create $fileName file") {
 | 
				
			||||||
      with(projectViewTree) {
 | 
					      with(projectViewTree) {
 | 
				
			||||||
        setExpandTimeout(15_000)
 | 
					        setExpandTimeout(30_000)
 | 
				
			||||||
        expand(projectName, "src")
 | 
					        expand(projectName, "src")
 | 
				
			||||||
        findText("src").click(MouseButton.RIGHT_BUTTON)
 | 
					        findText("src").click(MouseButton.RIGHT_BUTTON)
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -510,4 +517,187 @@ class UiTests {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    vimExit()
 | 
					    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
 | 
					  val caretOffset: Int
 | 
				
			||||||
    get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
 | 
					    get() = callJs("component.getEditor().getCaretModel().getOffset()", runInEdt = true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  val caretCount: Int
 | 
				
			||||||
 | 
					    get() = callJs("component.getEditor().getCaretModel().getCaretCount()", runInEdt = true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val isBlockCursor: Boolean
 | 
					  val isBlockCursor: Boolean
 | 
				
			||||||
//    get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
 | 
					//    get() = callJs("component.getEditor().getSettings().isBlockCursor()", true)
 | 
				
			||||||
    // Doesn't work at the moment because remote robot can't resolve classes from a plugin classloader
 | 
					    // 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 {
 | 
					  private fun isDumbMode(): Boolean {
 | 
				
			||||||
    return callJs("com.intellij.openapi. project.DumbService.isDumb(component.project);", true)
 | 
					    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
 | 
				
			||||||
import ui.pages.DialogFixture.Companion.byTitle
 | 
					import ui.pages.DialogFixture.Companion.byTitle
 | 
				
			||||||
import ui.pages.IdeaFrame
 | 
					import ui.pages.IdeaFrame
 | 
				
			||||||
import ui.pages.dialog
 | 
					 | 
				
			||||||
import ui.pages.idea
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
 | 
					class JavaExampleSteps(private val remoteRobot: RemoteRobot) {
 | 
				
			||||||
  @Suppress("unused")
 | 
					  @Suppress("unused")
 | 
				
			||||||
  private val keyboard: Keyboard = Keyboard(remoteRobot)
 | 
					  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") {
 | 
					  fun closeTipOfTheDay() = optionalStep("Close Tip of the Day if it appears") {
 | 
				
			||||||
    val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
 | 
					    val idea: IdeaFrame = remoteRobot.find(IdeaFrame::class.java)
 | 
				
			||||||
    idea.dumbAware {
 | 
					    idea.dumbAware {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -16,7 +16,7 @@ import com.maddyhome.idea.vim.command.Command
 | 
				
			|||||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
					import com.maddyhome.idea.vim.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
					import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@CommandOrMotion(keys = ["<C-R>"], modes = [Mode.NORMAL])
 | 
					@CommandOrMotion(keys = ["U", "<C-R>"], modes = [Mode.NORMAL, Mode.VISUAL])
 | 
				
			||||||
public class RedoAction : VimActionHandler.SingleExecution() {
 | 
					public class RedoAction : VimActionHandler.SingleExecution() {
 | 
				
			||||||
  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
					  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,7 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
				
			|||||||
import java.awt.event.KeyEvent
 | 
					import java.awt.event.KeyEvent
 | 
				
			||||||
import javax.swing.KeyStroke
 | 
					import javax.swing.KeyStroke
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL])
 | 
					@CommandOrMotion(keys = ["u", "<Undo>"], modes = [Mode.NORMAL, Mode.VISUAL])
 | 
				
			||||||
public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
 | 
					public class UndoAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
 | 
				
			||||||
  override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
 | 
					  override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
 | 
				
			||||||
    injector.parser.parseKeys("u"),
 | 
					    injector.parser.parseKeys("u"),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -28,7 +28,7 @@ import java.util.*
 | 
				
			|||||||
public class AutoIndentLinesVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
					public class AutoIndentLinesVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
				
			||||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
					  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(
 | 
					  override fun executeAction(
 | 
				
			||||||
    editor: VimEditor,
 | 
					    editor: VimEditor,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,29 +8,23 @@
 | 
				
			|||||||
package com.maddyhome.idea.vim.action.change.change
 | 
					package com.maddyhome.idea.vim.action.change.change
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
					import com.intellij.vim.annotations.CommandOrMotion
 | 
				
			||||||
import com.intellij.vim.annotations.Mode
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
					import com.maddyhome.idea.vim.api.VimCaret
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.command.Command
 | 
					import com.maddyhome.idea.vim.command.Command
 | 
				
			||||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
					import com.maddyhome.idea.vim.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
					import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
					import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.CharacterHelper
 | 
					import com.maddyhome.idea.vim.helper.CharacterHelper
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
					 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author vlan
 | 
					 * @author vlan
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
@CommandOrMotion(keys = ["u"], modes = [Mode.VISUAL])
 | 
					@CommandOrMotion(keys = [], modes = [])
 | 
				
			||||||
public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
					public class ChangeCaseLowerVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
				
			||||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
					  override val type: Command.Type = Command.Type.CHANGE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun executeAction(
 | 
					  override fun executeAction(
 | 
				
			||||||
    editor: VimEditor,
 | 
					    editor: VimEditor,
 | 
				
			||||||
    caret: VimCaret,
 | 
					    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.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.command.Command
 | 
					import com.maddyhome.idea.vim.command.Command
 | 
				
			||||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
					import com.maddyhome.idea.vim.command.OperatorArguments
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
					import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
				
			||||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
					import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.CharacterHelper
 | 
					import com.maddyhome.idea.vim.helper.CharacterHelper
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
					 | 
				
			||||||
import java.util.*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author vlan
 | 
					 * @author vlan
 | 
				
			||||||
@@ -29,8 +26,6 @@ import java.util.*
 | 
				
			|||||||
public class ChangeCaseToggleVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
					public class ChangeCaseToggleVisualAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
				
			||||||
  override val type: Command.Type = Command.Type.CHANGE
 | 
					  override val type: Command.Type = Command.Type.CHANGE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  override fun executeAction(
 | 
					  override fun executeAction(
 | 
				
			||||||
    editor: VimEditor,
 | 
					    editor: VimEditor,
 | 
				
			||||||
    caret: VimCaret,
 | 
					    caret: VimCaret,
 | 
				
			||||||
 
 | 
				
			|||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user