mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 01:40:12 +01:00 
			
		
		
		
	Compare commits
	
		
			225 Commits
		
	
	
		
			customized
			...
			customized
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						dd8bfac3cf
	
				 | 
					
					
						|||
| 
						
						
							
						
						fa434074d7
	
				 | 
					
					
						|||
| 
						
						
							
						
						b488295b59
	
				 | 
					
					
						|||
| 
						
						
							
						
						990ce13d3e
	
				 | 
					
					
						|||
| 
						
						
							
						
						b8cf11257c
	
				 | 
					
					
						|||
| 
						
						
							
						
						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 | ||
| 
						 | 
					9d5aa83786 | ||
| 
						 | 
					463164cb88 | ||
| 
						 | 
					4809742088 | ||
| 
						 | 
					9cf0e285b4 | ||
| 
						 | 
					a6ca6f1cf9 | ||
| 
						 | 
					bd7479e1c0 | ||
| 
						 | 
					b35b51c203 | ||
| 
						 | 
					5652774888 | ||
| 
						 | 
					836e9a2fbc | ||
| 
						 | 
					64538c255d | ||
| 
						 | 
					62a9293dcf | ||
| 
						 | 
					1faae92f33 | ||
| 
						 | 
					dee808752f | ||
| 
						 | 
					5590af6995 | ||
| 
						 | 
					5afd161fba | ||
| 
						 | 
					336efa1e8b | ||
| 
						 | 
					568d5ca4ff | ||
| 
						 | 
					a9991f2a50 | ||
| 
						 | 
					1c8096444a | ||
| 
						 | 
					f424de46e6 | ||
| 
						 | 
					8fcca05565 | ||
| 
						 | 
					ed1f3cec59 | ||
| 
						 | 
					c29a409f28 | ||
| 
						 | 
					1a46936ad6 | ||
| 
						 | 
					e82abfb948 | ||
| 
						 | 
					c3409be780 | ||
| 
						 | 
					1557ab3474 | ||
| 
						 | 
					75fdda4fbf | ||
| 
						 | 
					4d75ef2849 | ||
| 
						 | 
					a1da23d1ba | ||
| 
						 | 
					c4bc751df7 | ||
| 
						 | 
					972d89ec6e | ||
| 
						 | 
					70f040e104 | ||
| 
						 | 
					d4de0b49c8 | ||
| 
						 | 
					2a42d58361 | ||
| 
						 | 
					14308956d7 | ||
| 
						 | 
					71339a66d7 | ||
| 
						 | 
					85f0664b56 | ||
| 
						 | 
					2f86ac0dfa | ||
| 
						 | 
					79d7b7a08d | ||
| 
						 | 
					b550d1990e | ||
| 
						 | 
					22062f0c77 | ||
| 
						 | 
					515f613a53 | ||
| 
						 | 
					615ed6b713 | ||
| 
						 | 
					f6eab62c3c | ||
| 
						 | 
					7d1e00ff0d | ||
| 
						 | 
					692439953c | ||
| 
						 | 
					6960a34d02 | ||
| 
						 | 
					b3662d4e6e | ||
| 
						 | 
					50c9b7c352 | ||
| 
						 | 
					f395d3b2bf | ||
| 
						 | 
					4fbf6cbc50 | ||
| 
						 | 
					9916958d6c | ||
| 
						 | 
					184a069c7f | ||
| 
						 | 
					0b65346633 | ||
| 
						 | 
					11f23dcc9e | ||
| 
						 | 
					f80d1defcb | ||
| 
						 | 
					e95d6343cb | ||
| 
						 | 
					a9052a068f | ||
| 
						 | 
					b1323c0d67 | ||
| 
						 | 
					87ceb8fb58 | 
							
								
								
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitattributes
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					* text=auto eol=lf
 | 
				
			||||||
							
								
								
									
										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>
 | 
				
			||||||
							
								
								
									
										16
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							@@ -5,13 +5,13 @@ object Constants {
 | 
				
			|||||||
  const val EAP_CHANNEL = "eap"
 | 
					  const val EAP_CHANNEL = "eap"
 | 
				
			||||||
  const val DEV_CHANNEL = "Dev"
 | 
					  const val DEV_CHANNEL = "Dev"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const val GITHUB_TESTS = "2023.1.2"
 | 
					  const val GITHUB_TESTS = "2023.3.2"
 | 
				
			||||||
  const val NVIM_TESTS = "2023.1.2"
 | 
					  const val NVIM_TESTS = "2023.3.2"
 | 
				
			||||||
  const val PROPERTY_TESTS = "2023.1.2"
 | 
					  const val PROPERTY_TESTS = "2023.3.2"
 | 
				
			||||||
  const val LONG_RUNNING_TESTS = "2023.1.2"
 | 
					  const val LONG_RUNNING_TESTS = "2023.3.2"
 | 
				
			||||||
  const val QODANA_TESTS = "2023.1.2"
 | 
					  const val QODANA_TESTS = "2023.3.2"
 | 
				
			||||||
  const val RELEASE = "2023.1.2"
 | 
					  const val RELEASE = "2023.3.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const val RELEASE_DEV = "2023.1.2"
 | 
					  const val RELEASE_DEV = "2023.3.2"
 | 
				
			||||||
  const val RELEASE_EAP = "2023.1.2"
 | 
					  const val RELEASE_EAP = "2023.3.2"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										8
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										8
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							@@ -23,9 +23,8 @@ object Project : Project({
 | 
				
			|||||||
  vcsRoot(GitHubPullRequest)
 | 
					  vcsRoot(GitHubPullRequest)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Active tests
 | 
					  // Active tests
 | 
				
			||||||
  buildType(TestingBuildType("2023.2", "<default>", version = "2023.2.3"))
 | 
					 | 
				
			||||||
  buildType(TestingBuildType("2023.1", "<default>", version = "2023.1.5"))
 | 
					 | 
				
			||||||
  buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
					  buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
				
			||||||
 | 
					  buildType(TestingBuildType("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)
 | 
				
			||||||
@@ -40,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 {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										2
									
								
								.teamcity/_Self/subprojects/OldTests.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.teamcity/_Self/subprojects/OldTests.kt
									
									
									
									
										vendored
									
									
								
							@@ -20,4 +20,6 @@ object OldTests : Project({
 | 
				
			|||||||
  buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
 | 
					  buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
 | 
				
			||||||
  buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
 | 
					  buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
 | 
				
			||||||
  buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
 | 
					  buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
 | 
				
			||||||
 | 
					  buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
 | 
				
			||||||
 | 
					  buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								CHANGES.md
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								CHANGES.md
									
									
									
									
									
								
							@@ -31,6 +31,26 @@ usual beta standards.
 | 
				
			|||||||
* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
 | 
					* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
 | 
				
			||||||
* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
 | 
					* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
 | 
				
			||||||
* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
 | 
					* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
 | 
				
			||||||
 | 
					* [VIM-3195](https://youtrack.jetbrains.com/issue/VIM-3195) Fix escape in injected editor
 | 
				
			||||||
 | 
					* [VIM-3190](https://youtrack.jetbrains.com/issue/VIM-3190) Do not use octopus handler if the enter key is used with modifiers like shift or control
 | 
				
			||||||
 | 
					* [VIM-3203](https://youtrack.jetbrains.com/issue/VIM-3203) Split action not works in normal mode
 | 
				
			||||||
 | 
					* [VIM-3184](https://youtrack.jetbrains.com/issue/VIM-3184) Revert "VIM-3184: Temporally disable new handlers for the thin client"
 | 
				
			||||||
 | 
					* [VIM-3186](https://youtrack.jetbrains.com/issue/VIM-3186) Do not multiply the enter action by the amount of carets
 | 
				
			||||||
 | 
					* [VIM-3177](https://youtrack.jetbrains.com/issue/VIM-3177) Formatting of commit message works again
 | 
				
			||||||
 | 
					* [VIM-1611](https://youtrack.jetbrains.com/issue/VIM-1611) actions related to resolving conflicts doesn't seem to work
 | 
				
			||||||
 | 
					* [VIM-3204](https://youtrack.jetbrains.com/issue/VIM-3204) Add checker that verifies the configuratin of the keymap
 | 
				
			||||||
 | 
					* [VIM-3084](https://youtrack.jetbrains.com/issue/VIM-3084) Double update for the status bar icon
 | 
				
			||||||
 | 
					* [VIM-3176](https://youtrack.jetbrains.com/issue/VIM-3176) Reselecting visual selection after pasting above it select wrong lines
 | 
				
			||||||
 | 
					* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
 | 
				
			||||||
 | 
					* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
 | 
				
			||||||
 | 
					* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### Merged PRs:
 | 
				
			||||||
 | 
					* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
 | 
				
			||||||
 | 
					* [772](https://github.com/JetBrains/ideavim/pull/772) by [chylex](https://github.com/chylex): Prevent code completion popup from appearing after running a macro
 | 
				
			||||||
 | 
					* [787](https://github.com/JetBrains/ideavim/pull/787) by [Leonid Danilov](https://github.com/Infonautica): Added "Which-Key" to Plugins
 | 
				
			||||||
 | 
					* [778](https://github.com/JetBrains/ideavim/pull/778) by [lippfi](https://github.com/lippfi): Showmode
 | 
				
			||||||
 | 
					* [788](https://github.com/JetBrains/ideavim/pull/788) by [Matt Ellis](https://github.com/citizenmatt): Refactor VimOptionGroupBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 2.7.0, 2023-11-07
 | 
					## 2.7.0, 2023-11-07
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -324,7 +324,7 @@ IdeaVim tips and tricks
 | 
				
			|||||||
- Use the power of IJ and Vim:
 | 
					- Use the power of IJ and Vim:
 | 
				
			||||||
    - `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
 | 
					    - `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
 | 
				
			||||||
    - Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
 | 
					    - Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
 | 
				
			||||||
    - Sync IJ bookmarks and Vim marks: `set ideamarks`
 | 
					    - Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
 | 
				
			||||||
    - Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
 | 
					    - Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
 | 
					- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,6 +11,8 @@ plugins {
 | 
				
			|||||||
  kotlin("plugin.serialization") version "1.8.21"
 | 
					  kotlin("plugin.serialization") version "1.8.21"
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					val kotlinxSerializationVersion: String by project
 | 
				
			||||||
 | 
					
 | 
				
			||||||
group = "com.intellij"
 | 
					group = "com.intellij"
 | 
				
			||||||
version = "SNAPSHOT"
 | 
					version = "SNAPSHOT"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -19,6 +21,10 @@ repositories {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.20-1.0.14")
 | 
					  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16")
 | 
				
			||||||
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
 | 
					  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
 | 
				
			||||||
 | 
					    // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
 | 
				
			||||||
 | 
					    exclude("org.jetbrains.kotlin", "kotlin-stdlib")
 | 
				
			||||||
 | 
					    exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -44,7 +44,7 @@ enum class Mode(val abbrev: Char) {
 | 
				
			|||||||
  OP_PENDING('O'),
 | 
					  OP_PENDING('O'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Indicates this key mapping applies to Insert mode
 | 
					   * Indicates this key mapping applies to Insert or Replace modes
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  INSERT('I'),
 | 
					  INSERT('I'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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.5")
 | 
					        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
 | 
				
			||||||
@@ -116,7 +116,7 @@ repositories {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
    compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
 | 
					    compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
 | 
				
			||||||
    compileOnly("org.jetbrains:annotations:24.0.1")
 | 
					    compileOnly("org.jetbrains:annotations:24.1.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
 | 
					    // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
 | 
				
			||||||
    testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
 | 
					    testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
 | 
				
			||||||
@@ -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")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -141,7 +141,7 @@ dependencies {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    testApi("com.squareup.okhttp3:okhttp:4.12.0")
 | 
					    testApi("com.squareup.okhttp3:okhttp:4.12.0")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.0")
 | 
					    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
 | 
				
			||||||
    testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
 | 
					    testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
 | 
				
			||||||
    testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
 | 
					    testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -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("231.7515.13")
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Get the latest available change notes from the changelog file
 | 
					        // Get the latest available change notes from the changelog file
 | 
				
			||||||
        changeNotes.set(
 | 
					        changeNotes.set(
 | 
				
			||||||
            provider {
 | 
					            provider {
 | 
				
			||||||
@@ -524,10 +531,12 @@ tasks.register("releaseActions") {
 | 
				
			|||||||
        if (tickets.isNotEmpty()) {
 | 
					        if (tickets.isNotEmpty()) {
 | 
				
			||||||
            println("Updating statuses for tickets: $tickets")
 | 
					            println("Updating statuses for tickets: $tickets")
 | 
				
			||||||
            setYoutrackStatus(tickets, "Fixed")
 | 
					            setYoutrackStatus(tickets, "Fixed")
 | 
				
			||||||
            if (getVersionIdByName(version.toString()) != null) {
 | 
					            println("Checking if version $version exists...")
 | 
				
			||||||
 | 
					            val versionId = getVersionIdByName(version.toString())
 | 
				
			||||||
 | 
					            if (versionId == null) {
 | 
				
			||||||
                addReleaseToYoutrack(version.toString())
 | 
					                addReleaseToYoutrack(version.toString())
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
                println("Version $version is already exists in YouTrack")
 | 
					                println("Version $version already exists in YouTrack. Version id: $versionId")
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
            setYoutrackFixVersion(tickets, version.toString())
 | 
					            setYoutrackFixVersion(tickets, version.toString())
 | 
				
			||||||
        } else {
 | 
					        } else {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,11 @@ Put `set ideajoin` to your `~/.ideavimrc` to enable this functionality.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
 | 
					Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					:warning: This feature is language-specific. This means that the IDE should implement this feature for a particular
 | 
				
			||||||
 | 
					language in order for the IDE to work as described below. If any of the examples provided below don't match your case, 
 | 
				
			||||||
 | 
					please file an issue in the project related to your IDE: https://youtrack.jetbrains.com/.  
 | 
				
			||||||
 | 
					Here is a list of known requests: https://youtrack.jetbrains.com/issues?q=links:VIM-3214.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
* Automatic join concatenated lines:
 | 
					* Automatic join concatenated lines:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,20 +8,26 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
# suppress inspection "UnusedProperty" for whole file
 | 
					# suppress inspection "UnusedProperty" for whole file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ideaVersion=2023.1.2
 | 
					ideaVersion=2023.3.2
 | 
				
			||||||
downloadIdeaSources=true
 | 
					downloadIdeaSources=true
 | 
				
			||||||
instrumentPluginCode=true
 | 
					instrumentPluginCode=true
 | 
				
			||||||
version=SNAPSHOT
 | 
					version=chylex-26
 | 
				
			||||||
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
 | 
				
			||||||
kotlinVersion=1.8.21
 | 
					kotlinVersion=1.8.21
 | 
				
			||||||
publishToken=token
 | 
					publishToken=token
 | 
				
			||||||
publishChannels=eap
 | 
					publishChannels=eap
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
 | 
				
			||||||
 | 
					#   we exclude this version from the dependency and use our own version of kotlin that is specified above
 | 
				
			||||||
 | 
					kotlinxSerializationVersion=1.5.1
 | 
				
			||||||
 | 
					
 | 
				
			||||||
slackUrl=
 | 
					slackUrl=
 | 
				
			||||||
youtrackToken=
 | 
					youtrackToken=
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,17 +20,17 @@ repositories {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
dependencies {
 | 
					dependencies {
 | 
				
			||||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.20")
 | 
					  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.5")
 | 
					  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")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
 | 
				
			|||||||
  println("HI!")
 | 
					  println("HI!")
 | 
				
			||||||
  val projectDir = args[0]
 | 
					  val projectDir = args[0]
 | 
				
			||||||
  println("Working directory: $projectDir")
 | 
					  println("Working directory: $projectDir")
 | 
				
			||||||
  val (lastVersion, objectId) = getVersion(projectDir, onlyStable = true)
 | 
					  val (lastVersion, objectId) = getVersion(projectDir, ReleaseType.STABLE_NO_PATCH)
 | 
				
			||||||
  println("Last version: $lastVersion, hash: ${objectId.name}")
 | 
					  println("Last version: $lastVersion, hash: ${objectId.name}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val branch = withRepo(projectDir) { it.branch }
 | 
					  val branch = withRepo(projectDir) { it.branch }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
 | 
				
			|||||||
  println("HI!")
 | 
					  println("HI!")
 | 
				
			||||||
  val projectDir = args[0]
 | 
					  val projectDir = args[0]
 | 
				
			||||||
  println("Working directory: $projectDir")
 | 
					  println("Working directory: $projectDir")
 | 
				
			||||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = false)
 | 
					  val (lastVersion, _) = getVersion(projectDir, ReleaseType.ANY)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
 | 
					  val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
 | 
				
			||||||
    lastVersion.nextMinor().withSuffix("eap.1").value
 | 
					    lastVersion.nextMinor().withSuffix("eap.1").value
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,7 @@ fun main(args: Array<String>) {
 | 
				
			|||||||
  val releaseType = args[1]
 | 
					  val releaseType = args[1]
 | 
				
			||||||
  println("Working directory: $projectDir")
 | 
					  println("Working directory: $projectDir")
 | 
				
			||||||
  println("Release type: $releaseType")
 | 
					  println("Release type: $releaseType")
 | 
				
			||||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = true)
 | 
					  val (lastVersion, _) = getVersion(projectDir, ReleaseType.ONLY_STABLE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val nextVersion = when (releaseType) {
 | 
					  val nextVersion = when (releaseType) {
 | 
				
			||||||
    "major" -> lastVersion.nextMajor()
 | 
					    "major" -> lastVersion.nextMajor()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,13 @@ internal fun checkBranch(rootDir: String, releaseType: String) {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, ObjectId> {
 | 
					enum class ReleaseType {
 | 
				
			||||||
 | 
					  ANY,
 | 
				
			||||||
 | 
					  ONLY_STABLE,
 | 
				
			||||||
 | 
					  STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
 | 
				
			||||||
  val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
 | 
					  val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
 | 
				
			||||||
  val git = Git(repository)
 | 
					  val git = Git(repository)
 | 
				
			||||||
  println(git.log().call().first())
 | 
					  println(git.log().call().first())
 | 
				
			||||||
@@ -75,10 +81,10 @@ internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, O
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
    .sortedBy { it.first }
 | 
					    .sortedBy { it.first }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  val version = if (onlyStable) {
 | 
					  val version = when (releaseType) {
 | 
				
			||||||
    versions.last { it.first.isStable }
 | 
					    ReleaseType.ANY -> versions.last()
 | 
				
			||||||
  } else {
 | 
					    ReleaseType.ONLY_STABLE -> versions.last { it.first.isStable }
 | 
				
			||||||
    versions.last()
 | 
					    ReleaseType.STABLE_NO_PATCH -> versions.last { it.first.isStable && it.first.patch == 0 }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return version
 | 
					  return version
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -11,7 +11,7 @@ package com.maddyhome.idea.vim
 | 
				
			|||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
					import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
				
			||||||
import com.intellij.openapi.project.Project
 | 
					import com.intellij.openapi.project.Project
 | 
				
			||||||
import com.intellij.openapi.project.ProjectManagerListener
 | 
					import com.intellij.openapi.project.ProjectManagerListener
 | 
				
			||||||
import com.intellij.openapi.startup.StartupActivity
 | 
					import com.intellij.openapi.startup.ProjectActivity
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.localEditors
 | 
					import com.maddyhome.idea.vim.helper.localEditors
 | 
				
			||||||
@@ -20,16 +20,11 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
				
			|||||||
/**
 | 
					/**
 | 
				
			||||||
 * @author Alex Plate
 | 
					 * @author Alex Plate
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
// This service should be migrated to ProjectActivity. But we should cariful because simple replacement
 | 
					internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
 | 
				
			||||||
// leads to deadlock in tests. I'm not sure about the exact reasons, but "invokeAndWait" inside "initialize" function
 | 
					 | 
				
			||||||
// causes this deadlock. Good new: it's easy reproducible in tests.
 | 
					 | 
				
			||||||
// Previous migration: fc7efd5484a13b40ba9bf86a1d5429e215d973f3
 | 
					 | 
				
			||||||
// Revert: 24dd84b31cffb99eb6114524859a46d02717d33f
 | 
					 | 
				
			||||||
internal class PluginStartup : StartupActivity.DumbAware/*, LightEditCompatible*/ {
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private var firstInitializationOccurred = false
 | 
					  private var firstInitializationOccurred = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun runActivity(project: Project) {
 | 
					  override suspend fun execute(project: Project) {
 | 
				
			||||||
    if (firstInitializationOccurred) return
 | 
					    if (firstInitializationOccurred) return
 | 
				
			||||||
    firstInitializationOccurred = true
 | 
					    firstInitializationOccurred = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,7 +236,13 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
				
			|||||||
      getInstance().turnOnPlugin();
 | 
					      getInstance().turnOnPlugin();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    StatusBarIconFactory.Companion.updateIcon();
 | 
					    if (enabled) {
 | 
				
			||||||
 | 
					      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    StatusBarIconFactory.Util.INSTANCE.updateIcon();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public static String getMessage() {
 | 
					  public static String getMessage() {
 | 
				
			||||||
@@ -264,7 +274,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
				
			|||||||
    if (enabled) {
 | 
					    if (enabled) {
 | 
				
			||||||
      Application application = ApplicationManager.getApplication();
 | 
					      Application application = ApplicationManager.getApplication();
 | 
				
			||||||
      if (application.isUnitTestMode()) {
 | 
					      if (application.isUnitTestMode()) {
 | 
				
			||||||
        application.invokeAndWait(this::turnOnPlugin);
 | 
					        turnOnPlugin();
 | 
				
			||||||
 | 
					        //application.invokeAndWait(this::turnOnPlugin);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      else {
 | 
					      else {
 | 
				
			||||||
        application.invokeLater(this::turnOnPlugin);
 | 
					        application.invokeLater(this::turnOnPlugin);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,74 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Use of this source code is governed by an MIT-style
 | 
					 | 
				
			||||||
 * license that can be found in the LICENSE.txt file or at
 | 
					 | 
				
			||||||
 * https://opensource.org/licenses/MIT.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
package com.maddyhome.idea.vim.action
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.intellij.codeInsight.hint.HintManagerImpl
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.ActionManager
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.AnAction
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.PerformWithDocumentsCommitted
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.PopupAction
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.impl.ActionConfigurationCustomizer
 | 
					 | 
				
			||||||
import com.intellij.openapi.editor.Editor
 | 
					 | 
				
			||||||
import com.intellij.openapi.editor.EditorMouseHoverPopupManager
 | 
					 | 
				
			||||||
import com.intellij.openapi.editor.event.EditorMouseEvent
 | 
					 | 
				
			||||||
import com.intellij.openapi.editor.event.EditorMouseEventArea
 | 
					 | 
				
			||||||
import com.intellij.openapi.project.DumbAware
 | 
					 | 
				
			||||||
import java.awt.event.MouseEvent
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// [VERSION UPDATE] 233+ Remove class
 | 
					 | 
				
			||||||
// The ShowHoverInfo action is built into the platform (using a nicer EditorMouseHoverPopupManager API)
 | 
					 | 
				
			||||||
public class VimActionConfigurationCustomizer : ActionConfigurationCustomizer {
 | 
					 | 
				
			||||||
  public override fun customize(actionManager: ActionManager) {
 | 
					 | 
				
			||||||
    // If the ShowHoverInfo action doesn't exist in the platform, add our own implementation
 | 
					 | 
				
			||||||
    if (actionManager.getAction("ShowHoverInfo") == null) {
 | 
					 | 
				
			||||||
      actionManager.registerAction("ShowHoverInfo", VimShowHoverInfoAction())
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private class VimShowHoverInfoAction : AnAction(), HintManagerImpl.ActionToIgnore, PopupAction, DumbAware,
 | 
					 | 
				
			||||||
    PerformWithDocumentsCommitted {
 | 
					 | 
				
			||||||
    override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun update(e: AnActionEvent) {
 | 
					 | 
				
			||||||
      val dataContext = e.dataContext
 | 
					 | 
				
			||||||
      val editor = CommonDataKeys.EDITOR.getData(dataContext)
 | 
					 | 
				
			||||||
      if (editor == null) {
 | 
					 | 
				
			||||||
        e.presentation.isEnabledAndVisible = false
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    override fun actionPerformed(e: AnActionEvent) {
 | 
					 | 
				
			||||||
      val editor = CommonDataKeys.EDITOR.getData(e.dataContext) ?: return
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      val editorMouseEvent = createFakeEditorMouseEvent(editor)
 | 
					 | 
				
			||||||
      EditorMouseHoverPopupManager.getInstance().showInfoTooltip(editorMouseEvent)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    private fun createFakeEditorMouseEvent(editor: Editor): EditorMouseEvent {
 | 
					 | 
				
			||||||
      val xy = editor.offsetToXY(editor.caretModel.offset)
 | 
					 | 
				
			||||||
      val mouseEvent =
 | 
					 | 
				
			||||||
        MouseEvent(editor.component, MouseEvent.MOUSE_MOVED, System.currentTimeMillis(), 0, xy.x, xy.y, 0, false)
 | 
					 | 
				
			||||||
      val editorMouseEvent = EditorMouseEvent(
 | 
					 | 
				
			||||||
        editor,
 | 
					 | 
				
			||||||
        mouseEvent,
 | 
					 | 
				
			||||||
        EditorMouseEventArea.EDITING_AREA,
 | 
					 | 
				
			||||||
        editor.caretModel.offset,
 | 
					 | 
				
			||||||
        editor.caretModel.logicalPosition,
 | 
					 | 
				
			||||||
        editor.caretModel.visualPosition,
 | 
					 | 
				
			||||||
        true,
 | 
					 | 
				
			||||||
        null,
 | 
					 | 
				
			||||||
        null,
 | 
					 | 
				
			||||||
        null
 | 
					 | 
				
			||||||
      )
 | 
					 | 
				
			||||||
      return editorMouseEvent
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@@ -54,9 +54,17 @@ 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 = injector.globalOptions().ideatracetime
 | 
					  private val traceTime: Boolean
 | 
				
			||||||
 | 
					    get() {
 | 
				
			||||||
 | 
					      // Make sure the injector is initialized
 | 
				
			||||||
 | 
					      VimPlugin.getInstance()
 | 
				
			||||||
 | 
					      return injector.globalOptions().ideatracetime
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun actionPerformed(e: AnActionEvent) {
 | 
					  override fun actionPerformed(e: AnActionEvent) {
 | 
				
			||||||
    LOG.trace("Executing shortcut key action")
 | 
					    LOG.trace("Executing shortcut key action")
 | 
				
			||||||
@@ -90,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
 | 
				
			||||||
@@ -105,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)) {
 | 
				
			||||||
@@ -224,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
 | 
				
			||||||
@@ -234,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
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -269,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,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
 | 
				
			|||||||
    get() {
 | 
					    get() {
 | 
				
			||||||
      val myMode = machine.mode
 | 
					      val myMode = machine.mode
 | 
				
			||||||
      return when (myMode) {
 | 
					      return when (myMode) {
 | 
				
			||||||
        com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
					        is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
				
			||||||
        com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
 | 
					        com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
 | 
				
			||||||
        is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
					        is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
				
			||||||
        is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
					        is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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,8 +309,10 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private val LOG = logger<VimSurroundExtension>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  companion object {
 | 
					 | 
				
			||||||
private const val REGISTER = '"'
 | 
					private const val REGISTER = '"'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
					private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
				
			||||||
@@ -341,22 +372,24 @@ internal class VimSurroundExtension : VimExtension {
 | 
				
			|||||||
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
 | 
				
			||||||
@@ -82,14 +78,12 @@ import java.math.BigInteger
 | 
				
			|||||||
import java.util.*
 | 
					import java.util.*
 | 
				
			||||||
import java.util.function.Consumer
 | 
					import java.util.function.Consumer
 | 
				
			||||||
import kotlin.math.max
 | 
					import kotlin.math.max
 | 
				
			||||||
import kotlin.math.min
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Provides all the insert/replace related functionality
 | 
					 * Provides all the insert/replace related functionality
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
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 +97,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
 | 
				
			||||||
@@ -404,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
				
			|||||||
    context: ExecutionContext,
 | 
					    context: ExecutionContext,
 | 
				
			||||||
    range: TextRange,
 | 
					    range: TextRange,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
 | 
					    val startPos = editor.offsetToBufferPosition(caret.offset.point)
 | 
				
			||||||
    val startOffset = editor.getLineStartForOffset(range.startOffset)
 | 
					    val startOffset = editor.getLineStartForOffset(range.startOffset)
 | 
				
			||||||
    val endOffset = editor.getLineEndForOffset(range.endOffset)
 | 
					    val endOffset = editor.getLineEndForOffset(range.endOffset)
 | 
				
			||||||
    val ijEditor = (editor as IjVimEditor).editor
 | 
					    val ijEditor = (editor as IjVimEditor).editor
 | 
				
			||||||
@@ -428,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    val afterAction = {
 | 
					    val afterAction = {
 | 
				
			||||||
      val firstLine = editor.offsetToBufferPosition(
 | 
					      caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
 | 
				
			||||||
        min(startOffset.toDouble(), endOffset.toDouble()).toInt()
 | 
					 | 
				
			||||||
      ).line
 | 
					 | 
				
			||||||
      val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
 | 
					 | 
				
			||||||
      caret.moveToOffset(newOffset)
 | 
					 | 
				
			||||||
      restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
 | 
					      restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (project != null) {
 | 
					    if (project != null) {
 | 
				
			||||||
@@ -645,25 +632,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>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -275,8 +275,8 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
 | 
				
			|||||||
  private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
 | 
					  private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
 | 
				
			||||||
    for (KeyStroke key : keys) {
 | 
					    for (KeyStroke key : keys) {
 | 
				
			||||||
      if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
 | 
					      if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
 | 
				
			||||||
          key.getKeyCode() != KeyEvent.VK_ESCAPE &&
 | 
					          !(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
 | 
				
			||||||
          key.getKeyCode() != KeyEvent.VK_ENTER) {
 | 
					          !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
 | 
				
			||||||
        getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
 | 
					        getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					package com.maddyhome.idea.vim.group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.codeInsight.daemon.ReferenceImporter
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.DataContext
 | 
				
			||||||
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
 | 
					import com.intellij.openapi.application.ReadAction
 | 
				
			||||||
 | 
					import com.intellij.openapi.command.WriteCommandAction
 | 
				
			||||||
 | 
					import com.intellij.openapi.editor.Editor
 | 
				
			||||||
 | 
					import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.ProgressIndicator
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.ProgressManager
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.Task
 | 
				
			||||||
 | 
					import com.intellij.psi.PsiDocumentManager
 | 
				
			||||||
 | 
					import com.intellij.psi.PsiElement
 | 
				
			||||||
 | 
					import com.intellij.psi.PsiRecursiveElementWalkingVisitor
 | 
				
			||||||
 | 
					import java.util.function.BooleanSupplier
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal object MacroAutoImport {
 | 
				
			||||||
 | 
					  fun run(editor: Editor, dataContext: DataContext) {
 | 
				
			||||||
 | 
					    val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
 | 
				
			||||||
 | 
					    val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val importers = ReferenceImporter.EP_NAME.extensionList
 | 
				
			||||||
 | 
					    if (importers.isEmpty()) {
 | 
				
			||||||
 | 
					      return
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
 | 
				
			||||||
 | 
					      override fun run(indicator: ProgressIndicator) {
 | 
				
			||||||
 | 
					        val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
 | 
				
			||||||
 | 
					          val fixes = mutableListOf<BooleanSupplier>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          file.accept(object : PsiRecursiveElementWalkingVisitor() {
 | 
				
			||||||
 | 
					            override fun visitElement(element: PsiElement) {
 | 
				
			||||||
 | 
					              for (reference in element.references) {
 | 
				
			||||||
 | 
					                if (reference.resolve() != null) {
 | 
				
			||||||
 | 
					                  continue
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					                for (importer in importers) {
 | 
				
			||||||
 | 
					                  importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
 | 
				
			||||||
 | 
					                    ?.let(fixes::add)
 | 
				
			||||||
 | 
					                }
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					              super.visitElement(element)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return@nonBlocking fixes
 | 
				
			||||||
 | 
					        }.executeSynchronously()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        ApplicationManager.getApplication().invokeAndWait {
 | 
				
			||||||
 | 
					          WriteCommandAction.writeCommandAction(project)
 | 
				
			||||||
 | 
					            .withName("Auto Import")
 | 
				
			||||||
 | 
					            .withGroupId("IdeaVimAutoImportAfterMacro")
 | 
				
			||||||
 | 
					            .shouldRecordActionForActiveDocument(true)
 | 
				
			||||||
 | 
					            .run<RuntimeException> {
 | 
				
			||||||
 | 
					              fixes.forEach { it.asBoolean }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -7,6 +7,8 @@
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
package com.maddyhome.idea.vim.group
 | 
					package com.maddyhome.idea.vim.group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.codeInsight.completion.CompletionPhase
 | 
				
			||||||
 | 
					import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
 | 
				
			||||||
import com.intellij.openapi.application.ApplicationManager
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
import com.intellij.openapi.diagnostic.logger
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
import com.intellij.openapi.progress.ProcessCanceledException
 | 
					import com.intellij.openapi.progress.ProcessCanceledException
 | 
				
			||||||
@@ -19,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
				
			|||||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
					import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
				
			||||||
import com.maddyhome.idea.vim.macro.VimMacroBase
 | 
					import com.maddyhome.idea.vim.macro.VimMacroBase
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
					import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Used to handle playback of macros
 | 
					 * Used to handle playback of macros
 | 
				
			||||||
@@ -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) {
 | 
				
			||||||
@@ -472,3 +473,4 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,8 +21,11 @@ import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
				
			|||||||
import com.intellij.openapi.actionSystem.AnAction
 | 
					import com.intellij.openapi.actionSystem.AnAction
 | 
				
			||||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
					import com.intellij.openapi.actionSystem.AnActionEvent
 | 
				
			||||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
					import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
				
			||||||
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
import com.intellij.openapi.ide.CopyPasteManager
 | 
					import com.intellij.openapi.ide.CopyPasteManager
 | 
				
			||||||
import com.intellij.openapi.keymap.KeymapUtil
 | 
					import com.intellij.openapi.keymap.KeymapUtil
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.impl.ui.KeymapPanel
 | 
				
			||||||
import com.intellij.openapi.options.ShowSettingsUtil
 | 
					import com.intellij.openapi.options.ShowSettingsUtil
 | 
				
			||||||
import com.intellij.openapi.project.DumbAwareAction
 | 
					import com.intellij.openapi.project.DumbAwareAction
 | 
				
			||||||
import com.intellij.openapi.project.Project
 | 
					import com.intellij.openapi.project.Project
 | 
				
			||||||
@@ -32,6 +35,7 @@ import com.maddyhome.idea.vim.VimPlugin
 | 
				
			|||||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
					import com.maddyhome.idea.vim.api.globalOptions
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.handler.KeyMapIssue
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
					import com.maddyhome.idea.vim.helper.MessageHelper
 | 
				
			||||||
import com.maddyhome.idea.vim.key.ShortcutOwner
 | 
					import com.maddyhome.idea.vim.key.ShortcutOwner
 | 
				
			||||||
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
 | 
					import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
 | 
				
			||||||
@@ -180,6 +184,77 @@ internal class NotificationService(private val project: Project?) {
 | 
				
			|||||||
    ActionIdNotifier.notifyActionId(id, project)
 | 
					    ActionIdNotifier.notifyActionId(id, project)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
 | 
				
			||||||
 | 
					    val keymapManager = KeymapManagerEx.getInstanceEx()
 | 
				
			||||||
 | 
					    val keymap = keymapManager.activeKeymap
 | 
				
			||||||
 | 
					    val message = buildString {
 | 
				
			||||||
 | 
					      appendLine("Current IDE keymap (${keymap.name}) has issues:<br/>")
 | 
				
			||||||
 | 
					      issues.forEach {
 | 
				
			||||||
 | 
					        when (it) {
 | 
				
			||||||
 | 
					          is KeyMapIssue.AddShortcut -> {
 | 
				
			||||||
 | 
					            appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					          is KeyMapIssue.RemoveShortcut -> {
 | 
				
			||||||
 | 
					            appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    val notification = IDEAVIM_STICKY_GROUP.createNotification(
 | 
				
			||||||
 | 
					      IDEAVIM_NOTIFICATION_TITLE,
 | 
				
			||||||
 | 
					      message,
 | 
				
			||||||
 | 
					      NotificationType.ERROR,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    notification.subtitle = "IDE keymap misconfigured"
 | 
				
			||||||
 | 
					    notification.addAction(object : DumbAwareAction("Fix Keymap") {
 | 
				
			||||||
 | 
					      override fun actionPerformed(e: AnActionEvent) {
 | 
				
			||||||
 | 
					        issues.forEach {
 | 
				
			||||||
 | 
					          when (it) {
 | 
				
			||||||
 | 
					            is KeyMapIssue.AddShortcut -> {
 | 
				
			||||||
 | 
					              keymap.addShortcut(it.actionId, KeyboardShortcut(it.keyStroke, null))
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            is KeyMapIssue.RemoveShortcut -> {
 | 
				
			||||||
 | 
					              keymap.removeShortcut(it.actionId, it.shortcut)
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        LOG.info("Shortcuts updated $issues")
 | 
				
			||||||
 | 
					        notification.expire()
 | 
				
			||||||
 | 
					        requiredShortcutsAssigned()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
 | 
				
			||||||
 | 
					      override fun actionPerformed(e: AnActionEvent) {
 | 
				
			||||||
 | 
					        ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
 | 
				
			||||||
 | 
					        notification.hideBalloon()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    notification.addAction(object : DumbAwareAction("Ignore") {
 | 
				
			||||||
 | 
					      override fun actionPerformed(e: AnActionEvent) {
 | 
				
			||||||
 | 
					        LOG.info("Ignored to update shortcuts $issues")
 | 
				
			||||||
 | 
					        notification.hideBalloon()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    notification.notify(project)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun requiredShortcutsAssigned() {
 | 
				
			||||||
 | 
					    val notification = Notification(
 | 
				
			||||||
 | 
					      IDEAVIM_NOTIFICATION_ID,
 | 
				
			||||||
 | 
					      IDEAVIM_NOTIFICATION_TITLE,
 | 
				
			||||||
 | 
					      "Keymap fixed",
 | 
				
			||||||
 | 
					      NotificationType.INFORMATION,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
 | 
				
			||||||
 | 
					      override fun actionPerformed(e: AnActionEvent) {
 | 
				
			||||||
 | 
					        ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
 | 
				
			||||||
 | 
					        notification.hideBalloon()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    notification.notify(project)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  object ActionIdNotifier {
 | 
					  object ActionIdNotifier {
 | 
				
			||||||
    private var notification: Notification? = null
 | 
					    private var notification: Notification? = null
 | 
				
			||||||
    private const val NO_ID = "<i>Cannot detect action id</i>"
 | 
					    private const val NO_ID = "<i>Cannot detect action id</i>"
 | 
				
			||||||
@@ -314,6 +389,8 @@ internal class NotificationService(private val project: Project?) {
 | 
				
			|||||||
    const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
 | 
					    const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
 | 
				
			||||||
    const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
 | 
					    const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private val LOG = logger<NotificationService>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private fun createIdeaVimRcManually(message: String, project: Project?) {
 | 
					    private fun createIdeaVimRcManually(message: String, project: Project?) {
 | 
				
			||||||
      val notification =
 | 
					      val notification =
 | 
				
			||||||
        Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
 | 
					        Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,285 +0,0 @@
 | 
				
			|||||||
/*
 | 
					 | 
				
			||||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
					 | 
				
			||||||
 *
 | 
					 | 
				
			||||||
 * Use of this source code is governed by an MIT-style
 | 
					 | 
				
			||||||
 * license that can be found in the LICENSE.txt file or at
 | 
					 | 
				
			||||||
 * https://opensource.org/licenses/MIT.
 | 
					 | 
				
			||||||
 */
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
package com.maddyhome.idea.vim.group;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import com.intellij.execution.ExecutionException;
 | 
					 | 
				
			||||||
import com.intellij.execution.configurations.GeneralCommandLine;
 | 
					 | 
				
			||||||
import com.intellij.execution.process.CapturingProcessHandler;
 | 
					 | 
				
			||||||
import com.intellij.execution.process.ProcessAdapter;
 | 
					 | 
				
			||||||
import com.intellij.execution.process.ProcessEvent;
 | 
					 | 
				
			||||||
import com.intellij.execution.process.ProcessOutput;
 | 
					 | 
				
			||||||
import com.intellij.openapi.actionSystem.DataContext;
 | 
					 | 
				
			||||||
import com.intellij.openapi.diagnostic.Logger;
 | 
					 | 
				
			||||||
import com.intellij.openapi.editor.Editor;
 | 
					 | 
				
			||||||
import com.intellij.openapi.progress.ProcessCanceledException;
 | 
					 | 
				
			||||||
import com.intellij.openapi.progress.ProgressIndicator;
 | 
					 | 
				
			||||||
import com.intellij.openapi.progress.ProgressIndicatorProvider;
 | 
					 | 
				
			||||||
import com.intellij.openapi.progress.ProgressManager;
 | 
					 | 
				
			||||||
import com.intellij.util.execution.ParametersListUtil;
 | 
					 | 
				
			||||||
import com.intellij.util.text.CharSequenceReader;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.KeyHandler;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimInjectorKt;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimProcessGroupBase;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.command.Command;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.ex.ExException;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.ex.InvalidCommandException;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.helper.UiHelper;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext;
 | 
					 | 
				
			||||||
import org.jetbrains.annotations.NotNull;
 | 
					 | 
				
			||||||
import org.jetbrains.annotations.Nullable;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import javax.swing.*;
 | 
					 | 
				
			||||||
import java.io.*;
 | 
					 | 
				
			||||||
import java.util.ArrayList;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.globalOptions;
 | 
					 | 
				
			||||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
public class ProcessGroup extends VimProcessGroupBase {
 | 
					 | 
				
			||||||
  public String getLastCommand() {
 | 
					 | 
				
			||||||
    return lastCommand;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Override
 | 
					 | 
				
			||||||
  public void startSearchCommand(@NotNull VimEditor editor, ExecutionContext context, int count, char leader) {
 | 
					 | 
				
			||||||
    if (((IjVimEditor)editor).getEditor().isOneLineMode()) // Don't allow searching in one line editors
 | 
					 | 
				
			||||||
    {
 | 
					 | 
				
			||||||
      return;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    String initText = "";
 | 
					 | 
				
			||||||
    String label = String.valueOf(leader);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ExEntryPanel panel = ExEntryPanel.getInstance();
 | 
					 | 
				
			||||||
    panel.activate(((IjVimEditor)editor).getEditor(), ((DataContext)context.getContext()), label, initText, count);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Override
 | 
					 | 
				
			||||||
  public @NotNull String endSearchCommand() {
 | 
					 | 
				
			||||||
    ExEntryPanel panel = ExEntryPanel.getInstance();
 | 
					 | 
				
			||||||
    panel.deactivate(true);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return panel.getText();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public void startExCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
 | 
					 | 
				
			||||||
    // Don't allow ex commands in one line editors
 | 
					 | 
				
			||||||
    if (editor.isOneLineMode()) return;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    String initText = getRange(((IjVimEditor) editor).getEditor(), cmd);
 | 
					 | 
				
			||||||
    injector.getMarkService().setVisualSelectionMarks(editor);
 | 
					 | 
				
			||||||
    VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
 | 
					 | 
				
			||||||
    ExEntryPanel panel = ExEntryPanel.getInstance();
 | 
					 | 
				
			||||||
    panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Override
 | 
					 | 
				
			||||||
  public boolean processExKey(@NotNull VimEditor editor, @NotNull KeyStroke stroke) {
 | 
					 | 
				
			||||||
    // This will only get called if somehow the key focus ended up in the editor while the ex entry window
 | 
					 | 
				
			||||||
    // is open. So I'll put focus back in the editor and process the key.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    ExEntryPanel panel = ExEntryPanel.getInstance();
 | 
					 | 
				
			||||||
    if (panel.isActive()) {
 | 
					 | 
				
			||||||
      UiHelper.requestFocus(panel.getEntry());
 | 
					 | 
				
			||||||
      panel.handleKey(stroke);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return true;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else {
 | 
					 | 
				
			||||||
      VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
 | 
					 | 
				
			||||||
      KeyHandler.getInstance().reset(editor);
 | 
					 | 
				
			||||||
      return false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public boolean processExEntry(final @NotNull VimEditor editor, final @NotNull ExecutionContext context) {
 | 
					 | 
				
			||||||
    ExEntryPanel panel = ExEntryPanel.getInstance();
 | 
					 | 
				
			||||||
    panel.deactivate(true);
 | 
					 | 
				
			||||||
    boolean res = true;
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      logger.debug("processing command");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      final String text = panel.getText();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (!panel.getLabel().equals(":")) {
 | 
					 | 
				
			||||||
        // Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
 | 
					 | 
				
			||||||
        // <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
 | 
					 | 
				
			||||||
        // We should never be invoked for anything other than an actual ex command.
 | 
					 | 
				
			||||||
        throw new InvalidCommandException("Expected ':' command. Got '" + panel.getLabel() + "'", text);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (logger.isDebugEnabled()) logger.debug("swing=" + SwingUtilities.isEventDispatchThread());
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      VimInjectorKt.getInjector().getVimscriptExecutor().execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext.INSTANCE);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    catch (ExException e) {
 | 
					 | 
				
			||||||
      VimPlugin.showMessage(e.getMessage());
 | 
					 | 
				
			||||||
      VimPlugin.indicateError();
 | 
					 | 
				
			||||||
      res = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    catch (Exception bad) {
 | 
					 | 
				
			||||||
      ProcessGroup.logger.error(bad);
 | 
					 | 
				
			||||||
      VimPlugin.indicateError();
 | 
					 | 
				
			||||||
      res = false;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return res;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // commands executed from map command / macro should not be added to history
 | 
					 | 
				
			||||||
  private boolean skipHistory(VimEditor editor) {
 | 
					 | 
				
			||||||
    return VimStateMachine.Companion.getInstance(editor).getMappingState().isExecutingMap() || injector.getMacro().isExecutingMacro();
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public void cancelExEntry(final @NotNull VimEditor editor, boolean resetCaret) {
 | 
					 | 
				
			||||||
    VimStateMachine.Companion.getInstance(editor).setMode(new Mode.NORMAL());
 | 
					 | 
				
			||||||
    KeyHandler.getInstance().reset(editor);
 | 
					 | 
				
			||||||
    ExEntryPanel panel = ExEntryPanel.getInstance();
 | 
					 | 
				
			||||||
    panel.deactivate(true, resetCaret);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @Override
 | 
					 | 
				
			||||||
  public void startFilterCommand(@NotNull VimEditor editor, ExecutionContext context, @NotNull Command cmd) {
 | 
					 | 
				
			||||||
    String initText = getRange(((IjVimEditor) editor).getEditor(), cmd) + "!";
 | 
					 | 
				
			||||||
    VimStateMachine.Companion.getInstance(editor).setMode(Mode.CMD_LINE.INSTANCE);
 | 
					 | 
				
			||||||
    ExEntryPanel panel = ExEntryPanel.getInstance();
 | 
					 | 
				
			||||||
    panel.activate(((IjVimEditor) editor).getEditor(), ((IjEditorExecutionContext) context).getContext(), ":", initText, 1);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private @NotNull String getRange(Editor editor, @NotNull Command cmd) {
 | 
					 | 
				
			||||||
    String initText = "";
 | 
					 | 
				
			||||||
    if (VimStateMachine.Companion.getInstance(new IjVimEditor(editor)).getMode() instanceof Mode.VISUAL) {
 | 
					 | 
				
			||||||
      initText = "'<,'>";
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    else if (cmd.getRawCount() > 0) {
 | 
					 | 
				
			||||||
      if (cmd.getCount() == 1) {
 | 
					 | 
				
			||||||
        initText = ".";
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      else {
 | 
					 | 
				
			||||||
        initText = ".,.+" + (cmd.getCount() - 1);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return initText;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  public @Nullable String executeCommand(@NotNull VimEditor editor, @NotNull String command, @Nullable CharSequence input, @Nullable String currentDirectoryPath)
 | 
					 | 
				
			||||||
    throws ExecutionException, ProcessCanceledException {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    // This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
 | 
					 | 
				
			||||||
    // redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
 | 
					 | 
				
			||||||
    // not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
 | 
					 | 
				
			||||||
    // better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
 | 
					 | 
				
			||||||
    // Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
 | 
					 | 
				
			||||||
    return ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> {
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      final String shell = globalOptions(injector).getShell();
 | 
					 | 
				
			||||||
      final String shellcmdflag = globalOptions(injector).getShellcmdflag();
 | 
					 | 
				
			||||||
      final String shellxescape = globalOptions(injector).getShellxescape();
 | 
					 | 
				
			||||||
      final String shellxquote = globalOptions(injector).getShellxquote();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // For Win32. See :help 'shellxescape'
 | 
					 | 
				
			||||||
      final String escapedCommand = shellxquote.equals("(")
 | 
					 | 
				
			||||||
                                    ? doEscape(command, shellxescape, "^")
 | 
					 | 
				
			||||||
                                    : command;
 | 
					 | 
				
			||||||
      // Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
 | 
					 | 
				
			||||||
      final String quotedCommand = shellxquote.equals("(")
 | 
					 | 
				
			||||||
                                   ? "(" + escapedCommand + ")"
 | 
					 | 
				
			||||||
                                   : (shellxquote.equals("\"(")
 | 
					 | 
				
			||||||
                                      ? "\"(" + escapedCommand + ")\""
 | 
					 | 
				
			||||||
                                      : shellxquote + escapedCommand + shellxquote);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      final ArrayList<String> commands = new ArrayList<>();
 | 
					 | 
				
			||||||
      commands.add(shell);
 | 
					 | 
				
			||||||
      if (!shellcmdflag.isEmpty()) {
 | 
					 | 
				
			||||||
        // Note that Vim also does a simple whitespace split for multiple parameters
 | 
					 | 
				
			||||||
        commands.addAll(ParametersListUtil.parse(shellcmdflag));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      commands.add(quotedCommand);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (logger.isDebugEnabled()) {
 | 
					 | 
				
			||||||
        logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand));
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      final GeneralCommandLine commandLine = new GeneralCommandLine(commands);
 | 
					 | 
				
			||||||
      if (currentDirectoryPath != null) {
 | 
					 | 
				
			||||||
        commandLine.setWorkDirectory(currentDirectoryPath);
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
      final CapturingProcessHandler handler = new CapturingProcessHandler(commandLine);
 | 
					 | 
				
			||||||
      if (input != null) {
 | 
					 | 
				
			||||||
        handler.addProcessListener(new ProcessAdapter() {
 | 
					 | 
				
			||||||
          @Override
 | 
					 | 
				
			||||||
          public void startNotified(@NotNull ProcessEvent event) {
 | 
					 | 
				
			||||||
            try {
 | 
					 | 
				
			||||||
              final CharSequenceReader charSequenceReader = new CharSequenceReader(input);
 | 
					 | 
				
			||||||
              final BufferedWriter outputStreamWriter = new BufferedWriter(new OutputStreamWriter(handler.getProcessInput()));
 | 
					 | 
				
			||||||
              copy(charSequenceReader, outputStreamWriter);
 | 
					 | 
				
			||||||
              outputStreamWriter.close();
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
            catch (IOException e) {
 | 
					 | 
				
			||||||
              logger.error(e);
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getInstance().getProgressIndicator();
 | 
					 | 
				
			||||||
      final ProcessOutput output = handler.runProcessWithProgressIndicator(progressIndicator);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      lastCommand = command;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      if (output.isCancelled()) {
 | 
					 | 
				
			||||||
        // TODO: Vim will use whatever text has already been written to stdout
 | 
					 | 
				
			||||||
        // For whatever reason, we're not getting any here, so just throw an exception
 | 
					 | 
				
			||||||
        throw new ProcessCanceledException();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      final Integer exitCode = handler.getExitCode();
 | 
					 | 
				
			||||||
      if (exitCode != null && exitCode != 0) {
 | 
					 | 
				
			||||||
        VimPlugin.showMessage("shell returned " + exitCode);
 | 
					 | 
				
			||||||
        VimPlugin.indicateError();
 | 
					 | 
				
			||||||
      }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      // Get stderr; stdout and strip colors, which are not handles properly.
 | 
					 | 
				
			||||||
      return (output.getStderr() + output.getStdout()).replaceAll("\u001B\\[[;\\d]*m", "");
 | 
					 | 
				
			||||||
    }, "IdeaVim - !" + command, true, ((IjVimEditor) editor).getEditor().getProject());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private String doEscape(String original, String charsToEscape, String escapeChar) {
 | 
					 | 
				
			||||||
    String result = original;
 | 
					 | 
				
			||||||
    for (char c : charsToEscape.toCharArray()) {
 | 
					 | 
				
			||||||
      result = result.replace("" + c, escapeChar + c);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return result;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  // TODO: Java 10 has a transferTo method we could use instead
 | 
					 | 
				
			||||||
  private void copy(@NotNull Reader from, @NotNull Writer to) throws IOException {
 | 
					 | 
				
			||||||
    char[] buf = new char[2048];
 | 
					 | 
				
			||||||
    int cnt;
 | 
					 | 
				
			||||||
    while ((cnt = from.read(buf)) != -1) {
 | 
					 | 
				
			||||||
      to.write(buf, 0, cnt);
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private String lastCommand;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private static final Logger logger = Logger.getInstance(ProcessGroup.class.getName());
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
							
								
								
									
										281
									
								
								src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								src/main/java/com/maddyhome/idea/vim/group/ProcessGroup.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,281 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2003-2023 The IdeaVim authors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Use of this source code is governed by an MIT-style
 | 
				
			||||||
 | 
					 * license that can be found in the LICENSE.txt file or at
 | 
				
			||||||
 | 
					 * https://opensource.org/licenses/MIT.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					package com.maddyhome.idea.vim.group
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.execution.ExecutionException
 | 
				
			||||||
 | 
					import com.intellij.execution.configurations.GeneralCommandLine
 | 
				
			||||||
 | 
					import com.intellij.execution.process.CapturingProcessHandler
 | 
				
			||||||
 | 
					import com.intellij.execution.process.ProcessAdapter
 | 
				
			||||||
 | 
					import com.intellij.execution.process.ProcessEvent
 | 
				
			||||||
 | 
					import com.intellij.openapi.diagnostic.debug
 | 
				
			||||||
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.ProcessCanceledException
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.ProgressIndicatorProvider
 | 
				
			||||||
 | 
					import com.intellij.openapi.progress.ProgressManager
 | 
				
			||||||
 | 
					import com.intellij.util.execution.ParametersListUtil
 | 
				
			||||||
 | 
					import com.intellij.util.text.CharSequenceReader
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.KeyHandler.Companion.getInstance
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.VimProcessGroupBase
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.globalOptions
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.command.Command
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.ex.ExException
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.ex.InvalidCommandException
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.helper.requestFocus
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.VimStateMachine.Companion.getInstance
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode.NORMAL
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.ReturnableFromCmd
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.mode
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
 | 
				
			||||||
 | 
					import java.io.BufferedWriter
 | 
				
			||||||
 | 
					import java.io.IOException
 | 
				
			||||||
 | 
					import java.io.OutputStreamWriter
 | 
				
			||||||
 | 
					import java.io.Reader
 | 
				
			||||||
 | 
					import java.io.Writer
 | 
				
			||||||
 | 
					import javax.swing.KeyStroke
 | 
				
			||||||
 | 
					import javax.swing.SwingUtilities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					public class ProcessGroup : VimProcessGroupBase() {
 | 
				
			||||||
 | 
					  override var lastCommand: String? = null
 | 
				
			||||||
 | 
					    private set
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override fun startSearchCommand(editor: VimEditor, context: ExecutionContext, count: Int, leader: Char) {
 | 
				
			||||||
 | 
					    // Don't allow searching in one line editors
 | 
				
			||||||
 | 
					    if (editor.isOneLineMode()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val initText = ""
 | 
				
			||||||
 | 
					    val label = leader.toString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val panel = ExEntryPanel.getInstance()
 | 
				
			||||||
 | 
					    panel.activate(editor.ij, context.ij, label, initText, count)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override fun endSearchCommand(): String {
 | 
				
			||||||
 | 
					    val panel = ExEntryPanel.getInstance()
 | 
				
			||||||
 | 
					    panel.deactivate(true)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return panel.text
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override fun startExCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
 | 
				
			||||||
 | 
					    // Don't allow ex commands in one line editors
 | 
				
			||||||
 | 
					    if (editor.isOneLineMode()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val currentMode = editor.vimStateMachine.mode
 | 
				
			||||||
 | 
					    check(currentMode is ReturnableFromCmd) {
 | 
				
			||||||
 | 
					      "Cannot enable cmd mode from current mode $currentMode"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val initText = getRange(editor, cmd)
 | 
				
			||||||
 | 
					    injector.markService.setVisualSelectionMarks(editor)
 | 
				
			||||||
 | 
					    editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
 | 
				
			||||||
 | 
					    val panel = ExEntryPanel.getInstance()
 | 
				
			||||||
 | 
					    panel.activate(editor.ij, context.ij, ":", initText, 1)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override fun processExKey(editor: VimEditor, stroke: KeyStroke): Boolean {
 | 
				
			||||||
 | 
					    // This will only get called if somehow the key focus ended up in the editor while the ex entry window
 | 
				
			||||||
 | 
					    // is open. So I'll put focus back in the editor and process the key.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val panel = ExEntryPanel.getInstance()
 | 
				
			||||||
 | 
					    if (panel.isActive) {
 | 
				
			||||||
 | 
					      requestFocus(panel.entry)
 | 
				
			||||||
 | 
					      panel.handleKey(stroke)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return true
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      getInstance(editor).mode = NORMAL()
 | 
				
			||||||
 | 
					      getInstance().reset(editor)
 | 
				
			||||||
 | 
					      return false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override fun processExEntry(editor: VimEditor, context: ExecutionContext): Boolean {
 | 
				
			||||||
 | 
					    val panel = ExEntryPanel.getInstance()
 | 
				
			||||||
 | 
					    panel.deactivate(true)
 | 
				
			||||||
 | 
					    var res = true
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      getInstance(editor).mode = NORMAL()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      logger.debug("processing command")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      val text = panel.text
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (panel.label != ":") {
 | 
				
			||||||
 | 
					        // Search is handled via Argument.Type.EX_STRING. Although ProcessExEntryAction is registered as the handler for
 | 
				
			||||||
 | 
					        // <CR> in both command and search modes, it's only invoked for command mode (see KeyHandler.handleCommandNode).
 | 
				
			||||||
 | 
					        // We should never be invoked for anything other than an actual ex command.
 | 
				
			||||||
 | 
					        throw InvalidCommandException("Expected ':' command. Got '" + panel.label + "'", text)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      logger.debug {
 | 
				
			||||||
 | 
					        "swing=" + SwingUtilities.isEventDispatchThread()
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      injector.vimscriptExecutor.execute(text, editor, context, skipHistory(editor), true, CommandLineVimLContext)
 | 
				
			||||||
 | 
					    } catch (e: ExException) {
 | 
				
			||||||
 | 
					      VimPlugin.showMessage(e.message)
 | 
				
			||||||
 | 
					      VimPlugin.indicateError()
 | 
				
			||||||
 | 
					      res = false
 | 
				
			||||||
 | 
					    } catch (bad: Exception) {
 | 
				
			||||||
 | 
					      logger.error(bad)
 | 
				
			||||||
 | 
					      VimPlugin.indicateError()
 | 
				
			||||||
 | 
					      res = false
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // commands executed from map command / macro should not be added to history
 | 
				
			||||||
 | 
					  private fun skipHistory(editor: VimEditor): Boolean {
 | 
				
			||||||
 | 
					    return getInstance(editor).mappingState.isExecutingMap() || injector.macro.isExecutingMacro
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
 | 
				
			||||||
 | 
					    editor.vimStateMachine.mode = NORMAL()
 | 
				
			||||||
 | 
					    getInstance().reset(editor)
 | 
				
			||||||
 | 
					    val panel = ExEntryPanel.getInstance()
 | 
				
			||||||
 | 
					    panel.deactivate(true, resetCaret)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public override fun startFilterCommand(editor: VimEditor, context: ExecutionContext, cmd: Command) {
 | 
				
			||||||
 | 
					    val initText = getRange(editor, cmd) + "!"
 | 
				
			||||||
 | 
					    val currentMode = editor.mode
 | 
				
			||||||
 | 
					    check(currentMode is ReturnableFromCmd) { "Cannot enable cmd mode from $currentMode" }
 | 
				
			||||||
 | 
					    editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
 | 
				
			||||||
 | 
					    val panel = ExEntryPanel.getInstance()
 | 
				
			||||||
 | 
					    panel.activate(editor.ij, context.ij, ":", initText, 1)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun getRange(editor: VimEditor, cmd: Command): String {
 | 
				
			||||||
 | 
					    var initText = ""
 | 
				
			||||||
 | 
					    if (editor.vimStateMachine.mode is VISUAL) {
 | 
				
			||||||
 | 
					      initText = "'<,'>"
 | 
				
			||||||
 | 
					    } else if (cmd.rawCount > 0) {
 | 
				
			||||||
 | 
					      initText = if (cmd.count == 1) {
 | 
				
			||||||
 | 
					        "."
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        ".,.+" + (cmd.count - 1)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return initText
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Throws(ExecutionException::class, ProcessCanceledException::class)
 | 
				
			||||||
 | 
					  public override fun executeCommand(
 | 
				
			||||||
 | 
					    editor: VimEditor,
 | 
				
			||||||
 | 
					    command: String,
 | 
				
			||||||
 | 
					    input: CharSequence?,
 | 
				
			||||||
 | 
					    currentDirectoryPath: String?
 | 
				
			||||||
 | 
					  ): String? {
 | 
				
			||||||
 | 
					    // This is a much simplified version of how Vim does this. We're using stdin/stdout directly, while Vim will
 | 
				
			||||||
 | 
					    // redirect to temp files ('shellredir' and 'shelltemp') or use pipes. We don't support 'shellquote', because we're
 | 
				
			||||||
 | 
					    // not handling redirection, but we do use 'shellxquote' and 'shellxescape', because these have defaults that work
 | 
				
			||||||
 | 
					    // better with Windows. We also don't bother using ShellExecute for Windows commands beginning with `start`.
 | 
				
			||||||
 | 
					    // Finally, we're also not bothering with the crazy space and backslash handling of the 'shell' options content.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return ProgressManager.getInstance().runProcessWithProgressSynchronously<String, ExecutionException>(
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        val shell = injector.globalOptions().shell
 | 
				
			||||||
 | 
					        val shellcmdflag = injector.globalOptions().shellcmdflag
 | 
				
			||||||
 | 
					        val shellxescape = injector.globalOptions().shellxescape
 | 
				
			||||||
 | 
					        val shellxquote = injector.globalOptions().shellxquote
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // For Win32. See :help 'shellxescape'
 | 
				
			||||||
 | 
					        val escapedCommand = if (shellxquote == "(") doEscape(command, shellxescape, "^")
 | 
				
			||||||
 | 
					        else command
 | 
				
			||||||
 | 
					        // Required for Win32+cmd.exe, defaults to "(". See :help 'shellxquote'
 | 
				
			||||||
 | 
					        val quotedCommand = if (shellxquote == "(") "($escapedCommand)"
 | 
				
			||||||
 | 
					        else (if (shellxquote == "\"(") "\"($escapedCommand)\""
 | 
				
			||||||
 | 
					        else shellxquote + escapedCommand + shellxquote)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val commands = ArrayList<String>()
 | 
				
			||||||
 | 
					        commands.add(shell)
 | 
				
			||||||
 | 
					        if (shellcmdflag.isNotEmpty()) {
 | 
				
			||||||
 | 
					          // Note that Vim also does a simple whitespace split for multiple parameters
 | 
				
			||||||
 | 
					          commands.addAll(ParametersListUtil.parse(shellcmdflag))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        commands.add(quotedCommand)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (logger.isDebugEnabled) {
 | 
				
			||||||
 | 
					          logger.debug(String.format("shell=%s shellcmdflag=%s command=%s", shell, shellcmdflag, quotedCommand))
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val commandLine = GeneralCommandLine(commands)
 | 
				
			||||||
 | 
					        if (currentDirectoryPath != null) {
 | 
				
			||||||
 | 
					          commandLine.setWorkDirectory(currentDirectoryPath)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        val handler = CapturingProcessHandler(commandLine)
 | 
				
			||||||
 | 
					        if (input != null) {
 | 
				
			||||||
 | 
					          handler.addProcessListener(object : ProcessAdapter() {
 | 
				
			||||||
 | 
					            override fun startNotified(event: ProcessEvent) {
 | 
				
			||||||
 | 
					              try {
 | 
				
			||||||
 | 
					                val charSequenceReader = CharSequenceReader(input)
 | 
				
			||||||
 | 
					                val outputStreamWriter = BufferedWriter(OutputStreamWriter(handler.processInput))
 | 
				
			||||||
 | 
					                copy(charSequenceReader, outputStreamWriter)
 | 
				
			||||||
 | 
					                outputStreamWriter.close()
 | 
				
			||||||
 | 
					              } catch (e: IOException) {
 | 
				
			||||||
 | 
					                logger.error(e)
 | 
				
			||||||
 | 
					              }
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          })
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val progressIndicator = ProgressIndicatorProvider.getInstance().progressIndicator
 | 
				
			||||||
 | 
					        val output = handler.runProcessWithProgressIndicator(progressIndicator)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        lastCommand = command
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (output.isCancelled) {
 | 
				
			||||||
 | 
					          // TODO: Vim will use whatever text has already been written to stdout
 | 
				
			||||||
 | 
					          // For whatever reason, we're not getting any here, so just throw an exception
 | 
				
			||||||
 | 
					          throw ProcessCanceledException()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        val exitCode = handler.exitCode
 | 
				
			||||||
 | 
					        if (exitCode != null && exitCode != 0) {
 | 
				
			||||||
 | 
					          VimPlugin.showMessage("shell returned $exitCode")
 | 
				
			||||||
 | 
					          VimPlugin.indicateError()
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        (output.stderr + output.stdout).replace("\u001B\\[[;\\d]*m".toRegex(), "")
 | 
				
			||||||
 | 
					      }, "IdeaVim - !$command", true, editor.ij.project
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @Suppress("SameParameterValue")
 | 
				
			||||||
 | 
					  private fun doEscape(original: String, charsToEscape: String, escapeChar: String): String {
 | 
				
			||||||
 | 
					    var result = original
 | 
				
			||||||
 | 
					    for (c in charsToEscape.toCharArray()) {
 | 
				
			||||||
 | 
					      result = result.replace("" + c, escapeChar + c)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return result
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // TODO: Java 10 has a transferTo method we could use instead
 | 
				
			||||||
 | 
					  @Throws(IOException::class)
 | 
				
			||||||
 | 
					  private fun copy(from: Reader, to: Writer) {
 | 
				
			||||||
 | 
					    val buf = CharArray(2048)
 | 
				
			||||||
 | 
					    var cnt: Int
 | 
				
			||||||
 | 
					    while ((from.read(buf).also { cnt = it }) != -1) {
 | 
				
			||||||
 | 
					      to.write(buf, 0, cnt)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public companion object {
 | 
				
			||||||
 | 
					    private val logger = logger<ProcessGroup>()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,91 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2003-2023 The IdeaVim authors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Use of this source code is governed by an MIT-style
 | 
				
			||||||
 | 
					 * license that can be found in the LICENSE.txt file or at
 | 
				
			||||||
 | 
					 * https://opensource.org/licenses/MIT.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package com.maddyhome.idea.vim.handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
				
			||||||
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.Keymap
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.KeymapManagerListener
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
				
			||||||
 | 
					import com.intellij.openapi.project.Project
 | 
				
			||||||
 | 
					import com.intellij.openapi.startup.StartupActivity
 | 
				
			||||||
 | 
					import com.intellij.util.SingleAlarm
 | 
				
			||||||
 | 
					import com.jetbrains.rd.util.ConcurrentHashMap
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// We use alarm with delay to avoid many actions in case many events are fired at the same time
 | 
				
			||||||
 | 
					// [VERSION UPDATE] 2023.3+ Replace SingleAlarm with coroutine flows https://youtrack.jetbrains.com/articles/IJPL-A-8/Alarm-Alternative
 | 
				
			||||||
 | 
					internal val correctorRequester = SingleAlarm({ correctCopilotKeymap() }, 1_000)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private val LOG = logger<CopilotKeymapCorrector>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal class CopilotKeymapCorrector : StartupActivity {
 | 
				
			||||||
 | 
					  override fun runActivity(project: Project) {
 | 
				
			||||||
 | 
					    correctorRequester.request()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal class IdeaVimCorrectorKeymapChangedListener : KeymapManagerListener {
 | 
				
			||||||
 | 
					  override fun activeKeymapChanged(keymap: Keymap?) {
 | 
				
			||||||
 | 
					    correctorRequester.request()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun shortcutChanged(keymap: Keymap, actionId: String) {
 | 
				
			||||||
 | 
					    correctorRequester.request()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
 | 
				
			||||||
 | 
					    correctorRequester.request()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					private val copilotHideActionMap = ConcurrentHashMap<String, Unit>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * See VIM-3206
 | 
				
			||||||
 | 
					 * The user expected to both copilot suggestion and the insert mode to be exited on a single esc.
 | 
				
			||||||
 | 
					 * However, for the moment, the first esc hides copilot suggestion and the second one exits insert mode.
 | 
				
			||||||
 | 
					 * To fix this, we remove the esc shortcut from the copilot action if the IdeaVim is active.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * This workaround is not the best solution, however, I don't see the better way with the current architecture of
 | 
				
			||||||
 | 
					 *   actions and EditorHandlers. Firstly, I wanted to suggest to copilot to migrate to EditorActionHandler as well,
 | 
				
			||||||
 | 
					 *   but this doesn't seem correct for me because in this case the user will lose an ability to change the shorcut for
 | 
				
			||||||
 | 
					 *   it. It seems like copilot has a similar problem as we do - we don't want to make a handler for "Editor enter action",
 | 
				
			||||||
 | 
					 *   but a handler for the esc key press. And, moreover, be able to communicate with other plugins about the ordering.
 | 
				
			||||||
 | 
					 *   Before this feature is implemented, hiding the copilot suggestion on esc looks like a good workaround.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					private fun correctCopilotKeymap() {
 | 
				
			||||||
 | 
					  // This is needed to initialize the injector in case this verification is called to fast
 | 
				
			||||||
 | 
					  VimPlugin.getInstance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (injector.enabler.isEnabled()) {
 | 
				
			||||||
 | 
					    val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
 | 
				
			||||||
 | 
					    val res = keymap.getShortcuts("copilot.disposeInlays")
 | 
				
			||||||
 | 
					    if (res.isEmpty()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val escapeShortcut = res.find { it.toString() == "[pressed ESCAPE]" } ?: return
 | 
				
			||||||
 | 
					    keymap.removeShortcut("copilot.disposeInlays", escapeShortcut)
 | 
				
			||||||
 | 
					    copilotHideActionMap[keymap.name] = Unit
 | 
				
			||||||
 | 
					    LOG.info("Remove copilot escape shortcut from keymap ${keymap.name}")
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  else {
 | 
				
			||||||
 | 
					    copilotHideActionMap.forEach { (name, _) ->
 | 
				
			||||||
 | 
					      val keymap = KeymapManagerEx.getInstanceEx().getKeymap(name) ?: return@forEach
 | 
				
			||||||
 | 
					      val currentShortcuts = keymap.getShortcuts("copilot.disposeInlays")
 | 
				
			||||||
 | 
					      if ("[pressed ESCAPE]" !in currentShortcuts.map { it.toString() }) {
 | 
				
			||||||
 | 
					        keymap.addShortcut("copilot.disposeInlays", KeyboardShortcut(key("<esc>"), null))
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      LOG.info("Restore copilot escape shortcut in keymap ${keymap.name}")
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,11 +8,14 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
package com.maddyhome.idea.vim.handler
 | 
					package com.maddyhome.idea.vim.handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.IdeActions
 | 
				
			||||||
import com.intellij.openapi.diagnostic.logger
 | 
					import com.intellij.openapi.diagnostic.logger
 | 
				
			||||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
 | 
					import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
 | 
				
			||||||
import com.intellij.openapi.extensions.ExtensionPointName
 | 
					import com.intellij.openapi.extensions.ExtensionPointName
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
				
			||||||
import com.intellij.openapi.project.Project
 | 
					import com.intellij.openapi.project.Project
 | 
				
			||||||
import com.intellij.openapi.startup.StartupActivity
 | 
					import com.intellij.openapi.startup.ProjectActivity
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.key
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Logs the chain of handlers for esc and enter
 | 
					 * Logs the chain of handlers for esc and enter
 | 
				
			||||||
@@ -26,11 +29,11 @@ import com.intellij.openapi.startup.StartupActivity
 | 
				
			|||||||
 * Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
 | 
					 * Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
 | 
				
			||||||
 *   otherwise, so let's use it as long as we can.
 | 
					 *   otherwise, so let's use it as long as we can.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
internal class EditorHandlersChainLogger : StartupActivity {
 | 
					internal class EditorHandlersChainLogger : ProjectActivity {
 | 
				
			||||||
  @Suppress("UnresolvedPluginConfigReference")
 | 
					  @Suppress("UnresolvedPluginConfigReference")
 | 
				
			||||||
  private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
 | 
					  private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun runActivity(project: Project) {
 | 
					  override suspend fun execute(project: Project) {
 | 
				
			||||||
    val escHandlers = editorHandlers.extensionList
 | 
					    val escHandlers = editorHandlers.extensionList
 | 
				
			||||||
      .filter { it.action == "EditorEscape" }
 | 
					      .filter { it.action == "EditorEscape" }
 | 
				
			||||||
      .joinToString("\n") { it.implementationClass }
 | 
					      .joinToString("\n") { it.implementationClass }
 | 
				
			||||||
@@ -40,6 +43,22 @@ internal class EditorHandlersChainLogger : StartupActivity {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    LOG.info("Esc handlers chain:\n$escHandlers")
 | 
					    LOG.info("Esc handlers chain:\n$escHandlers")
 | 
				
			||||||
    LOG.info("Enter handlers chain:\n$enterHandlers")
 | 
					    LOG.info("Enter handlers chain:\n$enterHandlers")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val keymapManager = KeymapManagerEx.getInstanceEx()
 | 
				
			||||||
 | 
					    val keymap = keymapManager.activeKeymap
 | 
				
			||||||
 | 
					    val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE).joinToString()
 | 
				
			||||||
 | 
					    val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER).joinToString()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info("Active keymap (${keymap.name}) shortcuts for esc: $keymapShortcutsForEsc, Shortcuts for enter: $keymapShortcutsForEnter")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val actionsForEsc = keymap.getActionIds(key("<esc>")).joinToString("\n")
 | 
				
			||||||
 | 
					    val actionsForEnter = keymap.getActionIds(key("<enter>")).joinToString("\n")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    LOG.info(
 | 
				
			||||||
 | 
					      "Also keymap (${keymap.name}) has " +
 | 
				
			||||||
 | 
					        "the following actions assigned to esc:\n$actionsForEsc " +
 | 
				
			||||||
 | 
					        "\nand following actions assigned to enter:\n$actionsForEnter"
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  companion object {
 | 
					  companion object {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										155
									
								
								src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										155
									
								
								src/main/java/com/maddyhome/idea/vim/handler/KeymapChecker.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,155 @@
 | 
				
			|||||||
 | 
					/*
 | 
				
			||||||
 | 
					 * Copyright 2003-2023 The IdeaVim authors
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Use of this source code is governed by an MIT-style
 | 
				
			||||||
 | 
					 * license that can be found in the LICENSE.txt file or at
 | 
				
			||||||
 | 
					 * https://opensource.org/licenses/MIT.
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package com.maddyhome.idea.vim.handler
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.IdeActions
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.Shortcut
 | 
				
			||||||
 | 
					import com.intellij.openapi.components.Service
 | 
				
			||||||
 | 
					import com.intellij.openapi.components.service
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.Keymap
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.KeymapManagerListener
 | 
				
			||||||
 | 
					import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
				
			||||||
 | 
					import com.intellij.openapi.project.Project
 | 
				
			||||||
 | 
					import com.intellij.openapi.startup.ProjectActivity
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.api.key
 | 
				
			||||||
 | 
					import kotlinx.coroutines.CoroutineScope
 | 
				
			||||||
 | 
					import kotlinx.coroutines.FlowPreview
 | 
				
			||||||
 | 
					import kotlinx.coroutines.channels.BufferOverflow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.MutableSharedFlow
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.collectLatest
 | 
				
			||||||
 | 
					import kotlinx.coroutines.flow.debounce
 | 
				
			||||||
 | 
					import kotlinx.coroutines.launch
 | 
				
			||||||
 | 
					import javax.swing.KeyStroke
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// We use alarm with delay to avoid many notifications in case many events are fired at the same time
 | 
				
			||||||
 | 
					internal val keyCheckRequests = MutableSharedFlow<Unit>(replay=1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * This checker verifies that the keymap has a correct configuration that is required for IdeaVim plugin
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					internal class KeymapChecker : ProjectActivity {
 | 
				
			||||||
 | 
					  override suspend fun execute(project: Project) {
 | 
				
			||||||
 | 
					    project.service<KeymapCheckerService>().start()
 | 
				
			||||||
 | 
					    keyCheckRequests.emit(Unit)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * At the moment of release 2023.3 there is a problem that starting a coroutine like this
 | 
				
			||||||
 | 
					 *   right in the project activity will block this project activity in tests.
 | 
				
			||||||
 | 
					 * To avoid that, there is an intermediate service that will allow to avoid this issue.
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * However, in general we should start this coroutine right in the [KeymapChecker]
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					@OptIn(FlowPreview::class)
 | 
				
			||||||
 | 
					@Service(Service.Level.PROJECT)
 | 
				
			||||||
 | 
					internal class KeymapCheckerService(private val cs: CoroutineScope) {
 | 
				
			||||||
 | 
					  fun start() {
 | 
				
			||||||
 | 
					    cs.launch {
 | 
				
			||||||
 | 
					      keyCheckRequests
 | 
				
			||||||
 | 
					        .debounce(5_000)
 | 
				
			||||||
 | 
					        .collectLatest { verifyKeymap() }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal class IdeaVimKeymapChangedListener : KeymapManagerListener {
 | 
				
			||||||
 | 
					  override fun activeKeymapChanged(keymap: Keymap?) {
 | 
				
			||||||
 | 
					    check(keyCheckRequests.tryEmit(Unit))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun shortcutChanged(keymap: Keymap, actionId: String) {
 | 
				
			||||||
 | 
					    check(keyCheckRequests.tryEmit(Unit))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun shortcutChanged(keymap: Keymap, actionId: String, fromSettings: Boolean) {
 | 
				
			||||||
 | 
					    check(keyCheckRequests.tryEmit(Unit))
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * After migration to the editor action handlers, we have to make sure that the keymap has a correct configuration.
 | 
				
			||||||
 | 
					 * For example, that esc key is assigned to esc editor action
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * Usually this is not a problem because this is a standard mapping, but the problem may appear in a misconfiguration
 | 
				
			||||||
 | 
					 *   like it was in VIM-3204
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					private fun verifyKeymap() {
 | 
				
			||||||
 | 
					  // This is needed to initialize the injector in case this verification is called to fast
 | 
				
			||||||
 | 
					  VimPlugin.getInstance()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!injector.enabler.isEnabled()) return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  val keymap = KeymapManagerEx.getInstanceEx().activeKeymap
 | 
				
			||||||
 | 
					  val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE)
 | 
				
			||||||
 | 
					  val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  val issues = ArrayList<KeyMapIssue>()
 | 
				
			||||||
 | 
					  val correctShortcutMissing = keymapShortcutsForEsc
 | 
				
			||||||
 | 
					    .filterIsInstance<KeyboardShortcut>()
 | 
				
			||||||
 | 
					    .none { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke == null }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // We also check if there are any shortcuts starting from esc and with a second key. This should also be removed.
 | 
				
			||||||
 | 
					  // For example, VIM-3162 has a case when two escapes were assigned to editor escape action
 | 
				
			||||||
 | 
					  val shortcutsStartingFromEsc = keymapShortcutsForEsc
 | 
				
			||||||
 | 
					    .filterIsInstance<KeyboardShortcut>()
 | 
				
			||||||
 | 
					    .filter { it.firstKeyStroke.toString() == "pressed ESCAPE" && it.secondKeyStroke != null }
 | 
				
			||||||
 | 
					  if (correctShortcutMissing) {
 | 
				
			||||||
 | 
					    issues += KeyMapIssue.AddShortcut(
 | 
				
			||||||
 | 
					      "esc",
 | 
				
			||||||
 | 
					      "editor escape",
 | 
				
			||||||
 | 
					      IdeActions.ACTION_EDITOR_ESCAPE,
 | 
				
			||||||
 | 
					      key("<esc>")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  shortcutsStartingFromEsc.forEach {
 | 
				
			||||||
 | 
					    issues += KeyMapIssue.RemoveShortcut("editor escape", IdeActions.ACTION_EDITOR_ESCAPE, it)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  val correctEnterShortcutMissing = keymapShortcutsForEnter
 | 
				
			||||||
 | 
					    .filterIsInstance<KeyboardShortcut>()
 | 
				
			||||||
 | 
					    .none { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke == null }
 | 
				
			||||||
 | 
					  val shortcutsStartingFromEnter = keymapShortcutsForEnter
 | 
				
			||||||
 | 
					    .filterIsInstance<KeyboardShortcut>()
 | 
				
			||||||
 | 
					    .filter { it.firstKeyStroke.toString() == "pressed ENTER" && it.secondKeyStroke != null }
 | 
				
			||||||
 | 
					  if (correctEnterShortcutMissing) {
 | 
				
			||||||
 | 
					    issues += KeyMapIssue.AddShortcut(
 | 
				
			||||||
 | 
					      "enter",
 | 
				
			||||||
 | 
					      "editor enter",
 | 
				
			||||||
 | 
					      IdeActions.ACTION_EDITOR_ENTER,
 | 
				
			||||||
 | 
					      key("<enter>")
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  shortcutsStartingFromEnter.forEach {
 | 
				
			||||||
 | 
					    issues += KeyMapIssue.RemoveShortcut("editor enter", IdeActions.ACTION_EDITOR_ENTER, it)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (issues.isNotEmpty()) {
 | 
				
			||||||
 | 
					    VimPlugin.getNotifications(null).notifyKeymapIssues(issues)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal sealed interface KeyMapIssue {
 | 
				
			||||||
 | 
					  data class AddShortcut(
 | 
				
			||||||
 | 
					    val key: String,
 | 
				
			||||||
 | 
					    val action: String,
 | 
				
			||||||
 | 
					    val actionId: String,
 | 
				
			||||||
 | 
					    val keyStroke: KeyStroke,
 | 
				
			||||||
 | 
					  ) : KeyMapIssue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  data class RemoveShortcut(
 | 
				
			||||||
 | 
					    val action: String,
 | 
				
			||||||
 | 
					    val actionId: String,
 | 
				
			||||||
 | 
					    val shortcut: Shortcut,
 | 
				
			||||||
 | 
					  ): KeyMapIssue
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -10,6 +10,7 @@ package com.maddyhome.idea.vim.handler
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
 | 
					import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
 | 
				
			||||||
import com.intellij.codeInsight.lookup.LookupManager
 | 
					import com.intellij.codeInsight.lookup.LookupManager
 | 
				
			||||||
 | 
					import com.intellij.formatting.LineWrappingUtil
 | 
				
			||||||
import com.intellij.ide.DataManager
 | 
					import com.intellij.ide.DataManager
 | 
				
			||||||
import com.intellij.openapi.actionSystem.DataContext
 | 
					import com.intellij.openapi.actionSystem.DataContext
 | 
				
			||||||
import com.intellij.openapi.application.ApplicationManager
 | 
					import com.intellij.openapi.application.ApplicationManager
 | 
				
			||||||
@@ -18,6 +19,7 @@ import com.intellij.openapi.diagnostic.logger
 | 
				
			|||||||
import com.intellij.openapi.editor.Caret
 | 
					import com.intellij.openapi.editor.Caret
 | 
				
			||||||
import com.intellij.openapi.editor.Editor
 | 
					import com.intellij.openapi.editor.Editor
 | 
				
			||||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
 | 
					import com.intellij.openapi.editor.actionSystem.EditorActionHandler
 | 
				
			||||||
 | 
					import com.intellij.openapi.editor.actions.SplitLineAction
 | 
				
			||||||
import com.intellij.openapi.editor.impl.CaretModelImpl
 | 
					import com.intellij.openapi.editor.impl.CaretModelImpl
 | 
				
			||||||
import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
					import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
				
			||||||
import com.intellij.openapi.util.Key
 | 
					import com.intellij.openapi.util.Key
 | 
				
			||||||
@@ -95,7 +97,15 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
				
			|||||||
        //   the condition (see VIM-3103 for example).
 | 
					        //   the condition (see VIM-3103 for example).
 | 
				
			||||||
        // Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
 | 
					        // Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
 | 
				
			||||||
        //   done by scheduling the execution of our code later via the invokeLater function.
 | 
					        //   done by scheduling the execution of our code later via the invokeLater function.
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // We run this job only once for a primary caret. In the handler itself, we'll multiply the execution by the
 | 
				
			||||||
 | 
					        //   number of carets. If we run this job for each caret, we may end up in the issue like VIM-3186.
 | 
				
			||||||
 | 
					        //   However, I think that we may do some refactoring to run this job for each caret (if needed).
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					        // For the moment, the known case when the caret is null - work in injected editor - VIM-3195
 | 
				
			||||||
 | 
					        if (caret == null || caret == editor.caretModel.primaryCaret) {
 | 
				
			||||||
          ApplicationManager.getApplication().invokeLater(executionHandler)
 | 
					          ApplicationManager.getApplication().invokeLater(executionHandler)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        executionHandler()
 | 
					        executionHandler()
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
@@ -106,12 +116,16 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  private fun executeInInvokeLater(editor: Editor): Boolean {
 | 
					  private fun executeInInvokeLater(editor: Editor): Boolean {
 | 
				
			||||||
    // Currently we have a workaround for the PY console VIM-3157
 | 
					    // Currently we have a workaround for the PY console VIM-3157
 | 
				
			||||||
    if (FileDocumentManager.getInstance().getFile(editor.document)?.name == "Python Console.py") return false
 | 
					    val fileName = FileDocumentManager.getInstance().getFile(editor.document)?.name
 | 
				
			||||||
 | 
					    if (
 | 
				
			||||||
 | 
					      fileName == "Python Console.py" || // This is the name in 232+
 | 
				
			||||||
 | 
					      fileName == "Python Console" // This is the name in 231
 | 
				
			||||||
 | 
					    ) return false
 | 
				
			||||||
    return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
 | 
					    return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  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
 | 
				
			||||||
@@ -131,7 +145,20 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
				
			|||||||
        return true
 | 
					        return true
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (dataManager.loadFromDataContext(dataContext, ShiftEnterDetector.Util.key) == true) {
 | 
					      // From VIM-3177
 | 
				
			||||||
 | 
					      val wrapLongLineDuringFormattingInProgress = dataManager
 | 
				
			||||||
 | 
					        .loadFromDataContext(dataContext, LineWrappingUtil.WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY)
 | 
				
			||||||
 | 
					      if (wrapLongLineDuringFormattingInProgress == true) {
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // From VIM-3203
 | 
				
			||||||
 | 
					      val splitLineInProgress = dataManager.loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY)
 | 
				
			||||||
 | 
					      if (splitLineInProgress == true) {
 | 
				
			||||||
 | 
					        return true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (dataManager.loadFromDataContext(dataContext, StartNewLineDetectorBase.Util.key) == true) {
 | 
				
			||||||
        return true
 | 
					        return true
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -202,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
 | 
				
			||||||
@@ -244,11 +271,17 @@ internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Workaround to support shift-enter in normal mode.
 | 
					 * Workaround to support "Start New Line" action in normal mode.
 | 
				
			||||||
 * IJ executes enter handler on shift-enter. This causes an issue that IdeaVim thinks that this is just an enter key.
 | 
					 * IJ executes enter handler on "Start New Line". This causes an issue that IdeaVim thinks that this is just an enter key.
 | 
				
			||||||
 * This thing should be refactored, but for now we'll use this workaround VIM-3159
 | 
					 * This thing should be refactored, but for now we'll use this workaround VIM-3159
 | 
				
			||||||
 | 
					 *
 | 
				
			||||||
 | 
					 * The Same thing happens with "Start New Line Before Current" action.
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
					internal class StartNewLineDetector(nextHandler: EditorActionHandler) : StartNewLineDetectorBase(nextHandler)
 | 
				
			||||||
 | 
					internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandler) :
 | 
				
			||||||
 | 
					  StartNewLineDetectorBase(nextHandler)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
				
			||||||
  override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
					  override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
				
			||||||
    DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
 | 
					    DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
 | 
				
			||||||
    nextHandler.execute(editor, caret, dataContext)
 | 
					    nextHandler.execute(editor, caret, dataContext)
 | 
				
			||||||
@@ -259,7 +292,7 @@ internal class ShiftEnterDetector(private val nextHandler: EditorActionHandler)
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  object Util {
 | 
					  object Util {
 | 
				
			||||||
    val key = Key.create<Boolean>("vim.is.shift.enter")
 | 
					    val key = Key.create<Boolean>("vim.is.start.new.line")
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  companion object {
 | 
					  companion object {
 | 
				
			||||||
@@ -311,9 +344,9 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
 | 
				
			|||||||
  // CMD line has a different processing mechanizm: the processing actions are registered
 | 
					  // CMD line has a different processing mechanizm: the processing actions are registered
 | 
				
			||||||
  //   for the input field component. These keys are not dispatched via the octopus handler.
 | 
					  //   for the input field component. These keys are not dispatched via the octopus handler.
 | 
				
			||||||
  if (editor.vim.mode is Mode.CMD_LINE) return false
 | 
					  if (editor.vim.mode is Mode.CMD_LINE) return false
 | 
				
			||||||
  when (s.keyCode) {
 | 
					  when {
 | 
				
			||||||
    KeyEvent.VK_ENTER -> return true
 | 
					    s.keyCode == KeyEvent.VK_ENTER && s.modifiers == 0 -> return true
 | 
				
			||||||
    KeyEvent.VK_ESCAPE -> return true
 | 
					    s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return false
 | 
					  return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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 {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
 | 
				
			|||||||
  get() {
 | 
					  get() {
 | 
				
			||||||
    val mode = this.vim.vimStateMachine.mode
 | 
					    val mode = this.vim.vimStateMachine.mode
 | 
				
			||||||
    return when (mode) {
 | 
					    return when (mode) {
 | 
				
			||||||
      Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
					      is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
				
			||||||
      Mode.INSERT -> CommandState.Mode.INSERT
 | 
					      Mode.INSERT -> CommandState.Mode.INSERT
 | 
				
			||||||
      is Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
					      is Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
				
			||||||
      is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
					      is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -335,7 +335,7 @@ public class EditorHelper {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
 | 
					    final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
 | 
				
			||||||
    @NotNull final VimEditor editor1 = new IjVimEditor(editor);
 | 
					    @NotNull final VimEditor editor1 = new IjVimEditor(editor);
 | 
				
			||||||
    final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) - 1;
 | 
					    final int lastVisualLine = EngineEditorHelperKt.getVisualLineCount(editor1) + editor.getSettings().getAdditionalLinesCount();
 | 
				
			||||||
    final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
 | 
					    final int offsetForLastLineAtBottom = getOffsetToScrollVisualLineToBottomOfScreen(editor, lastVisualLine);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
 | 
					    // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,7 @@ package com.maddyhome.idea.vim.helper
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import com.intellij.codeWithMe.ClientId
 | 
					import com.intellij.codeWithMe.ClientId
 | 
				
			||||||
import com.intellij.openapi.editor.Caret
 | 
					import com.intellij.openapi.editor.Caret
 | 
				
			||||||
 | 
					import com.intellij.openapi.editor.CaretState
 | 
				
			||||||
import com.intellij.openapi.editor.Editor
 | 
					import com.intellij.openapi.editor.Editor
 | 
				
			||||||
import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
					import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
				
			||||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
					import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
				
			||||||
@@ -19,6 +20,8 @@ import com.intellij.util.ui.table.JBTableRowEditor
 | 
				
			|||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
					import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
					import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.inBlockSelection
 | 
				
			||||||
import java.awt.Component
 | 
					import java.awt.Component
 | 
				
			||||||
import javax.swing.JComponent
 | 
					import javax.swing.JComponent
 | 
				
			||||||
import javax.swing.JTable
 | 
					import javax.swing.JTable
 | 
				
			||||||
@@ -93,3 +96,41 @@ internal val Caret.vimLine: Int
 | 
				
			|||||||
 */
 | 
					 */
 | 
				
			||||||
internal val Editor.vimLine: Int
 | 
					internal val Editor.vimLine: Int
 | 
				
			||||||
  get() = this.caretModel.currentCaret.vimLine
 | 
					  get() = this.caretModel.currentCaret.vimLine
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					internal inline fun Editor.runWithEveryCaretAndRestore(action: () -> Unit) {
 | 
				
			||||||
 | 
					  val caretModel = this.caretModel
 | 
				
			||||||
 | 
					  val carets = if (this.vim.inBlockSelection) null else caretModel.allCarets
 | 
				
			||||||
 | 
					  if (carets == null || carets.size == 1) {
 | 
				
			||||||
 | 
					    action()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  else {
 | 
				
			||||||
 | 
					    var initialDocumentSize = this.document.textLength
 | 
				
			||||||
 | 
					    var documentSizeDifference = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val caretOffsets = carets.map { it.selectionStart to it.selectionEnd }
 | 
				
			||||||
 | 
					    val restoredCarets = mutableListOf<CaretState>()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    caretModel.removeSecondaryCarets()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    for ((selectionStart, selectionEnd) in caretOffsets) {
 | 
				
			||||||
 | 
					      if (selectionStart == selectionEnd) {
 | 
				
			||||||
 | 
					        caretModel.primaryCaret.moveToOffset(selectionStart + documentSizeDifference)
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      else {
 | 
				
			||||||
 | 
					        caretModel.primaryCaret.setSelection(
 | 
				
			||||||
 | 
					          selectionStart + documentSizeDifference,
 | 
				
			||||||
 | 
					          selectionEnd + documentSizeDifference
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      action()
 | 
				
			||||||
 | 
					      restoredCarets.add(caretModel.caretsAndSelections.single())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      val documentLength = this.document.textLength
 | 
				
			||||||
 | 
					      documentSizeDifference += documentLength - initialDocumentSize
 | 
				
			||||||
 | 
					      initialDocumentSize = documentLength
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    caretModel.caretsAndSelections = restoredCarets
 | 
				
			||||||
 | 
					  } 
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,10 +15,12 @@ import com.intellij.openapi.actionSystem.AnAction
 | 
				
			|||||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
					import com.intellij.openapi.actionSystem.AnActionEvent
 | 
				
			||||||
import com.intellij.openapi.actionSystem.AnActionResult
 | 
					import com.intellij.openapi.actionSystem.AnActionResult
 | 
				
			||||||
import com.intellij.openapi.actionSystem.DataContextWrapper
 | 
					import com.intellij.openapi.actionSystem.DataContextWrapper
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.EmptyAction
 | 
				
			||||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
					import com.intellij.openapi.actionSystem.IdeActions
 | 
				
			||||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
					import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
				
			||||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
 | 
					import com.intellij.openapi.actionSystem.ex.ActionManagerEx
 | 
				
			||||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
 | 
					import com.intellij.openapi.actionSystem.ex.ActionUtil
 | 
				
			||||||
 | 
					import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
 | 
				
			||||||
import com.intellij.openapi.command.CommandProcessor
 | 
					import com.intellij.openapi.command.CommandProcessor
 | 
				
			||||||
import com.intellij.openapi.command.UndoConfirmationPolicy
 | 
					import com.intellij.openapi.command.UndoConfirmationPolicy
 | 
				
			||||||
import com.intellij.openapi.components.Service
 | 
					import com.intellij.openapi.components.Service
 | 
				
			||||||
@@ -39,6 +41,8 @@ import com.maddyhome.idea.vim.newapi.IjNativeAction
 | 
				
			|||||||
import com.maddyhome.idea.vim.newapi.ij
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.runFromVimKey
 | 
					import com.maddyhome.idea.vim.newapi.runFromVimKey
 | 
				
			||||||
import org.jetbrains.annotations.NonNls
 | 
					import org.jetbrains.annotations.NonNls
 | 
				
			||||||
 | 
					import java.awt.Component
 | 
				
			||||||
 | 
					import javax.swing.JComponent
 | 
				
			||||||
import javax.swing.SwingUtilities
 | 
					import javax.swing.SwingUtilities
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Service
 | 
					@Service
 | 
				
			||||||
@@ -139,7 +143,7 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
				
			|||||||
      manager.fireAfterActionPerformed(action, event, result!!)
 | 
					      manager.fireAfterActionPerformed(action, event, result!!)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (indexError != null) {
 | 
					    if (indexError != null) {
 | 
				
			||||||
      ActionUtil.showDumbModeWarning(project, event)
 | 
					      ActionUtil.showDumbModeWarning(project, action, event)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -150,11 +154,44 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
				
			|||||||
   * @param context The context to run it in
 | 
					   * @param context The context to run it in
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
 | 
					  override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
 | 
				
			||||||
    val aMgr = ActionManager.getInstance()
 | 
					    val action = getAction(name, context)
 | 
				
			||||||
    val action = aMgr.getAction(name)
 | 
					 | 
				
			||||||
    return action != null && executeAction(null, IjNativeAction(action), context)
 | 
					    return action != null && executeAction(null, IjNativeAction(action), context)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					  
 | 
				
			||||||
 | 
					  private fun getAction(name: String, context: ExecutionContext): AnAction? {
 | 
				
			||||||
 | 
					    val actionManager = ActionManager.getInstance()
 | 
				
			||||||
 | 
					    val action = actionManager.getAction(name)
 | 
				
			||||||
 | 
					    if (action !is EmptyAction) return action
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // But if the action is an instance of EmptyAction, the fun begins
 | 
				
			||||||
 | 
					    var component: Component? = context.ij.getData(PlatformDataKeys.CONTEXT_COMPONENT) ?: return null
 | 
				
			||||||
 | 
					    while (component != null) {
 | 
				
			||||||
 | 
					      if (component !is JComponent) {
 | 
				
			||||||
 | 
					        component = component.parent
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      val listOfActions = ActionUtil.getActions(component)
 | 
				
			||||||
 | 
					      if (listOfActions.isEmpty()) {
 | 
				
			||||||
 | 
					        component = component.getParent()
 | 
				
			||||||
 | 
					        continue
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fun AnAction.getId(): String? {
 | 
				
			||||||
 | 
					        return actionManager.getId(this)
 | 
				
			||||||
 | 
					          ?: (shortcutSet as? ProxyShortcutSet)?.actionId
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (action in listOfActions) {
 | 
				
			||||||
 | 
					        if (action.getId() == name) {
 | 
				
			||||||
 | 
					          return action
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      component = component.getParent()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  override fun executeCommand(
 | 
					  override fun executeCommand(
 | 
				
			||||||
    editor: VimEditor?,
 | 
					    editor: VimEditor?,
 | 
				
			||||||
    runnable: Runnable,
 | 
					    runnable: Runnable,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
				
			|||||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
 | 
					import com.maddyhome.idea.vim.api.normalizeVisualColumn
 | 
				
			||||||
import com.maddyhome.idea.vim.api.options
 | 
					import com.maddyhome.idea.vim.api.options
 | 
				
			||||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
					import com.maddyhome.idea.vim.command.CommandFlags
 | 
				
			||||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenHeight
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.getApproximateScreenWidth
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.getNonNormalizedVisualLineAtBottomOfScreen
 | 
				
			||||||
@@ -29,6 +28,7 @@ import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToBottomOfScre
 | 
				
			|||||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToMiddleOfScreen
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
 | 
					import com.maddyhome.idea.vim.helper.EditorHelper.scrollVisualLineToTopOfScreen
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.vim
 | 
					import com.maddyhome.idea.vim.newapi.vim
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.VimStateMachine
 | 
				
			||||||
import kotlin.math.max
 | 
					import kotlin.math.max
 | 
				
			||||||
import kotlin.math.min
 | 
					import kotlin.math.min
 | 
				
			||||||
import kotlin.math.roundToInt
 | 
					import kotlin.math.roundToInt
 | 
				
			||||||
@@ -56,7 +56,7 @@ internal object ScrollViewHelper {
 | 
				
			|||||||
    // that this needs to be replaced as a more or less dumb line for line rewrite.
 | 
					    // that this needs to be replaced as a more or less dumb line for line rewrite.
 | 
				
			||||||
    val topLine = getVisualLineAtTopOfScreen(editor)
 | 
					    val topLine = getVisualLineAtTopOfScreen(editor)
 | 
				
			||||||
    val bottomLine = getVisualLineAtBottomOfScreen(editor)
 | 
					    val bottomLine = getVisualLineAtBottomOfScreen(editor)
 | 
				
			||||||
    val lastLine = vimEditor.getVisualLineCount() - 1
 | 
					    val lastLine = vimEditor.getVisualLineCount() + editor.settings.additionalLinesCount
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
 | 
					    // We need the non-normalised value here, so we can handle cases such as so=999 to keep the current line centred
 | 
				
			||||||
    val scrollOffset = injector.options(vimEditor).scrolloff
 | 
					    val scrollOffset = injector.options(vimEditor).scrolloff
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,6 +9,7 @@
 | 
				
			|||||||
package com.maddyhome.idea.vim.helper;
 | 
					package com.maddyhome.idea.vim.helper;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import com.google.common.collect.Lists;
 | 
					import com.google.common.collect.Lists;
 | 
				
			||||||
 | 
					import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
 | 
				
			||||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
 | 
					import com.intellij.lang.CodeDocumentationAwareCommenter;
 | 
				
			||||||
import com.intellij.lang.Commenter;
 | 
					import com.intellij.lang.Commenter;
 | 
				
			||||||
import com.intellij.lang.Language;
 | 
					import com.intellij.lang.Language;
 | 
				
			||||||
@@ -16,15 +17,15 @@ import com.intellij.lang.LanguageCommenters;
 | 
				
			|||||||
import com.intellij.openapi.diagnostic.Logger;
 | 
					import com.intellij.openapi.diagnostic.Logger;
 | 
				
			||||||
import com.intellij.openapi.editor.Caret;
 | 
					import com.intellij.openapi.editor.Caret;
 | 
				
			||||||
import com.intellij.openapi.editor.Editor;
 | 
					import com.intellij.openapi.editor.Editor;
 | 
				
			||||||
 | 
					import com.intellij.openapi.project.Project;
 | 
				
			||||||
import com.intellij.psi.PsiComment;
 | 
					import com.intellij.psi.PsiComment;
 | 
				
			||||||
import com.intellij.psi.PsiElement;
 | 
					import com.intellij.psi.PsiElement;
 | 
				
			||||||
import com.intellij.psi.PsiFile;
 | 
					import com.intellij.psi.PsiFile;
 | 
				
			||||||
import com.intellij.psi.util.PsiTreeUtil;
 | 
					import com.intellij.psi.util.PsiTreeUtil;
 | 
				
			||||||
 | 
					import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
 | 
				
			||||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
					import com.maddyhome.idea.vim.VimPlugin;
 | 
				
			||||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
 | 
					import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
					import com.maddyhome.idea.vim.api.VimEditor;
 | 
				
			||||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
					 | 
				
			||||||
import com.maddyhome.idea.vim.common.CharacterPosition;
 | 
					import com.maddyhome.idea.vim.common.CharacterPosition;
 | 
				
			||||||
import com.maddyhome.idea.vim.common.Direction;
 | 
					import com.maddyhome.idea.vim.common.Direction;
 | 
				
			||||||
import com.maddyhome.idea.vim.common.TextRange;
 | 
					import com.maddyhome.idea.vim.common.TextRange;
 | 
				
			||||||
@@ -32,6 +33,12 @@ 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.regexp.CharPointer;
 | 
					import com.maddyhome.idea.vim.regexp.CharPointer;
 | 
				
			||||||
import com.maddyhome.idea.vim.regexp.RegExp;
 | 
					import com.maddyhome.idea.vim.regexp.RegExp;
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.Mode;
 | 
				
			||||||
 | 
					import it.unimi.dsi.fastutil.ints.IntComparator;
 | 
				
			||||||
 | 
					import it.unimi.dsi.fastutil.ints.IntIterator;
 | 
				
			||||||
 | 
					import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
 | 
				
			||||||
 | 
					import it.unimi.dsi.fastutil.ints.IntSortedSet;
 | 
				
			||||||
import kotlin.Pair;
 | 
					import kotlin.Pair;
 | 
				
			||||||
import org.jetbrains.annotations.Contract;
 | 
					import org.jetbrains.annotations.Contract;
 | 
				
			||||||
import org.jetbrains.annotations.NotNull;
 | 
					import org.jetbrains.annotations.NotNull;
 | 
				
			||||||
@@ -1523,6 +1530,42 @@ public class SearchHelper {
 | 
				
			|||||||
    return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
 | 
					    return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  public static int findMisspelledWords(@NotNull Editor editor,
 | 
				
			||||||
 | 
					                                       int startOffset,
 | 
				
			||||||
 | 
					                                       int endOffset,
 | 
				
			||||||
 | 
					                                       int skipCount,
 | 
				
			||||||
 | 
					                                       IntComparator offsetOrdering) {
 | 
				
			||||||
 | 
					    Project project = editor.getProject();
 | 
				
			||||||
 | 
					    if (project == null) {
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
 | 
				
			||||||
 | 
					    DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
 | 
				
			||||||
 | 
					                                           startOffset, endOffset, highlight -> {
 | 
				
			||||||
 | 
					        if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
 | 
				
			||||||
 | 
					          int offset = highlight.getStartOffset();
 | 
				
			||||||
 | 
					          if (offset >= startOffset && offset <= endOffset) {
 | 
				
			||||||
 | 
					            offsets.add(offset);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (offsets.isEmpty()) {
 | 
				
			||||||
 | 
					      return -1;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (skipCount >= offsets.size()) {
 | 
				
			||||||
 | 
					      return offsets.lastInt();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					      IntIterator offsetIterator = offsets.iterator();
 | 
				
			||||||
 | 
					      offsetIterator.skip(skipCount);
 | 
				
			||||||
 | 
					      return offsetIterator.nextInt();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
 | 
					  private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
 | 
				
			||||||
    List<String> pairs = options(injector, vimEditor).getMatchpairs();
 | 
					    List<String> pairs = options(injector, vimEditor).getMatchpairs();
 | 
				
			||||||
    StringBuilder res = new StringBuilder();
 | 
					    StringBuilder res = new StringBuilder();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,12 +14,16 @@ import com.intellij.openapi.command.CommandProcessor
 | 
				
			|||||||
import com.intellij.openapi.command.undo.UndoManager
 | 
					import com.intellij.openapi.command.undo.UndoManager
 | 
				
			||||||
import com.intellij.openapi.components.Service
 | 
					import com.intellij.openapi.components.Service
 | 
				
			||||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
 | 
					import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.VimPlugin
 | 
				
			||||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
					import com.maddyhome.idea.vim.api.ExecutionContext
 | 
				
			||||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
					import com.maddyhome.idea.vim.api.VimEditor
 | 
				
			||||||
import com.maddyhome.idea.vim.api.injector
 | 
					import com.maddyhome.idea.vim.api.injector
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.common.ChangesListener
 | 
				
			||||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
 | 
					import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
					import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
				
			||||||
import com.maddyhome.idea.vim.newapi.ij
 | 
					import com.maddyhome.idea.vim.newapi.ij
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.state.mode.inVisualMode
 | 
				
			||||||
import com.maddyhome.idea.vim.undo.UndoRedoBase
 | 
					import com.maddyhome.idea.vim.undo.UndoRedoBase
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -38,24 +42,20 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      if (injector.globalIjOptions().oldundo) {
 | 
					      if (injector.globalIjOptions().oldundo) {
 | 
				
			||||||
        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
					        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
				
			||||||
 | 
					        restoreVisualMode(editor)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
					        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
				
			||||||
 | 
					        editor.runWithChangeTracking {
 | 
				
			||||||
          undoManager.undo(fileEditor)
 | 
					          undoManager.undo(fileEditor)
 | 
				
			||||||
        if (hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
					
 | 
				
			||||||
          undoManager.undo(fileEditor) // execute one more time if the previous undo just restored selection
 | 
					          // We execute undo one more time if the previous one just restored selection
 | 
				
			||||||
 | 
					          if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
				
			||||||
 | 
					            undoManager.undo(fileEditor)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // remove selection
 | 
					 | 
				
			||||||
        editor.carets().forEach {
 | 
					 | 
				
			||||||
          val ijCaret = it.ij
 | 
					 | 
				
			||||||
          val hasSelection = ijCaret.hasSelection()
 | 
					 | 
				
			||||||
          if (hasSelection) {
 | 
					 | 
				
			||||||
            val selectionStart = ijCaret.selectionStart
 | 
					 | 
				
			||||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
					        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
				
			||||||
              it.ij.removeSelection()
 | 
					          removeSelections(editor)
 | 
				
			||||||
              it.ij.moveToOffset(selectionStart)
 | 
					 | 
				
			||||||
            }
 | 
					 | 
				
			||||||
          }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -78,14 +78,79 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
				
			|||||||
    if (undoManager.isRedoAvailable(fileEditor)) {
 | 
					    if (undoManager.isRedoAvailable(fileEditor)) {
 | 
				
			||||||
      if (injector.globalIjOptions().oldundo) {
 | 
					      if (injector.globalIjOptions().oldundo) {
 | 
				
			||||||
        SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
					        SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
				
			||||||
 | 
					        restoreVisualMode(editor)
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        undoManager.redo(fileEditor)
 | 
					        undoManager.redo(fileEditor)
 | 
				
			||||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
					        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
				
			||||||
          editor.carets().forEach { it.ij.removeSelection() }
 | 
					          editor.carets().forEach { it.ij.removeSelection() }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
				
			||||||
 | 
					        editor.runWithChangeTracking {
 | 
				
			||||||
 | 
					          undoManager.redo(fileEditor)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          // We execute undo one more time if the previous one just restored selection
 | 
				
			||||||
 | 
					          if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
 | 
				
			||||||
 | 
					            undoManager.redo(fileEditor)
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
				
			||||||
 | 
					          removeSelections(editor)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      return true
 | 
					      return true
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return false
 | 
					    return false
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun removeSelections(editor: VimEditor) {
 | 
				
			||||||
 | 
					    editor.carets().forEach {
 | 
				
			||||||
 | 
					      val ijCaret = it.ij
 | 
				
			||||||
 | 
					      if (!ijCaret.hasSelection()) return@forEach
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      val selectionStart = ijCaret.selectionStart
 | 
				
			||||||
 | 
					      ijCaret.removeSelection()
 | 
				
			||||||
 | 
					      ijCaret.moveToOffset(selectionStart)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
 | 
				
			||||||
 | 
					    val tracker = ChangeTracker(this)
 | 
				
			||||||
 | 
					    tracker.block()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private class ChangeTracker(private val editor: VimEditor) {
 | 
				
			||||||
 | 
					    private val initialPath = editor.getPath()
 | 
				
			||||||
 | 
					    private val changeListener = object : ChangesListener {
 | 
				
			||||||
 | 
					      var hasChanged = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      override fun documentChanged(change: ChangesListener.Change) {
 | 
				
			||||||
 | 
					        hasChanged = true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    init {
 | 
				
			||||||
 | 
					      editor.document.addChangeListener(changeListener)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    val hasChanges: Boolean
 | 
				
			||||||
 | 
					      get() = changeListener.hasChanged || initialPath != editor.getPath()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private fun restoreVisualMode(editor: VimEditor) {
 | 
				
			||||||
 | 
					    if (!editor.inVisualMode && editor.getSelectionModel().hasSelection()) {
 | 
				
			||||||
 | 
					      val detectedMode = VimPlugin.getVisualMotion().autodetectVisualSubmode(editor)
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      // Visual block selection is restored into multiple carets, so multi-carets that form a block are always
 | 
				
			||||||
 | 
					      // identified as visual block mode, leading to false positives.
 | 
				
			||||||
 | 
					      // Since I use visual block mode much less often than multi-carets, this is a judgment call to never restore
 | 
				
			||||||
 | 
					      // visual block mode.
 | 
				
			||||||
 | 
					      val wantedMode = if (detectedMode == SelectionType.BLOCK_WISE)
 | 
				
			||||||
 | 
					        SelectionType.CHARACTER_WISE
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        detectedMode
 | 
				
			||||||
 | 
					      
 | 
				
			||||||
 | 
					      VimPlugin.getVisualMotion().enterVisualMode(editor, wantedMode)
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -56,14 +56,16 @@ internal object IdeaSpecifics {
 | 
				
			|||||||
    private val surrounderAction =
 | 
					    private val surrounderAction =
 | 
				
			||||||
      "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
 | 
					      "com.intellij.codeInsight.generation.surroundWith.SurroundWithHandler\$InvokeSurrounderAction"
 | 
				
			||||||
    private var editor: Editor? = null
 | 
					    private var editor: Editor? = null
 | 
				
			||||||
 | 
					    private var caretOffset = -1
 | 
				
			||||||
    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) {
 | 
				
			||||||
        editor = hostEditor
 | 
					        editor = hostEditor
 | 
				
			||||||
 | 
					        caretOffset = hostEditor.caretModel.offset
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
 | 
					      val isVimAction = (action as? AnActionWrapper)?.delegate is VimShortcutKeyAction
 | 
				
			||||||
@@ -92,10 +94,11 @@ 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) {
 | 
				
			||||||
 | 
					        if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
 | 
				
			||||||
          val prevDocumentLength = completionPrevDocumentLength
 | 
					          val prevDocumentLength = completionPrevDocumentLength
 | 
				
			||||||
          val prevDocumentOffset = completionPrevDocumentOffset
 | 
					          val prevDocumentOffset = completionPrevDocumentOffset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -122,23 +125,27 @@ internal object IdeaSpecifics {
 | 
				
			|||||||
            )
 | 
					            )
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        ) {
 | 
					        ) {
 | 
				
			||||||
        editor?.let {
 | 
					          val commandState = editor.vim.vimStateMachine
 | 
				
			||||||
          val commandState = it.vim.vimStateMachine
 | 
					 | 
				
			||||||
          commandState.mode = Mode.NORMAL()
 | 
					          commandState.mode = Mode.NORMAL()
 | 
				
			||||||
          VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
 | 
					          VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
 | 
				
			||||||
          KeyHandler.getInstance().reset(it.vim)
 | 
					          KeyHandler.getInstance().reset(editor.vim)
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        //endregion
 | 
					        //endregion
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (caretOffset != -1 && caretOffset != editor.caretModel.offset) {
 | 
				
			||||||
 | 
					          injector.scroll.scrollCaretIntoView(editor.vim)
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      this.editor = null
 | 
					      this.editor = null
 | 
				
			||||||
 | 
					      this.caretOffset = -1
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  //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 +183,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 +206,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
 | 
				
			||||||
@@ -70,9 +70,10 @@ import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
 | 
				
			|||||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
					import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
 | 
					import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
 | 
				
			||||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
					import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.handler.correctorRequester
 | 
				
			||||||
 | 
					import com.maddyhome.idea.vim.handler.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
 | 
				
			||||||
@@ -89,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
 | 
				
			||||||
@@ -128,11 +134,14 @@ internal object VimListenerManager {
 | 
				
			|||||||
  fun turnOn() {
 | 
					  fun turnOn() {
 | 
				
			||||||
    GlobalListeners.enable()
 | 
					    GlobalListeners.enable()
 | 
				
			||||||
    EditorListeners.addAll()
 | 
					    EditorListeners.addAll()
 | 
				
			||||||
 | 
					    correctorRequester.request()
 | 
				
			||||||
 | 
					    check(keyCheckRequests.tryEmit(Unit))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  fun turnOff() {
 | 
					  fun turnOff() {
 | 
				
			||||||
    GlobalListeners.disable()
 | 
					    GlobalListeners.disable()
 | 
				
			||||||
    EditorListeners.removeAll()
 | 
					    EditorListeners.removeAll()
 | 
				
			||||||
 | 
					    correctorRequester.request()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  object GlobalListeners {
 | 
					  object GlobalListeners {
 | 
				
			||||||
@@ -150,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)
 | 
				
			||||||
@@ -157,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() {
 | 
				
			||||||
@@ -167,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)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -209,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
 | 
				
			||||||
@@ -266,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)
 | 
				
			||||||
@@ -331,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
 | 
				
			||||||
@@ -400,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)
 | 
				
			||||||
@@ -693,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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ import com.intellij.openapi.editor.CaretStateTransferableData
 | 
				
			|||||||
import com.intellij.openapi.editor.RawText
 | 
					import com.intellij.openapi.editor.RawText
 | 
				
			||||||
import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData
 | 
					import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData
 | 
				
			||||||
import com.intellij.openapi.editor.richcopy.view.RtfTransferableData
 | 
					import com.intellij.openapi.editor.richcopy.view.RtfTransferableData
 | 
				
			||||||
 | 
					import com.intellij.openapi.project.DumbService
 | 
				
			||||||
import com.intellij.openapi.project.IndexNotReadyException
 | 
					import com.intellij.openapi.project.IndexNotReadyException
 | 
				
			||||||
import com.intellij.psi.PsiDocumentManager
 | 
					import com.intellij.psi.PsiDocumentManager
 | 
				
			||||||
import com.intellij.util.ui.EmptyClipboardOwner
 | 
					import com.intellij.util.ui.EmptyClipboardOwner
 | 
				
			||||||
@@ -100,8 +101,7 @@ internal class IjClipboardManager : VimClipboardManager {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // This thing enables alternative context resolve for dumb mode.
 | 
					    // This thing enables alternative context resolve for dumb mode.
 | 
				
			||||||
    // Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled
 | 
					    // Please read docs for com.intellij.openapi.project.DumbService.isAlternativeResolveEnabled
 | 
				
			||||||
    // [VERSION UPDATE] 2023.2+ Enable alternative context back
 | 
					    DumbService.getInstance(project).withAlternativeResolveEnabled {
 | 
				
			||||||
//    DumbService.getInstance(project).withAlternativeResolveEnabled {
 | 
					 | 
				
			||||||
      for (processor in CopyPastePostProcessor.EP_NAME.extensionList) {
 | 
					      for (processor in CopyPastePostProcessor.EP_NAME.extensionList) {
 | 
				
			||||||
        try {
 | 
					        try {
 | 
				
			||||||
          logger.debug { "Copy paste processor: ${processor.javaClass.name}" }
 | 
					          logger.debug { "Copy paste processor: ${processor.javaClass.name}" }
 | 
				
			||||||
@@ -116,7 +116,7 @@ internal class IjClipboardManager : VimClipboardManager {
 | 
				
			|||||||
        } catch (ignore: IndexNotReadyException) {
 | 
					        } catch (ignore: IndexNotReadyException) {
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
//    }
 | 
					    }
 | 
				
			||||||
    transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
 | 
					    transferableData.add(CaretStateTransferableData(intArrayOf(0), intArrayOf(text.length)))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with
 | 
					    // These data provided by {@link com.intellij.openapi.editor.richcopy.TextWithMarkupProcessor} doesn't work with
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,6 +15,8 @@ import com.maddyhome.idea.vim.api.VimSearchHelperBase
 | 
				
			|||||||
import com.maddyhome.idea.vim.common.TextRange
 | 
					import com.maddyhome.idea.vim.common.TextRange
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.SearchHelper
 | 
					import com.maddyhome.idea.vim.helper.SearchHelper
 | 
				
			||||||
import com.maddyhome.idea.vim.helper.SearchOptions
 | 
					import com.maddyhome.idea.vim.helper.SearchOptions
 | 
				
			||||||
 | 
					import it.unimi.dsi.fastutil.ints.IntComparator
 | 
				
			||||||
 | 
					import it.unimi.dsi.fastutil.ints.IntComparators
 | 
				
			||||||
import java.util.*
 | 
					import java.util.*
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Service
 | 
					@Service
 | 
				
			||||||
@@ -93,4 +95,26 @@ internal class IjVimSearchHelper : VimSearchHelperBase() {
 | 
				
			|||||||
  ): TextRange? {
 | 
					  ): TextRange? {
 | 
				
			||||||
    return SearchHelper.findBlockRange(editor.ij, caret.ij, type, count, isOuter)
 | 
					    return SearchHelper.findBlockRange(editor.ij, caret.ij, type, count, isOuter)
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  override fun findMisspelledWord(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Int {
 | 
				
			||||||
 | 
					    val startOffset: Int
 | 
				
			||||||
 | 
					    val endOffset: Int
 | 
				
			||||||
 | 
					    val skipCount: Int
 | 
				
			||||||
 | 
					    val offsetOrdering: IntComparator
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    if (count < 0) {
 | 
				
			||||||
 | 
					      startOffset = 0
 | 
				
			||||||
 | 
					      endOffset = caret.offset.point - 1
 | 
				
			||||||
 | 
					      skipCount = -count - 1
 | 
				
			||||||
 | 
					      offsetOrdering = IntComparators.OPPOSITE_COMPARATOR
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    else {
 | 
				
			||||||
 | 
					      startOffset = caret.offset.point + 1
 | 
				
			||||||
 | 
					      endOffset = editor.ij.document.textLength
 | 
				
			||||||
 | 
					      skipCount = count - 1
 | 
				
			||||||
 | 
					      offsetOrdering = IntComparators.NATURAL_COMPARATOR
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return SearchHelper.findMisspelledWords(editor.ij, startOffset, endOffset, skipCount, offsetOrdering)
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -333,7 +333,7 @@
 | 
				
			|||||||
 * |[m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
 | 
					 * |[m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction}
 | 
				
			||||||
 * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
 | 
					 * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
 | 
				
			||||||
 * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
 | 
					 * |[p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
 | 
				
			||||||
 * |[s|                   TO BE IMPLEMENTED
 | 
					 * |[s|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction}
 | 
				
			||||||
 * |[z|                   TO BE IMPLEMENTED
 | 
					 * |[z|                   TO BE IMPLEMENTED
 | 
				
			||||||
 * |[{|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
 | 
					 * |[{|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceOpenAction}
 | 
				
			||||||
 * |]_CTRL-D|             TO BE IMPLEMENTED
 | 
					 * |]_CTRL-D|             TO BE IMPLEMENTED
 | 
				
			||||||
@@ -358,7 +358,7 @@
 | 
				
			|||||||
 * |]m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
 | 
					 * |]m|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction}
 | 
				
			||||||
 * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
 | 
					 * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutVisualTextAfterCursorNoIndentAction}
 | 
				
			||||||
 * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
 | 
					 * |]p|                   {@link com.maddyhome.idea.vim.action.copy.PutTextAfterCursorNoIndentAction}
 | 
				
			||||||
 * |]s|                   TO BE IMPLEMENTED
 | 
					 * |]s|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction}
 | 
				
			||||||
 * |]z|                   TO BE IMPLEMENTED
 | 
					 * |]z|                   TO BE IMPLEMENTED
 | 
				
			||||||
 * |]}|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
 | 
					 * |]}|                   {@link com.maddyhome.idea.vim.action.motion.text.MotionUnmatchedBraceCloseAction}
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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)
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -52,6 +52,7 @@ import java.awt.Point
 | 
				
			|||||||
import java.awt.event.MouseEvent
 | 
					import java.awt.event.MouseEvent
 | 
				
			||||||
import javax.swing.Icon
 | 
					import javax.swing.Icon
 | 
				
			||||||
import javax.swing.SwingConstants
 | 
					import javax.swing.SwingConstants
 | 
				
			||||||
 | 
					import javax.swing.Timer
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@NonNls
 | 
					@NonNls
 | 
				
			||||||
internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
 | 
					internal const val STATUS_BAR_ICON_ID = "IdeaVim-Icon"
 | 
				
			||||||
@@ -73,6 +74,14 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  override fun createWidget(project: Project): StatusBarWidget {
 | 
					  override fun createWidget(project: Project): StatusBarWidget {
 | 
				
			||||||
    VimPlugin.getOptionGroup().addGlobalOptionChangeListener(IjOptions.ideastatusicon) { updateAll() }
 | 
					    VimPlugin.getOptionGroup().addGlobalOptionChangeListener(IjOptions.ideastatusicon) { updateAll() }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Double update the status bar icon with 5-second delay
 | 
				
			||||||
 | 
					    // There is an issue VIM-3084 that must probably caused by some race between status bar icon initialization
 | 
				
			||||||
 | 
					    //   and .ideavimrc reading. I believe this is a simple fix for it.
 | 
				
			||||||
 | 
					    val timer = Timer(5_000) { updateAll() }
 | 
				
			||||||
 | 
					    timer.isRepeats = false
 | 
				
			||||||
 | 
					    timer.start()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return VimStatusBar()
 | 
					    return VimStatusBar()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -89,10 +98,10 @@ internal class StatusBarIconFactory : StatusBarWidgetFactory/*, LightEditCompati
 | 
				
			|||||||
      statusBarWidgetsManager.updateWidget(this)
 | 
					      statusBarWidgetsManager.updateWidget(this)
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    updateIcon()
 | 
					    Util.updateIcon()
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  companion object {
 | 
					  object Util {
 | 
				
			||||||
    fun updateIcon() {
 | 
					    fun updateIcon() {
 | 
				
			||||||
      val projectManager = ProjectManager.getInstanceIfCreated() ?: return
 | 
					      val projectManager = ProjectManager.getInstanceIfCreated() ?: return
 | 
				
			||||||
      for (project in projectManager.openProjects) {
 | 
					      for (project in projectManager.openProjects) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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>
 | 
				
			||||||
@@ -94,6 +94,8 @@
 | 
				
			|||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction" mappingModes="NXO" keys="[m"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodPreviousStartAction" mappingModes="NXO" keys="[m"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction" mappingModes="NXO" keys="]M"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextEndAction" mappingModes="NXO" keys="]M"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction" mappingModes="NXO" keys="]m"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMethodNextStartAction" mappingModes="NXO" keys="]m"/>
 | 
				
			||||||
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordPreviousAction" mappingModes="NXO" keys="[s"/>
 | 
				
			||||||
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.motion.text.MotionMisspelledWordNextAction" mappingModes="NXO" keys="]s"/>
 | 
				
			||||||
    <!-- Text Objects -->
 | 
					    <!-- Text Objects -->
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterWordAction" mappingModes="XO" keys="aw"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterWordAction" mappingModes="XO" keys="aw"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterBigWordAction" mappingModes="XO" keys="aW"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.motion.object.MotionOuterBigWordAction" mappingModes="XO" keys="aW"/>
 | 
				
			||||||
@@ -226,12 +228,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 +331,8 @@
 | 
				
			|||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.RepeatChangeAction" mappingModes="N" keys="."/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.ExEntryAction" mappingModes="NXO" keys=":"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.ResetModeAction" mappingModes="ALL" keys="«C-\»«C-N»"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="N" keys="«C-R»"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.RedoAction" mappingModes="NX" keys="U,«C-R»"/>
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="N"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.UndoAction" mappingModes="NX"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <!-- Keys -->
 | 
					    <!-- Keys -->
 | 
				
			||||||
    <vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>
 | 
					    <vimAction implementation="com.maddyhome.idea.vim.action.change.OperatorAction" mappingModes="N" keys="g@"/>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,6 +12,10 @@
 | 
				
			|||||||
              topic="com.intellij.ide.ui.LafManagerListener"/>
 | 
					              topic="com.intellij.ide.ui.LafManagerListener"/>
 | 
				
			||||||
    <listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter"
 | 
					    <listener class="com.maddyhome.idea.vim.extension.highlightedyank.HighlightColorResetter"
 | 
				
			||||||
              topic="com.intellij.ide.ui.LafManagerListener"/>
 | 
					              topic="com.intellij.ide.ui.LafManagerListener"/>
 | 
				
			||||||
 | 
					    <listener class="com.maddyhome.idea.vim.handler.IdeaVimKeymapChangedListener"
 | 
				
			||||||
 | 
					              topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
 | 
				
			||||||
 | 
					    <listener class="com.maddyhome.idea.vim.handler.IdeaVimCorrectorKeymapChangedListener"
 | 
				
			||||||
 | 
					              topic="com.intellij.openapi.keymap.KeymapManagerListener"/>
 | 
				
			||||||
  </applicationListeners>
 | 
					  </applicationListeners>
 | 
				
			||||||
  <projectListeners>
 | 
					  <projectListeners>
 | 
				
			||||||
    <listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"
 | 
					    <listener class="com.maddyhome.idea.vim.ui.ExOutputPanel$LafListener"
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,4 @@
 | 
				
			|||||||
<!--
 | 
					<idea-plugin xmlns:xi="http://www.w3.org/2001/XInclude">
 | 
				
			||||||
  ~ Copyright 2003-2023 The IdeaVim authors
 | 
					 | 
				
			||||||
  ~
 | 
					 | 
				
			||||||
  ~ Use of this source code is governed by an MIT-style
 | 
					 | 
				
			||||||
  ~ license that can be found in the LICENSE.txt file or at
 | 
					 | 
				
			||||||
  ~ https://opensource.org/licenses/MIT.
 | 
					 | 
				
			||||||
  -->
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<idea-plugin url="https://plugins.jetbrains.com/plugin/164" xmlns:xi="http://www.w3.org/2001/XInclude">
 | 
					 | 
				
			||||||
  <name>IdeaVim</name>
 | 
					  <name>IdeaVim</name>
 | 
				
			||||||
  <id>IdeaVIM</id>
 | 
					  <id>IdeaVIM</id>
 | 
				
			||||||
  <description><![CDATA[
 | 
					  <description><![CDATA[
 | 
				
			||||||
@@ -21,13 +13,13 @@
 | 
				
			|||||||
        <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
 | 
					        <li><a href="https://youtrack.jetbrains.com/issues/VIM">Issue tracker</a>: feature requests and bug reports</li>
 | 
				
			||||||
      </ul>
 | 
					      </ul>
 | 
				
			||||||
    ]]></description>
 | 
					    ]]></description>
 | 
				
			||||||
  <version>SNAPSHOT</version>
 | 
					  <version>chylex</version>
 | 
				
			||||||
  <vendor>JetBrains</vendor>
 | 
					  <vendor>JetBrains</vendor>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
 | 
					  <!-- Please search for "[VERSION UPDATE]" in project in case you update the since-build version -->
 | 
				
			||||||
  <!-- Check for [Version Update] tag in YouTrack as well -->
 | 
					  <!-- Check for [Version Update] tag in YouTrack as well -->
 | 
				
			||||||
  <!-- Also, please update the value in build.gradle.kts file-->
 | 
					  <!-- Also, please update the value in build.gradle.kts file-->
 | 
				
			||||||
  <idea-version since-build="231.8109.175"/>
 | 
					  <idea-version since-build="232"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
 | 
					  <!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform (including CWM) -->
 | 
				
			||||||
  <depends>com.intellij.modules.platform</depends>
 | 
					  <depends>com.intellij.modules.platform</depends>
 | 
				
			||||||
@@ -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"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -79,11 +77,12 @@
 | 
				
			|||||||
         core platform activities have IDs, so we can't use "before ID". We have to use "first" -->
 | 
					         core platform activities have IDs, so we can't use "before ID". We have to use "first" -->
 | 
				
			||||||
    <postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/>
 | 
					    <postStartupActivity implementation="com.maddyhome.idea.vim.PluginStartup" order="first"/>
 | 
				
			||||||
    <postStartupActivity implementation="com.maddyhome.idea.vim.handler.EditorHandlersChainLogger"/>
 | 
					    <postStartupActivity implementation="com.maddyhome.idea.vim.handler.EditorHandlersChainLogger"/>
 | 
				
			||||||
 | 
					    <postStartupActivity implementation="com.maddyhome.idea.vim.handler.KeymapChecker"/>
 | 
				
			||||||
 | 
					    <postStartupActivity implementation="com.maddyhome.idea.vim.handler.CopilotKeymapCorrector"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/>
 | 
					    <editorFloatingToolbarProvider implementation="com.maddyhome.idea.vim.ui.ReloadFloatingToolbar"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <actionPromoter implementation="com.maddyhome.idea.vim.key.VimActionsPromoter" order="last"/>
 | 
					    <actionPromoter implementation="com.maddyhome.idea.vim.key.VimActionsPromoter" order="last"/>
 | 
				
			||||||
    <actionConfigurationCustomizer implementation="com.maddyhome.idea.vim.action.VimActionConfigurationCustomizer"/>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    <spellchecker.bundledDictionaryProvider implementation="com.maddyhome.idea.vim.VimBundledDictionaryProvider"/>
 | 
					    <spellchecker.bundledDictionaryProvider implementation="com.maddyhome.idea.vim.VimBundledDictionaryProvider"/>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -125,8 +124,12 @@
 | 
				
			|||||||
                         id="ideavim-enter-logger"
 | 
					                         id="ideavim-enter-logger"
 | 
				
			||||||
                         order="first"/>
 | 
					                         order="first"/>
 | 
				
			||||||
    <editorActionHandler action="EditorStartNewLine"
 | 
					    <editorActionHandler action="EditorStartNewLine"
 | 
				
			||||||
                         implementationClass="com.maddyhome.idea.vim.handler.ShiftEnterDetector"
 | 
					                         implementationClass="com.maddyhome.idea.vim.handler.StartNewLineDetector"
 | 
				
			||||||
                         id="ideavim-shift-enter-detector"
 | 
					                         id="ideavim-start-new-line-detector"
 | 
				
			||||||
 | 
					                         order="first"/>
 | 
				
			||||||
 | 
					    <editorActionHandler action="EditorStartNewLineBefore"
 | 
				
			||||||
 | 
					                         implementationClass="com.maddyhome.idea.vim.handler.StartNewLineBeforeCurrentDetector"
 | 
				
			||||||
 | 
					                         id="ideavim-start-new-line-before-current-detector"
 | 
				
			||||||
                         order="first"/>
 | 
					                         order="first"/>
 | 
				
			||||||
  </extensions>
 | 
					  </extensions>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -149,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"
 | 
				
			||||||
@@ -157,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>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										11
									
								
								src/main/resources/icons/ideavim_outline.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/main/resources/icons/ideavim_outline.svg
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,11 @@
 | 
				
			|||||||
 | 
					<!--
 | 
				
			||||||
 | 
					  - Copyright 2003-2023 The IdeaVim authors
 | 
				
			||||||
 | 
					  -
 | 
				
			||||||
 | 
					  - Use of this source code is governed by an MIT-style
 | 
				
			||||||
 | 
					  - license that can be found in the LICENSE.txt file or at
 | 
				
			||||||
 | 
					  - https://opensource.org/licenses/MIT.
 | 
				
			||||||
 | 
					  -->
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 40 40" fill-opacity="0" stroke="#6E6E6E" stroke-width="3">
 | 
				
			||||||
 | 
					    <path d="M 28.019 4 L 15.988 24.119 L 15.988 4 L 4 4 L 4 36 L 17.953 36 L 36 4z"/>
 | 
				
			||||||
 | 
					</svg>
 | 
				
			||||||
| 
		 After Width: | Height: | Size: 446 B  | 
@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user