mirror of
				https://github.com/chylex/IntelliJ-IdeaVim.git
				synced 2025-11-04 10:40:10 +01:00 
			
		
		
		
	Compare commits
	
		
			248 Commits
		
	
	
		
			customized
			...
			customized
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						
						
							
						
						30de641513
	
				 | 
					
					
						|||
| 
						
						
							
						
						d1b58c9d39
	
				 | 
					
					
						|||
| 
						
						
							
						
						45ed79d865
	
				 | 
					
					
						|||
| 
						
						
							
						
						527eb4cbb3
	
				 | 
					
					
						|||
| 
						
						
							
						
						e32ac125b2
	
				 | 
					
					
						|||
| 
						
						
							
						
						4d1d3b697c
	
				 | 
					
					
						|||
| 
						
						
							
						
						3246832528
	
				 | 
					
					
						|||
| 
						
						
							
						
						6505bfc9aa
	
				 | 
					
					
						|||
| 
						
						
							
						
						0c63890e9d
	
				 | 
					
					
						|||
| 
						
						
							
						
						259958e702
	
				 | 
					
					
						|||
| 
						
						
							
						
						4916545b53
	
				 | 
					
					
						|||
| 
						
						
							
						
						b8a9bddfa9
	
				 | 
					
					
						|||
| 
						
						
							
						
						95688b33c8
	
				 | 
					
					
						|||
| 
						
						
							
						
						07f44f1c93
	
				 | 
					
					
						|||
| 
						
						
							
						
						2ce6239ad6
	
				 | 
					
					
						|||
| 
						
						
							
						
						a0d2d64237
	
				 | 
					
					
						|||
| 
						
						
							
						
						2e4e8c058b
	
				 | 
					
					
						|||
| 
						
						
							
						
						f464d25844
	
				 | 
					
					
						|||
| 
						
						
							
						
						acc12c5b17
	
				 | 
					
					
						|||
| 
						
						
							
						
						0c1bbd5e92
	
				 | 
					
					
						|||
| 
						
						
							
						
						f330e220ad
	
				 | 
					
					
						|||
| 
						 | 
					b86ec03dc4 | ||
| 
						 | 
					ae75498f8a | ||
| 
						 | 
					9d0b68b0f8 | ||
| 
						 | 
					eeb5939e59 | ||
| 
						 | 
					ef235a47bf | ||
| 
						 | 
					b66da76880 | ||
| 
						 | 
					54d6119784 | ||
| 
						 | 
					0b8c081425 | ||
| 
						 | 
					209052ffa6 | ||
| 
						 | 
					fe9a6b5cfb | ||
| 
						 | 
					9c0f74369f | ||
| 
						 | 
					cd27e5229b | ||
| 
						 | 
					472732905c | ||
| 
						 | 
					485d9f81cd | ||
| 
						 | 
					8cf136ce4c | ||
| 
						 | 
					116a8ac9d2 | ||
| 
						 | 
					fda310bda6 | ||
| 
						 | 
					e55619ea33 | ||
| 
						 | 
					b952b20128 | ||
| 
						 | 
					62d1f85648 | ||
| 
						 | 
					5e3c8c0e92 | ||
| 
						 | 
					b58dddf2ff | ||
| 
						 | 
					78d351a0b0 | ||
| 
						 | 
					61dbc948cc | ||
| 
						 | 
					c4d92ebe73 | ||
| 
						 | 
					d0cf827638 | ||
| 
						 | 
					6a6a92b6b9 | ||
| 
						 | 
					9869b8a34e | ||
| 
						 | 
					60fbf88322 | ||
| 
						 | 
					fae3924062 | ||
| 
						 | 
					dc2ce64823 | ||
| 
						 | 
					d0d86d9178 | ||
| 
						 | 
					f417af6148 | ||
| 
						 | 
					2fe2860a09 | ||
| 
						 | 
					cb40426976 | ||
| 
						 | 
					423ed390a2 | ||
| 
						 | 
					7652b16ca6 | ||
| 
						 | 
					618a010c15 | ||
| 
						 | 
					d44a34ed9b | ||
| 
						 | 
					c84fc996db | ||
| 
						 | 
					43f232543b | ||
| 
						 | 
					3f65d1d99a | ||
| 
						 | 
					bfcf706ca7 | ||
| 
						 | 
					8c1103c461 | ||
| 
						 | 
					ab75ace8db | ||
| 
						 | 
					4a58e6a282 | ||
| 
						 | 
					ac9e4f69b4 | ||
| 
						 | 
					581edba7fd | ||
| 
						 | 
					58a8b96c3c | ||
| 
						 | 
					0e057ca9ae | ||
| 
						 | 
					36bf2639bb | ||
| 
						 | 
					0c1326e689 | ||
| 
						 | 
					dd74438f68 | ||
| 
						 | 
					a9ddfac782 | ||
| 
						 | 
					79437df894 | ||
| 
						 | 
					b5a04af089 | ||
| 
						 | 
					52372ae3d3 | ||
| 
						 | 
					65d755d9b2 | ||
| 
						 | 
					1f1a8f3395 | ||
| 
						 | 
					629e4e7053 | ||
| 
						 | 
					c50a299cfd | ||
| 
						 | 
					4bad129caf | ||
| 
						 | 
					1ffb28e21b | ||
| 
						 | 
					c126243367 | ||
| 
						 | 
					6da6e461a8 | ||
| 
						 | 
					103101bbcb | ||
| 
						 | 
					f737fcba1a | ||
| 
						 | 
					c5fa0678b8 | ||
| 
						 | 
					00ccddf8cf | ||
| 
						 | 
					00cbf188fb | ||
| 
						 | 
					988ea74461 | ||
| 
						 | 
					0914cda7e5 | ||
| 
						 | 
					5959e9aaa1 | ||
| 
						 | 
					434df565ae | ||
| 
						 | 
					c8f36504d8 | ||
| 
						 | 
					06e1af371e | ||
| 
						 | 
					d744987ac8 | ||
| 
						 | 
					b4eef17aaa | ||
| 
						 | 
					5c50e8607c | ||
| 
						 | 
					9a324ab448 | ||
| 
						 | 
					c3978335f5 | ||
| 
						 | 
					051296c2aa | ||
| 
						 | 
					90f2d2ff29 | ||
| 
						 | 
					4c2edab406 | ||
| 
						 | 
					76e8fd69bf | ||
| 
						 | 
					5dd458bcf7 | ||
| 
						 | 
					a94a8b8539 | ||
| 
						 | 
					261230b23a | ||
| 
						 | 
					b90317e00e | ||
| 
						 | 
					21c9dc8785 | ||
| 
						 | 
					31bbc60325 | ||
| 
						 | 
					fec6e5c189 | ||
| 
						 | 
					23c1493f17 | ||
| 
						 | 
					00808af569 | ||
| 
						 | 
					3c94091d30 | ||
| 
						 | 
					b737362aba | ||
| 
						 | 
					db722fc4e5 | ||
| 
						 | 
					7d679e68dc | ||
| 
						 | 
					bc808403fb | ||
| 
						 | 
					9d6dc317a4 | ||
| 
						 | 
					cf29c50f31 | ||
| 
						 | 
					2a3c4cc441 | ||
| 
						 | 
					bd192561ae | ||
| 
						 | 
					66ff56a05e | ||
| 
						 | 
					def86d179e | ||
| 
						 | 
					3c9a343f8b | ||
| 
						 | 
					10b6b05fab | ||
| 
						 | 
					caa4ef736a | ||
| 
						 | 
					23702345a9 | ||
| 
						 | 
					ba89babd10 | ||
| 
						 | 
					2ce3fbd677 | ||
| 
						 | 
					d8de73a06d | ||
| 
						 | 
					8094e6711a | ||
| 
						 | 
					10edccc1d6 | ||
| 
						 | 
					247aaed188 | ||
| 
						 | 
					1a4333fa1b | ||
| 
						 | 
					8eaa6df318 | ||
| 
						 | 
					7523db186f | ||
| 
						 | 
					4aac113522 | ||
| 
						 | 
					795abd77a7 | ||
| 
						 | 
					38bc914504 | ||
| 
						 | 
					c8113eea83 | ||
| 
						 | 
					924b7418e8 | ||
| 
						 | 
					a7dfef61e9 | ||
| 
						 | 
					db35c979b4 | ||
| 
						 | 
					2de933c723 | ||
| 
						 | 
					d3704d602f | ||
| 
						 | 
					ea62f227bf | ||
| 
						 | 
					23fdadc32e | ||
| 
						 | 
					e9bf06686f | ||
| 
						 | 
					7842b155c1 | ||
| 
						 | 
					74a8277e10 | ||
| 
						 | 
					ddb1b80463 | ||
| 
						 | 
					eea3336934 | ||
| 
						 | 
					f801145712 | ||
| 
						 | 
					e033b08535 | ||
| 
						 | 
					1d9514a205 | ||
| 
						 | 
					6741120f19 | ||
| 
						 | 
					c501457322 | ||
| 
						 | 
					46425a24c3 | ||
| 
						 | 
					9826f0a7f0 | ||
| 
						 | 
					43175061e0 | ||
| 
						 | 
					0ab32cac34 | ||
| 
						 | 
					e3ec9c614b | ||
| 
						 | 
					f454d60234 | ||
| 
						 | 
					19fa00837c | ||
| 
						 | 
					275c5d28e1 | ||
| 
						 | 
					15ae069f6f | ||
| 
						 | 
					00f5541dc6 | ||
| 
						 | 
					02540eb303 | ||
| 
						 | 
					282e581bdb | ||
| 
						 | 
					31e7c49608 | ||
| 
						 | 
					7966a6dc91 | ||
| 
						 | 
					5fc2f04224 | ||
| 
						 | 
					6edfd8ed22 | ||
| 
						 | 
					363db05db7 | ||
| 
						 | 
					3738012dd6 | ||
| 
						 | 
					355cfe035d | ||
| 
						 | 
					6d01b5be77 | ||
| 
						 | 
					4938957483 | ||
| 
						 | 
					46f4fa7cdd | ||
| 
						 | 
					f696135f31 | ||
| 
						 | 
					52e0fcdc7d | ||
| 
						 | 
					ac17518a23 | ||
| 6dd924b2b2 | |||
| 
						 | 
					f439474b73 | ||
| 
						 | 
					d6cd92e256 | ||
| 
						 | 
					3a294268d9 | ||
| 
						 | 
					9b81c7e650 | ||
| 
						 | 
					e229fb3ad7 | ||
| 
						 | 
					720eae63fa | ||
| 
						 | 
					0df96a24bd | ||
| 
						 | 
					21a1588ede | ||
| 
						 | 
					7970006e8c | ||
| 
						 | 
					418d0cff7f | ||
| 
						 | 
					7284360774 | ||
| 
						 | 
					9fc3fadee8 | ||
| 
						 | 
					3d2db56f63 | ||
| 
						 | 
					e9c7cb8670 | ||
| 
						 | 
					87d19274c5 | ||
| 
						 | 
					3161bf8ffd | ||
| 
						 | 
					b68865587e | ||
| 
						 | 
					7dc0dbe944 | ||
| 
						 | 
					f50a363525 | ||
| 
						 | 
					57ad4c70d1 | ||
| 
						 | 
					d3d93b898f | ||
| 
						 | 
					7d8973edb2 | ||
| 
						 | 
					2302b576b0 | ||
| f4782630d4 | |||
| 
						 | 
					8c1a2a686f | ||
| 32d5e1e6fa | |||
| 
						 | 
					a381a1cacc | ||
| 
						 | 
					73c3c9f7fe | ||
| 
						 | 
					67ef0a75d5 | ||
| 
						 | 
					328bc5e95a | ||
| 
						 | 
					7f8021e37e | ||
| 
						 | 
					9701b7e79b | ||
| 
						 | 
					7a52c6fec9 | ||
| 
						 | 
					1503639d4b | ||
| 
						 | 
					e82f19c852 | ||
| 
						 | 
					edd69c9c25 | ||
| 
						 | 
					fc61e369fb | ||
| 
						 | 
					113586b59b | ||
| 
						 | 
					5dbd5e1c89 | ||
| 
						 | 
					04b7d9e2c3 | ||
| 
						 | 
					5f2743176a | ||
| 
						 | 
					3723488617 | ||
| 
						 | 
					0cc17a0791 | ||
| 
						 | 
					05a21e6091 | ||
| 
						 | 
					fc06bc7c6f | ||
| 
						 | 
					1bd005adc1 | ||
| 
						 | 
					4f208d1577 | ||
| 
						 | 
					eb6e0557a7 | ||
| 
						 | 
					cf09d66be6 | ||
| 
						 | 
					76cd127a8a | ||
| 
						 | 
					f6dd2a9968 | ||
| 
						 | 
					ae05a33e14 | ||
| 
						 | 
					b38fad323b | ||
| 
						 | 
					c6027fcf0f | ||
| 
						 | 
					f4cf06a50e | ||
| 
						 | 
					86bf8dcc60 | ||
| 
						 | 
					d37898b6d3 | ||
| 
						 | 
					1edd6a9002 | ||
| 
						 | 
					f7fa0dcbd1 | ||
| 
						 | 
					4f0a95a803 | ||
| 
						 | 
					e443cb0d3c | ||
| 
						 | 
					6fa228ee08 | 
							
								
								
									
										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 && github.repository == 'JetBrains/ideavim'
 | 
			
		||||
    if: false
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										12
									
								
								.github/workflows/runUiOctopusTests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										12
									
								
								.github/workflows/runUiOctopusTests.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,20 +9,13 @@ jobs:
 | 
			
		||||
    runs-on: macos-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - name: Apply Patch
 | 
			
		||||
        run: |
 | 
			
		||||
          git apply tests/ui-ij-tests/src/test/kotlin/ui/octopus.patch
 | 
			
		||||
      - name: Setup Java
 | 
			
		||||
        uses: actions/setup-java@v4
 | 
			
		||||
        with:
 | 
			
		||||
          distribution: zulu
 | 
			
		||||
          java-version: 17
 | 
			
		||||
      - name: Setup FFmpeg
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v3
 | 
			
		||||
        with:
 | 
			
		||||
          # Not strictly necessary, but it may prevent rate limit
 | 
			
		||||
          # errors especially on GitHub-hosted macos machines.
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        run: brew install ffmpeg
 | 
			
		||||
      - name: Setup Gradle
 | 
			
		||||
        uses: gradle/gradle-build-action@v2.4.2
 | 
			
		||||
      - name: Build Plugin
 | 
			
		||||
@@ -30,7 +23,7 @@ jobs:
 | 
			
		||||
      - name: Run Idea
 | 
			
		||||
        run: |
 | 
			
		||||
          mkdir -p build/reports
 | 
			
		||||
          gradle runIdeForUiTests > build/reports/idea.log &
 | 
			
		||||
          gradle runIdeForUiTests -Doctopus.handler=false > build/reports/idea.log &
 | 
			
		||||
      - name: Wait for Idea started
 | 
			
		||||
        uses: jtalk/url-health-check-action@v3
 | 
			
		||||
        with:
 | 
			
		||||
@@ -52,6 +45,7 @@ jobs:
 | 
			
		||||
          name: ui-test-fails-report-mac
 | 
			
		||||
          path: |
 | 
			
		||||
            build/reports
 | 
			
		||||
            tests/ui-ij-tests/build/reports
 | 
			
		||||
            sandbox-idea-log
 | 
			
		||||
#  build-for-ui-test-linux:
 | 
			
		||||
#    runs-on: ubuntu-latest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								.github/workflows/runUiPyTests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/runUiPyTests.yml
									
									
									
									
										vendored
									
									
								
							@@ -18,11 +18,7 @@ jobs:
 | 
			
		||||
        with:
 | 
			
		||||
          python-version: '3.10'
 | 
			
		||||
      - name: Setup FFmpeg
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v3
 | 
			
		||||
        with:
 | 
			
		||||
          # Not strictly necessary, but it may prevent rate limit
 | 
			
		||||
          # errors especially on GitHub-hosted macos machines.
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        run: brew install ffmpeg
 | 
			
		||||
      - name: Setup Gradle
 | 
			
		||||
        uses: gradle/gradle-build-action@v2.4.2
 | 
			
		||||
      - name: Build Plugin
 | 
			
		||||
@@ -52,4 +48,5 @@ jobs:
 | 
			
		||||
          name: ui-test-fails-report-mac
 | 
			
		||||
          path: |
 | 
			
		||||
            build/reports
 | 
			
		||||
            tests/ui-py-tests/build/reports
 | 
			
		||||
            sandbox-idea-log
 | 
			
		||||
							
								
								
									
										7
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/runUiTests.yml
									
									
									
									
										vendored
									
									
								
							@@ -15,11 +15,7 @@ jobs:
 | 
			
		||||
          distribution: zulu
 | 
			
		||||
          java-version: 17
 | 
			
		||||
      - name: Setup FFmpeg
 | 
			
		||||
        uses: FedericoCarboni/setup-ffmpeg@v3
 | 
			
		||||
        with:
 | 
			
		||||
          # Not strictly necessary, but it may prevent rate limit
 | 
			
		||||
          # errors especially on GitHub-hosted macos machines.
 | 
			
		||||
          github-token: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
        run: brew install ffmpeg
 | 
			
		||||
      - name: Setup Gradle
 | 
			
		||||
        uses: gradle/gradle-build-action@v2.4.2
 | 
			
		||||
      - name: Build Plugin
 | 
			
		||||
@@ -49,6 +45,7 @@ jobs:
 | 
			
		||||
          name: ui-test-fails-report-mac
 | 
			
		||||
          path: |
 | 
			
		||||
            build/reports
 | 
			
		||||
            tests/ui-ij-tests/build/reports
 | 
			
		||||
            sandbox-idea-log
 | 
			
		||||
#  build-for-ui-test-linux:
 | 
			
		||||
#    runs-on: ubuntu-latest
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										34
									
								
								.github/workflows/updateAffectedRate.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										34
									
								
								.github/workflows/updateAffectedRate.yml
									
									
									
									
										vendored
									
									
								
							@@ -1,34 +0,0 @@
 | 
			
		||||
# This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
 | 
			
		||||
# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
 | 
			
		||||
 | 
			
		||||
# This workflow syncs changes from the docs folder of IdeaVim to the IdeaVim.wiki repository
 | 
			
		||||
 | 
			
		||||
name: Update Affected Rate field on YouTrack
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '0 8 * * *'
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Fetch origin repo
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
      - name: Set up JDK 17
 | 
			
		||||
        uses: actions/setup-java@v2
 | 
			
		||||
        with:
 | 
			
		||||
          java-version: '17'
 | 
			
		||||
          distribution: 'adopt'
 | 
			
		||||
          server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
 | 
			
		||||
          settings-path: ${{ github.workspace }} # location for the settings.xml file
 | 
			
		||||
 | 
			
		||||
      - name: Update YouTrack
 | 
			
		||||
        run: ./gradlew scripts:updateAffectedRates
 | 
			
		||||
        env:
 | 
			
		||||
          YOUTRACK_TOKEN: ${{ secrets.YOUTRACK_TOKEN }}
 | 
			
		||||
							
								
								
									
										5
									
								
								.github/workflows/updateChangelog.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/updateChangelog.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,15 +7,12 @@ on:
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
  schedule:
 | 
			
		||||
    - cron: '0 10 * * *'
 | 
			
		||||
# Workflow run on push is disabled to avoid conflicts when merging PR
 | 
			
		||||
#  push:
 | 
			
		||||
#    branches: [ master ]
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  build:
 | 
			
		||||
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    if: github.repository == 'JetBrains/ideavim'
 | 
			
		||||
    if: false
 | 
			
		||||
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.teamcity/_Self/Project.kt
									
									
									
									
										vendored
									
									
								
							@@ -25,6 +25,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("2024.1", "<default>"))
 | 
			
		||||
  buildType(TestingBuildType("Latest EAP With Xorg", "<default>", version = "LATEST-EAP-SNAPSHOT"))
 | 
			
		||||
 | 
			
		||||
  buildType(PropertyBased)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										3
									
								
								.teamcity/_Self/buildTypes/Qodana.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										3
									
								
								.teamcity/_Self/buildTypes/Qodana.kt
									
									
									
									
										vendored
									
									
								
							@@ -46,8 +46,8 @@ object Qodana : IdeaVimBuildType({
 | 
			
		||||
        version = Qodana.JVMVersion.LATEST
 | 
			
		||||
      }
 | 
			
		||||
      reportAsTests = true
 | 
			
		||||
      additionalDockerArguments = "-e QODANA_TOKEN=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJvcmdhbml6YXRpb24iOiIzUFZrQSIsInByb2plY3QiOiIzN1FlQSIsInRva2VuIjoiM0t2bXoifQ.uohp81tM7iAfvvB6k8faarfpV-OjusAaEbWQ8iNrOgs"
 | 
			
		||||
      additionalQodanaArguments = "--baseline qodana.sarif.json"
 | 
			
		||||
      cloudToken = "credentialsJSON:6b79412e-9198-4862-9223-c5019488f903"
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -63,7 +63,6 @@ object Qodana : IdeaVimBuildType({
 | 
			
		||||
        timezone = "SERVER"
 | 
			
		||||
      }
 | 
			
		||||
      param("dayOfWeek", "Sunday")
 | 
			
		||||
      enabled = false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								.teamcity/_Self/buildTypes/ReleasePlugin.kt
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										55
									
								
								.teamcity/_Self/buildTypes/ReleasePlugin.kt
									
									
									
									
										vendored
									
									
								
							@@ -97,14 +97,14 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
 | 
			
		||||
      name = "Set TeamCity build number"
 | 
			
		||||
      tasks = "scripts:setTeamCityBuildNumber"
 | 
			
		||||
    }
 | 
			
		||||
    gradle {
 | 
			
		||||
      name = "Update change log"
 | 
			
		||||
      tasks = "scripts:changelogUpdateUnreleased"
 | 
			
		||||
    }
 | 
			
		||||
    gradle {
 | 
			
		||||
      name = "Commit preparation changes"
 | 
			
		||||
      tasks = "scripts:commitChanges"
 | 
			
		||||
    }
 | 
			
		||||
//    gradle {
 | 
			
		||||
//      name = "Update change log"
 | 
			
		||||
//      tasks = "scripts:changelogUpdateUnreleased"
 | 
			
		||||
//    }
 | 
			
		||||
//    gradle {
 | 
			
		||||
//      name = "Commit preparation changes"
 | 
			
		||||
//      tasks = "scripts:commitChanges"
 | 
			
		||||
//    }
 | 
			
		||||
    gradle {
 | 
			
		||||
      name = "Add release tag"
 | 
			
		||||
      tasks = "scripts:addReleaseTag"
 | 
			
		||||
@@ -117,33 +117,24 @@ sealed class ReleasePlugin(private val releaseType: String) : IdeaVimBuildType({
 | 
			
		||||
      name = "Publish release"
 | 
			
		||||
      tasks = "publishPlugin"
 | 
			
		||||
    }
 | 
			
		||||
    script {
 | 
			
		||||
      name = "Checkout master branch"
 | 
			
		||||
      scriptContent = """
 | 
			
		||||
        echo Checkout master
 | 
			
		||||
        git checkout master
 | 
			
		||||
      """.trimIndent()
 | 
			
		||||
    }
 | 
			
		||||
    gradle {
 | 
			
		||||
      name = "Update change log in master"
 | 
			
		||||
      tasks = "scripts:changelogUpdateUnreleased"
 | 
			
		||||
    }
 | 
			
		||||
    gradle {
 | 
			
		||||
      name = "Commit preparation changes in master"
 | 
			
		||||
      tasks = "scripts:commitChanges"
 | 
			
		||||
    }
 | 
			
		||||
//    script {
 | 
			
		||||
//      name = "Checkout master branch"
 | 
			
		||||
//      scriptContent = """
 | 
			
		||||
//        echo Checkout master
 | 
			
		||||
//        git checkout master
 | 
			
		||||
//      """.trimIndent()
 | 
			
		||||
//    }
 | 
			
		||||
//    gradle {
 | 
			
		||||
//      name = "Update change log in master"
 | 
			
		||||
//      tasks = "scripts:changelogUpdateUnreleased"
 | 
			
		||||
//    }
 | 
			
		||||
//    gradle {
 | 
			
		||||
//      name = "Commit preparation changes in master"
 | 
			
		||||
//      tasks = "scripts:commitChanges"
 | 
			
		||||
//    }
 | 
			
		||||
    script {
 | 
			
		||||
      name = "Push changes to the repo"
 | 
			
		||||
      scriptContent = """
 | 
			
		||||
      branch=$(git branch --show-current)  
 | 
			
		||||
      echo Current branch is ${'$'}branch
 | 
			
		||||
      if [ "master" != "${'$'}branch" ];
 | 
			
		||||
      then
 | 
			
		||||
        git checkout master
 | 
			
		||||
      fi
 | 
			
		||||
      
 | 
			
		||||
      git push origin
 | 
			
		||||
      
 | 
			
		||||
      git checkout release
 | 
			
		||||
      echo checkout release branch
 | 
			
		||||
      git branch --set-upstream-to=origin/release release
 | 
			
		||||
 
 | 
			
		||||
@@ -1,11 +1,9 @@
 | 
			
		||||
package patches.buildTypes
 | 
			
		||||
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.RelativeId
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.*
 | 
			
		||||
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
 | 
			
		||||
import jetbrains.buildServer.configs.kotlin.v2019_2.ui.*
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
This patch script was generated by TeamCity on settings change in UI.
 | 
			
		||||
@@ -13,6 +11,18 @@ To apply the patch, change the buildType with id = 'IdeaVimTests_Latest_EAP'
 | 
			
		||||
accordingly, and delete the patch script.
 | 
			
		||||
*/
 | 
			
		||||
changeBuildType(RelativeId("IdeaVimTests_Latest_EAP")) {
 | 
			
		||||
    check(artifactRules == """
 | 
			
		||||
        +:build/reports => build/reports
 | 
			
		||||
        +:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
 | 
			
		||||
    """.trimIndent()) {
 | 
			
		||||
        "Unexpected option value: artifactRules = $artifactRules"
 | 
			
		||||
    }
 | 
			
		||||
    artifactRules = """
 | 
			
		||||
        +:build/reports => build/reports
 | 
			
		||||
        +:/mnt/agent/temp/buildTmp/ => /mnt/agent/temp/buildTmp/
 | 
			
		||||
        +:tests/java-tests/build/reports => tests/java-tests/build/reports
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
 | 
			
		||||
    expectSteps {
 | 
			
		||||
        gradle {
 | 
			
		||||
            tasks = "clean test"
 | 
			
		||||
 
 | 
			
		||||
@@ -495,6 +495,14 @@ Contributors:
 | 
			
		||||
  [![icon][github]](https://github.com/emanuelgestosa)
 | 
			
		||||
   
 | 
			
		||||
  Emanuel Gestosa
 | 
			
		||||
* [![icon][mail]](mailto:81118900+lippfi@users.noreply.github.com)
 | 
			
		||||
  [![icon][github]](https://github.com/lippfi)
 | 
			
		||||
   
 | 
			
		||||
  lippfi, 
 | 
			
		||||
* [![icon][mail]](mailto:fillipser143@gmail.com)
 | 
			
		||||
  [![icon][github]](https://github.com/Parker7123)
 | 
			
		||||
   
 | 
			
		||||
  FilipParker
 | 
			
		||||
 | 
			
		||||
Previous contributors:
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								CHANGES.md
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								CHANGES.md
									
									
									
									
									
								
							@@ -23,13 +23,19 @@ It is important to distinguish EAP from traditional pre-release software.
 | 
			
		||||
Please note that the quality of EAP versions may at times be way below even
 | 
			
		||||
usual beta standards.
 | 
			
		||||
 | 
			
		||||
## To Be Released
 | 
			
		||||
## End of changelog file maintenance
 | 
			
		||||
 | 
			
		||||
Since version 2.9.0, the changelog can be found on YouTrack
 | 
			
		||||
 | 
			
		||||
To Be Released: https://youtrack.jetbrains.com/issues/VIM?q=%23%7BReady%20To%20Release%7D%20
 | 
			
		||||
Latest Fixes: https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20sort%20by:%20updated%20
 | 
			
		||||
 | 
			
		||||
## 2.9.0, 2024-02-20
 | 
			
		||||
 | 
			
		||||
### Fixes:
 | 
			
		||||
* [VIM-3055](https://youtrack.jetbrains.com/issue/VIM-3055) Fix the issue with double deleting after dot
 | 
			
		||||
 | 
			
		||||
### Merged PRs:
 | 
			
		||||
* [725](https://github.com/JetBrains/ideavim/pull/725) by [Emanuel Gestosa](https://github.com/emanuelgestosa): Regex
 | 
			
		||||
* [805](https://github.com/JetBrains/ideavim/pull/805) by [chylex](https://github.com/chylex): VIM-3238 Fix recording a macro that replays another macro
 | 
			
		||||
 | 
			
		||||
## 2.8.0, 2024-01-30
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.22-1.0.17")
 | 
			
		||||
  compileOnly("com.google.devtools.ksp:symbol-processing-api:1.9.23-1.0.20")
 | 
			
		||||
  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")
 | 
			
		||||
 
 | 
			
		||||
@@ -32,7 +32,6 @@ import org.eclipse.jgit.api.Git
 | 
			
		||||
import org.eclipse.jgit.lib.RepositoryBuilder
 | 
			
		||||
import org.intellij.markdown.ast.getTextInNode
 | 
			
		||||
import org.jetbrains.changelog.Changelog
 | 
			
		||||
import org.jetbrains.changelog.exceptions.MissingVersionException
 | 
			
		||||
import org.kohsuke.github.GHUser
 | 
			
		||||
import java.net.HttpURLConnection
 | 
			
		||||
import java.net.URL
 | 
			
		||||
@@ -49,14 +48,14 @@ buildscript {
 | 
			
		||||
    classpath("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
			
		||||
 | 
			
		||||
    // This is needed for jgit to connect to ssh
 | 
			
		||||
    classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
    classpath("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
 | 
			
		||||
    classpath("org.kohsuke:github-api:1.305")
 | 
			
		||||
 | 
			
		||||
    classpath("io.ktor:ktor-client-core:2.3.7")
 | 
			
		||||
    classpath("io.ktor:ktor-client-cio:2.3.7")
 | 
			
		||||
    classpath("io.ktor:ktor-client-auth:2.3.7")
 | 
			
		||||
    classpath("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
			
		||||
    classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
			
		||||
    classpath("io.ktor:ktor-client-core:2.3.10")
 | 
			
		||||
    classpath("io.ktor:ktor-client-cio:2.3.10")
 | 
			
		||||
    classpath("io.ktor:ktor-client-auth:2.3.10")
 | 
			
		||||
    classpath("io.ktor:ktor-client-content-negotiation:2.3.10")
 | 
			
		||||
    classpath("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
 | 
			
		||||
 | 
			
		||||
    // This comes from the changelog plugin
 | 
			
		||||
//        classpath("org.jetbrains:markdown:0.3.1")
 | 
			
		||||
@@ -70,11 +69,11 @@ plugins {
 | 
			
		||||
  application
 | 
			
		||||
  id("java-test-fixtures")
 | 
			
		||||
 | 
			
		||||
  id("org.jetbrains.intellij") version "1.17.1"
 | 
			
		||||
  id("org.jetbrains.intellij") version "1.17.3"
 | 
			
		||||
  id("org.jetbrains.changelog") version "2.2.0"
 | 
			
		||||
 | 
			
		||||
  id("org.jetbrains.kotlinx.kover") version "0.6.1"
 | 
			
		||||
  id("com.dorongold.task-tree") version "2.1.1"
 | 
			
		||||
  id("com.dorongold.task-tree") version "3.0.0"
 | 
			
		||||
 | 
			
		||||
  id("com.google.devtools.ksp") version "1.9.22-1.0.17"
 | 
			
		||||
}
 | 
			
		||||
@@ -142,14 +141,14 @@ dependencies {
 | 
			
		||||
  testFixturesImplementation("org.jetbrains.kotlin:kotlin-test:$kotlinVersion")
 | 
			
		||||
 | 
			
		||||
  // https://mvnrepository.com/artifact/org.mockito.kotlin/mockito-kotlin
 | 
			
		||||
  testImplementation("org.mockito.kotlin:mockito-kotlin:5.2.1")
 | 
			
		||||
  testImplementation("org.mockito.kotlin:mockito-kotlin:5.3.1")
 | 
			
		||||
 | 
			
		||||
  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")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.1")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.1")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.1")
 | 
			
		||||
  testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
 | 
			
		||||
  testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
 | 
			
		||||
  testImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.2")
 | 
			
		||||
  testFixturesImplementation("org.junit.jupiter:junit-jupiter-params:5.10.2")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
configurations {
 | 
			
		||||
@@ -207,6 +206,11 @@ tasks {
 | 
			
		||||
    systemProperty("jb.privacy.policy.text", "<!--999.999-->")
 | 
			
		||||
    systemProperty("jb.consents.confirmation.enabled", "false")
 | 
			
		||||
    systemProperty("ide.show.tips.on.startup.default.value", "false")
 | 
			
		||||
    systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  runIde {
 | 
			
		||||
    systemProperty("octopus.handler", System.getProperty("octopus.handler") ?: true)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -260,7 +264,6 @@ tasks {
 | 
			
		||||
  runPluginVerifier {
 | 
			
		||||
    downloadDir.set("${project.buildDir}/pluginVerifier/ides")
 | 
			
		||||
    teamCityOutputFormat.set(true)
 | 
			
		||||
//        ideVersions.set(listOf("IC-2021.3.4"))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  generateGrammarSource {
 | 
			
		||||
@@ -305,26 +308,14 @@ tasks {
 | 
			
		||||
    from(createOpenApiSourceJar) { into("lib/src") }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    val pluginVersion = version
 | 
			
		||||
  patchPluginXml {
 | 
			
		||||
    // Don't forget to update plugin.xml
 | 
			
		||||
    patchPluginXml {
 | 
			
		||||
        // Get the latest available change notes from the changelog file
 | 
			
		||||
        changeNotes.set(
 | 
			
		||||
            provider {
 | 
			
		||||
                with(changelog) {
 | 
			
		||||
                    val log = try {
 | 
			
		||||
                        getUnreleased()
 | 
			
		||||
                    } catch (e: MissingVersionException) {
 | 
			
		||||
                        getOrNull(pluginVersion.toString()) ?: getLatest()
 | 
			
		||||
                    }
 | 
			
		||||
                    renderItem(
 | 
			
		||||
                        log,
 | 
			
		||||
                        org.jetbrains.changelog.Changelog.OutputType.HTML,
 | 
			
		||||
                    )
 | 
			
		||||
                }
 | 
			
		||||
            },
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
    sinceBuild.set("233.11799.67")
 | 
			
		||||
 | 
			
		||||
    changeNotes.set(
 | 
			
		||||
      """<a href="https://youtrack.jetbrains.com/issues/VIM?q=State:%20Fixed%20Fix%20versions:%20${version.get()}">Changelog</a>"""
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// --- Tests
 | 
			
		||||
@@ -429,12 +420,14 @@ val prId: String by project
 | 
			
		||||
 | 
			
		||||
tasks.register("updateMergedPr") {
 | 
			
		||||
  doLast {
 | 
			
		||||
    if (project.hasProperty("prId")) {
 | 
			
		||||
      println("Got pr id: $prId")
 | 
			
		||||
      updateMergedPr(prId.toInt())
 | 
			
		||||
    } else {
 | 
			
		||||
      error("Cannot get prId")
 | 
			
		||||
    }
 | 
			
		||||
    val x = changelog.getUnreleased()
 | 
			
		||||
    println("x")
 | 
			
		||||
//    if (project.hasProperty("prId")) {
 | 
			
		||||
//      println("Got pr id: $prId")
 | 
			
		||||
//      updateMergedPr(prId.toInt())
 | 
			
		||||
//    } else {
 | 
			
		||||
//      error("Cannot get prId")
 | 
			
		||||
//    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -458,7 +451,7 @@ val fixVersionsElementType = "VersionBundleElement"
 | 
			
		||||
tasks.register("releaseActions") {
 | 
			
		||||
  group = "other"
 | 
			
		||||
  doLast {
 | 
			
		||||
    val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D")
 | 
			
		||||
    val tickets = getYoutrackTicketsByQuery("%23%7BReady+To+Release%7D%20and%20tag:%20%7BIdeaVim%20Released%20In%20EAP%7D%20")
 | 
			
		||||
    if (tickets.isNotEmpty()) {
 | 
			
		||||
      println("Updating statuses for tickets: $tickets")
 | 
			
		||||
      setYoutrackStatus(tickets, "Fixed")
 | 
			
		||||
 
 | 
			
		||||
@@ -8,12 +8,13 @@
 | 
			
		||||
 | 
			
		||||
# suppress inspection "UnusedProperty" for whole file
 | 
			
		||||
 | 
			
		||||
ideaVersion=2023.3.3
 | 
			
		||||
#ideaVersion=LATEST-EAP-SNAPSHOT
 | 
			
		||||
ideaVersion=2024.1
 | 
			
		||||
# Values for type: https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#intellij-extension-type
 | 
			
		||||
ideaType=IC
 | 
			
		||||
downloadIdeaSources=true
 | 
			
		||||
instrumentPluginCode=true
 | 
			
		||||
version=chylex-27
 | 
			
		||||
version=chylex-33
 | 
			
		||||
javaVersion=17
 | 
			
		||||
remoteRobotVersion=0.11.22
 | 
			
		||||
antlrVersion=4.10.1
 | 
			
		||||
@@ -28,7 +29,7 @@ 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
 | 
			
		||||
kotlinxSerializationVersion=1.6.2
 | 
			
		||||
 | 
			
		||||
slackUrl=
 | 
			
		||||
youtrackToken=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										99092
									
								
								qodana.sarif.json
									
									
									
									
									
								
							
							
						
						
									
										99092
									
								
								qodana.sarif.json
									
									
									
									
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -21,6 +21,9 @@ exclude:
 | 
			
		||||
      - src/test/java/org/jetbrains/plugins/ideavim/propertybased/samples/SimpleText.kt
 | 
			
		||||
      - src/main/java/com/maddyhome/idea/vim/vimscript/parser/generated
 | 
			
		||||
      - src/main/java/com/maddyhome/idea/vim/package-info.java
 | 
			
		||||
      - vim-engine/src/main/java/com/maddyhome/idea/vim/regexp/parser/generated
 | 
			
		||||
      - src/main/java/com/maddyhome/idea/vim/group/SearchGroup.java
 | 
			
		||||
      - tests/ui-fixtures
 | 
			
		||||
dependencyIgnores:
 | 
			
		||||
  - name: "acejump"
 | 
			
		||||
  - name: "icu4j"
 | 
			
		||||
 
 | 
			
		||||
@@ -20,17 +20,17 @@ repositories {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
dependencies {
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
 | 
			
		||||
  compileOnly("org.jetbrains.kotlin:kotlin-stdlib:1.9.23")
 | 
			
		||||
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-cio:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.7")
 | 
			
		||||
  implementation("io.ktor:ktor-client-core:2.3.10")
 | 
			
		||||
  implementation("io.ktor:ktor-client-cio:2.3.10")
 | 
			
		||||
  implementation("io.ktor:ktor-client-content-negotiation:2.3.10")
 | 
			
		||||
  implementation("io.ktor:ktor-serialization-kotlinx-json:2.3.10")
 | 
			
		||||
  implementation("io.ktor:ktor-client-auth:2.3.10")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit:6.6.0.202305301015-r")
 | 
			
		||||
 | 
			
		||||
  // This is needed for jgit to connect to ssh
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.8.0.202311291450-r")
 | 
			
		||||
  implementation("org.eclipse.jgit:org.eclipse.jgit.ssh.apache:6.9.0.202403050737-r")
 | 
			
		||||
  implementation("com.vdurmont:semver4j:3.1.0")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -58,13 +58,6 @@ tasks.register("checkNewPluginDependencies", JavaExec::class) {
 | 
			
		||||
  classpath = sourceSets["main"].runtimeClasspath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("updateAffectedRates", JavaExec::class) {
 | 
			
		||||
  group = "verification"
 | 
			
		||||
  description = "This job updates Affected Rate field on YouTrack"
 | 
			
		||||
  mainClass.set("scripts.YouTrackUsersAffectedKt")
 | 
			
		||||
  classpath = sourceSets["main"].runtimeClasspath
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
tasks.register("calculateNewVersion", JavaExec::class) {
 | 
			
		||||
  group = "release"
 | 
			
		||||
  mainClass.set("scripts.release.CalculateNewVersionKt")
 | 
			
		||||
 
 | 
			
		||||
@@ -22,7 +22,7 @@ import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Suppress("SpellCheckingInspection")
 | 
			
		||||
val knownPlugins = listOf(
 | 
			
		||||
val knownPlugins = setOf(
 | 
			
		||||
  "IdeaVimExtension",
 | 
			
		||||
  "github.zgqq.intellij-enhance",
 | 
			
		||||
  "org.jetbrains.IdeaVim-EasyMotion",
 | 
			
		||||
@@ -31,7 +31,12 @@ val knownPlugins = listOf(
 | 
			
		||||
  "com.github.copilot",
 | 
			
		||||
  "com.github.dankinsoid.multicursor",
 | 
			
		||||
  "com.joshestein.ideavim-quickscope",
 | 
			
		||||
 | 
			
		||||
  "ca.alexgirard.HarpoonIJ",
 | 
			
		||||
  "me.kyren223.harpoonforjb", // https://plugins.jetbrains.com/plugin/23771-harpoonforjb
 | 
			
		||||
  "com.github.erotourtes.harpoon", // https://plugins.jetbrains.com/plugin/21796-harpooner
 | 
			
		||||
  "me.kyren223.trident", // https://plugins.jetbrains.com/plugin/23818-trident
 | 
			
		||||
 | 
			
		||||
  "com.protoseo.input-source-auto-converter",
 | 
			
		||||
 | 
			
		||||
//   "cc.implicated.intellij.plugins.bunny", // I don't want to include this plugin in the list of IdeaVim plugins as I don't understand what this is for
 | 
			
		||||
@@ -42,19 +47,15 @@ suspend fun main() {
 | 
			
		||||
    parameter("dependency", "IdeaVIM")
 | 
			
		||||
    parameter("includeOptional", true)
 | 
			
		||||
  }
 | 
			
		||||
  val output = response.body<List<String>>()
 | 
			
		||||
  val output = response.body<List<String>>().toSet()
 | 
			
		||||
  println(output)
 | 
			
		||||
  if (knownPlugins != output) {
 | 
			
		||||
    val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
 | 
			
		||||
    val removedPlugins = (knownPlugins - output.toSet()).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
 | 
			
		||||
  val newPlugins = (output - knownPlugins).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
 | 
			
		||||
  if (newPlugins.isNotEmpty()) {
 | 
			
		||||
//    val removedPlugins = (knownPlugins - output.toSet()).map { it to (getPluginLinkByXmlId(it) ?: "Can't find plugin link") }
 | 
			
		||||
    error(
 | 
			
		||||
      """
 | 
			
		||||
        
 | 
			
		||||
      Unregistered plugins:
 | 
			
		||||
      ${if (newPlugins.isNotEmpty()) newPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" } else "No unregistered plugins"}
 | 
			
		||||
      
 | 
			
		||||
      Removed plugins:
 | 
			
		||||
      ${if (removedPlugins.isNotEmpty()) removedPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" } else "No removed plugins"}
 | 
			
		||||
      ${newPlugins.joinToString(separator = "\n") { it.first + "(" + it.second + ")" }}
 | 
			
		||||
    """.trimIndent()
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,12 @@
 | 
			
		||||
 | 
			
		||||
package scripts.release
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId
 | 
			
		||||
import org.eclipse.jgit.revwalk.RevCommit
 | 
			
		||||
import org.eclipse.jgit.revwalk.RevWalk
 | 
			
		||||
import org.eclipse.jgit.revwalk.filter.RevFilter
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
fun main(args: Array<String>) {
 | 
			
		||||
  println("HI!")
 | 
			
		||||
  val projectDir = args[0]
 | 
			
		||||
@@ -19,10 +25,12 @@ fun main(args: Array<String>) {
 | 
			
		||||
  check(branch == "master") {
 | 
			
		||||
    "We should be on master branch"
 | 
			
		||||
  }
 | 
			
		||||
  val mergeBaseCommit = getMergeBaseWithMaster(projectDir, objectId)
 | 
			
		||||
  println("Base commit $mergeBaseCommit")
 | 
			
		||||
  withGit(projectDir) { git ->
 | 
			
		||||
    val log = git.log().setMaxCount(500).call().toList()
 | 
			
		||||
    println("First commit hash in log: " + log.first().name + " log size: ${log.size}")
 | 
			
		||||
    val logDiff = log.takeWhile { it.id.name != objectId.name }
 | 
			
		||||
    val logDiff = log.takeWhile { it.id.name != mergeBaseCommit }
 | 
			
		||||
    val numCommits = logDiff.size
 | 
			
		||||
    println("Log diff size is $numCommits")
 | 
			
		||||
    check(numCommits < 450) {
 | 
			
		||||
@@ -35,3 +43,18 @@ fun main(args: Array<String>) {
 | 
			
		||||
    println("##teamcity[setParameter name='env.ORG_GRADLE_PROJECT_version' value='$nextVersion']")
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun getMergeBaseWithMaster(projectDir: String, tag: ObjectId): String {
 | 
			
		||||
  withRepo(projectDir) { repo ->
 | 
			
		||||
    val master = repo.resolve("master")
 | 
			
		||||
    RevWalk(repo).use { walk ->
 | 
			
		||||
      val tagRevCommit = walk.parseCommit(tag)
 | 
			
		||||
      val masterRevCommit = walk.parseCommit(master)
 | 
			
		||||
      walk.setRevFilter(RevFilter.MERGE_BASE)
 | 
			
		||||
      walk.markStart(tagRevCommit)
 | 
			
		||||
      walk.markStart(masterRevCommit)
 | 
			
		||||
      val mergeBase: RevCommit = walk.next()
 | 
			
		||||
      return mergeBase.name
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,62 +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 scripts
 | 
			
		||||
 | 
			
		||||
import io.ktor.client.call.*
 | 
			
		||||
import kotlinx.serialization.json.JsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import kotlinx.serialization.json.put
 | 
			
		||||
 | 
			
		||||
val areaWeights = setOf(
 | 
			
		||||
  Triple("118-53212", "Plugins", 50),
 | 
			
		||||
  Triple("118-53220", "Vim Script", 30),
 | 
			
		||||
  Triple("118-54084", "Esc", 100),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
suspend fun updateRates() {
 | 
			
		||||
  println("Updating rates of the issues")
 | 
			
		||||
  areaWeights.forEach { (id, name, weight) ->
 | 
			
		||||
    val unmappedIssues = unmappedIssues(name)
 | 
			
		||||
    println("Got ${unmappedIssues.size} for $name area")
 | 
			
		||||
 | 
			
		||||
    unmappedIssues.forEach { issueId ->
 | 
			
		||||
      print("Trying to update issue $issueId: ")
 | 
			
		||||
      val response = updateCustomField(issueId) {
 | 
			
		||||
        put("name", "Affected Rate")
 | 
			
		||||
        put("\$type", "SimpleIssueCustomField")
 | 
			
		||||
        put("value", weight)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      println(response)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private suspend fun unmappedIssues(area: String): List<String> {
 | 
			
		||||
  val areaProcessed = if (" " in area) "{$area}" else area
 | 
			
		||||
  val res = issuesQuery(
 | 
			
		||||
    query = "project: VIM Affected Rate: {No affected rate} Area: $areaProcessed #Unresolved",
 | 
			
		||||
    fields = "id,idReadable"
 | 
			
		||||
  )
 | 
			
		||||
  return res.body<JsonArray>().map { it.jsonObject }.map { it["idReadable"]!!.jsonPrimitive.content }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend fun getAreasWithoutWeight(): Set<Pair<String, String>> {
 | 
			
		||||
  val allAreas = getAreaValues()
 | 
			
		||||
  return allAreas
 | 
			
		||||
    .filterNot { it.key in areaWeights.map { it.first }.toSet() }
 | 
			
		||||
    .entries
 | 
			
		||||
    .map { it.key to it.value }
 | 
			
		||||
    .toSet()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
suspend fun main() {
 | 
			
		||||
  updateRates()
 | 
			
		||||
}
 | 
			
		||||
@@ -11,7 +11,6 @@ package com.maddyhome.idea.vim;
 | 
			
		||||
import com.intellij.openapi.Disposable;
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnAction;
 | 
			
		||||
import com.intellij.openapi.actionSystem.ShortcutSet;
 | 
			
		||||
import com.intellij.openapi.editor.Document;
 | 
			
		||||
import com.intellij.openapi.editor.Editor;
 | 
			
		||||
import com.intellij.openapi.editor.EditorFactory;
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.TypedAction;
 | 
			
		||||
@@ -80,14 +79,6 @@ public class EventFacade {
 | 
			
		||||
    action.unregisterCustomShortcutSet(component);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
 | 
			
		||||
    document.addDocumentListener(listener);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void removeDocumentListener(@NotNull Document document, @NotNull DocumentListener listener) {
 | 
			
		||||
    document.removeDocumentListener(listener);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addEditorFactoryListener(@NotNull EditorFactoryListener listener, @NotNull Disposable parentDisposable) {
 | 
			
		||||
    EditorFactory.getInstance().addEditorFactoryListener(listener, parentDisposable);
 | 
			
		||||
  }
 | 
			
		||||
@@ -98,20 +89,12 @@ public class EventFacade {
 | 
			
		||||
    editor.getCaretModel().addCaretListener(listener, disposable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void removeCaretListener(@NotNull Editor editor, @NotNull CaretListener listener) {
 | 
			
		||||
    editor.getCaretModel().removeCaretListener(listener);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addEditorMouseListener(@NotNull Editor editor,
 | 
			
		||||
                                     @NotNull EditorMouseListener listener,
 | 
			
		||||
                                     @NotNull Disposable disposable) {
 | 
			
		||||
    editor.addEditorMouseListener(listener, disposable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void removeEditorMouseListener(@NotNull Editor editor, @NotNull EditorMouseListener listener) {
 | 
			
		||||
    editor.removeEditorMouseListener(listener);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addComponentMouseListener(@NotNull Component component,
 | 
			
		||||
                                        @NotNull MouseListener mouseListener,
 | 
			
		||||
                                        @NotNull Disposable disposable) {
 | 
			
		||||
@@ -119,30 +102,18 @@ public class EventFacade {
 | 
			
		||||
    Disposer.register(disposable, () -> component.removeMouseListener(mouseListener));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void removeComponentMouseListener(@NotNull Component component, @NotNull MouseListener mouseListener) {
 | 
			
		||||
    component.removeMouseListener(mouseListener);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addEditorMouseMotionListener(@NotNull Editor editor,
 | 
			
		||||
                                           @NotNull EditorMouseMotionListener listener,
 | 
			
		||||
                                           @NotNull Disposable disposable) {
 | 
			
		||||
    editor.addEditorMouseMotionListener(listener, disposable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void removeEditorMouseMotionListener(@NotNull Editor editor, @NotNull EditorMouseMotionListener listener) {
 | 
			
		||||
    editor.removeEditorMouseMotionListener(listener);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void addEditorSelectionListener(@NotNull Editor editor,
 | 
			
		||||
                                         @NotNull SelectionListener listener,
 | 
			
		||||
                                         @NotNull Disposable disposable) {
 | 
			
		||||
    editor.getSelectionModel().addSelectionListener(listener, disposable);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void removeEditorSelectionListener(@NotNull Editor editor, @NotNull SelectionListener listener) {
 | 
			
		||||
    editor.getSelectionModel().removeSelectionListener(listener);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private @NotNull TypedAction getTypedAction() {
 | 
			
		||||
    return TypedAction.getInstance();
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ import com.intellij.openapi.project.ProjectManagerListener
 | 
			
		||||
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
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -36,8 +36,10 @@ internal class PluginStartup : ProjectActivity/*, LightEditCompatible*/ {
 | 
			
		||||
// This is a temporal workaround for VIM-2487
 | 
			
		||||
internal class PyNotebooksCloseWorkaround : ProjectManagerListener {
 | 
			
		||||
  override fun projectClosingBeforeSave(project: Project) {
 | 
			
		||||
    // TODO: Confirm context in CWM scenario
 | 
			
		||||
    if (injector.globalIjOptions().closenotebooks) {
 | 
			
		||||
      localEditors().forEach { editor ->
 | 
			
		||||
      injector.editorGroup.getEditors().forEach { vimEditor ->
 | 
			
		||||
        val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
        val virtualFile = EditorHelper.getVirtualFile(editor)
 | 
			
		||||
        if (virtualFile?.extension == "ipynb") {
 | 
			
		||||
          val fileEditorManager = FileEditorManagerEx.getInstanceEx(project)
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ public object RegisterActions {
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun registerActions() {
 | 
			
		||||
    registerVimCommandActions()
 | 
			
		||||
    registerEmptyShortcuts() // todo most likely it is not needed
 | 
			
		||||
    registerShortcutsWithoutActions()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun findAction(id: String): EditorActionHandlerBase? {
 | 
			
		||||
@@ -46,12 +46,11 @@ public object RegisterActions {
 | 
			
		||||
    IntellijCommandProvider.getCommands().forEach { parser.registerCommandAction(it) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun registerEmptyShortcuts() {
 | 
			
		||||
  private fun registerShortcutsWithoutActions() {
 | 
			
		||||
    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)
 | 
			
		||||
    parser.registerShortcutWithoutAction(KeyStroke.getKeyStroke(KeyEvent.VK_BACK_SPACE, 0), MappingOwner.IdeaVim.System)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -211,22 +211,22 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
  public static void setEnabled(final boolean enabled) {
 | 
			
		||||
    if (isEnabled() == enabled) return;
 | 
			
		||||
 | 
			
		||||
    if (!enabled) {
 | 
			
		||||
      getInstance().turnOffPlugin(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    getInstance().enabled = enabled;
 | 
			
		||||
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      getInstance().turnOnPlugin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOn();
 | 
			
		||||
    } else {
 | 
			
		||||
      VimInjectorKt.getInjector().getListenersNotifier().notifyPluginTurnedOff();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!enabled) {
 | 
			
		||||
      getInstance().turnOffPlugin(true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (enabled) {
 | 
			
		||||
      getInstance().turnOnPlugin();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    StatusBarIconFactory.Util.INSTANCE.updateIcon();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -353,6 +353,7 @@ public class VimPlugin implements PersistentStateComponent<Element>, Disposable
 | 
			
		||||
 | 
			
		||||
    if (onOffDisposable != null) {
 | 
			
		||||
      Disposer.dispose(onOffDisposable);
 | 
			
		||||
      onOffDisposable = null;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,7 @@ import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.maddyhome.idea.vim.group.EditorHolderService
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
@Service(Service.Level.PROJECT)
 | 
			
		||||
internal class VimProjectService(val project: Project) : Disposable {
 | 
			
		||||
  override fun dispose() {
 | 
			
		||||
    // Not sure if this is a best solution
 | 
			
		||||
 
 | 
			
		||||
@@ -77,7 +77,7 @@ public class VimTypedActionHandler(origHandler: TypedActionHandler) : TypedActio
 | 
			
		||||
      val modifiers = if (charTyped == ' ' && VimKeyListener.isSpaceShift) KeyEvent.SHIFT_DOWN_MASK else 0
 | 
			
		||||
      val keyStroke = KeyStroke.getKeyStroke(charTyped, modifiers)
 | 
			
		||||
      val startTime = if (traceTime) System.currentTimeMillis() else null
 | 
			
		||||
      handler.handleKey(editor.vim, keyStroke, injector.executionContextManager.onEditor(editor.vim, context.vim))
 | 
			
		||||
      handler.handleKey(editor.vim, keyStroke, context.vim, handler.keyHandlerState)
 | 
			
		||||
      if (startTime != null) {
 | 
			
		||||
        val duration = System.currentTimeMillis() - startTime
 | 
			
		||||
        LOG.info("VimTypedAction '$charTyped': $duration ms")
 | 
			
		||||
 
 | 
			
		||||
@@ -44,7 +44,6 @@ import com.maddyhome.idea.vim.listener.AceJumpService
 | 
			
		||||
import com.maddyhome.idea.vim.listener.AppCodeTemplates.appCodeTemplateCaptured
 | 
			
		||||
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.vimscript.model.datatypes.VimString
 | 
			
		||||
import java.awt.event.InputEvent
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
@@ -79,11 +78,8 @@ public class VimShortcutKeyAction : AnAction(), DumbAware/*, LightEditCompatible
 | 
			
		||||
      // Should we use HelperKt.getTopLevelEditor(editor) here, as we did in former EditorKeyHandler?
 | 
			
		||||
      try {
 | 
			
		||||
        val start = if (traceTime) System.currentTimeMillis() else null
 | 
			
		||||
        KeyHandler.getInstance().handleKey(
 | 
			
		||||
          editor.vim,
 | 
			
		||||
          keyStroke,
 | 
			
		||||
          injector.executionContextManager.onEditor(editor.vim, e.dataContext.vim),
 | 
			
		||||
        )
 | 
			
		||||
        val keyHandler = KeyHandler.getInstance()
 | 
			
		||||
        keyHandler.handleKey(editor.vim, keyStroke, e.dataContext.vim, keyHandler.keyHandlerState)
 | 
			
		||||
        if (start != null) {
 | 
			
		||||
          val duration = System.currentTimeMillis() - start
 | 
			
		||||
          LOG.info("VimShortcut update '$keyStroke': $duration ms")
 | 
			
		||||
@@ -380,6 +376,10 @@ private class ActionEnableStatus(
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun toString(): String {
 | 
			
		||||
    return "ActionEnableStatus(isEnabled=$isEnabled, message='$message', logLevel=$logLevel)"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    private val LOG = logger<ActionEnableStatus>()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
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.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.setChangeMarks
 | 
			
		||||
import com.maddyhome.idea.vim.command.Argument
 | 
			
		||||
@@ -21,6 +22,7 @@ import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.common.argumentCaptured
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExException
 | 
			
		||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.handler.VimActionHandler
 | 
			
		||||
@@ -29,21 +31,67 @@ import com.maddyhome.idea.vim.helper.MessageHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimFuncref
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.expressions.FunctionCallExpression
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.expressions.SimpleExpression
 | 
			
		||||
 | 
			
		||||
// todo make it multicaret
 | 
			
		||||
private fun doOperatorAction(editor: VimEditor, context: ExecutionContext, textRange: TextRange, selectionType: SelectionType): Boolean {
 | 
			
		||||
  val operatorFunction = injector.keyGroup.operatorFunction
 | 
			
		||||
  if (operatorFunction == null) {
 | 
			
		||||
  val func = injector.globalOptions().operatorfunc
 | 
			
		||||
  if (func.isEmpty()) {
 | 
			
		||||
    VimPlugin.showMessage(MessageHelper.message("E774"))
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val scriptContext = CommandLineVimLContext
 | 
			
		||||
 | 
			
		||||
  // The option value is either a function name, which should have a handler, or it might be a lambda expression, or a
 | 
			
		||||
  // `function` or `funcref` call expression, all of which will return a funcref (with a handler)
 | 
			
		||||
  var handler = injector.functionService.getFunctionHandlerOrNull(null, func, scriptContext)
 | 
			
		||||
  if (handler == null) {
 | 
			
		||||
    val expression = injector.vimscriptParser.parseExpression(func)
 | 
			
		||||
    if (expression != null) {
 | 
			
		||||
      try {
 | 
			
		||||
        val value = expression.evaluate(editor, context, scriptContext)
 | 
			
		||||
        if (value is VimFuncref) {
 | 
			
		||||
          handler = value.handler
 | 
			
		||||
        }
 | 
			
		||||
      } catch (ex: ExException) {
 | 
			
		||||
        // Get the argument for function('...') or funcref('...') for the error message
 | 
			
		||||
        val functionName = if (expression is FunctionCallExpression && expression.arguments.size > 0) {
 | 
			
		||||
          expression.arguments[0].evaluate(editor, context, scriptContext).toString()
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          func
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        VimPlugin.showMessage("E117: Unknown function: $functionName")
 | 
			
		||||
        return false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (handler == null) {
 | 
			
		||||
    VimPlugin.showMessage("E117: Unknown function: $func")
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val arg = when (selectionType) {
 | 
			
		||||
    SelectionType.LINE_WISE -> "line"
 | 
			
		||||
    SelectionType.CHARACTER_WISE -> "char"
 | 
			
		||||
    SelectionType.BLOCK_WISE -> "block"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val saveRepeatHandler = VimRepeater.repeatHandler
 | 
			
		||||
  injector.markService.setChangeMarks(editor.primaryCaret(), textRange)
 | 
			
		||||
  KeyHandler.getInstance().reset(editor)
 | 
			
		||||
  val result = operatorFunction.apply(editor, context, selectionType)
 | 
			
		||||
 | 
			
		||||
  val arguments = listOf(SimpleExpression(arg))
 | 
			
		||||
  handler.executeFunction(arguments, editor, context, scriptContext)
 | 
			
		||||
 | 
			
		||||
  VimRepeater.repeatHandler = saveRepeatHandler
 | 
			
		||||
  return result
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@CommandOrMotion(keys = ["g@"], modes = [Mode.NORMAL])
 | 
			
		||||
 
 | 
			
		||||
@@ -7,9 +7,9 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.action.change
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
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
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ public class DeleteJoinLinesSpacesAction : ChangeEditorActionHandler.SingleExecu
 | 
			
		||||
    }
 | 
			
		||||
    injector.editorGroup.notifyIdeaJoin(editor)
 | 
			
		||||
    var res = true
 | 
			
		||||
    editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
 | 
			
		||||
    editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
 | 
			
		||||
      if (!injector.changeGroup.deleteJoinLines(editor, caret, operatorArguments.count1, true, operatorArguments)) {
 | 
			
		||||
        res = false
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesAction : VisualOperatorActionHandler.SingleExe
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    var res = true
 | 
			
		||||
    editor.nativeCarets().sortedByDescending { it.offset.point }.forEach { caret ->
 | 
			
		||||
    editor.nativeCarets().sortedByDescending { it.offset }.forEach { caret ->
 | 
			
		||||
      if (!caret.isValid) return@forEach
 | 
			
		||||
      val range = caretsAndSelections[caret] ?: return@forEach
 | 
			
		||||
      if (!injector.changeGroup.deleteJoinRange(
 | 
			
		||||
 
 | 
			
		||||
@@ -39,7 +39,7 @@ public class DeleteJoinVisualLinesSpacesAction : VisualOperatorActionHandler.Sin
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    var res = true
 | 
			
		||||
    editor.carets().sortedByDescending { it.offset.point }.forEach { caret ->
 | 
			
		||||
    editor.carets().sortedByDescending { it.offset }.forEach { caret ->
 | 
			
		||||
      if (!caret.isValid) return@forEach
 | 
			
		||||
      val range = caretsAndSelections[caret] ?: return@forEach
 | 
			
		||||
      if (!injector.changeGroup.deleteJoinRange(
 | 
			
		||||
 
 | 
			
		||||
@@ -21,19 +21,19 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
public class CommandState(private val machine: VimStateMachine) {
 | 
			
		||||
 | 
			
		||||
  public val isOperatorPending: Boolean
 | 
			
		||||
    get() = machine.isOperatorPending
 | 
			
		||||
    get() = machine.isOperatorPending(machine.mode)
 | 
			
		||||
 | 
			
		||||
  public val mode: CommandState.Mode
 | 
			
		||||
  public val mode: Mode
 | 
			
		||||
    get() {
 | 
			
		||||
      val myMode = machine.mode
 | 
			
		||||
      return when (myMode) {
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> CommandState.Mode.CMD_LINE
 | 
			
		||||
        com.maddyhome.idea.vim.state.mode.Mode.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
 | 
			
		||||
        com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> CommandState.Mode.REPLACE
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> CommandState.Mode.SELECT
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> CommandState.Mode.VISUAL
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.CMD_LINE -> Mode.CMD_LINE
 | 
			
		||||
        com.maddyhome.idea.vim.state.mode.Mode.INSERT -> Mode.INSERT
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.NORMAL -> Mode.COMMAND
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.OP_PENDING -> Mode.OP_PENDING
 | 
			
		||||
        com.maddyhome.idea.vim.state.mode.Mode.REPLACE -> Mode.REPLACE
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.SELECT -> Mode.SELECT
 | 
			
		||||
        is com.maddyhome.idea.vim.state.mode.Mode.VISUAL -> Mode.VISUAL
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -9,8 +9,6 @@
 | 
			
		||||
package com.maddyhome.idea.vim.common
 | 
			
		||||
 | 
			
		||||
import com.intellij.application.options.CodeStyle
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.psi.codeStyle.CommonCodeStyleSettings.IndentOptions
 | 
			
		||||
@@ -39,13 +37,12 @@ internal class IndentConfig private constructor(indentOptions: IndentOptions) {
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    fun create(editor: Editor, context: DataContext): IndentConfig {
 | 
			
		||||
      return create(editor, PlatformDataKeys.PROJECT.getData(context))
 | 
			
		||||
    fun create(editor: Editor): IndentConfig {
 | 
			
		||||
      return create(editor, editor.project)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @JvmStatic
 | 
			
		||||
    @JvmOverloads
 | 
			
		||||
    fun create(editor: Editor, project: Project? = editor.project): IndentConfig {
 | 
			
		||||
    fun create(editor: Editor, project: Project?): IndentConfig {
 | 
			
		||||
      val indentOptions = if (project != null) {
 | 
			
		||||
        CodeStyle.getIndentOptions(project, editor.document)
 | 
			
		||||
      } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.extension
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.components.service
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
@@ -14,21 +15,34 @@ import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.action.change.Extension
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
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.MappingMode
 | 
			
		||||
import com.maddyhome.idea.vim.common.CommandAlias
 | 
			
		||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CommandLineHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.TestInputModel
 | 
			
		||||
import com.maddyhome.idea.vim.helper.noneOfEnum
 | 
			
		||||
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 com.maddyhome.idea.vim.vimscript.model.Executable
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.ExecutionResult
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.VimLContext
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimDataType
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Expression
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.statements.FunctionFlag
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import java.util.*
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -120,12 +134,6 @@ public object VimExtensionFacade {
 | 
			
		||||
      .setAlias(name, CommandAlias.Call(minimumNumberOfArguments, maximumNumberOfArguments, name, handler))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Sets the value of 'operatorfunc' to be used as the operator function in 'g@'. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun setOperatorFunction(function: OperatorFunction) {
 | 
			
		||||
    VimPlugin.getKey().operatorFunction = function
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Runs normal mode commands similar to ':normal! {commands}'.
 | 
			
		||||
   * Mappings doesn't work with this function
 | 
			
		||||
@@ -135,8 +143,9 @@ public object VimExtensionFacade {
 | 
			
		||||
   */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun executeNormalWithoutMapping(keys: List<KeyStroke>, editor: Editor) {
 | 
			
		||||
    val context = injector.executionContextManager.onEditor(editor.vim)
 | 
			
		||||
    keys.forEach { KeyHandler.getInstance().handleKey(editor.vim, it, context, false, false) }
 | 
			
		||||
    val context = injector.executionContextManager.getEditorExecutionContext(editor.vim)
 | 
			
		||||
    val keyHandler = KeyHandler.getInstance()
 | 
			
		||||
    keys.forEach { keyHandler.handleKey(editor.vim, it, context, false, false, keyHandler.keyHandlerState) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Returns a single key stroke from the user input similar to 'getchar()'. */
 | 
			
		||||
@@ -152,7 +161,7 @@ public object VimExtensionFacade {
 | 
			
		||||
      LOG.trace("Unit test mode is active")
 | 
			
		||||
      val mappingStack = KeyHandler.getInstance().keyStack
 | 
			
		||||
      mappingStack.feedSomeStroke() ?: TestInputModel.getInstance(editor).nextKeyStroke()?.also {
 | 
			
		||||
        if (editor.vim.vimStateMachine.isRecording) {
 | 
			
		||||
        if (injector.registerGroup.isRecording) {
 | 
			
		||||
          KeyHandler.getInstance().modalEntryKeys += it
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -173,8 +182,8 @@ public object VimExtensionFacade {
 | 
			
		||||
 | 
			
		||||
  /** Returns a string typed in the input box similar to 'input()'. */
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun inputString(editor: Editor, prompt: String, finishOn: Char?): String {
 | 
			
		||||
    return service<CommandLineHelper>().inputString(editor.vim, prompt, finishOn) ?: ""
 | 
			
		||||
  public fun inputString(editor: Editor, context: DataContext, prompt: String, finishOn: Char?): String {
 | 
			
		||||
    return service<CommandLineHelper>().inputString(editor.vim, context.vim, prompt, finishOn) ?: ""
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /** Get the current contents of the given register similar to 'getreg()'. */
 | 
			
		||||
@@ -207,4 +216,65 @@ public object VimExtensionFacade {
 | 
			
		||||
  public fun setRegister(register: Char, keys: List<KeyStroke?>?, type: SelectionType) {
 | 
			
		||||
    VimPlugin.getRegister().setKeys(register, keys?.filterNotNull() ?: emptyList(), type)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  public fun exportScriptFunction(
 | 
			
		||||
    scope: Scope?,
 | 
			
		||||
    name: String,
 | 
			
		||||
    args: List<String>,
 | 
			
		||||
    defaultArgs: List<Pair<String, Expression>>,
 | 
			
		||||
    hasOptionalArguments: Boolean,
 | 
			
		||||
    flags: EnumSet<FunctionFlag>,
 | 
			
		||||
    function: ScriptFunction
 | 
			
		||||
  ) {
 | 
			
		||||
    var functionDeclaration: FunctionDeclaration? = null
 | 
			
		||||
    val body = listOf(object : Executable {
 | 
			
		||||
      // This context is set to the function declaration during initialisation and then set to the function execution
 | 
			
		||||
      // context during execution
 | 
			
		||||
      override lateinit var vimContext: VimLContext
 | 
			
		||||
      override var rangeInScript: TextRange = TextRange(0, 0)
 | 
			
		||||
 | 
			
		||||
      override fun execute(editor: VimEditor, context: ExecutionContext): ExecutionResult {
 | 
			
		||||
        return function.execute(editor, context, functionDeclaration!!.functionVariables)
 | 
			
		||||
      }
 | 
			
		||||
    })
 | 
			
		||||
    functionDeclaration = FunctionDeclaration(
 | 
			
		||||
      scope,
 | 
			
		||||
      name,
 | 
			
		||||
      args,
 | 
			
		||||
      defaultArgs,
 | 
			
		||||
      body,
 | 
			
		||||
      replaceExisting = true,
 | 
			
		||||
      flags,
 | 
			
		||||
      hasOptionalArguments
 | 
			
		||||
    )
 | 
			
		||||
    functionDeclaration.rangeInScript = TextRange(0, 0)
 | 
			
		||||
    body.forEach { it.vimContext = functionDeclaration }
 | 
			
		||||
    injector.functionService.storeFunction(functionDeclaration)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun VimExtensionFacade.exportOperatorFunction(name: String, function: OperatorFunction) {
 | 
			
		||||
  exportScriptFunction(null, name, listOf("type"), emptyList(), false, noneOfEnum()) {
 | 
			
		||||
    editor, context, args ->
 | 
			
		||||
 | 
			
		||||
    val type = args["type"]?.asString()
 | 
			
		||||
    val selectionType = when (type) {
 | 
			
		||||
      "line" -> SelectionType.LINE_WISE
 | 
			
		||||
      "block" -> SelectionType.BLOCK_WISE
 | 
			
		||||
      "char" -> SelectionType.CHARACTER_WISE
 | 
			
		||||
      else -> return@exportScriptFunction ExecutionResult.Error
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (function.apply(editor, context, selectionType)) {
 | 
			
		||||
      ExecutionResult.Success
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      ExecutionResult.Error
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun interface ScriptFunction {
 | 
			
		||||
  public fun execute(editor: VimEditor, context: ExecutionContext, args: Map<String, VimDataType>): ExecutionResult
 | 
			
		||||
}
 | 
			
		||||
@@ -67,7 +67,7 @@ internal object VimExtensionRegistrar : VimExtensionRegistrator {
 | 
			
		||||
    VimPlugin.getOptionGroup().addGlobalOptionChangeListener(option) {
 | 
			
		||||
      if (injector.optionGroup.getOptionValue(option, OptionAccessScope.GLOBAL(null)).asBoolean()) {
 | 
			
		||||
        initExtension(extensionBean, name)
 | 
			
		||||
        PluginState.enabledExtensions.add(name)
 | 
			
		||||
        PluginState.Util.enabledExtensions.add(name)
 | 
			
		||||
      } else {
 | 
			
		||||
        extensionBean.instance.dispose()
 | 
			
		||||
      }
 | 
			
		||||
 
 | 
			
		||||
@@ -251,7 +251,7 @@ public class VimArgTextObjExtension implements VimExtension {
 | 
			
		||||
 | 
			
		||||
      final ArgumentTextObjectHandler textObjectHandler = new ArgumentTextObjectHandler(isInner);
 | 
			
		||||
      //noinspection DuplicatedCode
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending()) {
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending(editor.getMode())) {
 | 
			
		||||
        editor.nativeCarets().forEach((VimCaret caret) -> {
 | 
			
		||||
          final TextRange range = textObjectHandler.getRange(editor, caret, context, count, 0);
 | 
			
		||||
          if (range != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -22,26 +22,26 @@ import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
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.command.Argument
 | 
			
		||||
import com.maddyhome.idea.vim.command.Command
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandFlags
 | 
			
		||||
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.command.TextObjectVisualType
 | 
			
		||||
import com.maddyhome.idea.vim.common.CommandAliasHandler
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ranges.Ranges
 | 
			
		||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtension
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.addCommand
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMapping
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.helper.PsiHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
@@ -49,17 +49,19 @@ import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
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.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
internal class CommentaryExtension : VimExtension {
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
  object Util {
 | 
			
		||||
    fun doCommentary(
 | 
			
		||||
      editor: VimEditor,
 | 
			
		||||
      context: ExecutionContext,
 | 
			
		||||
      range: TextRange,
 | 
			
		||||
      selectionType: SelectionType,
 | 
			
		||||
      resetCaret: Boolean,
 | 
			
		||||
      resetCaret: Boolean = true,
 | 
			
		||||
    ): Boolean {
 | 
			
		||||
      val mode = editor.vimStateMachine.mode
 | 
			
		||||
      if (mode !is Mode.VISUAL) {
 | 
			
		||||
@@ -67,8 +69,7 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return runWriteAction {
 | 
			
		||||
        // Treat block- and character-wise selections as block comments. Be ready to fall back to if the first action
 | 
			
		||||
        // isn't available
 | 
			
		||||
        // Treat block- and character-wise selections as block comments. Fall back if the first action isn't available
 | 
			
		||||
        val actions = if (selectionType === SelectionType.LINE_WISE) {
 | 
			
		||||
          listOf(IdeActions.ACTION_COMMENT_LINE, IdeActions.ACTION_COMMENT_BLOCK)
 | 
			
		||||
        } else {
 | 
			
		||||
@@ -113,12 +114,17 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
      // first non-whitespace character, then the caret is in the right place. If it's inserted at the first column,
 | 
			
		||||
      // then the caret is now in a bit of a weird place. We can't detect this scenario, so we just have to accept
 | 
			
		||||
      // the difference
 | 
			
		||||
      // TODO: If we don't move the caret to the start offset, we should maintain the current logical position
 | 
			
		||||
      if (resetCaret) {
 | 
			
		||||
        editor.primaryCaret().moveToOffset(range.startOffset)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    private const val OPERATOR_FUNC = "CommentaryOperatorFunc"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getName() = "commentary"
 | 
			
		||||
 | 
			
		||||
  override fun init() {
 | 
			
		||||
@@ -145,6 +151,16 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
    putKeyMapping(MappingMode.N, injector.parser.parseKeys("<Plug>(CommentLine)"), owner, plugCommentaryLineKeys, true)
 | 
			
		||||
 | 
			
		||||
    addCommand("Commentary", CommentaryCommandAliasHandler())
 | 
			
		||||
 | 
			
		||||
    VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, CommentaryOperatorFunction())
 | 
			
		||||
 }
 | 
			
		||||
 | 
			
		||||
  private class CommentaryOperatorFunction : OperatorFunction {
 | 
			
		||||
    // todo make it multicaret
 | 
			
		||||
    override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
 | 
			
		||||
      val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
 | 
			
		||||
      return Util.doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
@@ -153,19 +169,13 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
   * E.g. handles the `gc` in `gc_`, by setting the operator function, then invoking `g@` to receive the `_` motion to
 | 
			
		||||
   * invoke the operator. This object is both the mapping handler and the operator function.
 | 
			
		||||
   */
 | 
			
		||||
  private class CommentaryOperatorHandler : OperatorFunction, ExtensionHandler {
 | 
			
		||||
  private class CommentaryOperatorHandler : ExtensionHandler {
 | 
			
		||||
    override val isRepeatable = true
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      setOperatorFunction(this)
 | 
			
		||||
      injector.globalOptions().operatorfunc = OPERATOR_FUNC
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // todo make it multicaret
 | 
			
		||||
    override fun apply(editor: VimEditor, context: ExecutionContext, selectionType: SelectionType?): Boolean {
 | 
			
		||||
      val range = injector.markService.getChangeMarks(editor.primaryCaret()) ?: return false
 | 
			
		||||
      return doCommentary(editor, context, range, selectionType ?: SelectionType.CHARACTER_WISE, true)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class CommentaryMappingHandler : ExtensionHandler {
 | 
			
		||||
@@ -239,7 +249,7 @@ internal class CommentaryExtension : VimExtension {
 | 
			
		||||
   */
 | 
			
		||||
  private class CommentaryCommandAliasHandler : CommandAliasHandler {
 | 
			
		||||
    override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
 | 
			
		||||
      doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
 | 
			
		||||
      Util.doCommentary(editor, context, ranges.getTextRange(editor, -1), SelectionType.LINE_WISE, false)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,24 +19,22 @@ import com.intellij.openapi.util.Key
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.getOffset
 | 
			
		||||
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.command.MappingMode
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType.CHARACTER_WISE
 | 
			
		||||
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
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegister
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setRegister
 | 
			
		||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.helper.fileSize
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareLogicalPosition
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
@@ -45,6 +43,8 @@ import com.maddyhome.idea.vim.mark.VimMarkConstants
 | 
			
		||||
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.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
			
		||||
import org.jetbrains.annotations.NonNls
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -72,30 +72,13 @@ internal class VimExchangeExtension : VimExtension {
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("X"), owner, injector.parser.parseKeys(EXCHANGE_CMD), true)
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxc"), owner, injector.parser.parseKeys(EXCHANGE_CLEAR_CMD), true)
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("cxx"), owner, injector.parser.parseKeys(EXCHANGE_LINE_CMD), true)
 | 
			
		||||
 | 
			
		||||
    VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    @NonNls
 | 
			
		||||
    const val EXCHANGE_CMD = "<Plug>(Exchange)"
 | 
			
		||||
 | 
			
		||||
    @NonNls
 | 
			
		||||
    const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
 | 
			
		||||
 | 
			
		||||
    @NonNls
 | 
			
		||||
    const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
 | 
			
		||||
 | 
			
		||||
  object Util {
 | 
			
		||||
    val EXCHANGE_KEY = Key<Exchange>("exchange")
 | 
			
		||||
 | 
			
		||||
    // End mark has always greater of eq offset than start mark
 | 
			
		||||
    class Exchange(val type: SelectionType, val start: Mark, val end: Mark, val text: String) {
 | 
			
		||||
      private var myHighlighter: RangeHighlighter? = null
 | 
			
		||||
      fun setHighlighter(highlighter: RangeHighlighter) {
 | 
			
		||||
        myHighlighter = highlighter
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      fun getHighlighter(): RangeHighlighter? = myHighlighter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun clearExchange(editor: Editor) {
 | 
			
		||||
      editor.getUserData(EXCHANGE_KEY)?.getHighlighter()?.let {
 | 
			
		||||
        editor.markupModel.removeHighlighter(it)
 | 
			
		||||
@@ -104,18 +87,25 @@ internal class VimExchangeExtension : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    @NonNls private const val EXCHANGE_CMD = "<Plug>(Exchange)"
 | 
			
		||||
    @NonNls private const val EXCHANGE_CLEAR_CMD = "<Plug>(ExchangeClear)"
 | 
			
		||||
    @NonNls private const val EXCHANGE_LINE_CMD = "<Plug>(ExchangeLine)"
 | 
			
		||||
    @NonNls private const val OPERATOR_FUNC = "ExchangeOperatorFunc"
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class ExchangeHandler(private val isLine: Boolean) : ExtensionHandler {
 | 
			
		||||
    override val isRepeatable = true
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      setOperatorFunction(Operator(false))
 | 
			
		||||
      injector.globalOptions().operatorfunc = OPERATOR_FUNC
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys(if (isLine) "g@_" else "g@"), editor.ij)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class ExchangeClearHandler : ExtensionHandler {
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      clearExchange(editor.ij)
 | 
			
		||||
      Util.clearExchange(editor.ij)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -125,12 +115,12 @@ internal class VimExchangeExtension : VimExtension {
 | 
			
		||||
        val mode = editor.mode
 | 
			
		||||
        // Leave visual mode to create selection marks
 | 
			
		||||
        executeNormalWithoutMapping(injector.parser.parseKeys("<Esc>"), editor.ij)
 | 
			
		||||
        Operator(true).apply(editor, context, mode.selectionType ?: CHARACTER_WISE)
 | 
			
		||||
        Operator(true).apply(editor, context, mode.selectionType ?: SelectionType.CHARACTER_WISE)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class Operator(private val isVisual: Boolean) : OperatorFunction {
 | 
			
		||||
  private class Operator(private val isVisual: Boolean = false) : OperatorFunction {
 | 
			
		||||
    fun Editor.getMarkOffset(mark: Mark) = IjVimEditor(this).getOffset(mark.line, mark.col)
 | 
			
		||||
    fun SelectionType.getString() = when (this) {
 | 
			
		||||
      SelectionType.CHARACTER_WISE -> "v"
 | 
			
		||||
@@ -148,7 +138,7 @@ internal class VimExchangeExtension : VimExtension {
 | 
			
		||||
          else -> HighlighterTargetArea.EXACT_RANGE
 | 
			
		||||
        }
 | 
			
		||||
        val isVisualLine = ex.type == SelectionType.LINE_WISE
 | 
			
		||||
        val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || (isVisual))) 1 else 0
 | 
			
		||||
        val endAdj = if (!(isVisualLine) && (hlArea == HighlighterTargetArea.EXACT_RANGE || isVisual)) 1 else 0
 | 
			
		||||
        return ijEditor.markupModel.addRangeHighlighter(
 | 
			
		||||
          ijEditor.getMarkOffset(ex.start),
 | 
			
		||||
          (ijEditor.getMarkOffset(ex.end) + endAdj).coerceAtMost(ijEditor.fileSize),
 | 
			
		||||
@@ -158,12 +148,12 @@ internal class VimExchangeExtension : VimExtension {
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: CHARACTER_WISE)
 | 
			
		||||
      val exchange1 = ijEditor.getUserData(EXCHANGE_KEY)
 | 
			
		||||
      val currentExchange = getExchange(ijEditor, isVisual, selectionType ?: SelectionType.CHARACTER_WISE)
 | 
			
		||||
      val exchange1 = ijEditor.getUserData(Util.EXCHANGE_KEY)
 | 
			
		||||
      if (exchange1 == null) {
 | 
			
		||||
        val highlighter = highlightExchange(currentExchange)
 | 
			
		||||
        currentExchange.setHighlighter(highlighter)
 | 
			
		||||
        ijEditor.putUserData(EXCHANGE_KEY, currentExchange)
 | 
			
		||||
        ijEditor.putUserData(Util.EXCHANGE_KEY, currentExchange)
 | 
			
		||||
        return true
 | 
			
		||||
      } else {
 | 
			
		||||
        val cmp = compareExchanges(exchange1, currentExchange)
 | 
			
		||||
@@ -189,7 +179,7 @@ internal class VimExchangeExtension : VimExtension {
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        exchange(ijEditor, ex1, ex2, reverse, expand)
 | 
			
		||||
        clearExchange(ijEditor)
 | 
			
		||||
        Util.clearExchange(ijEditor)
 | 
			
		||||
        return true
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -354,4 +344,14 @@ internal class VimExchangeExtension : VimExtension {
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // End mark has always greater of eq offset than start mark
 | 
			
		||||
  class Exchange(val type: SelectionType, val start: Mark, val end: Mark, val text: String) {
 | 
			
		||||
    private var myHighlighter: RangeHighlighter? = null
 | 
			
		||||
    fun setHighlighter(highlighter: RangeHighlighter) {
 | 
			
		||||
      myHighlighter = highlighter
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun getHighlighter(): RangeHighlighter? = myHighlighter
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -99,7 +99,7 @@ internal class Matchit : VimExtension {
 | 
			
		||||
 | 
			
		||||
      // Normally we want to jump to the start of the matching pair. But when moving forward in operator
 | 
			
		||||
      // pending mode, we want to include the entire match. isInOpPending makes that distinction.
 | 
			
		||||
      val isInOpPending = commandState.isOperatorPending
 | 
			
		||||
      val isInOpPending = commandState.isOperatorPending(editor.mode)
 | 
			
		||||
 | 
			
		||||
      if (isInOpPending) {
 | 
			
		||||
        val matchitAction = MatchitAction()
 | 
			
		||||
 
 | 
			
		||||
@@ -8,25 +8,21 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.extension.nerdtree
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.projectView.ProjectView
 | 
			
		||||
import com.intellij.ide.projectView.impl.AbstractProjectViewPane
 | 
			
		||||
import com.intellij.ide.projectView.impl.ProjectViewImpl
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionManager
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.CommonDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformCoreDataKeys
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
			
		||||
import com.intellij.openapi.project.DumbAwareAction
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager
 | 
			
		||||
import com.intellij.openapi.startup.ProjectActivity
 | 
			
		||||
import com.intellij.openapi.wm.ToolWindow
 | 
			
		||||
import com.intellij.openapi.ui.getUserData
 | 
			
		||||
import com.intellij.openapi.ui.putUserData
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.wm.ToolWindowId
 | 
			
		||||
import com.intellij.openapi.wm.ex.ToolWindowManagerEx
 | 
			
		||||
import com.intellij.openapi.wm.ex.ToolWindowManagerListener
 | 
			
		||||
import com.intellij.ui.KeyStrokeAdapter
 | 
			
		||||
import com.intellij.ui.TreeExpandCollapse
 | 
			
		||||
import com.intellij.ui.speedSearch.SpeedSearchSupply
 | 
			
		||||
@@ -53,6 +49,8 @@ import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.datatypes.VimString
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import javax.swing.JComponent
 | 
			
		||||
import javax.swing.JTree
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
import javax.swing.SwingConstants
 | 
			
		||||
 | 
			
		||||
@@ -130,15 +128,14 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    addCommand("NERDTreeFind", IjCommandHandler("SelectInProjectView"))
 | 
			
		||||
    addCommand("NERDTreeRefreshRoot", IjCommandHandler("Synchronize"))
 | 
			
		||||
 | 
			
		||||
    synchronized(monitor) {
 | 
			
		||||
      commandsRegistered = true
 | 
			
		||||
      ProjectManager.getInstance().openProjects.forEach { project -> installDispatcher(project) }
 | 
			
		||||
    synchronized(Util.monitor) {
 | 
			
		||||
      Util.commandsRegistered = true
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class IjCommandHandler(private val actionId: String) : CommandAliasHandler {
 | 
			
		||||
    override fun execute(command: String, ranges: Ranges, editor: VimEditor, context: ExecutionContext) {
 | 
			
		||||
      callAction(editor, actionId, context)
 | 
			
		||||
      Util.callAction(editor, actionId, context)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -149,7 +146,7 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
      if (toolWindow.isVisible) {
 | 
			
		||||
        toolWindow.hide()
 | 
			
		||||
      } else {
 | 
			
		||||
        callAction(editor, "ActivateProjectToolWindow", context)
 | 
			
		||||
        Util.callAction(editor, "ActivateProjectToolWindow", context)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -164,39 +161,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class ProjectViewListener(private val project: Project) : ToolWindowManagerListener {
 | 
			
		||||
    override fun toolWindowShown(toolWindow: ToolWindow) {
 | 
			
		||||
      if (ToolWindowId.PROJECT_VIEW != toolWindow.id) return
 | 
			
		||||
 | 
			
		||||
      val dispatcher = NerdDispatcher.getInstance(project)
 | 
			
		||||
      if (dispatcher.speedSearchListenerInstalled) return
 | 
			
		||||
 | 
			
		||||
      // I specify nullability explicitly as we've got a lot of exceptions saying this property is null
 | 
			
		||||
      val currentProjectViewPane: AbstractProjectViewPane? = ProjectView.getInstance(project).currentProjectViewPane
 | 
			
		||||
      val tree = currentProjectViewPane?.tree ?: return
 | 
			
		||||
      val supply = SpeedSearchSupply.getSupply(tree, true) ?: return
 | 
			
		||||
 | 
			
		||||
      // NB: Here might be some issues with concurrency, but it's not really bad, I think
 | 
			
		||||
      dispatcher.speedSearchListenerInstalled = true
 | 
			
		||||
      supply.addChangeListener {
 | 
			
		||||
        dispatcher.waitForSearch = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO I'm not sure is this activity runs at all? Should we use [RunOnceUtil] instead?
 | 
			
		||||
  class NerdStartupActivity : ProjectActivity {
 | 
			
		||||
    override suspend fun execute(project: Project) {
 | 
			
		||||
      synchronized(monitor) {
 | 
			
		||||
        if (!commandsRegistered) return
 | 
			
		||||
        installDispatcher(project)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class NerdDispatcher : DumbAwareAction() {
 | 
			
		||||
    internal var waitForSearch = false
 | 
			
		||||
    internal var speedSearchListenerInstalled = false
 | 
			
		||||
 | 
			
		||||
    override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
      var keyStroke = getKeyStroke(e) ?: return
 | 
			
		||||
@@ -214,7 +180,7 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
 | 
			
		||||
          val action = nextNode.actionHolder
 | 
			
		||||
          when (action) {
 | 
			
		||||
            is NerdAction.ToIj -> callAction(null, action.name, e.dataContext.vim)
 | 
			
		||||
            is NerdAction.ToIj -> Util.callAction(null, action.name, e.dataContext.vim)
 | 
			
		||||
            is NerdAction.Code -> e.project?.let { action.action(it, e.dataContext, e) }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
@@ -244,10 +210,6 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
      fun getInstance(project: Project): NerdDispatcher {
 | 
			
		||||
        return project.getService(NerdDispatcher::class.java)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      private const val ESCAPE_KEY_CODE = 27
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -283,19 +245,14 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapActivateNode",
 | 
			
		||||
      "o",
 | 
			
		||||
      NerdAction.Code { project, dataContext, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, dataContext, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
 | 
			
		||||
        val array = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext)?.filter { it.canNavigateToSource() }
 | 
			
		||||
        if (array.isNullOrEmpty()) {
 | 
			
		||||
          val row = tree.selectionRows?.getOrNull(0) ?: return@Code
 | 
			
		||||
          if (tree.isExpanded(row)) {
 | 
			
		||||
            tree.collapseRow(row)
 | 
			
		||||
          } else {
 | 
			
		||||
            tree.expandRow(row)
 | 
			
		||||
          }
 | 
			
		||||
        val row = tree.selectionRows?.getOrNull(0) ?: return@Code
 | 
			
		||||
        if (tree.isExpanded(row)) {
 | 
			
		||||
          tree.collapseRow(row)
 | 
			
		||||
        } else {
 | 
			
		||||
          array.forEach { it.navigate(true) }
 | 
			
		||||
          tree.expandRow(row)
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
@@ -356,7 +313,7 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
        currentWindow?.split(SwingConstants.VERTICAL, true, file, true)
 | 
			
		||||
 | 
			
		||||
        // FIXME: 22.01.2021 This solution bouncing a bit
 | 
			
		||||
        callAction(null, "ActivateProjectToolWindow", context.vim)
 | 
			
		||||
        Util.callAction(null, "ActivateProjectToolWindow", context.vim)
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
    registerCommand(
 | 
			
		||||
@@ -368,14 +325,14 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
        val currentWindow = splitters.currentWindow
 | 
			
		||||
        currentWindow?.split(SwingConstants.HORIZONTAL, true, file, true)
 | 
			
		||||
 | 
			
		||||
        callAction(null, "ActivateProjectToolWindow", context.vim)
 | 
			
		||||
        Util.callAction(null, "ActivateProjectToolWindow", context.vim)
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapOpenRecursively",
 | 
			
		||||
      "O",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        TreeExpandCollapse.expandAll(tree)
 | 
			
		||||
        tree.selectionPath?.let {
 | 
			
		||||
          TreeUtil.scrollToVisible(tree, it, false)
 | 
			
		||||
@@ -385,8 +342,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapCloseChildren",
 | 
			
		||||
      "X",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        TreeExpandCollapse.collapse(tree)
 | 
			
		||||
        tree.selectionPath?.let {
 | 
			
		||||
          TreeUtil.scrollToVisible(tree, it, false)
 | 
			
		||||
@@ -396,8 +353,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapCloseDir",
 | 
			
		||||
      "x",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
        if (tree.isExpanded(currentPath)) {
 | 
			
		||||
          tree.collapsePath(currentPath)
 | 
			
		||||
@@ -415,8 +372,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpParent",
 | 
			
		||||
      "p",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
        val parentPath = currentPath.parentPath ?: return@Code
 | 
			
		||||
        if (parentPath.parentPath != null) {
 | 
			
		||||
@@ -429,8 +386,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpFirstChild",
 | 
			
		||||
      "K",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
        val parent = currentPath.parentPath ?: return@Code
 | 
			
		||||
        val row = tree.getRowForPath(parent)
 | 
			
		||||
@@ -442,8 +399,8 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpLastChild",
 | 
			
		||||
      "J",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val tree = ProjectView.getInstance(project).currentProjectViewPane.tree
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        val currentPath = tree.selectionPath ?: return@Code
 | 
			
		||||
 | 
			
		||||
        val currentPathCount = currentPath.pathCount
 | 
			
		||||
@@ -464,12 +421,12 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    )
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpNextSibling",
 | 
			
		||||
      "<C-J>",
 | 
			
		||||
      "<A-J>",
 | 
			
		||||
      NerdAction.ToIj("Tree-selectNextSibling"),
 | 
			
		||||
    )
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "NERDTreeMapJumpPrevSibling",
 | 
			
		||||
      "<C-K>",
 | 
			
		||||
      "<A-K>",
 | 
			
		||||
      NerdAction.ToIj("Tree-selectPreviousSibling"),
 | 
			
		||||
    )
 | 
			
		||||
    registerCommand(
 | 
			
		||||
@@ -488,18 +445,17 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "/",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        NerdDispatcher.getInstance(project).waitForSearch = true
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        tree.getUserData(KEY)?.waitForSearch = true
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    registerCommand(
 | 
			
		||||
      "<ESC>",
 | 
			
		||||
      NerdAction.Code { project, _, _ ->
 | 
			
		||||
        val instance = NerdDispatcher.getInstance(project)
 | 
			
		||||
        if (instance.waitForSearch) {
 | 
			
		||||
          instance.waitForSearch = false
 | 
			
		||||
        }
 | 
			
		||||
      NerdAction.Code { _, _, e ->
 | 
			
		||||
        val tree = getTree(e) ?: return@Code
 | 
			
		||||
        tree.getUserData(KEY)?.waitForSearch = false
 | 
			
		||||
      },
 | 
			
		||||
    )
 | 
			
		||||
    
 | 
			
		||||
@@ -511,14 +467,9 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    const val pluginName = "NERDTree"
 | 
			
		||||
 | 
			
		||||
  object Util {
 | 
			
		||||
    internal val monitor = Any()
 | 
			
		||||
    internal var commandsRegistered = false
 | 
			
		||||
 | 
			
		||||
    private val LOG = logger<NerdTree>()
 | 
			
		||||
 | 
			
		||||
    fun callAction(editor: VimEditor?, name: String, context: ExecutionContext) {
 | 
			
		||||
      val action = ActionManager.getInstance().getAction(name) ?: run {
 | 
			
		||||
        VimPlugin.showMessage(MessageHelper.message("action.not.found.0", name))
 | 
			
		||||
@@ -533,45 +484,60 @@ internal class NerdTree : VimExtension {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
    private fun addCommand(alias: String, handler: CommandAliasHandler) {
 | 
			
		||||
      VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
 | 
			
		||||
    }
 | 
			
		||||
  companion object {
 | 
			
		||||
    const val pluginName = "NERDTree"
 | 
			
		||||
    private val LOG = logger<NerdTree>()
 | 
			
		||||
    private val KEY = Key.create<NerdDispatcher>("IdeaVim-NerdTree-Dispatcher")
 | 
			
		||||
 | 
			
		||||
    private fun registerCommand(variable: String, default: String, action: NerdAction) {
 | 
			
		||||
      val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
 | 
			
		||||
      val mappings = if (variableValue is VimString) {
 | 
			
		||||
        variableValue.value
 | 
			
		||||
      } else {
 | 
			
		||||
        default
 | 
			
		||||
      }
 | 
			
		||||
      actionsRoot.addLeafs(mappings, action)
 | 
			
		||||
    }
 | 
			
		||||
    fun installDispatcher(component: JComponent) {
 | 
			
		||||
      if (component.getUserData(KEY) != null) return
 | 
			
		||||
 | 
			
		||||
    private fun registerCommand(default: String, action: NerdAction) {
 | 
			
		||||
      actionsRoot.addLeafs(default, action)
 | 
			
		||||
    }
 | 
			
		||||
      val dispatcher = NerdDispatcher()
 | 
			
		||||
      component.putUserData(KEY, dispatcher)
 | 
			
		||||
 | 
			
		||||
    private val actionsRoot: RootNode<NerdAction> = RootNode()
 | 
			
		||||
    private var currentNode: CommandPartNode<NerdAction> = actionsRoot
 | 
			
		||||
 | 
			
		||||
    private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
 | 
			
		||||
      return if (node is CommandPartNode<NerdAction>) {
 | 
			
		||||
        val res = node.keys.toMutableSet()
 | 
			
		||||
        res += node.values.map { collectShortcuts(it) }.flatten()
 | 
			
		||||
        res
 | 
			
		||||
      } else {
 | 
			
		||||
        emptySet()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun installDispatcher(project: Project) {
 | 
			
		||||
      val dispatcher = NerdDispatcher.getInstance(project)
 | 
			
		||||
      val shortcuts = collectShortcuts(actionsRoot).map { RequiredShortcut(it, MappingOwner.Plugin.get(pluginName)) }
 | 
			
		||||
      dispatcher.registerCustomShortcutSet(
 | 
			
		||||
        KeyGroup.toShortcutSet(shortcuts),
 | 
			
		||||
        (ProjectView.getInstance(project) as ProjectViewImpl).component,
 | 
			
		||||
      )
 | 
			
		||||
      dispatcher.registerCustomShortcutSet(KeyGroup.toShortcutSet(shortcuts), component)
 | 
			
		||||
 | 
			
		||||
      SpeedSearchSupply.getSupply(component, true)?.addChangeListener {
 | 
			
		||||
        dispatcher.waitForSearch = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun addCommand(alias: String, handler: CommandAliasHandler) {
 | 
			
		||||
  VimPlugin.getCommand().setAlias(alias, CommandAlias.Call(0, -1, alias, handler))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun registerCommand(variable: String, default: String, action: NerdAction) {
 | 
			
		||||
  val variableValue = VimPlugin.getVariableService().getGlobalVariableValue(variable)
 | 
			
		||||
  val mappings = if (variableValue is VimString) {
 | 
			
		||||
    variableValue.value
 | 
			
		||||
  } else {
 | 
			
		||||
    default
 | 
			
		||||
  }
 | 
			
		||||
  actionsRoot.addLeafs(mappings, action)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun registerCommand(default: String, action: NerdAction) {
 | 
			
		||||
  actionsRoot.addLeafs(default, action)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
private val actionsRoot: RootNode<NerdAction> = RootNode()
 | 
			
		||||
private var currentNode: CommandPartNode<NerdAction> = actionsRoot
 | 
			
		||||
private fun collectShortcuts(node: Node<NerdAction>): Set<KeyStroke> {
 | 
			
		||||
  return if (node is CommandPartNode<NerdAction>) {
 | 
			
		||||
    val res = node.keys.toMutableSet()
 | 
			
		||||
    res += node.values.map { collectShortcuts(it) }.flatten()
 | 
			
		||||
    res
 | 
			
		||||
  } else {
 | 
			
		||||
    emptySet()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun getTree(e: AnActionEvent): JTree? {
 | 
			
		||||
  return e.dataContext.getData(PlatformCoreDataKeys.CONTEXT_COMPONENT) as? JTree
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.nerdtree
 | 
			
		||||
 | 
			
		||||
import com.intellij.ide.ApplicationInitializedListener
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.util.ui.StartupUiUtil
 | 
			
		||||
import kotlinx.coroutines.CoroutineScope
 | 
			
		||||
import java.awt.AWTEvent
 | 
			
		||||
import java.awt.event.FocusEvent
 | 
			
		||||
import javax.swing.JTree
 | 
			
		||||
 | 
			
		||||
@Suppress("UnstableApiUsage")
 | 
			
		||||
internal class NerdTreeApplicationListener : ApplicationInitializedListener {
 | 
			
		||||
  override suspend fun execute(asyncScope: CoroutineScope) {
 | 
			
		||||
    StartupUiUtil.addAwtListener(::handleEvent, AWTEvent.FOCUS_EVENT_MASK, ApplicationManager.getApplication().getService(NerdTreeDisposableService::class.java))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun handleEvent(event: AWTEvent) {
 | 
			
		||||
    if (event is FocusEvent && event.id == FocusEvent.FOCUS_GAINED) {
 | 
			
		||||
      val source = event.source
 | 
			
		||||
      if (source is JTree) {
 | 
			
		||||
        NerdTree.installDispatcher(source)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,9 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.nerdtree
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class NerdTreeDisposableService : Disposable {
 | 
			
		||||
  override fun dispose() {}
 | 
			
		||||
}
 | 
			
		||||
@@ -9,10 +9,11 @@
 | 
			
		||||
package com.maddyhome.idea.vim.extension.paragraphmotion
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
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.getLineEndForOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.normalizeOffset
 | 
			
		||||
import com.maddyhome.idea.vim.command.MappingMode
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
			
		||||
@@ -20,8 +21,10 @@ import com.maddyhome.idea.vim.extension.VimExtension
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimForEachCaret
 | 
			
		||||
import com.maddyhome.idea.vim.key.MappingOwner
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import javax.swing.KeyStroke
 | 
			
		||||
 | 
			
		||||
internal class ParagraphMotion : VimExtension {
 | 
			
		||||
  override fun getName(): String = "vim-paragraph-motion"
 | 
			
		||||
@@ -30,8 +33,8 @@ internal class ParagraphMotion : VimExtension {
 | 
			
		||||
    VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), owner, ParagraphMotionHandler(1), false)
 | 
			
		||||
    VimExtensionFacade.putExtensionHandlerMapping(MappingMode.NXO, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), owner, ParagraphMotionHandler(-1), false)
 | 
			
		||||
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true)
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true)
 | 
			
		||||
    putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("}"), owner, injector.parser.parseKeys("<Plug>(ParagraphNextMotion)"), true)
 | 
			
		||||
    putKeyMappingIfMissingFromAndToKeys(MappingMode.NXO, injector.parser.parseKeys("{"), owner, injector.parser.parseKeys("<Plug>(ParagraphPrevMotion)"), true)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class ParagraphMotionHandler(private val count: Int) : ExtensionHandler {
 | 
			
		||||
@@ -45,7 +48,21 @@ internal class ParagraphMotion : VimExtension {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun moveCaretToNextParagraph(editor: VimEditor, caret: Caret, count: Int): Int? {
 | 
			
		||||
      return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)?.let(editor::getLineEndForOffset)
 | 
			
		||||
      return injector.searchHelper.findNextParagraph(editor, caret.vim, count, true)
 | 
			
		||||
        ?.let { editor.normalizeOffset(it, true) }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // For VIM-3306
 | 
			
		||||
  @Suppress("SameParameterValue")
 | 
			
		||||
  private fun putKeyMappingIfMissingFromAndToKeys(
 | 
			
		||||
    modes: Set<MappingMode>,
 | 
			
		||||
    fromKeys: List<KeyStroke>,
 | 
			
		||||
    pluginOwner: MappingOwner,
 | 
			
		||||
    toKeys: List<KeyStroke>,
 | 
			
		||||
    recursive: Boolean,
 | 
			
		||||
  ) {
 | 
			
		||||
    val filteredModes = modes.filterNotTo(HashSet()) { VimPlugin.getKey().hasmapfrom(it, fromKeys) }
 | 
			
		||||
    putKeyMappingIfMissing(filteredModes, fromKeys, pluginOwner, toKeys, recursive)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,30 +8,26 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.extension.replacewithregister
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
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.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.CHARACTER_WISE
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isLine
 | 
			
		||||
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
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putKeyMappingIfMissing
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.setOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.extension.exportOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimSelection
 | 
			
		||||
import com.maddyhome.idea.vim.helper.exitVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
@@ -39,6 +35,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.isLine
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
			
		||||
import org.jetbrains.annotations.NonNls
 | 
			
		||||
 | 
			
		||||
internal class ReplaceWithRegister : VimExtension {
 | 
			
		||||
@@ -53,17 +53,19 @@ internal class ReplaceWithRegister : VimExtension {
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_OPERATOR), true)
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("grr"), owner, injector.parser.parseKeys(RWR_LINE), true)
 | 
			
		||||
    putKeyMappingIfMissing(MappingMode.X, injector.parser.parseKeys("gr"), owner, injector.parser.parseKeys(RWR_VISUAL), true)
 | 
			
		||||
 | 
			
		||||
    VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class RwrVisual : ExtensionHandler {
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      val typeInEditor = editor.mode.selectionType ?: CHARACTER_WISE
 | 
			
		||||
      val typeInEditor = editor.mode.selectionType ?: SelectionType.CHARACTER_WISE
 | 
			
		||||
      editor.sortedCarets().forEach { caret ->
 | 
			
		||||
        val selectionStart = caret.selectionStart
 | 
			
		||||
        val selectionEnd = caret.selectionEnd
 | 
			
		||||
 | 
			
		||||
        val visualSelection = caret to VimSelection.create(selectionStart, selectionEnd - 1, typeInEditor, editor)
 | 
			
		||||
        doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor))
 | 
			
		||||
        doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), typeInEditor))
 | 
			
		||||
      }
 | 
			
		||||
      editor.exitVisualMode()
 | 
			
		||||
    }
 | 
			
		||||
@@ -73,7 +75,7 @@ internal class ReplaceWithRegister : VimExtension {
 | 
			
		||||
    override val isRepeatable: Boolean = true
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      setOperatorFunction(Operator())
 | 
			
		||||
      injector.globalOptions().operatorfunc = OPERATOR_FUNC
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -91,7 +93,7 @@ internal class ReplaceWithRegister : VimExtension {
 | 
			
		||||
        val visualSelection = caret to VimSelection.create(lineStart, lineEnd, SelectionType.LINE_WISE, editor)
 | 
			
		||||
        caretsAndSelections += visualSelection
 | 
			
		||||
 | 
			
		||||
        doReplace(editor.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE))
 | 
			
		||||
        doReplace(editor.ij, context.ij, caret, PutData.VisualSelection(mapOf(visualSelection), SelectionType.LINE_WISE))
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      editor.sortedCarets().forEach { caret ->
 | 
			
		||||
@@ -112,14 +114,14 @@ internal class ReplaceWithRegister : VimExtension {
 | 
			
		||||
          editor.primaryCaret() to VimSelection.create(
 | 
			
		||||
            range.startOffset,
 | 
			
		||||
            range.endOffset - 1,
 | 
			
		||||
            selectionType ?: CHARACTER_WISE,
 | 
			
		||||
            selectionType ?: SelectionType.CHARACTER_WISE,
 | 
			
		||||
            editor,
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
        selectionType ?: CHARACTER_WISE,
 | 
			
		||||
        selectionType ?: SelectionType.CHARACTER_WISE,
 | 
			
		||||
      )
 | 
			
		||||
      // todo multicaret
 | 
			
		||||
      doReplace(ijEditor, editor.primaryCaret(), visualSelection)
 | 
			
		||||
      doReplace(ijEditor, context.ij, editor.primaryCaret(), visualSelection)
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -132,52 +134,49 @@ internal class ReplaceWithRegister : VimExtension {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    @NonNls
 | 
			
		||||
    private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
 | 
			
		||||
 | 
			
		||||
    @NonNls
 | 
			
		||||
    private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
 | 
			
		||||
 | 
			
		||||
    @NonNls
 | 
			
		||||
    private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
 | 
			
		||||
 | 
			
		||||
    private fun doReplace(editor: Editor, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
 | 
			
		||||
      val registerGroup = injector.registerGroup
 | 
			
		||||
      val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
 | 
			
		||||
      val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
 | 
			
		||||
 | 
			
		||||
      var usedType = savedRegister.type
 | 
			
		||||
      var usedText = savedRegister.text
 | 
			
		||||
      if (usedType.isLine && usedText?.endsWith('\n') == true) {
 | 
			
		||||
        // Code from original plugin implementation. Correct text for linewise selected text
 | 
			
		||||
        usedText = usedText.dropLast(1)
 | 
			
		||||
        usedType = SelectionType.CHARACTER_WISE
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData, savedRegister.name)
 | 
			
		||||
 | 
			
		||||
      val putData = PutData(
 | 
			
		||||
        textData,
 | 
			
		||||
        visualSelection,
 | 
			
		||||
        1,
 | 
			
		||||
        insertTextBeforeCaret = true,
 | 
			
		||||
        rawIndent = true,
 | 
			
		||||
        caretAfterInsertedText = false,
 | 
			
		||||
        putToLine = -1,
 | 
			
		||||
      )
 | 
			
		||||
      ClipboardOptionHelper.IdeaputDisabler().use {
 | 
			
		||||
        VimPlugin.getPut().putText(
 | 
			
		||||
          IjVimEditor(editor),
 | 
			
		||||
          injector.executionContextManager.onEditor(editor.vim),
 | 
			
		||||
          putData,
 | 
			
		||||
          operatorArguments = OperatorArguments(
 | 
			
		||||
            editor.vimStateMachine?.isOperatorPending ?: false,
 | 
			
		||||
            0,
 | 
			
		||||
            editor.vim.mode,
 | 
			
		||||
          ),
 | 
			
		||||
          saveToRegister = false
 | 
			
		||||
        )
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    @NonNls private const val RWR_OPERATOR = "<Plug>ReplaceWithRegisterOperator"
 | 
			
		||||
    @NonNls private const val RWR_LINE = "<Plug>ReplaceWithRegisterLine"
 | 
			
		||||
    @NonNls private const val RWR_VISUAL = "<Plug>ReplaceWithRegisterVisual"
 | 
			
		||||
    @NonNls private const val OPERATOR_FUNC = "ReplaceWithRegisterOperatorFunc"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun doReplace(editor: Editor, context: DataContext, caret: ImmutableVimCaret, visualSelection: PutData.VisualSelection) {
 | 
			
		||||
  val registerGroup = injector.registerGroup
 | 
			
		||||
  val lastRegisterChar = if (editor.caretModel.caretCount == 1) registerGroup.currentRegister else registerGroup.getCurrentRegisterForMulticaret()
 | 
			
		||||
  val savedRegister = caret.registerStorage.getRegister(lastRegisterChar) ?: return
 | 
			
		||||
 | 
			
		||||
  var usedType = savedRegister.type
 | 
			
		||||
  var usedText = savedRegister.text
 | 
			
		||||
  if (usedType.isLine && usedText?.endsWith('\n') == true) {
 | 
			
		||||
    // Code from original plugin implementation. Correct text for linewise selected text
 | 
			
		||||
    usedText = usedText.dropLast(1)
 | 
			
		||||
    usedType = SelectionType.CHARACTER_WISE
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val textData = PutData.TextData(usedText, usedType, savedRegister.transferableData, savedRegister.name)
 | 
			
		||||
 | 
			
		||||
  val putData = PutData(
 | 
			
		||||
    textData,
 | 
			
		||||
    visualSelection,
 | 
			
		||||
    1,
 | 
			
		||||
    insertTextBeforeCaret = true,
 | 
			
		||||
    rawIndent = true,
 | 
			
		||||
    caretAfterInsertedText = false,
 | 
			
		||||
    putToLine = -1,
 | 
			
		||||
  )
 | 
			
		||||
  val vimEditor = editor.vim
 | 
			
		||||
  ClipboardOptionHelper.IdeaputDisabler().use {
 | 
			
		||||
    VimPlugin.getPut().putText(
 | 
			
		||||
      vimEditor,
 | 
			
		||||
      context.vim,
 | 
			
		||||
      putData,
 | 
			
		||||
      operatorArguments = OperatorArguments(
 | 
			
		||||
        editor.vimStateMachine?.isOperatorPending(vimEditor.mode) ?: false,
 | 
			
		||||
        0,
 | 
			
		||||
        editor.vim.mode,
 | 
			
		||||
      ),
 | 
			
		||||
      saveToRegister = false
 | 
			
		||||
    )
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -36,6 +36,7 @@ import com.maddyhome.idea.vim.helper.StrictMode
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import java.awt.Font
 | 
			
		||||
import java.awt.event.KeyEvent
 | 
			
		||||
import java.util.*
 | 
			
		||||
import javax.swing.Timer
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -48,17 +49,20 @@ internal class IdeaVimSneakExtension : VimExtension {
 | 
			
		||||
 | 
			
		||||
  override fun init() {
 | 
			
		||||
    val highlightHandler = HighlightHandler()
 | 
			
		||||
    mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD))
 | 
			
		||||
    mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD))
 | 
			
		||||
    mapToFunctionAndProvideKeys("s", SneakHandler(highlightHandler, Direction.FORWARD), MappingMode.NXO)
 | 
			
		||||
 | 
			
		||||
    // vim-sneak uses `Z` for visual mode because `S` conflict with vim-sneak plugin VIM-3330
 | 
			
		||||
    mapToFunctionAndProvideKeys("S", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.NO)
 | 
			
		||||
    mapToFunctionAndProvideKeys("Z", SneakHandler(highlightHandler, Direction.BACKWARD), MappingMode.X)
 | 
			
		||||
 | 
			
		||||
    // workaround to support ; and , commands
 | 
			
		||||
    mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"))
 | 
			
		||||
    mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"))
 | 
			
		||||
    mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"))
 | 
			
		||||
    mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"))
 | 
			
		||||
    mapToFunctionAndProvideKeys("f", SneakMemoryHandler("f"), MappingMode.NXO)
 | 
			
		||||
    mapToFunctionAndProvideKeys("F", SneakMemoryHandler("F"), MappingMode.NXO)
 | 
			
		||||
    mapToFunctionAndProvideKeys("t", SneakMemoryHandler("t"), MappingMode.NXO)
 | 
			
		||||
    mapToFunctionAndProvideKeys("T", SneakMemoryHandler("T"), MappingMode.NXO)
 | 
			
		||||
 | 
			
		||||
    mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL))
 | 
			
		||||
    mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE))
 | 
			
		||||
    mapToFunctionAndProvideKeys(";", SneakRepeatHandler(highlightHandler, RepeatDirection.IDENTICAL), MappingMode.NXO)
 | 
			
		||||
    mapToFunctionAndProvideKeys(",", SneakRepeatHandler(highlightHandler, RepeatDirection.REVERSE), MappingMode.NXO)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class SneakHandler(
 | 
			
		||||
@@ -114,7 +118,7 @@ internal class IdeaVimSneakExtension : VimExtension {
 | 
			
		||||
    var lastSymbols: String = ""
 | 
			
		||||
    fun jumpTo(editor: VimEditor, charone: Char, chartwo: Char, sneakDirection: Direction): TextRange? {
 | 
			
		||||
      val caret = editor.primaryCaret()
 | 
			
		||||
      val position = caret.offset.point
 | 
			
		||||
      val position = caret.offset
 | 
			
		||||
      val chars = editor.text()
 | 
			
		||||
      val foundPosition = sneakDirection.findBiChar(editor, chars, position, charone, chartwo)
 | 
			
		||||
      if (foundPosition != null) {
 | 
			
		||||
@@ -273,16 +277,18 @@ internal class IdeaVimSneakExtension : VimExtension {
 | 
			
		||||
 * Map some <Plug>(keys) command to given handler
 | 
			
		||||
 *  and create mapping to <Plug>(prefix)[keys]
 | 
			
		||||
 */
 | 
			
		||||
private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: ExtensionHandler) {
 | 
			
		||||
private fun VimExtension.mapToFunctionAndProvideKeys(
 | 
			
		||||
  keys: String, handler: ExtensionHandler, mappingModes: EnumSet<MappingMode>
 | 
			
		||||
) {
 | 
			
		||||
  VimExtensionFacade.putExtensionHandlerMapping(
 | 
			
		||||
    MappingMode.NXO,
 | 
			
		||||
    mappingModes,
 | 
			
		||||
    injector.parser.parseKeys(command(keys)),
 | 
			
		||||
    owner,
 | 
			
		||||
    handler,
 | 
			
		||||
    false
 | 
			
		||||
  )
 | 
			
		||||
  VimExtensionFacade.putExtensionHandlerMapping(
 | 
			
		||||
    MappingMode.NXO,
 | 
			
		||||
    mappingModes,
 | 
			
		||||
    injector.parser.parseKeys(commandFromOriginalPlugin(keys)),
 | 
			
		||||
    owner,
 | 
			
		||||
    handler,
 | 
			
		||||
@@ -294,17 +300,17 @@ private fun VimExtension.mapToFunctionAndProvideKeys(keys: String, handler: Exte
 | 
			
		||||
  //  - The shortcut should not be registered if any of these mappings is overridden in .ideavimrc
 | 
			
		||||
  //  - The shortcut should not be registered if some other shortcut for this key exists
 | 
			
		||||
  val fromKeys = injector.parser.parseKeys(keys)
 | 
			
		||||
  val filteredModes = MappingMode.NXO.filterNotTo(HashSet()) {
 | 
			
		||||
  val filteredModes = mappingModes.filterNotTo(HashSet()) {
 | 
			
		||||
    VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(command(keys)))
 | 
			
		||||
  }
 | 
			
		||||
  val filteredModes2 = MappingMode.NXO.filterNotTo(HashSet()) {
 | 
			
		||||
  val filteredModes2 = mappingModes.filterNotTo(HashSet()) {
 | 
			
		||||
    VimPlugin.getKey().hasmapto(it, injector.parser.parseKeys(commandFromOriginalPlugin(keys)))
 | 
			
		||||
  }
 | 
			
		||||
  val filteredFromModes = MappingMode.NXO.filterNotTo(HashSet()) {
 | 
			
		||||
  val filteredFromModes = mappingModes.filterNotTo(HashSet()) {
 | 
			
		||||
    injector.keyGroup.hasmapfrom(it, fromKeys)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val doubleFiltered = MappingMode.NXO
 | 
			
		||||
  val doubleFiltered = mappingModes
 | 
			
		||||
    .filter { it in filteredModes2 && it in filteredModes && it in filteredFromModes }
 | 
			
		||||
    .toSet()
 | 
			
		||||
  putKeyMapping(doubleFiltered, fromKeys, owner, injector.parser.parseKeys(command(keys)), true)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.extension.surround
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.application.runWriteAction
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
@@ -17,6 +18,7 @@ import com.maddyhome.idea.vim.api.VimChangeGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.endsWithNewLine
 | 
			
		||||
import com.maddyhome.idea.vim.api.getLeadingCharacterOffset
 | 
			
		||||
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.command.MappingMode
 | 
			
		||||
@@ -24,14 +26,16 @@ import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.extension.ExtensionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtension
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.executeNormalWithoutMapping
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.getRegisterForCaret
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputKeyStroke
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.inputString
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtensionFacade.putExtensionHandlerMapping
 | 
			
		||||
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.extension.exportOperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.group.findBlockRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.runWithEveryCaretAndRestore
 | 
			
		||||
import com.maddyhome.idea.vim.key.OperatorFunction
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimCaret
 | 
			
		||||
@@ -42,7 +46,6 @@ 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
 | 
			
		||||
@@ -78,13 +81,15 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      putKeyMappingIfMissing(MappingMode.N, injector.parser.parseKeys("ds"), owner, injector.parser.parseKeys("<Plug>DSurround"), true)
 | 
			
		||||
      putKeyMappingIfMissing(MappingMode.XO, injector.parser.parseKeys("S"), owner, injector.parser.parseKeys("<Plug>VSurround"), true)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    VimExtensionFacade.exportOperatorFunction(OPERATOR_FUNC, Operator(supportsMultipleCursors = false, count = 1)) // TODO
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class YSurroundHandler : ExtensionHandler {
 | 
			
		||||
    override val isRepeatable = true
 | 
			
		||||
 | 
			
		||||
    override fun execute(editor: VimEditor, context: ExecutionContext, operatorArguments: OperatorArguments) {
 | 
			
		||||
      setOperatorFunction(Operator(supportsMultipleCursors = false, count = 1)) // TODO
 | 
			
		||||
      injector.globalOptions().operatorfunc = OPERATOR_FUNC
 | 
			
		||||
      executeNormalWithoutMapping(injector.parser.parseKeys("g@"), editor.ij)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -96,7 +101,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      val ijEditor = editor.ij
 | 
			
		||||
      val c = getChar(ijEditor)
 | 
			
		||||
      if (c.code == 0) return
 | 
			
		||||
      val pair = getOrInputPair(c, ijEditor) ?: return
 | 
			
		||||
      val pair = getOrInputPair(c, ijEditor, context.ij) ?: return
 | 
			
		||||
 | 
			
		||||
      editor.forEachCaret {
 | 
			
		||||
        val line = it.getBufferPosition().line
 | 
			
		||||
@@ -146,7 +151,7 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
      val charTo = getChar(editor.ij)
 | 
			
		||||
      if (charTo.code == 0) return
 | 
			
		||||
 | 
			
		||||
      val newSurround = getOrInputPair(charTo, editor.ij) ?: return
 | 
			
		||||
      val newSurround = getOrInputPair(charTo, editor.ij, context.ij) ?: return
 | 
			
		||||
      runWriteAction { change(editor, context, charFrom, newSurround) }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -223,12 +228,12 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
        val searchHelper = injector.searchHelper
 | 
			
		||||
        return when (char) {
 | 
			
		||||
          't' -> searchHelper.findBlockTagRange(editor, caret, 1, true)
 | 
			
		||||
          '(', ')', 'b' -> searchHelper.findBlockRange(editor, caret, '(', 1, true)
 | 
			
		||||
          '[', ']' -> searchHelper.findBlockRange(editor, caret, '[', 1, true)
 | 
			
		||||
          '{', '}', 'B' -> searchHelper.findBlockRange(editor, caret, '{', 1, true)
 | 
			
		||||
          '<', '>' -> searchHelper.findBlockRange(editor, caret, '<', 1, true)
 | 
			
		||||
          '(', ')', 'b' -> findBlockRange(editor, caret, '(', 1, true)
 | 
			
		||||
          '[', ']' -> findBlockRange(editor, caret, '[', 1, true)
 | 
			
		||||
          '{', '}', 'B' -> findBlockRange(editor, caret, '{', 1, true)
 | 
			
		||||
          '<', '>' -> findBlockRange(editor, caret, '<', 1, true)
 | 
			
		||||
          '`', '\'', '"' -> {
 | 
			
		||||
            val caretOffset = caret.offset.point
 | 
			
		||||
            val caretOffset = caret.offset
 | 
			
		||||
            val text = editor.text()
 | 
			
		||||
            if (text.getOrNull(caretOffset - 1) == char && text.getOrNull(caretOffset) == char) {
 | 
			
		||||
              TextRange(caretOffset - 1, caretOffset + 1)
 | 
			
		||||
@@ -265,23 +270,23 @@ internal class VimSurroundExtension : VimExtension {
 | 
			
		||||
 | 
			
		||||
  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)
 | 
			
		||||
      val ijEditor = vimEditor.ij
 | 
			
		||||
      val c = getChar(ijEditor)
 | 
			
		||||
      if (c.code == 0) return true
 | 
			
		||||
 | 
			
		||||
      val pair = getOrInputPair(c, editor) ?: return false
 | 
			
		||||
      val pair = getOrInputPair(c, ijEditor, context.ij) ?: return false
 | 
			
		||||
 | 
			
		||||
      runWriteAction {
 | 
			
		||||
        val change = VimPlugin.getChange()
 | 
			
		||||
        if (supportsMultipleCursors) {
 | 
			
		||||
          editor.runWithEveryCaretAndRestore {
 | 
			
		||||
            applyOnce(editor, change, pair, count)
 | 
			
		||||
          ijEditor.runWithEveryCaretAndRestore {
 | 
			
		||||
            applyOnce(ijEditor, change, pair, count)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        else {
 | 
			
		||||
          applyOnce(editor, change, pair, count)
 | 
			
		||||
          applyOnce(ijEditor, change, pair, count)
 | 
			
		||||
          // Jump back to start
 | 
			
		||||
          executeNormalWithoutMapping(injector.parser.parseKeys("`["), editor)
 | 
			
		||||
          executeNormalWithoutMapping(injector.parser.parseKeys("`["), ijEditor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
@@ -315,7 +320,9 @@ private val LOG = logger<VimSurroundExtension>()
 | 
			
		||||
 | 
			
		||||
private const val REGISTER = '"'
 | 
			
		||||
 | 
			
		||||
private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
private const val OPERATOR_FUNC = "SurroundOperatorFunc"
 | 
			
		||||
 | 
			
		||||
    private val tagNameAndAttributesCapturePattern = "(\\S+)([^>]*)>".toPattern()
 | 
			
		||||
 | 
			
		||||
private val SURROUND_PAIRS = mapOf(
 | 
			
		||||
  'b' to ("(" to ")"),
 | 
			
		||||
@@ -341,8 +348,8 @@ private fun getSurroundPair(c: Char): Pair<String, String>? = if (c in SURROUND_
 | 
			
		||||
  null
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
			
		||||
  val tagInput = inputString(editor, "<", '>')
 | 
			
		||||
private fun inputTagPair(editor: Editor, context: DataContext): Pair<String, String>? {
 | 
			
		||||
  val tagInput = inputString(editor, context, "<", '>')
 | 
			
		||||
  val matcher = tagNameAndAttributesCapturePattern.matcher(tagInput)
 | 
			
		||||
  return if (matcher.find()) {
 | 
			
		||||
    val tagName = matcher.group(1)
 | 
			
		||||
@@ -355,17 +362,18 @@ private fun inputTagPair(editor: Editor): Pair<String, String>? {
 | 
			
		||||
 | 
			
		||||
private fun inputFunctionName(
 | 
			
		||||
  editor: Editor,
 | 
			
		||||
  context: DataContext,
 | 
			
		||||
  withInternalSpaces: Boolean,
 | 
			
		||||
): Pair<String, String>? {
 | 
			
		||||
  val functionNameInput = inputString(editor, "function: ", null)
 | 
			
		||||
  val functionNameInput = inputString(editor, context, "function: ", null)
 | 
			
		||||
  if (functionNameInput.isEmpty()) return null
 | 
			
		||||
  return if (withInternalSpaces) "$functionNameInput( " to " )" else "$functionNameInput(" to ")"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun getOrInputPair(c: Char, editor: Editor): Pair<String, String>? = when (c) {
 | 
			
		||||
  '<', 't' -> inputTagPair(editor)
 | 
			
		||||
  'f' -> inputFunctionName(editor, false)
 | 
			
		||||
  'F' -> inputFunctionName(editor, true)
 | 
			
		||||
private fun getOrInputPair(c: Char, editor: Editor, context: DataContext): Pair<String, String>? = when (c) {
 | 
			
		||||
  '<', 't' -> inputTagPair(editor, context)
 | 
			
		||||
  'f' -> inputFunctionName(editor, context, false)
 | 
			
		||||
  'F' -> inputFunctionName(editor, context, true)
 | 
			
		||||
  else -> getSurroundPair(c)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -138,7 +138,7 @@ public class VimTextObjEntireExtension implements VimExtension {
 | 
			
		||||
 | 
			
		||||
      final EntireTextObjectHandler textObjectHandler = new EntireTextObjectHandler(ignoreLeadingAndTrailing);
 | 
			
		||||
      //noinspection DuplicatedCode
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending()) {
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending(editor.getMode())) {
 | 
			
		||||
        ((IjVimEditor) editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
 | 
			
		||||
          final TextRange range = textObjectHandler.getRange(editor, new IjVimCaret(caret), context, count, 0);
 | 
			
		||||
          if (range != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -267,7 +267,7 @@ public class VimIndentObject implements VimExtension {
 | 
			
		||||
 | 
			
		||||
      final IndentObjectHandler textObjectHandler = new IndentObjectHandler(includeAbove, includeBelow);
 | 
			
		||||
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending()) {
 | 
			
		||||
      if (!vimStateMachine.isOperatorPending(editor.getMode())) {
 | 
			
		||||
        ((IjVimEditor)editor).getEditor().getCaretModel().runForEachCaret((Caret caret) -> {
 | 
			
		||||
          final TextRange range = textObjectHandler.getRange(vimEditor, new IjVimCaret(caret), context, count, 0);
 | 
			
		||||
          if (range != null) {
 | 
			
		||||
 
 | 
			
		||||
@@ -68,10 +68,11 @@ 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.regexp.VimRegex
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.match.VimMatchResult
 | 
			
		||||
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
 | 
			
		||||
@@ -196,7 +197,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    val allowWrap = injector.options(editor).whichwrap.contains("~")
 | 
			
		||||
    var motion = injector.motion.getHorizontalMotion(editor, caret, count, true, allowWrap)
 | 
			
		||||
    if (motion is Motion.Error) return false
 | 
			
		||||
    changeCase(editor, caret, caret.offset.point, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
 | 
			
		||||
    changeCase(editor, caret, caret.offset, (motion as AbsoluteOffset).offset, CharacterHelper.CASE_TOGGLE)
 | 
			
		||||
    motion = injector.motion.getHorizontalMotion(
 | 
			
		||||
      editor,
 | 
			
		||||
      caret,
 | 
			
		||||
@@ -234,8 +235,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
      }
 | 
			
		||||
      val lineLength = editor.lineLength(line)
 | 
			
		||||
      if (column < VimMotionGroupBase.LAST_COLUMN && lineLength < column) {
 | 
			
		||||
        val pad =
 | 
			
		||||
          EditorHelper.pad((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context, line, column)
 | 
			
		||||
        val pad = EditorHelper.pad((editor as IjVimEditor).editor, line, column)
 | 
			
		||||
        val offset = editor.getLineEndOffset(line)
 | 
			
		||||
        insertText(editor, caret, offset, pad)
 | 
			
		||||
      }
 | 
			
		||||
@@ -394,7 +394,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    context: ExecutionContext,
 | 
			
		||||
    range: TextRange,
 | 
			
		||||
  ) {
 | 
			
		||||
    val startPos = editor.offsetToBufferPosition(caret.offset.point)
 | 
			
		||||
    val startPos = editor.offsetToBufferPosition(caret.offset)
 | 
			
		||||
    val startOffset = editor.getLineStartForOffset(range.startOffset)
 | 
			
		||||
    val endOffset = editor.getLineEndForOffset(range.endOffset)
 | 
			
		||||
    val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
@@ -450,7 +450,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    dir: Int,
 | 
			
		||||
    operatorArguments: OperatorArguments,
 | 
			
		||||
  ) {
 | 
			
		||||
    val start = caret.offset.point
 | 
			
		||||
    val start = caret.offset
 | 
			
		||||
    val end = injector.motion.moveCaretToRelativeLineEnd(editor, caret, lines - 1, true)
 | 
			
		||||
    indentRange(editor, caret, context, TextRange(start, end), 1, dir, operatorArguments)
 | 
			
		||||
  }
 | 
			
		||||
@@ -484,7 +484,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
 | 
			
		||||
    // Remember the current caret column
 | 
			
		||||
    val intendedColumn = caret.vimLastColumn
 | 
			
		||||
    val indentConfig = create((editor as IjVimEditor).editor, (context as IjEditorExecutionContext).context)
 | 
			
		||||
    val indentConfig = create((editor as IjVimEditor).editor)
 | 
			
		||||
    val sline = editor.offsetToBufferPosition(range.startOffset).line
 | 
			
		||||
    val endLogicalPosition = editor.offsetToBufferPosition(range.endOffset)
 | 
			
		||||
    val eline = if (endLogicalPosition.column == 0) max((endLogicalPosition.line - 1).toDouble(), 0.0)
 | 
			
		||||
@@ -531,7 +531,7 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
        val soff = editor.getLineStartOffset(l)
 | 
			
		||||
        val eoff = editor.getLineEndOffset(l, true)
 | 
			
		||||
        val woff = injector.motion.moveCaretToLineStartSkipLeading(editor, l)
 | 
			
		||||
        val col = editor.offsetToVisualPosition(woff).column
 | 
			
		||||
        val col = editor.offsetToBufferPosition(woff).column
 | 
			
		||||
        val limit = max(0.0, (col + dir * indentConfig.getTotalIndent(count)).toDouble())
 | 
			
		||||
          .toInt()
 | 
			
		||||
        if (col > 0 || soff != eoff) {
 | 
			
		||||
@@ -573,48 +573,62 @@ public class ChangeGroup : VimChangeGroupBase() {
 | 
			
		||||
    }
 | 
			
		||||
    val startOffset = editor.getLineStartOffset(startLine)
 | 
			
		||||
    val endOffset = editor.getLineEndOffset(endLine)
 | 
			
		||||
    return sortTextRange(editor, caret, startOffset, endOffset, lineComparator, sortOptions)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Sorts a text range with a comparator. Returns true if a replace was performed, false otherwise.
 | 
			
		||||
   *
 | 
			
		||||
   * @param editor         The editor to replace text in
 | 
			
		||||
   * @param start          The starting position for the sort
 | 
			
		||||
   * @param end            The ending position for the sort
 | 
			
		||||
   * @param lineComparator The comparator to use to sort
 | 
			
		||||
   * @param sortOption     The option to sort the range
 | 
			
		||||
   * @return true if able to sort the text, false if not
 | 
			
		||||
   */
 | 
			
		||||
  private fun sortTextRange(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: VimCaret,
 | 
			
		||||
    start: Int,
 | 
			
		||||
    end: Int,
 | 
			
		||||
    lineComparator: Comparator<String>,
 | 
			
		||||
    sortOption: SortOption,
 | 
			
		||||
  ): Boolean {
 | 
			
		||||
    val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(start, end))
 | 
			
		||||
    val lines: MutableList<String> = selectedText.split("\n").sortedWith(lineComparator).toMutableList()
 | 
			
		||||
    if (sortOption.unique) {
 | 
			
		||||
      val iterator = lines.iterator()
 | 
			
		||||
    val selectedText = (editor as IjVimEditor).editor.document.getText(TextRangeInterval(startOffset, endOffset))
 | 
			
		||||
    val lines = selectedText.split("\n")
 | 
			
		||||
    val modifiedLines = sortOptions.pattern?.let {
 | 
			
		||||
      if (sortOptions.sortOnPattern) {
 | 
			
		||||
        extractPatternFromLines(editor, lines, startLine, it)
 | 
			
		||||
      } else {
 | 
			
		||||
        deletePatternFromLines(editor, lines, startLine, it)
 | 
			
		||||
      }
 | 
			
		||||
    } ?: lines
 | 
			
		||||
    val sortedLines = lines.zip(modifiedLines)
 | 
			
		||||
      .sortedWith { l1, l2 -> lineComparator.compare(l1.second, l2.second) }
 | 
			
		||||
      .map {it.first}
 | 
			
		||||
      .toMutableList()
 | 
			
		||||
 | 
			
		||||
    if (sortOptions.unique) {
 | 
			
		||||
      val iterator = sortedLines.iterator()
 | 
			
		||||
      var previous: String? = null
 | 
			
		||||
      while (iterator.hasNext()) {
 | 
			
		||||
        val current = iterator.next()
 | 
			
		||||
        if (current == previous || sortOption.ignoreCase && current.equals(previous, ignoreCase = true)) {
 | 
			
		||||
        if (current == previous || sortOptions.ignoreCase && current.equals(previous, ignoreCase = true)) {
 | 
			
		||||
          iterator.remove()
 | 
			
		||||
        } else {
 | 
			
		||||
          previous = current
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    if (lines.size < 1) {
 | 
			
		||||
    if (sortedLines.isEmpty()) {
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
    replaceText(editor, caret, start, end, StringUtil.join(lines, "\n"))
 | 
			
		||||
    replaceText(editor, caret, startOffset, endOffset, StringUtil.join(sortedLines, "\n"))
 | 
			
		||||
    return true
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun extractPatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
 | 
			
		||||
    val regex = VimRegex(pattern)
 | 
			
		||||
    return lines.mapIndexed { i: Int, line: String ->
 | 
			
		||||
      val result = regex.findInLine(editor, startLine + i, 0)
 | 
			
		||||
      when (result) {
 | 
			
		||||
        is VimMatchResult.Success -> result.value
 | 
			
		||||
        is VimMatchResult.Failure -> line
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun deletePatternFromLines(editor: VimEditor, lines: List<String>, startLine: Int, pattern: String): List<String> {
 | 
			
		||||
    val regex = VimRegex(pattern)
 | 
			
		||||
    return lines.mapIndexed { i: Int, line: String ->
 | 
			
		||||
      val result = regex.findInLine(editor, startLine + i, 0)
 | 
			
		||||
      when (result) {
 | 
			
		||||
        is VimMatchResult.Success -> line.substring(result.value.length, line.length)
 | 
			
		||||
        is VimMatchResult.Failure -> line
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Perform increment and decrement for numbers in visual mode
 | 
			
		||||
   *
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,11 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCommandGroupBase
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Elliot Courant
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
internal class CommandGroup : VimCommandGroupBase()
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,9 @@ package com.maddyhome.idea.vim.group;
 | 
			
		||||
import com.intellij.execution.impl.ConsoleViewImpl;
 | 
			
		||||
import com.intellij.find.EditorSearchSession;
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager;
 | 
			
		||||
import com.intellij.openapi.client.ClientAppSession;
 | 
			
		||||
import com.intellij.openapi.client.ClientKind;
 | 
			
		||||
import com.intellij.openapi.client.ClientSessionsManager;
 | 
			
		||||
import com.intellij.openapi.components.PersistentStateComponent;
 | 
			
		||||
import com.intellij.openapi.components.State;
 | 
			
		||||
import com.intellij.openapi.components.Storage;
 | 
			
		||||
@@ -22,7 +25,10 @@ import com.intellij.openapi.project.Project;
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.api.*;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.*;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CommandStateHelper;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.UserDataManager;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimDocument;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.options.EffectiveOptionValueChangeListener;
 | 
			
		||||
@@ -34,10 +40,10 @@ import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
import java.util.stream.Collectors;
 | 
			
		||||
import java.util.stream.Stream;
 | 
			
		||||
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.options;
 | 
			
		||||
import static com.maddyhome.idea.vim.helper.CaretVisualAttributesHelperKt.updateCaretsVisualAttributes;
 | 
			
		||||
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.ijOptions;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -204,7 +210,8 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void editorCreated(@NotNull Editor editor) {
 | 
			
		||||
    DocumentManager.INSTANCE.addListeners(editor.getDocument());
 | 
			
		||||
    UserDataManager.setVimInitialised(editor, true);
 | 
			
		||||
 | 
			
		||||
    VimPlugin.getKey().registerRequiredShortcutKeys(new IjVimEditor(editor));
 | 
			
		||||
 | 
			
		||||
    initLineNumbers(editor);
 | 
			
		||||
@@ -228,7 +235,7 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
    // Note that we need a similar check in `VimEditor.isWritable` to allow Escape to work to exit insert mode. We need
 | 
			
		||||
    // to know that a read-only editor that is hosting a console view with a running process can be treated as writable.
 | 
			
		||||
    Runnable switchToInsertMode = () -> {
 | 
			
		||||
      ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
 | 
			
		||||
      ExecutionContext context = injector.getExecutionContextManager().getEditorExecutionContext(new IjVimEditor(editor));
 | 
			
		||||
      VimPlugin.getChange().insertBeforeCursor(new IjVimEditor(editor), context);
 | 
			
		||||
      KeyHandler.getInstance().reset(new IjVimEditor(editor));
 | 
			
		||||
    };
 | 
			
		||||
@@ -246,14 +253,13 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
          switchToInsertMode.run();
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
    updateCaretsVisualAttributes(editor);
 | 
			
		||||
    updateCaretsVisualAttributes(new IjVimEditor(editor));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void editorDeinit(@NotNull Editor editor, boolean isReleased) {
 | 
			
		||||
    deinitLineNumbers(editor, isReleased);
 | 
			
		||||
    UserDataManager.unInitializeEditor(editor);
 | 
			
		||||
    VimPlugin.getKey().unregisterShortcutKeys(new IjVimEditor(editor));
 | 
			
		||||
    DocumentManager.INSTANCE.removeListeners(editor.getDocument());
 | 
			
		||||
    CaretVisualAttributesHelperKt.removeCaretsVisualAttributes(editor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -284,6 +290,18 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
    notifyIdeaJoin(((IjVimEditor) editor).getEditor().getProject(), editor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void updateCaretsVisualAttributes(@NotNull VimEditor editor) {
 | 
			
		||||
    Editor ijEditor = ((IjVimEditor) editor).getEditor();
 | 
			
		||||
    CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void updateCaretsVisualPosition(@NotNull VimEditor editor) {
 | 
			
		||||
    Editor ijEditor = ((IjVimEditor) editor).getEditor();
 | 
			
		||||
    CaretVisualAttributesHelperKt.updateCaretsVisualAttributes(ijEditor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static class NumberChangeListener implements EffectiveOptionValueChangeListener {
 | 
			
		||||
    public static NumberChangeListener INSTANCE = new NumberChangeListener();
 | 
			
		||||
 | 
			
		||||
@@ -324,20 +342,45 @@ public class EditorGroup implements PersistentStateComponent<Element>, VimEditor
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @NotNull
 | 
			
		||||
  @Override
 | 
			
		||||
  public Collection<VimEditor> localEditors() {
 | 
			
		||||
    return HelperKt.localEditors().stream()
 | 
			
		||||
  public @NotNull Collection<VimEditor> getEditorsRaw() {
 | 
			
		||||
    return getLocalEditors()
 | 
			
		||||
      .map(IjVimEditor::new)
 | 
			
		||||
      .collect(Collectors.toList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @NotNull
 | 
			
		||||
  @Override
 | 
			
		||||
  public Collection<VimEditor> localEditors(@NotNull VimDocument buffer) {
 | 
			
		||||
    final Document document = ((IjVimDocument)buffer).getDocument();
 | 
			
		||||
    return HelperKt.localEditors(document).stream()
 | 
			
		||||
  public Collection<VimEditor> getEditors() {
 | 
			
		||||
    return getLocalEditors()
 | 
			
		||||
      .filter(UserDataManager::getVimInitialised)
 | 
			
		||||
      .map(IjVimEditor::new)
 | 
			
		||||
      .collect(Collectors.toList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @NotNull
 | 
			
		||||
  @Override
 | 
			
		||||
  public Collection<VimEditor> getEditors(@NotNull VimDocument buffer) {
 | 
			
		||||
    final Document document = ((IjVimDocument)buffer).getDocument();
 | 
			
		||||
    return getLocalEditors()
 | 
			
		||||
      .filter(editor -> UserDataManager.getVimInitialised(editor) && editor.getDocument().equals(document))
 | 
			
		||||
      .map(IjVimEditor::new)
 | 
			
		||||
      .collect(Collectors.toList());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private Stream<Editor> getLocalEditors() {
 | 
			
		||||
    // Always fetch local editors. If we're hosting a Code With Me session, any connected guests will create hidden
 | 
			
		||||
    // editors to handle syntax highlighting, completion requests, etc. We need to make sure that IdeaVim only makes
 | 
			
		||||
    // changes (e.g. adding search highlights) to local editors, so things don't incorrectly flow through to any Clients.
 | 
			
		||||
    // In non-CWM scenarios, or if IdeaVim is installed on the Client, there are only ever local editors, so this will
 | 
			
		||||
    // also work there. In Gateway remote development scenarios, IdeaVim should not be installed on the host, only the
 | 
			
		||||
    // Client, so all should work there too.
 | 
			
		||||
    // Note that most IdeaVim operations are in response to interactive keystrokes, which would mean that
 | 
			
		||||
    // ClientEditorManager.getCurrentInstance would return local editors. However, some operations are in response to
 | 
			
		||||
    // events such as document change (to update search highlights) and these can come from CWM guests, and we'd get the
 | 
			
		||||
    // remote editors.
 | 
			
		||||
    // This invocation will always get local editors, regardless of current context.
 | 
			
		||||
    final ClientAppSession localSession = ClientSessionsManager.getAppSessions(ClientKind.LOCAL).get(0);
 | 
			
		||||
    return localSession.getService(ClientEditorManager.class).editors();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -22,16 +22,17 @@ import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
 | 
			
		||||
import com.intellij.openapi.fileTypes.FileType;
 | 
			
		||||
import com.intellij.openapi.fileTypes.FileTypeManager;
 | 
			
		||||
import com.intellij.openapi.project.Project;
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager;
 | 
			
		||||
import com.intellij.openapi.roots.ProjectRootManager;
 | 
			
		||||
import com.intellij.openapi.vfs.LocalFileSystem;
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFile;
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFileManager;
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFileSystem;
 | 
			
		||||
import com.intellij.psi.search.FilenameIndex;
 | 
			
		||||
import com.intellij.psi.search.GlobalSearchScope;
 | 
			
		||||
import com.intellij.psi.search.ProjectScope;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.api.*;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelperRt;
 | 
			
		||||
@@ -40,10 +41,13 @@ import com.maddyhome.idea.vim.helper.SearchHelper;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ExecuteExtensionKt;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjEditorExecutionContext;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
 | 
			
		||||
import java.io.File;
 | 
			
		||||
import java.util.Arrays;
 | 
			
		||||
import java.util.Collection;
 | 
			
		||||
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
 | 
			
		||||
@@ -438,14 +442,35 @@ public class FileGroup extends VimFileBase {
 | 
			
		||||
  private static final @NotNull Logger logger = Logger.getInstance(FileGroup.class.getName());
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This method listens for editor tab changes so any insert/replace modes that need to be reset can be.
 | 
			
		||||
   * Respond to editor tab selection and remember the last used tab
 | 
			
		||||
   */
 | 
			
		||||
  public static void fileEditorManagerSelectionChangedCallback(@NotNull FileEditorManagerEvent event) {
 | 
			
		||||
    // The user has changed the editor they are working with - exit insert/replace mode, and complete any
 | 
			
		||||
    // appropriate repeat
 | 
			
		||||
    if (event.getOldFile() != null) {
 | 
			
		||||
      LastTabService.getInstance(event.getManager().getProject()).setLastTab(event.getOldFile());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
  @Nullable
 | 
			
		||||
  @Override
 | 
			
		||||
  public VimEditor selectEditor(@NotNull String projectId, @NotNull String documentPath, @Nullable String protocol) {
 | 
			
		||||
    VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(protocol);
 | 
			
		||||
    if (fileSystem == null) return null;
 | 
			
		||||
    VirtualFile virtualFile = fileSystem.findFileByPath(documentPath);
 | 
			
		||||
    if (virtualFile == null) return null;
 | 
			
		||||
 | 
			
		||||
    Project project = Arrays.stream(ProjectManager.getInstance().getOpenProjects())
 | 
			
		||||
      .filter(p -> injector.getFile().getProjectId(p).equals(projectId))
 | 
			
		||||
      .findFirst().orElseThrow();
 | 
			
		||||
 | 
			
		||||
    Editor editor = selectEditor(project, virtualFile);
 | 
			
		||||
    if (editor == null) return null;
 | 
			
		||||
    return new IjVimEditor(editor);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @NotNull
 | 
			
		||||
  @Override
 | 
			
		||||
  public String getProjectId(@NotNull Object project) {
 | 
			
		||||
    if (!(project instanceof Project)) throw new IllegalArgumentException();
 | 
			
		||||
    return ((Project) project).getName();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
 | 
			
		||||
 */
 | 
			
		||||
@Suppress("SpellCheckingInspection")
 | 
			
		||||
public open class GlobalIjOptions(scope: OptionAccessScope) : OptionsPropertiesBase(scope) {
 | 
			
		||||
  public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
 | 
			
		||||
  public var ide: String by optionProperty(IjOptions.ide)
 | 
			
		||||
  public var ideamarks: Boolean by optionProperty(IjOptions.ideamarks)
 | 
			
		||||
  public var ideastatusicon: String by optionProperty(IjOptions.ideastatusicon)
 | 
			
		||||
@@ -29,15 +28,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)
 | 
			
		||||
 | 
			
		||||
  // Temporary options to control work-in-progress behaviour
 | 
			
		||||
  public var closenotebooks: Boolean by optionProperty(IjOptions.closenotebooks)
 | 
			
		||||
  public var commandOrMotionAnnotation: Boolean by optionProperty(IjOptions.commandOrMotionAnnotation)
 | 
			
		||||
  public var exCommandAnnotation: Boolean by optionProperty(IjOptions.exCommandAnnotation)
 | 
			
		||||
  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)
 | 
			
		||||
  public var useNewRegex: Boolean by optionProperty(IjOptions.useNewRegex)
 | 
			
		||||
  public var vimscriptFunctionAnnotation: Boolean by optionProperty(IjOptions.vimscriptFunctionAnnotation)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 
 | 
			
		||||
@@ -33,8 +33,6 @@ public object IjOptions {
 | 
			
		||||
    Options.overrideDefaultValue(Options.clipboard, VimString("ideaput,autoselect,exclude:cons\\|linux"))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true))
 | 
			
		||||
  public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true))
 | 
			
		||||
  public val ide: StringOption = addOption(
 | 
			
		||||
    StringOption("ide", GLOBAL, "ide", ApplicationNamesInfo.getInstance().fullProductNameWithEdition)
 | 
			
		||||
  )
 | 
			
		||||
@@ -81,13 +79,16 @@ public object IjOptions {
 | 
			
		||||
      "<Tab>,<Down>,<Up>,<Enter>,<Left>,<Right>,<C-Down>,<C-Up>,<PageUp>,<PageDown>,<C-J>,<C-Q>")
 | 
			
		||||
  )
 | 
			
		||||
  public val trackactionids: ToggleOption = addOption(ToggleOption("trackactionids", GLOBAL, "tai", false))
 | 
			
		||||
  public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", 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 useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isTemporary = true))
 | 
			
		||||
 | 
			
		||||
  // Temporary feature flags during development, not really intended for external use
 | 
			
		||||
  public val closenotebooks: ToggleOption = addOption(ToggleOption("closenotebooks", GLOBAL, "closenotebooks", true, isHidden = true))
 | 
			
		||||
  public val commandOrMotionAnnotation: ToggleOption = addOption(ToggleOption("commandormotionannotation", GLOBAL, "commandormotionannotation", true, isHidden = true))
 | 
			
		||||
  public val exCommandAnnotation: ToggleOption = addOption(ToggleOption("excommandannotation", GLOBAL, "excommandannotation", true, isHidden = true))
 | 
			
		||||
  public val oldundo: ToggleOption = addOption(ToggleOption("oldundo", GLOBAL, "oldundo", true, isHidden = true))
 | 
			
		||||
  public val unifyjumps: ToggleOption = addOption(ToggleOption("unifyjumps", GLOBAL, "unifyjumps", true, isHidden = true))
 | 
			
		||||
  public val useNewRegex: ToggleOption = addOption(ToggleOption("usenewregex", GLOBAL, "usenewregex", true, isHidden = true))
 | 
			
		||||
  public val vimscriptFunctionAnnotation: ToggleOption = addOption(ToggleOption("vimscriptfunctionannotation", GLOBAL, "vimscriptfunctionannotation", true, isHidden = 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>
 | 
			
		||||
 
 | 
			
		||||
@@ -15,38 +15,38 @@ import com.maddyhome.idea.vim.statistic.VimscriptState
 | 
			
		||||
internal class IjStatisticsService : VimStatistics {
 | 
			
		||||
 | 
			
		||||
  override fun logTrackedAction(actionId: String) {
 | 
			
		||||
    ActionTracker.logTrackedAction(actionId)
 | 
			
		||||
    ActionTracker.Util.logTrackedAction(actionId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun logCopiedAction(actionId: String) {
 | 
			
		||||
    ActionTracker.logCopiedAction(actionId)
 | 
			
		||||
    ActionTracker.Util.logCopiedAction(actionId)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setIfLoopUsed(value: Boolean) {
 | 
			
		||||
    VimscriptState.isLoopUsed = value
 | 
			
		||||
    VimscriptState.Util.isLoopUsed = value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setIfMapExprUsed(value: Boolean) {
 | 
			
		||||
    VimscriptState.isMapExprUsed = value
 | 
			
		||||
    VimscriptState.Util.isMapExprUsed = value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setIfFunctionCallUsed(value: Boolean) {
 | 
			
		||||
    VimscriptState.isFunctionCallUsed = value
 | 
			
		||||
    VimscriptState.Util.isFunctionCallUsed = value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setIfFunctionDeclarationUsed(value: Boolean) {
 | 
			
		||||
    VimscriptState.isFunctionDeclarationUsed = value
 | 
			
		||||
    VimscriptState.Util.isFunctionDeclarationUsed = value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setIfIfUsed(value: Boolean) {
 | 
			
		||||
    VimscriptState.isIfUsed = value
 | 
			
		||||
    VimscriptState.Util.isIfUsed = value
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun addExtensionEnabledWithPlug(extension: String) {
 | 
			
		||||
    VimscriptState.extensionsEnabledWithPlug.add(extension)
 | 
			
		||||
    VimscriptState.Util.extensionsEnabledWithPlug.add(extension)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun addSourcedFile(path: String) {
 | 
			
		||||
    VimscriptState.sourcedFiles.add(path)
 | 
			
		||||
    VimscriptState.Util.sourcedFiles.add(path)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,61 @@
 | 
			
		||||
/*
 | 
			
		||||
 * 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.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.lang.CodeDocumentationAwareCommenter
 | 
			
		||||
import com.intellij.lang.LanguageCommenters
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.psi.PsiComment
 | 
			
		||||
import com.intellij.psi.util.PsiTreeUtil
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimPsiService
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.PsiHelper
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
public class IjVimPsiService: VimPsiService {
 | 
			
		||||
  override fun getCommentAtPos(editor: VimEditor, pos: Int): Pair<TextRange, Pair<String, String>?>? {
 | 
			
		||||
    val psiFile = PsiHelper.getFile(editor.ij) ?: return null
 | 
			
		||||
    val psiElement = psiFile.findElementAt(pos) ?: return null
 | 
			
		||||
    val language = psiElement.language
 | 
			
		||||
    val commenter = LanguageCommenters.INSTANCE.forLanguage(language)
 | 
			
		||||
    val psiComment = PsiTreeUtil.getParentOfType(psiElement, PsiComment::class.java, false) ?: return null
 | 
			
		||||
    val commentText = psiComment.text
 | 
			
		||||
 | 
			
		||||
    val blockCommentPrefix = commenter.blockCommentPrefix
 | 
			
		||||
    val blockCommentSuffix = commenter.blockCommentSuffix
 | 
			
		||||
 | 
			
		||||
    val docCommentPrefix = (commenter as? CodeDocumentationAwareCommenter)?.documentationCommentPrefix
 | 
			
		||||
    val docCommentSuffix = (commenter as? CodeDocumentationAwareCommenter)?.documentationCommentSuffix
 | 
			
		||||
 | 
			
		||||
    val prefixToSuffix: Pair<String, String>? =
 | 
			
		||||
      if (docCommentPrefix != null && docCommentSuffix != null && commentText.startsWith(docCommentPrefix) && commentText.endsWith(docCommentSuffix)) {
 | 
			
		||||
        docCommentPrefix to docCommentSuffix
 | 
			
		||||
      }
 | 
			
		||||
      else if (blockCommentPrefix != null && blockCommentSuffix != null && commentText.startsWith(blockCommentPrefix) && commentText.endsWith(blockCommentSuffix)) {
 | 
			
		||||
        blockCommentPrefix to blockCommentSuffix
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
        null
 | 
			
		||||
      }
 | 
			
		||||
    return Pair(psiComment.textRange.vim, prefixToSuffix)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getDoubleQuotedString(editor: VimEditor, pos: Int, isInner: Boolean): TextRange? {
 | 
			
		||||
    // TODO[ideavim] It wasn't implemented before, but implementing it will significantly improve % motion
 | 
			
		||||
    return getDoubleQuotesRangeNoPSI(editor.text(), pos, isInner)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getSingleQuotedString(editor: VimEditor, pos: Int, isInner: Boolean): TextRange? {
 | 
			
		||||
    // TODO[ideavim] It wasn't implemented before, but implementing it will significantly improve % motion
 | 
			
		||||
    return getSingleQuotesRangeNoPSI(editor.text(), pos, isInner)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -26,10 +26,12 @@ import com.maddyhome.idea.vim.EventFacade;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
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.api.NativeAction;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimInjectorKt;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimKeyGroupBase;
 | 
			
		||||
import com.maddyhome.idea.vim.command.MappingMode;
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.HelperKt;
 | 
			
		||||
import com.maddyhome.idea.vim.key.*;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjNativeAction;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
@@ -99,9 +101,9 @@ public class KeyGroup extends VimKeyGroupBase implements PersistentStateComponen
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public void updateShortcutKeysRegistration() {
 | 
			
		||||
    for (Editor editor : HelperKt.localEditors()) {
 | 
			
		||||
      unregisterShortcutKeys(new IjVimEditor(editor));
 | 
			
		||||
      registerRequiredShortcutKeys(new IjVimEditor(editor));
 | 
			
		||||
    for (VimEditor editor : injector.getEditorGroup().getEditors()) {
 | 
			
		||||
      unregisterShortcutKeys(editor);
 | 
			
		||||
      registerRequiredShortcutKeys(editor);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -228,7 +230,7 @@ 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 (!injector.getOptionGroup().getGlobalOptions().getOctopushandler() ||
 | 
			
		||||
        if (!injector.getApplication().isOctopusEnabled() ||
 | 
			
		||||
            !(key.getKeyCode() == KeyEvent.VK_ESCAPE && key.getModifiers() == 0) &&
 | 
			
		||||
            !(key.getKeyCode() == KeyEvent.VK_ENTER && key.getModifiers() == 0)) {
 | 
			
		||||
          getRequiredShortcutKeys().add(new RequiredShortcut(key, owner));
 | 
			
		||||
 
 | 
			
		||||
@@ -7,7 +7,10 @@
 | 
			
		||||
 */
 | 
			
		||||
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.components.Service
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.progress.ProcessCanceledException
 | 
			
		||||
import com.intellij.openapi.progress.ProgressManager
 | 
			
		||||
@@ -24,6 +27,7 @@ import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
/**
 | 
			
		||||
 * Used to handle playback of macros
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
 | 
			
		||||
  // If it's null, this is the top macro (as in most cases). If it's not null, this macro is executed from top macro
 | 
			
		||||
@@ -74,11 +78,12 @@ internal class MacroGroup : VimMacroBase() {
 | 
			
		||||
                } catch (e: ProcessCanceledException) {
 | 
			
		||||
                  return@runnable
 | 
			
		||||
                }
 | 
			
		||||
                val keyHandler = getInstance()
 | 
			
		||||
                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)
 | 
			
		||||
                  CompletionServiceImpl.setCompletionPhase(CompletionPhase.NoCompletion)
 | 
			
		||||
                  keyHandler.handleKey(editor, key, context, keyHandler.keyHandlerState)
 | 
			
		||||
                }
 | 
			
		||||
                if (injector.messages.isError()) return@runnable
 | 
			
		||||
              }
 | 
			
		||||
 
 | 
			
		||||
@@ -8,43 +8,27 @@
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.LogicalPosition
 | 
			
		||||
import com.intellij.openapi.editor.VisualPosition
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
 | 
			
		||||
import com.intellij.openapi.fileEditor.TextEditor
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.vfs.LocalFileSystem
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFile
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFileManager
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFileSystem
 | 
			
		||||
import com.intellij.util.MathUtil.clamp
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.BufferPosition
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ImmutableVimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimChangeGroupBase
 | 
			
		||||
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
 | 
			
		||||
import com.maddyhome.idea.vim.api.lineLength
 | 
			
		||||
import com.maddyhome.idea.vim.api.normalizeColumn
 | 
			
		||||
import com.maddyhome.idea.vim.api.normalizeLine
 | 
			
		||||
import com.maddyhome.idea.vim.api.normalizeOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.normalizeVisualColumn
 | 
			
		||||
import com.maddyhome.idea.vim.api.normalizeVisualLine
 | 
			
		||||
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
 | 
			
		||||
@@ -53,12 +37,9 @@ import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
			
		||||
import com.maddyhome.idea.vim.handler.Motion
 | 
			
		||||
import com.maddyhome.idea.vim.handler.Motion.AbsoluteOffset
 | 
			
		||||
import com.maddyhome.idea.vim.handler.Motion.AdjustedOffset
 | 
			
		||||
import com.maddyhome.idea.vim.handler.MotionActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.handler.TextObjectActionHandler
 | 
			
		||||
import com.maddyhome.idea.vim.handler.toMotionOrError
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.SearchHelper
 | 
			
		||||
import com.maddyhome.idea.vim.helper.exitVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.fileSize
 | 
			
		||||
import com.maddyhome.idea.vim.helper.getNormalizedScrollOffset
 | 
			
		||||
@@ -66,46 +47,25 @@ import com.maddyhome.idea.vim.helper.getNormalizedSideScrollOffset
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isEndAllowed
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimLastColumn
 | 
			
		||||
import com.maddyhome.idea.vim.listener.AppCodeTemplates
 | 
			
		||||
import com.maddyhome.idea.vim.mark.Mark
 | 
			
		||||
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.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
 | 
			
		||||
import kotlin.math.max
 | 
			
		||||
import kotlin.math.min
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * This handles all motion related commands and marks
 | 
			
		||||
 */
 | 
			
		||||
@Service
 | 
			
		||||
internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
  override fun onAppCodeMovement(editor: VimEditor, caret: VimCaret, offset: Int, oldOffset: Int) {
 | 
			
		||||
    AppCodeTemplates.onMovement(editor.ij, caret.ij, oldOffset < offset)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun selectEditor(project: Project, mark: Mark): Editor? {
 | 
			
		||||
    val virtualFile = markToVirtualFile(mark) ?: return null
 | 
			
		||||
    return selectEditor(project, virtualFile)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun markToVirtualFile(mark: Mark): VirtualFile? {
 | 
			
		||||
    val protocol = mark.protocol
 | 
			
		||||
    val fileSystem: VirtualFileSystem? = VirtualFileManager.getInstance().getFileSystem(protocol)
 | 
			
		||||
    return fileSystem?.findFileByPath(mark.filepath)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun selectEditor(project: Project?, file: VirtualFile) =
 | 
			
		||||
    VimPlugin.getFile().selectEditor(project, file)
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToMatchingPair(editor: VimEditor, caret: ImmutableVimCaret): Motion {
 | 
			
		||||
    return SearchHelper.findMatchingPairOnCurrentLine(editor.ij, caret.ij).toMotionOrError()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToFirstDisplayLine(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: ImmutableVimCaret,
 | 
			
		||||
@@ -128,85 +88,12 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
    return moveCaretToScreenLocation(editor.ij, caret.ij, ScreenLocation.MIDDLE, 0, false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToMark(caret: ImmutableVimCaret, ch: Char, toLineStart: Boolean): Motion {
 | 
			
		||||
    val markService = injector.markService
 | 
			
		||||
    val mark = markService.getMark(caret, ch) ?: return Motion.Error
 | 
			
		||||
 | 
			
		||||
    val caretEditor = caret.editor
 | 
			
		||||
    val caretVirtualFile = EditorHelper.getVirtualFile((caretEditor as IjVimEditor).editor)
 | 
			
		||||
 | 
			
		||||
    val line = mark.line
 | 
			
		||||
 | 
			
		||||
    if (caretVirtualFile!!.path == mark.filepath) {
 | 
			
		||||
      val offset = if (toLineStart) {
 | 
			
		||||
        moveCaretToLineStartSkipLeading(caretEditor, line)
 | 
			
		||||
      } else {
 | 
			
		||||
        caretEditor.bufferPositionToOffset(BufferPosition(line, mark.col, false))
 | 
			
		||||
      }
 | 
			
		||||
      return offset.toMotionOrError()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val project = caretEditor.editor.project
 | 
			
		||||
    val markEditor = selectEditor(project!!, mark)
 | 
			
		||||
    if (markEditor != null) {
 | 
			
		||||
      // todo should we move all the carets or only one?
 | 
			
		||||
      for (carett in markEditor.caretModel.allCarets) {
 | 
			
		||||
        val offset = if (toLineStart) {
 | 
			
		||||
          moveCaretToLineStartSkipLeading(IjVimEditor(markEditor), line)
 | 
			
		||||
        } else {
 | 
			
		||||
          // todo should it be the same as getting offset above?
 | 
			
		||||
          markEditor.logicalPositionToOffset(LogicalPosition(line, mark.col))
 | 
			
		||||
        }
 | 
			
		||||
        IjVimCaret(carett!!).moveToOffset(offset)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return Motion.Error
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToJump(editor: VimEditor, caret: ImmutableVimCaret, count: Int): Motion {
 | 
			
		||||
    val jumpService = injector.jumpService
 | 
			
		||||
    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)
 | 
			
		||||
    return if (vf.path != fileName) {
 | 
			
		||||
      val newFile = LocalFileSystem.getInstance().findFileByPath(fileName.replace(File.separatorChar, '/'))
 | 
			
		||||
        ?: return Motion.Error
 | 
			
		||||
      selectEditor(editor.ij.project, newFile)?.let { newEditor ->
 | 
			
		||||
        if (spot == -1) {
 | 
			
		||||
          jumpService.addJump(editor, false)
 | 
			
		||||
        }
 | 
			
		||||
        newEditor.vim.let {
 | 
			
		||||
          it.currentCaret().moveToOffset(it.normalizeOffset(newEditor.logicalPositionToOffset(lpNative), false))
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      Motion.Error
 | 
			
		||||
    } else {
 | 
			
		||||
      if (spot == -1) {
 | 
			
		||||
        jumpService.addJump(editor, false)
 | 
			
		||||
      }
 | 
			
		||||
      editor.bufferPositionToOffset(lp).toMotionOrError()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToCurrentDisplayLineMiddle(editor: VimEditor, caret: ImmutableVimCaret): Motion {
 | 
			
		||||
    val width = EditorHelper.getApproximateScreenWidth(editor.ij) / 2
 | 
			
		||||
    val len = editor.lineLength(editor.currentCaret().getBufferPosition().line)
 | 
			
		||||
    return moveCaretToColumn(editor, caret, max(0, min(len - 1, width)), false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToColumn(editor: VimEditor, caret: ImmutableVimCaret, count: Int, allowEnd: Boolean): Motion {
 | 
			
		||||
    val line = caret.getLine().line
 | 
			
		||||
    val column = editor.normalizeColumn(line, count, allowEnd)
 | 
			
		||||
    val offset = editor.bufferPositionToOffset(BufferPosition(line, column, false))
 | 
			
		||||
    return if (column != count) {
 | 
			
		||||
      AdjustedOffset(offset, count)
 | 
			
		||||
    } else {
 | 
			
		||||
      AbsoluteOffset(offset)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToCurrentDisplayLineStart(editor: VimEditor, caret: ImmutableVimCaret): Motion {
 | 
			
		||||
    val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
 | 
			
		||||
    return moveCaretToColumn(editor, caret, col, false)
 | 
			
		||||
@@ -217,7 +104,7 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
    caret: ImmutableVimCaret,
 | 
			
		||||
  ): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
 | 
			
		||||
    val col = EditorHelper.getVisualColumnAtLeftOfDisplay(editor.ij, caret.getVisualPosition().line)
 | 
			
		||||
    val bufferLine = caret.getLine().line
 | 
			
		||||
    val bufferLine = caret.getLine()
 | 
			
		||||
    return editor.getLeadingCharacterOffset(bufferLine, col)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -230,36 +117,6 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
    return moveCaretToColumn(editor, caret, col, allowEnd)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToLineWithSameColumn(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    line: Int,
 | 
			
		||||
    caret: ImmutableVimCaret,
 | 
			
		||||
  ): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
 | 
			
		||||
    var c = caret.vimLastColumn
 | 
			
		||||
    var l = line
 | 
			
		||||
    if (l < 0) {
 | 
			
		||||
      l = 0
 | 
			
		||||
      c = 0
 | 
			
		||||
    } else if (l >= editor.lineCount()) {
 | 
			
		||||
      l = editor.normalizeLine(editor.lineCount() - 1)
 | 
			
		||||
      c = editor.lineLength(l)
 | 
			
		||||
    }
 | 
			
		||||
    val newPos = BufferPosition(l, editor.normalizeColumn(l, c, false))
 | 
			
		||||
    return editor.bufferPositionToOffset(newPos)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToLineWithStartOfLineOption(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    line: Int,
 | 
			
		||||
    caret: ImmutableVimCaret,
 | 
			
		||||
  ): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
 | 
			
		||||
    return if (injector.options(editor).startofline) {
 | 
			
		||||
      moveCaretToLineStartSkipLeading(editor, line)
 | 
			
		||||
    } else {
 | 
			
		||||
      moveCaretToLineWithSameColumn(editor, line, caret)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * If 'absolute' is true, then set tab index to 'value', otherwise add 'value' to tab index with wraparound.
 | 
			
		||||
   */
 | 
			
		||||
@@ -277,30 +134,18 @@ internal class MotionGroup : VimMotionGroupBase() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretGotoPreviousTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
 | 
			
		||||
    val project = editor.ij.project ?: return editor.currentCaret().offset.point
 | 
			
		||||
    val project = editor.ij.project ?: return editor.currentCaret().offset
 | 
			
		||||
    val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
 | 
			
		||||
    switchEditorTab(currentWindow, if (rawCount >= 1) -rawCount else -1, false)
 | 
			
		||||
    return editor.currentCaret().offset.point
 | 
			
		||||
    return editor.currentCaret().offset
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretGotoNextTab(editor: VimEditor, context: ExecutionContext, rawCount: Int): Int {
 | 
			
		||||
    val absolute = rawCount >= 1
 | 
			
		||||
    val project = editor.ij.project ?: return editor.currentCaret().offset.point
 | 
			
		||||
    val project = editor.ij.project ?: return editor.currentCaret().offset
 | 
			
		||||
    val currentWindow = FileEditorManagerEx.getInstanceEx(project).splitters.currentWindow
 | 
			
		||||
    switchEditorTab(currentWindow, if (absolute) rawCount - 1 else 1, absolute)
 | 
			
		||||
    return editor.currentCaret().offset.point
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun moveCaretToLinePercent(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    caret: ImmutableVimCaret,
 | 
			
		||||
    count: Int,
 | 
			
		||||
  ): @Range(from = 0, to = Int.MAX_VALUE.toLong()) Int {
 | 
			
		||||
    return moveCaretToLineWithStartOfLineOption(
 | 
			
		||||
      editor,
 | 
			
		||||
      editor.normalizeLine((editor.lineCount() * clamp(count, 0, 100) + 99) / 100 - 1),
 | 
			
		||||
      caret,
 | 
			
		||||
    )
 | 
			
		||||
    return editor.currentCaret().offset
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private enum class ScreenLocation {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ 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.components.Service
 | 
			
		||||
import com.intellij.openapi.diagnostic.logger
 | 
			
		||||
import com.intellij.openapi.ide.CopyPasteManager
 | 
			
		||||
import com.intellij.openapi.keymap.KeymapUtil
 | 
			
		||||
@@ -55,6 +56,7 @@ import javax.swing.KeyStroke
 | 
			
		||||
 * This service is can be used as application level and as project level service.
 | 
			
		||||
 * If project is null, this means that this is an application level service and notification will be shown for all projects
 | 
			
		||||
 */
 | 
			
		||||
@Service(Service.Level.PROJECT, Service.Level.APP)
 | 
			
		||||
internal class NotificationService(private val project: Project?) {
 | 
			
		||||
  // This constructor is used to create an applicationService
 | 
			
		||||
  @Suppress("unused")
 | 
			
		||||
@@ -276,7 +278,7 @@ internal class NotificationService(private val project: Project?) {
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (id != null) {
 | 
			
		||||
        ActionTracker.logTrackedAction(id)
 | 
			
		||||
        ActionTracker.Util.logTrackedAction(id)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -284,7 +286,7 @@ internal class NotificationService(private val project: Project?) {
 | 
			
		||||
      override fun actionPerformed(e: AnActionEvent) {
 | 
			
		||||
        CopyPasteManager.getInstance().setContents(StringSelection(id ?: ""))
 | 
			
		||||
        if (id != null) {
 | 
			
		||||
          ActionTracker.logCopiedAction(id)
 | 
			
		||||
          ActionTracker.Util.logCopiedAction(id)
 | 
			
		||||
        }
 | 
			
		||||
        notification?.expire()
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,7 @@ 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.KeyProcessResult
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
@@ -37,7 +38,6 @@ 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
 | 
			
		||||
@@ -85,24 +85,27 @@ public class ProcessGroup : VimProcessGroupBase() {
 | 
			
		||||
    modeBeforeCommandProcessing = currentMode
 | 
			
		||||
    val initText = getRange(editor, cmd)
 | 
			
		||||
    injector.markService.setVisualSelectionMarks(editor)
 | 
			
		||||
    editor.vimStateMachine.mode = Mode.CMD_LINE(currentMode)
 | 
			
		||||
    editor.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 {
 | 
			
		||||
  public override fun processExKey(editor: VimEditor, stroke: KeyStroke, processResultBuilder: KeyProcessResult.KeyProcessResultBuilder): 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)
 | 
			
		||||
 | 
			
		||||
      processResultBuilder.addExecutionStep { _, _, _ ->
 | 
			
		||||
        requestFocus(panel.entry)
 | 
			
		||||
        panel.handleKey(stroke)
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
    } else {
 | 
			
		||||
      getInstance(editor).mode = NORMAL()
 | 
			
		||||
      getInstance().reset(editor)
 | 
			
		||||
      processResultBuilder.addExecutionStep { _, lambdaEditor, _ ->
 | 
			
		||||
        lambdaEditor.mode = NORMAL()
 | 
			
		||||
        getInstance().reset(lambdaEditor)
 | 
			
		||||
      }
 | 
			
		||||
      return false
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -112,7 +115,7 @@ public class ProcessGroup : VimProcessGroupBase() {
 | 
			
		||||
    panel.deactivate(true)
 | 
			
		||||
    var res = true
 | 
			
		||||
    try {
 | 
			
		||||
      getInstance(editor).mode = NORMAL()
 | 
			
		||||
      editor.mode = NORMAL()
 | 
			
		||||
 | 
			
		||||
      logger.debug("processing command")
 | 
			
		||||
 | 
			
		||||
@@ -152,7 +155,7 @@ public class ProcessGroup : VimProcessGroupBase() {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public override fun cancelExEntry(editor: VimEditor, resetCaret: Boolean) {
 | 
			
		||||
    editor.vimStateMachine.mode = NORMAL()
 | 
			
		||||
    editor.mode = NORMAL()
 | 
			
		||||
    getInstance().reset(editor)
 | 
			
		||||
    val panel = ExEntryPanel.getInstance()
 | 
			
		||||
    panel.deactivate(true, resetCaret)
 | 
			
		||||
@@ -162,7 +165,7 @@ public class ProcessGroup : VimProcessGroupBase() {
 | 
			
		||||
    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)
 | 
			
		||||
    editor.mode = Mode.CMD_LINE(currentMode)
 | 
			
		||||
    val panel = ExEntryPanel.getInstance()
 | 
			
		||||
    panel.activate(editor.ij, context.ij, ":", initText, 1)
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -14,9 +14,9 @@ import com.intellij.openapi.components.State;
 | 
			
		||||
import com.intellij.openapi.components.Storage;
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType;
 | 
			
		||||
import com.maddyhome.idea.vim.register.Register;
 | 
			
		||||
import com.maddyhome.idea.vim.register.VimRegisterGroupBase;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType;
 | 
			
		||||
import org.jdom.Element;
 | 
			
		||||
import org.jetbrains.annotations.NotNull;
 | 
			
		||||
import org.jetbrains.annotations.Nullable;
 | 
			
		||||
@@ -37,6 +37,10 @@ public class RegisterGroup extends VimRegisterGroupBase implements PersistentSta
 | 
			
		||||
 | 
			
		||||
  private static final Logger logger = Logger.getInstance(RegisterGroup.class);
 | 
			
		||||
 | 
			
		||||
  public RegisterGroup() {
 | 
			
		||||
    this.initClipboardOptionListener();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public void saveData(final @NotNull Element element) {
 | 
			
		||||
    logger.debug("Save registers data");
 | 
			
		||||
    final Element registersElement = new Element("registers");
 | 
			
		||||
 
 | 
			
		||||
@@ -21,8 +21,6 @@ import com.intellij.openapi.editor.event.DocumentEvent;
 | 
			
		||||
import com.intellij.openapi.editor.event.DocumentListener;
 | 
			
		||||
import com.intellij.openapi.editor.markup.RangeHighlighter;
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent;
 | 
			
		||||
import com.intellij.openapi.project.Project;
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager;
 | 
			
		||||
import com.intellij.openapi.util.Ref;
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin;
 | 
			
		||||
import com.maddyhome.idea.vim.api.*;
 | 
			
		||||
@@ -33,12 +31,11 @@ import com.maddyhome.idea.vim.ex.ExException;
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ranges.LineRange;
 | 
			
		||||
import com.maddyhome.idea.vim.helper.*;
 | 
			
		||||
import com.maddyhome.idea.vim.history.HistoryConstants;
 | 
			
		||||
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.IjVimSearchGroup;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.*;
 | 
			
		||||
import com.maddyhome.idea.vim.options.GlobalOptionChangeListener;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.*;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.CharPointer;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.CharacterClasses;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.RegExp;
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ModalEntry;
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.VimLContext;
 | 
			
		||||
@@ -59,7 +56,6 @@ import java.text.ParsePosition;
 | 
			
		||||
import java.util.*;
 | 
			
		||||
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.*;
 | 
			
		||||
import static com.maddyhome.idea.vim.helper.HelperKt.localEditors;
 | 
			
		||||
import static com.maddyhome.idea.vim.helper.SearchHelperKtKt.shouldIgnoreCase;
 | 
			
		||||
import static com.maddyhome.idea.vim.newapi.IjVimInjectorKt.globalIjOptions;
 | 
			
		||||
import static com.maddyhome.idea.vim.register.RegisterConstants.LAST_SEARCH_REGISTER;
 | 
			
		||||
@@ -542,20 +538,24 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
   *
 | 
			
		||||
   * @param editor  The editor to search in
 | 
			
		||||
   * @param caret   The caret to use for initial search offset, and to move for interactive substitution
 | 
			
		||||
   * @param context
 | 
			
		||||
   * @param range   Only search and substitute within the given line range. Must be valid
 | 
			
		||||
   * @param excmd   The command part of the ex command line, e.g. `s` or `substitute`, or `~`
 | 
			
		||||
   * @param exarg   The argument to the substitute command, such as `/{pattern}/{string}/[flags]`
 | 
			
		||||
   * @return        True if the substitution succeeds, false on error. Will succeed even if nothing is modified
 | 
			
		||||
   * @return True if the substitution succeeds, false on error. Will succeed even if nothing is modified
 | 
			
		||||
   */
 | 
			
		||||
  @Override
 | 
			
		||||
  @RWLockLabel.SelfSynchronized
 | 
			
		||||
  public boolean processSubstituteCommand(@NotNull VimEditor editor,
 | 
			
		||||
                                          @NotNull VimCaret caret,
 | 
			
		||||
                                          @NotNull ExecutionContext context,
 | 
			
		||||
                                          @NotNull LineRange range,
 | 
			
		||||
                                          @NotNull @NonNls String excmd,
 | 
			
		||||
                                          @NotNull @NonNls String exarg,
 | 
			
		||||
                                          @NotNull VimLContext parent) {
 | 
			
		||||
    if (globalIjOptions(injector).getUseNewRegex()) return super.processSubstituteCommand(editor, caret, range, excmd, exarg, parent);
 | 
			
		||||
    if (globalIjOptions(injector).getUseNewRegex()) {
 | 
			
		||||
      return super.processSubstituteCommand(editor, caret, context, range, excmd, exarg, parent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Explicitly exit visual mode here, so that visual mode marks don't change when we move the cursor to a match.
 | 
			
		||||
    List<ExException> exceptions = new ArrayList<>();
 | 
			
		||||
@@ -812,7 +812,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
            RangeHighlighter hl =
 | 
			
		||||
              SearchHighlightsHelper.addSubstitutionConfirmationHighlight(((IjVimEditor)editor).getEditor(), startoff,
 | 
			
		||||
                                                                          endoff);
 | 
			
		||||
            final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), match, ((IjVimCaret)caret).getCaret(), startoff);
 | 
			
		||||
            final ReplaceConfirmationChoice choice = confirmChoice(((IjVimEditor)editor).getEditor(), context, match, ((IjVimCaret)caret).getCaret(), startoff);
 | 
			
		||||
            ((IjVimEditor)editor).getEditor().getMarkupModel().removeHighlighter(hl);
 | 
			
		||||
            switch (choice) {
 | 
			
		||||
              case SUBSTITUTE_THIS:
 | 
			
		||||
@@ -841,8 +841,7 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
            caret.moveToOffset(startoff);
 | 
			
		||||
            if (expression != null) {
 | 
			
		||||
              try {
 | 
			
		||||
                match =
 | 
			
		||||
                  expression.evaluate(editor, injector.getExecutionContextManager().onEditor(editor, null), parent).toInsertableString();
 | 
			
		||||
                match = expression.evaluate(editor, context, parent).toInsertableString();
 | 
			
		||||
              }
 | 
			
		||||
              catch (Exception e) {
 | 
			
		||||
                exceptions.add((ExException)e);
 | 
			
		||||
@@ -993,7 +992,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
    return new Pair<>(true, new Triple<>(regmatch, pattern, sp));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor, @NotNull String match, @NotNull Caret caret, int startoff) {
 | 
			
		||||
  private static @NotNull ReplaceConfirmationChoice confirmChoice(@NotNull Editor editor,
 | 
			
		||||
                                                                  @NotNull ExecutionContext context,
 | 
			
		||||
                                                                  @NotNull String match, @NotNull Caret caret, int startoff) {
 | 
			
		||||
    final Ref<ReplaceConfirmationChoice> result = Ref.create(ReplaceConfirmationChoice.QUIT);
 | 
			
		||||
    final Function1<KeyStroke, Boolean> keyStrokeProcessor = key -> {
 | 
			
		||||
      final ReplaceConfirmationChoice choice;
 | 
			
		||||
@@ -1027,7 +1028,6 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
    else {
 | 
			
		||||
      // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for this method
 | 
			
		||||
      final ExEntryPanel exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts();
 | 
			
		||||
      ExecutionContext.Editor context = injector.getExecutionContextManager().onEditor(new IjVimEditor(editor), null);
 | 
			
		||||
      exEntryPanel.activate(editor, ((IjEditorExecutionContext)context).getContext(), MessageHelper.message("replace.with.0", match), "", 1);
 | 
			
		||||
      new IjVimCaret(caret).moveToOffset(startoff);
 | 
			
		||||
      ModalEntry.INSTANCE.activate(new IjVimEditor(editor), keyStrokeProcessor);
 | 
			
		||||
@@ -1085,9 +1085,9 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
  private @Nullable TextRange findNextSearchForGn(@NotNull VimEditor editor, int count, boolean forwards) {
 | 
			
		||||
    if (forwards) {
 | 
			
		||||
      final EnumSet<SearchOptions> searchOptions = EnumSet.of(SearchOptions.WRAP, SearchOptions.WHOLE_FILE);
 | 
			
		||||
      return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset().getPoint(), count, searchOptions);
 | 
			
		||||
      return VimInjectorKt.getInjector().getSearchHelper().findPattern(editor, getLastUsedPattern(), editor.primaryCaret().getOffset(), count, searchOptions);
 | 
			
		||||
    } else {
 | 
			
		||||
      return searchBackward(editor, editor.primaryCaret().getOffset().getPoint(), count);
 | 
			
		||||
      return searchBackward(editor, editor.primaryCaret().getOffset(), count);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -1200,47 +1200,50 @@ public class SearchGroup extends IjVimSearchGroup implements PersistentStateComp
 | 
			
		||||
    public static DocumentSearchListener INSTANCE = new DocumentSearchListener();
 | 
			
		||||
 | 
			
		||||
    @Contract(pure = true)
 | 
			
		||||
    private DocumentSearchListener () {
 | 
			
		||||
    private DocumentSearchListener() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public void documentChanged(@NotNull DocumentEvent event) {
 | 
			
		||||
      for (Project project : ProjectManager.getInstance().getOpenProjects()) {
 | 
			
		||||
        final Document document = event.getDocument();
 | 
			
		||||
      // Loop over all local editors for the changed document, across all projects, and update search highlights.
 | 
			
		||||
      // Note that the change may have come from a remote guest in Code With Me scenarios (in which case
 | 
			
		||||
      // ClientId.current will be a guest ID), but we don't care - we still need to add/remove highlights for the
 | 
			
		||||
      // changed text. Make sure we only update local editors, though.
 | 
			
		||||
      final Document document = event.getDocument();
 | 
			
		||||
      for (VimEditor vimEditor : injector.getEditorGroup().getEditors(new IjVimDocument(document))) {
 | 
			
		||||
        final Editor editor = ((IjVimEditor)vimEditor).getEditor();
 | 
			
		||||
        Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor);
 | 
			
		||||
        if (hls == null) {
 | 
			
		||||
          continue;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        for (Editor editor : localEditors(document, project)) {
 | 
			
		||||
          Collection<RangeHighlighter> hls = UserDataManager.getVimLastHighlighters(editor);
 | 
			
		||||
          if (hls == null) {
 | 
			
		||||
            continue;
 | 
			
		||||
        if (logger.isDebugEnabled()) {
 | 
			
		||||
          logger.debug("hls=" + hls);
 | 
			
		||||
          logger.debug("event=" + event);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We can only re-highlight whole lines, so clear any highlights in the affected lines
 | 
			
		||||
        final LogicalPosition startPosition = editor.offsetToLogicalPosition(event.getOffset());
 | 
			
		||||
        final LogicalPosition endPosition = editor.offsetToLogicalPosition(event.getOffset() + event.getNewLength());
 | 
			
		||||
        final int startLineOffset = document.getLineStartOffset(startPosition.line);
 | 
			
		||||
        final int endLineOffset = document.getLineEndOffset(endPosition.line);
 | 
			
		||||
 | 
			
		||||
        final Iterator<RangeHighlighter> iter = hls.iterator();
 | 
			
		||||
        while (iter.hasNext()) {
 | 
			
		||||
          final RangeHighlighter highlighter = iter.next();
 | 
			
		||||
          if (!highlighter.isValid() ||
 | 
			
		||||
              (highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
 | 
			
		||||
            iter.remove();
 | 
			
		||||
            editor.getMarkupModel().removeHighlighter(highlighter);
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
          if (logger.isDebugEnabled()) {
 | 
			
		||||
            logger.debug("hls=" + hls);
 | 
			
		||||
            logger.debug("event=" + event);
 | 
			
		||||
          }
 | 
			
		||||
        VimPlugin.getSearch().highlightSearchLines(editor, startPosition.line, endPosition.line);
 | 
			
		||||
 | 
			
		||||
          // We can only re-highlight whole lines, so clear any highlights in the affected lines
 | 
			
		||||
          final LogicalPosition startPosition = editor.offsetToLogicalPosition(event.getOffset());
 | 
			
		||||
          final LogicalPosition endPosition = editor.offsetToLogicalPosition(event.getOffset() + event.getNewLength());
 | 
			
		||||
          final int startLineOffset = document.getLineStartOffset(startPosition.line);
 | 
			
		||||
          final int endLineOffset = document.getLineEndOffset(endPosition.line);
 | 
			
		||||
 | 
			
		||||
          final Iterator<RangeHighlighter> iter = hls.iterator();
 | 
			
		||||
          while (iter.hasNext()) {
 | 
			
		||||
            final RangeHighlighter highlighter = iter.next();
 | 
			
		||||
            if (!highlighter.isValid() || (highlighter.getStartOffset() >= startLineOffset && highlighter.getEndOffset() <= endLineOffset)) {
 | 
			
		||||
              iter.remove();
 | 
			
		||||
              editor.getMarkupModel().removeHighlighter(highlighter);
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          VimPlugin.getSearch().highlightSearchLines(editor, startPosition.line, endPosition.line);
 | 
			
		||||
 | 
			
		||||
          if (logger.isDebugEnabled()) {
 | 
			
		||||
            hls = UserDataManager.getVimLastHighlighters(editor);
 | 
			
		||||
            logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line);
 | 
			
		||||
            logger.debug("hls=" + hls);
 | 
			
		||||
          }
 | 
			
		||||
        if (logger.isDebugEnabled()) {
 | 
			
		||||
          hls = UserDataManager.getVimLastHighlighters(editor);
 | 
			
		||||
          logger.debug("sl=" + startPosition.line + ", el=" + endPosition.line);
 | 
			
		||||
          logger.debug("hls=" + hls);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -111,7 +111,7 @@ internal class JumpsListener(val project: Project) : RecentPlacesListener {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun buildJump(place: PlaceInfo): Jump? {
 | 
			
		||||
    val editor = injector.editorGroup.localEditors().firstOrNull { it.ij.virtualFile == place.file } ?: return null
 | 
			
		||||
    val editor = injector.editorGroup.getEditors().firstOrNull { it.ij.virtualFile == place.file } ?: return null
 | 
			
		||||
    val offset = place.caretPosition?.startOffset ?: return null
 | 
			
		||||
 | 
			
		||||
    val bufferPosition = editor.offsetToBufferPosition(offset)
 | 
			
		||||
 
 | 
			
		||||
@@ -7,6 +7,7 @@
 | 
			
		||||
 */
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeWithMe.ClientId
 | 
			
		||||
import com.intellij.ide.bookmark.Bookmark
 | 
			
		||||
import com.intellij.ide.bookmark.BookmarkGroup
 | 
			
		||||
import com.intellij.ide.bookmark.BookmarksListener
 | 
			
		||||
@@ -18,7 +19,7 @@ import com.intellij.openapi.components.State
 | 
			
		||||
import com.intellij.openapi.components.Storage
 | 
			
		||||
import com.intellij.openapi.diagnostic.Logger
 | 
			
		||||
import com.intellij.openapi.editor.Document
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.EditorFactory
 | 
			
		||||
import com.intellij.openapi.editor.event.DocumentEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.DocumentListener
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
@@ -28,11 +29,11 @@ import com.intellij.openapi.util.text.StringUtil
 | 
			
		||||
import com.intellij.util.asSafely
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditorGroup
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimMarkService
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimMarkServiceBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.group.SystemMarks.Companion.createOrGetSystemMark
 | 
			
		||||
import com.maddyhome.idea.vim.helper.localEditors
 | 
			
		||||
import com.maddyhome.idea.vim.mark.IntellijMark
 | 
			
		||||
import com.maddyhome.idea.vim.mark.Mark
 | 
			
		||||
import com.maddyhome.idea.vim.mark.VimMark.Companion.create
 | 
			
		||||
@@ -193,6 +194,10 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
     * This event indicates that a document is about to be changed. We use this event to update all the
 | 
			
		||||
     * editor's marks if text is about to be deleted.
 | 
			
		||||
     *
 | 
			
		||||
     * Note that the event is fired for both local changes and changes from remote guests in Code With Me scenarios (in
 | 
			
		||||
     * which case [ClientId.current] will be the remote client). We don't care who caused it, we just need to update the
 | 
			
		||||
     * stored marks.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event The change event
 | 
			
		||||
     */
 | 
			
		||||
    override fun beforeDocumentChange(event: DocumentEvent) {
 | 
			
		||||
@@ -200,15 +205,18 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
      if (logger.isDebugEnabled) logger.debug("MarkUpdater before, event = $event")
 | 
			
		||||
      if (event.oldLength == 0) return
 | 
			
		||||
      val doc = event.document
 | 
			
		||||
      val anEditor = getAnEditor(doc) ?: return
 | 
			
		||||
      injector.markService
 | 
			
		||||
        .updateMarksFromDelete(IjVimEditor(anEditor), event.offset, event.oldLength)
 | 
			
		||||
      val anEditor = getAnyEditorForDocument(doc) ?: return
 | 
			
		||||
      injector.markService.updateMarksFromDelete(anEditor, event.offset, event.oldLength)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * This event indicates that a document was just changed. We use this event to update all the editor's
 | 
			
		||||
     * marks if text was just added.
 | 
			
		||||
     *
 | 
			
		||||
     * Note that the event is fired for both local changes and changes from remote guests in Code With Me scenarios (in
 | 
			
		||||
     * which case [ClientId.current] will be the remote client). We don't care who caused it, we just need to update the
 | 
			
		||||
     * stored marks.
 | 
			
		||||
     *
 | 
			
		||||
     * @param event The change event
 | 
			
		||||
     */
 | 
			
		||||
    override fun documentChanged(event: DocumentEvent) {
 | 
			
		||||
@@ -216,19 +224,19 @@ internal class VimMarkServiceImpl : VimMarkServiceBase(), PersistentStateCompone
 | 
			
		||||
      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
 | 
			
		||||
      val anEditor = getAnEditor(doc) ?: return
 | 
			
		||||
      injector.markService
 | 
			
		||||
        .updateMarksFromInsert(IjVimEditor(anEditor), event.offset, event.newLength)
 | 
			
		||||
      val anEditor = getAnyEditorForDocument(doc) ?: return
 | 
			
		||||
      injector.markService.updateMarksFromInsert(anEditor, event.offset, event.newLength)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun getAnEditor(doc: Document): Editor? {
 | 
			
		||||
      val editors = localEditors(doc)
 | 
			
		||||
      return if (editors.size > 0) {
 | 
			
		||||
        editors[0]
 | 
			
		||||
      } else {
 | 
			
		||||
        null
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    /**
 | 
			
		||||
     * Get any editor for the given document
 | 
			
		||||
     *
 | 
			
		||||
     * We need an editor to help calculate offsets for marks, and it doesn't matter which one we use, because they would
 | 
			
		||||
     * all return the same results. However, we cannot use [VimEditorGroup.getEditors] because the change might have
 | 
			
		||||
     * come from a remote guest and there might not be an open local editor.
 | 
			
		||||
     */
 | 
			
		||||
    private fun getAnyEditorForDocument(doc: Document) =
 | 
			
		||||
      EditorFactory.getInstance().getEditors(doc).firstOrNull()?.let { IjVimEditor(it) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  class VimBookmarksListener(private val myProject: Project) : BookmarksListener {
 | 
			
		||||
 
 | 
			
		||||
@@ -8,9 +8,11 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.group
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import org.apache.commons.codec.binary.Base64
 | 
			
		||||
import org.jdom.Element
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class XMLGroup {
 | 
			
		||||
  /**
 | 
			
		||||
   * Set the text of an XML element, safely encode it if needed.
 | 
			
		||||
 
 | 
			
		||||
@@ -14,6 +14,7 @@ import com.intellij.ide.DataManager
 | 
			
		||||
import com.intellij.ide.PasteProvider
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.RangeMarker
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorEx
 | 
			
		||||
@@ -51,6 +52,7 @@ import com.maddyhome.idea.vim.state.mode.isChar
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.isLine
 | 
			
		||||
import java.awt.datatransfer.DataFlavor
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class PutGroup : VimPutBase() {
 | 
			
		||||
 | 
			
		||||
  override fun getProviderForPasteViaIde(
 | 
			
		||||
 
 | 
			
		||||
@@ -20,9 +20,8 @@ import com.maddyhome.idea.vim.helper.exitVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.hasVisualSelection
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inInsertMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inNormalMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isTemplateActive
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimDisabled
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerManager
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.options.OptionConstants
 | 
			
		||||
@@ -30,7 +29,6 @@ import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inNormalMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inSelectMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.options.helpers.IdeaRefactorModeHelper
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeKeep
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.options.helpers.isIdeaRefactorModeSelect
 | 
			
		||||
@@ -55,9 +53,7 @@ internal object IdeaSelectionControl {
 | 
			
		||||
    selectionSource: VimListenerManager.SelectionSource = VimListenerManager.SelectionSource.OTHER,
 | 
			
		||||
  ) {
 | 
			
		||||
    VimVisualTimer.singleTask(editor.vim.mode) { initialMode ->
 | 
			
		||||
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return@singleTask
 | 
			
		||||
      if (editor.isIdeaVimDisabledHere) return@singleTask
 | 
			
		||||
      if (vimDisabled(editor)) return@singleTask
 | 
			
		||||
 | 
			
		||||
      logger.debug("Adjust non-vim selection. Source: $selectionSource, initialMode: $initialMode")
 | 
			
		||||
 | 
			
		||||
@@ -79,7 +75,7 @@ internal object IdeaSelectionControl {
 | 
			
		||||
 | 
			
		||||
        logger.debug("Some carets have selection. State before adjustment: ${editor.vim.mode}")
 | 
			
		||||
 | 
			
		||||
        editor.vim.vimStateMachine.mode = Mode.NORMAL()
 | 
			
		||||
        editor.vim.mode = Mode.NORMAL()
 | 
			
		||||
 | 
			
		||||
        activateMode(editor, chooseSelectionMode(editor, selectionSource, true))
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -121,7 +117,7 @@ internal object IdeaSelectionControl {
 | 
			
		||||
      is Mode.VISUAL -> VimPlugin.getVisualMotion().enterVisualMode(editor.vim, mode.selectionType)
 | 
			
		||||
      is Mode.SELECT -> VimPlugin.getVisualMotion().enterSelectMode(editor.vim, mode.selectionType)
 | 
			
		||||
      is Mode.INSERT -> VimPlugin.getChange()
 | 
			
		||||
        .insertBeforeCursor(editor.vim, injector.executionContextManager.onEditor(editor.vim))
 | 
			
		||||
        .insertBeforeCursor(editor.vim, injector.executionContextManager.getEditorExecutionContext(editor.vim))
 | 
			
		||||
 | 
			
		||||
      is Mode.NORMAL -> Unit
 | 
			
		||||
      else -> error("Unexpected mode: $mode")
 | 
			
		||||
 
 | 
			
		||||
@@ -12,13 +12,13 @@ import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.api.getLineEndForOffset
 | 
			
		||||
import com.maddyhome.idea.vim.api.getLineStartForOffset
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isEndAllowed
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimSelectionStart
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
 | 
			
		||||
 | 
			
		||||
internal fun moveCaretOneCharLeftFromSelectionEnd(editor: Editor, predictedMode: Mode) {
 | 
			
		||||
  if (predictedMode !is Mode.VISUAL) {
 | 
			
		||||
 
 | 
			
		||||
@@ -13,10 +13,10 @@ import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimVisualMotionGroupBase
 | 
			
		||||
import com.maddyhome.idea.vim.command.CommandState
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.command.engine
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @author Alex Plate
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,6 @@ 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.globalOptions
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.key
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
			
		||||
@@ -39,7 +38,6 @@ 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
 | 
			
		||||
 | 
			
		||||
@@ -339,8 +337,9 @@ internal abstract class VimKeyHandler(nextHandler: EditorActionHandler?) : Octop
 | 
			
		||||
 | 
			
		||||
  override fun executeHandler(editor: Editor, caret: Caret?, dataContext: DataContext?) {
 | 
			
		||||
    val enterKey = key(key)
 | 
			
		||||
    val context = injector.executionContextManager.onEditor(editor.vim, dataContext?.vim)
 | 
			
		||||
    KeyHandler.getInstance().handleKey(editor.vim, enterKey, context)
 | 
			
		||||
    val context = dataContext?.vim ?: injector.executionContextManager.getEditorExecutionContext(editor.vim)
 | 
			
		||||
    val keyHandler = KeyHandler.getInstance()
 | 
			
		||||
    keyHandler.handleKey(editor.vim, enterKey, context, keyHandler.keyHandlerState)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isHandlerEnabled(editor: Editor, dataContext: DataContext?): Boolean {
 | 
			
		||||
@@ -362,4 +361,4 @@ internal fun isOctopusEnabled(s: KeyStroke, editor: Editor): Boolean {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal val enableOctopus: Boolean
 | 
			
		||||
  get() = injector.globalOptions().octopushandler
 | 
			
		||||
  get() = injector.application.isOctopusEnabled()
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.diagnostic.thisLogger
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.CaretVisualAttributes
 | 
			
		||||
@@ -18,14 +19,17 @@ 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.common.IsReplaceCharListener
 | 
			
		||||
import com.maddyhome.idea.vim.common.ModeChangeListener
 | 
			
		||||
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.options.EffectiveOptionValueChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.options.helpers.GuiCursorMode
 | 
			
		||||
import com.maddyhome.idea.vim.options.helpers.GuiCursorOptionHelper
 | 
			
		||||
import com.maddyhome.idea.vim.options.helpers.GuiCursorType
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import org.jetbrains.annotations.TestOnly
 | 
			
		||||
import java.awt.Color
 | 
			
		||||
 | 
			
		||||
@@ -85,7 +89,12 @@ private fun Editor.updatePrimaryCaretVisualAttributes() {
 | 
			
		||||
  caretModel.primaryCaret.visualAttributes = AttributesCache.getCaretVisualAttributes(this)
 | 
			
		||||
 | 
			
		||||
  // Make sure the caret is visible as soon as it's set. It might be invisible while blinking
 | 
			
		||||
  (this as? EditorEx)?.setCaretVisible(true)
 | 
			
		||||
  // NOTE: At the moment, this causes project leak in tests
 | 
			
		||||
  // IJPL-928 - this will be fixed in 2024.2
 | 
			
		||||
  // [VERSION UPDATE] 2024.2 - remove if wrapping
 | 
			
		||||
  if (!ApplicationManager.getApplication().isUnitTestMode) {
 | 
			
		||||
    (this as? EditorEx)?.setCaretVisible(true)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun Editor.updateSecondaryCaretsVisualAttributes() {
 | 
			
		||||
@@ -134,3 +143,31 @@ private object AttributesCache {
 | 
			
		||||
 | 
			
		||||
@TestOnly
 | 
			
		||||
internal fun getGuiCursorMode(editor: Editor) = editor.guicursorMode()
 | 
			
		||||
 | 
			
		||||
public class CaretVisualAttributesListener : IsReplaceCharListener, ModeChangeListener {
 | 
			
		||||
  override fun isReplaceCharChanged(editor: VimEditor) {
 | 
			
		||||
    updateCaretsVisual(editor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun modeChanged(editor: VimEditor, oldMode: Mode) {
 | 
			
		||||
    updateCaretsVisual(editor)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun updateCaretsVisual(editor: VimEditor) {
 | 
			
		||||
    if (injector.globalOptions().ideaglobalmode) {
 | 
			
		||||
      updateAllEditorsCaretsVisual()
 | 
			
		||||
    } else {
 | 
			
		||||
      val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
      ijEditor.updateCaretsVisualAttributes()
 | 
			
		||||
      ijEditor.updateCaretsVisualPosition()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public fun updateAllEditorsCaretsVisual() {
 | 
			
		||||
    injector.editorGroup.getEditors().forEach { editor ->
 | 
			
		||||
      val ijEditor = (editor as IjVimEditor).editor
 | 
			
		||||
      ijEditor.updateCaretsVisualAttributes()
 | 
			
		||||
      ijEditor.updateCaretsVisualPosition()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -11,8 +11,8 @@ package com.maddyhome.idea.vim.helper
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.maddyhome.idea.vim.action.change.Extension
 | 
			
		||||
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.newapi.ij
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ModalEntry
 | 
			
		||||
@@ -23,7 +23,7 @@ import javax.swing.KeyStroke
 | 
			
		||||
@Service
 | 
			
		||||
internal class CommandLineHelper : VimCommandLineHelper {
 | 
			
		||||
 | 
			
		||||
  override fun inputString(vimEditor: VimEditor, prompt: String, finishOn: Char?): String? {
 | 
			
		||||
  override fun inputString(vimEditor: VimEditor, context: ExecutionContext, prompt: String, finishOn: Char?): String? {
 | 
			
		||||
    val editor = vimEditor.ij
 | 
			
		||||
    if (vimEditor.vimStateMachine.isDotRepeatInProgress) {
 | 
			
		||||
      val input = Extension.consumeString()
 | 
			
		||||
@@ -53,7 +53,7 @@ internal class CommandLineHelper : VimCommandLineHelper {
 | 
			
		||||
      var text: String? = null
 | 
			
		||||
      // XXX: The Ex entry panel is used only for UI here, its logic might be inappropriate for input()
 | 
			
		||||
      val exEntryPanel = ExEntryPanel.getInstanceWithoutShortcuts()
 | 
			
		||||
      exEntryPanel.activate(editor, injector.executionContextManager.onEditor(editor.vim).ij, prompt.ifEmpty { " " }, "", 1)
 | 
			
		||||
      exEntryPanel.activate(editor, context.ij, prompt.ifEmpty { " " }, "", 1)
 | 
			
		||||
      ModalEntry.activate(editor.vim) { key: KeyStroke ->
 | 
			
		||||
        return@activate when {
 | 
			
		||||
          key.isCloseKeyStroke() -> {
 | 
			
		||||
 
 | 
			
		||||
@@ -20,7 +20,6 @@ import com.maddyhome.idea.vim.options.OptionAccessScope
 | 
			
		||||
import com.maddyhome.idea.vim.options.OptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
 | 
			
		||||
internal val Mode.hasVisualSelection
 | 
			
		||||
  get() = when (this) {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,45 +0,0 @@
 | 
			
		||||
/*
 | 
			
		||||
 * Copyright 2003-2023 The IdeaVim authors
 | 
			
		||||
 *
 | 
			
		||||
 * Use of this source code is governed by an MIT-style
 | 
			
		||||
 * license that can be found in the LICENSE.txt file or at
 | 
			
		||||
 * https://opensource.org/licenses/MIT.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.Document
 | 
			
		||||
import com.intellij.openapi.editor.event.DocumentListener
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.maddyhome.idea.vim.EventFacade
 | 
			
		||||
import com.maddyhome.idea.vim.group.SearchGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
 | 
			
		||||
 | 
			
		||||
internal object DocumentManager {
 | 
			
		||||
  private val docListeners = mutableSetOf<DocumentListener>()
 | 
			
		||||
  private val LISTENER_MARKER = Key<String>("VimlistenerMarker")
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    docListeners += VimMarkServiceImpl.MarkUpdater
 | 
			
		||||
    docListeners += SearchGroup.DocumentSearchListener.INSTANCE
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun addListeners(doc: Document) {
 | 
			
		||||
    val marker = doc.getUserData(LISTENER_MARKER)
 | 
			
		||||
    if (marker != null) return
 | 
			
		||||
 | 
			
		||||
    doc.putUserData(LISTENER_MARKER, "foo")
 | 
			
		||||
    for (docListener in docListeners) {
 | 
			
		||||
      EventFacade.getInstance().addDocumentListener(doc, docListener)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun removeListeners(doc: Document) {
 | 
			
		||||
    doc.getUserData(LISTENER_MARKER) ?: return
 | 
			
		||||
 | 
			
		||||
    doc.putUserData(LISTENER_MARKER, null)
 | 
			
		||||
    for (docListener in docListeners) {
 | 
			
		||||
      EventFacade.getInstance().removeDocumentListener(doc, docListener)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -14,6 +14,7 @@ import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.util.UserDataHolder
 | 
			
		||||
 | 
			
		||||
@Deprecated("Do not use context wrappers, use existing provided contexts. If no context available, use `injector.getExecutionContextManager().getEditorExecutionContext(editor)`")
 | 
			
		||||
internal class EditorDataContext @Deprecated("Please use `init` method") constructor(
 | 
			
		||||
  private val editor: Editor,
 | 
			
		||||
  private val editorContext: DataContext,
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import com.intellij.testFramework.LightVirtualFile;
 | 
			
		||||
import com.maddyhome.idea.vim.api.EngineEditorHelperKt;
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.common.IndentConfig;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimDocument;
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor;
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel;
 | 
			
		||||
import kotlin.Pair;
 | 
			
		||||
@@ -29,6 +30,7 @@ import java.util.Collections;
 | 
			
		||||
import java.util.Comparator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
import static com.maddyhome.idea.vim.api.VimInjectorKt.injector;
 | 
			
		||||
import static java.lang.Integer.max;
 | 
			
		||||
import static java.lang.Integer.min;
 | 
			
		||||
 | 
			
		||||
@@ -197,7 +199,7 @@ public class EditorHelper {
 | 
			
		||||
   * @param file The virtual file get the editor for
 | 
			
		||||
   * @return The matching editor or null if no match was found
 | 
			
		||||
   */
 | 
			
		||||
  public static @Nullable Editor getEditor(final @Nullable VirtualFile file) {
 | 
			
		||||
  public static @Nullable VimEditor getEditor(final @Nullable VirtualFile file) {
 | 
			
		||||
    if (file == null) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -206,23 +208,15 @@ public class EditorHelper {
 | 
			
		||||
    if (doc == null) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    final List<Editor> editors = HelperKt.localEditors(doc);
 | 
			
		||||
    if (editors.size() > 0) {
 | 
			
		||||
      return editors.get(0);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return null;
 | 
			
		||||
    return injector.getEditorGroup().getEditors(new IjVimDocument(doc)).stream().findFirst().orElse(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public static @NotNull String pad(final @NotNull Editor editor,
 | 
			
		||||
                                    @NotNull DataContext context,
 | 
			
		||||
                                    int line,
 | 
			
		||||
                                    final int to) {
 | 
			
		||||
  public static @NotNull String pad(final @NotNull Editor editor, int line, final int to) {
 | 
			
		||||
    final int len = EngineEditorHelperKt.lineLength(new IjVimEditor(editor), line);
 | 
			
		||||
    if (len >= to) return "";
 | 
			
		||||
 | 
			
		||||
    final int limit = to - len;
 | 
			
		||||
    return IndentConfig.create(editor, context).createIndentBySize(limit);
 | 
			
		||||
    return IndentConfig.create(editor).createIndentBySize(limit);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@ import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
			
		||||
import com.intellij.openapi.fileEditor.ex.FileEditorManagerEx
 | 
			
		||||
import com.intellij.util.ui.table.JBTableRowEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.StringListOptionValue
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptionConstants
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.globalIjOptions
 | 
			
		||||
@@ -37,25 +38,27 @@ public val Editor.fileSize: Int
 | 
			
		||||
internal val Editor.isIdeaVimDisabledHere: Boolean
 | 
			
		||||
  get() {
 | 
			
		||||
    val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport
 | 
			
		||||
    return disabledInDialog ||
 | 
			
		||||
      (!ClientId.isCurrentlyUnderLocalId) || // CWM-927
 | 
			
		||||
      (!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline) && isDatabaseCell()) ||
 | 
			
		||||
      (!ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline) && isOneLineMode)
 | 
			
		||||
    return (ideaVimDisabledInDialog(ideaVimSupportValue) && isInDialog()) ||
 | 
			
		||||
      !ClientId.isCurrentlyUnderLocalId || // CWM-927
 | 
			
		||||
      (ideaVimDisabledForSingleLine(ideaVimSupportValue) && isSingleLine())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
private fun Editor.isDatabaseCell(): Boolean {
 | 
			
		||||
  return isTableCellEditor(this.component)
 | 
			
		||||
private fun ideaVimDisabledInDialog(ideaVimSupportValue: StringListOptionValue): Boolean {
 | 
			
		||||
  return !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialog)
 | 
			
		||||
    && !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialoglegacy)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private val Editor.disabledInDialog: Boolean
 | 
			
		||||
  get() {
 | 
			
		||||
    val ideaVimSupportValue = injector.globalIjOptions().ideavimsupport
 | 
			
		||||
    return (
 | 
			
		||||
      !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialog) &&
 | 
			
		||||
        !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_dialoglegacy)
 | 
			
		||||
      ) &&
 | 
			
		||||
      (!this.isPrimaryEditor() && !EditorHelper.isFileEditor(this))
 | 
			
		||||
  }
 | 
			
		||||
private fun ideaVimDisabledForSingleLine(ideaVimSupportValue: StringListOptionValue): Boolean {
 | 
			
		||||
  return !ideaVimSupportValue.contains(IjOptionConstants.ideavimsupport_singleline)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun Editor.isInDialog(): Boolean {
 | 
			
		||||
  return !this.isPrimaryEditor() && !EditorHelper.isFileEditor(this)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private fun Editor.isSingleLine(): Boolean {
 | 
			
		||||
  return isTableCellEditor(this.component) || isOneLineMode
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks if the editor is a primary editor in the main editing area.
 | 
			
		||||
 
 | 
			
		||||
@@ -9,19 +9,12 @@
 | 
			
		||||
package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInsight.template.TemplateManager
 | 
			
		||||
import com.intellij.codeWithMe.ClientId
 | 
			
		||||
import com.intellij.injected.editor.EditorWindow
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.ClientEditorManager
 | 
			
		||||
import com.intellij.openapi.editor.Document
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import com.intellij.openapi.editor.EditorFactory
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.inBlockSelection
 | 
			
		||||
import java.util.stream.Collectors
 | 
			
		||||
 | 
			
		||||
internal fun <T : Comparable<T>> sort(a: T, b: T) = if (a > b) b to a else a to b
 | 
			
		||||
 | 
			
		||||
@@ -36,34 +29,6 @@ internal inline fun Editor.vimForEachCaret(action: (caret: Caret) -> Unit) {
 | 
			
		||||
 | 
			
		||||
internal fun Editor.getTopLevelEditor() = if (this is EditorWindow) this.delegate else this
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Return list of editors for local host (for code with me plugin)
 | 
			
		||||
 */
 | 
			
		||||
public fun localEditors(): List<Editor> {
 | 
			
		||||
  return ClientEditorManager.getCurrentInstance().editors().collect(Collectors.toList())
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun localEditors(doc: Document): List<Editor> {
 | 
			
		||||
  return EditorFactory.getInstance().getEditors(doc)
 | 
			
		||||
    .filter { editor -> editor.editorClientId.let { it == null || it == ClientId.currentOrNull } }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public fun localEditors(doc: Document, project: Project): List<Editor> {
 | 
			
		||||
  return EditorFactory.getInstance().getEditors(doc, project)
 | 
			
		||||
    .filter { editor -> editor.editorClientId.let { it == null || it == ClientId.currentOrNull } }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private val Editor.editorClientId: ClientId?
 | 
			
		||||
  get() {
 | 
			
		||||
    if (editorClientKey == null) {
 | 
			
		||||
      @Suppress("DEPRECATION")
 | 
			
		||||
      editorClientKey = Key.findKeyByName("editorClientIdby userData()") ?: return null
 | 
			
		||||
    }
 | 
			
		||||
    return editorClientKey?.let { this.getUserData(it) as? ClientId }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
private var editorClientKey: Key<*>? = null
 | 
			
		||||
 | 
			
		||||
@Suppress("IncorrectParentDisposable")
 | 
			
		||||
internal fun Editor.isTemplateActive(): Boolean {
 | 
			
		||||
  val project = this.project ?: return false
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,7 @@ 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.actionSystem.impl.Utils
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
import com.intellij.openapi.command.UndoConfirmationPolicy
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
@@ -34,7 +35,6 @@ import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.NativeAction
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimActionExecutor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.command.OperatorArguments
 | 
			
		||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjNativeAction
 | 
			
		||||
@@ -78,6 +78,7 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
    val dataContext = DataContextWrapper(context.ij)
 | 
			
		||||
    dataContext.putUserData(runFromVimKey, true)
 | 
			
		||||
 | 
			
		||||
    val actionId = ActionManager.getInstance().getId(ijAction)
 | 
			
		||||
    val event = AnActionEvent(
 | 
			
		||||
      null,
 | 
			
		||||
      dataContext,
 | 
			
		||||
@@ -86,11 +87,20 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
      ActionManager.getInstance(),
 | 
			
		||||
      0,
 | 
			
		||||
    )
 | 
			
		||||
    Utils.initUpdateSession(event)
 | 
			
		||||
    // beforeActionPerformedUpdate should be called to update the action. It fixes some rider-specific problems.
 | 
			
		||||
    //   because rider use async update method. See VIM-1819.
 | 
			
		||||
    // This method executes inside of lastUpdateAndCheckDumb
 | 
			
		||||
    // Another related issue: VIM-2604
 | 
			
		||||
    if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
 | 
			
		||||
 | 
			
		||||
    // This is a hack to fix the tests and fix VIM-3332
 | 
			
		||||
    // We should get rid of it in VIM-3376
 | 
			
		||||
    if (actionId == "RunClass" || actionId == IdeActions.ACTION_COMMENT_LINE || actionId == IdeActions.ACTION_COMMENT_BLOCK) {
 | 
			
		||||
      ijAction.beforeActionPerformedUpdate(event)
 | 
			
		||||
      if (!event.presentation.isEnabled) return false
 | 
			
		||||
    } else {
 | 
			
		||||
      if (!ActionUtil.lastUpdateAndCheckDumb(ijAction, event, false)) return false
 | 
			
		||||
    }
 | 
			
		||||
    if (ijAction is ActionGroup && !event.presentation.isPerformGroup) {
 | 
			
		||||
      // Some ActionGroups should not be performed, but shown as a popup
 | 
			
		||||
      val popup = JBPopupFactory.getInstance()
 | 
			
		||||
@@ -214,7 +224,7 @@ internal class IjActionExecutor : VimActionExecutor {
 | 
			
		||||
    CommandProcessor.getInstance()
 | 
			
		||||
      .executeCommand(
 | 
			
		||||
        editor.ij.project,
 | 
			
		||||
        { cmd.execute(editor, injector.executionContextManager.onEditor(editor, context), operatorArguments) },
 | 
			
		||||
        { cmd.execute(editor, context, operatorArguments) },
 | 
			
		||||
        cmd.id,
 | 
			
		||||
        DocCommandGroupId.noneGroupId(editor.ij.document),
 | 
			
		||||
        UndoConfirmationPolicy.DEFAULT,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,6 @@ import com.intellij.openapi.editor.VisualPosition
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.EditorActionManager
 | 
			
		||||
import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
			
		||||
import com.maddyhome.idea.vim.api.EngineEditorHelper
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimVisualPosition
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
@@ -51,8 +50,8 @@ internal class IjEditorHelper : EngineEditorHelper {
 | 
			
		||||
    return EditorHelper.getVisualLineAtBottomOfScreen(editor.ij)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun pad(editor: VimEditor, context: ExecutionContext, line: Int, to: Int): String {
 | 
			
		||||
    return EditorHelper.pad(editor.ij, context.ij, line, to)
 | 
			
		||||
  override fun pad(editor: VimEditor, line: Int, to: Int): String {
 | 
			
		||||
    return EditorHelper.pad(editor.ij, line, to)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun inlayAwareOffsetToVisualPosition(editor: VimEditor, offset: Int): VimVisualPosition {
 | 
			
		||||
 
 | 
			
		||||
@@ -34,15 +34,15 @@ internal fun Editor.exitSelectMode(adjustCaretPosition: Boolean) {
 | 
			
		||||
  val returnTo = this.vim.vimStateMachine.mode.returnTo
 | 
			
		||||
  when (returnTo) {
 | 
			
		||||
    ReturnTo.INSERT -> {
 | 
			
		||||
      this.vim.vimStateMachine.mode = Mode.INSERT
 | 
			
		||||
      this.vim.mode = Mode.INSERT
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ReturnTo.REPLACE -> {
 | 
			
		||||
      this.vim.vimStateMachine.mode = Mode.REPLACE
 | 
			
		||||
      this.vim.mode = Mode.REPLACE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    null -> {
 | 
			
		||||
      this.vim.vimStateMachine.mode = Mode.NORMAL()
 | 
			
		||||
      this.vim.mode = Mode.NORMAL()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  SelectionVimListenerSuppressor.lock().use {
 | 
			
		||||
@@ -67,15 +67,15 @@ internal fun VimEditor.exitSelectMode(adjustCaretPosition: Boolean) {
 | 
			
		||||
  val returnTo = this.vimStateMachine.mode.returnTo
 | 
			
		||||
  when (returnTo) {
 | 
			
		||||
    ReturnTo.INSERT -> {
 | 
			
		||||
      this.vimStateMachine.mode = Mode.INSERT
 | 
			
		||||
      this.mode = Mode.INSERT
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    ReturnTo.REPLACE -> {
 | 
			
		||||
      this.vimStateMachine.mode = Mode.REPLACE
 | 
			
		||||
      this.mode = Mode.REPLACE
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    null -> {
 | 
			
		||||
      this.vimStateMachine.mode = Mode.NORMAL()
 | 
			
		||||
      this.mode = Mode.NORMAL()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  SelectionVimListenerSuppressor.lock().use {
 | 
			
		||||
 
 | 
			
		||||
@@ -26,15 +26,13 @@ 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.regexp.*;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
 | 
			
		||||
import com.maddyhome.idea.vim.common.CharacterPosition;
 | 
			
		||||
import com.maddyhome.idea.vim.common.Direction;
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange;
 | 
			
		||||
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.regexp.*;
 | 
			
		||||
import com.maddyhome.idea.vim.regexp.match.VimMatchResult;
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine;
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode;
 | 
			
		||||
import it.unimi.dsi.fastutil.ints.IntComparator;
 | 
			
		||||
@@ -634,113 +632,6 @@ public class SearchHelper {
 | 
			
		||||
    return new TextRange(bstart, bend + 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static int findMatchingBlockCommentPair(@NotNull PsiComment comment,
 | 
			
		||||
                                                  int pos,
 | 
			
		||||
                                                  @Nullable String prefix,
 | 
			
		||||
                                                  @Nullable String suffix) {
 | 
			
		||||
    if (prefix != null && suffix != null) {
 | 
			
		||||
      // TODO: Try to get rid of `getText()` because it takes a lot of time to calculate the string
 | 
			
		||||
      final String commentText = comment.getText();
 | 
			
		||||
      if (commentText.startsWith(prefix) && commentText.endsWith(suffix)) {
 | 
			
		||||
        final int endOffset = comment.getTextOffset() + comment.getTextLength();
 | 
			
		||||
        if (pos < comment.getTextOffset() + prefix.length()) {
 | 
			
		||||
          return endOffset;
 | 
			
		||||
        }
 | 
			
		||||
        else if (pos >= endOffset - suffix.length()) {
 | 
			
		||||
          return comment.getTextOffset();
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static int findMatchingBlockCommentPair(@NotNull PsiElement element, int pos) {
 | 
			
		||||
    final Language language = element.getLanguage();
 | 
			
		||||
    final Commenter commenter = LanguageCommenters.INSTANCE.forLanguage(language);
 | 
			
		||||
    final PsiComment comment = PsiTreeUtil.getParentOfType(element, PsiComment.class, false);
 | 
			
		||||
    if (comment != null) {
 | 
			
		||||
      final int ret = findMatchingBlockCommentPair(comment, pos, commenter.getBlockCommentPrefix(),
 | 
			
		||||
                                                   commenter.getBlockCommentSuffix());
 | 
			
		||||
      if (ret >= 0) {
 | 
			
		||||
        return ret;
 | 
			
		||||
      }
 | 
			
		||||
      if (commenter instanceof CodeDocumentationAwareCommenter docCommenter) {
 | 
			
		||||
        return findMatchingBlockCommentPair(comment, pos, docCommenter.getDocumentationCommentPrefix(),
 | 
			
		||||
                                            docCommenter.getDocumentationCommentSuffix());
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * This looks on the current line, starting at the cursor position for one of {, }, (, ), [, or ]. It then searches
 | 
			
		||||
   * forward or backward, as appropriate for the associated match pair. String in double quotes are skipped over.
 | 
			
		||||
   * Single characters in single quotes are skipped too.
 | 
			
		||||
   *
 | 
			
		||||
   * @param editor The editor to search in
 | 
			
		||||
   * @return The offset within the editor of the found character or -1 if no match was found or none of the characters
 | 
			
		||||
   * were found on the remainder of the current line.
 | 
			
		||||
   */
 | 
			
		||||
  public static int findMatchingPairOnCurrentLine(@NotNull Editor editor, @NotNull Caret caret) {
 | 
			
		||||
    int pos = caret.getOffset();
 | 
			
		||||
 | 
			
		||||
    final int commentPos = findMatchingComment(editor, pos);
 | 
			
		||||
    if (commentPos >= 0) {
 | 
			
		||||
      return commentPos;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int line = caret.getLogicalPosition().line;
 | 
			
		||||
    final IjVimEditor vimEditor = new IjVimEditor(editor);
 | 
			
		||||
    int end = EngineEditorHelperKt.getLineEndOffset(vimEditor, line, true);
 | 
			
		||||
 | 
			
		||||
    // To handle the case where visual mode allows the user to go past the end of the line,
 | 
			
		||||
    // which will prevent loc from finding a pairable character below
 | 
			
		||||
    if (pos > 0 && pos == end) {
 | 
			
		||||
      pos = end - 1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    final String pairChars = parseMatchPairsOption(vimEditor);
 | 
			
		||||
 | 
			
		||||
    CharSequence chars = editor.getDocument().getCharsSequence();
 | 
			
		||||
    int loc = -1;
 | 
			
		||||
    // Search the remainder of the current line for one of the candidate characters
 | 
			
		||||
    while (pos < end) {
 | 
			
		||||
      loc = pairChars.indexOf(chars.charAt(pos));
 | 
			
		||||
      if (loc >= 0) {
 | 
			
		||||
        break;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      pos++;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    int res = -1;
 | 
			
		||||
    // If we found one ...
 | 
			
		||||
    if (loc >= 0) {
 | 
			
		||||
      // What direction should we go now (-1 is backward, 1 is forward)
 | 
			
		||||
      Direction dir = loc % 2 == 0 ? Direction.FORWARDS : Direction.BACKWARDS;
 | 
			
		||||
      // Which character did we find and which should we now search for
 | 
			
		||||
      char found = pairChars.charAt(loc);
 | 
			
		||||
      char match = pairChars.charAt(loc + dir.toInt());
 | 
			
		||||
      res = findBlockLocation(chars, found, match, dir, pos, 1, true);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return res;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * If on the start/end of a block comment, jump to the matching of that comment, or vice versa.
 | 
			
		||||
   */
 | 
			
		||||
  private static int findMatchingComment(@NotNull Editor editor, int pos) {
 | 
			
		||||
    final PsiFile psiFile = PsiHelper.getFile(editor);
 | 
			
		||||
    if (psiFile != null) {
 | 
			
		||||
      final PsiElement element = psiFile.findElementAt(pos);
 | 
			
		||||
      if (element != null) {
 | 
			
		||||
        return findMatchingBlockCommentPair(element, pos);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return -1;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static int findBlockLocation(@NotNull CharSequence chars,
 | 
			
		||||
                                       char found,
 | 
			
		||||
                                       char match,
 | 
			
		||||
@@ -1603,21 +1494,27 @@ public class SearchHelper {
 | 
			
		||||
        }
 | 
			
		||||
        return true;
 | 
			
		||||
      });
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    if (offsets.isEmpty()) {
 | 
			
		||||
      return -1;
 | 
			
		||||
    }
 | 
			
		||||
    
 | 
			
		||||
 | 
			
		||||
    if (skipCount >= offsets.size()) {
 | 
			
		||||
      return offsets.lastInt();
 | 
			
		||||
    }
 | 
			
		||||
    else {
 | 
			
		||||
      IntIterator offsetIterator = offsets.iterator();
 | 
			
		||||
      offsetIterator.skip(skipCount);
 | 
			
		||||
      skip(offsetIterator, skipCount);
 | 
			
		||||
      return offsetIterator.nextInt();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void skip(IntIterator iterator, final int n) {
 | 
			
		||||
    if (n < 0) throw new IllegalArgumentException("Argument must be nonnegative: " + n);
 | 
			
		||||
    int i = n;
 | 
			
		||||
    while (i-- != 0 && iterator.hasNext()) iterator.nextInt();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static @NotNull String parseMatchPairsOption(final VimEditor vimEditor) {
 | 
			
		||||
    List<String> pairs = options(injector, vimEditor).getMatchpairs();
 | 
			
		||||
    StringBuilder res = new StringBuilder();
 | 
			
		||||
 
 | 
			
		||||
@@ -26,6 +26,7 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.api.options
 | 
			
		||||
import com.maddyhome.idea.vim.common.TextRange
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ranges.LineRange
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimDocument
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.IjVimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import org.jetbrains.annotations.Contract
 | 
			
		||||
@@ -86,11 +87,24 @@ private fun updateSearchHighlights(
 | 
			
		||||
): Int {
 | 
			
		||||
  var currentMatchOffset = -1
 | 
			
		||||
  val projectManager = ProjectManager.getInstanceIfCreated() ?: return currentMatchOffset
 | 
			
		||||
 | 
			
		||||
  // TODO: This implementation needs rethinking
 | 
			
		||||
  // It's a bit weird that we update search highlights across all open projects. It would make more sense to treat top
 | 
			
		||||
  // level project frame windows as separate applications, but we can't do this because IdeaVim does not maintain state
 | 
			
		||||
  // per-project.
 | 
			
		||||
  // So, to be clear, this will loop over each project, and therefore, for each project top-level frame, will update
 | 
			
		||||
  // search highlights in all editors for the document of the currently selected editor. It does not update highlights
 | 
			
		||||
  // for editors for the document that are in other projects.
 | 
			
		||||
 | 
			
		||||
  for (project in projectManager.openProjects) {
 | 
			
		||||
    val current = FileEditorManager.getInstance(project).selectedTextEditor ?: continue
 | 
			
		||||
    // [VERSION UPDATE] 202+ Use editors
 | 
			
		||||
    val editors = localEditors(current.document, project)
 | 
			
		||||
    for (editor in editors) {
 | 
			
		||||
    val editors = injector.editorGroup.getEditors(IjVimDocument(current.document))
 | 
			
		||||
    for (vimEditor in editors) {
 | 
			
		||||
      val editor = (vimEditor as IjVimEditor).editor
 | 
			
		||||
      if (editor.project != project) {
 | 
			
		||||
        continue
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // Try to keep existing highlights if possible. Update if hlsearch has changed or if the pattern has changed.
 | 
			
		||||
      // Force update for the situations where the text is the same, but the ignore case values have changed.
 | 
			
		||||
      // E.g. Use `*` to search for a word (which ignores smartcase), then use `/<Up>` to search for the same pattern,
 | 
			
		||||
 
 | 
			
		||||
@@ -31,4 +31,8 @@ public object StringHelper {
 | 
			
		||||
    return Arrays.stream(string).flatMap { o: String -> injector.parser.parseKeys(o).stream() }
 | 
			
		||||
      .collect(Collectors.toList())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @JvmStatic
 | 
			
		||||
  @Deprecated("Use key.isCloseKeyStroke()", ReplaceWith("key.isCloseKeyStroke()"))
 | 
			
		||||
  public fun isCloseKeyStroke(key: KeyStroke): Boolean = key.isCloseKeyStroke()
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,10 +10,13 @@ package com.maddyhome.idea.vim.helper
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.actionSystem.PlatformDataKeys
 | 
			
		||||
import com.intellij.openapi.application.ApplicationInfo
 | 
			
		||||
import com.intellij.openapi.command.CommandProcessor
 | 
			
		||||
import com.intellij.openapi.command.undo.UndoManager
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.fileEditor.TextEditor
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider
 | 
			
		||||
import com.intellij.openapi.util.registry.Registry
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
@@ -40,23 +43,11 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
      val scrollingModel = editor.getScrollingModel()
 | 
			
		||||
      scrollingModel.accumulateViewportChanges()
 | 
			
		||||
 | 
			
		||||
      if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      // [VERSION UPDATE] 241+ remove this if
 | 
			
		||||
      if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
 | 
			
		||||
        undoFor241plus(editor, undoManager, fileEditor)
 | 
			
		||||
      } else {
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.undo(fileEditor)
 | 
			
		||||
 | 
			
		||||
          // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
          if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
			
		||||
            undoManager.undo(fileEditor)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
          removeSelections(editor)
 | 
			
		||||
        }
 | 
			
		||||
        undoForLessThan241(undoManager, fileEditor, editor)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      scrollingModel.flushViewportChanges()
 | 
			
		||||
@@ -66,6 +57,61 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun undoForLessThan241(
 | 
			
		||||
    undoManager: UndoManager,
 | 
			
		||||
    fileEditor: TextEditor,
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
  ) {if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use { undoManager.undo(fileEditor) }
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      } else {
 | 
			
		||||
        // TODO refactor me after VIM-308 when restoring selection and caret movement will be ignored by undo
 | 
			
		||||
        editor.runWithChangeTracking {
 | 
			
		||||
          undoManager.undo(fileEditor)
 | 
			
		||||
 | 
			
		||||
        // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
        if (!hasChanges && hasSelection(editor) && undoManager.isUndoAvailable(fileEditor)) {
 | 
			
		||||
          undoManager.undo(fileEditor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
        removeSelections(editor)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
  private fun undoFor241plus(
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
    undoManager: UndoManager,
 | 
			
		||||
    fileEditor: TextEditor,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
      // 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)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
        removeSelections(editor)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
 | 
			
		||||
        undoManager.undo(fileEditor)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
        removeSelections(editor)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun hasSelection(editor: VimEditor): Boolean {
 | 
			
		||||
    return editor.primaryCaret().ij.hasSelection()
 | 
			
		||||
  }
 | 
			
		||||
@@ -76,7 +122,23 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    val fileEditor = TextEditorProvider.getInstance().getTextEditor(editor.ij)
 | 
			
		||||
    val undoManager = UndoManager.getInstance(project)
 | 
			
		||||
    if (undoManager.isRedoAvailable(fileEditor)) {
 | 
			
		||||
      if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
      // [VERSION UPDATE] 241+ remove this if
 | 
			
		||||
      if (ApplicationInfo.getInstance().build.baselineVersion >= 241) {
 | 
			
		||||
        redoFor241Plus(undoManager, fileEditor, editor)
 | 
			
		||||
      } else {
 | 
			
		||||
        redoForLessThan241(undoManager, fileEditor, editor)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      return true
 | 
			
		||||
    }
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun redoForLessThan241(
 | 
			
		||||
    undoManager: UndoManager,
 | 
			
		||||
    fileEditor: TextEditor,
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
  ) {if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
        SelectionVimListenerSuppressor.lock().use { undoManager.redo(fileEditor) }
 | 
			
		||||
        restoreVisualMode(editor)
 | 
			
		||||
      } else {
 | 
			
		||||
@@ -88,19 +150,50 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
        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)
 | 
			
		||||
        // We execute undo one more time if the previous one just restored selection
 | 
			
		||||
        if (!hasChanges && hasSelection(editor) && undoManager.isRedoAvailable(fileEditor)) {
 | 
			
		||||
          undoManager.redo(fileEditor)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return true
 | 
			
		||||
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
        removeSelections(editor)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun redoFor241Plus(
 | 
			
		||||
    undoManager: UndoManager,
 | 
			
		||||
    fileEditor: TextEditor,
 | 
			
		||||
    editor: VimEditor,
 | 
			
		||||
  ) {
 | 
			
		||||
    if (injector.globalIjOptions().oldundo) {
 | 
			
		||||
      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)
 | 
			
		||||
      }
 | 
			
		||||
    } else {
 | 
			
		||||
      runWithBooleanRegistryOption("ide.undo.transparent.caret.movement", true) {
 | 
			
		||||
        undoManager.redo(fileEditor)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      CommandProcessor.getInstance().runUndoTransparentAction {
 | 
			
		||||
        removeSelections(editor)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return false
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun removeSelections(editor: VimEditor) {
 | 
			
		||||
@@ -114,6 +207,17 @@ internal class UndoRedoHelper : UndoRedoBase() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun runWithBooleanRegistryOption(option: String, value: Boolean, block: () -> Unit) {
 | 
			
		||||
    val registry = Registry.get(option)
 | 
			
		||||
    val oldValue = registry.asBoolean()
 | 
			
		||||
    registry.setValue(value)
 | 
			
		||||
    try {
 | 
			
		||||
      block()
 | 
			
		||||
    } finally {
 | 
			
		||||
      registry.setValue(oldValue)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun VimEditor.runWithChangeTracking(block: ChangeTracker.() -> Unit) {
 | 
			
		||||
    val tracker = ChangeTracker(this)
 | 
			
		||||
    tracker.block()
 | 
			
		||||
 
 | 
			
		||||
@@ -21,13 +21,13 @@ import com.intellij.openapi.util.UserDataHolder
 | 
			
		||||
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.LocalMarkStorage
 | 
			
		||||
import com.maddyhome.idea.vim.api.SelectionInfo
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VisualChange
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimLeadSelectionOffset
 | 
			
		||||
import com.maddyhome.idea.vim.newapi.vim
 | 
			
		||||
import com.maddyhome.idea.vim.state.VimStateMachine
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.Mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ExOutputPanel
 | 
			
		||||
import kotlin.properties.ReadWriteProperty
 | 
			
		||||
import kotlin.reflect.KProperty
 | 
			
		||||
@@ -99,6 +99,8 @@ internal var Caret.registerStorage: CaretRegisterStorageBase? by userDataCaretTo
 | 
			
		||||
internal var Caret.markStorage: LocalMarkStorage? by userDataCaretToEditor()
 | 
			
		||||
internal var Caret.lastSelectionInfo: SelectionInfo? by userDataCaretToEditor()
 | 
			
		||||
 | 
			
		||||
internal var Editor.vimInitialised: Boolean by userDataOr { false }
 | 
			
		||||
 | 
			
		||||
// ------------------ Editor
 | 
			
		||||
internal fun unInitializeEditor(editor: Editor) {
 | 
			
		||||
  editor.vimLastSelectionType = null
 | 
			
		||||
@@ -106,6 +108,7 @@ internal fun unInitializeEditor(editor: Editor) {
 | 
			
		||||
  editor.vimMorePanel = null
 | 
			
		||||
  editor.vimExOutput = null
 | 
			
		||||
  editor.vimLastHighlighters = null
 | 
			
		||||
  editor.vimInitialised = false
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal var Editor.vimLastSearch: String? by userData()
 | 
			
		||||
 
 | 
			
		||||
@@ -21,6 +21,6 @@ public final class VimIcons {
 | 
			
		||||
  public static final @NotNull Icon YOUTRACK = load("/icons/youtrack.svg");
 | 
			
		||||
 | 
			
		||||
  private static @NotNull Icon load(@NotNull @NonNls String path) {
 | 
			
		||||
    return IconManager.getInstance().getIcon(path, VimIcons.class);
 | 
			
		||||
    return IconManager.getInstance().getIcon(path, VimIcons.class.getClassLoader());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -9,11 +9,19 @@
 | 
			
		||||
package com.maddyhome.idea.vim.inspections
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeInspection.LocalInspectionTool
 | 
			
		||||
import com.intellij.codeInspection.LocalQuickFix
 | 
			
		||||
import com.intellij.codeInspection.ProblemDescriptor
 | 
			
		||||
import com.intellij.codeInspection.ProblemsHolder
 | 
			
		||||
import com.intellij.openapi.project.Project
 | 
			
		||||
import com.intellij.openapi.util.TextRange
 | 
			
		||||
import com.intellij.psi.PsiElement
 | 
			
		||||
import com.intellij.psi.PsiElementVisitor
 | 
			
		||||
import com.intellij.psi.impl.source.tree.LeafPsiElement
 | 
			
		||||
import com.intellij.psi.util.PsiEditorUtil
 | 
			
		||||
import com.maddyhome.idea.vim.extension.ExtensionBeanClass
 | 
			
		||||
import com.maddyhome.idea.vim.extension.VimExtension
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.model.commands.SetCommand
 | 
			
		||||
import com.maddyhome.idea.vim.vimscript.parser.VimscriptParser
 | 
			
		||||
 | 
			
		||||
internal class UsePlugSyntaxInspection : LocalInspectionTool() {
 | 
			
		||||
  override fun getGroupDisplayName(): String {
 | 
			
		||||
@@ -23,11 +31,54 @@ internal class UsePlugSyntaxInspection : LocalInspectionTool() {
 | 
			
		||||
  override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
 | 
			
		||||
    val file = holder.file
 | 
			
		||||
    if (file.name != ".ideavimrc" && file.name != "_ideavimrc") return PsiElementVisitor.EMPTY_VISITOR
 | 
			
		||||
    val plugins = buildPlugins()
 | 
			
		||||
    return object : PsiElementVisitor() {
 | 
			
		||||
      override fun visitElement(element: PsiElement) {
 | 
			
		||||
        if (element !is LeafPsiElement) return
 | 
			
		||||
        holder.registerProblem(element, TextRange.create(10, 20), "Hi there")
 | 
			
		||||
        val myScript = VimscriptParser.parse(element.text)
 | 
			
		||||
        myScript.units.forEach { unit ->
 | 
			
		||||
          if (unit is SetCommand) {
 | 
			
		||||
            val argument = unit.argument
 | 
			
		||||
            val alias = plugins[argument]
 | 
			
		||||
            if (alias != null) {
 | 
			
		||||
              holder.registerProblem(
 | 
			
		||||
                element,
 | 
			
		||||
                unit.rangeInScript.let { TextRange(it.startOffset, it.endOffset - 1) },
 | 
			
		||||
                """
 | 
			
		||||
                  Use `Plug` syntax for defining extensions
 | 
			
		||||
                """.trimIndent(),
 | 
			
		||||
                object : LocalQuickFix {
 | 
			
		||||
                  override fun getFamilyName(): String {
 | 
			
		||||
                    return "Use Plug syntax"
 | 
			
		||||
                  }
 | 
			
		||||
 | 
			
		||||
                  override fun applyFix(p0: Project, p1: ProblemDescriptor) {
 | 
			
		||||
                    val editor = PsiEditorUtil.findEditor(file)
 | 
			
		||||
                    editor?.document?.replaceString(
 | 
			
		||||
                      unit.rangeInScript.startOffset,
 | 
			
		||||
                      unit.rangeInScript.endOffset - 1,
 | 
			
		||||
                      "Plug '$alias'"
 | 
			
		||||
                    )
 | 
			
		||||
                  }
 | 
			
		||||
                }
 | 
			
		||||
              )
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun buildPlugins(): HashMap<String, String> {
 | 
			
		||||
    val res = HashMap<String, String>()
 | 
			
		||||
    VimExtension.EP_NAME.extensions.forEach { extension: ExtensionBeanClass ->
 | 
			
		||||
      val alias = extension.aliases?.first { it.name?.count { it == '/' } == 1 }?.name
 | 
			
		||||
        ?: extension.aliases?.firstOrNull()?.name
 | 
			
		||||
      val name = extension.name
 | 
			
		||||
      if (alias != null && name != null) {
 | 
			
		||||
        res[name] = alias
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    return res
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -8,7 +8,7 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.listener
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.ServiceManager
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
import com.intellij.openapi.editor.Editor
 | 
			
		||||
import org.acejump.session.SessionManager
 | 
			
		||||
 | 
			
		||||
@@ -16,12 +16,11 @@ import org.acejump.session.SessionManager
 | 
			
		||||
 * Key handling for IdeaVim should be updated to editorHandler usage. In this case this class can be safely removed.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@Suppress("DEPRECATION")
 | 
			
		||||
internal interface AceJumpService {
 | 
			
		||||
  fun isActive(editor: Editor): Boolean
 | 
			
		||||
 | 
			
		||||
  companion object {
 | 
			
		||||
    fun getInstance(): AceJumpService? = ServiceManager.getService(AceJumpService::class.java)
 | 
			
		||||
    fun getInstance(): AceJumpService? = ApplicationManager.getApplication().getService(AceJumpService::class.java)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,7 @@ import com.intellij.codeInsight.template.TemplateManagerListener
 | 
			
		||||
import com.intellij.codeInsight.template.impl.TemplateState
 | 
			
		||||
import com.intellij.find.FindModelListener
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionManager
 | 
			
		||||
import com.intellij.openapi.actionSystem.ActionUpdateThread
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnAction
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionEvent
 | 
			
		||||
import com.intellij.openapi.actionSystem.AnActionResult
 | 
			
		||||
@@ -77,7 +78,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (hostEditor != null && action is ChooseItemAction && hostEditor.vimStateMachine?.isRecording == true) {
 | 
			
		||||
      if (hostEditor != null && action is ChooseItemAction && injector.registerGroup.isRecording) {
 | 
			
		||||
        val lookup = LookupManager.getActiveLookup(hostEditor)
 | 
			
		||||
        if (lookup != null) {
 | 
			
		||||
          val charsToRemove = hostEditor.caretModel.primaryCaret.offset - lookup.lookupStart
 | 
			
		||||
@@ -99,7 +100,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
 | 
			
		||||
      val editor = editor
 | 
			
		||||
      if (editor != null) {
 | 
			
		||||
        if (action is ChooseItemAction && editor.vimStateMachine?.isRecording == true) {
 | 
			
		||||
        if (action is ChooseItemAction && injector.registerGroup.isRecording) {
 | 
			
		||||
          val prevDocumentLength = completionPrevDocumentLength
 | 
			
		||||
          val prevDocumentOffset = completionPrevDocumentOffset
 | 
			
		||||
 | 
			
		||||
@@ -126,10 +127,12 @@ internal object IdeaSpecifics {
 | 
			
		||||
            )
 | 
			
		||||
          }
 | 
			
		||||
        ) {
 | 
			
		||||
          val commandState = editor.vim.vimStateMachine
 | 
			
		||||
          commandState.mode = Mode.NORMAL()
 | 
			
		||||
          VimPlugin.getChange().insertBeforeCursor(editor.vim, event.dataContext.vim)
 | 
			
		||||
          KeyHandler.getInstance().reset(editor.vim)
 | 
			
		||||
          editor?.let {
 | 
			
		||||
            val commandState = it.vim.vimStateMachine
 | 
			
		||||
            it.vim.mode = Mode.NORMAL()
 | 
			
		||||
            VimPlugin.getChange().insertBeforeCursor(it.vim, event.dataContext.vim)
 | 
			
		||||
            KeyHandler.getInstance().reset(it.vim)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
        //endregion
 | 
			
		||||
 | 
			
		||||
@@ -179,7 +182,7 @@ internal object IdeaSpecifics {
 | 
			
		||||
          if (editor.vim.inNormalMode) {
 | 
			
		||||
            VimPlugin.getChange().insertBeforeCursor(
 | 
			
		||||
              editor.vim,
 | 
			
		||||
              injector.executionContextManager.onEditor(editor.vim),
 | 
			
		||||
              injector.executionContextManager.getEditorExecutionContext(editor.vim),
 | 
			
		||||
            )
 | 
			
		||||
            KeyHandler.getInstance().reset(editor.vim)
 | 
			
		||||
          }
 | 
			
		||||
@@ -229,5 +232,7 @@ internal class FindActionIdAction : DumbAwareToggleAction() {
 | 
			
		||||
  override fun setSelected(e: AnActionEvent, state: Boolean) {
 | 
			
		||||
    injector.globalIjOptions().trackactionids = !injector.globalIjOptions().trackactionids
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getActionUpdateThread(): ActionUpdateThread = ActionUpdateThread.BGT
 | 
			
		||||
}
 | 
			
		||||
//endregion
 | 
			
		||||
 
 | 
			
		||||
@@ -8,6 +8,7 @@
 | 
			
		||||
 | 
			
		||||
package com.maddyhome.idea.vim.listener
 | 
			
		||||
 | 
			
		||||
import com.intellij.codeWithMe.ClientId
 | 
			
		||||
import com.intellij.ide.ui.UISettings
 | 
			
		||||
import com.intellij.openapi.Disposable
 | 
			
		||||
import com.intellij.openapi.application.ApplicationManager
 | 
			
		||||
@@ -20,6 +21,8 @@ import com.intellij.openapi.editor.EditorKind
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.TypedAction
 | 
			
		||||
import com.intellij.openapi.editor.event.CaretEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.CaretListener
 | 
			
		||||
import com.intellij.openapi.editor.event.DocumentEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.DocumentListener
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorFactoryEvent
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorFactoryListener
 | 
			
		||||
import com.intellij.openapi.editor.event.EditorMouseEvent
 | 
			
		||||
@@ -32,6 +35,7 @@ import com.intellij.openapi.editor.ex.DocumentEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.EditorEventMulticasterEx
 | 
			
		||||
import com.intellij.openapi.editor.ex.FocusChangeListener
 | 
			
		||||
import com.intellij.openapi.editor.impl.EditorComponentImpl
 | 
			
		||||
import com.intellij.openapi.editor.impl.EditorImpl
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManager
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
 | 
			
		||||
import com.intellij.openapi.fileEditor.FileEditorManagerListener
 | 
			
		||||
@@ -42,13 +46,17 @@ import com.intellij.openapi.fileEditor.ex.FileEditorWithProvider
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorComposite
 | 
			
		||||
import com.intellij.openapi.fileEditor.impl.EditorWindow
 | 
			
		||||
import com.intellij.openapi.project.ProjectManager
 | 
			
		||||
import com.intellij.openapi.rd.createLifetime
 | 
			
		||||
import com.intellij.openapi.rd.createNestedDisposable
 | 
			
		||||
import com.intellij.openapi.util.Disposer
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.util.removeUserData
 | 
			
		||||
import com.intellij.openapi.vfs.VirtualFile
 | 
			
		||||
import com.intellij.util.ExceptionUtil
 | 
			
		||||
import com.jetbrains.rd.util.lifetime.Lifetime
 | 
			
		||||
import com.maddyhome.idea.vim.EventFacade
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandler
 | 
			
		||||
import com.maddyhome.idea.vim.KeyHandlerStateResetter
 | 
			
		||||
import com.maddyhome.idea.vim.VimKeyListener
 | 
			
		||||
import com.maddyhome.idea.vim.VimPlugin
 | 
			
		||||
import com.maddyhome.idea.vim.VimTypedActionHandler
 | 
			
		||||
@@ -62,17 +70,17 @@ import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
import com.maddyhome.idea.vim.ex.ExOutputModel
 | 
			
		||||
import com.maddyhome.idea.vim.group.EditorGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.FileGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.IjOptions
 | 
			
		||||
import com.maddyhome.idea.vim.group.MotionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.OptionGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.ScrollGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.SearchGroup
 | 
			
		||||
import com.maddyhome.idea.vim.group.VimMarkServiceImpl
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.IdeaSelectionControl
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VimVisualTimer
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.moveCaretOneCharLeftFromSelectionEnd
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.vimSetSystemSelectionSilently
 | 
			
		||||
import com.maddyhome.idea.vim.handler.correctorRequester
 | 
			
		||||
import com.maddyhome.idea.vim.handler.keyCheckRequests
 | 
			
		||||
import com.maddyhome.idea.vim.helper.CaretVisualAttributesListener
 | 
			
		||||
import com.maddyhome.idea.vim.helper.GuicursorChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.helper.StrictMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.exitSelectMode
 | 
			
		||||
@@ -80,27 +88,23 @@ import com.maddyhome.idea.vim.helper.exitVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.forceBarCursor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.inVisualMode
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isEndAllowed
 | 
			
		||||
import com.maddyhome.idea.vim.helper.isIdeaVimDisabledHere
 | 
			
		||||
import com.maddyhome.idea.vim.helper.localEditors
 | 
			
		||||
import com.maddyhome.idea.vim.helper.moveToInlayAwareOffset
 | 
			
		||||
import com.maddyhome.idea.vim.helper.resetVimLastColumn
 | 
			
		||||
import com.maddyhome.idea.vim.helper.updateCaretsVisualAttributes
 | 
			
		||||
import com.maddyhome.idea.vim.helper.vimDisabled
 | 
			
		||||
import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipEvents
 | 
			
		||||
import com.maddyhome.idea.vim.listener.MouseEventsDataHolder.skipNDragEvents
 | 
			
		||||
import com.maddyhome.idea.vim.listener.VimListenerManager.EditorListeners.add
 | 
			
		||||
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.state.mode.inSelectMode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.mode
 | 
			
		||||
import com.maddyhome.idea.vim.state.mode.selectionType
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ShowCmdOptionChangeListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ShowCmdWidgetUpdater
 | 
			
		||||
import com.maddyhome.idea.vim.ui.ex.ExEntryPanel
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.macro.MacroWidgetListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.macro.macroWidgetOptionListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.listeners.ModeWidgetListener
 | 
			
		||||
import com.maddyhome.idea.vim.ui.widgets.mode.modeWidgetOptionListener
 | 
			
		||||
import com.maddyhome.idea.vim.vimDisposable
 | 
			
		||||
import java.awt.event.MouseAdapter
 | 
			
		||||
import java.awt.event.MouseEvent
 | 
			
		||||
import javax.swing.SwingUtilities
 | 
			
		||||
@@ -130,6 +134,7 @@ private fun getOpeningEditor(newEditor: Editor) = newEditor.project?.let { proje
 | 
			
		||||
internal object VimListenerManager {
 | 
			
		||||
 | 
			
		||||
  private val logger = Logger.getInstance(VimListenerManager::class.java)
 | 
			
		||||
  private val editorListenersDisposableKey = Key.create<Disposable>("IdeaVim listeners disposable")
 | 
			
		||||
  private var firstEditorInitialised = false
 | 
			
		||||
 | 
			
		||||
  fun turnOn() {
 | 
			
		||||
@@ -137,11 +142,30 @@ internal object VimListenerManager {
 | 
			
		||||
    EditorListeners.addAll()
 | 
			
		||||
    check(correctorRequester.tryEmit(Unit))
 | 
			
		||||
    check(keyCheckRequests.tryEmit(Unit))
 | 
			
		||||
 | 
			
		||||
    val caretVisualAttributesListener = CaretVisualAttributesListener()
 | 
			
		||||
    injector.listenersNotifier.modeChangeListeners.add(caretVisualAttributesListener)
 | 
			
		||||
    injector.listenersNotifier.isReplaceCharListeners.add(caretVisualAttributesListener)
 | 
			
		||||
    caretVisualAttributesListener.updateAllEditorsCaretsVisual()
 | 
			
		||||
 | 
			
		||||
    val modeWidgetListener = ModeWidgetListener()
 | 
			
		||||
    injector.listenersNotifier.modeChangeListeners.add(modeWidgetListener)
 | 
			
		||||
    injector.listenersNotifier.myEditorListeners.add(modeWidgetListener)
 | 
			
		||||
    injector.listenersNotifier.vimPluginListeners.add(modeWidgetListener)
 | 
			
		||||
 | 
			
		||||
    val macroWidgetListener = MacroWidgetListener()
 | 
			
		||||
    injector.listenersNotifier.macroRecordingListeners.add(macroWidgetListener)
 | 
			
		||||
    injector.listenersNotifier.vimPluginListeners.add(macroWidgetListener)
 | 
			
		||||
 | 
			
		||||
    injector.listenersNotifier.myEditorListeners.add(KeyHandlerStateResetter())
 | 
			
		||||
    injector.listenersNotifier.myEditorListeners.add(ShowCmdWidgetUpdater())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  fun turnOff() {
 | 
			
		||||
    GlobalListeners.disable()
 | 
			
		||||
    EditorListeners.removeAll()
 | 
			
		||||
    injector.listenersNotifier.reset()
 | 
			
		||||
 | 
			
		||||
    check(correctorRequester.tryEmit(Unit))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -159,23 +183,29 @@ internal object VimListenerManager {
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.number, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
			
		||||
 | 
			
		||||
      // This code is executed after ideavimrc execution, so we trigger onGlobalOptionChanged just in case
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener)
 | 
			
		||||
      optionGroup.addGlobalOptionChangeListener(Options.showmode, macroWidgetOptionListener)
 | 
			
		||||
      modeWidgetOptionListener.onGlobalOptionChanged()
 | 
			
		||||
      macroWidgetOptionListener.onGlobalOptionChanged()
 | 
			
		||||
 | 
			
		||||
      optionGroup.addEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
			
		||||
 | 
			
		||||
      // Listen for and initialise new editors
 | 
			
		||||
      EventFacade.getInstance().addEditorFactoryListener(VimEditorFactoryListener, VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
      val busConnection = ApplicationManager.getApplication().messageBus.connect(VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
      busConnection.subscribe(FileOpenedSyncListener.TOPIC, VimEditorFactoryListener)
 | 
			
		||||
 | 
			
		||||
      EditorFactory.getInstance().eventMulticaster.addCaretListener(VimCaretListener, VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
      val eventMulticaster = EditorFactory.getInstance().eventMulticaster as? EditorEventMulticasterEx
 | 
			
		||||
      eventMulticaster?.addFocusChangeListener(VimFocusListener, VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
      // Listen for focus change to update various features such as mode widget
 | 
			
		||||
      val eventMulticaster = EditorFactory.getInstance().eventMulticaster
 | 
			
		||||
      (eventMulticaster as? EditorEventMulticasterEx)?.addFocusChangeListener(
 | 
			
		||||
        VimFocusListener,
 | 
			
		||||
        VimPlugin.getInstance().onOffDisposable
 | 
			
		||||
      )
 | 
			
		||||
 | 
			
		||||
      // Listen for document changes to update document state such as marks
 | 
			
		||||
      eventMulticaster.addDocumentListener(VimDocumentListener, VimPlugin.getInstance().onOffDisposable)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun disable() {
 | 
			
		||||
@@ -186,8 +216,8 @@ internal object VimListenerManager {
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.relativenumber, EditorGroup.NumberChangeListener.INSTANCE)
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.scrolloff, ScrollGroup.ScrollOptionsChangeListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(Options.showcmd, ShowCmdOptionChangeListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, modeWidgetOptionListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(IjOptions.showmodewidget, macroWidgetOptionListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(Options.showmode, modeWidgetOptionListener)
 | 
			
		||||
      optionGroup.removeGlobalOptionChangeListener(Options.showmode, macroWidgetOptionListener)
 | 
			
		||||
      optionGroup.removeEffectiveOptionValueChangeListener(Options.guicursor, GuicursorChangeListener)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
@@ -216,7 +246,9 @@ internal object VimListenerManager {
 | 
			
		||||
 | 
			
		||||
      // We could have a split window in this list, but since they're all being initialised from the same opening editor
 | 
			
		||||
      // there's no need to use the SPLIT scenario
 | 
			
		||||
      localEditors().forEach { editor ->
 | 
			
		||||
      // Make sure we get all editors, including uninitialised
 | 
			
		||||
      injector.editorGroup.getEditorsRaw().forEach { vimEditor ->
 | 
			
		||||
        val editor = vimEditor.ij
 | 
			
		||||
        if (!initialisedEditors.contains(editor)) {
 | 
			
		||||
          add(editor, getOpeningEditor(editor)?.vim ?: injector.fallbackWindow, LocalOptionInitialisationScenario.NEW)
 | 
			
		||||
        }
 | 
			
		||||
@@ -224,18 +256,24 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun removeAll() {
 | 
			
		||||
      localEditors().forEach { editor ->
 | 
			
		||||
        remove(editor, false)
 | 
			
		||||
      injector.editorGroup.getEditors().forEach { editor ->
 | 
			
		||||
        remove(editor.ij)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun add(editor: Editor, openingEditor: VimEditor, scenario: LocalOptionInitialisationScenario) {
 | 
			
		||||
      // As I understand, there is no need to pass a disposable that also disposes on editor close
 | 
			
		||||
      //   because all editor resources will be garbage collected anyway on editor close
 | 
			
		||||
      val disposable = editor.project?.vimDisposable ?: return
 | 
			
		||||
      // We shouldn't be called with anything other than local editors, but let's just be sure. This will prevent any
 | 
			
		||||
      // unsupported editor from incorrectly being initialised.
 | 
			
		||||
      // TODO: If the user changes the 'ideavimsupport' option, existing editors won't be initialised
 | 
			
		||||
      if (vimDisabled(editor)) return
 | 
			
		||||
 | 
			
		||||
      val pluginLifetime = VimPlugin.getInstance().createLifetime()
 | 
			
		||||
      val editorLifetime = (editor as EditorImpl).disposable.createLifetime()
 | 
			
		||||
      val disposable =
 | 
			
		||||
        Lifetime.intersect(pluginLifetime, editorLifetime).createNestedDisposable("MyLifetimedDisposable")
 | 
			
		||||
 | 
			
		||||
      val listenersDisposable = Disposer.newDisposable(disposable)
 | 
			
		||||
      editor.putUserData(editorListenersDisposable, listenersDisposable)
 | 
			
		||||
      editor.putUserData(editorListenersDisposableKey, listenersDisposable)
 | 
			
		||||
 | 
			
		||||
      Disposer.register(listenersDisposable) {
 | 
			
		||||
        if (VimListenerTestObject.enabled) {
 | 
			
		||||
@@ -258,60 +296,75 @@ internal object VimListenerManager {
 | 
			
		||||
      eventFacade.addCaretListener(editor, EditorCaretHandler, listenersDisposable)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getEditor().editorCreated(editor)
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getChange().editorCreated(editor, listenersDisposable)
 | 
			
		||||
 | 
			
		||||
      injector.listenersNotifier.notifyEditorCreated(vimEditor)
 | 
			
		||||
 | 
			
		||||
      Disposer.register(listenersDisposable) {
 | 
			
		||||
        VimPlugin.getEditorIfCreated()?.editorDeinit(editor, true)
 | 
			
		||||
        VimPlugin.getEditor().editorDeinit(editor, true)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun remove(editor: Editor, isReleased: Boolean) {
 | 
			
		||||
      val editorDisposable = editor.getUserData(editorListenersDisposable)
 | 
			
		||||
    fun remove(editor: Editor) {
 | 
			
		||||
      val editorDisposable = editor.removeUserData(editorListenersDisposableKey)
 | 
			
		||||
      if (editorDisposable != null) {
 | 
			
		||||
        Disposer.dispose(editorDisposable)
 | 
			
		||||
      }
 | 
			
		||||
      else StrictMode.fail("Editor doesn't have disposable attached. $editor")
 | 
			
		||||
 | 
			
		||||
      VimPlugin.getEditorIfCreated()?.editorDeinit(editor, isReleased)
 | 
			
		||||
      else {
 | 
			
		||||
        // We definitely do not expect this to happen
 | 
			
		||||
        StrictMode.fail("Editor doesn't have disposable attached. $editor")
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
  
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Notifies other IdeaVim components of focus gain/loss, e.g. the mode widget. This will be called with non-local Code
 | 
			
		||||
   * With Me editors.
 | 
			
		||||
   */
 | 
			
		||||
  private object VimFocusListener : FocusChangeListener {
 | 
			
		||||
    override fun focusGained(editor: Editor) {
 | 
			
		||||
      if (vimDisabled(editor)) return
 | 
			
		||||
      injector.listenersNotifier.notifyEditorFocusGained(editor.vim)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun focusLost(editor: Editor) {
 | 
			
		||||
      if (vimDisabled(editor)) return
 | 
			
		||||
      injector.listenersNotifier.notifyEditorFocusLost(editor.vim)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  val editorListenersDisposable = Key.create<Disposable>("IdeaVim listeners disposable")
 | 
			
		||||
 | 
			
		||||
  object VimCaretListener : CaretListener {
 | 
			
		||||
    override fun caretAdded(event: CaretEvent) {
 | 
			
		||||
      if (vimDisabled(event.editor)) return
 | 
			
		||||
      event.editor.updateCaretsVisualAttributes()
 | 
			
		||||
  /**
 | 
			
		||||
   * Notifies other IdeaVim components of document changes. This will be called for all documents, even those only
 | 
			
		||||
   * open in non-local Code With Me guest editors, which we still want to process (e.g. to update marks when a guest
 | 
			
		||||
   * edits a file. Updating search highlights will be a no-op if there are no open local editors)
 | 
			
		||||
   */
 | 
			
		||||
  private object VimDocumentListener : DocumentListener {
 | 
			
		||||
    override fun beforeDocumentChange(event: DocumentEvent) {
 | 
			
		||||
      VimMarkServiceImpl.MarkUpdater.beforeDocumentChange(event)
 | 
			
		||||
      SearchGroup.DocumentSearchListener.INSTANCE.beforeDocumentChange(event)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun caretRemoved(event: CaretEvent) {
 | 
			
		||||
      if (vimDisabled(event.editor)) return
 | 
			
		||||
      event.editor.updateCaretsVisualAttributes()
 | 
			
		||||
    override fun documentChanged(event: DocumentEvent) {
 | 
			
		||||
      VimMarkServiceImpl.MarkUpdater.documentChanged(event)
 | 
			
		||||
      SearchGroup.DocumentSearchListener.INSTANCE.documentChanged(event)
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Called when the selected file editor changes. In other words, when the user selects a new tab. Used to remember the
 | 
			
		||||
   * last selected file, update search highlights in the new tab, etc. This will be called with non-local Code With Me
 | 
			
		||||
   * guest editors.
 | 
			
		||||
   */
 | 
			
		||||
  class VimFileEditorManagerListener : FileEditorManagerListener {
 | 
			
		||||
    override fun selectionChanged(event: FileEditorManagerEvent) {
 | 
			
		||||
      if (VimPlugin.isNotEnabled()) return
 | 
			
		||||
      // We can't rely on being passed a non-null editor, so check for Code With Me scenarios explicitly
 | 
			
		||||
      if (VimPlugin.isNotEnabled() || !ClientId.isCurrentlyUnderLocalId) return
 | 
			
		||||
      
 | 
			
		||||
      val newEditor = event.newEditor
 | 
			
		||||
      if (newEditor is TextEditor) {
 | 
			
		||||
        val editor = newEditor.editor
 | 
			
		||||
        if (editor.isInsertMode) {
 | 
			
		||||
          VimStateMachine.getInstance(editor).mode = Mode.NORMAL()
 | 
			
		||||
          editor.vim.mode = Mode.NORMAL()
 | 
			
		||||
          KeyHandler.getInstance().reset(editor.vim)
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -323,6 +376,10 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Listen to editor creation events in order to initialise IdeaVim compatible editors. This listener is called for all
 | 
			
		||||
   * editors, including non-local hidden Code With Me editors.
 | 
			
		||||
   */
 | 
			
		||||
  private object VimEditorFactoryListener : EditorFactoryListener, FileOpenedSyncListener {
 | 
			
		||||
    private data class OpeningEditor(
 | 
			
		||||
      val editor: Editor,
 | 
			
		||||
@@ -334,6 +391,8 @@ internal object VimListenerManager {
 | 
			
		||||
    private val openingEditorKey: Key<OpeningEditor> = Key("IdeaVim::OpeningEditor")
 | 
			
		||||
 | 
			
		||||
    override fun editorCreated(event: EditorFactoryEvent) {
 | 
			
		||||
      if (vimDisabled(event.editor)) return
 | 
			
		||||
 | 
			
		||||
      // This callback is called when an editor is created, but we cannot completely rely on it to initialise options.
 | 
			
		||||
      // We can find the currently selected editor, which we can use as the opening editor, and we're given the new
 | 
			
		||||
      // editor, but we don't know enough about it - this function is called before the new editor is added to an
 | 
			
		||||
@@ -354,7 +413,7 @@ internal object VimListenerManager {
 | 
			
		||||
          openingEditor == null -> LocalOptionInitialisationScenario.EDIT
 | 
			
		||||
          else -> LocalOptionInitialisationScenario.NEW
 | 
			
		||||
        }
 | 
			
		||||
        add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario)
 | 
			
		||||
        EditorListeners.add(event.editor, openingEditor?.vim ?: injector.fallbackWindow, scenario)
 | 
			
		||||
        firstEditorInitialised = true
 | 
			
		||||
      }
 | 
			
		||||
      else {
 | 
			
		||||
@@ -383,6 +442,7 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun editorReleased(event: EditorFactoryEvent) {
 | 
			
		||||
      if (vimDisabled(event.editor)) return
 | 
			
		||||
      val vimEditor = event.editor.vim
 | 
			
		||||
      injector.listenersNotifier.notifyEditorReleased(vimEditor)
 | 
			
		||||
      injector.markService.editorReleased(vimEditor)
 | 
			
		||||
@@ -406,6 +466,8 @@ internal object VimListenerManager {
 | 
			
		||||
      // editor is modified
 | 
			
		||||
      editorsWithProviders.forEach {
 | 
			
		||||
        (it.fileEditor as? TextEditor)?.editor?.let { editor ->
 | 
			
		||||
          if (vimDisabled(editor)) return@let
 | 
			
		||||
 | 
			
		||||
          val openingEditor = editor.removeUserData(openingEditorKey)
 | 
			
		||||
          val owningEditorWindow = getOwningEditorWindow(editor)
 | 
			
		||||
          val isInSameSplit = owningEditorWindow == openingEditor?.owningEditorWindow
 | 
			
		||||
@@ -426,7 +488,7 @@ internal object VimListenerManager {
 | 
			
		||||
            (openingEditor.canBeReused || openingEditor.isPreview) && isInSameSplit && openingEditorIsClosed -> LocalOptionInitialisationScenario.EDIT
 | 
			
		||||
            else -> LocalOptionInitialisationScenario.NEW
 | 
			
		||||
          }
 | 
			
		||||
          add(editor, openingEditor?.editor?.vim ?: injector.fallbackWindow, scenario)
 | 
			
		||||
          EditorListeners.add(editor, openingEditor?.editor?.vim ?: injector.fallbackWindow, scenario)
 | 
			
		||||
          firstEditorInitialised = true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -441,14 +503,16 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private object EditorSelectionHandler : SelectionListener {
 | 
			
		||||
    private var myMakingChanges = false
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Callback for when an editor's text selection changes. Only registered for editors that we're interested in (so only
 | 
			
		||||
   * local editors). Fixes incorrect mouse selection at end of line, and synchronises selections across other editors.
 | 
			
		||||
   */
 | 
			
		||||
  private object EditorSelectionHandler : SelectionListener {
 | 
			
		||||
    /**
 | 
			
		||||
     * This event is executed for each caret using [com.intellij.openapi.editor.CaretModel.runForEachCaret]
 | 
			
		||||
     */
 | 
			
		||||
    override fun selectionChanged(selectionEvent: SelectionEvent) {
 | 
			
		||||
      if (selectionEvent.editor.isIdeaVimDisabledHere) return
 | 
			
		||||
      VimVisualTimer.drop()
 | 
			
		||||
      val editor = selectionEvent.editor
 | 
			
		||||
      val document = editor.document
 | 
			
		||||
@@ -468,20 +532,22 @@ internal object VimListenerManager {
 | 
			
		||||
      val startOffset = selectionEvent.newRange.startOffset
 | 
			
		||||
      val endOffset = selectionEvent.newRange.endOffset
 | 
			
		||||
 | 
			
		||||
      if (skipNDragEvents < skipEvents && lineStart != lineEnd && startOffset == caretOffset) {
 | 
			
		||||
      // TODO: It is very confusing that this logic is split between EditorSelectionHandler and EditorMouseHandler
 | 
			
		||||
      if (MouseEventsDataHolder.dragEventCount < MouseEventsDataHolder.allowedSkippedDragEvents
 | 
			
		||||
        && lineStart != lineEnd && startOffset == caretOffset) {
 | 
			
		||||
        if (lineEnd == endOffset - 1) {
 | 
			
		||||
          // When starting on an empty line and dragging vertically upwards onto
 | 
			
		||||
          // another line, the selection should include the entirety of the empty line
 | 
			
		||||
          caret.setSelection(
 | 
			
		||||
            ijVimEditor.coerceOffset(endOffset + 1).point,
 | 
			
		||||
            ijVimEditor.coerceOffset(startOffset).point,
 | 
			
		||||
            ijVimEditor.coerceOffset(endOffset + 1),
 | 
			
		||||
            ijVimEditor.coerceOffset(startOffset),
 | 
			
		||||
          )
 | 
			
		||||
        } else if (lineEnd == startOffset + 1 && startOffset == endOffset) {
 | 
			
		||||
          // When dragging left from EOL on a non-empty line, the selection
 | 
			
		||||
          // should include the last character on the line
 | 
			
		||||
          caret.setSelection(
 | 
			
		||||
            ijVimEditor.coerceOffset(lineEnd).point,
 | 
			
		||||
            ijVimEditor.coerceOffset(lineEnd - 1).point,
 | 
			
		||||
            ijVimEditor.coerceOffset(lineEnd),
 | 
			
		||||
            ijVimEditor.coerceOffset(lineEnd - 1),
 | 
			
		||||
          )
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@@ -492,32 +558,30 @@ internal object VimListenerManager {
 | 
			
		||||
        IdeaSelectionControl.controlNonVimSelectionChange(editor)
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (myMakingChanges || document is DocumentEx && document.isInEventsHandling) {
 | 
			
		||||
      if (document is DocumentEx && document.isInEventsHandling) {
 | 
			
		||||
        return
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      myMakingChanges = true
 | 
			
		||||
      try {
 | 
			
		||||
        // Synchronize selections between editors
 | 
			
		||||
        val newRange = selectionEvent.newRange
 | 
			
		||||
        for (e in localEditors(document)) {
 | 
			
		||||
          if (e != editor) {
 | 
			
		||||
            e.selectionModel.vimSetSystemSelectionSilently(newRange.startOffset, newRange.endOffset)
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      } finally {
 | 
			
		||||
        myMakingChanges = false
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Listener for mouse events registered with editors that we are interested (so only local editors). Responsible for:
 | 
			
		||||
   * * Hiding ex entry and output panels when clicking inside editor area (but not when right-clicking)
 | 
			
		||||
   * * Removing secondary carets on mouse click (such as visual block selection)
 | 
			
		||||
   * * Exiting visual or select mode on mouse click
 | 
			
		||||
   * * Resets the dragEventCount on mouse press + release
 | 
			
		||||
   * * Fix up Vim selected mode on mouse release, after dragging
 | 
			
		||||
   * * Force bar cursor while dragging, which looks better because IntelliJ selects a character once selection has got
 | 
			
		||||
   *   over halfway through the char, while Vim selects when it enters the character bounding box
 | 
			
		||||
   *
 | 
			
		||||
   * @see ComponentMouseListener
 | 
			
		||||
   */
 | 
			
		||||
  // TODO: Can we merge this with ComponentMouseListener to fully handle all mouse actions in one place?
 | 
			
		||||
  private object EditorMouseHandler : EditorMouseListener, EditorMouseMotionListener {
 | 
			
		||||
    private var mouseDragging = false
 | 
			
		||||
    private var cutOffFixed = false
 | 
			
		||||
 | 
			
		||||
    override fun mouseDragged(e: EditorMouseEvent) {
 | 
			
		||||
      if (e.editor.isIdeaVimDisabledHere) return
 | 
			
		||||
 | 
			
		||||
      val caret = e.editor.caretModel.primaryCaret
 | 
			
		||||
 | 
			
		||||
      clearFirstSelectionEvents(e)
 | 
			
		||||
@@ -569,7 +633,7 @@ internal object VimListenerManager {
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      skipNDragEvents -= 1
 | 
			
		||||
      MouseEventsDataHolder.dragEventCount -= 1
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
@@ -584,7 +648,7 @@ internal object VimListenerManager {
 | 
			
		||||
     * (Both with mouse and with v$. IdeaVim treats v$ as an exclusive selection)
 | 
			
		||||
     */
 | 
			
		||||
    private fun clearFirstSelectionEvents(e: EditorMouseEvent) {
 | 
			
		||||
      if (skipNDragEvents > 0) {
 | 
			
		||||
      if (MouseEventsDataHolder.dragEventCount > 0) {
 | 
			
		||||
        logger.debug("Mouse dragging")
 | 
			
		||||
        VimVisualTimer.swingTimer?.stop()
 | 
			
		||||
        if (!mouseDragging) {
 | 
			
		||||
@@ -610,9 +674,7 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mousePressed(event: EditorMouseEvent) {
 | 
			
		||||
      if (event.editor.isIdeaVimDisabledHere) return
 | 
			
		||||
 | 
			
		||||
      skipNDragEvents = skipEvents
 | 
			
		||||
      MouseEventsDataHolder.dragEventCount = MouseEventsDataHolder.allowedSkippedDragEvents
 | 
			
		||||
      SelectionVimListenerSuppressor.reset()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -623,12 +685,10 @@ internal object VimListenerManager {
 | 
			
		||||
     * - Click-hold and switch editor (ctrl-tab)
 | 
			
		||||
     */
 | 
			
		||||
    override fun mouseReleased(event: EditorMouseEvent) {
 | 
			
		||||
      if (event.editor.isIdeaVimDisabledHere) return
 | 
			
		||||
 | 
			
		||||
      SelectionVimListenerSuppressor.unlock()
 | 
			
		||||
 | 
			
		||||
      clearFirstSelectionEvents(event)
 | 
			
		||||
      skipNDragEvents = skipEvents
 | 
			
		||||
      MouseEventsDataHolder.dragEventCount = MouseEventsDataHolder.allowedSkippedDragEvents
 | 
			
		||||
      if (mouseDragging) {
 | 
			
		||||
        logger.debug("Release mouse after dragging")
 | 
			
		||||
        val editor = event.editor
 | 
			
		||||
@@ -648,7 +708,6 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun mouseClicked(event: EditorMouseEvent) {
 | 
			
		||||
      if (event.editor.isIdeaVimDisabledHere) return
 | 
			
		||||
      logger.debug("Mouse clicked")
 | 
			
		||||
 | 
			
		||||
      if (event.area == EditorMouseEventArea.EDITING_AREA) {
 | 
			
		||||
@@ -694,13 +753,21 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * A mouse listener registered to the editor component for editors that we are interested in (so only local editors).
 | 
			
		||||
   * Fixes some issues with mouse selection, namely:
 | 
			
		||||
   * * Clicking at the end of the line will place the caret on the last character rather than after it
 | 
			
		||||
   * * Double-clicking a word will place the caret on the last character rather than after it
 | 
			
		||||
   *
 | 
			
		||||
   * @see EditorMouseHandler
 | 
			
		||||
   */
 | 
			
		||||
  // TODO: Can we merge this with ComponentMouseListener to fully handle all mouse actions in one place?
 | 
			
		||||
  private object ComponentMouseListener : MouseAdapter() {
 | 
			
		||||
 | 
			
		||||
    var cutOffEnd = false
 | 
			
		||||
 | 
			
		||||
    override fun mousePressed(e: MouseEvent?) {
 | 
			
		||||
      val editor = (e?.component as? EditorComponentImpl)?.editor ?: return
 | 
			
		||||
      if (editor.isIdeaVimDisabledHere) return
 | 
			
		||||
      val predictedMode = IdeaSelectionControl.predictMode(editor, SelectionSource.MOUSE)
 | 
			
		||||
      when (e.clickCount) {
 | 
			
		||||
        1 -> {
 | 
			
		||||
@@ -737,10 +804,22 @@ internal object VimListenerManager {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Caret listener registered only for editors that we're interested in. Used to update caret shapes when carets are
 | 
			
		||||
   * added/removed. Also tracks the expected last column location of the caret.
 | 
			
		||||
   */
 | 
			
		||||
  private object EditorCaretHandler : CaretListener {
 | 
			
		||||
    override fun caretPositionChanged(event: CaretEvent) {
 | 
			
		||||
      event.caret?.resetVimLastColumn()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun caretAdded(event: CaretEvent) {
 | 
			
		||||
      event.editor.updateCaretsVisualAttributes()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun caretRemoved(event: CaretEvent) {
 | 
			
		||||
      event.editor.updateCaretsVisualAttributes()
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  enum class SelectionSource {
 | 
			
		||||
@@ -755,6 +834,6 @@ internal object VimListenerTestObject {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
private object MouseEventsDataHolder {
 | 
			
		||||
  const val skipEvents = 3
 | 
			
		||||
  var skipNDragEvents = skipEvents
 | 
			
		||||
  const val allowedSkippedDragEvents = 3
 | 
			
		||||
  var dragEventCount = allowedSkippedDragEvents
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -12,16 +12,8 @@ import com.intellij.openapi.actionSystem.DataContext
 | 
			
		||||
import com.intellij.openapi.util.Key
 | 
			
		||||
import com.intellij.openapi.util.UserDataHolder
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.injector
 | 
			
		||||
 | 
			
		||||
internal open class IjEditorExecutionContext(override val context: DataContext) : ExecutionContext.Editor {
 | 
			
		||||
  override fun updateEditor(editor: VimEditor): ExecutionContext {
 | 
			
		||||
    return IjEditorExecutionContext(injector.executionContextManager.onEditor(editor, context.vim).ij)
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
internal class IjCaretAndEditorExecutionContext(override val context: DataContext) : IjEditorExecutionContext(context), ExecutionContext.CaretAndEditor
 | 
			
		||||
internal open class IjEditorExecutionContext(override val context: DataContext) : ExecutionContext
 | 
			
		||||
 | 
			
		||||
// This key is stored in data context when the action is started from vim
 | 
			
		||||
internal val runFromVimKey = Key.create<Boolean>("RunFromVim")
 | 
			
		||||
 
 | 
			
		||||
@@ -9,23 +9,15 @@
 | 
			
		||||
package com.maddyhome.idea.vim.newapi
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.components.Service
 | 
			
		||||
import com.intellij.openapi.editor.actionSystem.CaretSpecificDataContext
 | 
			
		||||
import com.intellij.openapi.editor.ex.util.EditorUtil
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContext
 | 
			
		||||
import com.maddyhome.idea.vim.api.ExecutionContextManagerBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.helper.EditorDataContext
 | 
			
		||||
 | 
			
		||||
@Service
 | 
			
		||||
internal class IjExecutionContextManager : ExecutionContextManagerBase() {
 | 
			
		||||
  override fun onEditor(editor: VimEditor, prevContext: ExecutionContext?): ExecutionContext.Editor {
 | 
			
		||||
    if (prevContext is ExecutionContext.CaretAndEditor) {
 | 
			
		||||
      return prevContext
 | 
			
		||||
    }
 | 
			
		||||
    return IjEditorExecutionContext(EditorDataContext.init((editor as IjVimEditor).editor, prevContext?.ij))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun onCaret(caret: VimCaret, prevContext: ExecutionContext.Editor): ExecutionContext.CaretAndEditor {
 | 
			
		||||
    return IjCaretAndEditorExecutionContext(CaretSpecificDataContext.create(prevContext.ij, caret.ij))
 | 
			
		||||
  override fun getEditorExecutionContext(editor: VimEditor): ExecutionContext {
 | 
			
		||||
    return EditorUtil.getEditorDataContext(editor.ij).vim
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,12 +10,10 @@ package com.maddyhome.idea.vim.newapi
 | 
			
		||||
 | 
			
		||||
import com.intellij.openapi.editor.RangeMarker
 | 
			
		||||
import com.maddyhome.idea.vim.common.LiveRange
 | 
			
		||||
import com.maddyhome.idea.vim.common.Offset
 | 
			
		||||
import com.maddyhome.idea.vim.common.offset
 | 
			
		||||
 | 
			
		||||
internal class IjLiveRange(val marker: RangeMarker) : LiveRange {
 | 
			
		||||
  override val startOffset: Offset
 | 
			
		||||
    get() = marker.startOffset.offset
 | 
			
		||||
  override val startOffset: Int
 | 
			
		||||
    get() = marker.startOffset
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
public val RangeMarker.vim: LiveRange
 | 
			
		||||
 
 | 
			
		||||
@@ -34,4 +34,8 @@ internal class IjNativeActionManager : NativeActionManager {
 | 
			
		||||
public val AnAction.vim: IjNativeAction
 | 
			
		||||
  get() = IjNativeAction(this)
 | 
			
		||||
 | 
			
		||||
public class IjNativeAction(override val action: AnAction) : NativeAction
 | 
			
		||||
public class IjNativeAction(override val action: AnAction) : NativeAction {
 | 
			
		||||
  override fun toString(): String {
 | 
			
		||||
    return "IjNativeAction(action=$action)"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -43,6 +43,10 @@ internal class IjVimApplication : VimApplicationBase() {
 | 
			
		||||
    return ApplicationManager.getApplication().isUnitTestMode
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isInternal(): Boolean {
 | 
			
		||||
    return ApplicationManager.getApplication().isInternal
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun postKey(stroke: KeyStroke, editor: VimEditor) {
 | 
			
		||||
    val component: Component = SwingUtilities.getAncestorOfClass(Window::class.java, editor.ij.component)
 | 
			
		||||
    val event = createKeyEvent(stroke, component)
 | 
			
		||||
@@ -54,10 +58,6 @@ internal class IjVimApplication : VimApplicationBase() {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun localEditors(): List<VimEditor> {
 | 
			
		||||
    return com.maddyhome.idea.vim.helper.localEditors().map { IjVimEditor(it) }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun runWriteCommand(editor: VimEditor, name: String?, groupId: Any?, command: Runnable) {
 | 
			
		||||
    RunnableHelper.runWriteCommand((editor as IjVimEditor).editor.project, command, name, groupId)
 | 
			
		||||
  }
 | 
			
		||||
@@ -82,6 +82,12 @@ internal class IjVimApplication : VimApplicationBase() {
 | 
			
		||||
    com.maddyhome.idea.vim.helper.runAfterGotFocus(runnable)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun isOctopusEnabled(): Boolean {
 | 
			
		||||
    val property = System.getProperty("octopus.handler") ?: "true"
 | 
			
		||||
    if (property.isBlank()) return true
 | 
			
		||||
    return property.toBoolean()
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private fun createKeyEvent(stroke: KeyStroke, component: Component): KeyEvent {
 | 
			
		||||
    return KeyEvent(
 | 
			
		||||
      component,
 | 
			
		||||
 
 | 
			
		||||
@@ -11,7 +11,6 @@ package com.maddyhome.idea.vim.newapi
 | 
			
		||||
import com.intellij.openapi.editor.Caret
 | 
			
		||||
import com.intellij.openapi.editor.LogicalPosition
 | 
			
		||||
import com.intellij.openapi.editor.VisualPosition
 | 
			
		||||
import com.intellij.openapi.util.Disposer
 | 
			
		||||
import com.maddyhome.idea.vim.api.BufferPosition
 | 
			
		||||
import com.maddyhome.idea.vim.api.CaretRegisterStorage
 | 
			
		||||
import com.maddyhome.idea.vim.api.CaretRegisterStorageBase
 | 
			
		||||
@@ -22,10 +21,7 @@ import com.maddyhome.idea.vim.api.VimCaret
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimCaretBase
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimEditor
 | 
			
		||||
import com.maddyhome.idea.vim.api.VimVisualPosition
 | 
			
		||||
import com.maddyhome.idea.vim.common.EditorLine
 | 
			
		||||
import com.maddyhome.idea.vim.common.LiveRange
 | 
			
		||||
import com.maddyhome.idea.vim.common.Offset
 | 
			
		||||
import com.maddyhome.idea.vim.common.offset
 | 
			
		||||
import com.maddyhome.idea.vim.group.visual.VisualChange
 | 
			
		||||
import com.maddyhome.idea.vim.helper.lastSelectionInfo
 | 
			
		||||
import com.maddyhome.idea.vim.helper.markStorage
 | 
			
		||||
@@ -42,14 +38,6 @@ import com.maddyhome.idea.vim.state.mode.SelectionType
 | 
			
		||||
 | 
			
		||||
internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
 | 
			
		||||
 | 
			
		||||
  init {
 | 
			
		||||
    if (caret.isValid) {
 | 
			
		||||
      Disposer.register(caret) {
 | 
			
		||||
        (registerStorage as CaretRegisterStorageBase).clearListener()
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override val registerStorage: CaretRegisterStorage
 | 
			
		||||
    get() {
 | 
			
		||||
      var storage = this.caret.registerStorage
 | 
			
		||||
@@ -87,8 +75,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
 | 
			
		||||
    }
 | 
			
		||||
  override val editor: VimEditor
 | 
			
		||||
    get() = IjVimEditor(caret.editor)
 | 
			
		||||
  override val offset: Offset
 | 
			
		||||
    get() = caret.offset.offset
 | 
			
		||||
  override val offset: Int
 | 
			
		||||
    get() = caret.offset
 | 
			
		||||
  override var vimLastColumn: Int
 | 
			
		||||
    get() = caret.vimLastColumn
 | 
			
		||||
    set(value) {
 | 
			
		||||
@@ -127,8 +115,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
 | 
			
		||||
    this.caret.moveToLogicalPosition(LogicalPosition(position.line, position.column, position.leansForward))
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun getLine(): EditorLine.Pointer {
 | 
			
		||||
    return EditorLine.Pointer.init(caret.logicalPosition.line, editor)
 | 
			
		||||
  override fun getLine(): Int {
 | 
			
		||||
    return caret.logicalPosition.line
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun hasSelection(): Boolean {
 | 
			
		||||
@@ -173,8 +161,8 @@ internal class IjVimCaret(val caret: Caret) : VimCaretBase() {
 | 
			
		||||
    return this
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun setSelection(start: Offset, end: Offset) {
 | 
			
		||||
    caret.setSelection(start.point, end.point)
 | 
			
		||||
  override fun setSelection(start: Int, end: Int) {
 | 
			
		||||
    caret.setSelection(start, end)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override fun removeSelection() {
 | 
			
		||||
 
 | 
			
		||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user