mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 10:40:10 +01:00 
			
		
		
		
	Compare commits
	
		
			330 Commits
		
	
	
		
			8bcb0d116d
			...
			customized
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						07659cc64d
	
				 | 
					
					
						|||
| 
						
						
							
						
						c089bd4bd3
	
				 | 
					
					
						|||
| 
						
						
							
						
						b96708fdb8
	
				 | 
					
					
						|||
| 
						
						
							
						
						e3bed58a29
	
				 | 
					
					
						|||
| 
						
						
							
						
						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 | ||
| 
						 | 
					c3134b9426 | ||
| 
						 | 
					06c036d373 | ||
| 
						 | 
					2f8bd29725 | ||
| 
						 | 
					1eae211b41 | ||
| 
						 | 
					b02eb7a422 | ||
| 
						 | 
					3db31e9347 | ||
| 
						 | 
					1dc6045ae1 | ||
| 
						 | 
					2436164b1e | ||
| 
						 | 
					c13fc8a805 | ||
| 
						 | 
					41025d78de | ||
| 
						 | 
					b3ad222cdc | ||
| 
						 | 
					efd9ed0a5f | ||
| 
						 | 
					9d20061924 | ||
| 
						 | 
					ddfe8cf361 | ||
| 
						 | 
					93c83f773a | ||
| 
						 | 
					876e16fa9e | ||
| 
						 | 
					37067d5c72 | ||
| 
						 | 
					083ac8cfa3 | ||
| 
						 | 
					fe6c1ae452 | ||
| 
						 | 
					30165f5047 | ||
| 
						 | 
					3046c61447 | ||
| 
						 | 
					ced50bb2e8 | ||
| 
						 | 
					dee84bcc63 | ||
| 
						 | 
					0f0bafb66a | ||
| 
						 | 
					7cdc3611a5 | ||
| 
						 | 
					6eda6aebba | ||
| 
						 | 
					2d23c81ebb | ||
| 
						 | 
					5602058849 | ||
| 
						 | 
					b1ec021c1e | ||
| 
						 | 
					7073b2410b | ||
| 
						 | 
					ab9068bc0a | ||
| 
						 | 
					0c66fb474e | ||
| 
						 | 
					fef6c651ea | ||
| 
						 | 
					620f54344f | ||
| 
						 | 
					ef1259a87a | ||
| 
						 | 
					5ef4af6b55 | ||
| 
						 | 
					6d17304e4e | ||
| 
						 | 
					2e4062b5db | ||
| 
						 | 
					b294bdd013 | ||
| 
						 | 
					dc95c7fc2c | ||
| 
						 | 
					dfe8c43c33 | ||
| 
						 | 
					3e54ad5a68 | ||
| 
						 | 
					288c66d8a2 | ||
| 
						 | 
					44c8a97f44 | ||
| 
						 | 
					60c27b1dea | ||
| 
						 | 
					ce8b77b240 | ||
| 
						 | 
					718c5fb30e | ||
| 419160724c | |||
| 
						 | 
					c905dfe6d8 | ||
| 
						 | 
					70eb008412 | ||
| 
						 | 
					93feaadacf | ||
| 
						 | 
					0b7610607d | ||
| 
						 | 
					922fea5395 | ||
| 
						 | 
					1841b7c4e6 | ||
| 
						 | 
					ed966faaf4 | ||
| 
						 | 
					03efeed6ae | ||
| 
						 | 
					675c5ae480 | ||
| 
						 | 
					d575b22e2e | ||
| 
						 | 
					5e4ee1b60f | ||
| 
						 | 
					d8ce20c2f9 | ||
| 
						 | 
					b164dc1b55 | ||
| 
						 | 
					530eba3d00 | ||
| 
						 | 
					808066f2e2 | ||
| 
						 | 
					cb3e683c8e | ||
| 
						 | 
					6ff57775ed | ||
| 
						 | 
					6c07687a86 | ||
| 
						 | 
					497a8c19c5 | ||
| 
						 | 
					c13f7468ef | ||
| 
						 | 
					847872cdb6 | ||
| 
						 | 
					f0abe5d80d | ||
| 
						 | 
					465c5b9e77 | ||
| fb78cdd304 | |||
| 
						 | 
					5b17fe2410 | ||
| 
						 | 
					5fd54dccd3 | ||
| 
						 | 
					1695afd915 | ||
| 
						 | 
					5ab549ae96 | ||
| 
						 | 
					35123e7c1e | ||
| 
						 | 
					1badade841 | ||
| 
						 | 
					b357625529 | ||
| 
						 | 
					9ccd39d724 | ||
| 
						 | 
					c876079e04 | ||
| 
						 | 
					162c1c59fe | ||
| 
						 | 
					06ef1c1182 | ||
| 
						 | 
					a9ba9789fd | ||
| 
						 | 
					fdd32cb954 | ||
| 
						 | 
					9fd7d86998 | ||
| 
						 | 
					5973903313 | ||
| 
						 | 
					75e4b19b88 | ||
| 
						 | 
					af7bdb55a1 | ||
| 
						 | 
					69af9aeff0 | ||
| 
						 | 
					88f4192d61 | ||
| 
						 | 
					96db8a326e | ||
| 
						 | 
					8c06767fdc | ||
| 
						 | 
					25877e369b | ||
| 
						 | 
					0271a475a2 | ||
| 
						 | 
					eef3ab5a15 | ||
| 
						 | 
					26f48c5820 | ||
| 
						 | 
					236ca36c79 | ||
| 
						 | 
					405b9ba7ea | ||
| 
						 | 
					ab9bd76d34 | ||
| 
						 | 
					677da7d80a | ||
| 
						 | 
					b3ad2fd715 | ||
| 
						 | 
					97ca6ce5b8 | ||
| 
						 | 
					e1abc4374e | ||
| 
						 | 
					9eeeb15c6c | 
							
								
								
									
										1
									
								
								.github/workflows/checkNewPlugins.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/checkNewPlugins.yml
									
									
									
									
										vendored
									
									
								
							@@ -14,6 +14,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Fetch origin repo
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/closeYoutrackOnCommit.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/closeYoutrackOnCommit.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,6 +12,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/integrationsTest.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/integrationsTest.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,6 +12,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/kover.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/kover.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,6 +12,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/mergeDependabotPR.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/mergeDependabotPR.yml
									
									
									
									
										vendored
									
									
								
							@@ -8,7 +8,7 @@ permissions:
 | 
			
		||||
jobs:
 | 
			
		||||
  dependabot:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: ${{ github.actor == 'dependabot[bot]' }}
 | 
			
		||||
    if: ${{ github.actor == 'dependabot[bot]' && github.repository == 'JetBrains/ideavim' }}
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Auto-merge Dependabot PR
 | 
			
		||||
        run: gh pr merge --auto --rebase "$PR_URL"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/mergePr.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/mergePr.yml
									
									
									
									
										vendored
									
									
								
							@@ -11,7 +11,7 @@ on:
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    if: github.event.pull_request.merged == true
 | 
			
		||||
    if: github.event.pull_request.merged == true && github.repository == 'JetBrains/ideavim'
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										25
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										25
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							@@ -5,20 +5,23 @@ on:
 | 
			
		||||
      - cron: '0 12 * * *'
 | 
			
		||||
jobs:
 | 
			
		||||
  build-for-ui-test-mac-os:
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
    runs-on: macos-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - name: Setup Java
 | 
			
		||||
        uses: actions/setup-java@v2.1.0
 | 
			
		||||
        uses: actions/setup-java@v4
 | 
			
		||||
        with:
 | 
			
		||||
          distribution: zulu
 | 
			
		||||
          java-version: 11
 | 
			
		||||
      - name: Setup FFmpeg
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v1
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v3
 | 
			
		||||
        with:
 | 
			
		||||
          # Not strictly necessary, but it may prevent rate limit
 | 
			
		||||
          # errors especially on GitHub-hosted macos machines.
 | 
			
		||||
          token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
      - name: Setup Gradle
 | 
			
		||||
        uses: gradle/gradle-build-action@v2.4.2
 | 
			
		||||
      - name: Build Plugin
 | 
			
		||||
        run: gradle :buildPlugin
 | 
			
		||||
      - name: Run Idea
 | 
			
		||||
@@ -26,7 +29,7 @@ jobs:
 | 
			
		||||
          mkdir -p build/reports
 | 
			
		||||
          gradle :runIdeForUiTests > build/reports/idea.log &
 | 
			
		||||
      - name: Wait for Idea started
 | 
			
		||||
        uses: jtalk/url-health-check-action@1.5
 | 
			
		||||
        uses: jtalk/url-health-check-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
          url: http://127.0.0.1:8082
 | 
			
		||||
          max-attempts: 20
 | 
			
		||||
@@ -34,15 +37,19 @@ jobs:
 | 
			
		||||
      - name: Tests
 | 
			
		||||
        run: gradle :testUi
 | 
			
		||||
      - name: Move video
 | 
			
		||||
        if: ${{ failure() }}
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: mv video build/reports
 | 
			
		||||
      - name: Save fails report
 | 
			
		||||
        if: ${{ failure() }}
 | 
			
		||||
        uses: actions/upload-artifact@v2
 | 
			
		||||
      - name: Move sandbox logs
 | 
			
		||||
        if: always()
 | 
			
		||||
        run: mv build/idea-sandbox/system/log sandbox-idea-log
 | 
			
		||||
      - name: Save report
 | 
			
		||||
        if: always()
 | 
			
		||||
        uses: actions/upload-artifact@v4
 | 
			
		||||
        with:
 | 
			
		||||
          name: ui-test-fails-report-mac
 | 
			
		||||
          path: |
 | 
			
		||||
            build/reports
 | 
			
		||||
            sandbox-idea-log
 | 
			
		||||
#  build-for-ui-test-linux:
 | 
			
		||||
#    runs-on: ubuntu-latest
 | 
			
		||||
#    steps:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/syncDoc.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/syncDoc.yml
									
									
									
									
										vendored
									
									
								
							@@ -14,6 +14,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Fetch origin repo
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/updateAffectedRate.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/updateAffectedRate.yml
									
									
									
									
										vendored
									
									
								
							@@ -14,6 +14,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Fetch origin repo
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/updateAuthors.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/updateAuthors.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,6 +15,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/updateChangelog.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/updateChangelog.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,6 +15,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.github/workflows/updateFormatting.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/updateFormatting.yml
									
									
									
									
										vendored
									
									
								
							@@ -12,6 +12,7 @@ jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -23,6 +23,9 @@
 | 
			
		||||
 | 
			
		||||
# Generated by gradle task "generateGrammarSource"
 | 
			
		||||
src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
 | 
			
		||||
# Generated JSONs for lazy classloading
 | 
			
		||||
/vim-engine/src/main/resources/ksp-generated
 | 
			
		||||
/src/main/resources/ksp-generated
 | 
			
		||||
 | 
			
		||||
# Created by github automation
 | 
			
		||||
settings.xml
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.idea/copyright/IdeaVim.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/copyright/IdeaVim.xml
									
									
									
										generated
									
									
									
								
							@@ -1,6 +1,6 @@
 | 
			
		||||
<component name="CopyrightManager">
 | 
			
		||||
  <copyright>
 | 
			
		||||
    <option name="notice" value="Copyright 2003-2023 The IdeaVim authors

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

Use of this source code is governed by an MIT-style
license that can be found in the LICENSE.txt file or at
https://opensource.org/licenses/MIT." />
 | 
			
		||||
    <option name="myName" value="IdeaVim" />
 | 
			
		||||
  </copyright>
 | 
			
		||||
</component>
 | 
			
		||||
							
								
								
									
										18
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										18
									
								
								.teamcity/_Self/Constants.kt
									
									
									
									
										vendored
									
									
								
							@@ -5,15 +5,13 @@ object Constants {
 | 
			
		||||
  const val EAP_CHANNEL = "eap"
 | 
			
		||||
  const val DEV_CHANNEL = "Dev"
 | 
			
		||||
 | 
			
		||||
  const val VERSION = "2.4.0"
 | 
			
		||||
  const val GITHUB_TESTS = "2023.3.2"
 | 
			
		||||
  const val NVIM_TESTS = "2023.3.2"
 | 
			
		||||
  const val PROPERTY_TESTS = "2023.3.2"
 | 
			
		||||
  const val LONG_RUNNING_TESTS = "2023.3.2"
 | 
			
		||||
  const val QODANA_TESTS = "2023.3.2"
 | 
			
		||||
  const val RELEASE = "2023.3.2"
 | 
			
		||||
 | 
			
		||||
  const val GITHUB_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val NVIM_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val PROPERTY_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val LONG_RUNNING_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val QODANA_TESTS = "LATEST-EAP-SNAPSHOT"
 | 
			
		||||
  const val RELEASE = "2023.1.2"
 | 
			
		||||
 | 
			
		||||
  const val RELEASE_DEV = "2023.1.2"
 | 
			
		||||
  const val RELEASE_EAP = "2023.1.2"
 | 
			
		||||
  const val RELEASE_DEV = "2023.3.2"
 | 
			
		||||
  const val RELEASE_EAP = "2023.3.2"
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							@@ -24,6 +24,7 @@ object Project : Project({
 | 
			
		||||
 | 
			
		||||
  // Active tests
 | 
			
		||||
  buildType(TestingBuildType("Latest EAP", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
			
		||||
  buildType(TestingBuildType("2023.3", "<default>", version = "2023.3"))
 | 
			
		||||
  buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
			
		||||
 | 
			
		||||
  buildType(PropertyBased)
 | 
			
		||||
@@ -38,6 +39,11 @@ object Project : Project({
 | 
			
		||||
 | 
			
		||||
// Common build type for all configurations
 | 
			
		||||
abstract class IdeaVimBuildType(init: BuildType.() -> Unit) : BuildType({
 | 
			
		||||
  artifactRules = """
 | 
			
		||||
        +:build/reports => build/reports
 | 
			
		||||
        +:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
 | 
			
		||||
  init()
 | 
			
		||||
 | 
			
		||||
  requirements {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.teamcity/_Self/buildTypes/ReleasePlugin.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.teamcity/_Self/buildTypes/ReleasePlugin.kt
									
									
									
									
										vendored
									
									
								
							@@ -118,6 +118,7 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
 | 
			
		||||
      then
 | 
			
		||||
        git checkout release
 | 
			
		||||
        echo checkout release branch
 | 
			
		||||
        git branch --set-upstream-to=origin/release release
 | 
			
		||||
        git push --tags
 | 
			
		||||
        git push origin --force
 | 
			
		||||
      fi
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.teamcity/_Self/subprojects/OldTests.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.teamcity/_Self/subprojects/OldTests.kt
									
									
									
									
										vendored
									
									
								
							@@ -20,4 +20,6 @@ object OldTests : Project({
 | 
			
		||||
  buildType(TestingBuildType("IC-2021.2.2", "203-212", javaVersion = "1.8", javaPlugin = false))
 | 
			
		||||
  buildType(TestingBuildType("IC-2021.3.2", "213-221", javaVersion = "1.8", javaPlugin = false))
 | 
			
		||||
  buildType(TestingBuildType("IC-2022.2.3", branch = "222", javaPlugin = false))
 | 
			
		||||
  buildType(TestingBuildType("IC-2023.1", "231-232", javaPlugin = false))
 | 
			
		||||
  buildType(TestingBuildType("IC-2023.2", "231-232", javaPlugin = false))
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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"
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								.teamcity/patches/buildTypes/ReleaseMinor.kts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								.teamcity/patches/buildTypes/ReleaseMinor.kts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
			
		||||
package patches.buildTypes
 | 
			
		||||
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
 | 
			
		||||
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 buildType with id = 'ReleaseMinor'
 | 
			
		||||
accordingly, and delete the patch script.
 | 
			
		||||
*/
 | 
			
		||||
changeBuildType(RelativeId("ReleaseMinor")) {
 | 
			
		||||
    params {
 | 
			
		||||
        expect {
 | 
			
		||||
            password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:3cd3e867-282c-451f-b958-bc95d56a8450", display = ParameterDisplay.HIDDEN)
 | 
			
		||||
        }
 | 
			
		||||
        update {
 | 
			
		||||
            password("env.ORG_GRADLE_PROJECT_youtrackToken", "credentialsJSON:7bc0eb3a-b86a-4ebd-b622-d4ef12d7e1d3", display = ParameterDisplay.HIDDEN)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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"
 | 
			
		||||
}
 | 
			
		||||
@@ -483,6 +483,14 @@ Contributors:
 | 
			
		||||
  [![icon][github]](https://github.com/ludwig-jb)
 | 
			
		||||
   
 | 
			
		||||
  ludwig-jb
 | 
			
		||||
* [![icon][mail]](mailto:pvydmuch@gmail.com)
 | 
			
		||||
  [![icon][github]](https://github.com/pWydmuch)
 | 
			
		||||
   
 | 
			
		||||
  pWydmuch
 | 
			
		||||
* [![icon][mail]](mailto:leonid989@gmail.com)
 | 
			
		||||
  [![icon][github]](https://github.com/Infonautica)
 | 
			
		||||
   
 | 
			
		||||
  Leonid Danilov
 | 
			
		||||
 | 
			
		||||
Previous contributors:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										62
									
								
								CHANGES.md
									
									
									
									
									
								
							
							
						
						
									
										62
									
								
								CHANGES.md
									
									
									
									
									
								
							@@ -25,11 +25,73 @@ usual beta standards.
 | 
			
		||||
 | 
			
		||||
## To Be Released
 | 
			
		||||
 | 
			
		||||
### Fixes:
 | 
			
		||||
* [VIM-3130](https://youtrack.jetbrains.com/issue/VIM-3130) Change the build version to 2023.1.2
 | 
			
		||||
* [VIM-3168](https://youtrack.jetbrains.com/issue/VIM-3168) Do not switch to block caret after enter if the IdeaVim is disabled
 | 
			
		||||
* [VIM-3165](https://youtrack.jetbrains.com/issue/VIM-3165) Do not process enter key as IdeaVim shortcut if it's not an actual keypress
 | 
			
		||||
* [VIM-3159](https://youtrack.jetbrains.com/issue/VIM-3159) Shift-enter now works in normal mode again
 | 
			
		||||
* [VIM-3157](https://youtrack.jetbrains.com/issue/VIM-3157) Do not invoke enter in invokeLater for python console
 | 
			
		||||
* [VIM-3195](https://youtrack.jetbrains.com/issue/VIM-3195) Fix escape in injected editor
 | 
			
		||||
* [VIM-3190](https://youtrack.jetbrains.com/issue/VIM-3190) Do not use octopus handler if the enter key is used with modifiers like shift or control
 | 
			
		||||
* [VIM-3203](https://youtrack.jetbrains.com/issue/VIM-3203) Split action not works in normal mode
 | 
			
		||||
* [VIM-3184](https://youtrack.jetbrains.com/issue/VIM-3184) Revert "VIM-3184: Temporally disable new handlers for the thin client"
 | 
			
		||||
* [VIM-3186](https://youtrack.jetbrains.com/issue/VIM-3186) Do not multiply the enter action by the amount of carets
 | 
			
		||||
* [VIM-3177](https://youtrack.jetbrains.com/issue/VIM-3177) Formatting of commit message works again
 | 
			
		||||
* [VIM-1611](https://youtrack.jetbrains.com/issue/VIM-1611) actions related to resolving conflicts doesn't seem to work
 | 
			
		||||
* [VIM-3204](https://youtrack.jetbrains.com/issue/VIM-3204) Add checker that verifies the configuratin of the keymap
 | 
			
		||||
* [VIM-3084](https://youtrack.jetbrains.com/issue/VIM-3084) Double update for the status bar icon
 | 
			
		||||
* [VIM-3176](https://youtrack.jetbrains.com/issue/VIM-3176) Reselecting visual selection after pasting above it select wrong lines
 | 
			
		||||
* [VIM-3206](https://youtrack.jetbrains.com/issue/VIM-3206) Disable both copilot suggestion and insert mode on a single escape
 | 
			
		||||
* [VIM-3090](https://youtrack.jetbrains.com/issue/VIM-3090) Cmd line mode saves the visual mode
 | 
			
		||||
* [VIM-3085](https://youtrack.jetbrains.com/issue/VIM-3085) Open access to VimTypedActionHandler and VimShortcutKeyAction
 | 
			
		||||
 | 
			
		||||
### Merged PRs:
 | 
			
		||||
* [763](https://github.com/JetBrains/ideavim/pull/763) by [Sam Ng](https://github.com/samabcde): Fix(VIM-3176) add test for restore selection after pasting in/below s…
 | 
			
		||||
* [772](https://github.com/JetBrains/ideavim/pull/772) by [chylex](https://github.com/chylex): Prevent code completion popup from appearing after running a macro
 | 
			
		||||
* [787](https://github.com/JetBrains/ideavim/pull/787) by [Leonid Danilov](https://github.com/Infonautica): Added "Which-Key" to Plugins
 | 
			
		||||
* [778](https://github.com/JetBrains/ideavim/pull/778) by [lippfi](https://github.com/lippfi): Showmode
 | 
			
		||||
* [788](https://github.com/JetBrains/ideavim/pull/788) by [Matt Ellis](https://github.com/citizenmatt): Refactor VimOptionGroupBase
 | 
			
		||||
 | 
			
		||||
## 2.7.0, 2023-11-07
 | 
			
		||||
 | 
			
		||||
### Fixes:
 | 
			
		||||
* [VIM-2933](https://youtrack.jetbrains.com/issue/VIM-2933) Reloading/sourcing .ideavimrc does not initialize new plugins
 | 
			
		||||
* [VIM-3138](https://youtrack.jetbrains.com/issue/VIM-3138) Do not try to register disposer if the caret is already disposed
 | 
			
		||||
 | 
			
		||||
### Merged PRs:
 | 
			
		||||
* [734](https://github.com/JetBrains/ideavim/pull/734) by [Matt Ellis](https://github.com/citizenmatt): Support `~/` on Windows
 | 
			
		||||
* [736](https://github.com/JetBrains/ideavim/pull/736) by [chylex](https://github.com/chylex): Fix(VIM-2933): Reloading/sourcing .ideavimrc does not initialize new plugins
 | 
			
		||||
 | 
			
		||||
## 2.6.3, 2023-10-30
 | 
			
		||||
 | 
			
		||||
### Changes:
 | 
			
		||||
- 2.6.0 and 2.6.1 releases are broken. Version 2.6.3 reverts IdeaVim plugin to the working state as for 2.5.1.
 | 
			
		||||
 | 
			
		||||
## 2.6.0, 2023-10-27
 | 
			
		||||
 | 
			
		||||
This version of IdeaVim contains a lot of issues. Version 2.6.3 reverts these changes.
 | 
			
		||||
 | 
			
		||||
### Features:
 | 
			
		||||
 | 
			
		||||
* `ShowHoverInfo` action can be used in mappings to open a tooltip that is shown by
 | 
			
		||||
  mouse hovering | [VIM-2106](https://youtrack.jetbrains.com/issue/VIM-2106)
 | 
			
		||||
* `has` Vim Script function supports the most common OS checks: win32, win64, linux, mac, macunix, osx, osxdarwin, bsd, sun, unix
 | 
			
		||||
  * See https://github.com/JetBrains/ideavim#vim-script for details about Vim Script
 | 
			
		||||
 | 
			
		||||
### Fixes:
 | 
			
		||||
* [VIM-3060](https://youtrack.jetbrains.com/issue/VIM-3060) Clipboard interaction stopped working
 | 
			
		||||
* [VIM-3095](https://youtrack.jetbrains.com/issue/VIM-3095) Fix missing ellipsis digraph
 | 
			
		||||
* [VIM-2562](https://youtrack.jetbrains.com/issue/VIM-2562) Fix hang with multi-width chars in command line
 | 
			
		||||
* [VIM-696](https://youtrack.jetbrains.com/issue/VIM-696) Vim selection issue after undo
 | 
			
		||||
* [VIM-1639](https://youtrack.jetbrains.com/issue/VIM-1639) Ctrl-o and Ctrl-i jumping in files of different projects
 | 
			
		||||
 | 
			
		||||
### Merged PRs:
 | 
			
		||||
* [697](https://github.com/JetBrains/ideavim/pull/697) by [Matt Ellis](https://github.com/citizenmatt): Support per-window "global" values for local-to-window options
 | 
			
		||||
* [717](https://github.com/JetBrains/ideavim/pull/717) by [Matt Ellis](https://github.com/citizenmatt): Fix(VIM-2562): Fix hang with multi-width chars in command line
 | 
			
		||||
* [732](https://github.com/JetBrains/ideavim/pull/732) by [pWydmuch](https://github.com/pWydmuch): Fix md links in doc
 | 
			
		||||
* [733](https://github.com/JetBrains/ideavim/pull/733) by [Matt Ellis](https://github.com/citizenmatt): Add support for ShowHoverInfo action to 2023.1 and 2023.2
 | 
			
		||||
* [729](https://github.com/JetBrains/ideavim/pull/729) by [chylex](https://github.com/chylex): Add operating system type to `has()` function
 | 
			
		||||
* [726](https://github.com/JetBrains/ideavim/pull/726) by [Matt Ellis](https://github.com/citizenmatt): Fix range for fall back comment mode
 | 
			
		||||
 | 
			
		||||
## 2.5.0, 2023-09-01
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -255,8 +255,7 @@ Ex commands or via `:map` command mappings:
 | 
			
		||||
##### Some popular actions:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
QuickJavaDoc - Quick Documentation (not only for java, all languages)
 | 
			
		||||
ShowErrorDescription - Show description of the error under the caret (cursor hovering)
 | 
			
		||||
ShowHoverInfo - Quick Documentation and Error Description
 | 
			
		||||
QuickImplementations - Quick Definition
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
@@ -325,7 +324,7 @@ IdeaVim tips and tricks
 | 
			
		||||
- Use the power of IJ and Vim:
 | 
			
		||||
    - `set ideajoin` to enable join via the IDE. See the [examples](https://jb.gg/f9zji9).
 | 
			
		||||
    - Make sure `ideaput` is enabled for `clipboard` to enable native IJ insertion in Vim.
 | 
			
		||||
    - Sync IJ bookmarks and Vim marks: `set ideamarks`
 | 
			
		||||
    - Sync IJ bookmarks and IdeaVim global marks: `set ideamarks` (works for marks with capital letters only)
 | 
			
		||||
    - Check out more [ex commands](https://github.com/JetBrains/ideavim/wiki/%22set%22-commands).
 | 
			
		||||
 | 
			
		||||
- Use your vim settings with IdeaVim. Put `source ~/.vimrc` in `~/.ideavimrc`.
 | 
			
		||||
 
 | 
			
		||||
@@ -8,8 +8,11 @@
 | 
			
		||||
 | 
			
		||||
plugins {
 | 
			
		||||
  kotlin("jvm")
 | 
			
		||||
  kotlin("plugin.serialization") version "1.8.21"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
val kotlinxSerializationVersion: String by project
 | 
			
		||||
 | 
			
		||||
group = "com.intellij"
 | 
			
		||||
version = "SNAPSHOT"
 | 
			
		||||
 | 
			
		||||
@@ -18,6 +21,10 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.10-1.0.13")
 | 
			
		||||
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0")
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.16")
 | 
			
		||||
  implementation("org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:$kotlinxSerializationVersion") {
 | 
			
		||||
    // kotlin stdlib is provided by IJ, so there is no need to include it into the distribution
 | 
			
		||||
    exclude("org.jetbrains.kotlin", "kotlin-stdlib")
 | 
			
		||||
    exclude("org.jetbrains.kotlin", "kotlin-stdlib-common")
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.intellij.vim.annotations
 | 
			
		||||
 | 
			
		||||
// TODO support numpad keys parsing, see :keycodes
 | 
			
		||||
/**
 | 
			
		||||
 * It's not necessary a Vim command
 | 
			
		||||
 * This annotation may be used for:
 | 
			
		||||
 * - commands
 | 
			
		||||
 * - motions
 | 
			
		||||
 */
 | 
			
		||||
@Target(AnnotationTarget.CLASS)
 | 
			
		||||
@Retention(AnnotationRetention.SOURCE)
 | 
			
		||||
annotation class CommandOrMotion(val keys: Array<String>, vararg val modes: Mode)
 | 
			
		||||
 | 
			
		||||
annotation class TextObject(val keys: String)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
enum class Mode(val abbrev: Char) {
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Normal mode
 | 
			
		||||
   */
 | 
			
		||||
  NORMAL('N'),
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Visual mode
 | 
			
		||||
   */
 | 
			
		||||
  VISUAL('X'),
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Select mode
 | 
			
		||||
   */
 | 
			
		||||
  SELECT('S'),
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Operator Pending mode
 | 
			
		||||
   */
 | 
			
		||||
  OP_PENDING('O'),
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Insert or Replace modes
 | 
			
		||||
   */
 | 
			
		||||
  INSERT('I'),
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Indicates this key mapping applies to Command Line mode
 | 
			
		||||
   */
 | 
			
		||||
  CMD_LINE('C'),
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,14 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.intellij.vim.processors
 | 
			
		||||
 | 
			
		||||
import kotlinx.serialization.Serializable
 | 
			
		||||
 | 
			
		||||
@Serializable
 | 
			
		||||
data class CommandBean(val keys: String, val `class`: String, val modes: String)
 | 
			
		||||
@@ -0,0 +1,62 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.intellij.vim.processors
 | 
			
		||||
 | 
			
		||||
import com.google.devtools.ksp.KspExperimental
 | 
			
		||||
import com.google.devtools.ksp.getAnnotationsByType
 | 
			
		||||
import com.google.devtools.ksp.processing.Resolver
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessor
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 | 
			
		||||
import com.google.devtools.ksp.symbol.KSAnnotated
 | 
			
		||||
import com.google.devtools.ksp.symbol.KSClassDeclaration
 | 
			
		||||
import com.google.devtools.ksp.symbol.KSFile
 | 
			
		||||
import com.google.devtools.ksp.symbol.KSVisitorVoid
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
import kotlin.io.path.Path
 | 
			
		||||
import kotlin.io.path.writeText
 | 
			
		||||
 | 
			
		||||
class CommandOrMotionProcessor(private val environment: SymbolProcessorEnvironment): SymbolProcessor {
 | 
			
		||||
  private val visitor = CommandOrMotionVisitor()
 | 
			
		||||
  private val commands = mutableListOf<CommandBean>()
 | 
			
		||||
 | 
			
		||||
  private val json = Json { prettyPrint = true }
 | 
			
		||||
 | 
			
		||||
  override fun process(resolver: Resolver): List<KSAnnotated> {
 | 
			
		||||
    resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
 | 
			
		||||
 | 
			
		||||
    val generatedDirPath = Path(environment.options["generated_directory"]!!)
 | 
			
		||||
    Files.createDirectories(generatedDirPath)
 | 
			
		||||
 | 
			
		||||
    val filePath = generatedDirPath.resolve(environment.options["commands_file"]!!)
 | 
			
		||||
    val fileContent = json.encodeToString(commands)
 | 
			
		||||
    filePath.writeText(fileContent)
 | 
			
		||||
 | 
			
		||||
    return emptyList()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private inner class CommandOrMotionVisitor : KSVisitorVoid() {
 | 
			
		||||
    @OptIn(KspExperimental::class)
 | 
			
		||||
    override fun visitClassDeclaration(classDeclaration: KSClassDeclaration, data: Unit) {
 | 
			
		||||
      val commandAnnotation = classDeclaration.getAnnotationsByType(CommandOrMotion::class).firstOrNull() ?: return
 | 
			
		||||
      for (key in commandAnnotation.keys) {
 | 
			
		||||
        commands.add(
 | 
			
		||||
          CommandBean(key, classDeclaration.qualifiedName!!.asString(), commandAnnotation.modes.map { it.abbrev }.joinToString(separator = ""))
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun visitFile(file: KSFile, data: Unit) {
 | 
			
		||||
      file.declarations.forEach { it.accept(this, Unit) }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -20,6 +20,7 @@ import com.google.devtools.ksp.symbol.KSVisitorVoid
 | 
			
		||||
import com.intellij.vim.annotations.ExCommand
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
import kotlin.io.path.Path
 | 
			
		||||
import kotlin.io.path.writeText
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +32,11 @@ class ExCommandProcessor(private val environment: SymbolProcessorEnvironment): S
 | 
			
		||||
 | 
			
		||||
  override fun process(resolver: Resolver): List<KSAnnotated> {
 | 
			
		||||
    resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
 | 
			
		||||
    val filePath = Path(environment.options["generated_directory"]!!, environment.options["ex_commands_file"]!!)
 | 
			
		||||
 | 
			
		||||
    val generatedDirPath = Path(environment.options["generated_directory"]!!)
 | 
			
		||||
    Files.createDirectories(generatedDirPath)
 | 
			
		||||
 | 
			
		||||
    val filePath = generatedDirPath.resolve(environment.options["ex_commands_file"]!!)
 | 
			
		||||
    val fileContent = json.encodeToString(commandToClass)
 | 
			
		||||
    filePath.writeText(fileContent)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ import com.google.devtools.ksp.symbol.KSVisitorVoid
 | 
			
		||||
import com.intellij.vim.annotations.VimscriptFunction
 | 
			
		||||
import kotlinx.serialization.encodeToString
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import java.nio.file.Files
 | 
			
		||||
import kotlin.io.path.Path
 | 
			
		||||
import kotlin.io.path.writeText
 | 
			
		||||
 | 
			
		||||
@@ -31,7 +32,11 @@ class VimscriptFunctionProcessor(private val environment: SymbolProcessorEnviron
 | 
			
		||||
 | 
			
		||||
  override fun process(resolver: Resolver): List<KSAnnotated> {
 | 
			
		||||
    resolver.getAllFiles().forEach { it.accept(visitor, Unit) }
 | 
			
		||||
    val filePath = Path(environment.options["generated_directory"]!!, environment.options["vimscript_functions_file"]!!)
 | 
			
		||||
 | 
			
		||||
    val generatedDirPath = Path(environment.options["generated_directory"]!!)
 | 
			
		||||
    Files.createDirectories(generatedDirPath)
 | 
			
		||||
 | 
			
		||||
    val filePath = generatedDirPath.resolve(environment.options["vimscript_functions_file"]!!)
 | 
			
		||||
    val fileContent = json.encodeToString(nameToClass)
 | 
			
		||||
    filePath.writeText(fileContent)
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,20 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.intellij.vim.providers
 | 
			
		||||
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessor
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 | 
			
		||||
import com.google.devtools.ksp.processing.SymbolProcessorProvider
 | 
			
		||||
import com.intellij.vim.processors.CommandOrMotionProcessor
 | 
			
		||||
 | 
			
		||||
class CommandOrMotionProcessorProvider : SymbolProcessorProvider {
 | 
			
		||||
  override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
 | 
			
		||||
    return CommandOrMotionProcessor(environment)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,2 +1,3 @@
 | 
			
		||||
com.intellij.vim.providers.VimscriptFunctionProcessorProvider
 | 
			
		||||
com.intellij.vim.providers.CommandOrMotionProcessorProvider
 | 
			
		||||
com.intellij.vim.providers.ExCommandProcessorProvider
 | 
			
		||||
com.intellij.vim.providers.VimscriptFunctionProcessorProvider
 | 
			
		||||
 
 | 
			
		||||
@@ -49,14 +49,14 @@ buildscript {
 | 
			
		||||
        classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
			
		||||
 | 
			
		||||
        // This is needed for jgit to connect to ssh
 | 
			
		||||
        classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
 | 
			
		||||
        classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
        classpath("org.kohsuke:github-api:1.305")
 | 
			
		||||
 | 
			
		||||
        classpath("io.ktor:ktor-client-core:2.3.4")
 | 
			
		||||
        classpath("io.ktor:ktor-client-cio:2.3.4")
 | 
			
		||||
        classpath("io.ktor:ktor-client-auth:2.3.4")
 | 
			
		||||
        classpath("io.ktor:ktor-client-content-negotiation:2.3.4")
 | 
			
		||||
        classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.4")
 | 
			
		||||
        classpath("io.ktor:ktor-client-core:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-client-cio:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-client-auth:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
			
		||||
        classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
			
		||||
 | 
			
		||||
        // This comes from the changelog plugin
 | 
			
		||||
//        classpath("org.jetbrains:markdown:0.3.1")
 | 
			
		||||
@@ -69,7 +69,7 @@ plugins {
 | 
			
		||||
    kotlin("jvm") version "1.8.21"
 | 
			
		||||
    application
 | 
			
		||||
 | 
			
		||||
    id("org.jetbrains.intellij") version "1.15.0"
 | 
			
		||||
    id("org.jetbrains.intellij") version "1.16.1"
 | 
			
		||||
    id("org.jetbrains.changelog") version "2.2.0"
 | 
			
		||||
 | 
			
		||||
    // ktlint linter - read more: https://github.com/JLLeitschuh/ktlint-gradle
 | 
			
		||||
@@ -82,9 +82,10 @@ plugins {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ksp {
 | 
			
		||||
  arg("generated_directory", "$projectDir/src/main/resources")
 | 
			
		||||
  arg("generated_directory", "$projectDir/src/main/resources/ksp-generated")
 | 
			
		||||
  arg("vimscript_functions_file", "intellij_vimscript_functions.json")
 | 
			
		||||
  arg("ex_commands_file", "intellij_ex_commands.json")
 | 
			
		||||
  arg("commands_file", "intellij_commands.json")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
afterEvaluate {
 | 
			
		||||
@@ -115,7 +116,7 @@ repositories {
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
    compileOnly("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
 | 
			
		||||
    compileOnly("org.jetbrains:annotations:24.0.1")
 | 
			
		||||
    compileOnly("org.jetbrains:annotations:24.1.0")
 | 
			
		||||
 | 
			
		||||
    // https://mvnrepository.com/artifact/com.ensarsarajcic.neovim.java/neovim-api
 | 
			
		||||
    testImplementation("com.ensarsarajcic.neovim.java:neovim-api:0.2.3")
 | 
			
		||||
@@ -125,24 +126,24 @@ dependencies {
 | 
			
		||||
    testImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
 | 
			
		||||
 | 
			
		||||
    // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
 | 
			
		||||
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.1.0")
 | 
			
		||||
    testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
 | 
			
		||||
 | 
			
		||||
    testImplementation("com.intellij.remoterobot:remote-robot:$remoteRobotVersion")
 | 
			
		||||
    testImplementation("com.intellij.remoterobot:remote-fixtures:$remoteRobotVersion")
 | 
			
		||||
    testImplementation("com.automation-remarks:video-recorder-junit:2.0")
 | 
			
		||||
    testImplementation("com.automation-remarks:video-recorder-junit5:2.0")
 | 
			
		||||
    runtimeOnly("org.antlr:antlr4-runtime:$antlrVersion")
 | 
			
		||||
    antlr("org.antlr:antlr4:$antlrVersion")
 | 
			
		||||
 | 
			
		||||
    api(project(":vim-engine"))
 | 
			
		||||
 | 
			
		||||
    ksp(project(":annotation-processors"))
 | 
			
		||||
    compileOnly(project(":annotation-processors"))
 | 
			
		||||
    implementation(project(":annotation-processors"))
 | 
			
		||||
 | 
			
		||||
    testApi("com.squareup.okhttp3:okhttp:4.11.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-engine:5.10.0")
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.0")
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
 | 
			
		||||
    testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
configurations {
 | 
			
		||||
@@ -183,6 +184,14 @@ tasks {
 | 
			
		||||
        include("**/*test.class")
 | 
			
		||||
        include("**/*Tests.class")
 | 
			
		||||
        exclude("**/ParserTest.class")
 | 
			
		||||
 | 
			
		||||
        // Set teamcity env variable locally to run additional tests for leaks.
 | 
			
		||||
        // By default, this test runs on TC only, but this test doesn't take a lot of time,
 | 
			
		||||
        //   so we can turn it on for local development
 | 
			
		||||
        if (environment["TEAMCITY_VERSION"] == null) {
 | 
			
		||||
            println("Set env TEAMCITY_VERSION to X")
 | 
			
		||||
            environment("TEAMCITY_VERSION" to "X")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val testWithNeovim by getting(Test::class) {
 | 
			
		||||
@@ -293,6 +302,7 @@ tasks {
 | 
			
		||||
        systemProperty("ide.mac.message.dialogs.as.sheets", "false")
 | 
			
		||||
        systemProperty("jb.privacy.policy.text", "<!--999.999-->")
 | 
			
		||||
        systemProperty("jb.consents.confirmation.enabled", "false")
 | 
			
		||||
        systemProperty("ide.show.tips.on.startup.default.value", "false")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    runPluginVerifier {
 | 
			
		||||
@@ -343,8 +353,6 @@ tasks {
 | 
			
		||||
    val pluginVersion = version
 | 
			
		||||
    // Don't forget to update plugin.xml
 | 
			
		||||
    patchPluginXml {
 | 
			
		||||
        sinceBuild.set("231.7515.13")
 | 
			
		||||
 | 
			
		||||
        // Get the latest available change notes from the changelog file
 | 
			
		||||
        changeNotes.set(
 | 
			
		||||
            provider {
 | 
			
		||||
@@ -523,10 +531,12 @@ tasks.register("releaseActions") {
 | 
			
		||||
        if (tickets.isNotEmpty()) {
 | 
			
		||||
            println("Updating statuses for tickets: $tickets")
 | 
			
		||||
            setYoutrackStatus(tickets, "Fixed")
 | 
			
		||||
            if (getVersionIdByName(version.toString()) != null) {
 | 
			
		||||
            println("Checking if version $version exists...")
 | 
			
		||||
            val versionId = getVersionIdByName(version.toString())
 | 
			
		||||
            if (versionId == null) {
 | 
			
		||||
                addReleaseToYoutrack(version.toString())
 | 
			
		||||
            } else {
 | 
			
		||||
                println("Version $version is already exists in YouTrack")
 | 
			
		||||
                println("Version $version already exists in YouTrack. Version id: $versionId")
 | 
			
		||||
            }
 | 
			
		||||
            setYoutrackFixVersion(tickets, version.toString())
 | 
			
		||||
        } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
Welcome to the IdeaVim wiki!
 | 
			
		||||
 | 
			
		||||
- List of IdeaVim plugins: [[plugins|IdeaVim Plugins]]
 | 
			
		||||
- Examples of `ideajoin` option (also known as "smart join"): [["ideajoin" examples|ideajoin-examples]]
 | 
			
		||||
- List of "set" commands: [["set" commands|set-commands]]
 | 
			
		||||
- Docs about "select" mode in vim: [[select mode|Select-mode]]
 | 
			
		||||
- List of IdeaVim plugins: [plugins](IdeaVim%20Plugins.md)
 | 
			
		||||
- Examples of `ideajoin` option (also known as "smart join"): ["ideajoin" examples](ideajoin-examples.md)
 | 
			
		||||
- List of "set" commands: ["set" commands](set-commands.md)
 | 
			
		||||
- Docs about "select" mode in vim: [select mode](Select-mode.md)
 | 
			
		||||
 
 | 
			
		||||
@@ -77,7 +77,7 @@ Original plugin: [NERDTree](https://github.com/preservim/nerdtree).
 | 
			
		||||
   
 | 
			
		||||
### Instructions
 | 
			
		||||
   
 | 
			
		||||
[[See here|NERDTree-support]].
 | 
			
		||||
[See here](NERDTree-support.md).
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
@@ -396,3 +396,19 @@ Original plugin: [quick-scope](https://github.com/unblevable/quick-scope).
 | 
			
		||||
https://plugins.jetbrains.com/plugin/19417-ideavim-quickscope
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
<details>
 | 
			
		||||
<summary><h2>Which-Key</h2></summary>
 | 
			
		||||
 | 
			
		||||
Original plugin: [vim-which-key](https://github.com/liuchengxu/vim-which-key).
 | 
			
		||||
 | 
			
		||||
### Setup:
 | 
			
		||||
- Install [Which-Key](https://plugins.jetbrains.com/plugin/15976-which-key) plugin.
 | 
			
		||||
- Add the following command to `~/.ideavimrc`: `set which-key`
 | 
			
		||||
 | 
			
		||||
### Instructions
 | 
			
		||||
 | 
			
		||||
https://github.com/TheBlob42/idea-which-key?tab=readme-ov-file#installation
 | 
			
		||||
 | 
			
		||||
</details>
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,11 @@ Put `set ideajoin` to your `~/.ideavimrc` to enable this functionality.
 | 
			
		||||
 | 
			
		||||
Now, you can press `J` (`shift+j`) on a line or a selected block of text to join the lines together.
 | 
			
		||||
 | 
			
		||||
:warning: This feature is language-specific. This means that the IDE should implement this feature for a particular
 | 
			
		||||
language in order for the IDE to work as described below. If any of the examples provided below don't match your case, 
 | 
			
		||||
please file an issue in the project related to your IDE: https://youtrack.jetbrains.com/.  
 | 
			
		||||
Here is a list of known requests: https://youtrack.jetbrains.com/issues?q=links:VIM-3214.
 | 
			
		||||
 | 
			
		||||
* Automatic join concatenated lines:
 | 
			
		||||
 | 
			
		||||
```
 | 
			
		||||
 
 | 
			
		||||
@@ -8,21 +8,26 @@
 | 
			
		||||
 | 
			
		||||
# suppress inspection "UnusedProperty" for whole file
 | 
			
		||||
 | 
			
		||||
ideaVersion=2023.2.1
 | 
			
		||||
ideaVersion=2023.3.2
 | 
			
		||||
downloadIdeaSources=true
 | 
			
		||||
instrumentPluginCode=true
 | 
			
		||||
version=chylex-16
 | 
			
		||||
version=chylex-24
 | 
			
		||||
javaVersion=17
 | 
			
		||||
remoteRobotVersion=0.11.17
 | 
			
		||||
remoteRobotVersion=0.11.21
 | 
			
		||||
antlrVersion=4.10.1
 | 
			
		||||
 | 
			
		||||
kotlin.incremental.useClasspathSnapshot=false
 | 
			
		||||
 | 
			
		||||
# Please don't forget to update kotlin version in buildscript section
 | 
			
		||||
# Also update kotlinxSerializationVersion version
 | 
			
		||||
kotlinVersion=1.8.21
 | 
			
		||||
publishToken=token
 | 
			
		||||
publishChannels=eap
 | 
			
		||||
 | 
			
		||||
# Kotlinx serialization also uses some version of kotlin stdlib under the hood. However,
 | 
			
		||||
#   we exclude this version from the dependency and use our own version of kotlin that is specified above
 | 
			
		||||
kotlinxSerializationVersion=1.5.1
 | 
			
		||||
 | 
			
		||||
slackUrl=
 | 
			
		||||
youtrackToken=
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,17 +20,17 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.10")
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
 | 
			
		||||
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.4")
 | 
			
		||||
  implementation("io.ktor:ktor-client-cio:2.3.4")
 | 
			
		||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.4")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.4")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.4")
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-cio:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.7")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
			
		||||
 | 
			
		||||
  // This is needed for jgit to connect to ssh
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.7.0.202309050840-r")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
  implementation("com.vdurmont:semver4j:3.1.0")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
 | 
			
		||||
  println("HI!")
 | 
			
		||||
  val projectDir = args[0]
 | 
			
		||||
  println("Working directory: $projectDir")
 | 
			
		||||
  val (lastVersion, objectId) = getVersion(projectDir, onlyStable = true)
 | 
			
		||||
  val (lastVersion, objectId) = getVersion(projectDir, ReleaseType.STABLE_NO_PATCH)
 | 
			
		||||
  println("Last version: $lastVersion, hash: ${objectId.name}")
 | 
			
		||||
 | 
			
		||||
  val branch = withRepo(projectDir) { it.branch }
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@ fun main(args: Array<String>) {
 | 
			
		||||
  println("HI!")
 | 
			
		||||
  val projectDir = args[0]
 | 
			
		||||
  println("Working directory: $projectDir")
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = false)
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, ReleaseType.ANY)
 | 
			
		||||
 | 
			
		||||
  val nextVersion = if (lastVersion.suffixTokens.isEmpty()) {
 | 
			
		||||
    lastVersion.nextMinor().withSuffix("eap.1").value
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ fun main(args: Array<String>) {
 | 
			
		||||
  val releaseType = args[1]
 | 
			
		||||
  println("Working directory: $projectDir")
 | 
			
		||||
  println("Release type: $releaseType")
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, onlyStable = true)
 | 
			
		||||
  val (lastVersion, _) = getVersion(projectDir, ReleaseType.ONLY_STABLE)
 | 
			
		||||
 | 
			
		||||
  val nextVersion = when (releaseType) {
 | 
			
		||||
    "major" -> lastVersion.nextMajor()
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
package scripts.release
 | 
			
		||||
 | 
			
		||||
import com.vdurmont.semver4j.Semver
 | 
			
		||||
import org.eclipse.jgit.api.CreateBranchCommand
 | 
			
		||||
import org.eclipse.jgit.api.Git
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId
 | 
			
		||||
import org.eclipse.jgit.lib.Repository
 | 
			
		||||
@@ -57,7 +58,13 @@ internal fun checkBranch(rootDir: String, releaseType: String) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, ObjectId> {
 | 
			
		||||
enum class ReleaseType {
 | 
			
		||||
  ANY,
 | 
			
		||||
  ONLY_STABLE,
 | 
			
		||||
  STABLE_NO_PATCH, // Version that ends on 0. Like 2.5.0
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun getVersion(projectDir: String, releaseType: ReleaseType): Pair<Semver, ObjectId> {
 | 
			
		||||
  val repository = RepositoryBuilder().setGitDir(File("$projectDir/.git")).build()
 | 
			
		||||
  val git = Git(repository)
 | 
			
		||||
  println(git.log().call().first())
 | 
			
		||||
@@ -74,19 +81,24 @@ internal fun getVersion(projectDir: String, onlyStable: Boolean): Pair<Semver, O
 | 
			
		||||
  }
 | 
			
		||||
    .sortedBy { it.first }
 | 
			
		||||
 | 
			
		||||
  val version = if (onlyStable) {
 | 
			
		||||
    versions.last { it.first.isStable }
 | 
			
		||||
  } else {
 | 
			
		||||
    versions.last()
 | 
			
		||||
  val version = when (releaseType) {
 | 
			
		||||
    ReleaseType.ANY -> versions.last()
 | 
			
		||||
    ReleaseType.ONLY_STABLE -> versions.last { it.first.isStable }
 | 
			
		||||
    ReleaseType.STABLE_NO_PATCH -> versions.last { it.first.isStable && it.first.patch == 0 }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return version
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun Git.checkoutBranch(name: String) {
 | 
			
		||||
  println("Checking out $name")
 | 
			
		||||
  val shouldCreateBranch = this.branchList().call().any { it.name == "refs/heads/$name" }.not()
 | 
			
		||||
  checkout()
 | 
			
		||||
  val checkoutCommand = checkout()
 | 
			
		||||
    .setCreateBranch(shouldCreateBranch)
 | 
			
		||||
    .setName(name)
 | 
			
		||||
    .call()
 | 
			
		||||
  if (shouldCreateBranch) {
 | 
			
		||||
    // Without starting point the branch will be created on HEAD.
 | 
			
		||||
    checkoutCommand.setStartPoint("origin/$name").setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.TRACK)
 | 
			
		||||
  }
 | 
			
		||||
  checkoutCommand.call()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,7 @@ package com.maddyhome.idea.vim
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.project.ProjectManagerListener
 | 
			
		||||
import com.intellij.openapi.startup.StartupActivity
 | 
			
		||||
import com.intellij.openapi.startup.ProjectActivity
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.localEditors
 | 
			
		||||
@@ -20,16 +20,11 @@ import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
/**
 | 
			
		||||
 * @author Alex Plate
 | 
			
		||||
 */
 | 
			
		||||
// This service should be migrated to ProjectActivity. But we should cariful because simple replacement
 | 
			
		||||
// leads to deadlock in tests. I'm not sure about the exact reasons, but "invokeAndWait" inside "initialize" function
 | 
			
		||||
// causes this deadlock. Good new: it's easy reproducible in tests.
 | 
			
		||||
// Previous migration: fc7efd5484a13b40ba9bf86a1d5429e215d973f3
 | 
			
		||||
// Revert: 24dd84b31cffb99eb6114524859a46d02717d33f
 | 
			
		||||
internal class PluginStartup : StartupActivity.DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
 | 
			
		||||
 | 
			
		||||
  private var firstInitializationOccurred = false
 | 
			
		||||
 | 
			
		||||
  override fun runActivity(project: Project) {
 | 
			
		||||
  override suspend fun execute(project: Project) {
 | 
			
		||||
    if (firstInitializationOccurred) return
 | 
			
		||||
    firstInitializationOccurred = true
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,79 +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;
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager;
 | 
			
		||||
import com.intellij.openapi.extensions.ExtensionPointName;
 | 
			
		||||
import com.maddyhome.idea.vim.group.KeyGroup;
 | 
			
		||||
import com.maddyhome.idea.vim.handler.ActionBeanClass;
 | 
			
		||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
 | 
			
		||||
import com.maddyhome.idea.vim.key.MappingOwner;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import javax.swing.*;
 | 
			
		||||
import java.awt.event.KeyEvent;
 | 
			
		||||
 | 
			
		||||
public class RegisterActions {
 | 
			
		||||
 | 
			
		||||
  public static final ExtensionPointName<ActionBeanClass> VIM_ACTIONS_EP =
 | 
			
		||||
    ExtensionPointName.create("IdeaVIM.vimAction");
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Register all the key/action mappings for the plugin.
 | 
			
		||||
   */
 | 
			
		||||
  public static void registerActions() {
 | 
			
		||||
    registerVimCommandActions();
 | 
			
		||||
    registerEmptyShortcuts();
 | 
			
		||||
    registerEpListener();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void registerEpListener() {
 | 
			
		||||
    // IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
 | 
			
		||||
    //   but let's support dynamic plugins in a more classic way and reload actions on every EP change.
 | 
			
		||||
    VIM_ACTIONS_EP.addChangeListener(() -> {
 | 
			
		||||
      unregisterActions();
 | 
			
		||||
      registerActions();
 | 
			
		||||
    }, VimPlugin.getInstance());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @Nullable EditorActionHandlerBase findAction(@NotNull String id) {
 | 
			
		||||
    return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream()
 | 
			
		||||
        .filter(vimActionBean -> vimActionBean.getActionId().equals(id)).findFirst().map(ActionBeanClass::getInstance)
 | 
			
		||||
        .orElse(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @NotNull EditorActionHandlerBase findActionOrDie(@NotNull String id) {
 | 
			
		||||
    EditorActionHandlerBase action = findAction(id);
 | 
			
		||||
    if (action == null) throw new RuntimeException("Action " + id + " is not registered");
 | 
			
		||||
    return action;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static void unregisterActions() {
 | 
			
		||||
    KeyGroup keyGroup = VimPlugin.getKeyIfCreated();
 | 
			
		||||
    if (keyGroup != null) {
 | 
			
		||||
      keyGroup.unregisterCommandActions();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void registerVimCommandActions() {
 | 
			
		||||
    KeyGroup parser = VimPlugin.getKey();
 | 
			
		||||
    VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map(IjVimActionsInitiator::new)
 | 
			
		||||
        .forEach(parser::registerCommandAction);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void registerEmptyShortcuts() {
 | 
			
		||||
    final KeyGroup parser = VimPlugin.getKey();
 | 
			
		||||
 | 
			
		||||
    // The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
 | 
			
		||||
    // still need to register the shortcut, to make sure the editor doesn't swallow it.
 | 
			
		||||
    parser
 | 
			
		||||
      .registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System.INSTANCE);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										100
									
								
								src/main/java/com/maddyhome/idea/vim/RegisterActions.kt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								src/main/java/com/maddyhome/idea/vim/RegisterActions.kt
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,100 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.extensions.ExtensionPointName
 | 
			
		||||
import com.maddyhome.idea.vim.action.EngineCommandProvider
 | 
			
		||||
import com.maddyhome.idea.vim.action.IntellijCommandProvider
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.handler.ActionBeanClass
 | 
			
		||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
 | 
			
		||||
import com.maddyhome.idea.vim.key.MappingOwner
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimActionsInitiator
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
public object RegisterActions {
 | 
			
		||||
  @Deprecated("Please use @CommandOrMotion annotation instead")
 | 
			
		||||
  internal val VIM_ACTIONS_EP: ExtensionPointName<ActionBeanClass> = ExtensionPointName.create("IdeaVIM.vimAction")
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Register all the key/action mappings for the plugin.
 | 
			
		||||
   */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun registerActions() {
 | 
			
		||||
    registerVimCommandActions()
 | 
			
		||||
    if (!injector.globalIjOptions().commandOrMotionAnnotation) {
 | 
			
		||||
      registerEmptyShortcuts()
 | 
			
		||||
      registerEpListener()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Deprecated("Moving to annotations approach instead of xml")
 | 
			
		||||
  private fun registerEpListener() {
 | 
			
		||||
    // IdeaVim doesn't support contribution to VIM_ACTIONS_EP extension point, so technically we can skip this update,
 | 
			
		||||
    //   but let's support dynamic plugins in a more classic way and reload actions on every EP change.
 | 
			
		||||
    VIM_ACTIONS_EP.addChangeListener({
 | 
			
		||||
      unregisterActions()
 | 
			
		||||
      registerActions()
 | 
			
		||||
    }, VimPlugin.getInstance())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun findAction(id: String): EditorActionHandlerBase? {
 | 
			
		||||
    if (injector.globalIjOptions().commandOrMotionAnnotation) {
 | 
			
		||||
      val commandBean = EngineCommandProvider.getCommands().firstOrNull { it.actionId == id }
 | 
			
		||||
        ?: IntellijCommandProvider.getCommands().firstOrNull { it.actionId == id } ?: return null
 | 
			
		||||
      return commandBean.instance
 | 
			
		||||
    } else {
 | 
			
		||||
      return VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream()
 | 
			
		||||
        .filter { vimActionBean: ActionBeanClass -> vimActionBean.actionId == id }
 | 
			
		||||
        .findFirst().map { obj: ActionBeanClass -> obj.instance }
 | 
			
		||||
        .orElse(null)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun findActionOrDie(id: String): EditorActionHandlerBase {
 | 
			
		||||
    return findAction(id) ?: throw RuntimeException("Action $id is not registered")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun unregisterActions() {
 | 
			
		||||
    val keyGroup = VimPlugin.getKeyIfCreated()
 | 
			
		||||
    keyGroup?.unregisterCommandActions()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun registerVimCommandActions() {
 | 
			
		||||
    val parser = VimPlugin.getKey()
 | 
			
		||||
    if (injector.globalIjOptions().commandOrMotionAnnotation) {
 | 
			
		||||
      EngineCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
 | 
			
		||||
      IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
 | 
			
		||||
    } else {
 | 
			
		||||
      VIM_ACTIONS_EP.getExtensionList(ApplicationManager.getApplication()).stream().map { bean: ActionBeanClass? ->
 | 
			
		||||
        IjVimActionsInitiator(
 | 
			
		||||
          bean!!
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
        .forEach { actionHolder: IjVimActionsInitiator? ->
 | 
			
		||||
          parser.registerCommandAction(
 | 
			
		||||
            actionHolder!!
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // todo do we really need this?
 | 
			
		||||
  private fun registerEmptyShortcuts() {
 | 
			
		||||
    val parser = VimPlugin.getKey()
 | 
			
		||||
 | 
			
		||||
    // The {char1} <BS> {char2} shortcut is handled directly by KeyHandler#handleKey, so doesn't have an action. But we
 | 
			
		||||
    // still need to register the shortcut, to make sure the editor doesn't swallow it.
 | 
			
		||||
    parser
 | 
			
		||||
      .registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -219,6 +219,10 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
    return getInstance().enabled;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static boolean isNotEnabled() {
 | 
			
		||||
    return !isEnabled();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static void setEnabled(final boolean enabled) {
 | 
			
		||||
    if (isEnabled() == enabled) return;
 | 
			
		||||
 | 
			
		||||
@@ -232,7 +236,13 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
      getInstance().turnOnPlugin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    StatusBarIconFactory.Companion.updateIcon();
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
 | 
			
		||||
    } else {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    StatusBarIconFactory.Util.INSTANCE.updateIcon();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static String getMessage() {
 | 
			
		||||
@@ -264,7 +274,8 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      Application application = ApplicationManager.getApplication();
 | 
			
		||||
      if (application.isUnitTestMode()) {
 | 
			
		||||
        application.invokeAndWait(this::turnOnPlugin);
 | 
			
		||||
        turnOnPlugin();
 | 
			
		||||
        //application.invokeAndWait(this::turnOnPlugin);
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        application.invokeLater(this::turnOnPlugin);
 | 
			
		||||
@@ -306,11 +317,6 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
   * This is required to ensure that all options are correctly initialised and registered. Must be before any commands
 | 
			
		||||
   * are executed.</li>
 | 
			
		||||
   * <li>~/.ideavimrc execution<br>
 | 
			
		||||
   * <ul>
 | 
			
		||||
   * <li>4.1 executes commands from the .ideavimrc file and 4.2 initializes extensions.</li>
 | 
			
		||||
   * <li>4.1 MUST BE BEFORE 4.2. This is a flow of vim/IdeaVim initialization, firstly .ideavimrc is executed and then
 | 
			
		||||
   * the extensions are initialized.</li>
 | 
			
		||||
   * </ul>
 | 
			
		||||
   * </li>
 | 
			
		||||
   * <li>Components initialization<br>
 | 
			
		||||
   * This should happen after ideavimrc execution because VimListenerManager accesses `number` option
 | 
			
		||||
@@ -339,13 +345,9 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
    VimInjectorKt.getInjector().getOptionGroup().initialiseOptions();
 | 
			
		||||
 | 
			
		||||
    // 4) ~/.ideavimrc execution
 | 
			
		||||
    // 4.1) Execute ~/.ideavimrc
 | 
			
		||||
    // Evaluate in the context of the fallback window, to capture local option state, to copy to the first editor window
 | 
			
		||||
    registerIdeavimrc(VimInjectorKt.getInjector().getFallbackWindow());
 | 
			
		||||
 | 
			
		||||
    // 4.2) Initialize extensions. Always after 4.1
 | 
			
		||||
    VimExtensionRegistrar.enableDelayedExtensions();
 | 
			
		||||
 | 
			
		||||
    // Turing on should be performed after all commands registration
 | 
			
		||||
    getSearch().turnOn();
 | 
			
		||||
    VimListenerManager.INSTANCE.turnOn();
 | 
			
		||||
 
 | 
			
		||||
@@ -28,8 +28,11 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * Accepts all regular keystrokes and passes them on to the Vim key handler.
 | 
			
		||||
 *
 | 
			
		||||
 * IDE shortcut keys used by Vim commands are handled by [com.maddyhome.idea.vim.action.VimShortcutKeyAction].
 | 
			
		||||
 *
 | 
			
		||||
 * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
 | 
			
		||||
 *   way to get ideavim keys for this plugin. See VIM-3085
 | 
			
		||||
 */
 | 
			
		||||
internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActionHandlerEx {
 | 
			
		||||
  private val handler = KeyHandler.getInstance()
 | 
			
		||||
  private val traceTime = injector.globalOptions().ideatracetime
 | 
			
		||||
 | 
			
		||||
@@ -86,7 +89,7 @@ internal class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedAct
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
  internal companion object {
 | 
			
		||||
    private val LOG = logger<VimTypedActionHandler>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
@@ -14,6 +16,7 @@ import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = [":"], modes = [Mode.NORMAL, Mode.VISUAL, Mode.OP_PENDING])
 | 
			
		||||
internal class ExEntryAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_READONLY
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,13 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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
 | 
			
		||||
 | 
			
		||||
public object IntellijCommandProvider : CommandProvider {
 | 
			
		||||
  override val commandListFileName: String = "intellij_commands.json"
 | 
			
		||||
}
 | 
			
		||||
@@ -28,7 +28,6 @@ import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.handler.enableOctopus
 | 
			
		||||
import com.maddyhome.idea.vim.handler.isOctopusEnabled
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.HandlerInjector
 | 
			
		||||
@@ -55,9 +54,17 @@ import javax.swing.KeyStroke
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 * These keys are not passed to [com.maddyhome.idea.vim.VimTypedActionHandler] and should be handled by actions.
 | 
			
		||||
 *
 | 
			
		||||
 * This class is used in Which-Key plugin, so don't make it internal. Generally, we should provide a proper
 | 
			
		||||
 *   way to get ideavim keys for this plugin. See VIM-3085
 | 
			
		||||
 */
 | 
			
		||||
internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
  private val traceTime = injector.globalOptions().ideatracetime
 | 
			
		||||
public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible*/ {
 | 
			
		||||
  private val traceTime: Boolean
 | 
			
		||||
    get() {
 | 
			
		||||
      // Make sure the injector is initialized
 | 
			
		||||
      VimPlugin.getInstance()
 | 
			
		||||
      return injector.globalOptions().ideatracetime
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
    LOG.trace("Executing shortcut key action")
 | 
			
		||||
@@ -91,29 +98,29 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
 | 
			
		||||
  // There is a chance that we can use BGT, but we call for isCell inside the update.
 | 
			
		||||
  // Not sure if can can use BGT with this call. Let's use EDT for now.
 | 
			
		||||
  override fun getActionUpdateThread() = ActionUpdateThread.EDT
 | 
			
		||||
  override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.EDT
 | 
			
		||||
 | 
			
		||||
  override fun update(e: AnActionEvent) {
 | 
			
		||||
    val start = if (traceTime) System.currentTimeMillis() else null
 | 
			
		||||
    val actionEnableStatus = isEnabled(e)
 | 
			
		||||
    e.presentation.isEnabled = actionEnableStatus.isEnabled
 | 
			
		||||
    actionEnableStatus.printLog()
 | 
			
		||||
    if (start != null) {
 | 
			
		||||
    val keyStroke = getKeyStroke(e)
 | 
			
		||||
    val actionEnableStatus = isEnabled(e, keyStroke)
 | 
			
		||||
    e.presentation.isEnabled = actionEnableStatus.isEnabled
 | 
			
		||||
    actionEnableStatus.printLog(keyStroke)
 | 
			
		||||
    if (start != null) {
 | 
			
		||||
      val duration = System.currentTimeMillis() - start
 | 
			
		||||
      LOG.info("VimShortcut update '$keyStroke': $duration ms")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun isEnabled(e: AnActionEvent): ActionEnableStatus {
 | 
			
		||||
    if (!VimPlugin.isEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
 | 
			
		||||
  private fun isEnabled(e: AnActionEvent, keyStroke: KeyStroke?): ActionEnableStatus {
 | 
			
		||||
    if (VimPlugin.isNotEnabled()) return ActionEnableStatus.no("IdeaVim is disabled", LogLevel.DEBUG)
 | 
			
		||||
    val editor = getEditor(e)
 | 
			
		||||
    val keyStroke = getKeyStroke(e)
 | 
			
		||||
    if (editor != null && keyStroke != null) {
 | 
			
		||||
      if (enableOctopus) {
 | 
			
		||||
      if (isOctopusEnabled(keyStroke, editor)) {
 | 
			
		||||
          return ActionEnableStatus.no("Octopus handler is enabled", LogLevel.DEBUG)
 | 
			
		||||
        }
 | 
			
		||||
        return ActionEnableStatus.no(
 | 
			
		||||
          "Processing VimShortcutKeyAction for the key that is used in the octopus handler",
 | 
			
		||||
          LogLevel.ERROR
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
      if (editor.isIdeaVimDisabledHere) {
 | 
			
		||||
        return ActionEnableStatus.no("IdeaVim is disabled in this place", LogLevel.INFO)
 | 
			
		||||
@@ -160,10 +167,6 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
        return ActionEnableStatus.no("App code template is active", LogLevel.INFO)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (keyCode == KeyEvent.VK_LEFT || keyCode == KeyEvent.VK_RIGHT || keyCode == KeyEvent.VK_UP || keyCode == KeyEvent.VK_DOWN || keyCode == KeyEvent.VK_HOME || keyCode == KeyEvent.VK_END) {
 | 
			
		||||
        return ActionEnableStatus.no("Special keys", LogLevel.INFO)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (editor.inInsertMode) {
 | 
			
		||||
        if (keyCode == KeyEvent.VK_TAB) {
 | 
			
		||||
          // TODO: This stops VimEditorTab seeing <Tab> in insert mode and correctly scrolling the view
 | 
			
		||||
@@ -229,9 +232,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
  /**
 | 
			
		||||
   * getDefaultKeyStroke is needed for NEO layout keyboard VIM-987
 | 
			
		||||
   * but we should cache the value because on the second call (isEnabled -> actionPerformed)
 | 
			
		||||
   * the event is already consumed
 | 
			
		||||
   * the event is already consumed and getDefaultKeyStroke returns null
 | 
			
		||||
   */
 | 
			
		||||
  private var keyStrokeCache: Pair<KeyEvent?, KeyStroke?> = null to null
 | 
			
		||||
  private var keyStrokeCache: Pair<Long?, KeyStroke?> = null to null
 | 
			
		||||
 | 
			
		||||
  private fun getKeyStroke(e: AnActionEvent): KeyStroke? {
 | 
			
		||||
    val inputEvent = e.inputEvent
 | 
			
		||||
@@ -239,9 +242,9 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
      val defaultKeyStroke = KeyStrokeAdapter.getDefaultKeyStroke(inputEvent)
 | 
			
		||||
      val strokeCache = keyStrokeCache
 | 
			
		||||
      if (defaultKeyStroke != null) {
 | 
			
		||||
        keyStrokeCache = inputEvent to defaultKeyStroke
 | 
			
		||||
        keyStrokeCache = inputEvent.`when` to defaultKeyStroke
 | 
			
		||||
        return defaultKeyStroke
 | 
			
		||||
      } else if (strokeCache.first === inputEvent) {
 | 
			
		||||
      } else if (strokeCache.first == inputEvent.`when`) {
 | 
			
		||||
        keyStrokeCache = null to null
 | 
			
		||||
        return strokeCache.second
 | 
			
		||||
      }
 | 
			
		||||
@@ -274,7 +277,7 @@ internal class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatib
 | 
			
		||||
      .toSet()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
  internal companion object {
 | 
			
		||||
    @JvmField
 | 
			
		||||
    val VIM_ONLY_EDITOR_KEYS: Set<KeyStroke> =
 | 
			
		||||
      ImmutableSet.builder<KeyStroke>().addAll(getKeyStrokes(KeyEvent.VK_ENTER, 0))
 | 
			
		||||
@@ -365,10 +368,12 @@ private class ActionEnableStatus(
 | 
			
		||||
  val message: String,
 | 
			
		||||
  val logLevel: LogLevel,
 | 
			
		||||
) {
 | 
			
		||||
  fun printLog() {
 | 
			
		||||
  fun printLog(keyStroke: KeyStroke?) {
 | 
			
		||||
    val message = "IdeaVim keys are enabled = $isEnabled for key '$keyStroke': $message"
 | 
			
		||||
    when (logLevel) {
 | 
			
		||||
      LogLevel.INFO -> LOG.info("IdeaVim keys are enabled = $isEnabled: $message")
 | 
			
		||||
      LogLevel.DEBUG -> LOG.debug("IdeaVim keys are enabled = $isEnabled: $message")
 | 
			
		||||
      LogLevel.INFO -> LOG.info(message)
 | 
			
		||||
      LogLevel.DEBUG -> LOG.debug(message)
 | 
			
		||||
      LogLevel.ERROR -> LOG.error(message)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -381,5 +386,5 @@ private class ActionEnableStatus(
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private enum class LogLevel {
 | 
			
		||||
  DEBUG, INFO,
 | 
			
		||||
  DEBUG, INFO, ERROR,
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.change
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -16,9 +18,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
			
		||||
import com.maddyhome.idea.vim.command.Argument
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.common.argumentCaptured
 | 
			
		||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
			
		||||
@@ -26,10 +26,9 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import java.util.*
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
 | 
			
		||||
// todo make it multicaret
 | 
			
		||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
 | 
			
		||||
@@ -47,6 +46,7 @@ private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textR
 | 
			
		||||
  return result
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])
 | 
			
		||||
internal class OperatorAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
			
		||||
 | 
			
		||||
@@ -97,11 +97,10 @@ internal class OperatorAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["g@"], modes = [Mode.VISUAL])
 | 
			
		||||
internal class VisualOperatorAction : VisualOperatorActionHandler.ForEachCaret() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_SELF_SYNCHRONIZED
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeAction(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.change
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -18,6 +20,7 @@ import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["."], modes = [Mode.NORMAL])
 | 
			
		||||
internal class RepeatChangeAction : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_WRITABLE
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.change.delete
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
@@ -17,6 +19,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.NORMAL])
 | 
			
		||||
public class DeleteJoinLinesAction : ChangeEditorActionHandler.ConditionalSingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
  override fun runAsMulticaret(
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.change.delete
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
@@ -16,6 +18,7 @@ import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.ChangeEditorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["J"], modes = [Mode.NORMAL])
 | 
			
		||||
public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,27 +7,25 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.change.delete
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
@CommandOrMotion(keys = ["gJ"], modes = [Mode.VISUAL])
 | 
			
		||||
public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeForAllCarets(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
 
 | 
			
		||||
@@ -7,27 +7,25 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.change.delete
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VisualOperatorActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.enumSetOf
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ijOptions
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
@CommandOrMotion(keys = ["J"], modes = [Mode.VISUAL])
 | 
			
		||||
public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_EXIT_VISUAL)
 | 
			
		||||
 | 
			
		||||
  override fun executeForAllCarets(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,8 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.action.editor
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
			
		||||
import com.maddyhome.idea.vim.action.ComplicatedKeysAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -23,6 +25,7 @@ import java.awt.event.KeyEvent
 | 
			
		||||
import java.util.*
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["<C-H>", "<BS>"], modes = [Mode.INSERT])
 | 
			
		||||
internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BACKSPACE), ComplicatedKeysAction {
 | 
			
		||||
  override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
 | 
			
		||||
    listOf(KeyStroke.getKeyStroke(KeyEvent.VK_H, KeyEvent.CTRL_DOWN_MASK)),
 | 
			
		||||
@@ -31,6 +34,7 @@ internal class VimEditorBackSpace : IdeActionHandler(IdeActions.ACTION_EDITOR_BA
 | 
			
		||||
  override val type: Command.Type = Command.Type.DELETE
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["<Del>"], modes = [Mode.INSERT])
 | 
			
		||||
internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELETE), ComplicatedKeysAction {
 | 
			
		||||
  override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
 | 
			
		||||
    listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0)),
 | 
			
		||||
@@ -39,6 +43,7 @@ internal class VimEditorDelete : IdeActionHandler(IdeActions.ACTION_EDITOR_DELET
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["<Down>", "<kDown>"], modes = [Mode.INSERT])
 | 
			
		||||
internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_DOWN), ComplicatedKeysAction {
 | 
			
		||||
  override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
 | 
			
		||||
    listOf(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0)),
 | 
			
		||||
@@ -48,6 +53,7 @@ internal class VimEditorDown : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CA
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["<Tab>", "<C-I>"], modes = [Mode.INSERT])
 | 
			
		||||
internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), ComplicatedKeysAction {
 | 
			
		||||
  override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
 | 
			
		||||
    listOf(KeyStroke.getKeyStroke(KeyEvent.VK_I, KeyEvent.CTRL_DOWN_MASK)),
 | 
			
		||||
@@ -57,6 +63,7 @@ internal class VimEditorTab : IdeActionHandler(IdeActions.ACTION_EDITOR_TAB), Co
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_SAVE_STROKE)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["<Up>", "<kUp>"], modes = [Mode.INSERT])
 | 
			
		||||
internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARET_UP), ComplicatedKeysAction {
 | 
			
		||||
  override val keyStrokesSet: Set<List<KeyStroke>> = setOf(
 | 
			
		||||
    listOf(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0)),
 | 
			
		||||
@@ -66,6 +73,7 @@ internal class VimEditorUp : IdeActionHandler(IdeActions.ACTION_EDITOR_MOVE_CARE
 | 
			
		||||
  override val flags: EnumSet<CommandFlags> = enumSetOf(CommandFlags.FLAG_CLEAR_STROKES)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["K"], modes = [Mode.NORMAL])
 | 
			
		||||
internal class VimQuickJavaDoc : VimActionHandler.SingleExecution() {
 | 
			
		||||
  override val type: Command.Type = Command.Type.OTHER_READONLY
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.ex
 | 
			
		||||
 | 
			
		||||
import com.intellij.vim.annotations.CommandOrMotion
 | 
			
		||||
import com.intellij.vim.annotations.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.action.ComplicatedKeysAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -23,6 +25,7 @@ import javax.swing.KeyStroke
 | 
			
		||||
 *
 | 
			
		||||
 * The mapping for this action means that the ex command is executed as a write action
 | 
			
		||||
 */
 | 
			
		||||
@CommandOrMotion(keys = ["<CR>", "<C-M>", "<C-J>"], modes = [Mode.CMD_LINE])
 | 
			
		||||
public class ProcessExEntryAction : VimActionHandler.SingleExecution(), ComplicatedKeysAction {
 | 
			
		||||
  override val keyStrokesSet: Set<List<KeyStroke>> =
 | 
			
		||||
    parseKeysSet("<CR>", "<C-M>", 0x0a.toChar().toString(), 0x0d.toChar().toString())
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ public class CommandState(private val machine: VimStateMachine) {
 | 
			
		||||
    get() {
 | 
			
		||||
      val myMode = machine.mode
 | 
			
		||||
      return when (myMode) {
 | 
			
		||||
        com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
        com.maddyhome.idea.vim.state.mode.Mode.INSERT -> CommandState.Mode.INSERT
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@ package com.maddyhome.idea.vim.extension
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
@@ -17,7 +18,6 @@ import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.MappingMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.CommandAlias
 | 
			
		||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
 | 
			
		||||
@@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.key.MappingOwner
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ModalEntry
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
@@ -38,6 +39,9 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * @author vlan
 | 
			
		||||
 */
 | 
			
		||||
public object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  private val LOG = logger<VimExtensionFacade>()
 | 
			
		||||
 | 
			
		||||
  /** The 'map' command for mapping keys to handlers defined in extensions. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun putExtensionHandlerMapping(
 | 
			
		||||
@@ -140,10 +144,12 @@ public object VimExtensionFacade {
 | 
			
		||||
  public fun inputKeyStroke(editor: Editor): KeyStroke {
 | 
			
		||||
    if (editor.vim.vimStateMachine.isDotRepeatInProgress) {
 | 
			
		||||
      val input = Extension.consumeKeystroke()
 | 
			
		||||
      LOG.trace("inputKeyStroke: dot repeat in progress. Input: $input")
 | 
			
		||||
      return input ?: error("Not enough keystrokes saved: ${Extension.lastExtensionHandler}")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val key: KeyStroke? = if (ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
      LOG.trace("Unit test mode is active")
 | 
			
		||||
      val mappingStack = KeyHandler.getInstance().keyStack
 | 
			
		||||
      mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
 | 
			
		||||
        if (editor.vim.vimStateMachine.isRecording) {
 | 
			
		||||
@@ -151,11 +157,13 @@ public object VimExtensionFacade {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      LOG.trace("Getting char from the modal entry...")
 | 
			
		||||
      var ref: KeyStroke? = null
 | 
			
		||||
      ModalEntry.activate(editor.vim) { stroke: KeyStroke? ->
 | 
			
		||||
        ref = stroke
 | 
			
		||||
        false
 | 
			
		||||
      }
 | 
			
		||||
      LOG.trace("Got char $ref")
 | 
			
		||||
      ref
 | 
			
		||||
    }
 | 
			
		||||
    val result = key ?: KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE.toChar())
 | 
			
		||||
 
 | 
			
		||||
@@ -82,6 +82,31 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * During vim initialization process, it firstly loads the .vimrc file, then executes scripts from the plugins folder.
 | 
			
		||||
   * This practically means that the .vimrc file is initialized first, then the plugins are loaded.
 | 
			
		||||
   * See `:h initialization`
 | 
			
		||||
   *
 | 
			
		||||
   * In IdeaVim we don't have a separate plugins folder to load it after .ideavimrc load. However, we can collect
 | 
			
		||||
   *   the list of plugins mentioned in the .ideavimrc and load them after .ideavimrc execution is finished.
 | 
			
		||||
   *
 | 
			
		||||
   * Why this matters? Because this affects the order of commands are executed. For example:
 | 
			
		||||
   * ```
 | 
			
		||||
   * plug 'tommcdo/vim-exchange'
 | 
			
		||||
   * let g:exchange_no_mappings=1
 | 
			
		||||
   * ```
 | 
			
		||||
   * Here the user will expect that the exchange plugin won't have default mappings. However, if we load vim-exchange
 | 
			
		||||
   *    immediately, this variable won't be initialized at the moment of plugin initialization.
 | 
			
		||||
   *
 | 
			
		||||
   * There is also a tricky case for mappings override:
 | 
			
		||||
   * ```
 | 
			
		||||
   * plug 'tommcdo/vim-exchange'
 | 
			
		||||
   * map X <Plug>(ExchangeLine)
 | 
			
		||||
   * ```
 | 
			
		||||
   * For this case, a plugin with a good implementation detects that there is already a defined mapping for
 | 
			
		||||
   *   `<Plug>(ExchangeLine)` and doesn't register the default cxx mapping. However, such detection requires the mapping
 | 
			
		||||
   *   to be defined before the plugin initialization.
 | 
			
		||||
   */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  fun enableDelayedExtensions() {
 | 
			
		||||
    delayedExtensionEnabling.forEach {
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ import com.intellij.codeInsight.actions.AsyncActionExecutionService
 | 
			
		||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
			
		||||
import com.intellij.openapi.application.runWriteAction
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.util.Ref
 | 
			
		||||
import com.intellij.psi.PsiComment
 | 
			
		||||
import com.intellij.psi.PsiElement
 | 
			
		||||
@@ -74,17 +75,24 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
          listOf(IdeActions.ACTION_COMMENT_BLOCK, IdeActions.ACTION_COMMENT_LINE)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val res = Ref.create<Boolean>(true)
 | 
			
		||||
        AsyncActionExecutionService.getInstance(editor.ij.project!!).withExecutionAfterAction(actions[0], {
 | 
			
		||||
          res.set(injector.actionExecutor.executeAction(actions[0], context))
 | 
			
		||||
        }, { afterCommenting(mode, editor, resetCaret, range) })
 | 
			
		||||
        if (!res.get()) {
 | 
			
		||||
          AsyncActionExecutionService.getInstance(editor.ij.project!!).withExecutionAfterAction(actions[1], {
 | 
			
		||||
            res.set(injector.actionExecutor.executeAction(actions[1], context))
 | 
			
		||||
          }, { afterCommenting(mode, editor, resetCaret, range) })
 | 
			
		||||
        val project = editor.ij.project!!
 | 
			
		||||
        val callback = { afterCommenting(mode, editor, resetCaret, range) }
 | 
			
		||||
        actions.any { executeActionWithCallbackOnSuccess(it, project, context, callback) }
 | 
			
		||||
      }
 | 
			
		||||
        res.get()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun executeActionWithCallbackOnSuccess(
 | 
			
		||||
      action: String,
 | 
			
		||||
      project: Project,
 | 
			
		||||
      context: ExecutionContext,
 | 
			
		||||
      callback: () -> Unit,
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
      val res = Ref.create<Boolean>(false)
 | 
			
		||||
      AsyncActionExecutionService.getInstance(project).withExecutionAfterAction(
 | 
			
		||||
        action,
 | 
			
		||||
        { res.set(injector.actionExecutor.executeAction(action, context)) },
 | 
			
		||||
        { if (res.get()) callback() })
 | 
			
		||||
      return res.get()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun afterCommenting(
 | 
			
		||||
@@ -148,11 +156,6 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
  private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
 | 
			
		||||
    override val isRepeatable = true
 | 
			
		||||
 | 
			
		||||
    // In this operator we process selection by ourselves. This is necessary for rider, VIM-1758
 | 
			
		||||
    override fun postProcessSelection(): Boolean {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      setOperatorFunction(this)
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
			
		||||
 
 | 
			
		||||
@@ -217,6 +217,8 @@ private object FileTypePatterns {
 | 
			
		||||
 | 
			
		||||
    return if (fileTypeName in htmlLikeFileTypes) {
 | 
			
		||||
      this.htmlPatterns
 | 
			
		||||
    } else if (fileTypeName == "JAVA" || fileExtension == "java") {
 | 
			
		||||
      this.javaPatterns
 | 
			
		||||
    } else if (fileTypeName == "Ruby" || fileExtension == "rb") {
 | 
			
		||||
      this.rubyPatterns
 | 
			
		||||
    } else if (fileTypeName == "RHTML" || fileExtension == "erb") {
 | 
			
		||||
@@ -242,6 +244,7 @@ private object FileTypePatterns {
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
  private val htmlPatterns = createHtmlPatterns()
 | 
			
		||||
  private val javaPatterns = createJavaPatterns()
 | 
			
		||||
  private val rubyPatterns = createRubyPatterns()
 | 
			
		||||
  private val rubyAndHtmlPatterns = rubyPatterns + htmlPatterns
 | 
			
		||||
  private val phpPatterns = createPhpPatterns()
 | 
			
		||||
@@ -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 {
 | 
			
		||||
    // 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.
 | 
			
		||||
 
 | 
			
		||||
@@ -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,6 +8,7 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.surround
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.runWriteAction
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
@@ -19,10 +20,7 @@ import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
			
		||||
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.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtension
 | 
			
		||||
@@ -34,7 +32,6 @@ import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMa
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
 | 
			
		||||
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.newapi.IjVimCaret
 | 
			
		||||
@@ -43,6 +40,10 @@ import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.options.helpers.ClipboardOptionHelper
 | 
			
		||||
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 java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
@@ -83,7 +84,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
    override val isRepeatable = true
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      setOperatorFunction(Operator(supportsMultipleCursors = false)) // TODO
 | 
			
		||||
      setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -104,7 +105,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
        val lastNonWhiteSpaceOffset = getLastNonWhitespaceCharacterOffset(editor.text(), lineStartOffset, lineEndOffset)
 | 
			
		||||
        if (lastNonWhiteSpaceOffset != null) {
 | 
			
		||||
          val range = TextRange(lineStartOffset, lastNonWhiteSpaceOffset + 1)
 | 
			
		||||
          performSurround(pair, range, it)
 | 
			
		||||
          performSurround(pair, range, it, count = operatorArguments.count1)
 | 
			
		||||
        }
 | 
			
		||||
//        it.moveToOffset(lineStartOffset)
 | 
			
		||||
      }
 | 
			
		||||
@@ -125,7 +126,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
  private class VSurroundHandler : ExtensionHandler {
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      // NB: Operator ignores SelectionType anyway
 | 
			
		||||
      if (!Operator(supportsMultipleCursors = true).apply(editor, context, editor.mode.selectionType)) {
 | 
			
		||||
      if (!Operator(supportsMultipleCursors = true, count = operatorArguments.count1).apply(editor, context, editor.mode.selectionType)) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
      runWriteAction {
 | 
			
		||||
@@ -255,13 +256,14 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      // Deleting surround is just changing the surrounding to "nothing"
 | 
			
		||||
      val charFrom = getChar(editor.ij)
 | 
			
		||||
      LOG.debug("DSurroundHandler: charFrom = $charFrom")
 | 
			
		||||
      if (charFrom.code == 0) return
 | 
			
		||||
 | 
			
		||||
      runWriteAction { CSurroundHandler.change(editor, context, charFrom, null) }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class Operator(private val supportsMultipleCursors: Boolean) : OperatorFunction {
 | 
			
		||||
  private class Operator(private val supportsMultipleCursors: Boolean, private val count: Int) : OperatorFunction {
 | 
			
		||||
    override fun apply(vimEditor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
 | 
			
		||||
      val editor = vimEditor.ij
 | 
			
		||||
      val c = getChar(editor)
 | 
			
		||||
@@ -273,11 +275,11 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
        val change = VimPlugin.getChange()
 | 
			
		||||
        if (supportsMultipleCursors) {
 | 
			
		||||
          editor.runWithEveryCaretAndRestore {
 | 
			
		||||
            applyOnce(editor, change, pair)
 | 
			
		||||
            applyOnce(editor, change, pair, count)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          applyOnce(editor, change, pair)
 | 
			
		||||
          applyOnce(editor, change, pair, count)
 | 
			
		||||
          // Jump back to start
 | 
			
		||||
          executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
 | 
			
		||||
        }
 | 
			
		||||
@@ -285,18 +287,15 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    private fun applyOnce(editor: Editor, change: VimChangeGroup, pair: Pair<String, String>) {
 | 
			
		||||
    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) {
 | 
			
		||||
        change.insertText(IjVimEditor(editor), IjVimCaret(primaryCaret), range.startOffset, pair.first)
 | 
			
		||||
        change.insertText(
 | 
			
		||||
          IjVimEditor(editor),
 | 
			
		||||
          IjVimCaret(primaryCaret),
 | 
			
		||||
          range.endOffset + pair.first.length,
 | 
			
		||||
          pair.second
 | 
			
		||||
        )
 | 
			
		||||
        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)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -310,13 +309,15 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    private const val REGISTER = '"'
 | 
			
		||||
private val LOG = logger<VimSurroundExtension>()
 | 
			
		||||
 | 
			
		||||
    private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
private const val REGISTER = '"'
 | 
			
		||||
 | 
			
		||||
    private val SURROUND_PAIRS = mapOf(
 | 
			
		||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
 | 
			
		||||
private val SURROUND_PAIRS = mapOf(
 | 
			
		||||
  'b' to ("(" to ")"),
 | 
			
		||||
  '(' to ("( " to " )"),
 | 
			
		||||
  ')' to ("(" to ")"),
 | 
			
		||||
@@ -329,18 +330,18 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
  'a' to ("<" to ">"),
 | 
			
		||||
  '>' to ("<" to ">"),
 | 
			
		||||
  's' to (" " to ""),
 | 
			
		||||
    )
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
    private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
 | 
			
		||||
private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_PAIRS) {
 | 
			
		||||
  SURROUND_PAIRS[c]
 | 
			
		||||
    } else if (!c.isLetter()) {
 | 
			
		||||
} else if (!c.isLetter()) {
 | 
			
		||||
  val s = c.toString()
 | 
			
		||||
  s to s
 | 
			
		||||
    } else {
 | 
			
		||||
} else {
 | 
			
		||||
  null
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
			
		||||
private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
			
		||||
  val tagInput = inputString(editor, "<", '>')
 | 
			
		||||
  val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
 | 
			
		||||
  return if (matcher.find()) {
 | 
			
		||||
@@ -350,43 +351,45 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
  } else {
 | 
			
		||||
    null
 | 
			
		||||
  }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private fun inputFunctionName(
 | 
			
		||||
private fun inputFunctionName(
 | 
			
		||||
  editor: Editor,
 | 
			
		||||
  withInternalSpaces: Boolean,
 | 
			
		||||
    ): Pair<String, String>? {
 | 
			
		||||
): Pair<String, String>? {
 | 
			
		||||
  val functionNameInput = inputString(editor, "function: ", null)
 | 
			
		||||
  if (functionNameInput.isEmpty()) return null
 | 
			
		||||
  return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
 | 
			
		||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
 | 
			
		||||
  '<', 't' -> inputTagPair(editor)
 | 
			
		||||
  'f' -> inputFunctionName(editor, false)
 | 
			
		||||
  'F' -> inputFunctionName(editor, true)
 | 
			
		||||
  else -> getSurroundPair(c)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private fun getChar(editor: Editor): Char {
 | 
			
		||||
private fun getChar(editor: Editor): Char {
 | 
			
		||||
  val key = inputKeyStroke(editor)
 | 
			
		||||
  val keyChar = key.keyChar
 | 
			
		||||
      return if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
 | 
			
		||||
  val res = if (keyChar == KeyEvent.CHAR_UNDEFINED || keyChar.code == KeyEvent.VK_ESCAPE) {
 | 
			
		||||
    0.toChar()
 | 
			
		||||
  } else {
 | 
			
		||||
    keyChar
 | 
			
		||||
  }
 | 
			
		||||
    }
 | 
			
		||||
  LOG.trace("getChar: $res")
 | 
			
		||||
  return res
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, tagsOnNewLines: Boolean = false) {
 | 
			
		||||
private fun performSurround(pair: Pair<String, String>, range: TextRange, caret: VimCaret, count: Int, tagsOnNewLines: Boolean = false) {
 | 
			
		||||
  runWriteAction {
 | 
			
		||||
    val editor = caret.editor
 | 
			
		||||
    val change = VimPlugin.getChange()
 | 
			
		||||
        val leftSurround = 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 hasNewLine = editor.endsWithNewLine()
 | 
			
		||||
        val rightSurround = if (tagsOnNewLines) {
 | 
			
		||||
    val rightSurround = (if (tagsOnNewLines) {
 | 
			
		||||
      if (isEOF && !hasNewLine) {
 | 
			
		||||
        "\n" + pair.second
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -394,12 +397,13 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      pair.second
 | 
			
		||||
        }
 | 
			
		||||
    }).let { RepeatedCharSequence.of(it, count) }
 | 
			
		||||
 | 
			
		||||
    change.insertText(editor, caret, range.startOffset, leftSurround)
 | 
			
		||||
    change.insertText(editor, caret, range.endOffset + leftSurround.length, rightSurround)
 | 
			
		||||
        injector.markService.setChangeMarks(caret, TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length))
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    injector.markService.setChangeMarks(
 | 
			
		||||
      caret,
 | 
			
		||||
      TextRange(range.startOffset, range.endOffset + leftSurround.length + rightSurround.length)
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -16,12 +16,11 @@ import com.intellij.openapi.command.UndoConfirmationPolicy
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.LogicalPosition
 | 
			
		||||
import com.intellij.openapi.editor.actions.EnterAction
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorMouseEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorMouseListener
 | 
			
		||||
import com.intellij.openapi.editor.impl.TextRangeInterval
 | 
			
		||||
import com.intellij.openapi.ui.MessageType
 | 
			
		||||
import com.intellij.openapi.ui.popup.Balloon
 | 
			
		||||
import com.intellij.openapi.ui.popup.JBPopupFactory
 | 
			
		||||
import com.intellij.openapi.util.UserDataHolder
 | 
			
		||||
import com.intellij.openapi.util.text.StringUtil
 | 
			
		||||
import com.intellij.psi.codeStyle.CodeStyleManager
 | 
			
		||||
import com.intellij.psi.util.PsiUtilBase
 | 
			
		||||
@@ -52,6 +51,7 @@ import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
			
		||||
import com.maddyhome.idea.vim.handler.Motion
 | 
			
		||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
 | 
			
		||||
import com.maddyhome.idea.vim.handler.commandContinuation
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CharacterHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CharacterHelper.changeCase
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CharacterHelper.charType
 | 
			
		||||
@@ -62,29 +62,28 @@ import com.maddyhome.idea.vim.helper.endOffsetInclusive
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inInsertMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.icons.VimIcons
 | 
			
		||||
import com.maddyhome.idea.vim.key.KeyHandlerKeeper.Companion.getInstance
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimInsertListener
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext
 | 
			
		||||
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.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode.VISUAL
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.commands.SortOption
 | 
			
		||||
import org.jetbrains.annotations.TestOnly
 | 
			
		||||
import java.math.BigInteger
 | 
			
		||||
import java.util.*
 | 
			
		||||
import java.util.function.Consumer
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
import kotlin.math.min
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Provides all the insert/replace related functionality
 | 
			
		||||
 */
 | 
			
		||||
public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
  private val insertListeners = ContainerUtil.createLockFreeCopyOnWriteList<VimInsertListener>()
 | 
			
		||||
  private var lastShownTime = 0L
 | 
			
		||||
  private val listener: EditorMouseListener = object : EditorMouseListener {
 | 
			
		||||
    override fun mouseClicked(event: EditorMouseEvent) {
 | 
			
		||||
      val editor = event.editor
 | 
			
		||||
@@ -98,10 +97,6 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    EventFacade.getInstance().addEditorMouseListener(editor!!, listener, disposable)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun editorReleased(editor: Editor?) {
 | 
			
		||||
    EventFacade.getInstance().removeEditorMouseListener(editor!!, listener)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun type(vimEditor: VimEditor, context: ExecutionContext, key: Char) {
 | 
			
		||||
    val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
    val ijContext = context.ij
 | 
			
		||||
@@ -116,6 +111,35 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    injector.scroll.scrollCaretIntoView(vimEditor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * If this is REPLACE mode we need to turn off OVERWRITE before and then turn OVERWRITE back on after sending the
 | 
			
		||||
   * "ENTER" key.
 | 
			
		||||
   */
 | 
			
		||||
  override fun processEnter(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (editor.mode is Mode.REPLACE) {
 | 
			
		||||
      editor.insertMode = true
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      val continuation = (context.context as UserDataHolder).getUserData(commandContinuation)
 | 
			
		||||
      val ijEditor = editor.ij
 | 
			
		||||
      val ij = context.ij
 | 
			
		||||
      val ijCaret = caret.ij
 | 
			
		||||
      if (continuation != null) {
 | 
			
		||||
        continuation.execute(ijEditor, ijCaret, ij)
 | 
			
		||||
      } else {
 | 
			
		||||
        EnterAction().handler.execute(ijEditor, ijCaret, ij)
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      if (editor.mode is Mode.REPLACE) {
 | 
			
		||||
        editor.insertMode = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getDeleteRangeAndType2(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
@@ -370,6 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
    range: TextRange,
 | 
			
		||||
  ) {
 | 
			
		||||
    val startPos = editor.offsetToBufferPosition(caret.offset.point)
 | 
			
		||||
    val startOffset = editor.getLineStartForOffset(range.startOffset)
 | 
			
		||||
    val endOffset = editor.getLineEndForOffset(range.endOffset)
 | 
			
		||||
    val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
@@ -394,11 +419,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    val afterAction = {
 | 
			
		||||
      val firstLine = editor.offsetToBufferPosition(
 | 
			
		||||
        min(startOffset.toDouble(), endOffset.toDouble()).toInt()
 | 
			
		||||
      ).line
 | 
			
		||||
      val newOffset = injector.motion.moveCaretToLineStartSkipLeading(editor, firstLine)
 | 
			
		||||
      caret.moveToOffset(newOffset)
 | 
			
		||||
      caret.moveToOffset(injector.motion.moveCaretToLineStartSkipLeading(editor, startPos.line))
 | 
			
		||||
      restoreCursor(editor, caret, (caret as IjVimCaret).caret.logicalPosition.line)
 | 
			
		||||
    }
 | 
			
		||||
    if (project != null) {
 | 
			
		||||
@@ -611,25 +632,6 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    avalanche: Boolean,
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
 | 
			
		||||
    // Just an easter egg
 | 
			
		||||
    if (avalanche) {
 | 
			
		||||
      val currentTime = System.currentTimeMillis()
 | 
			
		||||
      if (currentTime - lastShownTime > 60000) {
 | 
			
		||||
        lastShownTime = currentTime
 | 
			
		||||
        ApplicationManager.getApplication().invokeLater {
 | 
			
		||||
          val balloon = JBPopupFactory.getInstance()
 | 
			
		||||
            .createHtmlTextBalloonBuilder(
 | 
			
		||||
              "Wow, nice vim skills!", VimIcons.IDEAVIM,
 | 
			
		||||
              MessageType.INFO.titleForeground, MessageType.INFO.popupBackground,
 | 
			
		||||
              null
 | 
			
		||||
            ).createBalloon()
 | 
			
		||||
          balloon.show(
 | 
			
		||||
            JBPopupFactory.getInstance().guessBestPopupLocation((editor as IjVimEditor).editor),
 | 
			
		||||
            Balloon.Position.below
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    val nf: List<String> = injector.options(editor).nrformats
 | 
			
		||||
    val alpha = nf.contains("alpha")
 | 
			
		||||
    val hex = nf.contains("hex")
 | 
			
		||||
 
 | 
			
		||||
@@ -89,14 +89,17 @@ public class FileGroup extends VimFileBase {
 | 
			
		||||
 | 
			
		||||
  @Nullable VirtualFile findFile(@NotNull String filename, @NotNull Project project) {
 | 
			
		||||
    VirtualFile found;
 | 
			
		||||
    if (filename.length() > 2 && filename.charAt(0) == '~' && filename.charAt(1) == File.separatorChar) {
 | 
			
		||||
      String homefile = filename.substring(2);
 | 
			
		||||
    // Vim supports both ~/ and ~\ (tested on Mac and Windows). On Windows, it supports forward- and back-slashes, but
 | 
			
		||||
    // it only supports forward slash on Unix (tested on Mac)
 | 
			
		||||
    // VFS works with both directory separators (tested on Mac and Windows)
 | 
			
		||||
    if (filename.startsWith("~/") || filename.startsWith("~\\")) {
 | 
			
		||||
      String relativePath = filename.substring(2);
 | 
			
		||||
      String dir = System.getProperty("user.home");
 | 
			
		||||
      if (logger.isDebugEnabled()) {
 | 
			
		||||
        logger.debug("home dir file");
 | 
			
		||||
        logger.debug("looking for " + homefile + " in " + dir);
 | 
			
		||||
        logger.debug("looking for " + relativePath + " in " + dir);
 | 
			
		||||
      }
 | 
			
		||||
      found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, homefile));
 | 
			
		||||
      found = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(new File(dir, relativePath));
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      found = LocalFileSystem.getInstance().findFileByIoFile(new File(filename));
 | 
			
		||||
 
 | 
			
		||||
@@ -29,13 +29,15 @@ public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesB
 | 
			
		||||
  public val lookupkeys: StringListOptionValue by optionProperty(IjOptions.lookupkeys)
 | 
			
		||||
  public var trackactionids: Boolean by optionProperty(IjOptions.trackactionids)
 | 
			
		||||
  public var visualdelay: Int by optionProperty(IjOptions.visualdelay)
 | 
			
		||||
  public var showmodewidget: Boolean by optionProperty(IjOptions.showmodewidget)
 | 
			
		||||
  public var colorfulmodewidget: Boolean by optionProperty(IjOptions.colorfulmodewidget)
 | 
			
		||||
 | 
			
		||||
  // Temporary options to control work-in-progress behaviour
 | 
			
		||||
  public var octopushandler: Boolean by optionProperty(IjOptions.octopushandler)
 | 
			
		||||
  public var oldundo: Boolean by optionProperty(IjOptions.oldundo)
 | 
			
		||||
  public var unifyjumps: Boolean by optionProperty(IjOptions.unifyjumps)
 | 
			
		||||
  public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
 | 
			
		||||
  public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
 | 
			
		||||
  public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -80,12 +80,14 @@ public object IjOptions {
 | 
			
		||||
      "lookupkeys",
 | 
			
		||||
      "<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
 | 
			
		||||
  )
 | 
			
		||||
  public val octopushandler: ToggleOption = addOption(ToggleOption("octopushandler", GLOBAL, "octopushandler", false))
 | 
			
		||||
  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true))
 | 
			
		||||
  public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
 | 
			
		||||
  public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true))
 | 
			
		||||
  public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true))
 | 
			
		||||
  public val visualdelay: UnsignedNumberOption = addOption(UnsignedNumberOption("visualdelay", GLOBAL, "visualdelay", 100))
 | 
			
		||||
  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isTemporary = true))
 | 
			
		||||
  public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isTemporary = true))
 | 
			
		||||
  public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isTemporary = true))
 | 
			
		||||
  public val showmodewidget: ToggleOption = addOption(ToggleOption("showmodewidget", GLOBAL, "showmodewidget", false, isTemporary = true))
 | 
			
		||||
  public val colorfulmodewidget: ToggleOption = addOption(ToggleOption("colorfulmodewidget", GLOBAL, "colorfulmodewidget", false, isTemporary = true))
 | 
			
		||||
 | 
			
		||||
  // This needs to be Option<out VimDataType> so that it can work with derived option types, such as NumberOption, which
 | 
			
		||||
  // derives from Option<VimInt>
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.EventFacade;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.action.ComplicatedKeysAction;
 | 
			
		||||
import com.maddyhome.idea.vim.action.VimShortcutKeyAction;
 | 
			
		||||
import com.maddyhome.idea.vim.action.change.LazyVimCommand;
 | 
			
		||||
import com.maddyhome.idea.vim.api.*;
 | 
			
		||||
import com.maddyhome.idea.vim.command.MappingMode;
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel;
 | 
			
		||||
@@ -208,6 +209,25 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
 | 
			
		||||
    registerRequiredShortcut(Collections.singletonList(keyStroke), owner);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void registerCommandAction(@NotNull LazyVimCommand command) {
 | 
			
		||||
    if (ApplicationManager.getApplication().isUnitTestMode()) {
 | 
			
		||||
      initIdentityChecker();
 | 
			
		||||
      for (List<KeyStroke> keys : command.getKeys()) {
 | 
			
		||||
        checkCommand(command.getModes(), command, keys);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    for (List<KeyStroke> keyStrokes : command.getKeys()) {
 | 
			
		||||
      registerRequiredShortcut(keyStrokes, MappingOwner.IdeaVim.System.INSTANCE);
 | 
			
		||||
 | 
			
		||||
      for (MappingMode mappingMode : command.getModes()) {
 | 
			
		||||
        Node<VimActionsInitiator> node = getKeyRoot(mappingMode);
 | 
			
		||||
        NodesKt.addLeafs(node, keyStrokes, command);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Deprecated
 | 
			
		||||
  public void registerCommandAction(@NotNull VimActionsInitiator actionHolder) {
 | 
			
		||||
    IjVimActionsInitiator holder = (IjVimActionsInitiator)actionHolder;
 | 
			
		||||
 | 
			
		||||
@@ -254,7 +274,9 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
 | 
			
		||||
 | 
			
		||||
  private void registerRequiredShortcut(@NotNull List<KeyStroke> keys, MappingOwner owner) {
 | 
			
		||||
    for (KeyStroke key : keys) {
 | 
			
		||||
      if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED) {
 | 
			
		||||
      if (key.getKeyChar() == KeyEvent.CHAR_UNDEFINED &&
 | 
			
		||||
          !(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
 | 
			
		||||
          !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
 | 
			
		||||
        getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,68 @@
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.daemon.ReferenceImporter
 | 
			
		||||
import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.application.ReadAction
 | 
			
		||||
import com.intellij.openapi.command.WriteCommandAction
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
			
		||||
import com.intellij.openapi.progress.ProgressIndicator
 | 
			
		||||
import com.intellij.openapi.progress.ProgressManager
 | 
			
		||||
import com.intellij.openapi.progress.Task
 | 
			
		||||
import com.intellij.psi.PsiDocumentManager
 | 
			
		||||
import com.intellij.psi.PsiElement
 | 
			
		||||
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
 | 
			
		||||
import java.util.function.BooleanSupplier
 | 
			
		||||
 | 
			
		||||
internal object MacroAutoImport {
 | 
			
		||||
  fun run(editor: Editor, dataContext: DataContext) {
 | 
			
		||||
    val project = CommonDataKeys.PROJECT.getData(dataContext) ?: return
 | 
			
		||||
    val file = PsiDocumentManager.getInstance(project).getPsiFile(editor.document) ?: return
 | 
			
		||||
 | 
			
		||||
    if (!FileDocumentManager.getInstance().requestWriting(editor.document, project)) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val importers = ReferenceImporter.EP_NAME.extensionList
 | 
			
		||||
    if (importers.isEmpty()) {
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Auto import", true) {
 | 
			
		||||
      override fun run(indicator: ProgressIndicator) {
 | 
			
		||||
        val fixes = ReadAction.nonBlocking<List<BooleanSupplier>> {
 | 
			
		||||
          val fixes = mutableListOf<BooleanSupplier>()
 | 
			
		||||
 | 
			
		||||
          file.accept(object : PsiRecursiveElementWalkingVisitor() {
 | 
			
		||||
            override fun visitElement(element: PsiElement) {
 | 
			
		||||
              for (reference in element.references) {
 | 
			
		||||
                if (reference.resolve() != null) {
 | 
			
		||||
                  continue
 | 
			
		||||
                }
 | 
			
		||||
                for (importer in importers) {
 | 
			
		||||
                  importer.computeAutoImportAtOffset(editor, file, element.textRange.startOffset, true)
 | 
			
		||||
                    ?.let(fixes::add)
 | 
			
		||||
                }
 | 
			
		||||
              }
 | 
			
		||||
              super.visitElement(element)
 | 
			
		||||
            }
 | 
			
		||||
          })
 | 
			
		||||
 | 
			
		||||
          return@nonBlocking fixes
 | 
			
		||||
        }.executeSynchronously()
 | 
			
		||||
 | 
			
		||||
        ApplicationManager.getApplication().invokeAndWait {
 | 
			
		||||
          WriteCommandAction.writeCommandAction(project)
 | 
			
		||||
            .withName("Auto Import")
 | 
			
		||||
            .withGroupId("IdeaVimAutoImportAfterMacro")
 | 
			
		||||
            .shouldRecordActionForActiveDocument(true)
 | 
			
		||||
            .run<RuntimeException> {
 | 
			
		||||
              fixes.forEach { it.asBoolean }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -7,6 +7,8 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.completion.CompletionPhase
 | 
			
		||||
import com.intellij.codeInsight.completion.impl.CompletionServiceImpl
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.progress.ProcessCanceledException
 | 
			
		||||
@@ -19,6 +21,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper.message
 | 
			
		||||
import com.maddyhome.idea.vim.macro.VimMacroBase
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Used to handle playback of macros
 | 
			
		||||
@@ -61,8 +64,10 @@ internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
    try {
 | 
			
		||||
      myPotemkinProgress.text2 = if (isInternalMacro) "Executing internal macro" else ""
 | 
			
		||||
      val runnable = runnable@{
 | 
			
		||||
        try {
 | 
			
		||||
          // Handle one keystroke then queue up the next key
 | 
			
		||||
          for (i in 0 until total) {
 | 
			
		||||
            try {
 | 
			
		||||
              myPotemkinProgress.fraction = (i + 1).toDouble() / total
 | 
			
		||||
              while (keyStack.hasStroke()) {
 | 
			
		||||
                val key = keyStack.feedStroke()
 | 
			
		||||
@@ -71,13 +76,25 @@ internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
                } catch (e: ProcessCanceledException) {
 | 
			
		||||
                  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
 | 
			
		||||
              }
 | 
			
		||||
            } finally {
 | 
			
		||||
              keyStack.resetFirst()
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        } finally {
 | 
			
		||||
          keyStack.removeFirst()
 | 
			
		||||
        }
 | 
			
		||||
        if (!isInternalMacro) {
 | 
			
		||||
          MacroAutoImport.run(editor.ij, context.ij)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (isInternalMacro) {
 | 
			
		||||
        runnable()
 | 
			
		||||
 
 | 
			
		||||
@@ -28,7 +28,7 @@ import java.util.Set;
 | 
			
		||||
@Deprecated
 | 
			
		||||
@ApiStatus.ScheduledForRemoval(inVersion = "2.3")
 | 
			
		||||
public class MarkGroup {
 | 
			
		||||
  public List<Jump> jumps = VimInjectorKt.injector.getJumpService().getJumps();
 | 
			
		||||
  public List<Jump> jumps = VimInjectorKt.injector.getJumpService().getJumps("");
 | 
			
		||||
 | 
			
		||||
  public void saveJumpLocation(@NotNull Editor editor) {
 | 
			
		||||
    VimInjectorKt.injector.getJumpService().saveJumpLocation(new IjVimEditor(editor));
 | 
			
		||||
@@ -54,7 +54,7 @@ public class MarkGroup {
 | 
			
		||||
 | 
			
		||||
  @Nullable
 | 
			
		||||
  public Jump getJump(int count) {
 | 
			
		||||
    return VimInjectorKt.injector.getJumpService().getJump(count);
 | 
			
		||||
    return VimInjectorKt.injector.getJumpService().getJump("", count);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Nullable
 | 
			
		||||
@@ -115,7 +115,7 @@ public class MarkGroup {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public int getJumpSpot() {
 | 
			
		||||
    return VimInjectorKt.injector.getJumpService().getJumpSpot();
 | 
			
		||||
    return VimInjectorKt.injector.getJumpService().getJumpSpot("");
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void updateMarkFromDelete(@Nullable VimEditor editor,
 | 
			
		||||
@@ -133,6 +133,6 @@ public class MarkGroup {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void dropLastJump() {
 | 
			
		||||
    VimInjectorKt.injector.getJumpService().dropLastJump();
 | 
			
		||||
    VimInjectorKt.injector.getJumpService().dropLastJump("");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -33,6 +33,8 @@ import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimMotionGroupBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.addJump
 | 
			
		||||
import com.maddyhome.idea.vim.api.anyNonWhitespace
 | 
			
		||||
import com.maddyhome.idea.vim.api.getJump
 | 
			
		||||
import com.maddyhome.idea.vim.api.getJumpSpot
 | 
			
		||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.getVisualLineCount
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
@@ -46,9 +48,7 @@ import com.maddyhome.idea.vim.api.options
 | 
			
		||||
import com.maddyhome.idea.vim.api.visualLineToBufferLine
 | 
			
		||||
import com.maddyhome.idea.vim.command.Argument
 | 
			
		||||
import com.maddyhome.idea.vim.command.MotionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
			
		||||
import com.maddyhome.idea.vim.handler.Motion
 | 
			
		||||
@@ -72,6 +72,8 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
			
		||||
import org.jetbrains.annotations.Range
 | 
			
		||||
import java.io.File
 | 
			
		||||
@@ -163,8 +165,8 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToJump(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Motion {
 | 
			
		||||
    val jumpService = injector.jumpService
 | 
			
		||||
    val spot = jumpService.getJumpSpot()
 | 
			
		||||
    val (line, col, fileName) = jumpService.getJump(count) ?: return Motion.Error
 | 
			
		||||
    val spot = jumpService.getJumpSpot(editor)
 | 
			
		||||
    val (line, col, fileName) = jumpService.getJump(editor, count) ?: return Motion.Error
 | 
			
		||||
    val vf = EditorHelper.getVirtualFile(editor.ij) ?: return Motion.Error
 | 
			
		||||
    val lp = BufferPosition(line, col, false)
 | 
			
		||||
    val lpNative = LogicalPosition(line, col, false)
 | 
			
		||||
@@ -459,6 +461,7 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
      val fileEditor = event.oldEditor
 | 
			
		||||
      if (fileEditor is TextEditor) {
 | 
			
		||||
        val editor = fileEditor.editor
 | 
			
		||||
        if (!editor.isDisposed) {
 | 
			
		||||
          ExOutputModel.getInstance(editor).clear()
 | 
			
		||||
          editor.vim.let { vimEditor ->
 | 
			
		||||
            if (VimStateMachine.getInstance(vimEditor).mode is Mode.VISUAL) {
 | 
			
		||||
@@ -469,4 +472,5 @@ 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.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.KeyboardShortcut
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.ide.CopyPasteManager
 | 
			
		||||
import com.intellij.openapi.keymap.KeymapUtil
 | 
			
		||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
			
		||||
import com.intellij.openapi.keymap.impl.ui.KeymapPanel
 | 
			
		||||
import com.intellij.openapi.options.ShowSettingsUtil
 | 
			
		||||
import com.intellij.openapi.project.DumbAwareAction
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
@@ -32,6 +35,7 @@ import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.handler.KeyMapIssue
 | 
			
		||||
import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.key.ShortcutOwner
 | 
			
		||||
import com.maddyhome.idea.vim.key.ShortcutOwnerInfo
 | 
			
		||||
@@ -180,6 +184,77 @@ internal class NotificationService(private val project: Project?) {
 | 
			
		||||
    ActionIdNotifier.notifyActionId(id, project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun notifyKeymapIssues(issues: ArrayList<KeyMapIssue>) {
 | 
			
		||||
    val keymapManager = KeymapManagerEx.getInstanceEx()
 | 
			
		||||
    val keymap = keymapManager.activeKeymap
 | 
			
		||||
    val message = buildString {
 | 
			
		||||
      appendLine("Current IDE keymap (${keymap.name}) has issues:<br/>")
 | 
			
		||||
      issues.forEach {
 | 
			
		||||
        when (it) {
 | 
			
		||||
          is KeyMapIssue.AddShortcut -> {
 | 
			
		||||
            appendLine("- ${it.key} key is not assigned to the ${it.action} action.<br/>")
 | 
			
		||||
          }
 | 
			
		||||
          is KeyMapIssue.RemoveShortcut -> {
 | 
			
		||||
            appendLine("- ${it.shortcut} key is incorrectly assigned to the ${it.action} action.<br/>")
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    val notification = IDEAVIM_STICKY_GROUP.createNotification(
 | 
			
		||||
      IDEAVIM_NOTIFICATION_TITLE,
 | 
			
		||||
      message,
 | 
			
		||||
      NotificationType.ERROR,
 | 
			
		||||
    )
 | 
			
		||||
    notification.subtitle = "IDE keymap misconfigured"
 | 
			
		||||
    notification.addAction(object : DumbAwareAction("Fix Keymap") {
 | 
			
		||||
      override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
        issues.forEach {
 | 
			
		||||
          when (it) {
 | 
			
		||||
            is KeyMapIssue.AddShortcut -> {
 | 
			
		||||
              keymap.addShortcut(it.actionId, KeyboardShortcut(it.keyStroke, null))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            is KeyMapIssue.RemoveShortcut -> {
 | 
			
		||||
              keymap.removeShortcut(it.actionId, it.shortcut)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        LOG.info("Shortcuts updated $issues")
 | 
			
		||||
        notification.expire()
 | 
			
		||||
        requiredShortcutsAssigned()
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
 | 
			
		||||
      override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
        ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
 | 
			
		||||
        notification.hideBalloon()
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    notification.addAction(object : DumbAwareAction("Ignore") {
 | 
			
		||||
      override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
        LOG.info("Ignored to update shortcuts $issues")
 | 
			
		||||
        notification.hideBalloon()
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    notification.notify(project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun requiredShortcutsAssigned() {
 | 
			
		||||
    val notification = Notification(
 | 
			
		||||
      IDEAVIM_NOTIFICATION_ID,
 | 
			
		||||
      IDEAVIM_NOTIFICATION_TITLE,
 | 
			
		||||
      "Keymap fixed",
 | 
			
		||||
      NotificationType.INFORMATION,
 | 
			
		||||
    )
 | 
			
		||||
    notification.addAction(object : DumbAwareAction("Open Keymap Settings") {
 | 
			
		||||
      override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
        ShowSettingsUtil.getInstance().showSettingsDialog(e.project, KeymapPanel::class.java)
 | 
			
		||||
        notification.hideBalloon()
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    notification.notify(project)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object ActionIdNotifier {
 | 
			
		||||
    private var notification: Notification? = null
 | 
			
		||||
    private const val NO_ID = "<i>Cannot detect action id</i>"
 | 
			
		||||
@@ -314,6 +389,8 @@ internal class NotificationService(private val project: Project?) {
 | 
			
		||||
    const val IDEAVIM_NOTIFICATION_TITLE = "IdeaVim"
 | 
			
		||||
    const val ideajoinExamplesUrl = "https://jb.gg/f9zji9"
 | 
			
		||||
 | 
			
		||||
    private val LOG = logger<NotificationService>()
 | 
			
		||||
 | 
			
		||||
    private fun createIdeaVimRcManually(message: String, project: Project?) {
 | 
			
		||||
      val notification =
 | 
			
		||||
        Notification(IDEAVIM_NOTIFICATION_ID, IDEAVIM_NOTIFICATION_TITLE, message, NotificationType.WARNING)
 | 
			
		||||
 
 | 
			
		||||
@@ -40,10 +40,6 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
			
		||||
  override fun getGlobalIjOptions() = GlobalIjOptions(OptionAccessScope.GLOBAL(null))
 | 
			
		||||
  override fun getEffectiveIjOptions(editor: VimEditor) = EffectiveIjOptions(OptionAccessScope.EFFECTIVE(editor))
 | 
			
		||||
 | 
			
		||||
  private fun updateFallbackWindow(fallbackWindow: VimEditor, targetEditor: VimEditor) {
 | 
			
		||||
    copyPerWindowGlobalValues(fallbackWindow, targetEditor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    fun fileEditorManagerSelectionChangedCallback(event: FileEditorManagerEvent) {
 | 
			
		||||
      // Vim only has one window, and it's not possible to close it. This means that editing a new file will always
 | 
			
		||||
@@ -58,6 +54,8 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
			
		||||
      // Unfortunately, we can't reliably know if a closing editor is the selected editor. Instead, we rely on selection
 | 
			
		||||
      // change events. If an editor is losing selection and there is no new selection, we can assume this means that
 | 
			
		||||
      // the last editor has been closed, and use the closed editor to update the fallback window
 | 
			
		||||
      //
 | 
			
		||||
      // XXX: event.oldEditor will must probably return a disposed editor. So, it should be treated with care
 | 
			
		||||
      if (event.newEditor == null) {
 | 
			
		||||
        (event.oldEditor as? TextEditor)?.editor?.let {
 | 
			
		||||
          (VimPlugin.getOptionGroup() as OptionGroup).updateFallbackWindow(injector.fallbackWindow, it.vim)
 | 
			
		||||
@@ -68,7 +66,7 @@ internal class OptionGroup : VimOptionGroupBase(), IjVimOptionGroup {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class IjOptionConstants {
 | 
			
		||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate")
 | 
			
		||||
  @Suppress("SpellCheckingInspection", "MemberVisibilityCanBePrivate", "ConstPropertyName")
 | 
			
		||||
  companion object {
 | 
			
		||||
 | 
			
		||||
    const val idearefactormode_keep = "keep"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,293 +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, cmd.getCount());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @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");
 | 
			
		||||
 | 
			
		||||
      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());
 | 
			
		||||
 | 
			
		||||
      int repeat = 1;
 | 
			
		||||
      if (text.contains("raction ")) {
 | 
			
		||||
        text = text.replace("raction ", "action ");
 | 
			
		||||
        repeat = panel.getCount();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (int i = 0; i < repeat; i++) {
 | 
			
		||||
        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 direction       The direction to search
 | 
			
		||||
   */
 | 
			
		||||
  @TestOnly
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull Editor editor, @NotNull String pattern,
 | 
			
		||||
  @Override
 | 
			
		||||
  public void setLastSearchState(@SuppressWarnings("unused") @NotNull VimEditor editor, @NotNull String pattern,
 | 
			
		||||
                                 @NotNull String patternOffset, Direction direction) {
 | 
			
		||||
    setLastUsedPattern(pattern, RE_SEARCH, true);
 | 
			
		||||
    lastIgnoreSmartCase = false;
 | 
			
		||||
 
 | 
			
		||||
@@ -15,6 +15,7 @@ import com.intellij.openapi.components.Storage
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.PlaceInfo
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.IdeDocumentHistoryImpl.RecentPlacesListener
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.util.text.StringUtil
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimJumpServiceBase
 | 
			
		||||
@@ -40,24 +41,34 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // We do not delete old project records.
 | 
			
		||||
  // Rationale: It's more likely that users will want to review their old projects and access their jump history 
 | 
			
		||||
  // (e.g., recent files), than for the 100 jumps (max number of records) to consume enough space to be noticeable.
 | 
			
		||||
  override fun getState(): Element {
 | 
			
		||||
    val jumpsElem = Element("jumps")
 | 
			
		||||
    val projectsElem = Element("projects")
 | 
			
		||||
    for ((project, jumps) in projectToJumps) {
 | 
			
		||||
      val projectElement = Element("project").setAttribute("id", project)
 | 
			
		||||
      for (jump in jumps) {
 | 
			
		||||
        val jumpElem = Element("jump")
 | 
			
		||||
        jumpElem.setAttribute("line", jump.line.toString())
 | 
			
		||||
        jumpElem.setAttribute("column", jump.col.toString())
 | 
			
		||||
        jumpElem.setAttribute("filename", StringUtil.notNullize(jump.filepath))
 | 
			
		||||
      jumpsElem.addContent(jumpElem)
 | 
			
		||||
        projectElement.addContent(jumpElem)
 | 
			
		||||
        if (logger.isDebug()) {
 | 
			
		||||
          logger.debug("saved jump = $jump")
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    return jumpsElem
 | 
			
		||||
      projectsElem.addContent(projectElement)
 | 
			
		||||
    }
 | 
			
		||||
    return projectsElem
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun loadState(state: Element) {
 | 
			
		||||
    val jumpList = state.getChildren("jump")
 | 
			
		||||
    for (jumpElement in jumpList) {
 | 
			
		||||
    val projectElements = state.getChildren("project")
 | 
			
		||||
    for (projectElement in projectElements) {
 | 
			
		||||
      val jumps = mutableListOf<Jump>()
 | 
			
		||||
      val jumpElements = projectElement.getChildren("jump")
 | 
			
		||||
      for (jumpElement in jumpElements) {
 | 
			
		||||
        val jump = Jump(
 | 
			
		||||
          Integer.parseInt(jumpElement.getAttributeValue("line")),
 | 
			
		||||
          Integer.parseInt(jumpElement.getAttributeValue("column")),
 | 
			
		||||
@@ -65,14 +76,16 @@ internal class VimJumpServiceImpl : VimJumpServiceBase(), PersistentStateCompone
 | 
			
		||||
        )
 | 
			
		||||
        jumps.add(jump)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (logger.isDebug()) {
 | 
			
		||||
        logger.debug("jumps=$jumps")
 | 
			
		||||
      }
 | 
			
		||||
      val projectId = projectElement.getAttributeValue("id")
 | 
			
		||||
      projectToJumps[projectId] = jumps
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class JumpsListener : RecentPlacesListener {
 | 
			
		||||
internal class JumpsListener(val project: Project) : RecentPlacesListener {
 | 
			
		||||
  override fun recentPlaceAdded(changePlace: PlaceInfo, isChanged: Boolean) {
 | 
			
		||||
    if (!injector.globalIjOptions().unifyjumps) return
 | 
			
		||||
    
 | 
			
		||||
@@ -81,7 +94,7 @@ internal class JumpsListener : RecentPlacesListener {
 | 
			
		||||
      if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
 | 
			
		||||
      // we do not want jumps that were processed before
 | 
			
		||||
      val jump = buildJump(changePlace) ?: return
 | 
			
		||||
      jumpService.addJump(jump, true)
 | 
			
		||||
      jumpService.addJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump, true)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -93,7 +106,7 @@ internal class JumpsListener : RecentPlacesListener {
 | 
			
		||||
      if (changePlace.timeStamp < jumpService.lastJumpTimeStamp) return // this listener is notified asynchronously, and
 | 
			
		||||
      // we do not want jumps that were processed before
 | 
			
		||||
      val jump = buildJump(changePlace) ?: return
 | 
			
		||||
      jumpService.removeJump(jump)
 | 
			
		||||
      jumpService.removeJump(project.basePath ?: IjVimEditor.DEFAULT_PROJECT_ID, jump)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -205,7 +205,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
     * @param event The change event
 | 
			
		||||
     */
 | 
			
		||||
    override fun beforeDocumentChange(event: DocumentEvent) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
 | 
			
		||||
      if (event.oldLength == 0) return
 | 
			
		||||
      val doc = event.document
 | 
			
		||||
@@ -221,7 +221,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
     * @param event The change event
 | 
			
		||||
     */
 | 
			
		||||
    override fun documentChanged(event: DocumentEvent) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (logger.isDebugEnabled) logger.debug("MarkUpdater after, event = $event")
 | 
			
		||||
      if (event.newLength == 0 || event.newLength == 1 && event.newFragment[0] != '\n') return
 | 
			
		||||
      val doc = event.document
 | 
			
		||||
@@ -242,7 +242,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
 | 
			
		||||
  class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
 | 
			
		||||
    override fun bookmarkAdded(group: BookmarkGroup, bookmark: Bookmark) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
@@ -255,7 +255,7 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun bookmarkRemoved(group: BookmarkGroup, bookmark: Bookmark) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      if (!injector.globalIjOptions().ideamarks) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -27,15 +27,12 @@ import com.maddyhome.idea.vim.api.getLineEndOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isBlock
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isChar
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isLine
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.diagnostic.debug
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.RWLockLabel
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.ide.isClionNova
 | 
			
		||||
import com.maddyhome.idea.vim.mark.VimMarkConstants.MARK_CHANGE_POS
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
@@ -48,6 +45,10 @@ import com.maddyhome.idea.vim.put.PutData
 | 
			
		||||
import com.maddyhome.idea.vim.put.VimPasteProvider
 | 
			
		||||
import com.maddyhome.idea.vim.put.VimPutBase
 | 
			
		||||
import com.maddyhome.idea.vim.register.RegisterConstants
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isBlock
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isChar
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isLine
 | 
			
		||||
import java.awt.datatransfer.DataFlavor
 | 
			
		||||
 | 
			
		||||
internal class PutGroup : VimPutBase() {
 | 
			
		||||
@@ -189,7 +190,7 @@ internal class PutGroup : VimPutBase() {
 | 
			
		||||
    endOffset: Int,
 | 
			
		||||
  ): Int {
 | 
			
		||||
    // Temp fix for VIM-2808. Should be removed after rider will fix it's issues
 | 
			
		||||
    if (PlatformUtils.isRider()) return endOffset
 | 
			
		||||
    if (PlatformUtils.isRider() || isClionNova()) return endOffset
 | 
			
		||||
 | 
			
		||||
    val startLine = editor.offsetToBufferPosition(startOffset).line
 | 
			
		||||
    val endLine = editor.offsetToBufferPosition(endOffset - 1).line
 | 
			
		||||
 
 | 
			
		||||
@@ -40,9 +40,15 @@ internal object IdeaSelectionControl {
 | 
			
		||||
   * This method should be in sync with [predictMode]
 | 
			
		||||
   *
 | 
			
		||||
   * Control unexpected (non vim) selection change and adjust a mode to it. The new mode is not enabled immediately,
 | 
			
		||||
   *   but with some delay (using [VimVisualTimer])
 | 
			
		||||
   *   but with some delay (using [VimVisualTimer]). The delay is used because some platform functionality
 | 
			
		||||
   *   makes features by using selection. E.g. PyCharm unindent firstly select the indenting then applies delete action.
 | 
			
		||||
   *   Such "quick" selection breaks IdeaVim behaviour.
 | 
			
		||||
   *
 | 
			
		||||
   * See [VimVisualTimer] to more info.
 | 
			
		||||
   *
 | 
			
		||||
   * XXX: This method can be split into "change calculation" and "change apply". In this way, we would be able
 | 
			
		||||
   *   to calculate if we need to make a change or not and reduce the number of these calls.
 | 
			
		||||
   *   If this refactoring ever is applied, please add `assertNull(VimVisualTimer.timer)` to `tearDown` of VimTestCase.
 | 
			
		||||
   */
 | 
			
		||||
  fun controlNonVimSelectionChange(
 | 
			
		||||
    editor: Editor,
 | 
			
		||||
@@ -50,6 +56,7 @@ internal object IdeaSelectionControl {
 | 
			
		||||
  ) {
 | 
			
		||||
    VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
 | 
			
		||||
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return@singleTask
 | 
			
		||||
      if (editor.isIdeaVimDisabledHere) return@singleTask
 | 
			
		||||
 | 
			
		||||
      logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
 | 
			
		||||
@@ -121,8 +128,9 @@ internal object IdeaSelectionControl {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun dontChangeMode(editor: Editor): Boolean =
 | 
			
		||||
    editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
 | 
			
		||||
  private fun dontChangeMode(editor: Editor): Boolean {
 | 
			
		||||
    return editor.isTemplateActive() && (editor.vim.isIdeaRefactorModeKeep || editor.vim.mode.hasVisualSelection)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun chooseNonSelectionMode(editor: Editor): Mode {
 | 
			
		||||
    val templateActive = editor.isTemplateActive()
 | 
			
		||||
 
 | 
			
		||||
@@ -9,10 +9,10 @@
 | 
			
		||||
package com.maddyhome.idea.vim.group.visual
 | 
			
		||||
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.mode
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer.singleTask
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import java.awt.event.ActionEvent
 | 
			
		||||
import javax.swing.Timer
 | 
			
		||||
 | 
			
		||||
@@ -79,6 +79,11 @@ internal object VimVisualTimer {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun drop() {
 | 
			
		||||
    swingTimer?.stop()
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  inline fun timerAction(task: (initialMode: Mode?) -> Unit) {
 | 
			
		||||
    task(mode)
 | 
			
		||||
    swingTimer = null
 | 
			
		||||
 
 | 
			
		||||
@@ -12,6 +12,7 @@ import com.intellij.serviceContainer.BaseKeyedLazyInstance
 | 
			
		||||
import com.intellij.util.SmartList
 | 
			
		||||
import com.intellij.util.xmlb.annotations.Attribute
 | 
			
		||||
import com.maddyhome.idea.vim.command.MappingMode
 | 
			
		||||
import org.jetbrains.annotations.ApiStatus.ScheduledForRemoval
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -36,6 +37,8 @@ import javax.swing.KeyStroke
 | 
			
		||||
 *   The reason is startup performance. Using the extension points you don't even have to load classes of actions.
 | 
			
		||||
 *   So, all actions are loaded on demand, including classes in classloader.
 | 
			
		||||
 */
 | 
			
		||||
@Deprecated(message = "Please use CommandOrMotion annotation")
 | 
			
		||||
@ScheduledForRemoval(inVersion = "2.9.0")
 | 
			
		||||
internal class ActionBeanClass : BaseKeyedLazyInstance<EditorActionHandlerBase>() {
 | 
			
		||||
  @Attribute("implementation")
 | 
			
		||||
  var implementation: String? = 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}")
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,67 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandlerBean
 | 
			
		||||
import com.intellij.openapi.extensions.ExtensionPointName
 | 
			
		||||
import com.intellij.openapi.keymap.ex.KeymapManagerEx
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.startup.ProjectActivity
 | 
			
		||||
import com.maddyhome.idea.vim.api.key
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Logs the chain of handlers for esc and enter
 | 
			
		||||
 *
 | 
			
		||||
 * As we made a migration to the new way of handling esc keys (VIM-2974), we may face several issues around that
 | 
			
		||||
 * One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
 | 
			
		||||
 * the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
 | 
			
		||||
 *
 | 
			
		||||
 * This is a logger that logs the chain of handlers.
 | 
			
		||||
 *
 | 
			
		||||
 * Strictly speaking, such access to the extension point is not allowed by the platform. But we can't do this thing
 | 
			
		||||
 *   otherwise, so let's use it as long as we can.
 | 
			
		||||
 */
 | 
			
		||||
internal class EditorHandlersChainLogger : ProjectActivity {
 | 
			
		||||
  @Suppress("UnresolvedPluginConfigReference")
 | 
			
		||||
  private val editorHandlers = ExtensionPointName<EditorActionHandlerBean>("com.intellij.editorActionHandler")
 | 
			
		||||
 | 
			
		||||
  override suspend fun execute(project: Project) {
 | 
			
		||||
    val escHandlers = editorHandlers.extensionList
 | 
			
		||||
      .filter { it.action == "EditorEscape" }
 | 
			
		||||
      .joinToString("\n") { it.implementationClass }
 | 
			
		||||
    val enterHandlers = editorHandlers.extensionList
 | 
			
		||||
      .filter { it.action == "EditorEnter" }
 | 
			
		||||
      .joinToString("\n") { it.implementationClass }
 | 
			
		||||
 | 
			
		||||
    LOG.info("Esc handlers chain:\n$escHandlers")
 | 
			
		||||
    LOG.info("Enter handlers chain:\n$enterHandlers")
 | 
			
		||||
 | 
			
		||||
    val keymapManager = KeymapManagerEx.getInstanceEx()
 | 
			
		||||
    val keymap = keymapManager.activeKeymap
 | 
			
		||||
    val keymapShortcutsForEsc = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ESCAPE).joinToString()
 | 
			
		||||
    val keymapShortcutsForEnter = keymap.getShortcuts(IdeActions.ACTION_EDITOR_ENTER).joinToString()
 | 
			
		||||
 | 
			
		||||
    LOG.info("Active keymap (${keymap.name}) shortcuts for esc: $keymapShortcutsForEsc, Shortcuts for enter: $keymapShortcutsForEnter")
 | 
			
		||||
 | 
			
		||||
    val actionsForEsc = keymap.getActionIds(key("<esc>")).joinToString("\n")
 | 
			
		||||
    val actionsForEnter = keymap.getActionIds(key("<enter>")).joinToString("\n")
 | 
			
		||||
 | 
			
		||||
    LOG.info(
 | 
			
		||||
      "Also keymap (${keymap.name}) has " +
 | 
			
		||||
        "the following actions assigned to esc:\n$actionsForEsc " +
 | 
			
		||||
        "\nand following actions assigned to enter:\n$actionsForEnter"
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    val LOG = logger<EditorHandlersChainLogger>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
}
 | 
			
		||||
@@ -8,27 +8,67 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.handler
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.editorActions.AutoHardWrapHandler
 | 
			
		||||
import com.intellij.codeInsight.lookup.LookupManager
 | 
			
		||||
import com.intellij.formatting.LineWrappingUtil
 | 
			
		||||
import com.intellij.ide.DataManager
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.application.invokeLater
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.EditorActionHandler
 | 
			
		||||
import com.intellij.openapi.editor.actions.SplitLineAction
 | 
			
		||||
import com.intellij.openapi.editor.impl.CaretModelImpl
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileDocumentManager
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.util.UserDataHolder
 | 
			
		||||
import com.intellij.openapi.util.removeUserData
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.key
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandState
 | 
			
		||||
import com.maddyhome.idea.vim.helper.mode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inNormalMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isPrimaryEditor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.actionStartedFromVim
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
internal val commandContinuation = Key.create<EditorActionHandler>("commandContinuation")
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler that corrects the shape of the caret in python notebooks.
 | 
			
		||||
 *
 | 
			
		||||
 * By default, py notebooks show a thin caret after entering the cell.
 | 
			
		||||
 *   However, we're in normal mode, so this handler fixes it.
 | 
			
		||||
 */
 | 
			
		||||
internal class CaretShapeEnterEditorHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
			
		||||
  override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
			
		||||
    if (VimPlugin.isEnabled()) {
 | 
			
		||||
      invokeLater {
 | 
			
		||||
        editor.updateCaretsVisualAttributes()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    nextHandler.execute(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
 | 
			
		||||
    return nextHandler.isEnabled(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This handler doesn't work in tests for ex commands
 | 
			
		||||
 */
 | 
			
		||||
internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
			
		||||
internal abstract class OctopusHandler(private val nextHandler: EditorActionHandler?) : EditorActionHandler() {
 | 
			
		||||
 | 
			
		||||
  abstract fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?)
 | 
			
		||||
  open fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
 | 
			
		||||
@@ -37,64 +77,254 @@ internal abstract class OctopusHandler(private val nextHandler: EditorActionHand
 | 
			
		||||
 | 
			
		||||
  final override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
			
		||||
    if (isThisHandlerEnabled(editor, caret, dataContext)) {
 | 
			
		||||
      val executeInInvokeLater = executeInInvokeLater(editor)
 | 
			
		||||
      val executionHandler = {
 | 
			
		||||
        try {
 | 
			
		||||
          (dataContext as? UserDataHolder)?.putUserData(commandContinuation, nextHandler)
 | 
			
		||||
          executeHandler(editor, caret, dataContext)
 | 
			
		||||
    } else {
 | 
			
		||||
      nextHandler.execute(editor, caret, dataContext)
 | 
			
		||||
        } finally {
 | 
			
		||||
          (dataContext as? UserDataHolder)?.removeUserData(commandContinuation)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
  @Suppress("RedundantIf")
 | 
			
		||||
      if (executeInInvokeLater) {
 | 
			
		||||
        // This `invokeLater` is used to escape the potential `runForEachCaret` function.
 | 
			
		||||
        //
 | 
			
		||||
        // The `runForEachCaret` function is disallowed to be called recursively. However, with this new handler, we lose
 | 
			
		||||
        //   control if we execute the code inside this function or not. See IDEA-300030 for details.
 | 
			
		||||
        // This means the code in IdeaVim MUST NOT call `runForEachCaret` function. While this is possible for most cases,
 | 
			
		||||
        //   the user may make a mapping to some intellij action where the `runForEachCaret` is called. This breaks
 | 
			
		||||
        //   the condition (see VIM-3103 for example).
 | 
			
		||||
        // Since we can't make sure we don't execute `runForEachCaret`, we have to "escape" out of this function. This is
 | 
			
		||||
        //   done by scheduling the execution of our code later via the invokeLater function.
 | 
			
		||||
        //
 | 
			
		||||
        // We run this job only once for a primary caret. In the handler itself, we'll multiply the execution by the
 | 
			
		||||
        //   number of carets. If we run this job for each caret, we may end up in the issue like VIM-3186.
 | 
			
		||||
        //   However, I think that we may do some refactoring to run this job for each caret (if needed).
 | 
			
		||||
        //
 | 
			
		||||
        // For the moment, the known case when the caret is null - work in injected editor - VIM-3195
 | 
			
		||||
        if (caret == null || caret == editor.caretModel.primaryCaret) {
 | 
			
		||||
          ApplicationManager.getApplication().invokeLater(executionHandler)
 | 
			
		||||
        }
 | 
			
		||||
      } else {
 | 
			
		||||
        executionHandler()
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      nextHandler?.execute(editor, caret, dataContext)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun executeInInvokeLater(editor: Editor): Boolean {
 | 
			
		||||
    // Currently we have a workaround for the PY console VIM-3157
 | 
			
		||||
    val fileName = FileDocumentManager.getInstance().getFile(editor.document)?.name
 | 
			
		||||
    if (
 | 
			
		||||
      fileName == "Python Console.py" || // This is the name in 232+
 | 
			
		||||
      fileName == "Python Console" // This is the name in 231
 | 
			
		||||
    ) return false
 | 
			
		||||
    return (editor.caretModel as? CaretModelImpl)?.isIteratingOverCarets ?: true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  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 (dataContext?.actionStartedFromVim == true) return false
 | 
			
		||||
    if (!enableOctopus) return false
 | 
			
		||||
    if (isNotActualKeyPress(dataContext)) return false
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * In some cases IJ runs handlers to imitate "enter" or other key. In such cases we should not process it on the
 | 
			
		||||
   *   IdeaVim side because the user may have mappings on enter the we'll get an unexpected behaviour.
 | 
			
		||||
   * This method should return true if we detect that this handler is called in such case and this is not an
 | 
			
		||||
   *   actual keypress from the user.
 | 
			
		||||
   */
 | 
			
		||||
  private fun isNotActualKeyPress(dataContext: DataContext?): Boolean {
 | 
			
		||||
    if (dataContext != null) {
 | 
			
		||||
      // This flag is set when the enter handlers are executed as a part of moving the comment on the new line
 | 
			
		||||
      val dataManager = DataManager.getInstance()
 | 
			
		||||
      if (dataManager.loadFromDataContext(dataContext, AutoHardWrapHandler.AUTO_WRAP_LINE_IN_PROGRESS_KEY) == true) {
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // From VIM-3177
 | 
			
		||||
      val wrapLongLineDuringFormattingInProgress = dataManager
 | 
			
		||||
        .loadFromDataContext(dataContext, LineWrappingUtil.WRAP_LONG_LINE_DURING_FORMATTING_IN_PROGRESS_KEY)
 | 
			
		||||
      if (wrapLongLineDuringFormattingInProgress == true) {
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // From VIM-3203
 | 
			
		||||
      val splitLineInProgress = dataManager.loadFromDataContext(dataContext, SplitLineAction.SPLIT_LINE_KEY)
 | 
			
		||||
      if (splitLineInProgress == true) {
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (dataManager.loadFromDataContext(dataContext, StartNewLineDetectorBase.Util.key) == true) {
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (dataContext?.actionStartedFromVim == true) return true
 | 
			
		||||
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  final override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
 | 
			
		||||
    return isThisHandlerEnabled(editor, caret, dataContext) || nextHandler.isEnabled(editor, caret, dataContext)
 | 
			
		||||
    return isThisHandlerEnabled(editor, caret, dataContext)
 | 
			
		||||
      || nextHandler?.isEnabled(editor, caret, dataContext) == true
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Known conflicts & solutions:
 | 
			
		||||
 * - Smart step into - set handler after
 | 
			
		||||
 * - Python notebooks - set handler before - test needed!
 | 
			
		||||
 * - Python notebooks - set handler after
 | 
			
		||||
 * - Ace jump - set handler after
 | 
			
		||||
 * - Lookup - doesn't intersect with enter anymore
 | 
			
		||||
 * - App code - set handler after
 | 
			
		||||
 * - Template - doesn't intersect with enter anymore
 | 
			
		||||
 * - rd.client.editor.enter - set handler before. Otherwise, rider will add new line on enter even in normal mode
 | 
			
		||||
 *
 | 
			
		||||
 * This rule is disabled due to VIM-3124
 | 
			
		||||
 * - before terminalEnter - not necessary, but terminalEnter causes "file is read-only" tooltip for readonly files VIM-3122
 | 
			
		||||
 * - `first` is set to satisfy sorting condition "before terminalEnter".
 | 
			
		||||
 *
 | 
			
		||||
 *
 | 
			
		||||
 * DO NOT add handlers that force to add "first" ordering. This doesn't work with jupyterCommandModeEnterKeyHandler (see VIM-3124)
 | 
			
		||||
 */
 | 
			
		||||
internal class VimEnterHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
 | 
			
		||||
internal class VimEnterHandler(nextHandler: EditorActionHandler?) : VimKeyHandler(nextHandler) {
 | 
			
		||||
  override val key: String = "<CR>"
 | 
			
		||||
 | 
			
		||||
  override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
 | 
			
		||||
    if (!super.isHandlerEnabled(editor, dataContext)) return false
 | 
			
		||||
    // This is important for one-line editors, to turn off enter.
 | 
			
		||||
    // Some one-line editors rely on the fact that there are no enter actions registered. For example, hash search in git
 | 
			
		||||
    // See VIM-2974 for example where it was broken
 | 
			
		||||
    return !editor.isOneLineMode
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Known conflicts & solutions:
 | 
			
		||||
 *
 | 
			
		||||
 * - Smart step into - set handler after
 | 
			
		||||
 * - Python notebooks - set handler before - test needed
 | 
			
		||||
 * - Python notebooks - set handler before - yes, we have `<CR>` as "after" and `<esc>` as before. I'm not completely sure
 | 
			
		||||
 *   why this combination is correct, but other versions don't work.
 | 
			
		||||
 * - Ace jump - set handler after
 | 
			
		||||
 * - Lookup - It disappears after putting our esc before templateEscape. But I'm not sure why it works like that
 | 
			
		||||
 * - App code - Need to review
 | 
			
		||||
 * - Template - Need to review
 | 
			
		||||
 * - before backend.escape - to handle our handlers before Rider processing. Also, without this rule, we get problems like VIM-3146
 | 
			
		||||
 */
 | 
			
		||||
internal class VimEscHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
 | 
			
		||||
  override val key: String = "<Esc>"
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Also, we need to pass esc to IDE if we're in normal mode and there is nothing to cancel
 | 
			
		||||
   * (e.g. we still can cancel numbers, or cancel the replace character mode)
 | 
			
		||||
   */
 | 
			
		||||
  override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
 | 
			
		||||
    return editor.mode != CommandState.Mode.COMMAND ||
 | 
			
		||||
      editor.vimStateMachine?.commandBuilder?.count != 0 ||
 | 
			
		||||
      editor.vimStateMachine?.isReplaceCharacter == true
 | 
			
		||||
    val ideaVimSupportDialog =
 | 
			
		||||
      injector.globalIjOptions().ideavimsupport.contains(IjOptionConstants.ideavimsupport_dialog)
 | 
			
		||||
 | 
			
		||||
    return editor.isPrimaryEditor() ||
 | 
			
		||||
      EditorHelper.isFileEditor(editor) && !editor.vim.mode.inNormalMode ||
 | 
			
		||||
      ideaVimSupportDialog && !editor.vim.mode.inNormalMode
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal abstract class VimKeyHandler(nextHandler: EditorActionHandler) : OctopusHandler(nextHandler) {
 | 
			
		||||
/**
 | 
			
		||||
 * Rider (and CLion Nova) uses a separate handler for esc to close the completion. IdeaOnlyEscapeHandlerAction is especially
 | 
			
		||||
 *   designer to get all the esc presses, and if there is a completion close it and do not pass the execution further.
 | 
			
		||||
 *   This doesn't work the same as in IJ.
 | 
			
		||||
 * In IdeaVim, we'd like to exit insert mode on closing completion. This is a requirement as the change of this
 | 
			
		||||
 *   behaviour causes a lot of complaining from users. Since the rider handler gets execution control, we don't
 | 
			
		||||
 *    receive an event and don't exit the insert mode.
 | 
			
		||||
 * To fix it, this special handler exists only for rider and stands before the rider's handler. We don't execute the
 | 
			
		||||
 *   handler from rider because the autocompletion is closed automatically anyway.
 | 
			
		||||
 */
 | 
			
		||||
internal class VimEscForRiderHandler(nextHandler: EditorActionHandler) : VimKeyHandler(nextHandler) {
 | 
			
		||||
  override val key: String = "<Esc>"
 | 
			
		||||
 | 
			
		||||
  override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
 | 
			
		||||
    return LookupManager.getActiveLookup(editor) != null
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Empty logger for esc presses
 | 
			
		||||
 *
 | 
			
		||||
 * As we made a migration to the new way of handling esc keys (VIM-2974), we may face several issues around that
 | 
			
		||||
 * One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
 | 
			
		||||
 * the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
 | 
			
		||||
 * This handler, that should stand in front of handlers change, just logs the event of pressing the key
 | 
			
		||||
 * and passes the execution.
 | 
			
		||||
 */
 | 
			
		||||
internal class VimEscLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
			
		||||
  override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
			
		||||
    LOG.info("Esc pressed")
 | 
			
		||||
    nextHandler.execute(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
 | 
			
		||||
    return nextHandler.isEnabled(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    val LOG = logger<VimEscLoggerHandler>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Workaround to support "Start New Line" action in normal mode.
 | 
			
		||||
 * IJ executes enter handler on "Start New Line". This causes an issue that IdeaVim thinks that this is just an enter key.
 | 
			
		||||
 * This thing should be refactored, but for now we'll use this workaround VIM-3159
 | 
			
		||||
 *
 | 
			
		||||
 * The Same thing happens with "Start New Line Before Current" action.
 | 
			
		||||
 */
 | 
			
		||||
internal class StartNewLineDetector(nextHandler: EditorActionHandler) : StartNewLineDetectorBase(nextHandler)
 | 
			
		||||
internal class StartNewLineBeforeCurrentDetector(nextHandler: EditorActionHandler) :
 | 
			
		||||
  StartNewLineDetectorBase(nextHandler)
 | 
			
		||||
 | 
			
		||||
internal open class StartNewLineDetectorBase(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
			
		||||
  override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
			
		||||
    DataManager.getInstance().saveInDataContext(dataContext, Util.key, true)
 | 
			
		||||
    nextHandler.execute(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
 | 
			
		||||
    return nextHandler.isEnabled(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  object Util {
 | 
			
		||||
    val key = Key.create<Boolean>("vim.is.start.new.line")
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    val LOG = logger<VimEscLoggerHandler>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Empty logger for enter presses
 | 
			
		||||
 *
 | 
			
		||||
 * As we made a migration to the new way of handling enter keys (VIM-2974), we may face several issues around that
 | 
			
		||||
 * One of the possible issues is that some plugin may also register a shortcut for this key and do not pass
 | 
			
		||||
 * the control to the next handler. In this way, the esc won't work, but there will be no exceptions.
 | 
			
		||||
 * This handler, that should stand in front of handlers change, just logs the event of pressing the key
 | 
			
		||||
 * and passes the execution.
 | 
			
		||||
 */
 | 
			
		||||
internal class VimEnterLoggerHandler(private val nextHandler: EditorActionHandler) : EditorActionHandler() {
 | 
			
		||||
  override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
			
		||||
    LOG.info("Enter pressed")
 | 
			
		||||
    nextHandler.execute(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
 | 
			
		||||
    return nextHandler.isEnabled(editor, caret, dataContext)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    val LOG = logger<VimEnterLoggerHandler>()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : OctopusHandler(nextHandler) {
 | 
			
		||||
 | 
			
		||||
  abstract val key: String
 | 
			
		||||
 | 
			
		||||
@@ -111,27 +341,12 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler) : Octopu
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
 | 
			
		||||
  if (!enableOctopus) return false
 | 
			
		||||
  // CMD line has a different processing mechanizm: the processing actions are registered
 | 
			
		||||
  //   for the input field component. These keys are not dispatched via the octopus handler.
 | 
			
		||||
  if (editor.vim.mode is Mode.CMD_LINE) return false
 | 
			
		||||
  when {
 | 
			
		||||
    s.keyCode == KeyEvent.VK_ENTER -> return editor.mode in listOf(
 | 
			
		||||
      CommandState.Mode.COMMAND,
 | 
			
		||||
      CommandState.Mode.INSERT,
 | 
			
		||||
      CommandState.Mode.VISUAL,
 | 
			
		||||
    )
 | 
			
		||||
    s.keyCode == KeyEvent.VK_ESCAPE -> return editor.mode in listOf(
 | 
			
		||||
      CommandState.Mode.COMMAND,
 | 
			
		||||
      CommandState.Mode.INSERT,
 | 
			
		||||
      CommandState.Mode.VISUAL,
 | 
			
		||||
    )
 | 
			
		||||
    s.keyCode == KeyEvent.VK_ENTER && s.modifiers == 0 -> return true
 | 
			
		||||
    s.keyCode == KeyEvent.VK_ESCAPE && s.modifiers == 0 -> return true
 | 
			
		||||
  }
 | 
			
		||||
  return false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Experiment: At the moment, IdeaVim intersects all shortcuts and sends the to [KeyHandler]
 | 
			
		||||
 * However, this doesn't seem to be a good solution as other handlers are overridden by vim.
 | 
			
		||||
 * If this option is enabled, vim will connect to IDE via EditorActionHandler extension point
 | 
			
		||||
 *   what seems to be a way better solution as this is a correct way to override editor actions like enter, right, etc.
 | 
			
		||||
 */
 | 
			
		||||
internal val enableOctopus: Boolean
 | 
			
		||||
  get() = injector.globalIjOptions().octopushandler
 | 
			
		||||
 
 | 
			
		||||
@@ -8,11 +8,13 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.diagnostic.thisLogger
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.CaretVisualAttributes
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorSettingsExternalizable
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
@@ -79,6 +81,7 @@ private fun Editor.guicursorMode(): GuiCursorMode {
 | 
			
		||||
private fun isBlockCursorOverride() = EditorSettingsExternalizable.getInstance().isBlockCursor
 | 
			
		||||
 | 
			
		||||
private fun Editor.updatePrimaryCaretVisualAttributes() {
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
 | 
			
		||||
 | 
			
		||||
  // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
 | 
			
		||||
@@ -86,6 +89,7 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun Editor.updateSecondaryCaretsVisualAttributes() {
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) thisLogger().error("The caret attributes should not be updated if the IdeaVim is disabled")
 | 
			
		||||
  // IntelliJ simulates visual block with multiple carets with selections. Do our best to hide them
 | 
			
		||||
  val attributes = if (this.vim.inBlockSelection) HIDDEN else AttributesCache.getCaretVisualAttributes(this)
 | 
			
		||||
  this.caretModel.allCarets.forEach {
 | 
			
		||||
 
 | 
			
		||||
@@ -36,7 +36,7 @@ public val Editor.mode: CommandState.Mode
 | 
			
		||||
  get() {
 | 
			
		||||
    val mode = this.vim.vimStateMachine.mode
 | 
			
		||||
    return when (mode) {
 | 
			
		||||
      Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
      is Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
      Mode.INSERT -> CommandState.Mode.INSERT
 | 
			
		||||
      is Mode.NORMAL -> CommandState.Mode.COMMAND
 | 
			
		||||
      is Mode.OP_PENDING -> CommandState.Mode.OP_PENDING
 | 
			
		||||
 
 | 
			
		||||
@@ -335,7 +335,7 @@ public class EditorHelper {
 | 
			
		||||
 | 
			
		||||
    final int offset = y - ((screenHeight - lineHeight) / lineHeight / 2 * lineHeight);
 | 
			
		||||
    @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);
 | 
			
		||||
 | 
			
		||||
    // For `zz`, we want to use virtual space and move any line, including the last one, to the middle of the screen.
 | 
			
		||||
 
 | 
			
		||||
@@ -110,7 +110,7 @@ internal fun Editor.isTemplateActive(): Boolean {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun vimEnabled(editor: Editor?): Boolean {
 | 
			
		||||
  if (!VimPlugin.isEnabled()) return false
 | 
			
		||||
  if (VimPlugin.isNotEnabled()) return false
 | 
			
		||||
  if (editor != null && editor.isIdeaVimDisabledHere) return false
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,10 +15,12 @@ import com.intellij.openapi.actionSystem.AnAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionResult
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContextWrapper
 | 
			
		||||
import com.intellij.openapi.actionSystem.EmptyAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.IdeActions
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx
 | 
			
		||||
import com.intellij.openapi.actionSystem.ex.ActionUtil
 | 
			
		||||
import com.intellij.openapi.actionSystem.impl.ProxyShortcutSet
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
import com.intellij.openapi.command.UndoConfirmationPolicy
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
@@ -39,6 +41,8 @@ import com.maddyhome.idea.vim.newapi.IjNativeAction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.runFromVimKey
 | 
			
		||||
import org.jetbrains.annotations.NonNls
 | 
			
		||||
import java.awt.Component
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
import javax.swing.SwingUtilities
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@@ -139,7 +143,7 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
      manager.fireAfterActionPerformed(action, event, result!!)
 | 
			
		||||
    }
 | 
			
		||||
    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
 | 
			
		||||
   */
 | 
			
		||||
  override fun executeAction(name: @NonNls String, context: ExecutionContext): Boolean {
 | 
			
		||||
    val aMgr = ActionManager.getInstance()
 | 
			
		||||
    val action = aMgr.getAction(name)
 | 
			
		||||
    val action = getAction(name, context)
 | 
			
		||||
    return action != null && executeAction(null, IjNativeAction(action), context)
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  private fun getAction(name: String, context: ExecutionContext): AnAction? {
 | 
			
		||||
    val actionManager = ActionManager.getInstance()
 | 
			
		||||
    val action = actionManager.getAction(name)
 | 
			
		||||
    if (action !is EmptyAction) return action
 | 
			
		||||
 | 
			
		||||
    // But if the action is an instance of EmptyAction, the fun begins
 | 
			
		||||
    var component: Component? = context.ij.getData(PlatformDataKeys.CONTEXT_COMPONENT) ?: return null
 | 
			
		||||
    while (component != null) {
 | 
			
		||||
      if (component !is JComponent) {
 | 
			
		||||
        component = component.parent
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val listOfActions = ActionUtil.getActions(component)
 | 
			
		||||
      if (listOfActions.isEmpty()) {
 | 
			
		||||
        component = component.getParent()
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      fun AnAction.getId(): String? {
 | 
			
		||||
        return actionManager.getId(this)
 | 
			
		||||
          ?: (shortcutSet as? ProxyShortcutSet)?.actionId
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      for (action in listOfActions) {
 | 
			
		||||
        if (action.getId() == name) {
 | 
			
		||||
          return action
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      component = component.getParent()
 | 
			
		||||
    }
 | 
			
		||||
    return null
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun executeCommand(
 | 
			
		||||
    editor: VimEditor?,
 | 
			
		||||
    runnable: Runnable,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
 | 
			
		||||
import com.maddyhome.idea.vim.api.options
 | 
			
		||||
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.getApproximateScreenWidth
 | 
			
		||||
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.scrollVisualLineToTopOfScreen
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
import kotlin.math.min
 | 
			
		||||
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.
 | 
			
		||||
    val topLine = getVisualLineAtTopOfScreen(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
 | 
			
		||||
    val scrollOffset = injector.options(vimEditor).scrolloff
 | 
			
		||||
 
 | 
			
		||||
@@ -9,6 +9,7 @@
 | 
			
		||||
package com.maddyhome.idea.vim.helper;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Lists;
 | 
			
		||||
import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerEx;
 | 
			
		||||
import com.intellij.lang.CodeDocumentationAwareCommenter;
 | 
			
		||||
import com.intellij.lang.Commenter;
 | 
			
		||||
import com.intellij.lang.Language;
 | 
			
		||||
@@ -16,15 +17,15 @@ import com.intellij.lang.LanguageCommenters;
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger;
 | 
			
		||||
import com.intellij.openapi.editor.Caret;
 | 
			
		||||
import com.intellij.openapi.editor.Editor;
 | 
			
		||||
import com.intellij.openapi.project.Project;
 | 
			
		||||
import com.intellij.psi.PsiComment;
 | 
			
		||||
import com.intellij.psi.PsiElement;
 | 
			
		||||
import com.intellij.psi.PsiFile;
 | 
			
		||||
import com.intellij.psi.util.PsiTreeUtil;
 | 
			
		||||
import com.intellij.spellchecker.SpellCheckerSeveritiesProvider;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.common.CharacterPosition;
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction;
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange;
 | 
			
		||||
@@ -32,6 +33,12 @@ import com.maddyhome.idea.vim.newapi.IjVimCaret;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.CharPointer;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.RegExp;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparator;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntIterator;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntRBTreeSet;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntSortedSet;
 | 
			
		||||
import kotlin.Pair;
 | 
			
		||||
import org.jetbrains.annotations.Contract;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
@@ -1523,6 +1530,42 @@ public class SearchHelper {
 | 
			
		||||
    return PsiHelper.findMethodEnd(editor, caret.getOffset(), count);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static int findMisspelledWords(@NotNull Editor editor,
 | 
			
		||||
                                       int startOffset,
 | 
			
		||||
                                       int endOffset,
 | 
			
		||||
                                       int skipCount,
 | 
			
		||||
                                       IntComparator offsetOrdering) {
 | 
			
		||||
    Project project = editor.getProject();
 | 
			
		||||
    if (project == null) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    IntSortedSet offsets = new IntRBTreeSet(offsetOrdering);
 | 
			
		||||
    DaemonCodeAnalyzerEx.processHighlights(editor.getDocument(), project, SpellCheckerSeveritiesProvider.TYPO,
 | 
			
		||||
                                           startOffset, endOffset, highlight -> {
 | 
			
		||||
        if (highlight.getSeverity() == SpellCheckerSeveritiesProvider.TYPO) {
 | 
			
		||||
          int offset = highlight.getStartOffset();
 | 
			
		||||
          if (offset >= startOffset && offset <= endOffset) {
 | 
			
		||||
            offsets.add(offset);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
      });
 | 
			
		||||
    
 | 
			
		||||
    if (offsets.isEmpty()) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
    if (skipCount >= offsets.size()) {
 | 
			
		||||
      return offsets.lastInt();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      IntIterator offsetIterator = offsets.iterator();
 | 
			
		||||
      offsetIterator.skip(skipCount);
 | 
			
		||||
      return offsetIterator.nextInt();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
 | 
			
		||||
    List<String> pairs = options(injector, vimEditor).getMatchpairs();
 | 
			
		||||
    StringBuilder res = new StringBuilder();
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.helper
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
import com.intellij.openapi.command.impl.UndoManagerImpl
 | 
			
		||||
import com.intellij.openapi.command.undo.UndoManager
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
 | 
			
		||||
@@ -20,7 +19,6 @@ import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.common.ChangesListener
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.listener.SelectionVimListenerSuppressor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
@@ -33,15 +31,6 @@ import com.maddyhome.idea.vim.undo.UndoRedoBase
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
  init {
 | 
			
		||||
    fun onOldUndoChanged() {
 | 
			
		||||
      UndoManagerImpl.ourNeverAskUser = !injector.globalIjOptions().oldundo
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    injector.optionGroup.addGlobalOptionChangeListener(IjOptions.oldundo, ::onOldUndoChanged)
 | 
			
		||||
    onOldUndoChanged()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun undo(editor: VimEditor, context: ExecutionContext): Boolean {
 | 
			
		||||
    val ijContext = context.context as DataContext
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
 | 
			
		||||
@@ -55,18 +44,18 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      } else {
 | 
			
		||||
        performUntilFileChanges(editor, { undoManager.isUndoAvailable(fileEditor) }, { undoManager.undo(fileEditor) })
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.undo(fileEditor)
 | 
			
		||||
 | 
			
		||||
          // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
          if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
			
		||||
            undoManager.undo(fileEditor)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        editor.carets().forEach {
 | 
			
		||||
          val ijCaret = it.ij
 | 
			
		||||
          val hasSelection = ijCaret.hasSelection()
 | 
			
		||||
          if (hasSelection) {
 | 
			
		||||
            val selectionStart = ijCaret.selectionStart
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
              it.ij.removeSelection()
 | 
			
		||||
              it.ij.moveToOffset(selectionStart)
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
          removeSelections(editor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
@@ -77,6 +66,10 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun hasSelection(editor: VimEditor): Boolean {
 | 
			
		||||
    return editor.primaryCaret().ij.hasSelection()
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
  override fun redo(editor: VimEditor, context: ExecutionContext): Boolean {
 | 
			
		||||
    val ijContext = context.context as DataContext
 | 
			
		||||
    val project = PlatformDataKeys.PROJECT.getData(ijContext) ?: return false
 | 
			
		||||
@@ -87,21 +80,48 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      } else {
 | 
			
		||||
        performUntilFileChanges(editor, { undoManager.isRedoAvailable(fileEditor) }, { undoManager.redo(fileEditor) })
 | 
			
		||||
        undoManager.redo(fileEditor)
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
          editor.carets().forEach { it.ij.removeSelection() }
 | 
			
		||||
        }
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.redo(fileEditor)
 | 
			
		||||
 | 
			
		||||
          // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
          if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
 | 
			
		||||
            undoManager.redo(fileEditor)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
          removeSelections(editor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun performUntilFileChanges(editor: VimEditor?, check: () -> Boolean, action: Runnable) {
 | 
			
		||||
    if (editor == null) return
 | 
			
		||||
    val vimDocument = editor.document
 | 
			
		||||
  private fun removeSelections(editor: VimEditor) {
 | 
			
		||||
    editor.carets().forEach {
 | 
			
		||||
      val ijCaret = it.ij
 | 
			
		||||
      if (!ijCaret.hasSelection()) return@forEach
 | 
			
		||||
 | 
			
		||||
    val changeListener = object : ChangesListener {
 | 
			
		||||
      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) {
 | 
			
		||||
@@ -109,16 +129,12 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val oldPath = editor.getPath()
 | 
			
		||||
    vimDocument.addChangeListener(changeListener)
 | 
			
		||||
    while (check() && !changeListener.hasChanged && !ifFilePathChanged(editor, oldPath)) {
 | 
			
		||||
      action.run()
 | 
			
		||||
    }
 | 
			
		||||
    vimDocument.removeChangeListener(changeListener)
 | 
			
		||||
    init {
 | 
			
		||||
      editor.document.addChangeListener(changeListener)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
  private fun ifFilePathChanged(editor: VimEditor, oldPath: String?): Boolean {
 | 
			
		||||
    return editor.getPath() != oldPath
 | 
			
		||||
    val hasChanges: Boolean
 | 
			
		||||
      get() = changeListener.hasChanged || initialPath != editor.getPath()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun restoreVisualMode(editor: VimEditor) {
 | 
			
		||||
 
 | 
			
		||||
@@ -124,10 +124,6 @@ internal var Editor.vimMorePanel: ExOutputPanel? by userData()
 | 
			
		||||
internal var Editor.vimExOutput: ExOutputModel? by userData()
 | 
			
		||||
internal var Editor.vimTestInputModel: TestInputModel? by userData()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks whether a keeping visual mode visual operator action is performed on editor.
 | 
			
		||||
 */
 | 
			
		||||
internal var Editor.vimKeepingVisualOperatorAction: Boolean by userDataOr { false }
 | 
			
		||||
internal var Editor.vimChangeActionSwitchMode: Mode? by userData()
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2024 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.ide
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.extensions.ExtensionPointName
 | 
			
		||||
 | 
			
		||||
internal val clionEP = ExtensionPointName.create<ClionNovaProvider>("IdeaVIM.clionNovaProvider")
 | 
			
		||||
 | 
			
		||||
internal interface ClionNovaProvider {
 | 
			
		||||
  fun isClionNova(): Boolean
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class ClionNovaProviderImpl : ClionNovaProvider {
 | 
			
		||||
  override fun isClionNova(): Boolean = true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal fun isClionNova(): Boolean {
 | 
			
		||||
  return clionEP.extensions.any { it.isClionNova() }
 | 
			
		||||
}
 | 
			
		||||
@@ -40,7 +40,7 @@ internal object AppCodeTemplates {
 | 
			
		||||
    private var editor: Editor? = null
 | 
			
		||||
 | 
			
		||||
    override fun beforeActionPerformed(action: AnAction, event: AnActionEvent) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
 | 
			
		||||
      val hostEditor = event.dataContext.getData(CommonDataKeys.HOST_EDITOR)
 | 
			
		||||
      if (hostEditor != null) {
 | 
			
		||||
@@ -49,7 +49,7 @@ internal object AppCodeTemplates {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun afterActionPerformed(action: AnAction, event: AnActionEvent, result: AnActionResult) {
 | 
			
		||||
      if (!VimPlugin.isEnabled()) return
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
 | 
			
		||||
      if (ActionManager.getInstance().getId(action) == IdeActions.ACTION_CHOOSE_LOOKUP_ITEM) {
 | 
			
		||||
        val myEditor = editor
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user